Universe Login Plan

Branch: universe-login (never pushed straight to the live branch until approved). Owner: Ravneet. Updated 4 Oct 2026. Status: Build step 1 built and tested, switched OFF (details and switch-on steps in docs/LOGIN-STEP1.md). Steps 2–10 not started.

Build gate: nothing is built until every row in the Issue Register below has an agreed design answer, and each launch test linked to it is written. Current state: gate clear. All 22 rows answered; MSG91 approved; pilot chosen. Access tiers are needed only before build step 7.

Inputs: advice from Qwen, DeepSeek, Gemini, ChatGPT and Grok, tested against the current system (docs/cetking-one/LOGIN-SYSTEM-MEMORY.md, PR #118, plugin 0.6.2 at 784fe15) and the full login issue history (22 issues).

Goal

One central Cetking login ("Cetking Identity") used by Universe first, then every Cetking site (Arena, cetking.com, cetking.in, Cetking One, books, future apps). A new site plugs in; it never gets its own login screen or student table.

Core rules

  1. One student, one permanent Cetking identity. Phone OTP creates it. Google is a linked way back in, never a separate account.
  2. Reuse existing identity data. Permanent Person ID (ck_leads.people), Arena student ID (public.students) and coin ledger (public.ck_coin_ledger) stay. No new student table, no new wallet, no coin resets.
  3. Login proves who you are. Entitlements decide what you can open. Kept separate.
  4. The server decides who the student is, always. From the secure cookie only. Never from browser storage, a page field, or a student_id sent by the browser.
  5. Pages contain no login code. They call the shared login service. Replacing a page's HTML must never be able to break login.
  6. "Logged in" means the whole chain works. Login, name, coins, history and test launch are each confirmed for the same student before success is shown.
  7. One shared login component, one version, one place. Login form, OTP step, success card, Google buttons, profile and device list all come from it. Pages never copy or edit it; it is deployed only from the repository, never pasted in a live editor.
  8. No screen can get stuck. Every waiting state (sending OTP, verifying, Google return, loading account) has a time limit and ends in success, a clear error with Retry, or "Continue".
  9. The student always ends up where they started, with their unfinished work kept.
  10. Opacity: students never learn how many users Cetking has (see below).

Opacity rule (Ravneet, 4 Oct 2026)

Goal: a student sees only their own data and the shared leaderboard. Nothing on any screen, and nothing the server sends, lets anyone count or list Cetking's real users. Applies to every page and every bot (see Universe docs/ARCHITECTURE.md → Opacity and usernames).

  • No real totals anywhere students can reach. No live "X students", "rank 412 of 5,000", member counts or batch sizes in any page or server response.
  • Usernames, not real names. Students pick a public username (e.g. mickey121). Leaderboards, comparisons and challenges show usernames only; real names are seen only by the student, staff and Ravneet.
  • Leaderboard shows the top list only. A student outside the top list sees their position as a band ("Top 10%", "Top 25%") rather than a rank number, so their rank can't be used to estimate the total.
  • Bots know only the student they are talking to, plus anonymised toppers and benchmarks by username ("mickey121 is rank 1"). Code builds the bot's context, so other students' data and counts never reach the LLM.
  • No other students' IDs ever reach the browser. Leaderboard rows carry a short-lived display key, not the real student ID, so no one can look up another student by ID.
  • IDs can't be guessed or counted. Student and Person IDs are random (UUIDs), never 1, 2, 3…; no route lists students or accepts a page number over students.
  • Login never reveals who is registered. Same response for registered and unregistered numbers until the OTP is correct (already in Security settings).
  • Rate limits on leaderboard and profile requests, so the system can't be scraped one request at a time.
  • Errors give nothing away. "Not found" and "not allowed" look the same to students.
  • Staff (CK Backoffice) and Ravneet see real counts; students never do.
  • Exception: marketing numbers. A number Ravneet sets by hand for marketing (e.g. the Universe footer "5,314 users of 2026 batch") is allowed. It is a fixed figure he chooses, never computed from the real database.

Safe live-change rules (from the 3 Oct blank-test incident)

These apply to every Cetking site, not only login.

  1. One change at a time on the live site. Each change is checked on the live site (login, coins, history, open a test) before the next one goes in. Never update two systems (e.g. Arena board and login) together.
  2. No pasting code into a live editor. Every live file comes from the GitHub copy, so we always know which version is running.
  3. Automatic check before going live. Every changed page's JavaScript passes a syntax check and the launch tests; a failure blocks the change.
  4. Record what is live. After each change, note the file, version and time in the "what is live" record, so a broken page can be compared against a known-good copy.
  5. Know the way back. Before each change, keep the current live copy so it can be restored in one step.

Login screen (every browser without a valid session)

  • I am a new student → mobile + 6-digit OTP → account opened → "Link Google so next time you skip the OTP" (skip allowed; asked again on the next device).
  • I already have an account → Continue with Google. Matched on Google's permanent ID (sub), never the email. If that Google account is not linked, no new account is created: ask for the registered mobile, verify OTP, then link.
  • Can't access your account? → recovery.
  • Shared computer tick box → session ends when the browser closes.

The OTP box appears inside the same form (no separate popup). Name and number are kept on retry, resend and "change number".

After login: a success card (Welcome / Welcome back, name, permanent Cetking ID, real coin balance, big My profile button, "1,000 welcome coins added" only when the grant is confirmed). Then: "You are also signed in on Chrome on Android, last seen in Mumbai" → keep both, or sign that one out. A browser with a valid session shows "Continue as " / "Use another account".

Security settings (starting values, tune later)

  • OTP: 6 digits, 5-minute expiry, single use, stored hashed, never logged. 5 wrong tries per code; 3 sends per 15 min and 8 per day per number; 60-second resend wait; limits also per IP. No permanent lockouts. Never reveal whether a number is registered before the correct OTP.
  • Google: authorization code + PKCE, state and nonce, exact return URLs, basic scopes only (openid, email, profile).
  • Sessions: Secure, HttpOnly cookies; nothing in localStorage. Short access token (~15 min); refresh token rotates every use; reuse of an old one ends that session family. Personal device: 30-day max, 7-day idle. Max 5 devices; the 6th asks the student to remove one. Step 1 uses one server-checked session cookie per device (checked against the database on every request, so logout is instant); the access/refresh split with rotation is added in build step 5.
  • Device list in Profile: log out this device / other devices / everywhere.
  • Changing mobile or Google needs fresh verification. Lost phone = support ticket, then revoke all sessions. Mobile cannot be changed from a Google login alone.
  • Student IDs: once created, students cannot change or delete them; only Ravneet can delete an ID.
  • Audit log of every login, link, logout, recovery and change.
  • Welcome coins: existing once-per-Person rule kept exactly as is.

Access levels (entitlements)

Free, paid classes, paid books, classes + books, online, offline, more later. Stored per Person with start/end dates; checked on the server for every protected item. Arena tests already carry an access_level field; entitlements plug in there. A student without access sees "This needs " — never a blank page. Exact tier list: Ravneet to supply later. Needed before build step 7 (entitlement checks), not before steps 1-6.

How other sites connect

  • auth.cetking.com is the only place that sends OTPs or talks to Google.
  • Each site is a registered client (exact return URLs, allowed scopes).
  • *.cetking.com sites show the shared login form on the same page (no navigating away). Other domains (cetking.in) use the standard code hand-off, never a copied cookie or a token in a URL.
  • After any login (OTP or Google) the student returns to the exact page and unfinished card/result they started from.
  • Every page re-checks the session when shown, including Back-button and late-loaded pages, and keeps retrying after an early failure.
  • One shared "account changed" signal updates every part of the page and every open tab (header coins, name, profile) after login, logout, account switch or coin change.

Issue Register (all 22 past issues)

Status key: Designed = the new design prevents it by construction · Test = also needs a specific launch test · Closed = cause found and fixed.

# Past issue How the new design prevents it Status
1 OTP opened in a separate popup, sometimes behind the form OTP step is inside the same form (shared component) Designed + Test
2 OTP not received on /contact; some couldn't finish One login component for every page, so one thing to test; MSG91 delivery and failure rate monitored with alerts; every page phone-tested before launch Test
3 No thank-you confirmation Success card is a fixed step, shown only after name + coins confirmed Designed
4 Header coins stayed stale Shared "account changed" signal updates header and all tabs Designed + Test
5 Centre-page Login/Register went to Arena Login opens on the same page; links never navigate away Designed + Test
6 Google provider disabled / config incomplete Pre-launch config checklist (provider on, exact callback URLs, scopes) checked automatically by a health check Designed + Test
7 Google returned to a blank form Google return screen has fixed states with time limits (rule 8), ends on success card or error + Retry Designed + Test
8 No visible Connect Google option Shown right after first OTP login and permanently in Profile Designed
9 "Loading your account…" stayed after success No-stuck-screen rule (rule 8); loading text removed on every outcome Designed + Test
10 Extra Google panel stayed on profile Profile comes only from the shared component; no page adds its own Google panel Designed
11 No way to close profile / return to Arena Profile always has Back to where you came from Designed
12 Account buttons too small My profile ≥ 60px tall, other account buttons ≥ 52px, full width Designed
13 Existing students had no Google link Mobile OTP → Connect Google; OTP recovery always available Designed
14 Google UID, Person ID and Arena ID could be confused Server resolves one identity; apps see only the confirmed student; no email/name merging Designed
15 Welcome bonus could be paid twice Existing once-per-Person grant kept unchanged Designed + Test
16 Old browser flows wrote student records directly Browser writes stay blocked; only verified server registration Designed
17 HTML update broke Arena login return Pages hold no login code (rule 5) Designed + Test
18 Recovery stopped retrying; Back button and late scripts missed Session re-checked on every page show; retries continue after failure Designed + Test
19 PR #117 expected a missing endpoint Written "what is live" record; every change checked against it before deploy Designed
20 Arena looked logged in but data failed / blank tests Cause found: the live test screen had What''s wrong? (a doubled apostrophe, invalid JavaScript) from a damaged copy pasted during the avatar update, done at the same time as the login update. One bad character stopped the whole test screen. The GitHub copy has the correct What\'s wrong? and passes a syntax check; the PR #118 repair fixed the live file. Ravneet confirmed coins, tests and history work (4 Oct). Prevented by: safe live-change rules 1–3, rule 6 Closed + Test
21 Duplicate script copy in live editor Deploy only from repository (rule 7); deploy check confirms exactly one copy of the login script Designed + Test
22 Google doesn't resume custom cards Return-and-resume: unfinished card answers saved safely and restored after login (rule 9) Designed + Test

Testing limits from last time, now required: checks must run from a route the firewall doesn't block (HTTP 406 is not a pass), and every page's OTP, Google, PDF continuation, attendance and paid-access flow is tested on a real Android and iPhone before that page goes live.

Lessons from the current system (code test results)

Old problem Cause New design
Arena login broke when its HTML was replaced Login logic lived inside the page Pages hold no login code (rule 5)
Arena trusts cetkingStudentId from browser storage; #118 copies the ID there and reloads Identity read from editable storage Server decides identity (rule 4)
Coins/history showed empty Arena data routes accept student_id from the request Data routes derive student from the session
"Logged in but no data" 3 IDs linked through a 4-step chain; one gap = empty account One place resolves the chain; a missing link is a clear error, never "0"
PR #117 incompatible; live file has a duplicated block main, branches and live files drift apart Written record of exactly what is live; checked before every deploy
Google returns to Profile, not the original page No safe return path Return page carried through sign-in
Login API blocked (HTTP 406) WordPress host firewall Login runs on its own address
Clicking a test opened a blank page Damaged copy pasted into the live editor (invalid JavaScript), during two simultaneous updates Safe live-change rules: one change at a time, deploy from GitHub, automatic syntax check

Arena data-access check (4 Oct 2026, code reading only)

Question: can someone get another student's coins or history by sending a request with a different student_id directly to the server (bypassing the screen)? Students see only their own coins on screen (confirmed by Ravneet); this check is about the server, not the screen.

  • GitHub copy of Arena (plugins/study-arena-zone): safe. Private routes (/me, /history, /student, /result, /orders, /challenge-rival) require a valid Arena session and take the student only from that session, never from the request. Public routes (/arena, /leaderboard) accept a student_id but return only leaderboard data (rank, name, weekly coins).
  • Live Arena (legacy 1.3.4): not confirmed. The live file is older than the GitHub copy. The PR #118 repair patch shows the live test-list route read student_id from the request.
  • Not a build blocker: the new system makes this impossible by design (rule 4), and Arena joins the new login only at build step 9. Kept as a live-site follow-up.

Live-site follow-ups (separate from the new build)

  1. Get a read-only copy of the live Arena class-ck-lms-rest.php and confirm /me and /history use the session, not a browser-sent ID.
  2. Current Arena leaderboard sends real student IDs and real names to the browser; align with the opacity and usernames rule when Arena moves to the new login.

Keep from the current system

  • Person ID, Arena student ID, coin ledger and all history.
  • Once-only 1,000 welcome coins with duplicate protection.
  • "Coins unavailable" shown as "—", never 0.
  • Browser writes to student records stay blocked.
  • Inline OTP form, success card design (navy + gold), large buttons, Connect Google in profile, profile navigation — all already approved by Ravneet.
  • Server-side MSG91 verification and ticket exchange, refresh rotation and reuse detection, separate device sessions (re-use as proven design).

Dropped from earlier drafts

  • Fresh person table (would repeat the "logged in, no data" failure).
  • Device fingerprinting library (unreliable on classroom PCs).
  • Redis (rate limits already work in Supabase).
  • Session transfer codes / push approval (device list + sign-out is enough).

Build order

  1. ✅ Built, switched off (4 Oct 2026): phone OTP + one session, mapped to the existing Person and student IDs. /login, /api/auth/otp/verify, /api/auth/me, /api/auth/logout, ck_identity migration (not yet applied). See LOGIN-STEP1.md.
  2. Login choice screen ("I am a new student" registration), success card polish.
  3. Google linking with return-and-resume.
  4. Device list and "sign out other device".
  5. Rate limits and refresh rotation hardening (access/refresh split).
  6. Health check: login → name → coins → history → test launch, same student.
  7. Entitlement checks (needs Ravneet's access-tier list).
  8. Universe as first client. Pilot starts with Ravneet's own phone numbers (his main number plus a couple of his other numbers), then 20-30 students; old logins untouched.
  9. Register the second site (Arena) only after the above passes.
  10. Retire old login systems one at a time.

Launch tests (must pass)

Existing student taps "New student" (recovered, no duplicate, no extra coins) · Google linking cancelled (resumable) · two registrations at once (one account) · phone + laptop (same coins and history) · refresh during Google return (no duplicate link/coins) · moving between sites (same identity, right access) · session expires during a mock (answers kept) · log out everywhere · database/provider down (retry message, no false empty balance) · account switch on a shared PC (old student's data gone) · request for another student's records (denied by the server) · OTP inside the form on every page · header coins update without reload · login on a centre page stays on that page · Google return never blank or stuck · Back button after login still shows the student · unfinished card resumes after Google · exactly one login script on every live page · test opens (not blank) and history shows for the logged-in student · every changed page passes a JavaScript syntax check · no page or server response reveals a real user total, a full rank out of N, another student's real ID or real name · registered and unregistered numbers get identical pre-OTP responses · each page passes on real Android + iPhone.

Decided

  • OTP: 6 digits. Google sign-in: yes, in version 1.
  • MSG91 OTP template: DLT approved (confirmed by Ravneet, 4 Oct 2026).
  • Multiple devices: allowed (faculty already use multi-device login).
  • Pilot: Ravneet's own numbers first, then 20-30 students.
  • Access tiers: deferred; Ravneet supplies the list before build step 7.
  • Opacity: students never see real user counts; staff and Ravneet do.
  • Usernames: students will have public usernames; others (students and bots) see only usernames, never real names.
  • Universe footer "5,314 users of 2026 batch" stays: an intentional marketing number, set by hand, not a live count.

Not in this plan

Mock/bot features, WordPress feature work, payments, Arena redesign.

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