Private
Public Access
1
0

Add API key authentication for programmatic access

- New ApiKey model in Prisma schema (tenant + user scoped)
- Keys are SHA-256 hashed, only shown once on creation
- Bearer token auth in hooks.server.ts (alongside session cookies)
- CRUD endpoint at /api/account/api-keys (max 10 per user)
- Keys prefixed with pnc_ for easy identification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-03-08 11:34:56 -04:00
parent 44e5af6c2b
commit d8b3935fc5
4 changed files with 178 additions and 6 deletions
+44
View File
@@ -1,4 +1,5 @@
import bcrypt from 'bcrypt';
import crypto from 'crypto';
import { prisma } from './db.js';
import { withBypassRLS } from './rls.js';
@@ -70,3 +71,46 @@ export function sessionCookieOptions(expiresAt: Date) {
expires: expiresAt
};
}
// ─── API Keys ────────────────────────────────────────────────
export function hashApiKey(key: string): string {
return crypto.createHash('sha256').update(key).digest('hex');
}
export function generateApiKey(): { key: string; prefix: string } {
const key = `pnc_${crypto.randomBytes(32).toString('hex')}`;
const prefix = key.slice(0, 12);
return { key, prefix };
}
export async function validateApiKey(key: string) {
const keyHash = hashApiKey(key);
const apiKey = await prisma.apiKey.findUnique({
where: { keyHash },
include: {
user: {
select: {
id: true,
email: true,
name: true,
globalRole: true,
tenantRole: true,
tenantId: true,
avatarUrl: true
}
}
}
});
if (!apiKey) return null;
// Update last used timestamp (fire and forget)
prisma.apiKey.update({
where: { id: apiKey.id },
data: { lastUsedAt: new Date() }
}).catch(() => {});
return apiKey;
}