diff --git a/docs/WINDSURF/AUTH_CROSS_PRODUCT_ANALYSIS.md b/docs/WINDSURF/AUTH_CROSS_PRODUCT_ANALYSIS.md new file mode 100644 index 00000000..23ab8ad3 --- /dev/null +++ b/docs/WINDSURF/AUTH_CROSS_PRODUCT_ANALYSIS.md @@ -0,0 +1,239 @@ +# Auth Cross-Product Analysis — Full Workspace Audit + +> **Date:** 2026-02-28 +> **Scope:** All 4 product repos + common platform +> **Question:** Do all apps share the same auth? Can a ChronoMind user sign in to NomGap? What's missing? + +--- + +## 1. Backend Architecture (Single Source of Truth) + +All products share **one** platform-service (port 4003) in `learning_ai_common_plat`. + +### Auth endpoints available: + +| Endpoint | Status | Notes | +| -------------------------------- | -------------- | --------------------------------------------------- | +| `POST /auth/login` | ✅ Implemented | Requires `{ email, password, productId }` | +| `POST /auth/register` | ✅ Implemented | Creates user + subscription + license | +| `POST /auth/refresh` | ✅ Implemented | Exchanges refresh token for new pair | +| `GET /auth/me` | ✅ Implemented | Returns user from Bearer token | +| `PUT /auth/profile` | ✅ Implemented | Self-service profile update | +| `POST /auth/sso` | ✅ Implemented | Microsoft/Google OAuth (find-or-create) | +| `POST /auth/verify` | ✅ Implemented | Service-to-service token check | +| `POST /auth/forgot-password` | ✅ Implemented | Generates reset token (logs it, no email sent) | +| `POST /auth/reset-password` | ✅ Implemented | Resets password with token | +| `POST /auth/verify-email` | ✅ Implemented | Verifies email with token | +| `POST /auth/resend-verification` | ✅ Implemented | Resends verification email (logs it, no email sent) | +| Admin CRUD (`/auth/users/*`) | ✅ Implemented | List, count, get, update, delete | + +### Database: Single Cosmos DB + +- **Container:** `users` — all users across all products +- **Partition key:** user `id` +- **Product isolation:** Every user doc has a `productId` field +- **Lookup:** `getByEmail(email, productId)` — queries by BOTH email AND productId + +### JWT tokens + +- **Issuer:** `bytelyst-platform` +- **Access token:** 1 hour, contains `{ sub, email, role, productId, plan }` +- **Refresh token:** 7 days, contains `{ sub, productId }` +- **Secret:** Single shared `JWT_SECRET` env var + +--- + +## 2. The Cross-Product Sign-In Question + +### Current design: Users are **per-product** + +The `getByEmail()` function queries: + +```sql +SELECT * FROM c WHERE c.productId = @productId AND c.email = @email +``` + +This means: + +- **A user who registers on ChronoMind (productId: `chronomind`) is a DIFFERENT user than the same email on NomGap (productId: `nomgap`)** +- Same email can have separate accounts with different passwords on each product +- Each registration creates a separate subscription + license record per product +- JWT tokens are scoped to a productId — a ChronoMind token cannot be used for NomGap API calls + +### Is this the right design? + +**Yes, for now.** Here's why: + +1. **Different products = different plans/subscriptions** — A user might be on Pro for ChronoMind but Free for NomGap +2. **Clean data isolation** — each product's user data doesn't leak across +3. **Independent license/device management** — device limits are per-product +4. **Simpler admin** — admin dashboard shows users per product + +### Future consideration: ByteLyst Account (cross-product SSO) + +If/when you want "sign in once, use all ByteLyst apps": + +- Add a `byteLystAccountId` linking field to user docs +- Add a `/auth/link-account` endpoint +- This is a P3 feature, not needed now + +--- + +## 3. Per-App Auth Inventory + +### Legend + +- ✅ = Implemented and working +- ⚠️ = Partially implemented (missing features) +- ❌ = Not implemented + +### 3.1 LysnrAI (`learning_voice_ai_agent`) + +| Surface | Login | Register | Refresh | Forgot Password | Email Verify | SSO | +| --------------------------- | ----- | --------------- | ---------------- | --------------- | ------------ | ----------------------- | +| **User Dashboard (web)** | ✅ | ✅ | ✅ (cookie) | ❌ | ❌ | ✅ (Google + Microsoft) | +| **Admin Dashboard (web)** | ✅ | ❌ (admin-only) | ✅ (cookie) | ❌ | ❌ | ❌ | +| **Tracker Dashboard (web)** | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | +| **iOS mobile** | ✅ | ✅ | ✅ (Keychain) | ❌ | ❌ | ✅ (Apple, Google) | +| **Android mobile** | ✅ | ✅ | ✅ (SharedPrefs) | ❌ | ❌ | ✅ (Google) | +| **Desktop (Python)** | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | + +**productId:** `lysnrai` + +### 3.2 ChronoMind (`learning_ai_clock`) + +| Surface | Login | Register | Refresh | Forgot Password | Email Verify | SSO | +| ----------- | ----- | -------- | -------------------------- | --------------- | ------------ | --- | +| **Web PWA** | ✅ | ✅ | ❌ (no auto-refresh) | ❌ | ❌ | ❌ | +| **iOS** | ✅ | ✅ | ✅ (Keychain, 45min timer) | ❌ | ❌ | ❌ | +| **Android** | ✅ | ✅ | ✅ (SharedPrefs) | ❌ | ❌ | ❌ | + +**productId:** `chronomind` + +### 3.3 NomGap (`learning_ai_fastgap`) + +| Surface | Login | Register | Refresh | Forgot Password | Email Verify | SSO | +| ----------------------- | ---------- | ---------- | ----------------- | --------------- | ------------ | --- | +| **React Native (Expo)** | ✅ (store) | ✅ (store) | ⚠️ (hydrate only) | ❌ | ❌ | ❌ | + +**productId:** `nomgap` +**Note:** Auth store actions + ProfileScreen UI are wired. `hydrateFromToken()` calls `/auth/me` but there's no proactive refresh timer. No dedicated login screen — auth is inline in ProfileScreen. + +### 3.4 MindLyst (`learning_multimodal_memory_agents`) + +| Surface | Login | Register | Refresh | Forgot Password | Email Verify | SSO | +| ----------------- | ----- | -------- | -------------------------- | --------------- | ------------ | --- | +| **Web (Next.js)** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **iOS** | ✅ | ✅ | ✅ (Keychain, 45min timer) | ❌ | ❌ | ❌ | +| **Android** | ✅ | ✅ | ✅ (SharedPrefs) | ❌ | ❌ | ❌ | + +**productId:** `mindlyst` +**Note:** MindLyst web has NO auth at all — API routes use in-memory fallback or direct Cosmos, no platform-service integration. + +### 3.5 Dashboards (common platform) + +| Dashboard | Login | Register | Refresh | Forgot Password | SSO | +| ----------------------- | ----- | -------- | ------- | --------------- | --- | +| **Admin (port 3001)** | ✅ | ❌ | ✅ | ❌ | ❌ | +| **Tracker (port 3003)** | ✅ | ✅ | ✅ | ❌ | ❌ | + +--- + +## 4. Gaps — Prioritized Action List + +### P0: Critical (all users hit these) + +| # | Gap | Affected | Fix | +| ------ | ------------------------------------------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **G1** | **No "Forgot Password" UI anywhere** | ALL 4 products, ALL surfaces | Backend endpoints exist (`/auth/forgot-password`, `/auth/reset-password`) but ZERO clients call them. Need: forgot password form + reset password page in every app. | +| **G2** | **No email delivery for password reset / email verification** | ALL | Backend generates tokens but only LOGS them (`req.log.info`). The `TODO: Send email via delivery module` comment is still there. Need: wire delivery module (SendGrid/SES) or at minimum an SMTP transport. | +| **G3** | **MindLyst web has NO auth** | MindLyst web | Web dashboard has no login/register at all. API routes bypass platform-service entirely. Need: add auth flow matching ChronoMind web pattern. | + +### P1: Important (poor UX without these) + +| # | Gap | Affected | Fix | +| ------ | ----------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **G4** | **No email verification UI** | ALL | Backend has `/auth/verify-email` + `/auth/resend-verification` but no client calls them. Users register with `emailVerified: false` and it's never checked/enforced. | +| **G5** | **ChronoMind web missing token refresh** | ChronoMind web | Web stores token in localStorage but never refreshes it. After 1 hour the token expires silently. Need: add refresh logic (like the iOS 45min timer). | +| **G6** | **NomGap missing proactive token refresh** | NomGap mobile | `hydrateFromToken()` calls `/auth/me` on startup but there's no periodic refresh. Token expires after 1 hour. Need: add refresh timer or intercept 401s. | +| **G7** | **No "Change Password" in any settings screen** | ALL | Users can only reset password via forgot-password flow (which doesn't work yet per G2). Need: `PUT /auth/profile` or new endpoint for authenticated password change. | + +### P2: Consistency (works but inconsistent) + +| # | Gap | Affected | Fix | +| ------- | --------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **G8** | **Password validation inconsistent across clients** | ALL | Backend requires `min(8)`. iOS/Android enforce 8+ chars, uppercase, lowercase, digit. ChronoMind web has no client-side validation. NomGap ProfileScreen has no validation. Standardize. | +| **G9** | **Token storage inconsistent** | Mixed | LysnrAI iOS/Android: Keychain/EncryptedSharedPrefs. ChronoMind: Keychain/plain SharedPrefs. MindLyst: Keychain/plain SharedPrefs. NomGap: MMKV. ChronoMind web: localStorage. Dashboards: httpOnly cookies. Consider standardizing mobile to Keychain + EncryptedSharedPrefs. | +| **G10** | **No SSO on ChronoMind, NomGap, or MindLyst** | 3 products | Only LysnrAI has Google/Microsoft/Apple SSO. Backend supports `/auth/sso`. Could add SSO to other products later. | +| **G11** | **Inconsistent `x-product-id` header** | Various | iOS `PlatformSyncManager` for ChronoMind doesn't send `x-product-id`. Some Android clients send it lowercase, some uppercase. Standardize. | +| **G12** | **No "Delete Account" in any app** | ALL | GDPR/privacy requirement. Backend has `DELETE /auth/users/:id` (admin only). Need: self-service account deletion endpoint + UI. | + +### P3: Nice-to-have + +| # | Gap | Affected | Fix | +| ------- | -------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------ | +| **G13** | **No cross-product ByteLyst account linking** | Future | If same user uses ChronoMind + NomGap, they have 2 separate accounts. Could add account linking later. | +| **G14** | **No rate limiting on auth endpoints from clients** | ALL | Backend has rate limiting module but clients don't handle 429 gracefully. | +| **G15** | **No biometric auth (FaceID/TouchID) on any mobile app** | iOS/Android | Could add biometric unlock after initial login. | + +--- + +## 5. Architecture Diagram — Current State + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ platform-service (:4003) │ +│ │ +│ /auth/login ← email + password + productId │ +│ /auth/register ← email + password + displayName + productId │ +│ /auth/refresh ← refreshToken │ +│ /auth/me ← Bearer token │ +│ /auth/sso ← email + productId + provider │ +│ /auth/forgot-password ← email + productId (⚠️ no email sent) │ +│ /auth/reset-password ← token + newPassword (⚠️ no UI calls) │ +│ /auth/verify-email ← token (⚠️ no UI calls) │ +│ │ +│ Cosmos DB: users container (partitioned by id) │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ { id, productId, email, passwordHash, ... } │ │ +│ │ │ │ +│ │ productId="lysnrai" → LysnrAI users │ │ +│ │ productId="chronomind" → ChronoMind users │ │ +│ │ productId="nomgap" → NomGap users │ │ +│ │ productId="mindlyst" → MindLyst users │ │ +│ └─────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────┘ + ▲ ▲ ▲ ▲ ▲ + │ │ │ │ │ + LysnrAI ChronoMind NomGap MindLyst Dashboards + (all 6 (web+iOS (Expo (iOS+ (admin+ + surfaces) +Android) RN) Android) tracker) +``` + +--- + +## 6. Recommended Fix Order + +1. **G2 — Email delivery** (unblocks G1, G4) — Wire SendGrid/SES into platform-service delivery module +2. **G1 — Forgot Password UI** — Add to all apps (once email works) +3. **G3 — MindLyst web auth** — Add auth context + login form +4. **G5 — ChronoMind web token refresh** — Add refresh logic +5. **G6 — NomGap token refresh** — Add refresh timer +6. **G4 — Email verification UI** — Add verification prompt post-register +7. **G7 — Change Password** — Add endpoint + UI in all settings screens +8. **G8 — Password validation** — Standardize client-side rules +9. **G12 — Delete Account** — Self-service endpoint + UI +10. **G9–G11** — Consistency cleanup + +--- + +## 7. Summary Answer + +> **Q: Can a ChronoMind user sign in directly to NomGap?** +> **A: No.** They must register separately. Each product has its own user namespace (`productId`). Same email = different accounts on different products. This is **by design** — each product has independent plans, subscriptions, and licenses. Cross-product account linking is a future P3 feature. + +> **Q: Do all apps use the same backend?** +> **A: Yes.** All products call the same platform-service `/auth/*` endpoints, storing users in the same Cosmos DB `users` container, isolated by `productId`. + +> **Q: What's the biggest gap?** +> **A: Password reset doesn't work end-to-end.** The backend endpoints exist but (a) no email delivery is wired, and (b) zero client apps have forgot-password UI. This is the #1 gap to fix.