b37e15d59a
Phase 18 changes: OAuth/OIDC SSO (Authorization Code + PKCE): - alembic/versions/0015_add_oauth_fields.py: add oauth_sub UNIQUE column, drop NOT NULL on password_hash - src/fabledassistant/services/oauth.py: OIDC discovery (cached), build_auth_url, exchange_code, get_userinfo, find_or_create_oauth_user (sub→email auto-link→create) - src/fabledassistant/routes/auth.py: GET /api/auth/oauth/login and GET /api/auth/oauth/callback; LOCAL_AUTH_ENABLED guards on login/register; /api/auth/status now returns oauth_enabled + local_auth_enabled - src/fabledassistant/config.py: OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_SCOPES, LOCAL_AUTH_ENABLED, oidc_enabled() classmethod - src/fabledassistant/models/user.py: password_hash nullable, oauth_sub field, has_password bool in to_dict() - src/fabledassistant/services/auth.py: create_user accepts password=None + oauth_sub kwarg; authenticate returns None for OAuth-only users; add get_user_by_oauth_sub, link_oauth_sub, update_user_email - frontend: AuthStatus + User types updated; auth store exposes oauthEnabled + localAuthEnabled; LoginView shows SSO button / hides password form accordingly Email change: - PUT /api/auth/email: requires password confirmation for local-auth users, skips check for OAuth-only users; enforces email uniqueness - SettingsView.vue: new Email Address section pre-filled with current email, updates authStore.user in-place on success Docs: - docs/oauth-setup.md: step-by-step Authentik provider setup, example docker-compose env vars, account linking explanation, per-provider issuer URL table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
21 lines
622 B
Python
21 lines
622 B
Python
"""Add OAuth/OIDC fields to users table."""
|
|
|
|
from alembic import op
|
|
|
|
revision = "0015"
|
|
down_revision = "0014"
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.execute("ALTER TABLE users ADD COLUMN IF NOT EXISTS oauth_sub TEXT")
|
|
op.execute(
|
|
"CREATE UNIQUE INDEX IF NOT EXISTS ix_users_oauth_sub ON users(oauth_sub)"
|
|
)
|
|
op.execute("ALTER TABLE users ALTER COLUMN password_hash DROP NOT NULL")
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.execute("ALTER TABLE users ALTER COLUMN password_hash SET NOT NULL")
|
|
op.execute("DROP INDEX IF EXISTS ix_users_oauth_sub")
|
|
op.execute("ALTER TABLE users DROP COLUMN IF EXISTS oauth_sub")
|