154 lines
4.1 KiB
TypeScript
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>;
|
|
}
|