feat(store): OAuth tables migration (forgejo-mcp-broker-cpb)
Adds migrations/0002_oauth_tables.sql per design.md §4.2: clients, auth_codes, access_tokens, refresh_tokens. Cascading foreign keys guarantee that revoking a client tears down every dependent row, and that a refresh token can never outlive its access token. Storage choices: - Broker access/refresh tokens stored as hex-encoded SHA-256 hashes; plaintext leaves the broker exactly once (when handed to the MCP client). Lookups by hash are O(log n) via the PK index. - Forgejo tokens stored cleartext (subprocess spawning needs them). At-rest protection is the volume permissions + optional encrypted volume; application-layer encryption is tracked as backlog item -sd4. - Timestamps are unix epoch INTEGERs, set by the application — keeps deadline comparisons trivial and lets phase 5c inject a test clock. - Tables are not STRICT to stay consistent with the phase-1 broker_meta table; converting both is a future cleanup if we want it. Tests verify column sets via PRAGMA table_info, expected indexes are present, the FK CASCADE works in both directions (client → tokens, and access_token → refresh_token), and the oauth_schema_version marker is written. Existing migration-count assertions parameterised on embeddedMigrationCount so adding a third migration only needs that constant bumped. Closes forgejo-mcp-broker-cpb. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c5b19110c8
commit
e4a7baa0bc
4 changed files with 333 additions and 6 deletions
|
|
@ -14,6 +14,11 @@ func tempStorePath(t *testing.T) string {
|
|||
return filepath.Join(t.TempDir(), "broker.db")
|
||||
}
|
||||
|
||||
// embeddedMigrationCount is the number of *.sql files shipped under
|
||||
// internal/store/migrations/. Bump it when adding a migration so the
|
||||
// idempotency tests stay accurate.
|
||||
const embeddedMigrationCount = 2
|
||||
|
||||
func TestOpen_FreshDB_AppliesMigrations(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
s, err := store.Open(ctx, tempStorePath(t))
|
||||
|
|
@ -33,14 +38,14 @@ func TestOpen_FreshDB_AppliesMigrations(t *testing.T) {
|
|||
t.Errorf("schema_version = %q, want %q", version, "1")
|
||||
}
|
||||
|
||||
// schema_migrations should have recorded the initial migration.
|
||||
// schema_migrations should have recorded every embedded migration.
|
||||
var count int
|
||||
row = s.DB().QueryRowContext(ctx, `SELECT COUNT(*) FROM schema_migrations`)
|
||||
if err := row.Scan(&count); err != nil {
|
||||
t.Fatalf("counting schema_migrations: %v", err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Errorf("schema_migrations row count = %d, want 1", count)
|
||||
if count != embeddedMigrationCount {
|
||||
t.Errorf("schema_migrations row count = %d, want %d", count, embeddedMigrationCount)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -68,8 +73,8 @@ func TestOpen_ReopenIsIdempotent(t *testing.T) {
|
|||
`SELECT COUNT(*) FROM schema_migrations`).Scan(&count); err != nil {
|
||||
t.Fatalf("counting schema_migrations: %v", err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Errorf("after reopen, schema_migrations count = %d, want 1", count)
|
||||
if count != embeddedMigrationCount {
|
||||
t.Errorf("after reopen, schema_migrations count = %d, want %d", count, embeddedMigrationCount)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue