From 8e12aac2eb3481314d8315ccf13f40cbed240588 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Thu, 4 Sep 2025 00:48:49 +0200 Subject: [PATCH] feat: make GRPC server compatible with non-user callers --- src/services/grpc/boss/delete-file.ts | 9 ++---- src/services/grpc/boss/delete-task.ts | 9 ++---- .../middleware/authentication-middleware.ts | 30 +++++++++---------- src/services/grpc/boss/register-task.ts | 11 +++---- .../grpc/boss/update-file-metadata.ts | 9 ++---- src/services/grpc/boss/update-task.ts | 9 ++---- src/services/grpc/boss/upload-file.ts | 11 +++---- 7 files changed, 34 insertions(+), 54 deletions(-) diff --git a/src/services/grpc/boss/delete-file.ts b/src/services/grpc/boss/delete-file.ts index 48d6d2f..518acb5 100644 --- a/src/services/grpc/boss/delete-file.ts +++ b/src/services/grpc/boss/delete-file.ts @@ -1,16 +1,13 @@ import { Status, ServerError } from 'nice-grpc'; import { getTaskFileByDataID } from '@/database'; +import { hasPermission } from '@/services/grpc/boss/middleware/authentication-middleware'; +import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware'; import type { CallContext } from 'nice-grpc'; import type { DeleteFileRequest } from '@pretendonetwork/grpc/boss/delete_file'; -import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; import type { Empty } from '@pretendonetwork/grpc/boss/google/protobuf/empty'; -import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware'; export async function deleteFile(request: DeleteFileRequest, context: CallContext & AuthenticationCallContextExt): Promise { - // * This is asserted in authentication middleware, we know this is never null - const user: GetUserDataResponse = context.user!; - - if (!user.permissions?.deleteBossFiles) { + if (!hasPermission(context, 'deleteBossFiles')) { throw new ServerError(Status.PERMISSION_DENIED, 'PNID not authorized to delete files'); } diff --git a/src/services/grpc/boss/delete-task.ts b/src/services/grpc/boss/delete-task.ts index 4151eb7..8df48f9 100644 --- a/src/services/grpc/boss/delete-task.ts +++ b/src/services/grpc/boss/delete-task.ts @@ -1,16 +1,13 @@ import { Status, ServerError } from 'nice-grpc'; import { getTask } from '@/database'; +import { hasPermission } from '@/services/grpc/boss/middleware/authentication-middleware'; +import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware'; import type { CallContext } from 'nice-grpc'; import type { DeleteTaskRequest } from '@pretendonetwork/grpc/boss/delete_task'; -import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; -import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware'; import type { Empty } from '@pretendonetwork/grpc/boss/google/protobuf/empty'; export async function deleteTask(request: DeleteTaskRequest, context: CallContext & AuthenticationCallContextExt): Promise { - // * This is asserted in authentication middleware, we know this is never null - const user: GetUserDataResponse = context.user!; - - if (!user.permissions?.deleteBossTasks) { + if (!hasPermission(context, 'deleteBossTasks')) { throw new ServerError(Status.PERMISSION_DENIED, 'PNID not authorized to delete tasks'); } diff --git a/src/services/grpc/boss/middleware/authentication-middleware.ts b/src/services/grpc/boss/middleware/authentication-middleware.ts index ec00a33..7456dd4 100644 --- a/src/services/grpc/boss/middleware/authentication-middleware.ts +++ b/src/services/grpc/boss/middleware/authentication-middleware.ts @@ -2,14 +2,7 @@ import { Status, ServerError } from 'nice-grpc'; import { getUserDataByToken } from '@/util'; import type { ServerMiddlewareCall, CallContext } from 'nice-grpc'; import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; - -const TOKEN_REQUIRED_PATHS = [ - '/boss.BOSS/RegisterTask', - '/boss.BOSS/UpdateTask', - '/boss.BOSS/DeleteTask', - '/boss.BOSS/UploadFile', - '/boss.BOSS/DeleteFile' -]; +import type { PNIDPermissionFlags } from '@pretendonetwork/grpc/account/pnid_permission_flags'; export type AuthenticationCallContextExt = { user: GetUserDataResponse | null; @@ -21,19 +14,14 @@ export async function* authenticationMiddleware( ): AsyncGenerator { const token: string | undefined = context.metadata.get('X-Token')?.trim(); - if (!token && TOKEN_REQUIRED_PATHS.includes(call.method.path)) { - throw new ServerError(Status.UNAUTHENTICATED, 'Missing or invalid authentication token'); - } - try { let user: GetUserDataResponse | null = null; if (token) { user = await getUserDataByToken(token); - } - - if (!user && TOKEN_REQUIRED_PATHS.includes(call.method.path)) { - throw new ServerError(Status.UNAUTHENTICATED, 'Missing or invalid authentication token'); + if (!user) { + throw new ServerError(Status.UNAUTHENTICATED, 'User could not be found'); + } } return yield* call.next(call.request, { @@ -52,3 +40,13 @@ export async function* authenticationMiddleware( throw new ServerError(Status.INVALID_ARGUMENT, message); } } + +export function hasPermission(ctx: AuthenticationCallContextExt, perm: keyof PNIDPermissionFlags): boolean { + if (!ctx.user) { + return true; // Non users are always allowed + } + if (!ctx.user.permissions) { + return false; // No permissions, no entry + } + return ctx.user.permissions[perm]; +} diff --git a/src/services/grpc/boss/register-task.ts b/src/services/grpc/boss/register-task.ts index c7c4487..36c03b8 100644 --- a/src/services/grpc/boss/register-task.ts +++ b/src/services/grpc/boss/register-task.ts @@ -1,18 +1,15 @@ import { ServerError, Status } from 'nice-grpc'; import { getTask } from '@/database'; import { Task } from '@/models/task'; -import type { CallContext } from 'nice-grpc'; +import { hasPermission } from '@/services/grpc/boss/middleware/authentication-middleware'; import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware'; -import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; +import type { CallContext } from 'nice-grpc'; import type { RegisterTaskRequest, RegisterTaskResponse } from '@pretendonetwork/grpc/boss/register_task'; const BOSS_APP_ID_FILTER_REGEX = /^[A-Za-z0-9]*$/; export async function registerTask(request: RegisterTaskRequest, context: CallContext & AuthenticationCallContextExt): Promise { - // * This is asserted in authentication middleware, we know this is never null - const user: GetUserDataResponse = context.user!; - - if (!user.permissions?.createBossTasks) { + if (!hasPermission(context, 'createBossTasks')) { throw new ServerError(Status.PERMISSION_DENIED, 'PNID not authorized to register new tasks'); } @@ -55,7 +52,7 @@ export async function registerTask(request: RegisterTaskRequest, context: CallCo id: taskID.slice(0, 7), in_game_id: taskID, boss_app_id: bossAppID, - creator_pid: user.pid, + creator_pid: context.user?.pid, status: 'open', // TODO - Make this configurable title_id: titleID, description: description, diff --git a/src/services/grpc/boss/update-file-metadata.ts b/src/services/grpc/boss/update-file-metadata.ts index 01fc7f8..7c7bac1 100644 --- a/src/services/grpc/boss/update-file-metadata.ts +++ b/src/services/grpc/boss/update-file-metadata.ts @@ -1,17 +1,14 @@ import { Status, ServerError } from 'nice-grpc'; import { getTaskFileByDataID } from '@/database'; import { isValidFileNotifyCondition, isValidFileType } from '@/util'; +import { hasPermission } from '@/services/grpc/boss/middleware/authentication-middleware'; +import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware'; import type { CallContext } from 'nice-grpc'; import type { UpdateFileMetadataRequest } from '@pretendonetwork/grpc/boss/update_file_metadata'; -import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; -import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware'; import type { Empty } from '@pretendonetwork/grpc/boss/google/protobuf/empty'; export async function updateFileMetadata(request: UpdateFileMetadataRequest, context: CallContext & AuthenticationCallContextExt): Promise { - // * This is asserted in authentication middleware, we know this is never null - const user: GetUserDataResponse = context.user!; - - if (!user.permissions?.updateBossFiles) { + if (!hasPermission(context, 'updateBossFiles')) { throw new ServerError(Status.PERMISSION_DENIED, 'PNID not authorized to update file metadata'); } diff --git a/src/services/grpc/boss/update-task.ts b/src/services/grpc/boss/update-task.ts index 113cccb..eee00f9 100644 --- a/src/services/grpc/boss/update-task.ts +++ b/src/services/grpc/boss/update-task.ts @@ -1,16 +1,13 @@ import { Status, ServerError } from 'nice-grpc'; import { getTask } from '@/database'; +import { hasPermission } from '@/services/grpc/boss/middleware/authentication-middleware'; +import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware'; import type { CallContext } from 'nice-grpc'; import type { UpdateTaskRequest } from '@pretendonetwork/grpc/boss/update_task'; -import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; -import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware'; import type { Empty } from '@pretendonetwork/grpc/boss/google/protobuf/empty'; export async function updateTask(request: UpdateTaskRequest, context: CallContext & AuthenticationCallContextExt): Promise { - // * This is asserted in authentication middleware, we know this is never null - const user: GetUserDataResponse = context.user!; - - if (!user.permissions?.updateBossTasks) { + if (!hasPermission(context, 'updateBossTasks')) { throw new ServerError(Status.PERMISSION_DENIED, 'PNID not authorized to update tasks'); } diff --git a/src/services/grpc/boss/upload-file.ts b/src/services/grpc/boss/upload-file.ts index 7977bc8..4b8ae92 100644 --- a/src/services/grpc/boss/upload-file.ts +++ b/src/services/grpc/boss/upload-file.ts @@ -5,18 +5,15 @@ import { getTask, getTaskFile } from '@/database'; import { File } from '@/models/file'; import { config } from '@/config-manager'; import { uploadCdnFile } from '@/cdn'; -import type { CallContext } from 'nice-grpc'; +import { hasPermission } from '@/services/grpc/boss/middleware/authentication-middleware'; import type { AuthenticationCallContextExt } from '@/services/grpc/boss/middleware/authentication-middleware'; -import type { GetUserDataResponse } from '@pretendonetwork/grpc/account/get_user_data_rpc'; +import type { CallContext } from 'nice-grpc'; import type { UploadFileRequest, UploadFileResponse } from '@pretendonetwork/grpc/boss/upload_file'; const BOSS_APP_ID_FILTER_REGEX = /^[A-Za-z0-9]*$/; export async function uploadFile(request: UploadFileRequest, context: CallContext & AuthenticationCallContextExt): Promise { - // * This is asserted in authentication middleware, we know this is never null - const user: GetUserDataResponse = context.user!; - - if (!user.permissions?.uploadBossFiles) { + if (!hasPermission(context, 'uploadBossFiles')) { throw new ServerError(Status.PERMISSION_DENIED, 'PNID not authorized to upload new files'); } @@ -138,7 +135,7 @@ export async function uploadFile(request: UploadFileRequest, context: CallContex boss_app_id: bossAppID, supported_countries: supportedCountries, supported_languages: supportedLanguages, - creator_pid: user.pid, + creator_pid: context.user?.pid, name: name, type: type, hash: contentHash,