/** * Minimal partner token auth for agentify-help standalone app. * Replaces server/partner-api-routes.ts validatePartnerToken. */ import { pool } from "./db.js"; import crypto from "crypto"; function hashToken(token: string): string { return crypto.createHash("sha256").update(token).digest("hex"); } export interface PartnerTokenContext { id: number; partner_name: string; permissions: string[]; area_codes: string[] | null; } export async function validatePartnerToken( authHeader: string | undefined ): Promise { if (!authHeader?.startsWith("Bearer ws_")) return null; const token = authHeader.slice(7); if (!token.startsWith("ws_")) return null; const hash = hashToken(token); const result = await pool.query( `SELECT id, partner_name, permissions, area_codes FROM partner_api_tokens WHERE token_hash = $1 AND active = true AND revoked_at IS NULL LIMIT 1`, [hash] ); if (!result.rows.length) return null; const r = result.rows[0]; pool.query( `UPDATE partner_api_tokens SET last_used_at = NOW(), use_count = use_count + 1 WHERE id = $1`, [r.id] ).catch(() => {}); return { id: r.id, partner_name: r.partner_name, permissions: (r.permissions as string[]) || [], area_codes: r.area_codes || null, }; } export function requirePermission(permission: string) { return async (req: any, res: any, next: any) => { const ctx = await validatePartnerToken(req.headers.authorization).catch(() => null); if (!ctx) return res.status(401).json({ error: "Unauthorized" }); if (!ctx.permissions.includes(permission) && !ctx.permissions.includes("geo.seed")) { return res.status(403).json({ error: "Forbidden", required: permission }); } (req as any).partner = ctx; next(); }; }