60 lines
1.8 KiB
TypeScript
60 lines
1.8 KiB
TypeScript
/**
|
|
* 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<PartnerTokenContext | null> {
|
|
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();
|
|
};
|
|
}
|