From 4fdcc9b0316c6994fefa1dd270b45ff26733a1a1 Mon Sep 17 00:00:00 2001 From: Admin Date: Wed, 23 Sep 2026 02:59:20 +0000 Subject: [PATCH] refine: update server/auth.js --- server/auth.js | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/server/auth.js b/server/auth.js index 051b5d3..b516707 100644 --- a/server/auth.js +++ b/server/auth.js @@ -10,7 +10,11 @@ function config() { issuer: process.env.KEYCLOAK_ISSUER?.replace(/\/$/, '') || '', clientId: process.env.KEYCLOAK_CLIENT_ID || '', clientSecret: process.env.KEYCLOAK_CLIENT_SECRET || '', - baseUrl: process.env.APP_BASE_URL?.replace(/\/$/, '') || '' + baseUrl: process.env.APP_BASE_URL?.replace(/\/$/, '') || '', + allowedApiClients: String(process.env.TEMPLATE_API_ALLOWED_CLIENTS || '') + .split(',') + .map(value => value.trim()) + .filter(Boolean) } } @@ -69,9 +73,14 @@ function redirectUri(req) { return `${proto}://${req.get('host')}/auth/callback` } -function readSession(req) { +export function readSession(req) { if (!authRequired()) { - return { sub: 'local-dev', preferred_username: 'local-dev', name: 'Local developer', exp: Math.floor(Date.now() / 1000) + 3600 } + return { + sub: 'local-dev', + preferred_username: 'local-dev', + name: 'Local developer', + exp: Math.floor(Date.now() / 1000) + 3600 + } } const session = unpack(parseCookies(req)[SESSION_COOKIE], 'session') if (!session || Number(session.exp || 0) <= Math.floor(Date.now() / 1000)) return null @@ -83,6 +92,22 @@ function ensureConfigured() { if (!issuer || !clientId || !clientSecret) throw new Error('Keycloak OIDC is not fully configured') } +export async function verifyMachineToken(token) { + const { issuer, allowedApiClients } = config() + if (!issuer) throw new Error('KEYCLOAK_ISSUER is required') + if (!allowedApiClients.length) throw new Error('No API clients are allowed') + + jwks ||= createRemoteJWKSet(new URL(`${issuer}/protocol/openid-connect/certs`)) + const { payload } = await jwtVerify(token, jwks, { issuer }) + + const audiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud].filter(Boolean) + const client = String(payload.azp || '') + const allowed = allowedApiClients.some(value => value === client || audiences.includes(value)) + if (!allowed) throw new Error('Token client is not allowed to consume templates') + + return payload +} + export function registerAuthRoutes(app) { app.get('/auth/login', (req, res, next) => { try {