0.1.119 — OIDC profile refresh on every login + display_name/avatar_url + drop SocialAccount + Casdoor Manage button + settings menu consistency
User directives in this round (verbatim, condensed):
"For sign in and security settings, add button to redirect user to https://auth.trance-0.com/trance-0 (generated this url from environment variables) to setup
and help text for notify user to contact admin if casdoor backend is off
I see onbackend there is social accounts model, remove that since we use casdoor for now.
I see there is some setting menu inconsistency in portal and editor app. for example the agent skill is in different folder. in editor it is in api settings but in portal it is in signin and security, fix that (remember to use unifided ui when possible. ...)
In this round, you may read and parse the login info from oidc (avatar, firstname last name, email address, etc realted to creator profile. refresh if they made any changes. Note, never change the username to avoid corruptions). P.S. I noteices that we did not create display name atribute for creator model, add that and show display name by default. it should be optional and we show display names when user configured that in public note author names, etc. otherwise use the default settings.
Remember to add the instructions in the docs for setting up casdoor login providers for custom jwt-token"
Done in five docker-verified stages, all squashed into this one release commit so the rolling deploy picks them up atomically.
Backend
Creator profile fields (migration 0033)
backend/creators/migrations/0033_profile_refresh_drop_social.py
makes four schema changes:
Creator.display_name—CharField(max_length=255, blank, default=""). User-facing label preferred overUser.usernameon public surfaces (note bylines, comment headers, sidebar header). Editable from settings (the SPA can PATCH it via the existing/api/v1/settings/endpoint), but the next Casdoor sign-in re-overwrites with whatever the IdP currently has.Creator.avatar_url—URLField(max_length=512, blank, default=""). Remote avatar URL refreshed from the JWT'savatarclaim on every login. The SPA prefers this over the locally-uploadedCreator.imageso a Casdoor profile- pic change propagates everywhere on next login.Creator.casdoor_profile_synced_at—DateTimeField(blank, null). UTC timestamp of the most recent profile refresh. Drives the 5-minute throttle inside_sync_creator_from_claims.SocialAccounttable dropped along with theSocialProviderChoicesenum. Casdoor proxies third-party identities (Google, GitHub, etc.) on its applicationProviderstab now; the only Notechondria-side link isCreator.casdoor_sub. The legacy migration command (migrate_users_to_casdoor) had itsSocialAccount-backed provider pre-population block removed in the same commit sopython manage.py migrate_users_to_casdoor ...keeps working on databases wherecreators_socialaccountis already gone.
_sync_creator_from_claims(creator, claims) helper
New helper in backend/creators/casdoor_auth.py that mirrors
the Nextcloud user_oidc plugin's "Sync user attributes on
every login" pattern. Updates:
Creator.display_name←<CASDOOR_CLAIM_DISPLAY_NAME>Creator.avatar_url←<CASDOOR_CLAIM_AVATAR>(new env var, defaultavatar)User.first_name←<CASDOOR_CLAIM_GIVEN_NAME>User.last_name←<CASDOOR_CLAIM_FAMILY_NAME>User.email←<CASDOOR_CLAIM_EMAIL>
Deliberately not touched:
User.username— changing it breaks Django ORM PK references on every owner-keyed FK and every external link to/api/v1/creators/<username>/. Username is set once at account creation (bind or create-with-password path off the 0.1.118 link challenge) and is stable for the lifetime of the account.Creator.image— the locally-uploaded avatar. Left alone so a user who uploaded a custom image inside Notechondria doesn't have it silently replaced by the Casdoor avatar. The SPA prefersavatar_urlwhen set, falls back toimageotherwise (resolved server-side inauth_payload).
The helper is throttled to 5-minute granularity via
Creator.casdoor_profile_synced_at. Called from:
CasdoorJWTAuthentication.authenticate— every JWT- authenticated request (the throttle keeps DB writes manageable on a busy SPA).CasdoorExchangeApiViewfast path — when the exchange finds an already-linked account.CasdoorLinkBindApiViewandCasdoorLinkCreateApiView— after stampingcasdoor_sub. These re-verify theLinkChallenge.access_tokento recover the full claims dict, then sync.
Wire-shape additions
auth_payload and SettingsSerializer.to_representation now
expose three new fields:
display_name— same priority chain everywhere:Creator.display_name→User.first_name + last_name→User.username.avatar_url—Creator.avatar_url(or empty).image_url— kept for backward compat;Creator.avatar_urlpreferred over the localCreator.imageURL.casdoor_profile_synced_at— ISO-8601 timestamp.
SettingsSerializer.update accepts a writable display_name
field; the change persists locally until the next Casdoor login
overwrites it.
CASDOOR_CLAIM_AVATAR env var
backend/notechondria/settings.py reads
CASDOOR_CLAIM_AVATAR=os.getenv("CASDOOR_CLAIM_AVATAR", "avatar"). Operators with a Casdoor instance that names the
avatar field differently can override (comma-separated list,
first non-empty wins — same pattern as the other
CASDOOR_CLAIM_* vars from 0.1.110).
Frontend
"Manage Casdoor account" button + admin-contact help text
Added to all three apps' Connected Accounts UIs:
- editor:
_ConnectedAccountsSectionineditor_app/lib/modules/settings_sections.dart— gained acasdoorOrgLoginUrlparameter, renders anOutlinedButton("Manage Casdoor account") that navigates the browser to the org-themed login page when the URL is non-empty. Plus the text: "If sign-in is unavailable, contact your Notechondria admin (Casdoor backend may be off)." Threaded from_SignInSecurityPage. - portal: same in
portal_app/lib/modules/settings.dart, threaded from_ConnectedAccountsPageinsettings_pages.dart. - planner: same in
planner_app/lib/modules/settings.dart, threaded inline from the account card.
The URL itself is never hardcoded: it comes from
/api/v1/auth/casdoor/config/'s signin_url field, which the
backend builds from ${CASDOOR_ENDPOINT}/login/${CASDOOR_ORG_NAME}.
For the user's current Northflank env, that resolves to
https://auth.trance-0.com/login/trance-0.
Settings menu consistency — Agent Skill placement
Per the user's complaint that Agent Skill (MCP skill markdown) appears in different sections per app:
- editor: unchanged — Agent Skill already lived on
_ApiSettingsPagenext to the MCP API key + GitHub Sync card. This is the canonical pattern. - portal: moved off
_SignInSecurityPageand onto_ApiSettingsPageso it sits next to the MCP API key controls. Sign-in & Security now shows a one-line pointer to the Account page for Casdoor bind/unlink. - planner: planner has only one settings page (no subpages
yet), so Agent Skill moved out of the inline account card and
into a sibling Card alongside the GitHub Sync card. A
TODOmarker notes the future split into subpages to match the editor + portal pattern.
Deferred work
The user also asked for a deeper unification: "I don't see the
reason for using different login widget for the three apps."
Editor uses its own _buildSignedInAccount /
_buildSignedOutAccount helpers in
editor_app/lib/modules/settings_build.dart; portal + planner
share the AuthHub widget from notechondria_shared/lib/src/ components/auth_dialogs.dart.
Refactoring editor onto AuthHub is a deeper change with more
surface area (different parent state contracts, different
controllers). Deferred to a separate round so this commit
stays focused on data + UX-thread changes the operator can
deploy together.
Files changed
Backend
backend/creators/models.py— Creator field additions; SocialAccount + SocialProviderChoices removed.backend/creators/migrations/0033_profile_refresh_drop_social.py— autogenerated, verified viamanage.py check.backend/creators/casdoor_auth.py—_sync_creator_from_claimshelper; wired intoCasdoorJWTAuthentication.authenticate.backend/creators/api.py—auth_payload+SettingsSerializerthread the new fields; bind/create link endpoints sync profile after stampingcasdoor_sub; exchange fast path syncs after resolving an existing user.backend/creators/admin.py—SocialAccountAdminremoved.backend/creators/management/commands/migrate_users_to_casdoor.py—SocialAccountimport + provider pre-population block removed.backend/notechondria/settings.py—CASDOOR_CLAIM_AVATARenv var (defaultavatar).
Frontend
frontend/editor_app/lib/modules/settings_sections.dart—_ConnectedAccountsSectiongainscasdoorOrgLoginUrl+ Manage button + help text.frontend/editor_app/lib/modules/settings_pages.dart— threadscasdoorOrgLoginUrlinto_ConnectedAccountsSection.frontend/portal_app/lib/modules/settings.dart— same Manage-button pattern in portal's_ConnectedAccountsSection.frontend/portal_app/lib/modules/settings_pages.dart— Agent Skill moved from_SignInSecurityPageto_ApiSettingsPage; threadscasdoorOrgLoginUrl.frontend/planner_app/lib/modules/settings.dart— same Manage-button pattern; Agent Skill moved out of the account card into its own sibling Card alongside GitHub Sync.
Docs
docs/integrations/casdoor-setup.md— new §7 covering the per-login profile sync, theavatarCustom JWT field walk- through (Application → Token → Custom JWT fields → Add), the recommended full custom-fields table, theCASDOOR_CLAIM_AVATARenv var, and the username-stable / image-vs-avatar precedence contract.docs/SUMMARY.md— entry for 0.1.119.
Verification (run locally per AGENTS.md / user directive)
python3 -m py_compileclean across all modified backend files.docker build -f backend/Dockerfile --target builder -t notechondria-build-test:0.1.119 .— completed all 18 stages.- In-container
python manage.py check—System check identified no issues (0 silenced). - In-container
python manage.py makemigrations— produced0033_profile_refresh_drop_social.pycleanly. The migration adds three Creator fields and aDeleteModel("SocialAccount")step. - In-container Django bootstrap smoke test:
Creator._meta.get_fields()includesdisplay_name,avatar_url,casdoor_profile_synced_at.creators.models.SocialAccountimport raisesImportError._sync_creator_from_claimsis callable.auth_payloadsource containsdisplay_name,avatar_url,casdoor_profile_synced_at.SettingsSerializer().fields.keys()includesdisplay_name,avatar_url.
flutter analyzeclean acrossnotechondria_shared,editor_app,portal_app,planner_app— only the pre- existing_socialLinkErrorunused-field warning in planner remains.
Operator follow-up
The new 0033_profile_refresh_drop_social.py migration runs on
the next backend deploy:
- Adds
display_name,avatar_url,casdoor_profile_synced_attocreators_creator. - Drops the
creators_socialaccounttable — destroys the row data permanently. Casdoor doesn't have aSocialAccountanalogue; the per-provider linkage lives on Casdoor's Application > Providers tab. Confirm before redeploying that you don't need any of theSocialAccountrows (you almost certainly don't, since the model has been dead since the Casdoor cutover in 0.1.99).
After the deploy:
- A Casdoor sign-in (existing user, on a refreshed image) will
populate
Creator.display_name/Creator.avatar_urlfrom the JWT on first authenticated request. - The SPA's avatar widgets and byline labels will start showing
the Casdoor display name / avatar URL automatically once the
next page loads —
image_urlpriority chain is resolved server-side inauth_payload. - Add the
avatarCustom JWT field on the Casdoor side per §7 ofdocs/integrations/casdoor-setup.mdif it's not already set up; without it,_sync_creator_from_claimssilently skips the avatar update.