106 lines
2.3 KiB
TypeScript
106 lines
2.3 KiB
TypeScript
|
|
/**
|
||
|
|
* Authentication Middleware
|
||
|
|
*
|
||
|
|
* Validates API keys for incoming requests.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import * as http from 'node:http';
|
||
|
|
import { logger } from '../../logger.ts';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Authentication middleware for API key validation
|
||
|
|
*/
|
||
|
|
export class AuthMiddleware {
|
||
|
|
private apiKeys: Set<string>;
|
||
|
|
private allowNoAuth: boolean;
|
||
|
|
|
||
|
|
constructor(apiKeys: string[], allowNoAuth: boolean = false) {
|
||
|
|
this.apiKeys = new Set(apiKeys);
|
||
|
|
this.allowNoAuth = allowNoAuth;
|
||
|
|
|
||
|
|
if (this.apiKeys.size === 0 && !allowNoAuth) {
|
||
|
|
logger.warn('No API keys configured - authentication will fail for all requests');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Authenticate a request
|
||
|
|
*/
|
||
|
|
public authenticate(req: http.IncomingMessage): boolean {
|
||
|
|
// If no keys configured and allowNoAuth is true, allow all requests
|
||
|
|
if (this.apiKeys.size === 0 && this.allowNoAuth) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
const authHeader = req.headers.authorization;
|
||
|
|
|
||
|
|
if (!authHeader) {
|
||
|
|
logger.dim('Request rejected: No Authorization header');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Extract Bearer token
|
||
|
|
const match = authHeader.match(/^Bearer\s+(.+)$/i);
|
||
|
|
if (!match) {
|
||
|
|
logger.dim('Request rejected: Invalid Authorization header format');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const apiKey = match[1];
|
||
|
|
|
||
|
|
// Check if key is valid
|
||
|
|
if (!this.apiKeys.has(apiKey)) {
|
||
|
|
logger.dim('Request rejected: Invalid API key');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get API key from request (if authenticated)
|
||
|
|
*/
|
||
|
|
public getApiKey(req: http.IncomingMessage): string | null {
|
||
|
|
const authHeader = req.headers.authorization;
|
||
|
|
if (!authHeader) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
const match = authHeader.match(/^Bearer\s+(.+)$/i);
|
||
|
|
return match ? match[1] : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add an API key
|
||
|
|
*/
|
||
|
|
public addApiKey(key: string): void {
|
||
|
|
this.apiKeys.add(key);
|
||
|
|
logger.info('API key added');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Remove an API key
|
||
|
|
*/
|
||
|
|
public removeApiKey(key: string): boolean {
|
||
|
|
const removed = this.apiKeys.delete(key);
|
||
|
|
if (removed) {
|
||
|
|
logger.info('API key removed');
|
||
|
|
}
|
||
|
|
return removed;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get count of configured API keys
|
||
|
|
*/
|
||
|
|
public getKeyCount(): number {
|
||
|
|
return this.apiKeys.size;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if authentication is required
|
||
|
|
*/
|
||
|
|
public isAuthRequired(): boolean {
|
||
|
|
return !this.allowNoAuth || this.apiKeys.size > 0;
|
||
|
|
}
|
||
|
|
}
|