internal/serviceaccount: Service-Accounts als eigenstaendige Identitaetsklasse (eigene Tabelle service_accounts, getrennt von internal/user — Zitadel- Vorbild). Store.IssueToken gibt den Klartext-Token NUR einmal an den Aufrufer zurueck, gespeichert wird ausschliesslich der SHA-256-Hash (Akzeptanzkriterium 1). Scopes und optionale Ablaufzeit sind Teil des Tokens selbst (Akzeptanzkriterium 2). Store.Verify prueft Widerruf/Ablauf/Scope bei JEDEM Aufruf direkt gegen die Datenbank — kein Cache dazwischen, ein widerrufenes Token wird ab dem naechsten Request sofort abgewiesen (Akzeptanzkriterium 3). Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Token ausschliesslich gehasht gespeichert (Stichprobe in der Datenbank) — TestIssueToken_StoresOnlyHash: token_hash-Spalte direkt gelesen, enthaelt nachweislich nicht den Klartext, 32-Byte-SHA-256-Laenge bestaetigt. PASS. 2. Widerrufenes Token wird beim naechsten Request zuverlaessig abgewiesen — TestRevoke_TakesEffectImmediately: Verify vor Widerruf erfolgreich, sofort danach ErrTokenInvalid. PASS. 3. Scope-Verletzung wird korrekt abgewiesen — TestVerify_RejectsInsufficientScope: Token mit scope=read wird fuer scope=write abgewiesen (ErrScopeInsufficient), fuer scope=read akzeptiert. PASS. Zusaetzlich: TestVerify_RejectsExpiredToken belegt die optionale zeitliche Befristung aus Akzeptanzkriterium 2. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
21 lines
918 B
SQL
21 lines
918 B
SQL
-- Service-Accounts & API-Token (IAM-09, siehe core-kanban/tickets/IAM-09.md).
|
|
-- Service-Accounts sind eine eigenstaendige Identitaetsklasse neben
|
|
-- menschlichen Benutzern (users), nicht dieselbe Tabelle (Zitadel-Vorbild).
|
|
CREATE TABLE service_accounts (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- token_hash enthaelt NIEMALS den Klartext-Token, nur dessen SHA-256-Hash.
|
|
CREATE TABLE api_tokens (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
service_account_id UUID NOT NULL REFERENCES service_accounts(id),
|
|
token_hash BYTEA NOT NULL UNIQUE,
|
|
scopes TEXT[] NOT NULL DEFAULT '{}',
|
|
expires_at TIMESTAMPTZ,
|
|
revoked_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|