Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/dev/Unraid.net/myservers.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ wanaccess="yes"
wanport="8443"
upnpEnabled="no"
apikey="_______________________BIG_API_KEY_HERE_________________________"
localApiKey="_______________________LOCAL_API_KEY_HERE_________________________"
email="test@example.com"
username="zspearmint"
avatar="https://via.placeholder.com/200"
Expand Down
2 changes: 1 addition & 1 deletion api/dev/keys/10f356da-1e9e-43b8-9028-a26a645539a6.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"key": "73717ca0-8c15-40b9-bcca-8d85656d1438",
"name": "Test API Key",
"description": "Testing API key creation",
"roles": ["guest", "upc"],
"roles": ["guest", "connect"],
"createdAt": "2024-10-29T19:59:12.569Z"
}
10 changes: 10 additions & 0 deletions api/dev/keys/d166bf8b-3615-444a-8932-c460b2132ba3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"createdAt": "2024-12-20T15:05:55.336Z",
"description": "API key for Connect user",
"id": "d166bf8b-3615-444a-8932-c460b2132ba3",
"key": "_______________________LOCAL_API_KEY_HERE_________________________",
"name": "Connect",
"roles": [
"connect"
]
}
3 changes: 2 additions & 1 deletion api/dev/states/myservers.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ wanaccess="yes"
wanport="8443"
upnpEnabled="no"
apikey="_______________________BIG_API_KEY_HERE_________________________"
localApiKey="_______________________LOCAL_API_KEY_HERE_________________________"
email="test@example.com"
username="zspearmint"
avatar="https://via.placeholder.com/200"
Expand All @@ -21,4 +22,4 @@ dynamicRemoteAccessType="DISABLED"
[upc]
apikey="unupc_fab6ff6ffe51040595c6d9ffb63a353ba16cc2ad7d93f813a2e80a5810"
[connectionStatus]
minigraph="ERROR_RETRYING"
minigraph="PRE_INIT"
12 changes: 6 additions & 6 deletions api/src/__test__/graphql/resolvers/subscription/network.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ test('getUrlForServer - field exists, ssl yes, port empty', () => {
expect(result).toMatchInlineSnapshot('"https://192.168.1.1/"');
});

test('getUrlForServer - field exists, ssl auto', () => {
test('getUrlForServer - field exists, ssl auto', async () => {
const getResult = async () => getUrlForServer({
nginx: { lanIp: '192.168.1.1', sslEnabled: true, sslMode: 'auto', httpPort: 123, httpsPort: 445 } as const as Nginx,
field: 'lanIp',
});
void expect(getResult).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Cannot get IP Based URL for field: "lanIp" SSL mode auto]`);
await expect(getResult).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Cannot get IP Based URL for field: "lanIp" SSL mode auto]`);
});

test('getUrlForServer - field does not exist, ssl disabled', () => {
test('getUrlForServer - field does not exist, ssl disabled', async () => {
const getResult = async () => getUrlForServer(
{
nginx: { lanIp: '192.168.1.1', sslEnabled: false, sslMode: 'no' } as const as Nginx,
Expand All @@ -76,7 +76,7 @@ test('getUrlForServer - field does not exist, ssl disabled', () => {
// @ts-expect-error Field doesn't exist
field: 'idontexist',
});
void expect(getResult).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: IP URL Resolver: Could not resolve any access URL for field: "idontexist", is FQDN?: false]`);
await expect(getResult).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: IP URL Resolver: Could not resolve any access URL for field: "idontexist", is FQDN?: false]`);
});

test('getUrlForServer - FQDN - field exists, port non-empty', () => {
Expand Down Expand Up @@ -104,13 +104,13 @@ test.each([
expect(result.toString()).toBe('https://my-fqdn.unraid.net/');
});

test('getUrlForServer - field does not exist, ssl disabled', () => {
test('getUrlForServer - field does not exist, ssl disabled', async () => {
const getResult = async () => getUrlForServer({ nginx:
{ lanFqdn: 'my-fqdn.unraid.net' } as const as Nginx,
ports: { portSsl: '', port: '', defaultUrl: new URL('https://my-default-url.unraid.net') },
// @ts-expect-error Field doesn't exist
field: 'idontexist' });
void expect(getResult).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: IP URL Resolver: Could not resolve any access URL for field: "idontexist", is FQDN?: false]`);
await expect(getResult).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: IP URL Resolver: Could not resolve any access URL for field: "idontexist", is FQDN?: false]`);
});

test('integration test, loading nginx ini and generating all URLs', async () => {
Expand Down
4 changes: 2 additions & 2 deletions api/src/__test__/store/modules/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ test('After init returns values from cfg file for all fields', async () => {
dynamicRemoteAccessType: 'DISABLED',
email: 'test@example.com',
idtoken: '',
localApiKey: '',
localApiKey: '_______________________LOCAL_API_KEY_HERE_________________________',
refreshtoken: '',
regWizTime: '1611175408732_0951-1653-3509-FBA155FA23C0',
upnpEnabled: 'no',
Expand Down Expand Up @@ -139,7 +139,7 @@ test('updateUserConfig merges in changes to current state', async () => {
dynamicRemoteAccessType: 'DISABLED',
email: 'test@example.com',
idtoken: '',
localApiKey: '',
localApiKey: '_______________________LOCAL_API_KEY_HERE_________________________',
refreshtoken: '',
regWizTime: '1611175408732_0951-1653-3509-FBA155FA23C0',
upnpEnabled: 'no',
Expand Down
8 changes: 3 additions & 5 deletions api/src/graphql/generated/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1192,7 +1192,7 @@ export enum Resource {
CLOUD = 'cloud',
CONFIG = 'config',
CONNECT = 'connect',
CRASH_REPORTING_ENABLED = 'crash_reporting_enabled',
CONNECT__REMOTE_ACCESS = 'connect__remote_access',
CUSTOMIZATIONS = 'customizations',
DASHBOARD = 'dashboard',
DISK = 'disk',
Expand Down Expand Up @@ -1220,10 +1220,8 @@ export enum Resource {
/** Available roles for API keys and users */
export enum Role {
ADMIN = 'admin',
GUEST = 'guest',
MY_SERVERS = 'my_servers',
NOTIFIER = 'notifier',
UPC = 'upc'
CONNECT = 'connect',
GUEST = 'guest'
}

export type Server = {
Expand Down
9 changes: 2 additions & 7 deletions api/src/graphql/resolvers/mutation/connect/connect-sign-in.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { decodeJwt } from 'jose';

import type { ConnectSignInInput } from '@app/graphql/generated/api/types';
import { Role } from '@app/graphql/generated/api/types';
import { getters, store } from '@app/store/index';
import { loginUser } from '@app/store/modules/config';
import { FileLoadStatus } from '@app/store/types';
Expand Down Expand Up @@ -30,11 +29,7 @@ export const connectSignIn = async (input: ConnectSignInInput): Promise<boolean>
if (localApiKeyFromConfig == '') {
const apiKeyService = new ApiKeyService();
// Create local API key
const localApiKey = await apiKeyService.create(
`LOCAL_KEY_${userInfo.preferred_username.toUpperCase()}`,
`Local API key for Connect user ${userInfo.email}`,
[Role.ADMIN]
);
const localApiKey = await apiKeyService.createLocalConnectApiKey();

if (!localApiKey?.key) {
throw new Error('Failed to create local API key');
Expand All @@ -60,4 +55,4 @@ export const connectSignIn = async (input: ConnectSignInInput): Promise<boolean>
} else {
return false;
}
};
};
45 changes: 0 additions & 45 deletions api/src/graphql/schema/types/auth/auth.graphql
Original file line number Diff line number Diff line change
@@ -1,48 +1,3 @@
"""
Available resources for permissions
"""
enum Resource {
api_key
cloud
config
crash_reporting_enabled
customizations
disk
display
flash
info
logs
online
os
owner
permission
registration
servers
share
vars
connect
notifications
array
dashboard
docker
network
services
vms
me
welcome
}

"""
Available roles for API keys and users
"""
enum Role {
admin
upc
my_servers
notifier
guest
}

type ApiKey {
id: ID!
name: String!
Expand Down
42 changes: 42 additions & 0 deletions api/src/graphql/schema/types/auth/roles.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
Available resources for permissions
"""
enum Resource {
api_key
array
cloud
config
connect
connect__remote_access
customizations
dashboard
disk
display
docker
flash
info
logs
me
network
notifications
online
os
owner
permission
registration
servers
services
share
vars
vms
welcome
}

"""
Available roles for API keys and users
"""
enum Role {
admin
connect
guest
}
3 changes: 3 additions & 0 deletions api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { setupRegistrationKeyWatch } from '@app/store/watch/registration-watch';
import { StateManager } from '@app/store/watch/state-watch';
import { setupVarRunWatch } from '@app/store/watch/var-run-watch';
import { bootstrapNestServer } from '@app/unraid-api/main';
import { createLocalApiKeyForConnectIfNecessary } from '@app/mothership/utils/create-local-connect-api-key';

import { setupNewMothershipSubscription } from './mothership/subscribe-to-mothership';

Expand Down Expand Up @@ -87,6 +88,8 @@ try {
// Start listening to dynamix config file changes
setupDynamixConfigWatch();

await createLocalApiKeyForConnectIfNecessary();

// Disabled until we need the access token to work
// TokenRefresh.init();

Expand Down
33 changes: 33 additions & 0 deletions api/src/mothership/utils/create-local-connect-api-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { minigraphLogger } from '@app/core/log';
import { getters, store } from '@app/store/index';
import { updateUserConfig } from '@app/store/modules/config';
import { FileLoadStatus } from '@app/store/types';
import { ApiKeyService } from '@app/unraid-api/auth/api-key.service';

export const createLocalApiKeyForConnectIfNecessary = async () => {
if (getters.config().status !== FileLoadStatus.LOADED) {
minigraphLogger.error('Config file not loaded, cannot create local API key');
return;
}

const { remote } = getters.config();
const apiKeyService = new ApiKeyService();
// If the remote API Key is set and the local key is either not set or not found on disk, create a key
if (remote.apikey && (!remote.localApiKey || !(await apiKeyService.findByKey(remote.localApiKey)))) {
minigraphLogger.debug('Creating local API key for Connect');
// Create local API key
const localApiKey = await apiKeyService.createLocalConnectApiKey();

if (localApiKey?.key) {
store.dispatch(
updateUserConfig({
remote: {
localApiKey: localApiKey.key,
},
})
);
} else {
throw new Error('Failed to create local API key - no key returned');
}
}
};
10 changes: 3 additions & 7 deletions api/src/store/listeners/listener-middleware.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'reflect-metadata';

import type { TypedAddListener, TypedStartListening } from '@reduxjs/toolkit';
import { addListener, createListenerMiddleware } from '@reduxjs/toolkit';

Expand All @@ -6,17 +8,12 @@ import { enableArrayEventListener } from '@app/store/listeners/array-event-liste
import { enableConfigFileListener } from '@app/store/listeners/config-listener';
import { enableDynamicRemoteAccessListener } from '@app/store/listeners/dynamic-remote-access-listener';
import { enableMothershipJobsListener } from '@app/store/listeners/mothership-subscription-listener';
import { enableNotificationPathListener } from '@app/store/listeners/notification-path-listener';
import { enableServerStateListener } from '@app/store/listeners/server-state-listener';
import { enableUpnpListener } from '@app/store/listeners/upnp-listener';
import { enableVersionListener } from '@app/store/listeners/version-listener';
import { enableWanAccessChangeListener } from '@app/store/listeners/wan-access-change-listener';

import 'reflect-metadata';

import { enableNotificationPathListener } from '@app/store/listeners/notification-path-listener';

import { enableLocalApiKeyListener } from './local-api-key-listener';

export const listenerMiddleware = createListenerMiddleware();

export type AppStartListening = TypedStartListening<RootState, AppDispatch>;
Expand All @@ -29,7 +26,6 @@ export const addAppListener = addListener as TypedAddListener<RootState, AppDisp

export const startMiddlewareListeners = () => {
// Begin listening for events
enableLocalApiKeyListener();
enableMothershipJobsListener();
enableConfigFileListener('flash')();
enableConfigFileListener('memory')();
Expand Down
46 changes: 0 additions & 46 deletions api/src/store/listeners/local-api-key-listener.ts

This file was deleted.

Loading
Loading