- No auth required — mobile apps call this at launch before login
- Reads kill_switch feature flag from Cosmos DB
- If flag enabled=true → returns disabled:true with maintenance message
- Fail-open: DB errors allow app to run normally
- Response: { enabled: bool, disabled: bool, message: string }
Used by iOS KillSwitchService.swift and Android KillSwitchService.kt
Sets license status to 'revoked' and clears all device activations.
Returns the updated license doc. Idempotent — already-revoked licenses
return immediately.
- Add optional 'source' field to UsageDoc (desktop/web/ios/android)
- Add 'source' to UpsertUsageSchema validation
- Include source in upsert document ID to avoid cross-platform overwrites
- Add SourceBreakdown interface
- Aggregate sourceBreakdown in GET /usage/summary alongside modelBreakdown
- Clients can now pass source when reporting usage for per-app analytics
- POST /auth/sso — accepts verified email + provider + productId
- Creates user if not exists (with subscription + license provisioning)
- Issues platform JWT tokens for existing SSO users
- Supports Microsoft and Google OAuth providers
- Added SsoLoginSchema to types.ts
- GET /auth/users — list users (paginated, admin-only)
- GET /auth/users/count — total + by-plan counts
- GET /auth/users/:id — get user by id
- PUT /auth/users/:id — update user (displayName, role, plan, status)
- DELETE /auth/users/:id — delete user
- repository: added list, count, countByPlan, update, remove functions
- types: added UpdateUserSchema
Desktop and mobile clients send deviceName and platform in the activate
payload. Without these fields in the schema, they were silently stripped
by Zod. Now accepted as optional fields for contract alignment.
The Python backend users.py route was deleted in the backend cleanup.
Plan updates are already handled by authRepo.updatePlan() inline in
each webhook handler — the syncUserPlan fetch was redundant dead code.
- Added cross-product fallback lookup by stripeCustomerId when metadata lacks productId
- Ensure invoice payments are stored under the resolved subscription productId
- Normalize checkout metadata plan value before persistence/sync
- Keep auth plan sync aligned with resolved product context
- Verified: tsc --noEmit clean, 20 test files / 183 tests passing
- Make auth register provisioning truly best-effort (warn on failure, do not fail signup)
- Process Stripe webhook events for all products (remove non-default skip)
- Derive updated subscription plan from Stripe price IDs on subscription.updated
- Sync derived plan to auth users and backend plan sync endpoint
- Verified: tsc --noEmit clean, 20 test files / 183 tests passing
- Added plan to auth UserDoc model and token payload typing
- Register flow initializes user.plan from product default plan
- Login/Register/Me responses now include user.plan
- Access tokens now include optional plan claim
- Verified: tsc --noEmit clean, 19 test files / 178 tests passing
- LoginSchema and RegisterSchema now require productId field
- Login/Register routes use productId from request body (not env var)
- PRODUCT_ID import removed from auth/routes.ts
- Test fixtures updated with productId: 'lysnrai'
- createAccessToken() and createRefreshToken() now require productId parameter
- Issuer changed from PRODUCT_ID env var to generic 'bytelyst-platform'
- verifyToken() validates against 'bytelyst-platform' issuer
- auth/routes.ts callers updated to pass productId (still from PRODUCT_ID env var for now)
- Refresh endpoint reads productId from user doc
- Best-effort JWT parsing on every request (non-blocking for unauthenticated routes)
- Attaches parsed payload to req.jwtPayload for downstream use by getRequestProductId()
- Invalid/expired tokens silently ignored — auth-required routes handle their own validation
- New lib/request-context.ts with product validation against cache
- Priority: JWT payload > X-Product-Id header > env var fallback
- Rejects unknown or disabled products with 400 Bad Request
- Augments FastifyRequest with jwtPayload type declaration
- getRequestProductConfig() for modules needing product-specific values
- New products container in Cosmos DB (partition key: /id)
- ProductDoc: displayName, licensePrefix, deviceLimits, trialDays, status
- In-memory cache loaded on startup via loadProductCache()
- CRUD routes: GET/POST /products, GET/PUT /products/:id
- Cache refreshed after admin writes (create/update)
- Registered before all other modules in server.ts