From 2c3b45879ba2b89f6f430f01e4e4363e151c9f44 Mon Sep 17 00:00:00 2001 From: spaenleh Date: Wed, 20 May 2026 10:55:42 +0200 Subject: [PATCH 1/2] fix: allow admin to communicate with core --- src/plugins/meta.ts | 13 +++++++----- src/services/auth/plugins/passport/plugin.ts | 4 ++++ .../auth/plugins/passport/preHandlers.ts | 9 ++++++++ .../auth/plugins/passport/strategies.ts | 3 +++ .../passport/strategies/adminSharedSecret.ts | 21 +++++++++++++++++++ src/utils/config.ts | 2 ++ 6 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 src/services/auth/plugins/passport/strategies/adminSharedSecret.ts diff --git a/src/plugins/meta.ts b/src/plugins/meta.ts index 7832a19863..048a672737 100644 --- a/src/plugins/meta.ts +++ b/src/plugins/meta.ts @@ -10,6 +10,7 @@ import { resolveDependency } from '../di/utils'; import { db } from '../drizzle/db'; import { SearchService } from '../services/item/plugins/publication/published/plugins/search/search.service'; import { assertIsError } from '../utils/assertions'; +import { authenticateAdminSharedSecret } from '../services/auth/plugins/passport/preHandlers'; import { APP_VERSION, BUILD_TIMESTAMP, @@ -110,11 +111,13 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => { }; }); - fastify.get('/api/version', async (_, reply) => { - // allow request cross origin - reply.header('Access-Control-Allow-Origin', '*'); - return `${APP_VERSION} @ ${BUILD_TIMESTAMP}`; - }); + fastify.get( + '/api/version', + { preHandler: authenticateAdminSharedSecret }, + async () => { + return { version: APP_VERSION, buildTimestamp: BUILD_TIMESTAMP }; + }, + ); }; const getDBStatusCheck = async (_log: FastifyBaseLogger): Promise => { diff --git a/src/services/auth/plugins/passport/plugin.ts b/src/services/auth/plugins/passport/plugin.ts index e12a21ce90..c83a79f144 100644 --- a/src/services/auth/plugins/passport/plugin.ts +++ b/src/services/auth/plugins/passport/plugin.ts @@ -19,6 +19,7 @@ import { MemberRepository } from '../../../member/member.repository'; import { MemberPasswordService } from '../password/password.service'; import { SHORT_TOKEN_PARAM } from './constants'; import { PassportStrategy } from './strategies'; +import adminSharedSecretStrategy from './strategies/adminSharedSecret'; import emailChangeStrategy from './strategies/emailChange'; import jwtAppsStrategy from './strategies/jwtApps'; import jwtChallengeVerifierStrategy from './strategies/jwtChallengeVerifier'; @@ -57,6 +58,9 @@ const plugin: FastifyPluginAsync = async (fastify: FastifyInstance) => { //-- Sessions Strategies --// strictSessionStrategy(fastifyPassport); + //-- Shared Secret Strategy (machine-to-machine) --// + adminSharedSecretStrategy(fastifyPassport); + //-- Password Strategies --// passwordStrategy(fastifyPassport, memberPasswordService, { propagateError: true, diff --git a/src/services/auth/plugins/passport/preHandlers.ts b/src/services/auth/plugins/passport/preHandlers.ts index 6c0f9b97e9..a14fe8f387 100644 --- a/src/services/auth/plugins/passport/preHandlers.ts +++ b/src/services/auth/plugins/passport/preHandlers.ts @@ -74,6 +74,15 @@ export const guestAuthenticateAppsJWT = fastifyPassport.authenticate( }, ) as RouteShorthandHook; +/** + * Shared secret authentication for machine-to-machine routes. + * Requires `Authorization: Bearer ` header. + */ +export const authenticateAdminSharedSecret = fastifyPassport.authenticate( + PassportStrategy.AdminSharedSecret, + { session: false }, +) as RouteShorthandHook; + /** * Pre-handler function that checks if the user meets at least one of the specified access preconditions. * @param strategies The array of role strategies to check for access. diff --git a/src/services/auth/plugins/passport/strategies.ts b/src/services/auth/plugins/passport/strategies.ts index 32349ed35e..155534eb3c 100644 --- a/src/services/auth/plugins/passport/strategies.ts +++ b/src/services/auth/plugins/passport/strategies.ts @@ -17,4 +17,7 @@ export enum PassportStrategy { //-- From Password --// Password = 'password', + + //-- Shared Secret (machine-to-machine) --// + AdminSharedSecret = 'admin-shared-secret', } diff --git a/src/services/auth/plugins/passport/strategies/adminSharedSecret.ts b/src/services/auth/plugins/passport/strategies/adminSharedSecret.ts new file mode 100644 index 0000000000..22d447487d --- /dev/null +++ b/src/services/auth/plugins/passport/strategies/adminSharedSecret.ts @@ -0,0 +1,21 @@ +import { Strategy } from 'passport-custom'; + +import { Authenticator } from '@fastify/passport'; + +import { ADMIN_SHARED_SECRET } from '../../../../../utils/config'; +import { UnauthorizedMember } from '../../../../../utils/errors'; +import { PassportStrategy } from '../strategies'; +import type { StrictVerifiedCallback } from '../types'; + +export default (passport: Authenticator) => { + passport.use( + PassportStrategy.AdminSharedSecret, + new Strategy((req, done: StrictVerifiedCallback) => { + const authHeader = req.headers.authorization; + if (ADMIN_SHARED_SECRET && authHeader === `Bearer ${ADMIN_SHARED_SECRET}`) { + return done(null, {}); + } + return done(new UnauthorizedMember(), false); + }), + ); +}; diff --git a/src/utils/config.ts b/src/utils/config.ts index 3fc9c5c633..2297644cf5 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -17,6 +17,8 @@ export const LOG_LEVEL: string | undefined = process.env.LOG_LEVEL; export const APP_VERSION = process.env.APP_VERSION; export const BUILD_TIMESTAMP = process.env.BUILD_TIMESTAMP; +export const ADMIN_SHARED_SECRET = process.env.ADMIN_SHARED_SECRET; + export const CLIENT_HOST = process.env.CLIENT_HOST ?? 'http://localhost:3114'; export const LIBRARY_HOST = process.env.LIBRARY_CLIENT_HOST ?? CLIENT_HOST; From d06765810e3ad945d7bc0f1f1cb8306dce7b4f11 Mon Sep 17 00:00:00 2001 From: spaenleh Date: Thu, 21 May 2026 07:52:23 +0200 Subject: [PATCH 2/2] fix: add a protected endpoint that busts all file caches --- src/plugins/meta.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/plugins/meta.ts b/src/plugins/meta.ts index 048a672737..6aabd29c11 100644 --- a/src/plugins/meta.ts +++ b/src/plugins/meta.ts @@ -6,11 +6,12 @@ import type { FastifyBaseLogger, FastifySchema } from 'fastify'; import type { UnionOfConst } from '@graasp/sdk'; +import { bustFileCache } from '../bustCache'; import { resolveDependency } from '../di/utils'; import { db } from '../drizzle/db'; +import { authenticateAdminSharedSecret } from '../services/auth/plugins/passport/preHandlers'; import { SearchService } from '../services/item/plugins/publication/published/plugins/search/search.service'; import { assertIsError } from '../utils/assertions'; -import { authenticateAdminSharedSecret } from '../services/auth/plugins/passport/preHandlers'; import { APP_VERSION, BUILD_TIMESTAMP, @@ -111,13 +112,13 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => { }; }); - fastify.get( - '/api/version', - { preHandler: authenticateAdminSharedSecret }, - async () => { - return { version: APP_VERSION, buildTimestamp: BUILD_TIMESTAMP }; - }, - ); + fastify.get('/api/version', async () => { + return { version: APP_VERSION, buildTimestamp: BUILD_TIMESTAMP }; + }); + + fastify.get('/api/bust-cache', { preHandler: authenticateAdminSharedSecret }, async () => { + await bustFileCache(); + }); }; const getDBStatusCheck = async (_log: FastifyBaseLogger): Promise => {