Skip to content

fix(admin-form): validate email domain before transfer ownership#9235

Open
faizkhairi wants to merge 2 commits intoopengovsg:developfrom
faizkhairi:fix/transfer-ownership-domain-validation
Open

fix(admin-form): validate email domain before transfer ownership#9235
faizkhairi wants to merge 2 commits intoopengovsg:developfrom
faizkhairi:fix/transfer-ownership-domain-validation

Conversation

@faizkhairi
Copy link
Copy Markdown

Summary

  • Add domain whitelist validation to transferFormOwnership() to block ownership transfer when the target email domain is not whitelisted in any agency
  • Uses the same AgencyModel.findOne({ emailDomain }) pattern already used in collaborator validation (line 1300-1301)
  • Validation runs between Step 1 (verify different owner) and Step 2 (find new owner), so we fail fast on invalid domains before the user lookup

Related Issue

Closes #5971

Changes

File Change
apps/backend/src/app/modules/form/admin-form/admin-form.service.ts Add Step 1b: domain validation using AgencyModel.findOne({ emailDomain })

How It Works

  1. Extract domain from newOwnerEmail (e.g., "data.gov.sg" from "user@data.gov.sg")
  2. Query AgencyModel.findOne({ emailDomain }) to check if domain belongs to any whitelisted agency
  3. If no matching agency found, return TransferOwnershipError with message "{email} is not part of a whitelisted agency"
  4. If domain is whitelisted, proceed with existing transfer flow

Test Plan

  • Transfer to a whitelisted domain email succeeds
  • Transfer to a non-whitelisted domain email returns TransferOwnershipError
  • Transfer to same owner still returns "already the owner" error
  • Transfer to non-existent user still returns "must have logged in" error
  • Agency domain removal blocks subsequent transfers to that domain

Happy to add unit tests in admin-form.service.spec.ts if needed.

Add domain whitelist validation to transferFormOwnership() to prevent
admins from transferring form ownership to emails from non-whitelisted
domains. Uses the same AgencyModel.findOne({ emailDomain }) pattern
already used in collaborator validation.

The check runs after verifying the new owner is different from the
current owner (Step 1) and before looking up the new owner in the
database (Step 2), so we fail fast on invalid domains.
@faizkhairi faizkhairi requested a review from a team as a code owner March 24, 2026 17:47
})
// Step 1b: Validate that the new owner's email domain is whitelisted.
.andThen((currentOwner) => {
const emailDomain = newOwnerEmail.split('@').pop()
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.

hi there, instead of making a new database query and validation logic, could we reuse the AuthService validateEmailDomain(email) method?

Copy link
Copy Markdown
Author

@faizkhairi faizkhairi Mar 25, 2026

Choose a reason for hiding this comment

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

Yes, sure can! 👌🏼

Copy link
Copy Markdown
Author

@faizkhairi faizkhairi Mar 25, 2026

Choose a reason for hiding this comment

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

I refactored to reuse AuthService.validateEmailDomain() and map InvalidDomainError to TransferOwnershipError to keep the existing error contract. This also gives us the extra validator.isEmail() guard for free.

Hope this helps!

Replace inline AgencyModel.findOne() with AuthService.validateEmailDomain()
as suggested in review. Maps InvalidDomainError to TransferOwnershipError
to preserve existing error contract.
@faizkhairi
Copy link
Copy Markdown
Author

Hey @kevin9foong, just a friendly bump. The refactor to reuse \ is in place per your review feedback. Let me know if there's anything else needed for this to move forward.

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.

Transferring ownership to emails from non-whitelisted domains is allowed

2 participants