fix(auth): #29 OAuth display-name cascade β add user_name + preferred_username tiers#75
Merged
TortoiseWolfe merged 1 commit intomainfrom May 5, 2026
Merged
Conversation
1 task
β¦_username tiers GitHub OAuth puts the user's @handle in user_metadata.user_name, not .name. The previous cascade (full_name > name > email_prefix) skipped user_name entirely, so GitHub users with no display name set on GitHub fell through to email prefix even though the @handle was a meaningful identifier already provided by the OAuth flow. OIDC providers using preferred_username had the same issue. Cascade is now: full_name > name > user_name > preferred_username > email_prefix > "Anonymous User" Fixed in two places: 1. src/lib/auth/oauth-utils.ts extractOAuthDisplayName() β runtime path, called by populateOAuthProfile() during the OAuth callback. Each tier now trims whitespace; whitespace-only metadata fields fall through instead of producing a whitespace-only display name. Non-string values (e.g. accidental nulls in metadata) are skipped without throwing. 2. supabase/migrations/20251006_complete_monolithic_setup.sql PART 9 one-time UPDATE β mirrors the JS cascade so the SQL bootstrap path produces the same result. NULLIF(TRIM(...), '') handles whitespace- only fields the same way. Note on runtime behavior: create_user_profile() (the on_auth_user_created trigger at line 357) does NOT set display_name β it only inserts (id, created_at, updated_at). At signup display_name starts NULL and populateOAuthProfile() is the sole authoritative populator. The PART 9 UPDATE handles the one-time bootstrap for users who existed before that runtime path landed; idempotent (only fires when display_name IS NULL). Tests: 10 new cases in oauth-utils.test.ts cover the GitHub @handle fallthrough, OIDC preferred_username, whitespace handling, non-string metadata, and realistic Google/GitHub fixture shapes. Verification: - pnpm run type-check: clean - pnpm run lint: clean - pnpm test: 3247/3247 pass (was 3237 before β 10 new tests, all passing) - UPDATE applied to dev Supabase via Management API: 0 rows affected (no existing OAuth users on dev), confirming the WHERE clause is idempotent and selective. Closes #29 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ca61ee7 to
5e905f8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
GitHub OAuth puts the user's @handle in
user_metadata.user_name, not.name. The previous cascade (full_name > name > email_prefix) skippeduser_nameentirely, so GitHub users with no display name set on GitHub fell through to email prefix even though the @handle was a meaningful identifier already provided by the OAuth flow. OIDC providers usingpreferred_usernamehad the same issue.Cascade is now:
full_name > name > user_name > preferred_username > email_prefix > "Anonymous User"What changed
src/lib/auth/oauth-utils.tsβ runtime path. Each tier trims whitespace; whitespace-only fields fall through. Non-string values are skipped without throwing.supabase/migrations/20251006_complete_monolithic_setup.sqlPART 9 β bootstrap path mirrors JS cascade.NULLIF(TRIM(...), '')handles whitespace-only fields.src/lib/auth/oauth-utils.test.tsβ 10 new tests (GitHub @handle, OIDC preferred_username, tier precedence, whitespace handling, realistic Google/GitHub fixtures, non-string metadata).Why root-cause, not workaround
Per user preference: always prefer cleaner long-term solutions. Considered and rejected: detecting "auto-generated email prefix" in
populateOAuthProfileand overwriting on match β that would leave the underlying cascade gap unfixed.Bonus discovery:
create_user_profile()trigger does NOT setdisplay_name, only(id, created_at, updated_at). SopopulateOAuthProfile()is the sole authoritative runtime populator. The PART 9 UPDATE handles only the one-time bootstrap for pre-runtime-path users; idempotent viaWHERE display_name IS NULL.Verification
pnpm run type-check: cleanpnpm run lint: cleanpnpm test: 3247/3247 pass (added 10 new tests)Test plan
Closes #29
π€ Generated with Claude Code