container lifecycle management

This commit is contained in:
2026-03-12 15:13:38 -04:00
parent e99ef5d2dd
commit b9cc397e05
61 changed files with 6880 additions and 31 deletions

View File

@@ -0,0 +1,146 @@
import type { FastifyRequest, FastifyBaseLogger } from 'fastify';
import { UserService } from '../db/user-service.js';
import { ChannelType, type AuthContext } from '../types/user.js';
import type { ContainerManager } from '../k8s/container-manager.js';
export interface AuthenticatorConfig {
userService: UserService;
containerManager: ContainerManager;
logger: FastifyBaseLogger;
}
/**
* Multi-channel authenticator
* Handles authentication for WebSocket, Telegram, and other channels
*/
export class Authenticator {
private config: AuthenticatorConfig;
constructor(config: AuthenticatorConfig) {
this.config = config;
}
/**
* Authenticate WebSocket connection via JWT token
* Also ensures the user's container is running
*/
async authenticateWebSocket(
request: FastifyRequest
): Promise<AuthContext | null> {
try {
const token = this.extractBearerToken(request);
if (!token) {
this.config.logger.warn('No bearer token in WebSocket connection');
return null;
}
const userId = await this.config.userService.verifyWebToken(token);
if (!userId) {
this.config.logger.warn('Invalid JWT token');
return null;
}
const license = await this.config.userService.getUserLicense(userId);
if (!license) {
this.config.logger.warn({ userId }, 'User license not found');
return null;
}
// Ensure container is running (may take time if creating new container)
this.config.logger.info({ userId }, 'Ensuring user container is running');
const { mcpEndpoint, wasCreated } = await this.config.containerManager.ensureContainerRunning(
userId,
license
);
this.config.logger.info(
{ userId, mcpEndpoint, wasCreated },
'Container is ready'
);
// Update license with actual MCP endpoint
license.mcpServerUrl = mcpEndpoint;
const sessionId = `ws_${userId}_${Date.now()}`;
return {
userId,
channelType: ChannelType.WEBSOCKET,
channelUserId: userId, // For WebSocket, same as userId
sessionId,
license,
authenticatedAt: new Date(),
};
} catch (error) {
this.config.logger.error({ error }, 'WebSocket authentication error');
return null;
}
}
/**
* Authenticate Telegram webhook
* Also ensures the user's container is running
*/
async authenticateTelegram(telegramUserId: string): Promise<AuthContext | null> {
try {
const userId = await this.config.userService.getUserIdFromChannel(
'telegram',
telegramUserId
);
if (!userId) {
this.config.logger.warn(
{ telegramUserId },
'Telegram user not linked to platform user'
);
return null;
}
const license = await this.config.userService.getUserLicense(userId);
if (!license) {
this.config.logger.warn({ userId }, 'User license not found');
return null;
}
// Ensure container is running
this.config.logger.info({ userId }, 'Ensuring user container is running');
const { mcpEndpoint, wasCreated } = await this.config.containerManager.ensureContainerRunning(
userId,
license
);
this.config.logger.info(
{ userId, mcpEndpoint, wasCreated },
'Container is ready'
);
// Update license with actual MCP endpoint
license.mcpServerUrl = mcpEndpoint;
const sessionId = `tg_${telegramUserId}_${Date.now()}`;
return {
userId,
channelType: ChannelType.TELEGRAM,
channelUserId: telegramUserId,
sessionId,
license,
authenticatedAt: new Date(),
};
} catch (error) {
this.config.logger.error({ error }, 'Telegram authentication error');
return null;
}
}
/**
* Extract bearer token from request headers
*/
private extractBearerToken(request: FastifyRequest): string | null {
const auth = request.headers.authorization;
if (!auth || !auth.startsWith('Bearer ')) {
return null;
}
return auth.substring(7);
}
}