AUTH-CONTRACT — How Cetking login really works

Written 5 Oct 2026 from the actual code and database, not from plans. Updated 9 Oct 2026, 20:00 IST. Every AI tool and developer reads this before touching login. If code and this file disagree, the code wins and this file gets corrected.

Status at a glance (9 Oct 2026, 20:00 IST)

Universe login is LIVE for everyone (opened 6 Oct 2026, 08:27 IST, test week before the first rollout): https://cetking-platform.vercel.app/login

Piece Status
Join for Free | Login tabs, mobile OTP ✅ Live — Ravneet confirmed Join free works on his phone
New students (permanent Cetking ID, 0 coins) ✅ Live (switch ON)
Existing 414 students ✅ Already linked — no migration needed
Google: Connect Gmail after OTP, Continue with Google (Login tab, first) ✅ Live — phone test still pending
Google first, mobile second (unknown Gmail → mobile box → OTP → Gmail connected) 🔵 Built 9 Oct 2026 on branch login-google-then-mobile, waiting for "go live"
Rav's chat login: mobile OTP only (no tabs, no Google) 🔵 Same branch
Terms & Privacy line, home "Mobile Number | Join for Free" bar, profile Log out ✅ Live
1,000 coins at 100% profile ⏳ Waiting for Ravneet's decisions
Open to everyone ✅ 6 Oct 2026 — Ravneet removed CK_IDENTITY_PILOT_PHONES in Vercel (put it back to limit login to pilot numbers again)
Rolling login (returning students never re-login) ✅ Live 6 Oct 2026 (PR #144). See SESSION-CARRYOVER.md
Carry cetking.com logins into the new app (no re-login when cetking.com moves) 📝 Plan written — SESSION-CARRYOVER.md, step 2
Google One Tap ("Continue as ") 🔮 Future scope
Timetable ✅ Back on the live site: https://cetking-platform.vercel.app/timetable

PRs merged 5 Oct 2026: #134 (tabs + this doc), #135 (Google), #136 (Google first + profile logout), #137 (Terms line), #138 (home bar, "Join for Free", Google logo). Database migrations (Cetking Learn): ck_identity_universe_registration, ck_identity_google_signin.

The one rule

One person, one permanent Cetking ID, one coin wallet — on every site. Mobile OTP is the only way IN the first time. Gmail is only a faster way BACK. (Ravneet, 9 Oct 2026)

Mobile OTP / Google  →  Cetking identity (Cetking Learn database)  →  permanent person + student ID
                                                                    →  Universe, Arena, attendance, TCY, coins, future apps

A website is only a front door (gateway). Front doors can be added or removed. The identity behind them never moves and is never copied.

Never: create a second user table, a second wallet, merge accounts by email or name, let a Gmail open an account that has no verified mobile, or award coins from anything the browser sends.

Note for anyone counting sign-ups: Supabase Auth (auth.users) gets a row every time someone taps Google, even when Cetking refuses them. Those rows are attempts, not students. Real joins are ck_identity.audit_log event registered; real students are public.students.

Where identity lives

  • Database: Supabase project Cetking Learn (suqcijtpfeaystltekfn).
  • Permanent person: ck_leads.people. Student record: public.students. Coins: public.ck_coin_ledger.
  • Student ↔ person link: ck_leads.person_links (namespace arena:suqcijtpfeaystltekfn:students).
  • The Universe's own project (Cetking One AI, jfbauorxtmgvagwoyabr) holds no identity.
  • Check on 5 Oct 2026: 437 students = 414 real (all linked, no duplicate numbers, all numbers in +91… format) + 23 test accounts. No migration was needed for existing students.

Front door 1 — cetking.com (WordPress, built by ChatGPT, live)

  • Plugin plugins/cetking-one (branch chatgpt/cetking-one-welcome-campaign, PRs #114–#116, #118, #119; still draft/unmerged).
  • Addresses: cetking.com/wp-json/cetking-one/v1/ + otp/verify, session/exchange, session/refresh, session/logout, me, devices, devices/revoke, google/start, google/status, profile, profile/save, enquiries, session/migrate.
  • Steps: MSG91 OTP in the browser → server re-checks the proof with MSG91 (verifyAccessToken) → short-lived login ticket → device session.
  • Session = two random secrets in locked cookies (not readable by JavaScript, cetking.com only). No JWT. The database stores only scrambled (hashed) copies.
  • All database work goes through ck_one_auth, ck_one_google, ck_one_profile.
  • Deliberately refuses requests from other websites and only runs on exactly https://cetking.com. So other sites cannot borrow it from the browser. Do not loosen this.
  • Sites are switched on per row in ck_one_private.scopes. On 5 Oct 2026 there is one row: https://cetking.com (login on, registration on, welcome coins on).
  • Welcome coins: ck_one_private.award_welcome + ck_one_private.welcome_grants, keyed by person, so 1,000 coins once per person across every site. Today cetking.com gives them at registration and at Google linking.
  • Old Arena login bridge (legacy_until) ends 10 Oct 2026, ~04:20 IST. After that, old Arena logins must sign in again with OTP. To confirm with ChatGPT that this is intended.
  • Version note: branch code says 0.6.4; recorded live version was 0.6.2. Confirm before relying on it.

Front door 2 — Cetking Universe (Next.js on Vercel)

  • Code: cetking-one/lib/identity/*, cetking-one/app/api/auth/* (otp/verify, me, logout, google/start, google/callback), cetking-one/app/api/profile/*, screen cetking-one/components/auth/OtpLogin.tsx, page /login, home quick-join bar cetking-one/components/auth/QuickJoin.tsx, profile logout cetking-one/components/profile/ProfileLogout.tsx.
  • OTP: MSG91 in the browser → Universe server re-checks the proof with MSG91 → session cookie __Host-ck_session (random token; database stores only its hash).
  • Database functions (Cetking Learn, service role only): ck_identity_login, ck_identity_me, ck_identity_logout, ck_identity_profile, ck_identity_profile_save, ck_identity_rate_hit, ck_identity_google, helper ck_identity.ensure_student.
  • Settings (Vercel environment variables, Production only, server only; never in chat or code): CK_IDENTITY_ENABLED, CK_IDENTITY_SUPABASE_URL, CK_IDENTITY_SUPABASE_SERVICE_KEY, CK_IDENTITY_SECRET, MSG91_AUTH_KEY, CK_IDENTITY_PILOT_PHONES (pilot numbers only), CK_IDENTITY_GOOGLE_ENABLED (true shows the Google buttons). Browser: NEXT_PUBLIC_MSG91_WIDGET_ID, NEXT_PUBLIC_MSG91_TOKEN_AUTH. Because these are Production-only, preview (blue) builds cannot show login; login is tested on the live site, limited to the pilot numbers. https://vercel.com/cet-king-one/cetking-platform/settings/environment-variables

Current switches (6 Oct 2026, 08:27 IST) — open to everyone

  • CK_IDENTITY_PILOT_PHONES removed → every number can log in; unknown numbers join free (new Cetking ID, 0 coins). To go back to pilot-only, add it again with the pilot numbers and redeploy.
  • New-student joining ON (ck_identity.settings.allow_registration = true).
  • Google sign-in ON (CK_IDENTITY_GOOGLE_ENABLED=true; Supabase redirect URL added: https://cetking-platform.vercel.app/api/auth/google/callback?ck_state=*). https://supabase.com/dashboard/project/suqcijtpfeaystltekfn/auth/url-configuration

New students

  • Switch: ck_identity.settings.allow_registration (one row). Off = new numbers get "not registered".
  • On: a verified new number gets a public.students row (signup_tool = 'Cetking Universe'), a person in ck_leads.people and the link — through ck_identity.ensure_student, which uses the same locks and conflict checks as cetking.com registration, so the two sites can never create two students for one number. 0 coins.
  • Always on (fix): an existing student whose person link was missing is linked at login instead of being blocked.
  • Dry-run tested (all undone) and confirmed by Ravneet on his phone (Join free works).
  • Every new join also gets a lead card (ck_leads.lead_cases) for the counsellors.

Google sign-in

  • Same rules and same table as cetking.com (ck_one_private.google_identities), so a Gmail connected on one site works on both.
  • Connect: only from a session opened by mobile OTP in the last 10 minutes on that browser. Identity read from Supabase Auth's verified Google record (auth.identities), never a typed email. One Google ↔ one person; conflicts refused.
  • Login: a connected Gmail opens a normal Universe session (login_method = 'google') and goes straight back to the page the student started from.
  • Google first, mobile second (9 Oct 2026): a Gmail that is not connected yet is no longer turned away. The callback keeps that verified Google account in a signed, httpOnly cookie __Host-ck_google_pending (this browser only, 10 minutes; holds the Supabase Auth user id and a masked hint like ra•••@gmail.com) and opens /login?mode=login&google=google_pending&next=… with the mobile box ready (title "One last step", the masked Gmail and a "Not you?" link). When the mobile OTP succeeds on that browser, /api/auth/otp/verify (body linkGoogle: true) connects that Gmail to the account the OTP opened, through the same ck_identity_google link check (OTP session under 10 minutes old, conflicts refused), clears the cookie, and the student goes on to next.
    • New person → OTP creates the account → Gmail connected.
    • Existing student who never connected Gmail → OTP opens their existing account → Gmail connected.
    • The screen never says which of the two it was.
    • Never connected on a shared computer, or when the student tapped "Not you?".
    • /api/auth/me shows a signed-out visitor only the masked hint, never the id.
    • Tests: tests/identity/google-pending.test.ts.
  • Flow: /api/auth/google/start → Supabase /auth/v1/authorize (PKCE, signed httpOnly flow cookie) → Google → /api/auth/google/callback → code exchange + user check → ck_identity_google. The final address carries only a short result code.
  • Dry-run tested 5 Oct (all undone): connect after OTP ✓; Google login → same person and student ✓; connect from a Google session ✗; connect 10+ min after OTP ✗; unconnected Gmail login ✗ (now → mobile box).

Still open

  1. Coins — waiting for Ravneet. New rule wanted: 1,000 coins when the profile is 100% complete (not on joining). Decisions pending: what counts as 100% (the current 11-item checklist includes "email or Gmail"), whether cetking.com keeps registration coins, and old students who already got 1,000. Must be awarded inside the database through award_welcome (once per person), never from the browser.
  2. Sessions are separate per front door (logging in on cetking.com does not log you into the Universe). Plan to carry cetking.com logins over with no re-login: SESSION-CARRYOVER.md.
  3. Universe session lengths: 7 days without a visit, 12 hours on a shared computer. Since 6 Oct 2026 an active login is renewed once a day (ck_identity_renew), so the old hard 30-day end no longer forces daily users to log in again. Details: SESSION-CARRYOVER.md.

Future scope

  • Google One Tap ("Continue as " pop-up showing the student's own Google account, so they just tap it). Needs Google Identity Services on the page and an ID-token sign-in path in place of the current redirect button; about half a day. Must keep the same rules: a Gmail opens an account only once a mobile OTP has been done. We never pre-fill an email ourselves (we don't know who the visitor is before login). Requested by Ravneet 5 Oct 2026; parked as future scope.

Login screen rules (Ravneet, 5 Oct 2026; updated 9 Oct 2026)

  • One card, two tabs: Join for Free (default) | Login. /login?mode=login opens Login.
  • Stage 1 for joining is always mobile number → Send OTP → enter OTP. No name, no long form.
  • Both tabs run the same login. The server alone decides new vs existing. The screen must never reveal whether a number is already a student (no "account exists" checks before OTP) — this protects student privacy and the opacity rule.
  • Mobile OTP always works for everyone (first login and recovery). Google never replaces it.
  • Stage 2, right after OTP: a Connect your Gmail card for one-tap login next time.
  • Login tab: Continue with Google first (white button with the Google "G"), then "or log in with your mobile number" and the OTP form. A Gmail we don't know yet comes back to the mobile box (no "first time here?" question) and is connected after the OTP.
  • Inside Rav's chat (compact card): mobile OTP only — no tabs, no Google button. Number → OTP → "You're in" → the chat carries on (Ravneet, 9 Oct 2026: Google would take the student off the chat page mid-conversation, and every chat login should give a verified phone).
  • Under the card: "By continuing, you agree to Cetking's Terms & Privacy Policy" → https://cetking.com/privacy-policy-general-terms-conditions/
  • Home page: Mobile Number | Join for Free bar under the bot lineup (signed-out visitors only); it opens the join card with the number pre-filled (handed over in the browser tab, never in the web address).
  • Profile page: Log out of this device button at the bottom.
  • Signed in: the top bar shows the coins pill, which opens the profile; profile cards belong to Oracle.
  • Colours: greys only (the Google "G" keeps Google's own colours).

Order of work (Ravneet, 5 Oct 2026)

Finish login → test → then other features. The timetable code was removed from the live site and kept on branch parked-timetable (it has a known type error in app/api/timetable/route.ts to fix before it comes back).

Rejected ideas (and why)

  • "Existing number → skip OTP, Google only": locks out everyone who never linked Google.
  • "Check if the number exists before sending OTP": leaks who is a Cetking student.
  • "Google creates a new account instantly": accounts with no verified mobile, and duplicates.
  • "Universe keeps its own users in Cetking One AI": a second identity and wallet.
  • "Put mobile OTP above Google on the Login tab" (9 Oct 2026): returning students who connected Gmail would start using OTP again (slower, and an SMS each time). Instead, an unknown Gmail is caught and sent to the mobile box.

Source: GitHub cetking-one/docs/AUTH-CONTRACT.md. Edit the file there; this page updates on the next release.