Skip to content

feat(auth): invite-only / capped signup gate#3715

Merged
PierreBrisorgueil merged 15 commits into
masterfrom
feat/invite-only-signup-gate
May 28, 2026
Merged

feat(auth): invite-only / capped signup gate#3715
PierreBrisorgueil merged 15 commits into
masterfrom
feat/invite-only-signup-gate

Conversation

@PierreBrisorgueil
Copy link
Copy Markdown
Contributor

@PierreBrisorgueil PierreBrisorgueil commented May 28, 2026

Summary

  • What changed: New Invitation primitive in modules/auth (model/schema/policy/repo/service); admin CRUD (/api/auth/invitations) + public verify (/api/auth/invitations/verify/:token); two AND-ed signup gates — capacity (config.sign.cap, hard ceiling incl. invited) + eligibility (config.sign.up OR valid invite); local signup carries token via ?inviteToken= query, OAuth matches invite by provider email; single-use consume after successful create; config sign.cap + sign.inviteExpiresInDays; README + OpenAPI (modules/auth/doc/auth.invitations.yml).
  • Why: Enables private-beta and controlled-rollout scenarios without custom downstream code.
  • Related issues: Closes feat(auth): invite-only / capped signup gate #3714

Scope

  • Module(s) impacted: modules/auth (model, schema, policy, repo, service, controller, routes, template, doc), modules/users (count helper), config (sign.cap, sign.inviteExpiresInDays), README
  • Cross-module impact: yesmodules/users gains count() used by auth signup gate; no breaking changes to existing user/auth API
  • Risk level: medium — new auth path, additive only, behind config flags

Validation

  • npm run lint
  • npm test — 173 auth-module tests pass; npm run test:unit:coverage, npm run test:integration:coverage, npm run test:e2e all green locally
  • Manual checks done — local signup gate, OAuth gate, invite CRUD, token verify

Guardrails check

  • No secrets or credentials introduced (.env*, secrets/**, keys, tokens)
  • No risky rename/move of core stack paths
  • Changes remain merge-friendly for downstream projects
  • Tests added or updated when behavior changed

Optional: Infra/Stack alignment details

Before vs After (key changes only)

Area Before After Notes
Signup gate config.sign.up boolean only sign.cap ceiling + invite token OR sign.up Additive; default cap=0 (no ceiling)
Auth routes Local + OAuth only + /api/auth/invitations CRUD + /verify/:token Admin-gated CRUD; verify is public
Invitation tokens Single-use, email-pinned, TTL configurable Token not exposed in admin list response
OpenAPI modules/auth/doc/auth.invitations.yml New doc file, included in swagger build
  • Upstream parity target / source: new feature, no upstream to track
  • Automation or policy impact: none
  • Rollback plan: revert config sign.cap=0 disables ceiling; sign.up=true bypasses invite gate; feature is fully config-gated

Notes for reviewers

  • Security considerations: invite email-pin enforced even when request body has no email field (inferred from invite record); token not returned in admin list (select: 0); single-use consume is atomic (findOneAndUpdate + $set usedAt).
  • Cross-stack pair: Vue counterpart PR opened in parallel (pierreb-devkit/Vue, same branch name feat/invite-only-signup-gate).
  • Mergeability considerations: no migration needed; new Mongoose model + indexes are created on first boot.
  • Follow-up tasks: downstream projects may add sign.cap + sign.inviteExpiresInDays to their env config when they want to use the gate.

Summary by CodeRabbit

  • New Features

    • Admin-managed signup invitations with email invites and public token verification.
    • Signup gating with a configurable total-account cap and invite-only mode; invites expire by default in 14 days.
    • Invitation emails/templates and best-effort delivery for invite flows.
  • Documentation

    • README and OpenAPI docs updated to describe invitation flows, endpoints, and configuration.
  • Tests

    • New unit and integration tests covering invitation CRUD, signup gating, and OAuth signup behavior.

Review Change Stack

…ore :strategy wildcard)

Removes auth.invitation.routes.js so the glob auto-loader never picks it
up, eliminating the double-registration bug (two DB hits per DELETE,
duplicate app.param binding). Invitation routes are now declared once
inside auth.routes.js, before the greedy /api/auth/:strategy wildcard.
Two AND-ed guards on POST /api/auth/signup: (1) capacity ceiling
(config.sign.cap, invited users count toward the cap) blocks everyone
when total >= cap; (2) eligibility requires config.sign.up OR a valid
inviteToken query param (token consumed after account is fully
provisioned). Config gains sign.cap and sign.inviteExpiresInDays.
- fix: optional chain req.query?.inviteToken to guard against missing query
  object (broke analytics unit tests that inject req without a query key)
- fix: mock InvitationService + add UserService.count mock in
  analytics.identify.unit.tests to prevent MissingSchemaError on lazy
  mongoose.model('Invitation') call at repository import time
- test: extend auth.invitation.unit.tests — email-send branch, findValidByEmail
  null-guard, list/get/revoke delegation, invitationAbilities admin/non-admin
  paths, invitationSubjectRegistration predicate (policy 100% coverage)
@PierreBrisorgueil PierreBrisorgueil added the Feat A new feature label May 28, 2026
@PierreBrisorgueil PierreBrisorgueil self-assigned this May 28, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Warning

Review limit reached

@PierreBrisorgueil, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 42 minutes and 33 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5f4448c7-a784-42ae-a729-a17927771f4b

📥 Commits

Reviewing files that changed from the base of the PR and between ec3de30 and 43abee0.

📒 Files selected for processing (3)
  • modules/auth/controllers/auth.controller.js
  • modules/auth/tests/auth.invitation.integration.tests.js
  • modules/users/tests/users.service.count.unit.tests.js

Walkthrough

The PR implements a complete invite-only and capacity-capped signup system. It adds an Invitation model with token-based single-use lifecycle, a service layer for create/find/consume operations, and integrates two-gate signup eligibility (capacity ceiling AND public/invite) into existing local and OAuth signup flows. All endpoints are documented in OpenAPI and protected by admin authorization policies.

Changes

Signup invitations and capacity gating

Layer / File(s) Summary
Invitation data model, schema, repository, and user count
modules/auth/models/auth.invitation.model.mongoose.js, modules/auth/models/auth.invitation.schema.js, modules/auth/repositories/auth.invitation.repository.js, modules/users/repositories/users.repository.js, modules/users/services/users.service.js, modules/users/tests/users.service.count.unit.tests.js
Mongoose schema for Invitation with email, token, expiry, and usage; Zod validation schema; repository with full CRUD (create, findByToken, findByEmail, consume with atomicity, list, get, remove); UserService.count() method to support capacity gating.
Invitation service with unit tests
modules/auth/services/auth.invitation.service.js, modules/auth/tests/auth.invitation.unit.tests.js
Service layer implementing create (token generation, configurable expiry, best-effort email sending), findValid (unused/unexpired checks with optional case-insensitive email match), findValidByEmail, consume, list, get, and revoke; comprehensive unit tests validating all operations, email normalization, expiry calculation, and policy authorization.
Invitation API (controller, routes, policies, OpenAPI)
modules/auth/controllers/auth.invitation.controller.js, modules/auth/routes/auth.routes.js, modules/auth/policies/auth.invitation.policy.js, modules/auth/doc/auth.invitations.yml
Controller endpoints for admin create/list/remove and public verify with request validation and error handling; invitationByID middleware for ID resolution; JWT+policy-gated routes before the wildcard strategy matcher; CASL policies limiting manage ability to admin role; OpenAPI spec for all four endpoints plus Invitation schema.
Auth controller signup gating (local + OAuth)
modules/auth/controllers/auth.controller.js
Integrates invitation service into signup and checkOAuthUserProfile. Local signup enforces capacity check (userCount vs. config.sign.cap) and eligibility (public signup enabled OR valid invite matched to provided email). OAuth mirrors this, matching invites by provider-verified email. Both consume invite atomically after user creation.
Configuration, email template, and integration tests
modules/auth/config/auth.development.config.js, config/templates/signup-invite.html, modules/auth/tests/auth.invitation.integration.tests.js
Development config with cap (null unlimited) and inviteExpiresInDays (14); HTML email template for invitations with appName, url, and appContact placeholders; integration test suite covering admin CRUD authorization, public token verification, local signup gating (disabled/cap/mismatch/consumption), OAuth signup gating, and invite lifecycle.
Test mock updates
lib/services/tests/analytics.identify.unit.tests.js, modules/auth/tests/auth.config.controller.unit.tests.js, modules/auth/tests/auth.signout.controller.unit.tests.js, modules/auth/tests/auth.silent.catch.unit.tests.js
Updates to existing test mocks to include new InvitationService methods (create, list, get, revoke) and UserService.count; adjusts signup test request fixtures for invite token handling.
User documentation
README.md
Feature description in overview; detailed documentation of the two-gate mechanism, default config (cap: null, expiry: 14 days), scenarios (invite-only vs. beta cap), and API endpoints with auth requirements.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main feature: a two-gated signup system combining invite-only and capped enrollment mechanisms.
Description check ✅ Passed The PR description comprehensively covers the changeset structure, objectives, scope, validation status, and implementation details with all template sections completed.
Linked Issues check ✅ Passed All coding requirements from issue #3714 are met: invitation model/CRUD created, two AND-ed gates (capacity cap and eligibility) implemented in auth.controller, invites single-use and email-pinned, admin endpoints + public verify added, tokens hidden in list responses.
Out of Scope Changes check ✅ Passed All changes remain in scope: invitation primitive in auth, user count helper for gate logic, config additions for cap/expiry, tests, documentation, and email template — all directly supporting the issue #3714 requirements.
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
  • Commit unit tests in branch feat/invite-only-signup-gate

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.

@PierreBrisorgueil PierreBrisorgueil marked this pull request as ready for review May 28, 2026 17:15
Copilot AI review requested due to automatic review settings May 28, 2026 17:15
@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

❌ Patch coverage is 93.47826% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.89%. Comparing base (d87542b) to head (43abee0).
⚠️ Report is 11 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3715      +/-   ##
==========================================
+ Coverage   89.73%   89.89%   +0.15%     
==========================================
  Files         143      148       +5     
  Lines        4794     4888      +94     
  Branches     1505     1532      +27     
==========================================
+ Hits         4302     4394      +92     
- Misses        385      389       +4     
+ Partials      107      105       -2     
Flag Coverage Δ
integration 60.06% <92.39%> (+0.63%) ⬆️
unit 66.36% <39.13%> (+0.11%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8a50c8e...43abee0. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Adds an invitation-based / capacity-capped signup gate to modules/auth. Introduces an Invitation Mongoose model with admin CRUD + public verify endpoints, and wires two AND-ed gates (capacity ceiling + eligibility via config.sign.up OR a valid invite) into both local and OAuth signup paths. Includes a new UserService.count() helper used by the cap check, a signup-invite email template, OpenAPI docs, README updates, and unit/integration tests.

Changes:

  • New invitation primitive: model, schema, repository, service, policy, controller, routes, email template, and OpenAPI spec.
  • auth.controller.signup and checkOAuthUserProfile enforce capacity + eligibility gates; invite is consumed atomically post-create.
  • Adds UserService.count() / UserRepository.count() and updates existing auth unit tests to mock the new dependencies.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
README.md Documents the new signup access control feature and endpoints.
modules/auth/controllers/auth.controller.js Adds capacity + invite gates to local and OAuth signup; consumes invite post-create.
modules/auth/controllers/auth.invitation.controller.js New admin CRUD + public verify controller for invitations.
modules/auth/services/auth.invitation.service.js Token generation, expiry/email-pin validation, best-effort email send, single-use consume.
modules/auth/repositories/auth.invitation.repository.js Mongo access for invitations including atomic consume.
modules/auth/models/auth.invitation.model.mongoose.js Mongoose schema with unique-token index.
modules/auth/models/auth.invitation.schema.js Zod schema for admin create payload (email only).
modules/auth/policies/auth.invitation.policy.js CASL path→subject registration and admin-only abilities.
modules/auth/routes/auth.routes.js Registers verify (public + rate-limited) and admin-gated CRUD before the OAuth wildcard.
modules/auth/doc/auth.invitations.yml OpenAPI documentation for the new endpoints.
modules/auth/config/auth.development.config.js Adds sign.cap and sign.inviteExpiresInDays config keys.
modules/auth/tests/auth.invitation.unit.tests.js Unit coverage for invitation service + policy.
modules/auth/tests/auth.invitation.integration.tests.js End-to-end coverage of cap/invite gates for local + OAuth.
modules/auth/tests/auth.silent.catch.unit.tests.js Adds mocks for count + invitation service to existing test.
modules/auth/tests/auth.signout.controller.unit.tests.js Same dependency-mock updates.
modules/auth/tests/auth.config.controller.unit.tests.js Same dependency-mock updates.
modules/users/services/users.service.js New count(filter) delegating to repository.
modules/users/repositories/users.repository.js New exact countDocuments helper.
modules/users/tests/users.service.count.unit.tests.js Unit test for the new count helper.
lib/services/tests/analytics.identify.unit.tests.js Adds invite-service + count mocks to keep tests passing.
config/templates/signup-invite.html New invitation email template.

Comment thread modules/auth/controllers/auth.controller.js Outdated
Comment thread modules/auth/controllers/auth.controller.js Outdated
Comment thread modules/auth/config/auth.development.config.js
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: 9

Caution

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

⚠️ Outside diff range comments (2)
lib/services/tests/analytics.identify.unit.tests.js (1)

38-42: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider completing the InvitationService mock for consistency.

The InvitationService mock here includes only findValid and consume, while lib/services/tests/analytics.identify.unit.tests.js (lines 50-59, 246-256, 362-372) mocks all seven methods (create, list, get, revoke, findValidByEmail, findValid, consume). For consistency and to avoid potential issues if the controller module accesses other service methods during initialization, consider using the complete mock.

♻️ Align with the complete mock pattern
 jest.unstable_mockModule('../../../modules/auth/services/auth.invitation.service.js', () => ({
-  default: { findValid: jest.fn().mockResolvedValue(null), consume: jest.fn().mockResolvedValue(null) },
+  default: {
+    findValid: jest.fn().mockResolvedValue(null),
+    findValidByEmail: jest.fn().mockResolvedValue(null),
+    consume: jest.fn().mockResolvedValue(null),
+    create: jest.fn(),
+    list: jest.fn(),
+    get: jest.fn(),
+    revoke: jest.fn(),
+  },
 }));
🤖 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/services/tests/analytics.identify.unit.tests.js` around lines 38 - 42,
The InvitationService mock used inside setupSignupMocks only defines findValid
and consume which can leave other methods undefined and break module
initialization; update the jest.unstable_mockModule for the invitations service
to return a default object that defines all seven methods (create, list, get,
revoke, findValidByEmail, findValid, consume) as jest.fn() so the
controller/module can safely call any method during import; if specific behavior
is required in tests, override individual methods'
mockResolvedValue/mockImplementation after creating the base mock.
modules/auth/controllers/auth.controller.js (1)

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

Complete JSDoc for modified signup function.

The updated async signup handler is missing an explicit @returns tag in its JSDoc block.

As per coding guidelines **/*.js: “Every new or modified function must have a JSDoc header … @returns for async functions.”

🤖 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 `@modules/auth/controllers/auth.controller.js` around lines 61 - 66, The signup
async handler's JSDoc is missing an explicit `@returns` tag; update the JSDoc
above the signup function to include an `@returns` describing the promise return
type (e.g., `@returns` {Promise<void>} or `@returns` {Promise<void|Object>} if you
prefer to indicate the response payload) and a short phrase like "Resolves when
the response has been sent" so the async nature of signup is documented; ensure
the tag sits with the existing `@param` entries and matches the function name
signup.
🤖 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 `@config/templates/signup-invite.html`:
- Line 4: The email template signup-invite.html currently contains an empty
<title></title>; replace it with a meaningful, non-empty document title (e.g.,
"Invite to [ProjectName] — Complete Your Signup" or similar) so the <title> tag
in signup-invite.html contains descriptive text for linting and proper metadata.

In `@modules/auth/controllers/auth.controller.js`:
- Around line 71-83: The current cap check uses UserService.count() and
config.sign.cap which is racy; replace the count-then-create logic with an
atomic reservation mechanism: introduce a reservation counter (e.g., a dedicated
"signupCounter" document) and perform an atomic increment (DB $inc) to reserve a
slot before creating the user, check the returned counter value against
config.sign.cap, and on downstream failure (user creation, invitation checks via
InvitationService.findValid, etc.) decrement/rollback the reservation; ensure
the same change is applied to the other signup path that currently uses
UserService.count() (the block referenced around the secondary check) and
maintain existing error flows (responses.error(...)) when the reservation fails
or cap is exceeded.

In `@modules/auth/controllers/auth.invitation.controller.js`:
- Around line 8-60: The controller functions create, list, remove, verify and
invitationByID currently only have shorthand `@function` comments — update each
(create, list, remove, verify, invitationByID) to include full JSDoc blocks per
project standard: add a descriptive summary, `@param` for req (Express.Request)
and res (Express.Response) and next (where applicable), document specific param
properties used (e.g., req.body.email, req.user, req.params.token,
req.invitation.id, id), and an `@returns` tag indicating the Promise/void response
(e.g., Promise<void> or sends HTTP response). Ensure the JSDoc sits immediately
above each function declaration and mentions any thrown errors or response
shapes to satisfy the linter.

In `@modules/auth/doc/auth.invitations.yml`:
- Around line 21-24: The admin list response currently references the Invitation
schema (data.items $ref '`#/components/schemas/Invitation`') which includes the
sensitive token field (defined on Invitation at lines ~126-127); create a
separate schema (e.g., InvitationList or InvitationWithoutToken) that duplicates
Invitation minus the token field, and update the admin list response to use that
new schema instead of '`#/components/schemas/Invitation`' so tokens are not
exposed.

In `@modules/auth/models/auth.invitation.model.mongoose.js`:
- Around line 26-29: The function addID used as the virtual getter for
InvitationMongoose ('InvitationMongoose.virtual("id").get(addID)') lacks the
mandatory JSDoc header; add a JSDoc block immediately above addID describing the
function, include a `@this` annotation indicating the mongoose document context
(e.g., `@this` {Object} or a more specific Invitation document type), include any
`@param` entries if appropriate (none expected here) and a `@returns` {string}
describing that it returns the hex string representation of this._id. Ensure the
JSDoc follows the repository style and appears above the addID function
definition.

In `@modules/auth/tests/auth.invitation.integration.tests.js`:
- Around line 45-103: Add JSDoc `@returns` tags for the two named async helpers
createAdminAndSignin and createUserAndSignin: above each function provide an
`@returns` describing the resolved Promise type (e.g. Promise resolving to the
supertest agent/cookie-bound agent used by tests) so the async return is
explicitly documented per the project JS rules; update the JSDoc for both
createAdminAndSignin and createUserAndSignin accordingly.
- Around line 192-197: The test is order-dependent because config.sign.cap is
set before createAdminAndSignin() creates an extra account; change the setup so
you create the admin and issue the invitation first (call createAdminAndSignin()
and post to /api/auth/invitations to get created), then compute the current
total via UserService.count() and set config.sign.cap to that total before
making the request that should be blocked; update the test around the symbols
createAdminAndSignin, UserService.count, config.sign.cap and the invitation POST
flow so the cap is applied after the admin account exists.

In `@modules/auth/tests/auth.signout.controller.unit.tests.js`:
- Around line 23-27: The InvitationService test mock only defines findValid and
consume; extend it to mirror the complete mock pattern used elsewhere by adding
the other methods (create, getBrut, update, remove, search, count) alongside
findValid and consume so any test that calls those methods won't fail—ensure
count uses mockResolvedValue(0) and the others use jest.fn() (or
mockResolvedValue/null where appropriate) on the mock for the InvitationService
in the tests file.

In `@modules/auth/tests/auth.silent.catch.unit.tests.js`:
- Around line 38-44: The InvitationService mock in the test only stubs findValid
and consume but should mirror the complete mock used elsewhere for consistency;
update the jest.unstable_mockModule for
'../../../modules/auth/services/auth.invitation.service.js' to include all
public methods used by the service (at minimum mock findValid and consume plus
the other methods used across tests such as create, revoke, validate, list and
count) so it matches the full mock pattern (see auth.controller.js signup flow
at lines ~66-83 for which methods are required) and ensure each method uses
jest.fn().mockResolvedValue(...) or mockReturnValue as appropriate.

---

Outside diff comments:
In `@lib/services/tests/analytics.identify.unit.tests.js`:
- Around line 38-42: The InvitationService mock used inside setupSignupMocks
only defines findValid and consume which can leave other methods undefined and
break module initialization; update the jest.unstable_mockModule for the
invitations service to return a default object that defines all seven methods
(create, list, get, revoke, findValidByEmail, findValid, consume) as jest.fn()
so the controller/module can safely call any method during import; if specific
behavior is required in tests, override individual methods'
mockResolvedValue/mockImplementation after creating the base mock.

In `@modules/auth/controllers/auth.controller.js`:
- Around line 61-66: The signup async handler's JSDoc is missing an explicit
`@returns` tag; update the JSDoc above the signup function to include an `@returns`
describing the promise return type (e.g., `@returns` {Promise<void>} or `@returns`
{Promise<void|Object>} if you prefer to indicate the response payload) and a
short phrase like "Resolves when the response has been sent" so the async nature
of signup is documented; ensure the tag sits with the existing `@param` entries
and matches the function name signup.
🪄 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: b0ba9ee6-cb00-48ce-8bcb-45fbc3ca16b2

📥 Commits

Reviewing files that changed from the base of the PR and between 8a50c8e and 5ba9358.

📒 Files selected for processing (21)
  • README.md
  • config/templates/signup-invite.html
  • lib/services/tests/analytics.identify.unit.tests.js
  • modules/auth/config/auth.development.config.js
  • modules/auth/controllers/auth.controller.js
  • modules/auth/controllers/auth.invitation.controller.js
  • modules/auth/doc/auth.invitations.yml
  • modules/auth/models/auth.invitation.model.mongoose.js
  • modules/auth/models/auth.invitation.schema.js
  • modules/auth/policies/auth.invitation.policy.js
  • modules/auth/repositories/auth.invitation.repository.js
  • modules/auth/routes/auth.routes.js
  • modules/auth/services/auth.invitation.service.js
  • modules/auth/tests/auth.config.controller.unit.tests.js
  • modules/auth/tests/auth.invitation.integration.tests.js
  • modules/auth/tests/auth.invitation.unit.tests.js
  • modules/auth/tests/auth.signout.controller.unit.tests.js
  • modules/auth/tests/auth.silent.catch.unit.tests.js
  • modules/users/repositories/users.repository.js
  • modules/users/services/users.service.js
  • modules/users/tests/users.service.count.unit.tests.js

Comment thread config/templates/signup-invite.html Outdated
Comment thread modules/auth/controllers/auth.controller.js Outdated
Comment thread modules/auth/controllers/auth.invitation.controller.js Outdated
Comment thread modules/auth/doc/auth.invitations.yml Outdated
Comment thread modules/auth/models/auth.invitation.model.mongoose.js
Comment thread modules/auth/tests/auth.invitation.integration.tests.js
Comment thread modules/auth/tests/auth.invitation.integration.tests.js
Comment thread modules/auth/tests/auth.signout.controller.unit.tests.js
Comment thread modules/auth/tests/auth.silent.catch.unit.tests.js
- email template: add non-empty <title> to signup-invite.html
- auth.controller: short-circuit UserService.count() when cap is null
- auth.controller: coerce sign.cap to Number + guard non-finite at gate
- auth.controller: only consume invite when it actually opened the gate
  (sign.up=true = invite not required, burning it would be wrong)
- auth.invitation.controller: add full JSDoc (@param/@returns) to all
  controller functions per project standard
- auth.invitations.yml: add InvitationListItem schema (token omitted);
  admin list endpoint now references it instead of Invitation
- auth.invitation.model.mongoose.js: add JSDoc to addID virtual getter
- auth.invitation.integration.tests.js: add @returns to test helpers;
  fix cap test to create admin before setting cap (was order-dependent)
- auth.signout.controller.unit.tests.js: complete InvitationService mock
- auth.silent.catch.unit.tests.js: complete InvitationService mock
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: 1

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)

66-74: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add missing @returns to modified signup JSDoc.

signup is async and modified in this PR, but its JSDoc lacks @returns.

Proposed 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>} Sends HTTP response for signup success or failure
  */
 const signup = async (req, res) => {
As per coding guidelines: "`**/*.js`: Every new or modified function must have a JSDoc header ... and `@returns` for async functions."
🤖 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 `@modules/auth/controllers/auth.controller.js` around lines 66 - 74, The JSDoc
for the async controller function signup is missing an `@returns` tag; update the
JSDoc block immediately above the signup function to include an `@returns`
describing the returned Promise (e.g. `@returns` {Promise<void>} — or `@returns`
{Promise<Express.Response>} if you prefer to document the response) and a short
phrase like "resolves when the HTTP response has been sent", so the async nature
is documented for the UserService/controller signup function.
🤖 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 `@modules/auth/controllers/auth.controller.js`:
- Around line 437-441: The code currently resolves oauthInvite using
profil.email regardless of whether the OAuth provider verified that email, which
can allow invite-based signups on untrusted claims; update the logic in the
block around oauthCap/capReached and oauthInvite so you only call
InvitationService.findValidByEmail(profil.email) when the provider has marked
the email as verified (e.g., check profil.email_verified or provider-specific
verified flag) and profil.email is present; if the email is unverified or
missing, treat oauthInvite as null (so the (!config.sign.up && !oauthInvite)
gate cannot be bypassed), ensuring you preserve the existing capReached
evaluation (UserService.count and config.sign.cap) and the surrounding
conditional that checks sign-up settings.

---

Outside diff comments:
In `@modules/auth/controllers/auth.controller.js`:
- Around line 66-74: The JSDoc for the async controller function signup is
missing an `@returns` tag; update the JSDoc block immediately above the signup
function to include an `@returns` describing the returned Promise (e.g. `@returns`
{Promise<void>} — or `@returns` {Promise<Express.Response>} if you prefer to
document the response) and a short phrase like "resolves when the HTTP response
has been sent", so the async nature is documented for the UserService/controller
signup function.
🪄 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: 8937e54f-9b23-41c3-a1c3-2f3b610d7c00

📥 Commits

Reviewing files that changed from the base of the PR and between 5ba9358 and ec3de30.

📒 Files selected for processing (8)
  • config/templates/signup-invite.html
  • modules/auth/controllers/auth.controller.js
  • modules/auth/controllers/auth.invitation.controller.js
  • modules/auth/doc/auth.invitations.yml
  • modules/auth/models/auth.invitation.model.mongoose.js
  • modules/auth/tests/auth.invitation.integration.tests.js
  • modules/auth/tests/auth.signout.controller.unit.tests.js
  • modules/auth/tests/auth.silent.catch.unit.tests.js

Comment thread modules/auth/controllers/auth.controller.js
…nistic CI)

Add repository-level mocks for organizations.repository.js and
organizations.membership.repository.js so mongoose.model('Organization')
is never evaluated at module-link time with --maxWorkers=2.

Service-level mocks intercept call paths but ESM static imports on the
real service files are still resolved by the V8 VM module linker in CI,
causing MissingSchemaError when the Organization schema hasn't been
registered yet in that worker's context.
@PierreBrisorgueil PierreBrisorgueil merged commit f0e7a83 into master May 28, 2026
8 checks passed
@PierreBrisorgueil PierreBrisorgueil deleted the feat/invite-only-signup-gate branch May 28, 2026 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feat A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(auth): invite-only / capped signup gate

2 participants