37 lines
1.3 KiB
TypeScript
37 lines
1.3 KiB
TypeScript
|
|
import crypto from "node:crypto";
|
||
|
|
import { prisma } from "@/lib/db";
|
||
|
|
|
||
|
|
const PREFIX = "pky_live_";
|
||
|
|
|
||
|
|
export function generateRawKey(): string {
|
||
|
|
return PREFIX + crypto.randomBytes(24).toString("base64url");
|
||
|
|
}
|
||
|
|
|
||
|
|
export function hashKey(raw: string): string {
|
||
|
|
return crypto.createHash("sha256").update(raw).digest("hex");
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Display-only prefix, e.g. "pky_live_abc123…". */
|
||
|
|
export function keyPreview(raw: string): string {
|
||
|
|
return raw.slice(0, PREFIX.length + 6) + "…";
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Verify a raw API key and return its owner, updating lastUsedAt. */
|
||
|
|
export async function verifyApiKey(raw: string | null): Promise<{ userId: string } | null> {
|
||
|
|
if (!raw || !raw.startsWith(PREFIX)) return null;
|
||
|
|
const key = await prisma.apiKey.findUnique({
|
||
|
|
where: { hashedKey: hashKey(raw) },
|
||
|
|
select: { id: true, userId: true, revokedAt: true },
|
||
|
|
});
|
||
|
|
if (!key || key.revokedAt) return null;
|
||
|
|
void prisma.apiKey.update({ where: { id: key.id }, data: { lastUsedAt: new Date() } }).catch(() => {});
|
||
|
|
return { userId: key.userId };
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Extract a bearer key from an Authorization header. */
|
||
|
|
export function bearerKey(authorization: string | null): string | null {
|
||
|
|
if (!authorization) return null;
|
||
|
|
const match = authorization.match(/^Bearer\s+(.+)$/i);
|
||
|
|
return match?.[1] ?? null;
|
||
|
|
}
|