Delete "API Key" page

This commit is contained in:
NeilClack
2026-09-11 12:44:44 -05:00
parent 020289966d
commit a909db39b1
4 changed files with 0 additions and 224 deletions
-1
View File
@@ -50,4 +50,3 @@ CRUD layer has been removed; see `docs/ADMIN_OVERHAUL.md` for the migration note
| `/admin/proclamation-categories` | Draggable reorder |
| `/admin/proclamation-groups` | Draggable reorder |
| `/admin/license-keys` | List-only + rotate |
| `/admin/api-keys` | Super-only key rotation |
@@ -1,62 +0,0 @@
import type { PageServerLoad } from './$types';
import { error } from '@sveltejs/kit';
import { prisma } from '$lib/db';
import { rotateApiKey } from '$lib/server/api-key';
import { isSuper } from '$lib/server/roles';
export const load: PageServerLoad = async ({ locals }) => {
if (!isSuper(locals)) {
throw error(403, 'Only Super admins can manage API keys');
}
const now = new Date();
const activeKey = await prisma.apiKey.findFirst({
where: {
revokedAt: null,
OR: [{ expiresAt: null }, { expiresAt: { gt: now } }]
},
select: { keyPrefix: true, createdAt: true, expiresAt: true },
orderBy: { createdAt: 'desc' }
});
const expiredCount = await prisma.apiKey.count({
where: {
OR: [{ revokedAt: { not: null, lte: now } }, { expiresAt: { not: null, lte: now } }]
}
});
return {
activeKey: activeKey
? {
prefix: activeKey.keyPrefix,
createdAt: activeKey.createdAt.toISOString(),
expiresAt: activeKey.expiresAt?.toISOString() ?? null
}
: null,
totalRotated: expiredCount
};
};
export const actions = {
rotate: async ({ locals }) => {
if (!isSuper(locals)) {
return { success: false, error: 'Not authorized' };
}
const result = await rotateApiKey(false);
return {
success: true,
newKey: result.plaintext,
mode: 'normal' as const,
expiresAt: result.previousExpiresAt?.toISOString() ?? null
};
},
rotateEmergency: async ({ locals }) => {
if (!isSuper(locals)) {
return { success: false, error: 'Not authorized' };
}
const result = await rotateApiKey(true);
return { success: true, newKey: result.plaintext, mode: 'emergency' as const };
}
};
@@ -1,143 +0,0 @@
<script lang="ts">
import type { PageProps } from './$types';
import { enhance } from '$app/forms';
import type { SubmitFunction } from '@sveltejs/kit';
let { data }: PageProps = $props();
let newKey = $state('');
let mode = $state<'normal' | 'emergency' | null>(null);
let expiryDate = $state<string | null>(null);
let toastMessage = $state('');
function handleRotate(emergency: boolean): SubmitFunction {
return ({ cancel }) => {
const msg = emergency
? 'EMERGENCY ROTATION: The current key will be revoked IMMEDIATELY. Continue?'
: 'Rotate the API key? The current key will remain valid for 48 hours.';
if (!confirm(msg)) {
cancel();
return;
}
return async ({ result }) => {
if (result.type === 'success' && result.status === 200) {
if (result.data?.newKey) {
newKey = result.data.newKey;
mode = result.data.mode ?? null;
expiryDate = result.data.expiresAt ?? null;
}
} else {
toastMessage = 'Rotation failed';
}
};
};
}
function copyKey() {
navigator.clipboard.writeText(newKey);
toastMessage = 'Copied to clipboard';
}
function dismissKey() {
newKey = '';
mode = null;
expiryDate = null;
}
</script>
<div class="mx-auto w-full">
<div class="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<h2 class="text-xl font-semibold text-slate-900">API Key Management</h2>
{#if data.activeKey}
<span
class="inline-flex items-center gap-1.5 rounded-full bg-green-100 px-3 py-1 text-xs font-medium text-green-700"
>
Active
</span>
{/if}
</div>
{#if newKey}
<div class="mb-4 rounded-lg border border-slate-200 bg-white p-4 shadow-sm">
<p class="text-sm font-medium text-slate-900">
{mode === 'emergency' ? 'Emergency Rotation Complete' : 'New API Key Generated'}
</p>
<p class="mt-1 text-sm text-slate-600">This key will not be shown again. Copy it now.</p>
<div class="mt-3 rounded-md bg-slate-100 px-3 py-2">
<code class="text-sm break-all text-slate-800">{newKey}</code>
</div>
<div class="mt-3 flex gap-2">
<button
type="button"
onclick={copyKey}
class="inline-flex h-10 items-center justify-center gap-1.5 rounded-md bg-indigo-600 px-4 text-sm font-medium text-white shadow-sm transition hover:bg-indigo-700"
>
Copy to Clipboard
</button>
<button
type="button"
onclick={dismissKey}
class="inline-flex h-10 items-center justify-center rounded-md border border-slate-300 bg-white px-4 text-sm font-medium text-slate-700 shadow-sm transition hover:bg-slate-50"
>
Dismiss
</button>
</div>
{#if mode === 'normal' && expiryDate}
<p class="mt-3 text-xs text-slate-500">
The previous key will remain valid until {new Date(expiryDate).toLocaleString()}
to give in-field devices time to update.
</p>
{/if}
</div>
{/if}
{#if toastMessage}
<div
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700"
>
{toastMessage}
</div>
{/if}
<div class="mb-4 rounded-lg border border-slate-200 bg-white p-4 shadow-sm">
<h3 class="text-base font-semibold text-slate-900">Current Key Status</h3>
{#if data.activeKey}
<div class="mt-3 space-y-1 text-sm text-slate-700">
<div>
Prefix: <code class="rounded bg-slate-100 px-1.5 py-0.5">{data.activeKey.prefix}</code>
</div>
<div>Created: {new Date(data.activeKey.createdAt).toLocaleString()}</div>
{#if data.activeKey.expiresAt}
<div>Expires: {new Date(data.activeKey.expiresAt).toLocaleString()}</div>
{/if}
</div>
{:else}
<p class="mt-2 text-sm text-slate-600">No active API key. Generate one below.</p>
{/if}
{#if data.totalRotated > 0}
<p class="mt-2 text-sm text-slate-600">
{data.totalRotated} previous key(s) have been rotated.
</p>
{/if}
</div>
<div class="flex flex-col gap-3 sm:flex-row">
<form method="POST" action="?/rotate" use:enhance={handleRotate(false)}>
<button
type="submit"
class="inline-flex h-10 items-center justify-center gap-1.5 rounded-md bg-indigo-600 px-4 text-sm font-medium text-white shadow-sm transition hover:bg-indigo-700"
>
Generate New API Key
</button>
</form>
<form method="POST" action="?/rotateEmergency" use:enhance={handleRotate(true)}>
<button
type="submit"
class="inline-flex h-10 items-center justify-center gap-1.5 rounded-md bg-red-600 px-4 text-sm font-medium text-white shadow-sm transition hover:bg-red-700"
>
Emergency Rotation
</button>
</form>
</div>
</div>
-18
View File
@@ -165,17 +165,6 @@
{model.displayName}
</a>
{/each}
<a
href={resolve('/admin/api-keys')}
onclick={() => (adminOpen = false)}
class={`block rounded-md px-3 py-2 text-sm transition ${
isActive('/admin/api-keys')
? 'bg-indigo-50 text-indigo-700'
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
}`}
>
API Keys
</a>
</div>
{/if}
</div>
@@ -317,13 +306,6 @@
{model.displayName}
</a>
{/each}
<a
href={resolve('/admin/api-keys')}
onclick={closeMenu}
class={`rounded-lg px-4 py-3 text-base transition ${activeClass('/admin/api-keys')}`}
>
API Keys
</a>
</div>
{/if}
</div>