UNPKG

@citrineos/util

Version:

The OCPP util module which supplies helpful utilities like cache and queue connectors, etc.

122 lines 4.81 kB
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project // // SPDX-License-Identifier: Apache-2.0 import { Logger } from 'tslog'; import fp from 'fastify-plugin'; import { HttpStatus } from '@citrineos/base'; /** * Authentication plugin for Fastify * This plugin adds authentication and authorization capabilities to Fastify * using the provided auth provider * * @param fastify Fastify instance * @param provider Auth provider instance * @param options Plugin options */ const apiAuthPlugin = async (fastify, { provider, options = {}, logger }) => { //TODO add logger instance ? const _logger = logger ? logger.getSubLogger({ name: 'AuthPlugin' }) : new Logger({ name: 'AuthPlugin' }); // Register the auth provider fastify.decorate('authProvider', provider); // Helper to check if a route is excluded from authentication function isExcludedRoute(url) { // Always exclude health check if (url === '/health') { return true; } const isExcluded = !!options.excludedRoutes?.some((route) => url === route || url.startsWith(`${route}/`)); if (isExcluded && options.debug) { _logger.debug(`Skipping authentication for excluded route: ${url}`); } return isExcluded; } // Authentication decorator - validates token from Authorization header fastify.decorate('authenticate', async function (request, reply) { try { // Extract token const token = await provider.extractToken(request); if (!token) { reply.code(HttpStatus.UNAUTHORIZED).send({ error: 'Unauthorized', message: 'Missing or invalid authorization header', }); return; } // Authenticate token const authResult = await provider.authenticateToken(token); if (!authResult.isAuthenticated || !authResult.user) { reply.code(HttpStatus.UNAUTHORIZED).send({ error: 'Unauthorized', message: authResult.error || 'Invalid token', }); return; } // Store user info in request request.user = authResult.user; if (options.debug) { _logger.debug(`Authenticated user: ${authResult.user.id} (${authResult.user.name})`); _logger.debug(`Roles: ${authResult.user.roles.join(', ')}`); } } catch (error) { _logger.error('Authentication error:', error); reply.code(HttpStatus.UNAUTHORIZED).send({ error: 'Unauthorized', message: 'Authentication failed', }); } }); // Authorization decorator - authorizes user for the requested resource fastify.decorate('authorize', async function (request, reply) { try { // Check if user is authenticated if (!request.user) { reply.code(HttpStatus.UNAUTHORIZED).send({ error: 'Unauthorized', message: 'Authentication required', }); return; } // Authorize user for this request const authzResult = await provider.authorizeUser(request.user, request); if (!authzResult.isAuthorized) { reply.code(HttpStatus.FORBIDDEN).send({ error: 'Forbidden', message: authzResult.error || 'Insufficient permissions', }); return; } if (options.debug) { _logger.debug(`Authorized user ${request.user.id} for ${request.method} ${request.url}`); } } catch (error) { _logger.error('Authorization error:', error); reply.code(HttpStatus.FORBIDDEN).send({ error: 'Forbidden', message: 'Authorization failed', }); } }); // Add global authentication hook for all routes fastify.addHook('onRequest', async (request, reply) => { // Skip authentication for excluded routes if (isExcludedRoute(request.url)) { if (options.debug) { _logger.trace(`Skipping authentication for excluded route: ${request.url}`); } return; } // Authenticate and authorize the request await fastify.authenticate(request, reply); await fastify.authorize(request, reply); }); _logger.info('Authentication plugin registered'); }; export const apiAuthPluginFp = fp(apiAuthPlugin, { name: 'apiAuth', fastify: '5.x', }); //# sourceMappingURL=ApiAuthPlugin.js.map