fix: Add email verification requirement for API v1 and v2 email updates (#24988)

* fix: Add email verification requirement for API v1 and v2 email updates

- API v2 (/v2/me): Implement email verification check when updating primary email
  - Store new email in metadata as emailChangeWaitingForVerification when verification is required
  - Send verification email using sendChangeOfEmailVerification
  - Allow immediate email change if new email is already a verified secondary email
  - Add hasEmailBeenChanged and sendEmailVerification flags to response
  - Add tests to verify email verification behavior
  - Add findVerifiedSecondaryEmail method to UsersRepository
  - Add PrismaFeaturesRepository to MeModule providers

- API v1 (/v1/users/{userId}): Implement same email verification logic
  - Check if email-verification feature flag is enabled
  - Store new email in metadata when verification is required
  - Send verification email for unverified email changes
  - Allow immediate change for verified secondary emails
  - Preserve existing API v1 response format

- Test fixtures: Add createSecondaryEmail method to UserRepositoryFixture

This fixes the vulnerability where users could update their primary email
without verification via API endpoints.

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* update

* update

* refactor: Extract business logic to MeService and add platform-managed bypass

- Create MeService with all business logic extracted from MeController
- Update MeController to delegate to MeService (thin controller pattern)
- Add PrismaWorkerModule to MeModule imports to provide PrismaWriteService
- Add isPlatformManaged bypass: platform-managed users can update email without verification
- Fix test cleanup to use delete by ID instead of email to avoid failures when email changes
- Remove hasEmailBeenChanged and sendEmailVerification response flags per reviewer feedback
- Add comprehensive documentation to @ApiOperation describing email verification behavior
- Update tests to remove flag assertions

Addresses PR comments:
- supalarry: Extract logic to service layer
- supalarry: Allow platform-managed users to update email without verification
- supalarry: Remove response flags and document behavior in @ApiOperation instead
- cubic-dev-ai: Fix module dependency for PrismaFeaturesRepository
- cubic-dev-ai: Fix test cleanup issue

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* docs: Simplify API operation description

Remove internal implementation details about metadata staging and shorten the description to focus on API consumer behavior.

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* fix

* update

* update

* update

* remove comment

* addressed commit

* review addressed

* use repository

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-01-08 15:51:11 +00:00
1 parent 03bb9481a2
commit ccff0045fc
9 files changed
+336 -18

No files matched your search

@@ -434,4 +434,48 @@ export class UsersRepository {
},
});
}
async findVerifiedSecondaryEmail(userId: number, email: string) {
return this.dbRead.prisma.secondaryEmail.findUnique({
where: {
userId_email: {
userId: userId,
email: email,
},
},
select: {
id: true,
emailVerified: true,
},
});
}
async swapPrimaryEmailWithSecondaryEmail(
userId: number,
secondaryEmailId: number,
oldPrimaryEmail: string,
oldPrimaryEmailVerified: Date | null,
newPrimaryEmail: string
) {
const [, updatedUser] = await this.dbWrite.prisma.$transaction([
this.dbWrite.prisma.secondaryEmail.update({
where: {
id: secondaryEmailId,
userId: userId,
},
data: {
email: oldPrimaryEmail,
emailVerified: oldPrimaryEmailVerified,
},
}),
this.dbWrite.prisma.user.update({
where: { id: userId },
data: {
email: newPrimaryEmail,
},
}),
]);
return updatedUser;
}
}