import { Request, Response, NextFunction } from 'express'; import jwt, { JwtPayload } from 'jsonwebtoken'; import { jwksClientInstance } from './jwks-client'; import { extractBearerToken } from './extract-bearer-token'; import { env } from '../config/env'; import { ServiceAuthContext } from './auth.types'; interface ServiceTokenPayload extends JwtPayload { // "azp" (authorized party) e' il client_id del client che ha ottenuto il token // via client credentials grant: e' il modo standard per riconoscere quale // servizio sta chiamando in una chiamata machine-to-machine. azp?: string; } // Middleware da usare al posto di (non insieme a) verifyToken sulle route // machine-to-machine, ad es. POST /eventi/:id/risorse chiamata da altri backend // (scouthub-attivita-be, scouthub-magazzino-be, ...) con le proprie credenziali // di servizio. Il client chiamante deve essere elencato in // KEYCLOAK_AUTHORIZED_SERVICE_CLIENTS. export async function verifyServiceToken(req: Request, res: Response, next: NextFunction): Promise { const token = extractBearerToken(req); if (!token) { res.status(401).json({ message: 'Token mancante' }); return; } try { const decoded = jwt.decode(token, { complete: true }); if (!decoded || !decoded.header.kid) { throw new Error("Header del token privo di 'kid'"); } const signingKey = await jwksClientInstance.getSigningKey(decoded.header.kid); const payload = jwt.verify(token, signingKey.getPublicKey(), { algorithms: ['RS256'] }); if (typeof payload === 'string') { throw new Error('Payload del token non valido'); } const clientId = (payload as ServiceTokenPayload).azp; if (!clientId || !env.keycloak.authorizedServiceClients.includes(clientId)) { res.status(403).json({ message: 'Client di servizio non autorizzato' }); return; } const service: ServiceAuthContext = { clientId }; req.service = service; next(); } catch { res.status(401).json({ message: 'Token non valido' }); } }