feat(enterprise): Phase 14 — API Key Management + SSO/SAML 2.0
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
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>
This commit is contained in:
75
backend/alembic/versions/b040_enterprise_readiness.py
Normal file
75
backend/alembic/versions/b040_enterprise_readiness.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""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"))
|
||||
Reference in New Issue
Block a user