Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
- ApiKey model (SHA-256 hash, prefix, scopes, expiry) + Alembic migration (b040ent) - SsoConfig model for SAML 2.0 IdP settings (attribute mapping, auto-provision) - API key auth integrated into get_current_user (aegis_ prefix detection) - Routers: /api/v1/api-keys (full CRUD + revoke) and /api/v1/sso (metadata, login, callback, config) - python3-saml added to requirements; Dockerfile adds libxmlsec1-dev for SAML XML signing - QA script: 52 assertions covering key lifecycle, API key auth, SSO config Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
76 lines
3.1 KiB
Python
76 lines
3.1 KiB
Python
"""Phase 14: Enterprise Readiness — api_keys and sso_configs tables.
|
|
|
|
Revision ID: b040ent
|
|
Revises: b039exec
|
|
Create Date: 2026-05-20
|
|
"""
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
revision = "b040ent"
|
|
down_revision = "b039exec"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
conn = op.get_bind()
|
|
|
|
# ── api_keys ──────────────────────────────────────────────────────────────
|
|
conn.execute(sa.text("""
|
|
CREATE TABLE IF NOT EXISTS api_keys (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name VARCHAR(200) NOT NULL,
|
|
description TEXT,
|
|
key_prefix VARCHAR(13) NOT NULL,
|
|
key_hash VARCHAR(64) NOT NULL UNIQUE,
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
scopes JSONB NOT NULL DEFAULT '["read"]',
|
|
last_used_at TIMESTAMP WITHOUT TIME ZONE,
|
|
expires_at TIMESTAMP WITHOUT TIME ZONE,
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now()
|
|
)
|
|
"""))
|
|
conn.execute(sa.text(
|
|
"CREATE INDEX IF NOT EXISTS ix_api_keys_user_id ON api_keys (user_id)"
|
|
))
|
|
conn.execute(sa.text(
|
|
"CREATE INDEX IF NOT EXISTS ix_api_keys_key_hash ON api_keys (key_hash)"
|
|
))
|
|
conn.execute(sa.text(
|
|
"CREATE INDEX IF NOT EXISTS ix_api_keys_active ON api_keys (is_active)"
|
|
))
|
|
|
|
# ── sso_configs ───────────────────────────────────────────────────────────
|
|
conn.execute(sa.text("""
|
|
CREATE TABLE IF NOT EXISTS sso_configs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
is_enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
|
provider_name VARCHAR(200),
|
|
sp_entity_id VARCHAR(500),
|
|
sp_acs_url VARCHAR(500),
|
|
sp_slo_url VARCHAR(500),
|
|
sp_certificate TEXT,
|
|
sp_private_key TEXT,
|
|
idp_entity_id VARCHAR(500),
|
|
idp_sso_url VARCHAR(500),
|
|
idp_slo_url VARCHAR(500),
|
|
idp_certificate TEXT,
|
|
attr_email VARCHAR(200) DEFAULT 'email',
|
|
attr_username VARCHAR(200) DEFAULT 'username',
|
|
attr_role VARCHAR(200) DEFAULT 'role',
|
|
default_role VARCHAR(50) DEFAULT 'viewer',
|
|
auto_provision BOOLEAN NOT NULL DEFAULT TRUE,
|
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now(),
|
|
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now()
|
|
)
|
|
"""))
|
|
|
|
|
|
def downgrade() -> None:
|
|
conn = op.get_bind()
|
|
conn.execute(sa.text("DROP TABLE IF EXISTS api_keys CASCADE"))
|
|
conn.execute(sa.text("DROP TABLE IF EXISTS sso_configs CASCADE"))
|