import auth from 'basic-auth'; import { Request } from 'express'; import { ApiClient, User } from '@/entities'; import logger from '@/logger'; import { BasicAuthStrategy } from '@/services/AuthProvider/strategies'; import { UserRole } from '@/types'; import { AuthenticateError, ForbiddenError } from './types'; import { decodeApiKey, isSecurityTypeValid } from './utils'; const isMasterAdmin = ({ username, password, }: { username: string; password: string; }) => { const masterAdminUsername = process.env.ROOT_API_KEY_CLIENT_ID; const masterAdminPassword = process.env.ROOT_API_KEY_CLIENT_SECRET; if (!masterAdminUsername || !masterAdminPassword) { logger.error("Master admin keys aren't set yet"); return false; } return username === masterAdminUsername && password === masterAdminPassword; }; const canUserAccessRoute = (user: User, scopes: string[]) => { if ( scopes.length === 0 || scopes.some((role) => user.roles.includes(role as UserRole)) ) { return true; } return false; }; /** * This value returned by this function will be injected into * express' req.user. * * req.user will be undefined when the master admin accesses the route, * otherwise it will be equal to the typeorm User object of the requester. */ export async function expressAuthentication( req: Request, securityName: string, scopes?: string[] ): Promise { if (!isSecurityTypeValid(securityName)) { throw new Error('Invalid security type'); } switch (securityName) { case 'basic': { const basicAuthData = auth(req); if (!basicAuthData) { throw new AuthenticateError('Invalid credentials'); } const { name: username, pass: password } = basicAuthData; if (isMasterAdmin({ username, password })) { return; } const extractedClientData = decodeApiKey(username); if (!extractedClientData) { throw new AuthenticateError('Invalid credentials'); } const { clientId, clientSecret } = extractedClientData; const apiClient = await ApiClient.findOne( { clientId }, { relations: ['user'], } ); if (!apiClient) { throw new AuthenticateError(); } const strategy = new BasicAuthStrategy(); const isAuthorized = await strategy.verifyPersistedPassword({ passwordData: apiClient.clientSecret, passwordAttempt: clientSecret, }); if (!isAuthorized) { throw new AuthenticateError(); } if (scopes && !canUserAccessRoute(apiClient.user, scopes)) { throw new ForbiddenError(); } return apiClient.user; } default: throw new Error('Security type not implemented yet'); } }