diff --git a/src/plugins/meta.ts b/src/plugins/meta.ts index 7832a1986..6aabd29c1 100644 --- a/src/plugins/meta.ts +++ b/src/plugins/meta.ts @@ -6,8 +6,10 @@ 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 { @@ -110,10 +112,12 @@ 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', async () => { + return { version: APP_VERSION, buildTimestamp: BUILD_TIMESTAMP }; + }); + + fastify.get('/api/bust-cache', { preHandler: authenticateAdminSharedSecret }, async () => { + await bustFileCache(); }); }; diff --git a/src/services/auth/plugins/passport/plugin.ts b/src/services/auth/plugins/passport/plugin.ts index e12a21ce9..c83a79f14 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 6c0f9b97e..a14fe8f38 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 32349ed35..155534eb3 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 000000000..22d447487 --- /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 3fc9c5c63..2297644cf 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;