7.3 KiB
Plan 03: OAuth Foundation (Pillar 2 — Storage-Layer)
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans.
Goal: OAuth-Token-Speicher in SQLite + verschluesseltes Persistieren via Electron safeStorage. Keine eigentliche OAuth-Flow-Implementierung in Plan 03 — die kommt in einem Folgeplan (Plan 03b), nachdem die Flow-Entscheidung (Authorization Code mit PKCE via System-Browser + localhost-Redirect, oder Implicit via embedded BrowserWindow) bestaetigt ist.
Architecture: Schema-Extension via additives CREATE TABLE IF NOT EXISTS. Crypto-Layer abstrahiert hinter SecureStorage Interface — Electron-safeStorage in Production, In-Memory in Tests. Token-CRUD ueber token-store.ts modul. Twitch ist der erste/einzige Provider in Plan 03 — Interface ist provider-agnostisch fuer spaeter.
Tech Stack: Electron safeStorage (Win Credential Manager im Hintergrund), better-sqlite3, vitest.
Aenderung vs Goal.md
Goal sagt "Device Code Flow". Twitch unterstuetzt das nicht — nur Authorization Code + PKCE, Implicit, oder Client Credentials. Plan 03 macht den Storage-Layer flow-agnostisch fertig. Konkrete Flow-Wahl + Implementation = Plan 03b (separat).
Out of Scope fuer Plan 03
- Eigentliche OAuth-Flow-Implementation (Browser-Window oder System-Browser + Loopback)
- Twitch Helix-Endpunkte mit Token (zB
/users/me) — kommt mit Flow - IPC-Handler
login/logout/whoami— kommt mit Flow - Renderer-UI fuer Multi-Account — kommt mit UI-Plan
- Sub-only-Stream-Aufnahme — kommt mit Live-Rec-Plan
File Structure
Neu:
src/main/infra/secure-storage.ts—SecureStorageinterface + Electron-Impl + MemoryImplsrc/main/infra/secure-storage.test.tssrc/main/domain/token-store.ts— CRUD auf oauth_accountssrc/main/domain/token-store.test.ts
Modifiziert:
src/main/infra/schema-v5.ts— neue Tabelleoauth_accountsCLAUDE.mdtasks/v5.0.0-roadmap.md
Tasks
Task 1: Schema-Extension oauth_accounts
In schema-v5.ts an das Ende der Tabellen-Deklarationen (vor migrations_applied) anhaengen:
CREATE TABLE IF NOT EXISTS oauth_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
provider TEXT NOT NULL,
twitch_user_id TEXT,
login TEXT,
display_name TEXT,
encrypted_access_token TEXT NOT NULL,
encrypted_refresh_token TEXT,
expires_at INTEGER,
scopes_json TEXT,
is_default INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
UNIQUE(provider, twitch_user_id)
);
CREATE INDEX IF NOT EXISTS idx_oauth_provider ON oauth_accounts(provider);
CREATE INDEX IF NOT EXISTS idx_oauth_default ON oauth_accounts(is_default);
- Schema-Konstante erweitern
- db.test.ts: 1 Test adden —
oauth_accountsTabelle existiert nach openDatabase npm run test:unitgruen- Commit:
feat(db): add oauth_accounts table to schema v5
Task 2: secure-storage Modul + 4 Tests
Public Interface:
export interface SecureStorage {
isEncryptionAvailable(): boolean;
encrypt(plaintext: string): string; // base64-encoded ciphertext
decrypt(ciphertext: string): string;
}
export class MemorySecureStorage implements SecureStorage { ... } // for tests
export function createElectronSecureStorage(): SecureStorage; // requires app.whenReady fired
MemoryImpl: verschluesselt nicht, gibt plaintext unveraendert zurueck. Liefert isEncryptionAvailable() === false. Markiert klar im Logger, dass kein Crypto greift — fuer Tests OK.
ElectronImpl: wrappt electron.safeStorage. isEncryptionAvailable() ruft safeStorage.isEncryptionAvailable(). encrypt() ruft safeStorage.encryptString(plaintext) → Buffer → base64-string. decrypt() inverse.
Tests (secure-storage.test.ts):
-
MemoryImpl:
isEncryptionAvailable()returns false -
MemoryImpl:
decrypt(encrypt('hello')) === 'hello' -
MemoryImpl: roundtrip mit multi-byte (
'aeoeue-test') -
MemoryImpl: empty string roundtrip
-
ElectronImpl: skip in vitest-env (Electron nicht verfuegbar), aber Existence-Check via
typeof createElectronSecureStorage === 'function' -
Tests + Modul schreiben
-
npm run test:unitgruen (+5 tests) -
Commit:
feat(auth): SecureStorage interface + Memory + Electron impls (4 tests)
Task 3: token-store Modul + 6 Tests
Public Surface:
export interface TokenRecord {
id: number;
provider: string;
twitchUserId: string | null;
login: string | null;
displayName: string | null;
expiresAt: number | null;
scopes: string[];
isDefault: boolean;
createdAt: number;
updatedAt: number;
}
export interface TokenWriteInput {
provider: string;
twitchUserId?: string;
login?: string;
displayName?: string;
accessToken: string; // plaintext, wird verschluesselt geschrieben
refreshToken?: string;
expiresAt?: number;
scopes?: string[];
isDefault?: boolean;
}
export interface TokenStore {
upsert(input: TokenWriteInput): TokenRecord;
list(provider?: string): TokenRecord[];
getDefault(provider: string): TokenRecord | null;
setDefault(id: number): void;
getAccessToken(id: number): string; // entschluesselt
getRefreshToken(id: number): string | null;
delete(id: number): void;
}
export function createTokenStore(db: DbHandle, storage: SecureStorage): TokenStore;
Tests (mit MemorySecureStorage):
upsertneuer Account → record returned mit id > 0upsertsame provider + twitch_user_id → updated, kein neuer recordlist()liefert alle accountslist('twitch')filtert nach providergetDefault('twitch')liefert isDefault=1, oder nullsetDefault(id)setzt is_default=1 fuer id, 0 fuer alle anderen mit gleichem providergetAccessTokenentschluesselt korrektdeleteentfernt record
(Mind. 6 Tests — gerne mehr.)
- Tests + Modul schreiben
npm run test:unitgruen (+6 tests)- Commit:
feat(auth): token-store CRUD on oauth_accounts (encrypted, 6 tests)
Task 4: Full verification + Version Bump 5.0.0-alpha.2
npm run test:e2e:releaseExit 0 (unit jetzt >= 117)npm version 5.0.0-alpha.2 --no-git-tag-versionnpm run buildExit 0- Commit:
release: 5.0.0-alpha.2 - OAuth foundation (storage layer) - Tag:
git tag v5.0.0-alpha.2
Task 5: Docs
CLAUDE.md unter Key Patterns adden:
Secrets: OAuth-Token (Twitch + spaeter) landen verschluesselt via Electron
safeStorage(Win Credential Manager) inoauth_accountsTabelle der SQLite. Zugriff uebersrc/main/domain/token-store.ts. Memory-Impl fuer Tests.
Roadmap: Plan 03 → DONE, Plan 03b "OAuth Flow Implementation" als neuer NEXT-Eintrag dazu.
- Updates + Commit:
docs: OAuth foundation pattern + roadmap
Done-Definition Plan 03
oauth_accountsTabelle in Schema- SecureStorage Interface + MemoryImpl + ElectronImpl
- TokenStore CRUD getestet
npm run test:e2e:releaseExit 0-
= 11 neue unit-tests (1 schema + 4 secure-storage + 6 token-store)
- Version 5.0.0-alpha.2 getaggt
- CLAUDE.md + Roadmap aktualisiert
Execution Handoff
Inline-Execution. Plan 03b (OAuth Flow Implementation) wird danach geschrieben.