Files
smartregistry/ts/core/classes.baseregistry.ts
T

154 lines
4.1 KiB
TypeScript

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<string, string>, 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<void>;
/**
* 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<IResponse>;
/**
* 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<boolean>;
}