import { execFileSync } from 'node:child_process' import { Buffer } from 'node:buffer' import { createSign, randomUUID } from 'node:crypto' import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs' import { join } from 'node:path' import { homedir } from 'node:os' const SESSION_DIR = join(homedir(), '.tela') const DOCKER_PS_FORMAT = '{{.Names}}\t{{.Label "com.docker.compose.service"}}' const LOCALHOST_TOKEN_SCRIPT = ` import { randomUUID } from 'node:crypto' import { Client } from 'pg' import { symmetricDecrypt, symmetricEncrypt } from 'better-auth/crypto' import { importJWK, exportJWK, generateKeyPair, SignJWT } from 'jose' const betterAuthSecret = process.env.TELA_BETTER_AUTH_SECRET const betterAuthUrl = process.env.TELA_BETTER_AUTH_URL const databaseUrl = process.env.TELA_DATABASE_URL const workspaceId = process.env.TELA_WORKSPACE_ID const workspaceTitle = process.env.TELA_WORKSPACE_TITLE if (!betterAuthSecret || !betterAuthUrl || !databaseUrl || !workspaceId || !workspaceTitle) { throw new Error('Missing localhost auth configuration') } const client = new Client({ connectionString: databaseUrl, }) await client.connect() let privateKey: Awaited> | undefined let kid: string | undefined try { const result = await client.query('select id, private_key from jwks order by created_at desc limit 10') for (const row of result.rows) { try { const privateWebKey = await symmetricDecrypt({ key: betterAuthSecret, data: JSON.parse(row.private_key), }) privateKey = await importJWK(JSON.parse(privateWebKey), 'RS256') kid = row.id break } catch { // Ignore stale rows encrypted with an older Better Auth secret. } } if (!privateKey || !kid) { const keyPair = await generateKeyPair('RS256', { modulusLength: 2048, extractable: true, }) const publicWebKey = await exportJWK(keyPair.publicKey) const privateWebKey = await exportJWK(keyPair.privateKey) kid = randomUUID() const encryptedPrivateKey = await symmetricEncrypt({ key: betterAuthSecret, data: JSON.stringify(privateWebKey), }) await client.query( 'insert into jwks (id, public_key, private_key) values ($1, $2, $3)', [kid, JSON.stringify(publicWebKey), JSON.stringify(encryptedPrivateKey)], ) privateKey = await importJWK(privateWebKey, 'RS256') } } finally { await client.end() } const token = await new SignJWT({ requestId: randomUUID(), workspace: { id: workspaceId, title: workspaceTitle, }, }) .setIssuedAt() .setIssuer(betterAuthUrl) .setAudience(betterAuthUrl) .setSubject('unknown') .setProtectedHeader({ alg: 'RS256', kid }) .setExpirationTime('30d') .sign(privateKey) console.log(token) ` // --- Docker Discovery --- export function extractPublishedDockerPort(output: string): number | null { const match = output.match(/-> [^:]+:(\d+)/) return match ? Number.parseInt(match[1]!, 10) : null } function escapeRegExp(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } export function resolveDockerContainerName(target: string, dockerPsOutput: string): string | null { const containers = dockerPsOutput .split('\n') .map(line => line.trim()) .filter(Boolean) .map((line) => { const [name, service] = line.split('\t') return { name: name?.trim() ?? '', service: service?.trim() || null, } }) const exactName = containers.find(container => container.name === target) if (exactName) { return exactName.name } const exactService = containers.find(container => container.service === target) if (exactService) { return exactService.name } const composeSuffixPattern = new RegExp(`(?:^|-)${escapeRegExp(target)}-\\d+$`) const composeNameMatches = containers.filter(container => composeSuffixPattern.test(container.name)) if (composeNameMatches.length === 1) { return composeNameMatches[0]!.name } return null } function getDockerPsOutput(): string { return execFileSync('docker', ['ps', '--format', DOCKER_PS_FORMAT], { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], }) } export function getDockerPort(container: string, dockerPsOutput?: string): number | null { try { const resolvedContainer = resolveDockerContainerName(container, dockerPsOutput ?? getDockerPsOutput()) ?? container const output = execFileSync('docker', ['port', resolvedContainer], { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], }) return extractPublishedDockerPort(output) } catch { return null } } export function discoverLocalhostServices(): { authApiPort: number | null authApiDbPort: number | null telaApiPort: number | null telaAppPort: number | null } { try { const dockerPsOutput = getDockerPsOutput() return { authApiPort: getDockerPort('auth-api', dockerPsOutput), authApiDbPort: getDockerPort('auth-postgres', dockerPsOutput), telaApiPort: getDockerPort('tela-api', dockerPsOutput), telaAppPort: getDockerPort('tela-app', dockerPsOutput), } } catch { return { authApiPort: null, authApiDbPort: null, telaApiPort: null, telaAppPort: null, } } } // --- RSA Key Parsing --- function parseEnvFile(content: string): Record { return Object.fromEntries( content .replace(/\r\n/g, '\n') .split(/\n(?=[A-Z_]\w*=)/i) .filter(line => !line.trimStart().startsWith('#')) .map((line) => { const firstEqual = line.indexOf('=') if (firstEqual === -1) return [line, ''] const key = line.slice(0, firstEqual) let value = line.slice(firstEqual + 1) // Strip surrounding single or double quotes if present if ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith('"') && value.endsWith('"'))) { value = value.slice(1, -1) } return [key, value] }), ) } export function parseApiGatewayKeys(reposPath: string): { kid: string privateKey: string publicKey: string } { const envPath = join(reposPath, '.repositories', 'api-gateway', '.env') if (!existsSync(envPath)) { throw new Error(`api-gateway .env not found at ${envPath}`) } const content = readFileSync(envPath, 'utf-8') const env = parseEnvFile(content) const kid = env.CURRENT_KEY_ID const privateKey = env.CURRENT_KEY_PRIVATE const publicKey = env.CURRENT_KEY_PUBLIC if (!kid || !privateKey || !publicKey) { throw new Error(`Missing RSA key variables in ${envPath}. Expected: CURRENT_KEY_ID, CURRENT_KEY_PRIVATE, CURRENT_KEY_PUBLIC`) } return { kid, privateKey, publicKey } } // --- Auth API Secret --- export function readAuthApiSecret(reposPath: string): string { const envPath = join(reposPath, 'packages', 'api', '.env') if (!existsSync(envPath)) { throw new Error(`tela-api .env not found at ${envPath}. Expected at: ${envPath}`) } const content = readFileSync(envPath, 'utf-8') const env = parseEnvFile(content) const secret = env.AUTH_API_SECRET if (!secret) { throw new Error(`AUTH_API_SECRET not found in ${envPath}`) } return secret } export function readAuthApiConfig(reposPath: string): { betterAuthSecret: string betterAuthUrl: string databaseUrl: string } { const envPath = join(reposPath, '.repositories', 'auth-api', '.env') if (!existsSync(envPath)) { throw new Error(`auth-api .env not found at ${envPath}`) } const content = readFileSync(envPath, 'utf-8') const env = parseEnvFile(content) const betterAuthSecret = env.BETTER_AUTH_SECRET const betterAuthUrl = env.BETTER_AUTH_URL const databaseUrl = env.DATABASE_URL if (!betterAuthSecret || !betterAuthUrl || !databaseUrl) { throw new Error(`Missing auth-api variables in ${envPath}. Expected: BETTER_AUTH_SECRET, BETTER_AUTH_URL, DATABASE_URL`) } return { betterAuthSecret, betterAuthUrl, databaseUrl, } } export function rewriteDatabaseUrlForHost(databaseUrl: string, hostPort: number): string { const url = new URL(databaseUrl) url.hostname = 'localhost' url.port = String(hostPort) return url.toString() } export function generateLocalhostJwt(opts: { reposPath: string workspace: { id: string, title: string } authApiDbPort: number }): string { const authApiConfig = readAuthApiConfig(opts.reposPath) const hostDatabaseUrl = rewriteDatabaseUrlForHost(authApiConfig.databaseUrl, opts.authApiDbPort) let token: string try { token = execFileSync('bun', ['-e', LOCALHOST_TOKEN_SCRIPT], { cwd: opts.reposPath, encoding: 'utf-8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env, TELA_BETTER_AUTH_SECRET: authApiConfig.betterAuthSecret, TELA_BETTER_AUTH_URL: authApiConfig.betterAuthUrl, TELA_DATABASE_URL: hostDatabaseUrl, TELA_WORKSPACE_ID: opts.workspace.id, TELA_WORKSPACE_TITLE: opts.workspace.title, }, }).trim() } catch (error: unknown) { const stderr = typeof error === 'object' && error !== null && 'stderr' in error ? String(error.stderr ?? '').trim() : '' const details = stderr || (error instanceof Error ? error.message : String(error)) throw new Error(`bun subprocess failed: ${details}`) } if (token.split('.').length !== 3) { throw new Error('Failed to generate a valid localhost session token') } return token } // --- JWT Signing --- export function signLocalhostJwt(opts: { workspace: { id: string, title: string } privateKey: string kid: string }): string { const header = { alg: 'RS256', typ: 'JWT', kid: opts.kid } const b64 = (obj: object) => Buffer.from(JSON.stringify(obj)).toString('base64url') const headerB64 = b64(header) const payloadB64 = b64({ requestId: randomUUID(), workspace: opts.workspace, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60, iss: 'api.tela.com', }) const signature = createSign('RSA-SHA256') .update(`${headerB64}.${payloadB64}`) .sign(opts.privateKey, 'base64url') return `${headerB64}.${payloadB64}.${signature}` } // --- Workspace Fetching --- export type Workspace = { id: string title: string slug: string } export async function fetchWorkspaces(authApiUrl: string, authApiSecret: string): Promise { const response = await fetch(`${authApiUrl}/organizations`, { headers: { Authorization: `Bearer ${authApiSecret}` }, }) if (!response.ok) { throw new Error(`Failed to fetch workspaces: ${response.status} ${response.statusText}`) } const { data } = await response.json() as { data: Workspace[] } return data } // --- Session Management --- export function saveLocalhostSession(token: string): void { mkdirSync(SESSION_DIR, { recursive: true }) writeFileSync(join(SESSION_DIR, 'session.local'), token, { mode: 0o600 }) } export function writeLocalhostEnv(apiPort: number, appPort: number | null): void { mkdirSync(SESSION_DIR, { recursive: true }) const apiUrl = `http://localhost:${apiPort}` const appUrl = appPort ? `http://localhost:${appPort}` : 'http://localhost:3000' writeFileSync( join(SESSION_DIR, '.env'), `TELA_API_URL=${apiUrl}\nTELA_APP_URL=${appUrl}\n`, { mode: 0o600 }, ) } export function readSavedReposPath(): string | null { const saved = join(SESSION_DIR, 'tela-repo-path') if (existsSync(saved)) { return readFileSync(saved, 'utf-8').trim() || null } return null } export function saveReposPath(reposPath: string): void { mkdirSync(SESSION_DIR, { recursive: true }) writeFileSync(join(SESSION_DIR, 'tela-repo-path'), reposPath, { mode: 0o600 }) }