#ADR-002: Auth provider cutover and signup activation (ACC-AUTH-SURFACE)
status: open item_id: AUTH-FOUNDATION module_id: MOD-AUTH
#Context
#In plain terms
Today people sign up, sign in and reset their password with Bubble's own built-in accounts. The rebuild runs on Supabase, and neither the logged-in sessions nor the stored passwords can be carried across: Bubble keeps the password hashes and never exports them. So before anyone writes the sign-in screen, two product decisions are needed.
First: how do the people who already have an account get back in? Either everybody is forced through "set a new password" once, or both systems run side by side for a while, or accounts move to an external identity provider. There is no silent option — any claim that passwords "just migrate" is wrong.
Second: what turns a new account on? Bubble does it in an unusual way today. Signup does not
send Bubble's own confirmation email; instead the app sends its own email titled "Verify" whose
link points at the onboarding page with active=yes in the address, and the onboarding page is
what flips the account to active. The target can keep that exact chain, or drop it for Supabase's
standard confirm-email — but then the document must say at which moment profiles.active becomes
true, because several screens gate on it.
A third, smaller choice rides along: those links are already in people's inboxes, and they spell
the mode with a capital letter (?nav=Contractor, ?nav=Verify), because Bubble puts the option's
display text in the URL rather than its stored value. Whatever this ADR picks, the new app should
read that parameter case-insensitively so old links keep working.
Until this is decided, the auth foundation slice is blocked; every other module's login-required behaviour depends on it.
#IR evidence
| Ref | Pointer | Finding |
|---|---|---|
bTGYf |
/pages/bTGbC |
index page — sign-in / sign-up / reset / verify modes on one surface |
AAL |
/pages/AAW |
reset_pw page — ResetPassword then ChangePage to onboarding |
bTHpx1 |
/pages/bTGbC/workflows/bTHjk1/actions/0 |
SignUp with send_confirm_email: false; writes role_option_role and approve1_boolean false; never writes active__boolean |
bTHqT1 |
/pages/bTGbC/workflows/bTHjk1/actions/2 |
SendEmail subject "Verify", sender "TradeForce", body links onboarding?nav= + current user's role display + &active=yes |
bTHqJ1 |
/pages/bTGbC/workflows/bTHjk1/actions/3 |
ChangePage back to index with nav / tab interpolated from option displays |
bTHsZ1 / bTHse1 |
/pages/bTHDr0/workflows/bTHsd1 (+ /actions/1) |
onboarding PageLoaded: when URL active=yes and the user is still inactive → ChangeThing active__boolean true |
bTHop1, bTHqz1, bTHrP1 |
index / reset_pw workflows | native LogIn, SendPasswordResetEmail, ResetPassword — no portable credential material |
navigationindex |
/option_sets/navigationindex |
db_values signin/signup/reset/verify; displays SignIn/SignUp/Reset/Verify; index conditions compare the display |
#Decision
open — no choice yet. Implement may build the Supabase session client, the profiles row and
RLS, but must not wire an activation path or a credential-cutover story until this ADR is accepted.
#Options
#A — Preserve the Bubble activation chain on Supabase Auth
Plain: people get the same "Verify" email they get today; clicking it lands them on onboarding and switches the account on there.
Confirm-email stays off in Supabase; an Edge Function sends the Verify template; the onboarding
route keeps the active=yes branch, now as a server-checked RPC rather than a browser write.
Trade-offs: highest fidelity and existing links keep working; but "email verified" is then an app convention rather than an Auth-enforced fact, and the activation write needs its own authorization.
#B — Replace with Supabase confirm-email
Plain: people get Supabase's standard confirmation email, and the account is on once they confirm.
profiles.active is set from the confirmation event (trigger or callback); onboarding drops its
activation branch and the custom Verify template is retired.
Trade-offs: identity semantics are owned by the platform and the deep-link branch disappears; but
in-flight Bubble links break unless a redirect is kept, and the exact moment active flips must be
written down because gates read it.
#C — Credential cutover: forced reset (pairs with A or B)
Plain: everyone signs in once via "forgot password".
Trade-offs: simplest and safest; one-time friction for every existing user, and the reset mail must reach addresses that may be stale.
#D — Credential cutover: time-boxed dual run (pairs with A or B)
Plain: both systems accept logins for a window; accounts move as people sign in.
Trade-offs: no user-visible break; two identity stores to reconcile, and a hard end date that someone must enforce.
#E — Credential cutover: external IdP bridge (pairs with A or B)
Plain: sign-in moves to an external provider (e.g. social or SSO) instead of email/password.
Trade-offs: removes password handling entirely; changes the product's sign-in surface, which the export does not evidence, and needs its own onboarding for people without such an account.
#Consequences
- Accepting this ADR unblocks
AUTH-FOUNDATIONand closes three open questions that are the same decision:DOC-OVERVIEWHQ-2,DOC-ARCHITECTUREHQ-1 andDOC-MOD-AUTHHQ-1. - Whatever is chosen, the target parses
navcase-insensitively and keeps a redirect foronboarding?nav=<role>&active=yesfor as long as old emails are live. - Option B requires
docs/modules/MOD-AUTH.mdJ2 and the onboarding activation branch to be rewritten; option A requires the activation write to move behind server authority (LOG-CLIENT-WRITES).
#Links
docs/modules/MOD-AUTH.md§ Needs human clarification (HQ-1), § Signup activation (chased)docs/open-questions/DOC-MOD-AUTH.md,docs/open-questions/DOC-ARCHITECTURE.mddocs/ARCHITECTURE.md§ Auth and sessiontooling/cursor-impl-cycle/output/MODULE-MAP.md→ ACC-AUTH-SURFACE