import { Hono } from 'hono' import { SignJWT } from 'jose' import { z } from 'zod' import { configuration } from '../../configuration' import { constantTimeEquals } from '../../utils/crypto' import { parseAccessToken } from '../../utils/accessToken' import { validateTelegramInitData } from '../telegramInitData' import { getOrCreateOwnerId } from '../ownerId' import type { WebAppEnv } from '../middleware/auth' import type { Store } from '../../store' const telegramAuthSchema = z.object({ initData: z.string() }) const accessTokenAuthSchema = z.object({ accessToken: z.string() }) const authBodySchema = z.union([telegramAuthSchema, accessTokenAuthSchema]) export function createAuthRoutes(jwtSecret: Uint8Array, store: Store): Hono { const app = new Hono() app.post('/auth', async (c) => { const json = await c.req.json().catch(() => null) const parsed = authBodySchema.safeParse(json) if (!parsed.success) { return c.json({ error: 'Invalid body' }, 400) } let userId: number let username: string | undefined let firstName: string | undefined let lastName: string | undefined let namespace: string // Access Token authentication (CLI_API_TOKEN) if ('accessToken' in parsed.data) { const parsedToken = parseAccessToken(parsed.data.accessToken) if (!parsedToken || !constantTimeEquals(parsedToken.baseToken, configuration.cliApiToken)) { return c.json({ error: 'Invalid access token' }, 401) } userId = await getOrCreateOwnerId() firstName = 'Web User' namespace = parsedToken.namespace } else { if (!configuration.telegramEnabled || !configuration.telegramBotToken) { return c.json({ error: 'Telegram authentication is disabled. Configure TELEGRAM_BOT_TOKEN.' }, 503) } // Telegram initData authentication const result = validateTelegramInitData(parsed.data.initData, configuration.telegramBotToken) if (!result.ok) { return c.json({ error: result.error }, 401) } const telegramUserId = String(result.user.id) const storedUser = store.getUser('telegram', telegramUserId) if (!storedUser) { return c.json({ error: 'not_bound' }, 401) } userId = await getOrCreateOwnerId() username = result.user.username firstName = result.user.first_name lastName = result.user.last_name namespace = storedUser.namespace } const token = await new SignJWT({ uid: userId, ns: namespace }) .setProtectedHeader({ alg: 'HS256' }) .setIssuedAt() .setExpirationTime('15m') .sign(jwtSecret) return c.json({ token, user: { id: userId, username, firstName, lastName } }) }) return app }