Skip to content

fix(username): normalize username handling to lowercase#240

Open
Aditya30ag wants to merge 4 commits into
vishnukothakapu:mainfrom
Aditya30ag:fix/username-case-normalization
Open

fix(username): normalize username handling to lowercase#240
Aditya30ag wants to merge 4 commits into
vishnukothakapu:mainfrom
Aditya30ag:fix/username-case-normalization

Conversation

@Aditya30ag

@Aditya30ag Aditya30ag commented May 27, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Fixes inconsistent username handling caused by case-sensitivity mismatches between username availability checks and username creation.

Changes made

  • Added a shared username normalization helper
  • Normalized usernames to lowercase before validation and database persistence
  • Updated availability checks to use the same normalization flow
  • Ensured frontend submissions use normalized usernames
  • Added regression test coverage for duplicate-case username scenarios

Problem solved

Previously, usernames like John and john could behave inconsistently because availability checks were case-insensitive while storage remained case-sensitive.

This PR ensures username validation, storage, availability checks, and profile URLs all follow the same lowercase normalization behavior.

Issue #239

Summary by CodeRabbit

  • Bug Fixes

    • Username availability and validation consistently use a normalized form (trimmed/lowercased), preventing duplicates and mismatches.
    • Creation requests with missing/invalid usernames now fail fast; uniqueness conflicts return proper 409 responses and are handled in a safer update flow.
  • New Features

    • Availability endpoint is rate-limited (429 on excess) and returns up to 5 validated suggestions when taken.
    • Profile workflows now persist and check normalized usernames consistently.
  • Tests

    • Added tests covering username normalization and validation.
  • Chores

    • Added a test script to run the test suite.

Review Change Stack

@vercel

vercel Bot commented May 27, 2026

Copy link
Copy Markdown

@Aditya30ag is attempting to deploy a commit to the vishnukothakapu's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds normalizeUsername (trim + lowercase), updates validateUsername to operate on normalized input and enforce lowercase, applies normalization in username check/create API handlers and profile workflow, adds tests for validation/normalization, and includes the new test file in the npm test script.

Changes

Username Normalization

Layer / File(s) Summary
Username normalization function and validation
lib/validations/username.ts, lib/validations/username.test.ts
New exported normalizeUsername trims and lowercases input. validateUsername normalizes input first, enforces lowercase-equality, and runs required/regex/length checks on the normalized value. Tests verify normalization and validation behavior.
Profile workflow normalization
lib/profileWorkflow.ts
upsertProfileDraft, isProfileUsernameAvailable, publishProfileDraft, and rollbackProfileVersion normalize usernames before validation, availability checks, lookups, and when composing the published snapshot; rollback rechecks normalized availability inside transactions.
Username check endpoint normalization
app/api/username/check/route.ts
GET /api/username/check adds per-client rate-limiting, normalizes the username query param, returns unavailable if normalization yields no usable value, validates suggestion candidates before availability checks, and uses a jsonResponse helper with Cache-Control.
Username creation endpoint normalization
app/api/username/create/route.ts
POST handler requires username be a string, normalizes and validates it, checks uniqueness in both user and userAlias using the normalized value inside a prisma.$transaction, and persists normalizedUsername; maps specific errors (including Prisma P2002) to 409 responses.
Test script update
package.json
Adds the new username validation test file to the npm test script command.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels

level2

Suggested reviewers

  • vedhapprakashni

Poem

🐰 I trim each edge and whisper small,
lowercase hops where capitals fall.
Checks and creates both sing the same tune,
suggestions scurry beneath the moon.
A tidy username—soft as a paw’s call.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change—normalizing username handling to lowercase—which is the core objective throughout all modified files.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@Aditya30ag

Copy link
Copy Markdown
Contributor Author

I had also test it and all tests passes!!
image

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
lib/profileWorkflow.ts (1)

220-236: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Recheck userAlias in the publish transaction guard.

The TOCTOU guard currently checks tx.user only. A concurrent UserAlias insert for the same username can still slip through and violate the user+alias uniqueness rule.

Proposed fix
-      const takenByOther = await tx.user.findFirst({
-        where: { username: afterSnapshot.username, NOT: { id: userId } },
-      });
-      if (takenByOther) {
+      const [takenByOtherUser, takenByOtherAlias] = await Promise.all([
+        tx.user.findFirst({
+          where: { username: afterSnapshot.username, NOT: { id: userId } },
+        }),
+        tx.userAlias.findFirst({
+          where: { username: afterSnapshot.username, NOT: { userId } },
+        }),
+      ]);
+      if (takenByOtherUser || takenByOtherAlias) {
         throw new Error("Username already taken");
       }
🤖 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 `@lib/profileWorkflow.ts` around lines 220 - 236, The TOCTOU guard that
rechecks username availability uses only tx.user.findFirst; also re-check
tx.userAlias inside the same transaction to prevent a concurrent UserAlias
insert from colliding. Inside the username-change branch (where
beforeSnapshot.username !== afterSnapshot.username) add a transactional query
like tx.userAlias.findFirst({ where: { username: afterSnapshot.username, NOT: {
userId } } }) and throw the same "Username already taken" error if it exists
(ensuring you exclude any alias already owned by this user); keep this check
before you proceed to create the alias.
app/api/username/create/route.ts (1)

41-47: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not return the full User record from this endpoint.

prisma.user.update without select can include sensitive columns (notably password), which should never be serialized in API responses.

Proposed fix
     const user = await prisma.user.update({
       where: { id: userId },
       data: { username: normalizedUsername },
+      select: {
+        id: true,
+        username: true,
+        name: true,
+        bio: true,
+        image: true,
+      },
     });
🤖 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 `@app/api/username/create/route.ts` around lines 41 - 47, The endpoint
currently returns the full User object from prisma.user.update (in route.ts),
which can include sensitive fields like password; modify the prisma.user.update
call to include an explicit select clause (e.g., select: { id: true, username:
true, email: true, createdAt: true } or whatever safe subset you need) so only
non-sensitive fields are returned, assign that result to the user variable, and
then return NextResponse.json({ success: true, user }, { status: 200 }) as
before; do not return the raw update result without select.
🤖 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 `@app/api/username/check/route.ts`:
- Around line 19-43: The route currently treats any normalizedUsername as valid
and generates suggestions that may not pass the shared validateUsername check;
update the handler to first run validateUsername(normalizedUsername) and return
available: false if it fails, and also filter the suggestions array (the
candidates construction using abbr, `${normalizedUsername}.dev`,
`the${normalizedUsername}`, etc.) through validateUsername before de-duplicating
and returning them; keep the existing isAvailable call but only use it for
inputs that passed validateUsername and only propose candidates that pass
validateUsername as well.

---

Outside diff comments:
In `@app/api/username/create/route.ts`:
- Around line 41-47: The endpoint currently returns the full User object from
prisma.user.update (in route.ts), which can include sensitive fields like
password; modify the prisma.user.update call to include an explicit select
clause (e.g., select: { id: true, username: true, email: true, createdAt: true }
or whatever safe subset you need) so only non-sensitive fields are returned,
assign that result to the user variable, and then return NextResponse.json({
success: true, user }, { status: 200 }) as before; do not return the raw update
result without select.

In `@lib/profileWorkflow.ts`:
- Around line 220-236: The TOCTOU guard that rechecks username availability uses
only tx.user.findFirst; also re-check tx.userAlias inside the same transaction
to prevent a concurrent UserAlias insert from colliding. Inside the
username-change branch (where beforeSnapshot.username !==
afterSnapshot.username) add a transactional query like tx.userAlias.findFirst({
where: { username: afterSnapshot.username, NOT: { userId } } }) and throw the
same "Username already taken" error if it exists (ensuring you exclude any alias
already owned by this user); keep this check before you proceed to create the
alias.
🪄 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: fd2fa135-9fe7-46bc-8f17-9e29ba363abb

📥 Commits

Reviewing files that changed from the base of the PR and between 0685853 and 83fc1de.

📒 Files selected for processing (6)
  • app/api/username/check/route.ts
  • app/api/username/create/route.ts
  • lib/profileWorkflow.ts
  • lib/validations/username.test.ts
  • lib/validations/username.ts
  • package.json

Comment thread app/api/username/check/route.ts
const normalizedUsername = normalizeUsername(username);

const validation = validateUsername(username);
const validation = validateUsername(normalizedUsername);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[validateUsername] currently allows uppercase; prefer enforcing a canonical rule (e.g. lowercase only) and normalize input before calling [validateUsername] so checks are consistent.

return { valid: false, error: "Username must be at least 3 characters" };
}
if (username.length > 30) {
if (normalizedUsername.length > 30) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The max-length error says "under 30 characters" but the code uses > 30. Make message precise ("at most 30 characters") to avoid confusion.

@Aditya30ag

Copy link
Copy Markdown
Contributor Author

Hello @Anushreebasics
I had inmplemented the requested changes

usernames are now normalized to lowercase before validation and persistence
validation now enforces lowercase as the canonical rule
the length error now says “at most 30 characters” to match the actual check

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/validations/username.ts (1)

1-1: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Align usernameRegex with lowercase-only policy.

usernameRegex still permits uppercase (A-Z), which conflicts with the lowercase-only rule and error text. Tightening the regex keeps the rule self-consistent if this exported regex is reused elsewhere.

Suggested diff
-export const usernameRegex = /^[a-zA-Z0-9-]+$/;
+export const usernameRegex = /^[a-z0-9-]+$/;

Also applies to: 19-23

🤖 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 `@lib/validations/username.ts` at line 1, usernameRegex currently allows
uppercase letters; update the exported usernameRegex to enforce lowercase-only
characters (use a pattern that permits only a–z, 0–9 and hyphen, e.g. replace
the A-Z character class with a-z) so it matches the lowercase-only policy and
error text; update any other occurrences of usernameRegex in the file (the
repeated definitions around lines 19–23) to the same lowercase-only pattern to
keep behavior consistent across exports.
🤖 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 `@lib/validations/username.test.ts`:
- Around line 10-12: The test title "validateUsername accepts mixed case input
after normalization" is misleading because the assertion calls
validateUsername("linkid-user") (all lowercase); rename the test description to
accurately reflect the assertion (e.g., "validateUsername accepts lowercase
input") or update the assertion to use a mixed-case input if you intended to
test normalization; locate the test that calls validateUsername in
username.test.ts and update the string passed to test(...) or the
validateUsername argument accordingly.

---

Outside diff comments:
In `@lib/validations/username.ts`:
- Line 1: usernameRegex currently allows uppercase letters; update the exported
usernameRegex to enforce lowercase-only characters (use a pattern that permits
only a–z, 0–9 and hyphen, e.g. replace the A-Z character class with a-z) so it
matches the lowercase-only policy and error text; update any other occurrences
of usernameRegex in the file (the repeated definitions around lines 19–23) to
the same lowercase-only pattern to keep behavior consistent across exports.
🪄 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: cb407fde-1ee2-4a63-addd-3d477419e094

📥 Commits

Reviewing files that changed from the base of the PR and between 83fc1de and a76b89d.

📒 Files selected for processing (2)
  • lib/validations/username.test.ts
  • lib/validations/username.ts

Comment thread lib/validations/username.test.ts
@Aditya30ag Aditya30ag requested a review from Anushreebasics May 27, 2026 07:01
Comment thread app/api/username/check/route.ts Outdated
@@ -1,4 +1,5 @@
import prisma from "@/lib/prisma";
import { normalizeUsername } from "@/lib/validations/username";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggestions like ${username}.dev include dots and will fail validation—filter candidates using [validateUsername] before returning.

Comment thread lib/validations/username.test.ts Outdated
test("validateUsername rejects uppercase usernames", () => {
assert.equal(validateUsername("LinkID-User").valid, false);
assert.equal(validateUsername(" LINKID-USER ").valid, false);
}); No newline at end of file

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

add a new line at the end of file

import prisma from "@/lib/prisma";
import { normalizeUsername } from "@/lib/validations/username";
import { NextResponse } from "next/server";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Consider rate-limiting/caching this endpoint to prevent username enumeration.

`${normalizedUsername}.${year}`,
`${normalizedUsername}${rand}`,
].filter(Boolean) as string[])];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Don’t return the full Prisma [user] object—return a minimal safe projection (e.g., [id], [username], [name])

`${normalizedUsername}${rand}`,
].filter(Boolean) as string[])];

const suggestions: string[] = [];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Avoid TOCTOU race by performing update + alias creation inside a transaction (or restrict endpoint to initial username set).

@vercel

vercel Bot commented May 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
linkid Ready Ready Preview, Comment May 27, 2026 10:08am

@Aditya30ag

Copy link
Copy Markdown
Contributor Author

@Anushreebasics
I had address the requested changes issue

@coderabbitai coderabbitai 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.

♻️ Duplicate comments (1)
app/api/username/check/route.ts (1)

63-71: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Main username is not validated before reporting availability.

The endpoint can return available: true for usernames containing invalid characters (e.g., AB!@#$CDab!@#$cd). The create endpoint would then reject this with a validation error, breaking the check→create flow.

Add validation after normalization:

🐛 Proposed fix
     if (!normalizedUsername) {
         return jsonResponse({ available: false, suggestions: [] });
     }
+
+    const baseValidation = validateUsername(normalizedUsername);
+    if (!baseValidation.valid) {
+        return jsonResponse({ available: false, suggestions: [] });
+    }

     const available = await isAvailable(normalizedUsername);
🤖 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 `@app/api/username/check/route.ts` around lines 63 - 71, After normalization,
the code must validate the normalizedUsername before calling isAvailable or
returning available: true; add a check (using the existing username validation
function if one exists—e.g., validateUsername or isValidUsername—or apply the
same regex/rules used by the create flow) in route.ts where normalizedUsername
is set and before the isAvailable call, and if invalid return jsonResponse({
available: false, suggestions: [] }); so the check endpoint mirrors create's
validation and never reports true for invalid usernames.
🧹 Nitpick comments (1)
app/api/username/check/route.ts (1)

5-43: ⚡ Quick win

Rate limit state lacks cleanup for expired entries.

The rateLimitState Map accumulates entries for every unique client IP but never removes them after they expire. In long-running server deployments this can grow unbounded.

Consider pruning stale entries periodically or switching to an LRU-based cache:

♻️ Suggested cleanup approach
+function pruneExpiredEntries() {
+    const now = Date.now();
+    for (const [key, value] of rateLimitState) {
+        if (value.resetAt <= now) {
+            rateLimitState.delete(key);
+        }
+    }
+}
+
 function isRateLimited(request: Request): boolean {
     const key = getClientKey(request);
     const now = Date.now();
+    // Prune occasionally (e.g., every 100 calls or via a time check)
+    if (rateLimitState.size > 1000) {
+        pruneExpiredEntries();
+    }
     const current = rateLimitState.get(key);
🤖 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 `@app/api/username/check/route.ts` around lines 5 - 43, The rateLimitState Map
(used by isRateLimited and keyed via getClientKey) never removes expired entries
which can grow unbounded; update the implementation to prune stale entries
(entries whose resetAt <= Date.now())—either by (a) running a periodic cleanup
task that iterates rateLimitState and deletes expired keys, or (b) performing an
in-line cleanup pass in isRateLimited before using the map; ensure the cleanup
references RATE_LIMIT_WINDOW_MS and RATE_LIMIT_MAX_REQUESTS consistently and
preserves the existing logic that creates/updates the per-key {count, resetAt}
objects.
🤖 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.

Duplicate comments:
In `@app/api/username/check/route.ts`:
- Around line 63-71: After normalization, the code must validate the
normalizedUsername before calling isAvailable or returning available: true; add
a check (using the existing username validation function if one exists—e.g.,
validateUsername or isValidUsername—or apply the same regex/rules used by the
create flow) in route.ts where normalizedUsername is set and before the
isAvailable call, and if invalid return jsonResponse({ available: false,
suggestions: [] }); so the check endpoint mirrors create's validation and never
reports true for invalid usernames.

---

Nitpick comments:
In `@app/api/username/check/route.ts`:
- Around line 5-43: The rateLimitState Map (used by isRateLimited and keyed via
getClientKey) never removes expired entries which can grow unbounded; update the
implementation to prune stale entries (entries whose resetAt <=
Date.now())—either by (a) running a periodic cleanup task that iterates
rateLimitState and deletes expired keys, or (b) performing an in-line cleanup
pass in isRateLimited before using the map; ensure the cleanup references
RATE_LIMIT_WINDOW_MS and RATE_LIMIT_MAX_REQUESTS consistently and preserves the
existing logic that creates/updates the per-key {count, resetAt} objects.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0394efa5-6ade-4b52-b291-a01f2841e0aa

📥 Commits

Reviewing files that changed from the base of the PR and between a76b89d and be491b8.

📒 Files selected for processing (3)
  • app/api/username/check/route.ts
  • app/api/username/create/route.ts
  • lib/validations/username.test.ts
✅ Files skipped from review due to trivial changes (1)
  • lib/validations/username.test.ts

`${short}${year}`,
`${username}.${year}`,
`${username}${rand}`,
`${normalizedUsername}.${year}`,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this file returns candidates with dots (e.g. .dev, [.${year}]) that violate [validateUsername] — filter candidates with the validator.

prisma.user.findUnique({ where: { username } }),
prisma.userAlias.findUnique({ where: { username } }),
]);
const user = await prisma.$transaction(async (tx) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

returns the full Prisma [user] — return a minimal projection (id, username, name).

const normalizedUsername = normalizeUsername(username);

const validation = validateUsername(username);
const validation = validateUsername(normalizedUsername);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

updates username without transaction/alias creation. Either restrict to initial set or perform update + alias creation inside a transaction (see [publishProfileDraft] behaviour)

Comment thread lib/profileWorkflow.ts
if (data.username) {
const basicValidation = validateUsername(data.username);
const normalizedUsername = normalizeUsername(data.username);
const basicValidation = validateUsername(normalizedUsername);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[rollbackProfileVersion]does not re-check username availability before applying historical usernames — add availability check and alias handling.

@Anushreebasics Anushreebasics left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Normalize inputs ([.trim().toLowerCase()]) consistently before validation in both check and create endpoints.

@Aditya30ag

Aditya30ag commented May 27, 2026

Copy link
Copy Markdown
Contributor Author

@Anushreebasics

  1. usernames are normalized to lowercase before validation and persistence in both the check and create flows
  2. invalid suggestion candidates are filtered through the validator before being returned
  3. the create endpoint now uses a transaction and returns only a minimal safe projection
  4. rollback now re-checks username availability before restoring a historical username
  5. the max-length validation message now matches the actual rule

@coderabbitai coderabbitai 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.

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 `@lib/profileWorkflow.ts`:
- Around line 430-450: The outer condition currently skips the
username-availability recheck when beforeSnapshot.username is falsy; change the
logic to run the rollback branch whenever beforeSnapshot.username !==
afterSnapshot.username, and inside that branch perform the collision checks only
when afterSnapshot.username is present (normalize via normalizeUsername and call
tx.user.findFirst / tx.userAlias.findFirst to detect conflicts before assigning
afterSnapshot.username), while only calling ensureUsernameAliases(tx, userId,
beforeSnapshot.username) when beforeSnapshot.username is present; update the
block around beforeSnapshot/afterSnapshot handling and references to
normalizeUsername, tx.user.findFirst, tx.userAlias.findFirst,
afterSnapshot.username, beforeSnapshot.username, ensureUsernameAliases, and
userId accordingly.
🪄 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: faf0db21-7106-4d90-8f93-3c20193eefae

📥 Commits

Reviewing files that changed from the base of the PR and between be491b8 and 25d8931.

📒 Files selected for processing (1)
  • lib/profileWorkflow.ts

Comment thread lib/profileWorkflow.ts
Comment on lines 430 to 450
if (beforeSnapshot.username && beforeSnapshot.username !== afterSnapshot.username) {
if (afterSnapshot.username) {
const normalizedUsername = normalizeUsername(afterSnapshot.username);
const takenByOther = await tx.user.findFirst({
where: { username: normalizedUsername, NOT: { id: userId } },
select: { id: true },
});

const aliasTakenByOther = await tx.userAlias.findFirst({
where: { username: normalizedUsername, NOT: { userId } },
select: { id: true },
});

if (takenByOther || aliasTakenByOther) {
throw new Error("Username already taken");
}

afterSnapshot.username = normalizedUsername;
}

await ensureUsernameAliases(tx, userId, beforeSnapshot.username);

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't skip rollback collision checks on null → username restores.

Line 430 still gates the new availability recheck on beforeSnapshot.username. If the current profile has no username and the historical version does, rollback skips the user/userAlias check entirely and can restore a username that's now reserved by someone else.

Suggested fix
-    if (beforeSnapshot.username && beforeSnapshot.username !== afterSnapshot.username) {
+    if (beforeSnapshot.username !== afterSnapshot.username) {
       if (afterSnapshot.username) {
         const normalizedUsername = normalizeUsername(afterSnapshot.username);
         const takenByOther = await tx.user.findFirst({
           where: { username: normalizedUsername, NOT: { id: userId } },
           select: { id: true },
         });

         const aliasTakenByOther = await tx.userAlias.findFirst({
           where: { username: normalizedUsername, NOT: { userId } },
           select: { id: true },
         });

         if (takenByOther || aliasTakenByOther) {
           throw new Error("Username already taken");
         }

         afterSnapshot.username = normalizedUsername;
       }

-      await ensureUsernameAliases(tx, userId, beforeSnapshot.username);
+      if (beforeSnapshot.username) {
+        await ensureUsernameAliases(tx, userId, beforeSnapshot.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 `@lib/profileWorkflow.ts` around lines 430 - 450, The outer condition currently
skips the username-availability recheck when beforeSnapshot.username is falsy;
change the logic to run the rollback branch whenever beforeSnapshot.username !==
afterSnapshot.username, and inside that branch perform the collision checks only
when afterSnapshot.username is present (normalize via normalizeUsername and call
tx.user.findFirst / tx.userAlias.findFirst to detect conflicts before assigning
afterSnapshot.username), while only calling ensureUsernameAliases(tx, userId,
beforeSnapshot.username) when beforeSnapshot.username is present; update the
block around beforeSnapshot/afterSnapshot handling and references to
normalizeUsername, tx.user.findFirst, tx.userAlias.findFirst,
afterSnapshot.username, beforeSnapshot.username, ensureUsernameAliases, and
userId accordingly.

@Anushreebasics Anushreebasics left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There are still some issues that were suggested but were not resolved, pls complete all the suggestions completely, then only ping for reviewing:
[route.ts]: suggestion generation still returns invalid usernames like ones with dots, so some suggestions can never be accepted.
[route.ts]: username input is still not normalized consistently, and the endpoint still returns the full Prisma user object.
[route.ts]: username change flow still does not create an alias for the previous username or do the update in a transaction.
[profileWorkflow.ts]: rollback still applies old usernames without rechecking availability, so conflicts can still happen.
[username.ts]: the length error text is still a bit inaccurate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants