Skip to content

feat(organizations): require email verification before domain-based org features#3234

Merged
PierreBrisorgueil merged 6 commits intomasterfrom
feat/email-verification-gate
Mar 15, 2026
Merged

feat(organizations): require email verification before domain-based org features#3234
PierreBrisorgueil merged 6 commits intomasterfrom
feat/email-verification-gate

Conversation

@PierreBrisorgueil
Copy link
Copy Markdown
Contributor

@PierreBrisorgueil PierreBrisorgueil commented Mar 15, 2026

Summary

  • Gate org provisioning during signup on emailVerified when mailer is configured
  • Gate explicit org creation, join requests, and domain search on email verification
  • Shared assertEmailVerified helper to avoid duplication
  • Without mailer configured, flow unchanged (dev-friendly)

Closes #3232

Test plan

  • Signup with mailer configured + unverified email → no org created, emailVerificationRequired=true
  • Verify email → org setup accessible
  • Signup without mailer → flow unchanged
  • Create org with unverified email + mailer → 403
  • Join request with unverified email + mailer → 403
  • Search by domain with unverified email + mailer → empty array

Summary by CodeRabbit

  • New Features

    • Added email verification requirement for organization creation and join requests when configured.
  • Bug Fixes

    • Fixed HTML escaping issue in email template.
    • Improved name display handling in emails to gracefully manage missing name components.
  • Tests

    • Added comprehensive test suite for email verification across organization operations.

…rg features

When mailer is configured, users must verify their email before:
- Organization auto-provisioning during signup
- Explicit organization creation
- Join request creation
- Domain-based organization search

Without mailer configured, the flow remains unchanged (dev-friendly).

Closes #3232
Copilot AI review requested due to automatic review settings March 15, 2026 08:42
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 15, 2026

Warning

Rate limit exceeded

@PierreBrisorgueil has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 11 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5ea48813-dd0e-4b06-b741-15bc4819fa7a

📥 Commits

Reviewing files that changed from the base of the PR and between e6153fc and 1dfb405.

📒 Files selected for processing (7)
  • lib/helpers/emailVerification.js
  • lib/helpers/getBaseUrl.js
  • modules/auth/controllers/auth.password.controller.js
  • modules/organizations/services/organizations.membership.service.js
  • modules/organizations/services/organizations.service.js
  • modules/organizations/tests/organizations.emailVerification.unit.tests.js
  • modules/users/controllers/users.data.controller.js

Walkthrough

The PR introduces email verification requirements for domain-based organization operations to prevent domain squatting. It centralizes base URL resolution via a new helper module, replaces inline config.cors.origin references throughout auth and organization services, adds email verification guards to signup and membership flows, updates displayName handling for graceful formatting, and includes a comprehensive test suite validating the new behavior.

Changes

Cohort / File(s) Summary
Configuration
config/defaults/development.config.js, config/templates/verify-email.html
Removed app.front property from development config; corrected HTML template escaping by removing unnecessary backslash before apostrophe in "didn't".
Email Verification Infrastructure
lib/helpers/emailVerification.js
New helper module providing assertEmailVerified(user) function that throws FORBIDDEN error when mailer is configured and user email is unverified.
Base URL Resolution
lib/helpers/getBaseUrl.js
New helper module that centralizes base URL resolution from config.cors.origin, returning the first element if array, the value if string, or empty string otherwise.
Auth Controllers
modules/auth/controllers/auth.controller.js, modules/auth/controllers/auth.password.controller.js
Replaced inline base URL resolution with getBaseUrl() calls; improved displayName formatting by safely joining firstName and lastName; added emailVerificationRequired flag to signup response.
Organization CRUD & Service
modules/organizations/services/organizations.crud.service.js, modules/organizations/services/organizations.service.js
Added email verification assertion in organization creation; added pre-provisioning check in signup flow to skip organization setup when mailer is configured and email unverified; updated documentation to reflect emailVerificationRequired response field.
Organization Membership & Search
modules/organizations/services/organizations.membership.service.js, modules/organizations/controllers/organizations.controller.js
Added email verification checks in createJoinRequest; replaced config.app.front with getBaseUrl() in mailer URLs; added conditional guard in search controller to return empty results for unverified users; standardized displayName formatting.
User Controllers & Tests
modules/users/controllers/users.data.controller.js, modules/organizations/tests/organizations.emailVerification.unit.tests.js
Updated displayName formatting in email template parameters for safe concatenation; added comprehensive 265-line unit test suite covering email verification gates across signup, organization creation, membership requests, and search operations with mocked mailer and repository dependencies.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #3193: Related through configuration layer changes—both PRs touch default config files, including removal/alteration of app-level configuration properties like app.front.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers key areas (summary, test plan) but is missing several required sections: Scope, Validation, Guardrails check, and Optional infra details. Add missing required sections: Scope (modules impacted, cross-module impact, risk level), Validation (lint, test, manual checks), Guardrails (secrets, risky moves, merge-friendliness, tests), and infrastructure details as applicable.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: requiring email verification before domain-based organization features.
Linked Issues check ✅ Passed All code changes comprehensively address the linked issue #3232: email verification gating is implemented across signup, org creation, join requests, and domain search when mailer is configured.
Out of Scope Changes check ✅ Passed Changes to displayName formatting, getBaseUrl helper extraction, and HTML escaping fix are supportive utilities aligned with email template improvements across the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 feat/email-verification-gate
📝 Coding Plan
  • Generate coding plan for human review comments

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
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds email-verification gating (when the mailer is configured) to prevent unverified accounts from using domain-based organization behaviors, addressing the domain-squatting risk described in #3232.

Changes:

  • Added a shared assertEmailVerified helper and applied it to org creation and join-request flows.
  • Blocked signup-time org provisioning and domain-based org search for unverified users when mailer is configured.
  • Added unit tests covering the new gating behavior across signup provisioning, org creation, join requests, and search.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
modules/organizations/tests/organizations.emailVerification.unit.tests.js Adds unit coverage for email-verification gates across org flows.
modules/organizations/services/organizations.service.js Skips signup org provisioning when mailer is configured and user email isn’t verified.
modules/organizations/services/organizations.membership.service.js Enforces email verification before creating join requests.
modules/organizations/services/organizations.crud.service.js Enforces email verification before explicit org creation.
modules/organizations/controllers/organizations.controller.js Returns empty search results for unverified users when mailer is configured.
lib/helpers/emailVerification.js Introduces shared assertEmailVerified helper for mailer-configured environments.

…lVerified false

- Extract getBaseUrl() into shared lib/helpers/getBaseUrl.js
- Replace all config.cors.origin[0] and config.app.front with getBaseUrl()
- Remove unused app.front config
- Fix backslash in verify-email template
- Fix displayName trailing space when lastName is empty
- Keep emailVerified false at signup even without mailer (gates enforce only when mailer configured)
- Update consecutive_zero threshold to 3 in pull-request skill
Copy link
Copy Markdown

@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: 8

Caution

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

⚠️ Outside diff range comments (1)
modules/auth/controllers/auth.controller.js (1)

58-63: 🛠️ Refactor suggestion | 🟠 Major

Add @returns to the modified async handlers.

signup and oauthCallback were both changed in this PR, but their JSDoc blocks still omit the resolved return type required by the repo rules.

📝 Minimal JSDoc fix
 /**
  * `@desc` Endpoint to ask the service to create a user
  * `@param` {Object} req - Express request object
  * `@param` {Object} res - Express response object
+ * `@returns` {Promise<void>} Resolves after sending the signup response
  */
 const signup = async (req, res) => {
 /**
  * `@desc` Endpoint for oautCallCallBack
  * `@param` {Object} req - Express request object
  * `@param` {Object} res - Express response object
  * `@param` {Function} next - Express next middleware function
+ * `@returns` {Promise<void>} Resolves after redirecting or sending the OAuth response
  */
 const oauthCallback = async (req, res, next) => {

As per coding guidelines, **/*.js: Every new or modified function must have a JSDoc header: one-line description, @param for each argument, @returns for any non-void return value (always include @returns for async functions to document the resolved value).

Also applies to: 315-321

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/auth/controllers/auth.controller.js` around lines 58 - 63, The JSDoc
for the modified async handlers is missing `@returns`; update the comment blocks
for the async functions signup and oauthCallback to include a `@returns` that
documents the resolved return value (e.g., Express response or Promise<void> /
Promise<Express.Response> as appropriate), keeping the existing one-line
description and `@param` entries and ensuring the async resolved type is clearly
specified for each handler.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/helpers/emailVerification.js`:
- Around line 10-12: The function assertEmailVerified currently dereferences
user.emailVerified without checking user; update assertEmailVerified to first
guard that user is non-null/undefined (e.g., if (!user || !user.emailVerified)
or separate checks) before accessing user.emailVerified, and when the guard
fails throw the same AppError used currently (with code 'FORBIDDEN' and status
403) so callers receive a controlled error; reference mailer.isConfigured(),
assertEmailVerified, user.emailVerified, and AppError when making the change.
- Around line 4-9: The JSDoc for the new helper is missing a `@returns` tag:
update the JSDoc for the helper function (assertEmailVerified) to include
"@returns {void}" so the function's return shape is explicitly documented; keep
the existing `@param` and description intact and place the `@returns` line with the
other tags in the comment block above the assertEmailVerified function.

In `@lib/helpers/getBaseUrl.js`:
- Around line 11-15: getBaseUrl currently returns origin values as-is which can
include trailing slashes causing double-slash URLs; update the getBaseUrl
function to trim whitespace and remove any trailing slash(es) from the resolved
origin (both when origin is an array element and when it's a string) before
returning it (e.g. normalize origin or origin[0] via a trailing-slash strip
using a regex or string method); ensure empty or undefined origin still returns
an empty string.

In `@modules/auth/controllers/auth.password.controller.js`:
- Around line 50-52: Add missing `@returns` JSDoc tags to the modified forgot and
reset handler headers in auth.password.controller.js: update the JSDoc for the
forgot function and the reset function to include a `@returns` line that documents
the return value and its type (for example a Promise resolving to the Express
response or void as appropriate) and a short description of what the handler
returns (e.g., Promise<void> or Promise<Response> / JSON result). Ensure both
handlers' JSDoc blocks include `@param` entries already present and now also an
explicit `@returns` description matching the actual returned value/type in the
forgot and reset functions.

In `@modules/organizations/services/organizations.membership.service.js`:
- Around line 136-138: The code calls UserService.getBrut({ id: String(userId)
}) and then immediately assertEmailVerified(user) without checking that user is
not null; add an existence check after UserService.getBrut (e.g., if (!user)
throw a NotFound/BadRequest error or return a clear failure) before calling
assertEmailVerified and before any pending membership creation logic so you
never pass a null user into assertEmailVerified or createPendingMembership;
update the flow around UserService.getBrut and assertEmailVerified to handle the
missing-user case explicitly.

In `@modules/organizations/services/organizations.service.js`:
- Around line 133-149: The early return in handleSignupOrganization omits the
suggestedOrganization key, violating the documented return shape; update the
mailer verification branch return to include suggestedOrganization: null
(alongside organization, membership, abilities, organizationSetupRequired,
emailVerificationRequired, pendingJoin) so consumers receive a consistent object
shape.

In `@modules/organizations/tests/organizations.emailVerification.unit.tests.js`:
- Around line 77-79: Update the tests to assert call flow rather than only HTTP
200: where the controller uses OrganizationsCrudService.searchByDomain(),
explicitly expect the mock to have been called (or not) depending on the gate
branch; add assertions for the mailer-enforced path to verify that the allow
branch calls OrganizationsCrudService.searchByDomain() and the blocked branch
does not call it. Also add a new test case exercising the configured+verified
scenario so the "allow" path under mailer enforcement is covered, and ensure you
reset mocks in beforeEach (jest.clearAllMocks()) so call counts are reliable.

In `@modules/users/controllers/users.data.controller.js`:
- Line 69: The JSDoc for the getMail function is missing a `@returns` tag; update
the JSDoc block above the getMail function (function name: getMail) to include a
`@returns` annotation that describes the returned value (e.g., the mail object or
response type), matching the existing `@param` style and project conventions so
every function header documents both `@param` and `@returns`.

---

Outside diff comments:
In `@modules/auth/controllers/auth.controller.js`:
- Around line 58-63: The JSDoc for the modified async handlers is missing
`@returns`; update the comment blocks for the async functions signup and
oauthCallback to include a `@returns` that documents the resolved return value
(e.g., Express response or Promise<void> / Promise<Express.Response> as
appropriate), keeping the existing one-line description and `@param` entries and
ensuring the async resolved type is clearly specified for each handler.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3b1cbb30-e2cf-4708-82e2-8f5969052962

📥 Commits

Reviewing files that changed from the base of the PR and between 1d070c2 and e6153fc.

📒 Files selected for processing (12)
  • config/defaults/development.config.js
  • config/templates/verify-email.html
  • lib/helpers/emailVerification.js
  • lib/helpers/getBaseUrl.js
  • modules/auth/controllers/auth.controller.js
  • modules/auth/controllers/auth.password.controller.js
  • modules/organizations/controllers/organizations.controller.js
  • modules/organizations/services/organizations.crud.service.js
  • modules/organizations/services/organizations.membership.service.js
  • modules/organizations/services/organizations.service.js
  • modules/organizations/tests/organizations.emailVerification.unit.tests.js
  • modules/users/controllers/users.data.controller.js
💤 Files with no reviewable changes (1)
  • config/defaults/development.config.js

@PierreBrisorgueil
Copy link
Copy Markdown
Contributor Author

re-triggering CI

@PierreBrisorgueil PierreBrisorgueil force-pushed the feat/email-verification-gate branch from 37a0493 to e15a1d6 Compare March 15, 2026 11:08
@PierreBrisorgueil PierreBrisorgueil merged commit 6665115 into master Mar 15, 2026
3 checks passed
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.

feat(organizations): require email verification before domain-based auto-join

2 participants