0.1.97 — Casdoor migration phase 3 (Flutter SDK leg)

Phase 3 of the auth migration plan. Frontend now speaks Casdoor end to end: every app probes /auth/casdoor/config/ at boot, surfaces a "Continue with Casdoor SSO" button when the backend reports configured: true, redirects to the configured Casdoor signin URL, and handles the state=casdoor round-trip via the existing AppShellOAuthMixin.handleOAuthCallback plumbing. Shadow mode is still the default — buttons stay hidden when env vars aren't set.

Shared (notechondria_shared)

  • AuthClient interface gains two methods:
    • getCasdoorConfig()GET /api/v1/auth/casdoor/config/. Returns {configured: bool, ...}; the second branch fires only when configured.
    • casdoorExchange(code, {state})POST /api/v1/auth/casdoor/exchange/. Returns the standard auth_payload shape so the existing applyAuthPayload keeps working unchanged.
  • AppShellOAuthMixin.launchOAuth extended for provider == 'casdoor': probes the config endpoint, builds a same-origin redirect URI from Uri.base, and redirects to the configured signin_url with state=casdoor.
  • AppShellOAuthMixin.handleOAuthCallback recognizes state=casdoor and routes the code to casdoorExchange, swallowing the legacy bind/login branches that the per-provider flows use. Casdoor bind isn't wired in this round — account linking lands later if the use case shows up.
  • AuthHub (login dialog) + editor's _buildSignedOutAccount card both gain an onCasdoorLogin prop. Casdoor button is rendered as a FilledButton.tonalIcon (primary) when configured; falls back to the existing Google / GitHub outlines when not.

Per-app

Editor / portal / planner each:

  • client.dart (or http_client.dart) gets the two new methods.
  • _SettingsPage gains an onCasdoorLogin prop and forwards it to its login surface (editor's _buildSignedOutAccount for the account card; portal/planner's AuthHub).
  • app_shell.dart gains a _casdoorConfigured boolean state field, populated by a fire-and-forget probe in _loadInitialData (unawaited(...) inside core/initial_data). Failures are swallowed silently — shadow mode and transient outages just leave the flag at false.
  • app_shell (or editor's build_helpers.dart) wires onCasdoorLogin: _casdoorConfigured ? () => launchOAuth('casdoor', intent: 'login') : null.

Verification

flutter analyze clean across editor_app, portal_app, planner_app. No new errors, no new warnings beyond the pre- existing unused-* and surface-deprecation lints unrelated to this round.

The end-to-end flow can be smoke-tested by setting CASDOOR_* env vars on the backend, signing in via the new SSO button, and observing the redirect-back populate Creator.casdoor_sub (the backend phase-2 logic from 0.1.96 takes over from there).

Carryover

  • Casdoor account-binding flow (link Casdoor to an existing legacy account post-login). Out of scope for phase 3 because the email-iexact backfill in _resolve_user already handles the common case automatically.
  • Phase 4: cutover (disable LoginApiView / RegisterApiView, freeze Session writes).
  • Phase 5: cleanup (delete every legacy auth class / serializer / template / helper enumerated in docs/integrations/casdoor-migration.md).
  • 0.1.94 GitHub Sync items: push-side conflict resolution + asset rotation/pruning.