0.1.111 — Restore email/username + password login (Casdoor-down fallback)

User directive verbatim:

"you may need to restore that. Our goal is to keep app running for existing users even when casdoor is down. (keep simple login, disable register, reset passwords, email verification functions.)"

0.1.106 deleted LoginApiView along with the rest of the legacy auth pipeline (register, password reset, email verification, sessions). After Casdoor came online, the assumption was that SSO would always be reachable. The user has now asked to restore only the login endpoint so existing accounts retain a fallback when auth.trance-0.com is unreachable. Register / password reset / email verification stay deleted per the directive — those flows continue to live entirely in Casdoor.

What's restored

POST /api/v1/auth/login/ — the same wire shape the SPA's client.login() already posts ({email, password}, historically with identifier / username aliases).

The new view (backend/creators/api.py, LoginApiView + LoginSerializer) does the minimum:

  1. Looks up the supplied email or username, case-insensitively.
  2. Calls django.contrib.auth.authenticate() against the stored password hash. No code-path bypasses the hasher.
  3. On success, calls Token.objects.get_or_create(user=user) (DRF's stock authtoken_token table — already in INSTALLED_APPS since long before the Casdoor migration).
  4. Returns auth_payload(user, token=token.key, request) — same shape as the Casdoor exchange flow, so the SPA's existing token-handling code keeps working unchanged. The SPA resends the token as Authorization: Token <hex> for every subsequent call, which rest_framework.authentication.TokenAuthentication validates (still in DEFAULT_AUTHENTICATION_CLASSES per notechondria/settings.py:303).

Token.objects.get_or_create is idempotent — repeated logins return the same token row. If you want forced rotation, the existing /auth/rotate-api-key/ route covers it.

What's not restored (deliberately)

Per the user directive:

  • Register — no RegisterApiView is added back.
  • Password reset — no PasswordResetRequestApiView / PasswordResetConfirmApiView. Use Casdoor's user portal.
  • Email verification — no VerifyEmailApiView / ResendVerificationApiView / SMTP code path. The VerificationCode model + _send_code_email helper stay deleted.
  • Per-device session list — no SessionApiView / SessionListApiView / SessionRevokeApiView. The creators.Session model stays deleted; its replacement is the user's Casdoor portal session list.
  • /auth/logout/ — not added back; client-side logout drops the locally-stored token, and the server-side token row stays valid until the user explicitly rotates the key (sufficient for the fallback case).

Files changed

  • backend/creators/api.py
    • New imports: from django.contrib.auth import authenticate, from rest_framework.authtoken.models import Token.
    • New LoginSerializer and LoginApiView classes immediately before SettingsSerializer. Section banner comment notes the restoration scope.
  • backend/notechondria/api_urls.py
    • Added LoginApiView to the creators.api import.
    • Added path("auth/login/", LoginApiView.as_view(), name="auth-login") immediately after the rotate-api-key route, with a comment block documenting the fallback role.

No model changes, no migrations.

Operator runbook

  1. Casdoor stays primary. The editor's signed-out account card surfaces Casdoor SSO as the default CTA (per 0.1.108). Tapping the legacy "Email + password" expander still calls the same client.login() it always has — which now reaches a real endpoint instead of 404'ing.
  2. Existing accounts keep their stored Django password. Because the legacy 0.1.105 auth pipeline used Django's standard hasher, the password hashes in auth_user.password are still valid. No password reset run is needed; users sign in with whatever credential they last set.
  3. New users sign up via Casdoor. There is no in-app registration. Direct them to the Casdoor signup link (<endpoint>/signup/<orgName> — the org-themed signup page, exposed by the SPA via the existing casdoorOrgLoginUrl pattern).
  4. Forgotten password — the user changes it in their Casdoor account portal, then either signs in via SSO or asks an operator to reset their Django password from the Django admin (/admin/auth/user/) for the fallback path.

Verification

  • python3 -m py_compile clean on backend/creators/api.py and backend/notechondria/api_urls.py.
  • rest_framework.authtoken already in INSTALLED_APPS (notechondria/settings.py:100) and DRF stock TokenAuthentication already in DEFAULT_AUTHENTICATION_CLASSES (settings.py:303), so the existing authtoken_token table is reused — no new migration needed.
  • Behavior unchanged for any caller that doesn't post to /auth/login/. The route is purely additive.

Note on the production deploy

The last user-supplied log shows the production backend is still 404'ing on /api/v1/auth/casdoor/config/, which means the deployed image predates 0.1.96 entirely. Until that backend is redeployed:

  • Casdoor SSO won't work (route missing).
  • This round's /auth/login/ won't work either (route also missing).

After redeploy, both work. The 0.1.110 group-ACL gate (CASDOOR_REQUIRED_GROUPS=trance-0/app-notechondria) only applies to JWT auth — the legacy /auth/login/ path is not gated by Casdoor groups, so the fallback works for any active Django user regardless of their Casdoor group membership. If you want to keep the legacy login locked down to a smaller set, restrict auth_user.is_active directly via the Django admin.