Auth entry consolidation¶
Kwilo used to have several self-serve auth entry points (/signup, /login, /register) with different UIs, different auth methods, and URL-based branching that didn't carry context through to post-login. This work consolidates them to one /signup and one /login, branched by persona inside the page, matching the GitHub / Atlassian / Notion / Canva / Stripe pattern.
Shipped: /register deleted, signup consolidated under /signup with an in-page user_type picker, the users.user_type column added, and the creator path unified across email, phone, and Google. Still proposed: a shared AuthShell, login method parity, and email-domain auto-link (tracked in auth-domain-matching.md).
How it works¶
Identity and membership are orthogonal. A user is one row in users (identity: email or phone) with one personal workspace and 0..N organization_memberships. Role is a property of the active membership, not the user row. signup_persona (now user_type) is captured once at signup for onboarding and analytics, not for authz.
BEFORE: URL encodes persona
/signup → b2c_user role implied
/register → external_educator role hardcoded
/login → role from DB, no URL hint
AFTER: URL encodes intent, persona is data
/signup → user-type picker when no ?as=
/signup?as=... → persona pre-selected
/login → method from last_login_method
Signup¶
One /signup URL. With no ?as= param it shows a persona picker (Trainer / Learner / Independent Educator / Institution); with the param present the persona is pre-selected. Creator signups get role=external_educator and land on /creator/onboarding. Picking Institution redirects to kwilo.ai/#demo (sales-led). A "None of these" fallback routes to kwilo.ai/#contact.
Login (proposed)¶
The proposed /login work adds Google OAuth and Phone OTP alongside email, so a user who signed up with one method can log in with it. The default tab honors last_login_method on the user row. Returning B2B users keep their exact username + password flow unchanged.
/register deleted, not redirected¶
RegisterPage.tsx was deleted outright rather than 301'd. The only inbound link was LoginPage's "Create account" link, updated to buildSignupUrl({ user_type: 'creator' }). The page was noindex with no bookmarks to protect, so APP_REGISTER_URL and the cross-app /register redirect on apps/site were removed too.
Non-goals¶
- No self-serve B2B institutional signup. Institutions go through demo, sales, then admin-provisioning.
- No change to role-based access (
RoleGuard,ROLE_GROUPS). - No migration of existing B2B users. Usernames and passwords keep working.
- No Slack-style per-workspace accounts. Email is the identity primitive: one person, one account, across orgs.
- No workspace switcher UI in this scope. A multi-org trainer lands in their primary org.
- No marketing-site nav rework. Only the auth-entry CTAs change.
- No magic-link auth. Phone OTP covers the same case faster for the Indian market.
Edge cases¶
- Institutional domain but school not onboarded yet: land in personal workspace with an "invite your school" nudge. No ghost orgs.
- Google OAuth email already in use as a B2B username: attach OAuth to the existing account, no duplicate row.
- 2+ org memberships: land in the last-visited org with a workspace switcher icon (full switcher is later work).
Where it lives¶
- Routes:
/signup,/login,/forgot-password,/reset-password apps/web/src/pages/auth/SignupPage,LoginPageusers.user_typecolumn (migration 00042), backfilled from the oldnotification_preferences ->> 'persona'- Phase 3 domain auto-link:
auth-domain-matching.md