feat: session issues

This commit is contained in:
Eli Bosley
2025-01-30 13:32:36 -05:00
parent 1ecac5ee4e
commit 8026ef53e8
10 changed files with 31 additions and 14 deletions

View File

@@ -19,5 +19,5 @@ dynamicRemoteAccessType="DISABLED"
ssoSubIds="" ssoSubIds=""
allowedOrigins="/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, http://localhost:8080, https://localhost:4443, https://tower.local:4443, https://192.168.1.150:4443, https://tower:4443, https://192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net:4443, https://85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net:8443, https://10-252-0-1.hash.myunraid.net:4443, https://10-252-1-1.hash.myunraid.net:4443, https://10-253-3-1.hash.myunraid.net:4443, https://10-253-4-1.hash.myunraid.net:4443, https://10-253-5-1.hash.myunraid.net:4443, https://10-100-0-1.hash.myunraid.net:4443, https://10-100-0-2.hash.myunraid.net:4443, https://10-123-1-2.hash.myunraid.net:4443, https://221-123-121-112.hash.myunraid.net:4443, https://google.com, https://test.com, https://connect.myunraid.net, https://connect-staging.myunraid.net, https://dev-my.myunraid.net:4000, https://studio.apollographql.com" allowedOrigins="/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, http://localhost:8080, https://localhost:4443, https://tower.local:4443, https://192.168.1.150:4443, https://tower:4443, https://192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net:4443, https://85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net:8443, https://10-252-0-1.hash.myunraid.net:4443, https://10-252-1-1.hash.myunraid.net:4443, https://10-253-3-1.hash.myunraid.net:4443, https://10-253-4-1.hash.myunraid.net:4443, https://10-253-5-1.hash.myunraid.net:4443, https://10-100-0-1.hash.myunraid.net:4443, https://10-100-0-2.hash.myunraid.net:4443, https://10-123-1-2.hash.myunraid.net:4443, https://221-123-121-112.hash.myunraid.net:4443, https://google.com, https://test.com, https://connect.myunraid.net, https://connect-staging.myunraid.net, https://dev-my.myunraid.net:4000, https://studio.apollographql.com"
[connectionStatus] [connectionStatus]
minigraph="PRE_INIT" minigraph="ERROR_RETRYING"
upnpStatus="" upnpStatus=""

View File

@@ -323,7 +323,9 @@ export function ContainerPortSchema(): z.ZodObject<Properties<ContainerPort>> {
export function CreateApiKeyInputSchema(): z.ZodObject<Properties<CreateApiKeyInput>> { export function CreateApiKeyInputSchema(): z.ZodObject<Properties<CreateApiKeyInput>> {
return z.object({ return z.object({
description: z.string().nullish(), description: z.string().nullish(),
memory: z.boolean().nullish(),
name: z.string(), name: z.string(),
overwrite: z.boolean().nullish(),
permissions: z.array(z.lazy(() => AddPermissionInputSchema())).nullish(), permissions: z.array(z.lazy(() => AddPermissionInputSchema())).nullish(),
roles: z.array(RoleSchema).nullish() roles: z.array(RoleSchema).nullish()
}) })

View File

@@ -347,7 +347,11 @@ export enum ContainerState {
export type CreateApiKeyInput = { export type CreateApiKeyInput = {
description?: InputMaybe<Scalars['String']['input']>; description?: InputMaybe<Scalars['String']['input']>;
/** Whether to create the key in memory only (true), or on disk (false) - memory only keys will not persist through reboots of the API */
memory?: InputMaybe<Scalars['Boolean']['input']>;
name: Scalars['String']['input']; name: Scalars['String']['input'];
/** This will replace the existing key if one already exists with the same name, otherwise returns the existing key */
overwrite?: InputMaybe<Scalars['Boolean']['input']>;
permissions?: InputMaybe<Array<AddPermissionInput>>; permissions?: InputMaybe<Array<AddPermissionInput>>;
roles?: InputMaybe<Array<Role>>; roles?: InputMaybe<Array<Role>>;
}; };

View File

@@ -27,6 +27,10 @@ input CreateApiKeyInput {
description: String description: String
roles: [Role!] roles: [Role!]
permissions: [AddPermissionInput!] permissions: [AddPermissionInput!]
""" This will replace the existing key if one already exists with the same name, otherwise returns the existing key """
overwrite: Boolean
""" Whether to create the key in memory only (true), or on disk (false) - memory only keys will not persist through reboots of the API """
memory: Boolean
} }
input AddPermissionInput { input AddPermissionInput {

View File

@@ -103,12 +103,14 @@ export class ApiKeyService implements OnModuleInit {
roles, roles,
permissions, permissions,
overwrite = false, overwrite = false,
memory = false,
}: { }: {
name: string; name: string;
description: string | undefined; description: string | undefined;
roles?: Role[]; roles?: Role[];
permissions?: Permission[] | AddPermissionInput[]; permissions?: Permission[] | AddPermissionInput[];
overwrite?: boolean; overwrite?: boolean;
memory?: boolean;
}): Promise<ApiKeyWithSecret> { }): Promise<ApiKeyWithSecret> {
const trimmedName = name?.trim(); const trimmedName = name?.trim();
const sanitizedName = this.sanitizeName(trimmedName); const sanitizedName = this.sanitizeName(trimmedName);
@@ -127,7 +129,7 @@ export class ApiKeyService implements OnModuleInit {
const existingKey = this.findByField('name', sanitizedName); const existingKey = this.findByField('name', sanitizedName);
if (!overwrite && existingKey) { if (!overwrite && existingKey) {
throw new GraphQLError('API key name already exists, use overwrite flag to update'); return existingKey;
} }
const apiKey: Partial<ApiKeyWithSecret> = { const apiKey: Partial<ApiKeyWithSecret> = {
id: uuidv4(), id: uuidv4(),
@@ -142,7 +144,11 @@ export class ApiKeyService implements OnModuleInit {
// Update createdAt date // Update createdAt date
apiKey.createdAt = new Date().toISOString(); apiKey.createdAt = new Date().toISOString();
await this.saveApiKey(apiKey as ApiKeyWithSecret); if (memory) {
this.memoryApiKeys.push(apiKey as ApiKeyWithSecret)
} else {
await this.saveApiKey(apiKey as ApiKeyWithSecret);
}
return apiKey as ApiKeyWithSecret; return apiKey as ApiKeyWithSecret;
} }

View File

@@ -8,10 +8,10 @@ import { ApiKeyService } from '@app/unraid-api/auth/api-key.service';
import { AuthService } from '@app/unraid-api/auth/auth.service'; import { AuthService } from '@app/unraid-api/auth/auth.service';
import { CookieService } from '@app/unraid-api/auth/cookie.service'; import { CookieService } from '@app/unraid-api/auth/cookie.service';
import { AuthResolver } from './auth.resolver'; import { ApiKeyResolver } from './api-key.resolver';
describe('AuthResolver', () => { describe('ApiKeyResolver', () => {
let resolver: AuthResolver; let resolver: ApiKeyResolver;
let authService: AuthService; let authService: AuthService;
let apiKeyService: ApiKeyService; let apiKeyService: ApiKeyService;
let authzService: AuthZService; let authzService: AuthZService;
@@ -45,7 +45,7 @@ describe('AuthResolver', () => {
authzService = new AuthZService(enforcer); authzService = new AuthZService(enforcer);
cookieService = new CookieService(); cookieService = new CookieService();
authService = new AuthService(cookieService, apiKeyService, authzService); authService = new AuthService(cookieService, apiKeyService, authzService);
resolver = new AuthResolver(authService, apiKeyService); resolver = new ApiKeyResolver(authService, apiKeyService);
}); });
describe('apiKeys', () => { describe('apiKeys', () => {

View File

@@ -6,7 +6,6 @@ import { AuthActionVerb, AuthPossession, UsePermissions } from 'nest-authz';
import type { import type {
AddRoleForApiKeyInput, AddRoleForApiKeyInput,
AddRoleForUserInput,
ApiKey, ApiKey,
ApiKeyWithSecret, ApiKeyWithSecret,
CreateApiKeyInput, CreateApiKeyInput,
@@ -17,10 +16,10 @@ import { ApiKeyService } from '@app/unraid-api/auth/api-key.service';
import { GraphqlAuthGuard } from '@app/unraid-api/auth/auth.guard'; import { GraphqlAuthGuard } from '@app/unraid-api/auth/auth.guard';
import { AuthService } from '@app/unraid-api/auth/auth.service'; import { AuthService } from '@app/unraid-api/auth/auth.service';
@Resolver('Auth') @Resolver('ApiKey')
@UseGuards(GraphqlAuthGuard) @UseGuards(GraphqlAuthGuard)
@Throttle({ default: { limit: 100, ttl: 60000 } }) // 100 requests per minute @Throttle({ default: { limit: 100, ttl: 60000 } }) // 100 requests per minute
export class AuthResolver { export class ApiKeyResolver {
constructor( constructor(
private authService: AuthService, private authService: AuthService,
private apiKeyService: ApiKeyService private apiKeyService: ApiKeyService
@@ -61,6 +60,8 @@ export class AuthResolver {
description: input.description ?? undefined, description: input.description ?? undefined,
roles: input.roles ?? [], roles: input.roles ?? [],
permissions: input.permissions ?? [], permissions: input.permissions ?? [],
memory: input.memory ?? false,
overwrite: input.overwrite ?? false
}); });
await this.authService.syncApiKeyRoles(apiKey.id, apiKey.roles); await this.authService.syncApiKeyRoles(apiKey.id, apiKey.roles);

View File

@@ -4,13 +4,14 @@ import { AuthModule } from '@app/unraid-api/auth/auth.module';
import { ArrayResolver } from '@app/unraid-api/graph/resolvers/array/array.resolver'; import { ArrayResolver } from '@app/unraid-api/graph/resolvers/array/array.resolver';
import { DockerResolver } from '@app/unraid-api/graph/resolvers/docker/docker.resolver'; import { DockerResolver } from '@app/unraid-api/graph/resolvers/docker/docker.resolver';
import { AuthResolver } from './auth/auth.resolver'; import { ApiKeyResolver } from './api-key/api-key.resolver';
import { CloudResolver } from './cloud/cloud.resolver'; import { CloudResolver } from './cloud/cloud.resolver';
import { ConfigResolver } from './config/config.resolver'; import { ConfigResolver } from './config/config.resolver';
import { DisksResolver } from './disks/disks.resolver'; import { DisksResolver } from './disks/disks.resolver';
import { DisplayResolver } from './display/display.resolver'; import { DisplayResolver } from './display/display.resolver';
import { FlashResolver } from './flash/flash.resolver'; import { FlashResolver } from './flash/flash.resolver';
import { InfoResolver } from './info/info.resolver'; import { InfoResolver } from './info/info.resolver';
import { MeResolver } from './me/me.resolver';
import { NotificationsResolver } from './notifications/notifications.resolver'; import { NotificationsResolver } from './notifications/notifications.resolver';
import { NotificationsService } from './notifications/notifications.service'; import { NotificationsService } from './notifications/notifications.service';
import { OnlineResolver } from './online/online.resolver'; import { OnlineResolver } from './online/online.resolver';
@@ -19,13 +20,12 @@ import { RegistrationResolver } from './registration/registration.resolver';
import { ServerResolver } from './servers/server.resolver'; import { ServerResolver } from './servers/server.resolver';
import { VarsResolver } from './vars/vars.resolver'; import { VarsResolver } from './vars/vars.resolver';
import { VmsResolver } from './vms/vms.resolver'; import { VmsResolver } from './vms/vms.resolver';
import { MeResolver } from './me/me.resolver';
@Module({ @Module({
imports: [AuthModule], imports: [AuthModule],
providers: [ providers: [
ArrayResolver, ArrayResolver,
AuthResolver, ApiKeyResolver,
CloudResolver, CloudResolver,
ConfigResolver, ConfigResolver,
DisksResolver, DisksResolver,
@@ -43,6 +43,6 @@ import { MeResolver } from './me/me.resolver';
NotificationsService, NotificationsService,
MeResolver, MeResolver,
], ],
exports: [AuthModule, AuthResolver], exports: [AuthModule, ApiKeyResolver],
}) })
export class ResolversModule {} export class ResolversModule {}