* fix(auth): provision invitees server-side when signups are disabled
Self-hosted installations with GoTrue disable_signup broke the invite
flow silently: invitees without an account were routed to /register,
where supabase.auth.signUp fails with "Signups not allowed for this
instance", surfaced only as a generic toast.
New server-only env flag AUTH_SIGNUPS_DISABLED (documented in
.env.example) mirrors the GoTrue setting. When true, POST
/api/company/members/invite checks check_email_exists and, for invitees
without an account, provisions one via auth.admin.inviteUserByEmail
with a redirect back to /invite/<token>, before the Resend email and
before the invitation row is written so a provisioning failure leaves
nothing half-created and the admin can retry. The response now carries
user_provisioned alongside email_sent, and a provisioning failure
returns 502 with a Swedish message mapped through getErrorMessage
instead of a silently-successful invite.
/auth/callback now routes type=invite verifications to /reset-password
(the existing set-password surface) instead of dropping the
passwordless user on the dashboard, and preserves the invite token from
next=/invite/<token> as the pre-auth invite cookie so the existing
reset-password invite handoff accepts the membership right after the
password is saved.
getErrorMessage learns two GoTrue patterns: "Signups not allowed"
(account creation closed on this installation, contact your inviter or
administrator) so the /register dead end is explained even for flows
that bypass provisioning, and "Error sending ... email" (GoTrue SMTP
not configured) so the 502 above is actionable.
Hosted is untouched: the flag is unset there and every new code path is
gated on it.
Fixes#1335
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(auth): restore check_email_exists RPC and harden self-host invite config
Adversarial review of #1404 found that the check_email_exists function the
invite flow depends on does not exist anywhere: it shipped in PR #229 and
was lost in the #244 migration consolidation before ever reaching prod
(verified missing on the hosted production database directly). Today
app/api/team/accept destructures only { data } from the RPC call, so
alreadyHasAccount is silently null on every deployment and the invite page
routes even existing-account invitees toward /register.
- New migration 20260804140000 restores the function exactly as originally
shipped: SECURITY DEFINER over auth.users, EXECUTE revoked from PUBLIC,
anon and authenticated, granted to service_role only (prevents email
enumeration). Fixes hosted prod behavior too once applied.
- New tests/pg/check-email-exists.pg.test.ts locks in existence,
case-insensitive matching, false-for-unknown, and the role grants.
- .env.docker.example gains the AUTH_SIGNUPS_DISABLED block self-hosters
actually use; both env templates now note that the GoTrue redirect URI
allow-list must include /invite/* or the invite email redirect silently
falls back to SITE_URL.
- Invite route test for the existsError branch: RPC failure logs a warning
and provisioning proceeds anyway (GoTrue is authoritative).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(auth): mask invitee email in provisioning-failure log (#1335)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>