51 lines
1.9 KiB
TypeScript
51 lines
1.9 KiB
TypeScript
import crypto from "crypto";
|
|
import { NextResponse } from "next/server";
|
|
import { getDb, type ApiTokenRow } from "./db";
|
|
|
|
export type Scope = "read:domains" | "write:domains" | "read:analytics" | "read:hits";
|
|
export const ALL_SCOPES: Scope[] = ["read:domains", "write:domains", "read:analytics", "read:hits"];
|
|
|
|
export function generateToken(): { plaintext: string; hash: string } {
|
|
const random = crypto.randomBytes(32).toString("hex");
|
|
const plaintext = `nrx_${random}`;
|
|
const hash = crypto.createHash("sha256").update(plaintext).digest("hex");
|
|
return { plaintext, hash };
|
|
}
|
|
|
|
function hashToken(plaintext: string): string {
|
|
return crypto.createHash("sha256").update(plaintext).digest("hex");
|
|
}
|
|
|
|
export type AuthedToken = {
|
|
id: number;
|
|
name: string;
|
|
scopes: Scope[];
|
|
};
|
|
|
|
export function authenticateToken(req: Request): AuthedToken | null {
|
|
const auth = req.headers.get("authorization");
|
|
if (!auth || !auth.toLowerCase().startsWith("bearer ")) return null;
|
|
const token = auth.slice(7).trim();
|
|
if (!token.startsWith("nrx_")) return null;
|
|
|
|
const hash = hashToken(token);
|
|
const row = getDb()
|
|
.prepare("SELECT * FROM api_tokens WHERE token_hash = ? AND revoked_at IS NULL LIMIT 1")
|
|
.get(hash) as ApiTokenRow | undefined;
|
|
if (!row) return null;
|
|
|
|
getDb().prepare("UPDATE api_tokens SET last_used_at = ? WHERE id = ?").run(Date.now(), row.id);
|
|
|
|
let scopes: Scope[] = [];
|
|
try { scopes = JSON.parse(row.scopes); } catch {}
|
|
return { id: row.id, name: row.name, scopes };
|
|
}
|
|
|
|
export function requireScope(req: Request, scope: Scope): AuthedToken | NextResponse {
|
|
const t = authenticateToken(req);
|
|
if (!t) return NextResponse.json({ error: "unauthorized", code: "no_token" }, { status: 401 });
|
|
if (!t.scopes.includes(scope)) {
|
|
return NextResponse.json({ error: "forbidden", code: "missing_scope", required: scope }, { status: 403 });
|
|
}
|
|
return t;
|
|
}
|