import * as plugins from '../plugins.js'; import type { IRequestContext, IResponse, IAuthToken, IRequestActor } from './interfaces.core.js'; /** * Abstract base class for all registry protocol implementations */ export abstract class BaseRegistry { protected getHeader(contextOrHeaders: IRequestContext | Record, name: string): string | undefined { const headers = 'headers' in contextOrHeaders ? contextOrHeaders.headers : contextOrHeaders; if (headers[name] !== undefined) { return headers[name]; } const lowerName = name.toLowerCase(); for (const [headerName, value] of Object.entries(headers)) { if (headerName.toLowerCase() === lowerName) { return value; } } return undefined; } protected getAuthorizationHeader(context: IRequestContext): string | undefined { return this.getHeader(context, 'authorization'); } protected getClientIp(context: IRequestContext): string | undefined { const forwardedFor = this.getHeader(context, 'x-forwarded-for'); if (forwardedFor) { return forwardedFor.split(',')[0]?.trim(); } return this.getHeader(context, 'x-real-ip'); } protected getUserAgent(context: IRequestContext): string | undefined { return this.getHeader(context, 'user-agent'); } protected extractBearerToken(contextOrHeader: IRequestContext | string | undefined): string | null { const authHeader = typeof contextOrHeader === 'string' ? contextOrHeader : contextOrHeader ? this.getAuthorizationHeader(contextOrHeader) : undefined; if (!authHeader || !/^Bearer\s+/i.test(authHeader)) { return null; } return authHeader.replace(/^Bearer\s+/i, ''); } protected parseBasicAuthHeader(authHeader: string | undefined): { username: string; password: string } | null { if (!authHeader || !/^Basic\s+/i.test(authHeader)) { return null; } const base64 = authHeader.replace(/^Basic\s+/i, ''); const decoded = Buffer.from(base64, 'base64').toString('utf-8'); const separatorIndex = decoded.indexOf(':'); if (separatorIndex < 0) { return { username: decoded, password: '', }; } return { username: decoded.substring(0, separatorIndex), password: decoded.substring(separatorIndex + 1), }; } protected buildRequestActor(context: IRequestContext, token: IAuthToken | null): IRequestActor { const actor: IRequestActor = { ...(context.actor ?? {}), }; if (token?.userId) { actor.userId = token.userId; } const ip = this.getClientIp(context); if (ip) { actor.ip = ip; } const userAgent = this.getUserAgent(context); if (userAgent) { actor.userAgent = userAgent; } return actor; } protected createProtocolLogger( containerName: string, zone: string ): plugins.smartlog.Smartlog { const logger = new plugins.smartlog.Smartlog({ logContext: { company: 'push.rocks', companyunit: 'smartregistry', containerName, environment: (process.env.NODE_ENV as any) || 'development', runtime: 'node', zone, } }); logger.enableConsole(); return logger; } /** * Initialize the registry */ abstract init(): Promise; /** * Clean up timers, connections, and other registry resources. */ public destroy(): void { // Default no-op for registries without persistent resources. } /** * Handle an incoming HTTP request * @param context - Request context * @returns Response object */ abstract handleRequest(context: IRequestContext): Promise; /** * Get the base path for this registry protocol */ abstract getBasePath(): string; /** * Validate that a token has the required permissions * @param token - Authentication token * @param resource - Resource being accessed * @param action - Action being performed * @returns true if authorized */ protected abstract checkPermission( token: IAuthToken | null, resource: string, action: string ): Promise; }