feat: revocable read-only API keys for machine access
Integrations such as the Wabenhain shop sync no longer need a service account with a password (and without MFA). An admin creates a key under Verwaltung > Automatisierung > API-Schlüssel. - Key format ack_<8>_<43>; only its SHA-256 hash is stored, the key is shown exactly once. Optional expiry, "last used" timestamp, revocation takes effect immediately; create/revoke are audit-logged without the key. - A key is not a user: JwtAuthGuard sets request.apiKey, PermissionsGuard admits it only to routes whose permission is among its scopes and never to user-only routes. Scopes come from a short allowlist (shop-sync:read). - Managing keys requires the new permission api-keys:manage. - Keys are excluded from backups on purpose; a restore means issuing a new key. Additive migration (new table). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
ede86c062a
commit
445fb3b94b
21 files changed
+848
-5
No files matched your search
@@ -0,0 +1,17 @@
|
||||
import { apiFetch } from "@/lib/auth/api-client";
|
||||
import { ApiKey, CreateApiKeyInput, CreatedApiKey } from "./types";
|
||||
|
||||
export function fetchApiKeys(): Promise<ApiKey[]> {
|
||||
return apiFetch<ApiKey[]>("/api-keys");
|
||||
}
|
||||
|
||||
export function createApiKey(input: CreateApiKeyInput): Promise<CreatedApiKey> {
|
||||
return apiFetch<CreatedApiKey>("/api-keys", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export function revokeApiKey(id: string): Promise<ApiKey> {
|
||||
return apiFetch<ApiKey>(`/api-keys/${id}/revoke`, { method: "POST" });
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export type ApiKeyScope = "shop-sync:read";
|
||||
|
||||
export const API_KEY_SCOPE_LABELS: Record<ApiKeyScope, string> = {
|
||||
"shop-sync:read": "Shop-Daten lesen (Wabenhain)",
|
||||
};
|
||||
|
||||
export interface ApiKey {
|
||||
id: string;
|
||||
name: string;
|
||||
prefix: string;
|
||||
scopes: ApiKeyScope[];
|
||||
createdByUserId: string | null;
|
||||
createdAt: string;
|
||||
lastUsedAt: string | null;
|
||||
expiresAt: string | null;
|
||||
revokedAt: string | null;
|
||||
}
|
||||
|
||||
export interface CreateApiKeyInput {
|
||||
name: string;
|
||||
scopes: ApiKeyScope[];
|
||||
expiresInDays?: number;
|
||||
}
|
||||
|
||||
export interface CreatedApiKey {
|
||||
apiKey: ApiKey;
|
||||
/** Shown exactly once - the server keeps only a hash. */
|
||||
token: string;
|
||||
}
|
||||
Reference in new issue
Block a user