Files
botfights/server/src/middleware/bot-auth.ts
T

71 lines
2.1 KiB
TypeScript
Raw Normal View History

// Bot authentication helper.
// Verifies bot identity via bot_id + secret (SHA256 hash comparison).
// Supports: Authorization header or query params.
import { createHash, timingSafeEqual } from 'crypto'
import { db, schema } from '../db/index.js'
import { eq } from 'drizzle-orm'
import type { Context } from 'hono'
export interface BotAuthContext {
botId: string
botName: string
webhookUrl: string
}
/** Extract and verify bot credentials from request. Returns bot context or error response. */
export async function authenticateBot(c: Context): Promise<BotAuthContext | Response> {
let botId: string | undefined
let secret: string | undefined
// 1. Authorization: Bot <bot_id>:<secret>
const auth = c.req.header('Authorization')
if (auth?.startsWith('Bot ')) {
const parts = auth.slice(4).split(':')
if (parts.length === 2 && parts[0] && parts[1]) {
botId = parts[0]
secret = parts[1]
}
}
// 2. Query params: ?bot_id=xxx&secret=yyy
if (!botId || !secret) {
const qBotId = c.req.query('bot_id')
const qSecret = c.req.query('secret')
if (qBotId && qSecret) {
botId = qBotId
secret = qSecret
}
}
if (!botId || !secret) {
return c.json({ error: 'Authentication required. Use Authorization: Bot <bot_id>:<secret> or query params ?bot_id=...&secret=...' }, 401)
}
const hash = createHash('sha256').update(secret).digest('hex')
const rows = await db.select({
id: schema.bots.id,
name: schema.bots.name,
secretHash: schema.bots.secretHash,
webhookUrl: schema.bots.webhookUrl,
}).from(schema.bots).where(eq(schema.bots.id, botId)).limit(1)
if (rows.length === 0) {
return c.json({ error: 'Invalid bot_id or secret.' }, 401)
}
// Constant-time comparison using Node's native timingSafeEqual
const expected = rows[0].secretHash
if (hash.length !== expected.length ||
!timingSafeEqual(Buffer.from(hash), Buffer.from(expected))) {
return c.json({ error: 'Invalid bot_id or secret.' }, 401)
}
return {
botId: rows[0].id,
botName: rows[0].name,
webhookUrl: rows[0].webhookUrl,
}
}