feat: make GRPC server compatible with non-user callers

This commit is contained in:
mrjvs
2025-09-04 00:48:49 +02:00
parent f8d5a0c7e3
commit 8e12aac2eb
7 changed files with 34 additions and 54 deletions

View File

@@ -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<Empty> {
// * 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');
}

View File

@@ -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<Empty> {
// * 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');
}

View File

@@ -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<Request, Response>(
): AsyncGenerator<Response, Response | void, undefined> {
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<Request, Response>(
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];
}

View File

@@ -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<RegisterTaskResponse> {
// * 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,

View File

@@ -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<Empty> {
// * 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');
}

View File

@@ -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<Empty> {
// * 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');
}

View File

@@ -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<UploadFileResponse> {
// * 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,