Skip to content

Add user-selected usernames - #451

Merged
kentcdodds merged 12 commits into
mainfrom
cursor/add-usernames-50f5
May 12, 2026
Merged

kentcdodds merged 12 commits into
mainfrom
cursor/add-usernames-50f5

Conversation

@kentcdodds

@kentcdodds kentcdodds commented May 12, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Require a unique username during signup and store it on the existing users.username column.
  • Add /account/profile.json for reading/updating the signed-in user's username.
  • Add a profile card to the account page for changing usernames.
  • Use the stored username as the authenticated display name in session-aware UI while keeping email-based storage identity stable.
  • Add a D1 backfill migration so existing usernames become email values except me@kentcdodds.com -> kentcdodds, and update seed defaults for valid usernames.

Walkthrough

username-nav-account-playwright-demo.webm
Account page showing username nav and profile email

Testing

  • npm run validate
  • npx playwright test e2e/smoke.spec.ts
  • npx vitest run --config vitest.node.config.ts tools/seed-test-data.node.test.ts packages/worker/client/app-session-refresh.node.test.ts
  • npm run typecheck
  • npm run lint
  • Browser walkthrough against local dev server showing signup username selection and username-based navigation display.
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Accounts now include usernames; signup requires a Username. Profile page/API lets you view and update username and email. Sessions and UI now use username for display names.
  • Bug Fixes / Validation

    • Username normalization, pattern validation, and case-insensitive uniqueness checks with clearer error handling.
  • Data Migration

    • Backfilled usernames for existing accounts.
  • Tests

    • Expanded coverage for profile API, auth signup/login, session handling, and username rules.

Review Change Stack

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@coderabbitai

coderabbitai Bot commented May 12, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements comprehensive username support across the application. Users now provide a username during signup, which is validated, stored, and included in session and profile data. A new account profile API endpoint allows users to view and update their username, with corresponding UI and test updates.

Changes

Username feature implementation

Layer / File(s) Summary
Username validation and database utilities
packages/worker/src/app/username.ts, packages/worker/src/app/database-errors.ts, packages/worker/src/env-schema.ts, packages/worker/src/app/audit-log.ts
New username validation, normalization and requirement text; DB error helper extracts unique-constraint field; environment schema adds WRANGLER_IS_LOCAL_DEV; audit categories include 'account'.
Authentication signup with username
packages/worker/src/app/handlers/auth.ts, packages/worker/src/app/handlers/auth-handler.node.test.ts
Signup flow validates normalized username, checks for duplicate username/email, inserts username into users table, and maps DB unique-constraint errors to field-specific 409 responses. Tests and in-memory test DB extended for username uniqueness.
Session and authenticated user models with username
packages/worker/client/session.ts, packages/worker/src/app/authenticated-user.ts, packages/worker/src/app/handlers/session.ts, packages/worker/src/app/handlers/session-handler.node.test.ts, packages/worker/src/app/authenticated-user.node.test.ts
Session payloads and AuthenticatedAppUser include username. Session handler refactored to a factory that looks up the user row, returns { email, username }, and clears stale auth cookies when the user record is missing. Tests updated.
Account profile API handler and routes
packages/worker/src/app/handlers/account-profile.ts, packages/worker/src/app/handlers/account-profile.node.test.ts, packages/worker/src/app/routes.ts, packages/worker/src/app/router.ts, packages/worker/client/routes/account-approval-shared.ts
New createAccountProfileApiHandler provides GET (read profile) and POST (update username) endpoints. POST normalizes/validates username, checks conflicts, updates DB, logs audit events, and returns appropriate HTTP status codes. Routes wired at /account/profile.json.
Account profile management UI and client wiring
packages/worker/client/routes/account.tsx, e2e/playwright-utils.ts, e2e/auth-test-user.ts, packages/worker/client/app.tsx, packages/worker/client/routes/oauth-authorize.tsx, packages/worker/client/app-session-refresh.node.test.ts
AccountRoute loads profile data and renders a Profile card with a controlled username input, save button with loading/disable logic, and success/error messaging. Playwright helpers updated to accept/derive username and return it with credentials. Client app and OAuth route now use session display name; session-refresh tests updated.
Login/signup form with username field
packages/worker/client/routes/login.tsx
Signup UI conditionally shows a Username input with validation attributes; submission requires username for signup and includes it only in signup payloads.
E2E, Playwright, and test-support updates
tools/mcp-test-support.ts, e2e/playwright-utils.ts, packages/worker/src/app/handlers/auth-handler.node.test.ts, tools/seed-test-data.ts, packages/worker/migrations/0038-backfill-usernames.sql
Test-support types and fixtures updated to include username; auth tests updated for username validation and duplicate-username behavior; seed-test-data defaults and SQL migration added.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • kentcdodds/kody#418: Related — changes to auth/session handling and account-related routes/handlers.
  • kentcdodds/kody#50: Related — overlapping edits to AuthenticatedAppUser and readAuthenticatedAppUser logic.

Poem

🐰 I stitched a name where emails once stood,
A tiny username, tidy and good.
From signup to profile it hops with delight,
Saved and shown from morning to night. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add user-selected usernames' directly and clearly summarizes the main change: implementing functionality for users to select and use usernames.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/add-usernames-50f5

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

cursoragent and others added 3 commits May 12, 2026 15:36
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@kentcdodds
kentcdodds marked this pull request as ready for review May 12, 2026 15:57
@github-actions

github-actions Bot commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-451.kentcdodds.workers.dev

Worker: kody-pr-451
D1: kody-pr-451-db
KV: kody-pr-451-oauth-kv

Mocks:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@e2e/playwright-utils.ts`:
- Around line 42-47: The helper currently treats any HTTP 409 as a successful
seed which can hide username collisions; update the response handling in the
seeding helper (the block that checks response.ok() / response.status() and
returns { email, username, password }) to parse the response body (e.g., await
response.json()) when status === 409 and verify the error indicates the same
user already exists (matching message/code or email/username in the payload);
only swallow the 409 and return credentials if that explicit "already exists for
this user" condition is met, otherwise throw an Error with the status and the
parsed error detail so collisions or other 409 causes fail the test.

In `@packages/worker/client/routes/account.tsx`:
- Around line 41-46: The code uses new URL(href).search which throws when href
is a relative path on the server; modify account.tsx to compute the query string
safely by using a conditional: keep setting href/lastLoadedHref as now, but
replace new URL(href).search with a safe value (e.g. const search = typeof
window === 'undefined' ? (href.includes('?') ? '?' + href.split('?')[1] : '') :
new URL(href).search) and then call fetch(`${accountProfileApiPath}${search}`,
...). Update references around lastLoadedHref/href and the fetch call so
server-side execution never calls new URL with a relative URL.

In `@packages/worker/src/app/database-errors.ts`:
- Around line 4-6: The regex extracting the column name from the SQLite unique
constraint error (the pattern assigned to match in database-errors.ts) only
allows letters and underscores and will miss names with digits; update the
capture group to include digits (for example change ([a-z_]+) to ([a-z0-9_]+) or
use \w+ with the existing case-insensitive flag) so the code that inspects
currentError.message correctly extracts column names like field_123 or col2.

In `@packages/worker/src/app/handlers/session.ts`:
- Around line 32-35: The code uses Number.parseInt on session.id which accepts
partial numeric strings; before converting and querying with
db.findOne(usersTable, { where: { id: userId } }) validate session.id with a
strict numeric check (e.g., test against /^\d+$/) and only call Number.parseInt
and perform the DB lookup when the test passes; otherwise treat userRecord as
null to avoid accidental partial-number lookups.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 950a47d4-3f75-40d3-9ab7-4bd91bfc3b38

📥 Commits

Reviewing files that changed from the base of the PR and between 7527892 and bc27180.

📒 Files selected for processing (20)
  • e2e/auth-test-user.ts
  • e2e/playwright-utils.ts
  • packages/worker/client/routes/account-approval-shared.ts
  • packages/worker/client/routes/account.tsx
  • packages/worker/client/routes/login.tsx
  • packages/worker/client/session.ts
  • packages/worker/src/app/audit-log.ts
  • packages/worker/src/app/authenticated-user.ts
  • packages/worker/src/app/database-errors.ts
  • packages/worker/src/app/handlers/account-profile.node.test.ts
  • packages/worker/src/app/handlers/account-profile.ts
  • packages/worker/src/app/handlers/auth-handler.node.test.ts
  • packages/worker/src/app/handlers/auth.ts
  • packages/worker/src/app/handlers/session-handler.node.test.ts
  • packages/worker/src/app/handlers/session.ts
  • packages/worker/src/app/router.ts
  • packages/worker/src/app/routes.ts
  • packages/worker/src/app/username.ts
  • packages/worker/src/env-schema.ts
  • tools/mcp-test-support.ts

Comment thread e2e/playwright-utils.ts Outdated
Comment thread packages/worker/client/routes/account.tsx Outdated
Comment thread packages/worker/src/app/database-errors.ts Outdated
Comment thread packages/worker/src/app/handlers/session.ts Outdated
Comment thread packages/worker/src/app/database-errors.ts Outdated
Comment thread packages/worker/client/routes/account.tsx Outdated
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Comment thread packages/worker/src/app/authenticated-user.ts
cursoragent and others added 2 commits May 12, 2026 16:14
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/worker/migrations/0038-backfill-usernames.sql`:
- Around line 1-12: The migration currently overwrites all usernames because
both UPDATEs lack a WHERE filter; change them to only touch rows with missing
usernames (e.g., WHERE username IS NULL OR username = '') and avoid
double-writing by applying the special-case mapping for email
'me@kentcdodds.com' first (UPDATE users SET username = 'kentcdodds', updated_at
= CURRENT_TIMESTAMP WHERE email = 'me@kentcdodds.com' AND (username IS NULL OR
username = '')), then set generated usernames only for remaining rows (UPDATE
users SET username = '__username_backfill__' || id, updated_at =
CURRENT_TIMESTAMP WHERE username IS NULL OR username = ''); reference the users
table and the username, email, id, updated_at columns when making these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6727db1a-b6db-4e78-8e05-bbf723975150

📥 Commits

Reviewing files that changed from the base of the PR and between 71acf64 and 9bc3be7.

📒 Files selected for processing (3)
  • packages/worker/migrations/0038-backfill-usernames.sql
  • tools/seed-test-data.node.test.ts
  • tools/seed-test-data.ts
✅ Files skipped from review due to trivial changes (1)
  • tools/seed-test-data.node.test.ts

Comment on lines +1 to +12
UPDATE users
SET
username = '__username_backfill__' || id,
updated_at = CURRENT_TIMESTAMP;

UPDATE users
SET
username = CASE
WHEN email = 'me@kentcdodds.com' THEN 'kentcdodds'
ELSE email
END,
updated_at = CURRENT_TIMESTAMP;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Backfill currently overwrites all usernames, not just missing ones

Line 6 updates every row with no guard, so any pre-existing username gets replaced (email/kentcdodds). It also makes Line 1’s value temporary and rewrites updated_at twice for all users.

Suggested migration shape
-UPDATE users
-SET
-	username = '__username_backfill__' || id,
-	updated_at = CURRENT_TIMESTAMP;
-
 UPDATE users
 SET
 	username = CASE
 		WHEN email = 'me@kentcdodds.com' THEN 'kentcdodds'
 		ELSE email
 	END,
 	updated_at = CURRENT_TIMESTAMP;
+WHERE username IS NULL OR username = '';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
UPDATE users
SET
username = '__username_backfill__' || id,
updated_at = CURRENT_TIMESTAMP;
UPDATE users
SET
username = CASE
WHEN email = 'me@kentcdodds.com' THEN 'kentcdodds'
ELSE email
END,
updated_at = CURRENT_TIMESTAMP;
UPDATE users
SET
username = CASE
WHEN email = 'me@kentcdodds.com' THEN 'kentcdodds'
ELSE email
END,
updated_at = CURRENT_TIMESTAMP
WHERE username IS NULL OR username = '';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/migrations/0038-backfill-usernames.sql` around lines 1 - 12,
The migration currently overwrites all usernames because both UPDATEs lack a
WHERE filter; change them to only touch rows with missing usernames (e.g., WHERE
username IS NULL OR username = '') and avoid double-writing by applying the
special-case mapping for email 'me@kentcdodds.com' first (UPDATE users SET
username = 'kentcdodds', updated_at = CURRENT_TIMESTAMP WHERE email =
'me@kentcdodds.com' AND (username IS NULL OR username = '')), then set generated
usernames only for remaining rows (UPDATE users SET username =
'__username_backfill__' || id, updated_at = CURRENT_TIMESTAMP WHERE username IS
NULL OR username = ''); reference the users table and the username, email, id,
updated_at columns when making these changes.

Comment thread packages/worker/client/session.ts
cursoragent and others added 3 commits May 12, 2026 16:59
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Comment thread e2e/playwright-utils.ts
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Comment thread tools/seed-test-data.ts Outdated
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit eb1d943. Configure here.

WHEN email = 'me@kentcdodds.com' THEN 'kentcdodds'
ELSE email
END,
updated_at = CURRENT_TIMESTAMP;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migration lacks UNIQUE constraint for username column

High Severity

The backfill migration populates the username column but never adds a UNIQUE constraint or unique index on it. The existing schema (from 0001-init.sql) only creates a non-unique index (CREATE INDEX ... idx_users_username). Both auth.ts and account-profile.ts contain catch blocks that handle UNIQUE constraint failed: users.username errors as a race-condition safety net, but those errors can never be raised without a database-level unique constraint. Concurrent signups or profile updates choosing the same username can slip past the application-level findOne pre-check and insert duplicate usernames.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eb1d943. Configure here.

@kentcdodds
kentcdodds merged commit f2ddf85 into main May 12, 2026
5 checks passed
@kentcdodds
kentcdodds deleted the cursor/add-usernames-50f5 branch May 12, 2026 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants