# 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.
