[split-required] [guardrail-change] Enforce 500 LOC budget across all services
Install LOC guardrails (check-loc.sh, architecture.md, pre-commit hook) and split all 44 files exceeding 500 LOC into domain-focused modules: - consent-service (Go): models, handlers, services, database splits - backend-core (Python): security_api, rbac_api, pdf_service, auth splits - admin-core (TypeScript): 5 page.tsx + sidebar extractions - pitch-deck (TypeScript): 6 slides, 3 UI components, engine.ts splits - voice-service (Python): enhanced_task_orchestrator split Result: 0 violations, 36 exempted (pipeline, tests, pure-data files). Go build verified clean. No behavior changes — pure structural splits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
171
consent-service/internal/database/migrate_oauth.go
Normal file
171
consent-service/internal/database/migrate_oauth.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// migrateOAuth creates OAuth 2.0 and 2FA tables (Phases 6-7),
|
||||
// plus default seed data for OAuth clients, cookie categories,
|
||||
// and legal documents.
|
||||
func migrateOAuth(db *DB) error {
|
||||
ctx := context.Background()
|
||||
|
||||
migrations := []string{
|
||||
// =============================================
|
||||
// Phase 6: OAuth 2.0 Authorization Code Flow
|
||||
// =============================================
|
||||
|
||||
// OAuth 2.0 Clients
|
||||
`CREATE TABLE IF NOT EXISTS oauth_clients (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
client_id VARCHAR(64) UNIQUE NOT NULL,
|
||||
client_secret VARCHAR(255),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
redirect_uris JSONB NOT NULL DEFAULT '[]',
|
||||
scopes JSONB NOT NULL DEFAULT '["openid", "profile", "email"]',
|
||||
grant_types JSONB NOT NULL DEFAULT '["authorization_code", "refresh_token"]',
|
||||
is_public BOOLEAN DEFAULT FALSE,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_by UUID REFERENCES users(id),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)`,
|
||||
|
||||
// OAuth 2.0 Authorization Codes
|
||||
`CREATE TABLE IF NOT EXISTS oauth_authorization_codes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
code VARCHAR(255) UNIQUE NOT NULL,
|
||||
client_id VARCHAR(64) NOT NULL REFERENCES oauth_clients(client_id),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
redirect_uri TEXT NOT NULL,
|
||||
scopes JSONB NOT NULL DEFAULT '[]',
|
||||
code_challenge VARCHAR(255),
|
||||
code_challenge_method VARCHAR(10),
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
used_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)`,
|
||||
|
||||
// OAuth 2.0 Access Tokens
|
||||
`CREATE TABLE IF NOT EXISTS oauth_access_tokens (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
token_hash VARCHAR(255) UNIQUE NOT NULL,
|
||||
client_id VARCHAR(64) NOT NULL REFERENCES oauth_clients(client_id),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
scopes JSONB NOT NULL DEFAULT '[]',
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
revoked_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)`,
|
||||
|
||||
// OAuth 2.0 Refresh Tokens
|
||||
`CREATE TABLE IF NOT EXISTS oauth_refresh_tokens (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
token_hash VARCHAR(255) UNIQUE NOT NULL,
|
||||
access_token_id UUID REFERENCES oauth_access_tokens(id) ON DELETE CASCADE,
|
||||
client_id VARCHAR(64) NOT NULL REFERENCES oauth_clients(client_id),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
scopes JSONB NOT NULL DEFAULT '[]',
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
revoked_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)`,
|
||||
|
||||
// =============================================
|
||||
// Phase 7: Two-Factor Authentication (2FA/TOTP)
|
||||
// =============================================
|
||||
|
||||
// User TOTP secrets and recovery codes
|
||||
`CREATE TABLE IF NOT EXISTS user_totp (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID UNIQUE NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
secret VARCHAR(255) NOT NULL,
|
||||
verified BOOLEAN DEFAULT FALSE,
|
||||
recovery_codes JSONB DEFAULT '[]',
|
||||
enabled_at TIMESTAMPTZ,
|
||||
last_used_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)`,
|
||||
|
||||
// 2FA challenges during login
|
||||
`CREATE TABLE IF NOT EXISTS two_factor_challenges (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
challenge_id VARCHAR(255) UNIQUE NOT NULL,
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
used_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)`,
|
||||
|
||||
// Add 2FA required flag to users
|
||||
`ALTER TABLE users ADD COLUMN IF NOT EXISTS two_factor_enabled BOOLEAN DEFAULT FALSE`,
|
||||
`ALTER TABLE users ADD COLUMN IF NOT EXISTS two_factor_verified_at TIMESTAMPTZ`,
|
||||
|
||||
// Phase 6 & 7 Indexes
|
||||
`CREATE INDEX IF NOT EXISTS idx_oauth_clients_client_id ON oauth_clients(client_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_oauth_auth_codes_code ON oauth_authorization_codes(code)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_oauth_auth_codes_user ON oauth_authorization_codes(user_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_oauth_access_tokens_hash ON oauth_access_tokens(token_hash)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_oauth_access_tokens_user ON oauth_access_tokens(user_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_oauth_refresh_tokens_hash ON oauth_refresh_tokens(token_hash)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_oauth_refresh_tokens_user ON oauth_refresh_tokens(user_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_user_totp_user ON user_totp(user_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_two_factor_challenges_id ON two_factor_challenges(challenge_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_two_factor_challenges_user ON two_factor_challenges(user_id)`,
|
||||
|
||||
// Insert default OAuth client for BreakPilot PWA (public client with PKCE)
|
||||
`INSERT INTO oauth_clients (client_id, name, description, redirect_uris, scopes, grant_types, is_public)
|
||||
VALUES (
|
||||
'breakpilot-pwa',
|
||||
'BreakPilot PWA',
|
||||
'Official BreakPilot Progressive Web Application',
|
||||
'["http://localhost:8000/oauth/callback", "http://localhost:8000/app/oauth/callback"]',
|
||||
'["openid", "profile", "email", "consent:read", "consent:write"]',
|
||||
'["authorization_code", "refresh_token"]',
|
||||
true
|
||||
) ON CONFLICT (client_id) DO NOTHING`,
|
||||
|
||||
// Insert default cookie categories
|
||||
`INSERT INTO cookie_categories (name, display_name_de, display_name_en, description_de, description_en, is_mandatory, sort_order)
|
||||
VALUES
|
||||
('necessary', 'Notwendige Cookies', 'Necessary Cookies',
|
||||
'Diese Cookies sind für die Grundfunktionen der Website unbedingt erforderlich.',
|
||||
'These cookies are essential for the basic functions of the website.',
|
||||
true, 1),
|
||||
('functional', 'Funktionale Cookies', 'Functional Cookies',
|
||||
'Diese Cookies ermöglichen erweiterte Funktionen und Personalisierung.',
|
||||
'These cookies enable enhanced functionality and personalization.',
|
||||
false, 2),
|
||||
('analytics', 'Analyse Cookies', 'Analytics Cookies',
|
||||
'Diese Cookies helfen uns zu verstehen, wie Besucher mit der Website interagieren.',
|
||||
'These cookies help us understand how visitors interact with the website.',
|
||||
false, 3),
|
||||
('marketing', 'Marketing Cookies', 'Marketing Cookies',
|
||||
'Diese Cookies werden verwendet, um Werbung relevanter für Sie zu gestalten.',
|
||||
'These cookies are used to make advertising more relevant to you.',
|
||||
false, 4)
|
||||
ON CONFLICT (name) DO NOTHING`,
|
||||
|
||||
// Insert default legal documents
|
||||
`INSERT INTO legal_documents (type, name, description, is_mandatory, sort_order)
|
||||
VALUES
|
||||
('terms', 'Allgemeine Geschäftsbedingungen', 'Die allgemeinen Geschäftsbedingungen für die Nutzung von BreakPilot.', true, 1),
|
||||
('privacy', 'Datenschutzerklärung', 'Informationen über die Verarbeitung Ihrer personenbezogenen Daten.', true, 2),
|
||||
('cookies', 'Cookie-Richtlinie', 'Informationen über die Verwendung von Cookies auf unserer Website.', false, 3),
|
||||
('community', 'Community Guidelines', 'Regeln für das Verhalten in der BreakPilot Community.', true, 4)
|
||||
ON CONFLICT DO NOTHING`,
|
||||
}
|
||||
|
||||
for _, migration := range migrations {
|
||||
if _, err := db.Pool.Exec(ctx, migration); err != nil {
|
||||
return fmt.Errorf("migrateOAuth: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user