UNPKG

@citrineos/util

Version:

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

202 lines 9.04 kB
"use strict"; /* * // Copyright Contributors to the CitrineOS Project * // * // SPDX-License-Identifier: Apache 2.0 * */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OIDCAuthProvider = void 0; const tslog_1 = require("tslog"); const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); const jwks_rsa_1 = __importDefault(require("jwks-rsa")); const base_1 = require("@citrineos/base"); const crypto_1 = require("crypto"); const RbacRulesLoader_1 = require("../rbac/RbacRulesLoader"); /** * OIDC authentication provider implementation */ class OIDCAuthProvider { /** * Creates a new Keycloak authentication provider * * @param config OIDC configuration * @param logger Optional logger instance */ constructor(config, logger) { this._defaultTenantId = '1'; //TODO get default from config this._config = Object.assign({ cacheTime: 60 * 60 * 1000, rateLimit: true }, config); this._logger = logger ? logger.getSubLogger({ name: this.constructor.name }) : new tslog_1.Logger({ name: this.constructor.name }); this._logger.info('OIDC auth provider config', this._config); // Create the JWKS client this._jkwsClient = (0, jwks_rsa_1.default)({ jwksUri: this._config.jwksUri, cache: true, cacheMaxAge: this._config.cacheTime, rateLimit: this._config.rateLimit, jwksRequestsPerMinute: 5, // Limit requests to JWKS endpoint }); this._rulesLoader = new RbacRulesLoader_1.RbacRulesLoader('rbac-rules.json', this._logger); this._logger.info(`OIDC auth provider setup with jwksUri: ${this._config.jwksUri}`); } extractToken(request) { return __awaiter(this, void 0, void 0, function* () { // Extract the Authorization header const authHeader = request.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { this._logger.warn('No Bearer token found in request headers'); return null; } // Return the token without the "Bearer " prefix const token = authHeader.slice(7).trim(); this._logger.debug('Extracted token from request:', token); return token; }); } /** * Authenticates a JWT token from and OIDC provider * * @param token JWT token to authenticate * @returns Authentication result with user info if successful */ authenticateToken(token) { return __awaiter(this, void 0, void 0, function* () { try { const decoded = jsonwebtoken_1.default.decode(token, { complete: true }); if (!decoded || typeof decoded !== 'object' || !decoded.header || !decoded.header.kid) { throw new Error('Invalid token format'); } const publicKey = yield this.fetchPublicKey(decoded.header.kid); // Verify the token with the public key const payload = jsonwebtoken_1.default.verify(token, (0, crypto_1.createPublicKey)(publicKey)); // Extract user info from the decoded token const user = { id: payload.sub, name: payload.preferred_username || payload.name || payload.sub, email: payload.email || '', roles: this.extractRoles(payload), tenantId: payload.tenant_id || this._defaultTenantId, metadata: { firstName: payload.given_name, lastName: payload.family_name, fullName: payload.name, emailVerified: payload.email_verified, locale: payload.locale || 'en-US', }, }; return base_1.ApiAuthenticationResult.success(user); } catch (error) { this._logger.error('Token authentication failed:', error); return base_1.ApiAuthenticationResult.failure(error instanceof Error ? error.message : 'Invalid token'); } }); } /** * Authorizes a user for a specific request * This implementation checks if the user has the required permissions * for the requested URL and method * * @param user User information * @param request Fastify request * @returns Authorization result */ authorizeUser(user, request) { return __awaiter(this, void 0, void 0, function* () { try { // Get the requested resource and method const url = request.url; const method = request.method; const tenantId = request.query.tenantId || this._defaultTenantId; const requiredRoles = this._rulesLoader.getRequiredRoles(tenantId, url, method); //If no role is found for the requested resource and tenant, decline access if (!requiredRoles || requiredRoles.length === 0) { return base_1.ApiAuthorizationResult.failure(`Tenant does not have access to this resource ${url}`); } if (this.userHasRequiredRole(user, requiredRoles)) { return base_1.ApiAuthorizationResult.success(); } return base_1.ApiAuthorizationResult.failure(`Missing required roles. Need one of: ${requiredRoles.join(', ')} for tenant ${tenantId}`); } catch (error) { this._logger.error('Authorization error:', error); return base_1.ApiAuthorizationResult.failure(`Authorization error: ${error instanceof Error ? error.message : String(error)}`); } }); } /** * Fetches the public key from OIDC provider * @param {string} kid Key ID from the JWT header * @returns {Promise<string>} Public key as a string * @private */ fetchPublicKey(kid) { return __awaiter(this, void 0, void 0, function* () { try { return new Promise((resolve, reject) => { this._jkwsClient.getSigningKey(kid, (err, key) => { if (err) { this._logger.error(`Error fetching signing key for kid: ${kid}`, err); return reject(err); } if (!key) { const error = new Error(`No signing key found for kid: ${kid}`); this._logger.error(error.message); return reject(error); } try { // Get the public key const signingKey = key.getPublicKey(); resolve(signingKey); } catch (keyError) { this._logger.error('Error extracting public key:', keyError); reject(keyError); } }); }); } catch (error) { this._logger.error('Failed to fetch public key:', error); throw error; } }); } /** * Extracts roles from a decoded JWT token * * @param decoded The decoded JWT token * @returns Array of role strings * @private */ extractRoles(decoded) { //Customize here to match your token structure return decoded.roles || []; } /** * Check if a user has any of the required roles for a specific tenant * * @param user User with roles * @param requiredRoles Array of role names (without tenant prefix) * @returns True if user has any of the required roles */ userHasRequiredRole(user, requiredRoles) { return user.roles.some((userRole) => requiredRoles.includes(userRole)); } } exports.OIDCAuthProvider = OIDCAuthProvider; //# sourceMappingURL=OIDCAuthProvider.js.map