Skip to content

Comments

Fix null-safe email validation for legacy users#404

Open
iammajid wants to merge 4 commits intodevelopfrom
fix/nullsafe-user-fields
Open

Fix null-safe email validation for legacy users#404
iammajid wants to merge 4 commits intodevelopfrom
fix/nullsafe-user-fields

Conversation

@iammajid
Copy link
Contributor

Email validation failed when editing users migrated from legacy systems with empty email. This fix makes email handling null-safe and keeps email required for new users and those with existing email, while making it optional for legacy users without one.

Copy link
Member

@overheadhunter overheadhunter left a comment

Choose a reason for hiding this comment

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

fix optional types (same for initialEmail)

name: string;
pictureUrl?: string;
email: string;
email?: string | null;
Copy link
Member

Choose a reason for hiding this comment

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

email?: string already implies string | undefined. Prefer undefined over null

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 718d612.

* Validates email format
*/
static isValidEmail(email: string): boolean {
static isValidEmail(email: string | null | undefined): boolean {
Copy link
Member

Choose a reason for hiding this comment

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

same as above: avoid null, no need for | undefined when using ?

Suggested change
static isValidEmail(email: string | null | undefined): boolean {
static isValidEmail(email?: string): boolean {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 718d612.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 2026

Walkthrough

User email in the backend DTO (UserDto.user.email) was changed to optional. FormValidator.validateUser now accepts an optional email and a new initialEmail, adds isEmailRequired and widens isValidEmail to handle undefined, and changes trimming/requirement logic for create vs edit modes. UserEditCreate forwards initialEmail, computes emailRequired, and trims email defensively before submit. UserList was adjusted so the tooltip title may be undefined when name and email are absent.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: making email validation null-safe to fix issues with legacy users lacking email addresses.
Description check ✅ Passed The description is clearly related to the changeset, explaining the problem (email validation failures for legacy users) and the solution (null-safe email handling with conditional requirements).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/nullsafe-user-fields

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
frontend/src/common/backend.ts (1)

90-90: CreateUserDto.email becomes optional, breaking the type-level creation contract.

Making UserDto.email optional propagates into CreateUserDto via Pick, making email optional there too. The PR explicitly states email must remain required for new users, but backend.users.createUser({ ...withoutEmail }) now compiles without error. The guarantee is only upheld by UI-layer validation, not the type system.

Consider overriding email as required in CreateUserDto:

♻️ Proposed fix
-export type CreateUserDto = Pick<UserDto, 'name' | 'email' | 'firstName' | 'lastName' | 'pictureUrl' | 'realmRoles'> & {
-  password: string;
-};
+export type CreateUserDto = Pick<UserDto, 'name' | 'firstName' | 'lastName' | 'pictureUrl' | 'realmRoles'> & {
+  email: string;
+  password: string;
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/common/backend.ts` at line 90, UserDto made email optional
accidentally makes CreateUserDto (which is derived via Pick<UserDto, ...>) allow
missing email; change CreateUserDto to explicitly require email again by
overriding the inherited optionality—locate the CreateUserDto type and replace
the pure Pick<UserDto, ...> usage with a construction that forces email required
(for example by intersecting or re-mapping the type to include { email: string }
or using Required<Pick<...,'email'>> merged with the other picked fields) so
that CreateUserDto.email is statically required while preserving the rest of the
picked UserDto fields.
frontend/src/components/authority/UserList.vue (1)

83-83: user.email ?? undefined is a no-op for string | undefined.

Since UserDto.email is now typed as string | undefined, the ?? undefined guard has no effect — nullish coalescing only replaces null or undefined with the RHS, so for a value already typed as string | undefined, this is always equivalent to user.email. The expression can be simplified to just user.email.

♻️ Suggested simplification
- <span class="text-xs text-gray-500 truncate" :title="user.firstName || user.lastName ? `${user.firstName ?? ''} ${user.lastName ?? ''}`.trim() : user.email ?? undefined">
+ <span class="text-xs text-gray-500 truncate" :title="user.firstName || user.lastName ? `${user.firstName ?? ''} ${user.lastName ?? ''}`.trim() : user.email">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/authority/UserList.vue` at line 83, The template
expression unnecessarily uses "user.email ?? undefined" which is a no-op given
UserDto.email is already string | undefined; in the span (the title and
displayed text) remove the "?? undefined" and just use "user.email" and keep the
rest of the ternary logic that builds `${user.firstName ?? ''} ${user.lastName
?? ''}`.trim() so the span's title and content use user.firstName/user.lastName
fallback to user.email without the redundant nullish coalescing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/authority/UserEditCreate.vue`:
- Line 398: The current assignment data.email = data.email?.trim() ?? '' forces
undefined legacy emails to become an empty string and thus gets serialized into
UpdateUserDto; change the assignment so that after trimming an empty string
collapses to undefined (use || logic) — e.g., in the UserEditCreate component
replace the nullish-coalescing fallback with a fallback that maps empty-string
to undefined so data.email is undefined for missing emails and will be omitted
from the JSON payload.

---

Nitpick comments:
In `@frontend/src/common/backend.ts`:
- Line 90: UserDto made email optional accidentally makes CreateUserDto (which
is derived via Pick<UserDto, ...>) allow missing email; change CreateUserDto to
explicitly require email again by overriding the inherited optionality—locate
the CreateUserDto type and replace the pure Pick<UserDto, ...> usage with a
construction that forces email required (for example by intersecting or
re-mapping the type to include { email: string } or using
Required<Pick<...,'email'>> merged with the other picked fields) so that
CreateUserDto.email is statically required while preserving the rest of the
picked UserDto fields.

In `@frontend/src/components/authority/UserList.vue`:
- Line 83: The template expression unnecessarily uses "user.email ?? undefined"
which is a no-op given UserDto.email is already string | undefined; in the span
(the title and displayed text) remove the "?? undefined" and just use
"user.email" and keep the rest of the ternary logic that builds
`${user.firstName ?? ''} ${user.lastName ?? ''}`.trim() so the span's title and
content use user.firstName/user.lastName fallback to user.email without the
redundant nullish coalescing.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/authority/UserEditCreate.vue`:
- Around line 94-96: The inline validation shows "invalid email" for inputs with
surrounding whitespace because the guard uses data.email?.trim() but
isValidEmail receives the untrimmed data.email; update the conditional to call
isValidEmail on the trimmed value (use isValidEmail(data.email.trim())) so the
validator receives a trimmed string — this is safe because the truthy guard
(data.email?.trim()) guarantees data.email is a non-null string; change the call
where the template uses isValidEmail to pass the trimmed email in the
UserEditCreate.vue template.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@frontend/src/components/authority/UserEditCreate.vue`:
- Line 398: The current trimming expression in UserEditCreate.vue (data.email =
data.email?.trim() || undefined) is correct and should be left as-is: it
preserves undefined, converts whitespace-only strings to undefined, and trims
valid emails before sending to the backend; do not change this line in the data
handling for the UserEditCreate component.

Comment on lines +105 to +112
/**
* Checks if email is required (always for CREATE, only if previously set for EDIT)
*/
static isEmailRequired(isEditMode: boolean, initialEmail?: string): boolean {
if (!isEditMode) return true;
return !!initialEmail?.trim();
}

Copy link
Member

Choose a reason for hiding this comment

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

legibility for humans is more important than compact code (js will be preprocessed during build anyway)

Suggested change
/**
* Checks if email is required (always for CREATE, only if previously set for EDIT)
*/
static isEmailRequired(isEditMode: boolean, initialEmail?: string): boolean {
if (!isEditMode) return true;
return !!initialEmail?.trim();
}
/**
* Checks if email is required
*/
static isEmailRequired(isEditMode: boolean, initialEmail?: string): boolean {
return isEditMode
? initialEmail?.trim() !== '' // required if previously set
: true; // always required in CREATE mode
}

data.lastName = data.lastName?.trim();
data.name = data.name.trim();
data.email = data.email.trim();
data.email = data.email?.trim() || undefined;
Copy link
Member

Choose a reason for hiding this comment

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

this is already optional

Suggested change
data.email = data.email?.trim() || undefined;
data.email = data.email?.trim();

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