Skip to content

πŸ› OAuth signup never provisions an organizationΒ #4115

Description

@PierreBrisorgueil

What β€” A user who signs up with Google or Apple gets their workspace created, like an email signup does.
Why β€” Today they land on the org-required page and must create a workspace by hand; most stop there. Email signup gets one automatically.

Scope

  • Root cause: checkOAuthUserProfile (modules/auth/controllers/auth.controller.js:307-496) creates or resolves the user, and oauthCallback (:568-602) only signs the JWT and redirects. Org provisioning (AuthOrganizationService.handleSignupOrganization) is only called by local signup (auth.signup.service.js:267) and verifyEmail (auth.controller.js:711).
  • The OAuth email already counts as verified (google.js:34, apple.js:36, branch 4 sets emailVerified at :428), so provisioning succeeds immediately.
  • Fix: in oauthCallback's passport callback (make it async), after the !user guard and before jwt.sign, call await AuthOrganizationService.handleSignupOrganization(user) in a try/catch, log-and-continue (same best-effort pattern as verifyEmail, feat(auth): wire handleSignupOrganization into verifyEmail (post-verification org setup)Β #3762/feat(auth): trigger handleSignupOrganization on verifyEmail success (best-effort)Β #3765).
  • Call it on every resolution (branches 1-4), not only new users: it converges on the existing ACTIVE membership, so it never creates a second org, and it self-heals accounts already orphaned by this bug with no backfill migration.

Edge cases β€” existing user linking a provider β†’ converges, no second org Β· domain match β†’ own workspace + suggestedJoin hint only Β· provider without email_verified β†’ returns emailVerificationRequired, and the existing verification gate on organizations.required.view.vue handles it.

Tests β€” new modules/auth/tests/auth.oauth.signup-org.unit.tests.js (mirrors auth.verifyEmail.signup-org.unit.tests.js): called with the resolved user on all 4 branches; a provisioning failure does not break the redirect.

Refs: #3762, #3765, #3680

Update (scope re-validated)

  • Correction to Why: the org-required page does have a "Create an organization" button, so this is friction, not a hard dead end. Goal = parity with email signup (workspace created automatically).
  • Call provisioning only when !user.currentOrganization β€” covers new users and users left without an org, zero extra queries/events on normal logins.
  • Keep only the await inside the try/catch; route any other throw in the now-async callback to oauthErrorRedirect (passport ignores the returned promise β†’ unhandled rejection otherwise).
  • Refs: modules/auth/strategies/local/google.js:35, apple.js:40 (other refs still accurate).
  • Tests (the fix lives in oauthCallback, which sees only the resolved user): called when no currentOrganization; not called when set; not called on err/!user; a provisioning rejection still sets the cookie and 302s to /token. Await the callback promise before asserting.
  • Known gaps, out of scope (note in PR): suggestedJoin is not carried through OAuth; a provider-unverified email with the mailer on still gets emailVerificationRequired.

Scope: validated 2026-09-25
Created via /dev:issue

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions