mirror of
https://github.com/unraid/api.git
synced 2025-12-31 13:39:52 -06:00
feat: generate key one time
This commit is contained in:
@@ -9,6 +9,7 @@ wanaccess="yes"
|
||||
wanport="8443"
|
||||
upnpEnabled="no"
|
||||
apikey="_______________________BIG_API_KEY_HERE_________________________"
|
||||
localApiKey="426b62b4d51e441fa97a93dfa1259920390a6eb61bd8675db0caa18dd0e414e9"
|
||||
email="test@example.com"
|
||||
username="zspearmint"
|
||||
avatar="https://via.placeholder.com/200"
|
||||
|
||||
@@ -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
api/dev/keys/d166bf8b-3615-444a-8932-c460b2132ba3.json
Normal file
10
api/dev/keys/d166bf8b-3615-444a-8932-c460b2132ba3.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"createdAt": "2024-12-19T16:49:56.848Z",
|
||||
"description": "API key for Connect user",
|
||||
"id": "d166bf8b-3615-444a-8932-c460b2132ba3",
|
||||
"key": "3a4e2332891e879d2ac8c3f25ef03a7b54f70b62cd6c5a08a86189cdd19ba203",
|
||||
"name": "Connect",
|
||||
"roles": [
|
||||
"admin"
|
||||
]
|
||||
}
|
||||
@@ -9,6 +9,7 @@ wanaccess="yes"
|
||||
wanport="8443"
|
||||
upnpEnabled="no"
|
||||
apikey="_______________________BIG_API_KEY_HERE_________________________"
|
||||
localApiKey="3a4e2332891e879d2ac8c3f25ef03a7b54f70b62cd6c5a08a86189cdd19ba203"
|
||||
email="test@example.com"
|
||||
username="zspearmint"
|
||||
avatar="https://via.placeholder.com/200"
|
||||
|
||||
@@ -1187,12 +1187,12 @@ export type RemoveRoleFromApiKeyInput = {
|
||||
|
||||
/** Available resources for permissions */
|
||||
export enum Resource {
|
||||
API_KEY = 'api_key',
|
||||
APIKEY = 'apikey',
|
||||
ARRAY = 'array',
|
||||
CLOUD = 'cloud',
|
||||
CONFIG = 'config',
|
||||
CONNECT = 'connect',
|
||||
CRASH_REPORTING_ENABLED = 'crash_reporting_enabled',
|
||||
CONNECT__REMOTE_ACCESS = 'connect__remote_access',
|
||||
CUSTOMIZATIONS = 'customizations',
|
||||
DASHBOARD = 'dashboard',
|
||||
DISK = 'disk',
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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';
|
||||
@@ -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');
|
||||
@@ -60,4 +55,4 @@ export const connectSignIn = async (input: ConnectSignInInput): Promise<boolean>
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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!
|
||||
|
||||
42
api/src/graphql/schema/types/auth/roles.graphql
Normal file
42
api/src/graphql/schema/types/auth/roles.graphql
Normal file
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
Available resources for permissions
|
||||
"""
|
||||
enum Resource {
|
||||
apikey
|
||||
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
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
34
api/src/mothership/utils/create-local-connect-api-key.ts
Normal file
34
api/src/mothership/utils/create-local-connect-api-key.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
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 service = 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 service.findById(remote.localApiKey)))) {
|
||||
minigraphLogger.debug('Creating local API key for Connect');
|
||||
// Create local API key
|
||||
const apiKeyService = new ApiKeyService();
|
||||
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');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import type { TypedAddListener, TypedStartListening } from '@reduxjs/toolkit';
|
||||
import { addListener, createListenerMiddleware } from '@reduxjs/toolkit';
|
||||
|
||||
@@ -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>;
|
||||
@@ -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')();
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { logger } from '@app/core/log';
|
||||
import { Role } from '@app/graphql/generated/api/types';
|
||||
import { getters } from '@app/store/index';
|
||||
import { startAppListening } from '@app/store/listeners/listener-middleware';
|
||||
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 enableLocalApiKeyListener = () =>
|
||||
startAppListening({
|
||||
predicate(_, currentState) {
|
||||
return (
|
||||
currentState.config.status === FileLoadStatus.LOADED &&
|
||||
currentState.config.remote.apikey !== '' &&
|
||||
currentState.config.remote.localApiKey === ''
|
||||
);
|
||||
},
|
||||
async effect(_, { dispatch }) {
|
||||
try {
|
||||
const { remote } = getters.config();
|
||||
const { apikey, username } = remote;
|
||||
// Validate the API key with the key server
|
||||
const apiKeyService = new ApiKeyService();
|
||||
// Create local API key
|
||||
const localApiKey = await apiKeyService.create(
|
||||
`LOCAL_KEY_${(username as string).toUpperCase()}`,
|
||||
`Local API key for Connect user ${username}`,
|
||||
[Role.ADMIN]
|
||||
);
|
||||
|
||||
if (localApiKey?.key) {
|
||||
dispatch(
|
||||
updateUserConfig({
|
||||
remote: {
|
||||
localApiKey: localApiKey.key,
|
||||
},
|
||||
})
|
||||
);
|
||||
} else {
|
||||
throw new Error('Failed to create local API key - no key returned');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to create local API key', error);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -3,20 +3,29 @@ import crypto from 'crypto';
|
||||
import { readdir, readFile, writeFile } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
|
||||
|
||||
|
||||
import { ensureDir } from 'fs-extra';
|
||||
import { GraphQLError } from 'graphql';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { ZodError } from 'zod';
|
||||
|
||||
|
||||
|
||||
import { ApiKeySchema, ApiKeyWithSecretSchema } from '@app/graphql/generated/api/operations';
|
||||
import { ApiKey, ApiKeyWithSecret, Role, UserAccount } from '@app/graphql/generated/api/types';
|
||||
import { getters } from '@app/store';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class ApiKeyService implements OnModuleInit {
|
||||
private readonly logger = new Logger(ApiKeyService.name);
|
||||
protected readonly basePath: string;
|
||||
protected readonly keyFile: (id: string) => string;
|
||||
protected memoryApiKeys = new Map<string, ApiKeyWithSecret>();
|
||||
private static readonly validRoles: Set<Role> = new Set(Object.values(Role));
|
||||
|
||||
constructor() {
|
||||
@@ -34,6 +43,8 @@ export class ApiKeyService implements OnModuleInit {
|
||||
throw new GraphQLError('Failed to initialize API key storage');
|
||||
}
|
||||
this.logger.verbose(`Using API key base path: ${this.basePath}`);
|
||||
|
||||
// @todo setup file watch to reload keys
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
@@ -41,13 +52,18 @@ export class ApiKeyService implements OnModuleInit {
|
||||
}
|
||||
|
||||
private sanitizeName(name: string): string {
|
||||
return name.replace(/[^a-zA-Z0-9-_]/g, '_').toUpperCase();
|
||||
if (/^[\p{L}\p{N} ]+$/u.test(name)) {
|
||||
return name;
|
||||
} else {
|
||||
throw new GraphQLError('API key name must be alphanumeric and can only contain spaces');
|
||||
}
|
||||
}
|
||||
|
||||
async create(
|
||||
name: string,
|
||||
description: string | undefined,
|
||||
roles: Role[]
|
||||
roles: Role[],
|
||||
overwrite: boolean = false
|
||||
): Promise<ApiKeyWithSecret> {
|
||||
const trimmedName = name?.trim();
|
||||
const sanitizedName = this.sanitizeName(trimmedName);
|
||||
@@ -63,19 +79,24 @@ export class ApiKeyService implements OnModuleInit {
|
||||
if (roles.some((role) => !ApiKeyService.validRoles.has(role))) {
|
||||
throw new GraphQLError('Invalid role specified');
|
||||
}
|
||||
|
||||
const apiKey: ApiKeyWithSecret = {
|
||||
const apiKey: Partial<ApiKeyWithSecret> = (await this.findByField('name', sanitizedName)) ?? {
|
||||
id: uuidv4(),
|
||||
key: this.generateApiKey(),
|
||||
name: sanitizedName,
|
||||
description,
|
||||
roles,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
await this.saveApiKey(apiKey);
|
||||
if (!overwrite && apiKey.createdAt) {
|
||||
throw new GraphQLError('API key name already exists, use overwrite flag to update');
|
||||
}
|
||||
|
||||
return apiKey;
|
||||
apiKey.description = description;
|
||||
apiKey.roles = roles;
|
||||
// Update createdAt date
|
||||
apiKey.createdAt = new Date().toISOString();
|
||||
|
||||
await this.saveApiKey(apiKey as ApiKeyWithSecret);
|
||||
|
||||
return apiKey as ApiKeyWithSecret;
|
||||
}
|
||||
|
||||
async findAll(): Promise<ApiKey[]> {
|
||||
@@ -162,12 +183,11 @@ export class ApiKeyService implements OnModuleInit {
|
||||
}
|
||||
}
|
||||
|
||||
async findByKey(key: string): Promise<ApiKeyWithSecret | null> {
|
||||
if (!key) return null;
|
||||
async findByField(field: keyof ApiKeyWithSecret, value: string): Promise<ApiKeyWithSecret | null> {
|
||||
if (!value) return null;
|
||||
|
||||
try {
|
||||
const files = await readdir(this.basePath);
|
||||
const keyBuffer1 = Buffer.from(key);
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.json')) continue;
|
||||
@@ -187,14 +207,14 @@ export class ApiKeyService implements OnModuleInit {
|
||||
}
|
||||
|
||||
const apiKey = ApiKeyWithSecretSchema().parse(parsedContent);
|
||||
const keyBuffer2 = Buffer.from(apiKey.key);
|
||||
|
||||
if (
|
||||
keyBuffer1.length === keyBuffer2.length &&
|
||||
crypto.timingSafeEqual(keyBuffer1, keyBuffer2)
|
||||
) {
|
||||
if (field === 'key') {
|
||||
if (crypto.timingSafeEqual(Buffer.from(apiKey[field]), Buffer.from(value))) {
|
||||
apiKey.roles = apiKey.roles.map((role) => role || Role.GUEST);
|
||||
return apiKey;
|
||||
}
|
||||
} else if (apiKey[field] === value) {
|
||||
apiKey.roles = apiKey.roles.map((role) => role || Role.GUEST);
|
||||
|
||||
return apiKey;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -203,7 +223,6 @@ export class ApiKeyService implements OnModuleInit {
|
||||
}
|
||||
|
||||
this.logger.error(`Error processing API key file ${file}: ${error}`);
|
||||
throw new GraphQLError('Authentication system error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +237,10 @@ export class ApiKeyService implements OnModuleInit {
|
||||
}
|
||||
}
|
||||
|
||||
async findByKey(key: string): Promise<ApiKeyWithSecret | null> {
|
||||
return this.findByField('key', key);
|
||||
}
|
||||
|
||||
async findOneByKey(apiKey: string): Promise<UserAccount | null> {
|
||||
try {
|
||||
const key = await this.findByKey(apiKey);
|
||||
@@ -247,11 +270,22 @@ export class ApiKeyService implements OnModuleInit {
|
||||
return crypto.randomBytes(32).toString('hex');
|
||||
}
|
||||
|
||||
public async createLocalConnectApiKey(): Promise<ApiKeyWithSecret> {
|
||||
return await this.create('Connect', 'API key for Connect user', [Role.ADMIN], true);
|
||||
}
|
||||
|
||||
public async saveApiKey(apiKey: ApiKeyWithSecret): Promise<void> {
|
||||
try {
|
||||
const validatedApiKey = ApiKeyWithSecretSchema().parse(apiKey);
|
||||
|
||||
await writeFile(this.keyFile(validatedApiKey.id), JSON.stringify(validatedApiKey, null, 2));
|
||||
const sortedApiKey = Object.keys(validatedApiKey)
|
||||
.sort()
|
||||
.reduce((acc, key) => {
|
||||
acc[key] = validatedApiKey[key];
|
||||
return acc;
|
||||
}, {} as ApiKeyWithSecret);
|
||||
|
||||
await writeFile(this.keyFile(validatedApiKey.id), JSON.stringify(sortedApiKey, null, 2));
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof ZodError) {
|
||||
this.logger.error('Invalid API key structure', error.errors);
|
||||
@@ -270,4 +304,4 @@ export class ApiKeyService implements OnModuleInit {
|
||||
keyFile: this.keyFile,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,63 +6,14 @@ export const BASE_POLICY = `
|
||||
# Admin permissions
|
||||
p, ${Role.ADMIN}, *, *, *
|
||||
|
||||
# UPC permissions for API keys
|
||||
p, ${Role.UPC}, ${Resource.API_KEY}, ${AuthAction.CREATE_ANY}
|
||||
p, ${Role.UPC}, ${Resource.API_KEY}, ${AuthAction.UPDATE_ANY}
|
||||
|
||||
# UPC permissions
|
||||
p, ${Role.UPC}, ${Resource.CLOUD}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, ${Resource.CONFIG}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, crash-reporting-enabled, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, ${Resource.CUSTOMIZATIONS}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, ${Resource.DISK}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, ${Resource.DISPLAY}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, ${Resource.FLASH}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, ${Resource.INFO}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, ${Resource.LOGS}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, ${Resource.OS}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, ${Resource.OWNER}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, ${Resource.REGISTRATION}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, ${Resource.SERVERS}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, ${Resource.VARS}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, ${Resource.CONFIG}, ${AuthAction.UPDATE_ANY}
|
||||
p, ${Role.UPC}, ${Resource.CONNECT}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, ${Resource.CONNECT}, ${AuthAction.UPDATE_ANY}
|
||||
p, ${Role.UPC}, ${Resource.CONNECT}, ${AuthAction.UPDATE_OWN}
|
||||
p, ${Role.UPC}, ${Resource.NOTIFICATIONS}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.UPC}, ${Resource.NOTIFICATIONS}, ${AuthAction.UPDATE_ANY}
|
||||
|
||||
# My Servers permissions
|
||||
p, ${Role.MY_SERVERS}, ${Resource.ARRAY}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, ${Resource.CONFIG}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, ${Resource.CONNECT}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, connect/dynamic-remote-access, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, connect/dynamic-remote-access, ${AuthAction.UPDATE_ANY}
|
||||
p, ${Role.MY_SERVERS}, ${Resource.CUSTOMIZATIONS}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, ${Resource.DASHBOARD}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, ${Resource.DISPLAY}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, docker/container, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, ${Resource.DOCKER}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, ${Resource.INFO}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, ${Resource.LOGS}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, ${Resource.NETWORK}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, ${Resource.NOTIFICATIONS}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, ${Resource.SERVICES}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, ${Resource.VARS}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, ${Resource.VMS}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, vms/domain, ${AuthAction.READ_ANY}
|
||||
p, ${Role.MY_SERVERS}, unraid-version, ${AuthAction.READ_ANY}
|
||||
|
||||
# Notifier permissions
|
||||
p, ${Role.NOTIFIER}, ${Resource.NOTIFICATIONS}, ${AuthAction.CREATE_OWN}
|
||||
# Connect Permissions
|
||||
p, ${Role.CONNECT}, *, ${AuthAction.READ_ANY}
|
||||
p, ${Role.CONNECT}, ${Resource.CONNECT__REMOTE_ACCESS}, ${AuthAction.UPDATE_ANY}
|
||||
|
||||
# Guest permissions
|
||||
p, ${Role.GUEST}, ${Resource.ME}, ${AuthAction.READ_ANY}
|
||||
p, ${Role.GUEST}, ${Resource.WELCOME}, ${AuthAction.READ_ANY}
|
||||
|
||||
# Role inheritance
|
||||
g, ${Role.ADMIN}, ${Role.GUEST}
|
||||
g, ${Role.UPC}, ${Role.GUEST}
|
||||
g, ${Role.MY_SERVERS}, ${Role.GUEST}
|
||||
g, ${Role.NOTIFIER}, ${Role.GUEST}
|
||||
g, ${Role.CONNECT}, ${Role.GUEST}
|
||||
`;
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
DynamicRemoteAccessStatus,
|
||||
EnableDynamicRemoteAccessInput,
|
||||
} from '@app/graphql/generated/api/types';
|
||||
import { ConnectResolvers, DynamicRemoteAccessType } from '@app/graphql/generated/api/types';
|
||||
import { ConnectResolvers, DynamicRemoteAccessType, Resource } from '@app/graphql/generated/api/types';
|
||||
import { RemoteAccessController } from '@app/remoteAccess/remote-access-controller';
|
||||
import { store } from '@app/store/index';
|
||||
import { setAllowedRemoteAccessUrl } from '@app/store/modules/dynamic-remote-access';
|
||||
@@ -20,7 +20,7 @@ export class ConnectResolver implements ConnectResolvers {
|
||||
@Query('connect')
|
||||
@UsePermissions({
|
||||
action: AuthActionVerb.READ,
|
||||
resource: 'connect/dynamic-remote-access',
|
||||
resource: Resource.CONNECT,
|
||||
possession: AuthPossession.ANY,
|
||||
})
|
||||
public connect() {
|
||||
@@ -46,7 +46,7 @@ export class ConnectResolver implements ConnectResolvers {
|
||||
@Mutation()
|
||||
@UsePermissions({
|
||||
action: AuthActionVerb.UPDATE,
|
||||
resource: 'connect/dynamic-remote-access',
|
||||
resource: Resource.CONNECT__REMOTE_ACCESS,
|
||||
possession: AuthPossession.ANY,
|
||||
})
|
||||
public async enableDynamicRemoteAccess(
|
||||
|
||||
Reference in New Issue
Block a user