Login build step 1 — phone OTP + one session
Branch: universe-login. Plan: docs/LOGIN-PLAN.md. Status: built and tested, switched OFF.
What it does
/loginpage: mobile number → OTP box inside the same card (no popup) → navy + gold account card showing the student's own name, Cetking ID and real Lifetime CK Coins. Shared-computer tick box (session ends when the browser closes).- Signs in existing Cetking students only, using their existing permanent Person ID, Arena student ID and coin ledger. No new student, wallet or coins are ever created. (New-student registration is build step 2.)
- One secure session per device:
__Host-ck_sessioncookie (HttpOnly, Secure, SameSite=Lax), 30 days max / 7 days idle on personal devices, 12 hours on shared computers. Up to 5 devices; the 6th is told to log out on one (never silently dropped). GET /api/auth/me(own profile only),POST /api/auth/logout(this device).- Home headline shows "'s Learning Universe" once login is switched on.
Safety built in
- The browser's "OTP verified" is never trusted: the server re-checks every verification with MSG91 and requires the verified number to match.
- Each MSG91 verification can open only one session (replay blocked).
- Rate limits per IP and per number; if the limiter is down, nobody gets in (fails closed).
- Pilot list: only the numbers in
CK_IDENTITY_PILOT_PHONEScan sign in. Checked only after the OTP is correct, so it never reveals anything about a number. - Cross-site requests blocked (same-origin check + custom header).
- Database: new separate area
ck_identityin Cetking Learn. Only the server can use it (no browser access; RLS on, no policies). It reads people/students/coin ledger and never writes them. The live WordPress login (ck_one_private) is not touched. - No OTPs, tokens, phone numbers or IPs are logged; the database stores only hashes.
- "Logged in" is shown only after name and coins load for that same session (plan rule 6). A database problem shows "temporarily unavailable", never "0 coins" or "signed out".
- While switched off, every page behaves exactly as before (home stays static).
Tests
npm test(incetking-one/): 64 tests — phone formats, MSG91 confirmation, rate limits, pilot list, replay, device cap, cookie flags, cross-site blocking, logout, no false zeros.supabase/tests/ck_identity_step1_test.sql: database functions on a throwaway local Postgres with fake data (login, replay, broken links, device cap, expiry, permissions). Never run it against a real project.next buildpasses;/stays static while login is off.
How to switch it on for the pilot (preview only, live site untouched)
- Database (Claude, after Ravneet approves): apply
supabase/migrations/20261004090000_ck_identity_step1.sqlto Cetking Learn (suqcijtpfeaystltekfn). Additive only; rollback steps are at the bottom of the file. - Vercel environment variables (ChatGPT, scoped to Preview for branch
universe-loginonly — not Production):CK_IDENTITY_ENABLED=trueCK_IDENTITY_SUPABASE_URL=https://suqcijtpfeaystltekfn.supabase.coCK_IDENTITY_SUPABASE_SERVICE_KEY= Cetking Learn service-role keyCK_IDENTITY_SECRET= a new random value, 64 characters (generate fresh; never reuse)MSG91_AUTH_KEY= the same MSG91 auth key the WordPress login usesNEXT_PUBLIC_MSG91_WIDGET_IDandNEXT_PUBLIC_MSG91_TOKEN_AUTH= the same MSG91 OTP widget ID and public token the WordPress login usesCK_IDENTITY_PILOT_PHONES= Ravneet's numbers, comma-separated
- MSG91: if the OTP widget restricts allowed domains, add the Vercel preview domain.
- Redeploy the
universe-loginpreview, open/loginon a phone, sign in with a pilot number.
Pilot checks (Ravneet, on a real phone)
- OTP arrives; wrong code shows a clear message; Resend works after 60s; Change number works.
- Card shows your real name and coins (same as Arena).
- Home page shows "'s Learning Universe".
- Log out works; signing in on a second device keeps the first signed in.
- A non-pilot number gets the "private test" message after OTP.