Kyle Pope
bcfebbc9ae
feat(backend): Phase 1 passwordless login — migration, models, toggle endpoints, unlock, delete guard, admin controls
- Migration 062: adds users.passwordless_enabled and system_config.allow_passwordless (both default false)
- User model: passwordless_enabled field after must_change_password
- SystemConfig model: allow_passwordless field after enforce_mfa_new_users
- auth.py login(): block passwordless-enabled accounts from password login path (403) with audit log
- auth.py auth_status(): change has_passkeys query to full COUNT, add passkey_count + passwordless_enabled to response
- auth.py get_current_user(): add /api/auth/passkeys/login/begin and /login/complete to lock_exempt set
- passkeys.py: add PasswordlessEnableRequest + PasswordlessDisableRequest schemas
- passkeys.py: PUT /passwordless/enable — verify password, check system config, require >= 2 passkeys, set flag
- passkeys.py: POST /passwordless/disable/begin — generate user-bound challenge for passkey auth ceremony
- passkeys.py: PUT /passwordless/disable — verify passkey auth response, clear flag, update sign count
- passkeys.py: PasskeyLoginCompleteRequest.unlock field — passkey re-auth into locked session without new session
- passkeys.py: delete guard — 409 if passwordless user attempts to drop below 2 passkeys
- schemas/admin.py: add passwordless_enabled to UserListItem + UserDetailResponse; add allow_passwordless to SystemConfigResponse + SystemConfigUpdate; add TogglePasswordlessRequest
- admin.py: PUT /users/{user_id}/passwordless — admin-only disable (enabled=False only), revokes all sessions, audit log
- admin.py: update_system_config handles allow_passwordless field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 00:15:39 +08:00
..
2026-02-15 16:13:41 +08:00
2026-02-16 01:31:46 +08:00
2026-02-20 07:37:43 +08:00
2026-02-20 13:15:43 +08:00
2026-02-21 12:11:02 +08:00
2026-02-21 19:07:35 +08:00
2026-02-22 00:37:21 +08:00
2026-02-22 01:33:45 +08:00
2026-02-22 03:22:44 +08:00
2026-02-22 11:58:19 +08:00
2026-02-22 17:34:16 +08:00
2026-02-23 01:20:36 +08:00
2026-02-23 10:55:27 +08:00
2026-02-23 17:04:12 +08:00
2026-02-23 19:59:38 +08:00
2026-02-23 21:24:59 +08:00
2026-02-24 01:02:19 +08:00
2026-02-24 04:07:51 +08:00
2026-02-24 21:10:26 +08:00
2026-02-24 21:10:26 +08:00
2026-02-25 01:04:20 +08:00
2026-02-25 04:04:23 +08:00
2026-02-25 04:34:21 +08:00
2026-02-25 04:18:05 +08:00
2026-02-25 10:03:12 +08:00
2026-02-26 19:19:04 +08:00
2026-02-26 19:06:25 +08:00
2026-02-26 19:06:25 +08:00
2026-02-26 19:06:25 +08:00
2026-02-27 04:49:57 +08:00
2026-02-26 19:06:25 +08:00
2026-02-26 19:06:25 +08:00
2026-02-26 19:06:25 +08:00
2026-02-27 06:06:13 +08:00
2026-02-27 05:41:16 +08:00
2026-02-27 19:20:47 +08:00
2026-02-27 22:40:20 +08:00
2026-03-02 19:21:11 +08:00
2026-03-04 02:10:16 +08:00
2026-03-04 02:10:16 +08:00
2026-03-04 02:10:16 +08:00
2026-03-04 02:10:16 +08:00
2026-03-04 02:10:16 +08:00
2026-03-04 07:17:31 +08:00
2026-03-04 07:34:13 +08:00
2026-03-04 08:37:01 +08:00
2026-03-06 03:22:44 +08:00
2026-03-06 03:22:44 +08:00
2026-03-06 03:22:44 +08:00
2026-03-06 03:22:44 +08:00
2026-03-06 03:22:44 +08:00
2026-03-12 19:00:55 +08:00
2026-03-13 00:08:45 +08:00
2026-03-15 02:47:27 +08:00
2026-03-16 20:27:01 +08:00
2026-03-17 00:59:36 +08:00
2026-03-17 03:30:19 +08:00
2026-03-17 03:18:35 +08:00
2026-03-17 03:18:35 +08:00
2026-03-17 03:54:54 +08:00
2026-03-17 22:46:00 +08:00
2026-03-18 00:15:39 +08:00