Multi-Platform / Multi-Tenant Architecture¶
How a single Express + TypeScript backend (mr-mentor-backend, port 8000) serves several brands and products at once — Mr. Mentor, My Analytics School (MAS), and the Mr. Hire HR portal — from one PostgreSQL database, one Redis/BullMQ instance, and one auth system. This document explains the x-platform header contract that distinguishes brands, how that flows into auth/branding/email/lead tracking, the cross-account "master login" mechanism, and the two distinct things the codebase calls "platform" (the tenancy header and the Mr. Hire job-board Platform entity) so you do not confuse them.
Status: documented from source on this branch.
Overview¶
The backend is soft multi-tenant: there is one shared schema and one shared user table, and the request's origin brand is carried in an HTTP header (x-platform) rather than encoded in the URL host, a subdomain, or a separate database. Tenancy here mostly affects presentation and tracking (which brand name to put in an email, where to attribute a signup, which sales lead to create), plus one access-control gate for the Mr. Hire portal. Data is not physically partitioned per brand — a user who signs up on my-analytics-school and later logs in on mr-mentor is the same User row.
Three header values matter:
x-platform value |
Brand / product | Frontend | Notes |
|---|---|---|---|
my-analytics-school |
My Analytics School (the default) | mas-website-live (Next.js Pages Router, :8088) |
Default when the header is absent or unrecognized. |
mr-mentor |
Mr. Mentor | mr-mentor-frontend (Next.js App Router, :3000 / superadmin :3002) |
Mentorship + admin dashboard brand. |
mr-hire |
Mr. Hire (HR/recruitment portal) | mr-hire-frontend (React + Vite, :5173) |
Role-gated — only HR/Admin may log in. |
Personas:
- End users / students hit the public MAS site or Mr. Mentor and authenticate normally.
- HR / recruiters (EXTERNAL_HR) and Admins log into the Mr. Hire portal — enforced at login by x-platform: mr-hire.
- Admins / Superadmins can use the master login (a global password + TOTP) to impersonate any account for support, regardless of brand.
Naming caution: the word "platform" is overloaded in this codebase. There are two unrelated meanings: 1. Tenancy / brand — the
x-platformrequest header (mr-mentor|my-analytics-school|mr-hire). This is the subject of most of this doc. 2. Mr. Hire job-board distribution — thePlatformTypeORM entity (google_jobs,indeed,naukri,internshala, ...) used to publish job posts to external boards. Seesrc/entities/Platform.ts. This is a data concept, not a tenancy concept. A third related-but-separate piece is the MAS LLM Gateway admin surface (MasGatewayService), where "MAS" refers to the Ask MAS product, not tenancy.
Key concepts & entities¶
Tenancy / branding (header-driven, no dedicated table):
- x-platform header — read directly off req.headers['x-platform'] in controllers (src/controllers/auth.controller.ts, src/controllers/issues.controller.ts). Normalized as platformHeader === 'mr-mentor' ? 'mr-mentor' : 'my-analytics-school', so anything that is not exactly mr-mentor collapses to the MAS default; mr-hire is handled separately as an access gate.
- User.signupSource — persists which brand a user first signed up on (src/services/AuthService.ts, defaults to 'mr-mentor').
- Lead.source — when a user logs in on a brand different from their signupSource, a Lead row is created so Sales can track the cross-brand interest (createCrossPlatformLead in auth.controller.ts).
- Email branding — EmailService.sendWelcomeEmail(..., platform) chooses the display name (MR Mentor vs My Analytics School) and subject line (src/services/EmailService.ts, src/services/templates/WelcomeEmailTemplate.ts).
Master login (cross-account auth):
- MasterAuthService (src/services/MasterAuthService.ts) — singleton holding a bcrypt-hashed master password + a TOTP secret, loaded from env (MASTER_PASSWORD, MASTER_TOTP_SECRET) and overridable via Redis (master_config:password_hash, master_config:totp_secret). Issues single-use, 5-minute Redis-backed challenge tokens for the password→TOTP two-step.
Mr. Hire job-board "platforms" (distinct entity family):
- Platform (src/entities/Platform.ts) — a catalog row for an external job board/social/portal. Key fields: id (e.g. google_jobs), method (automatic | oauth | api_key | manual), category, isActive, authFields (JSON describing required credential inputs).
- HrPlatformAccount (src/entities/HrPlatformAccount.ts) — an HR user's connected credentials for a Platform (AES-256 encrypted JSON in credentials), with connectionStatus lifecycle.
- JobPlatformPosting (src/entities/JobPlatformPosting.ts) — the join of a JobPost to a Platform, tracking status (pending | posted | failed | manual_required) and method.
- PlatformService (src/services/PlatformService.ts) — CRUD for the catalog, credential connect/disconnect (encrypt/decrypt), and publishToPlaftorms (sic) which decides per-board whether posting is automatic or manual.
LLM Gateway admin (Ask MAS):
- MasGatewayService (src/services/MasGatewayService.ts) — read/control layer over a LiteLLM gateway joined with UserLlmKey + AskMasLog, surfaced under /api/admin/mas/llm-gateway/*.
Architecture¶
flowchart TD
subgraph FE["Frontends (one per brand)"]
MAS["mas-website-live :8088<br/>x-platform: my-analytics-school"]
MM["mr-mentor-frontend :3000 / :3002<br/>x-platform: mr-mentor"]
MH["mr-hire-frontend :5173<br/>x-platform: mr-hire"]
end
subgraph BE["mr-mentor-backend :8000"]
CORS["CORS middleware<br/>allows X-Platform header"]
ROUTES["Express routes (/api/...)"]
AC["AuthController<br/>reads x-platform"]
IC["IssuesController<br/>reads x-platform"]
AS["AuthService"]
MAS_AUTH["MasterAuthService<br/>(singleton)"]
ES["EmailService<br/>brand-aware templates"]
PS["PlatformService<br/>(Mr. Hire job boards)"]
MGS["MasGatewayService<br/>(LLM gateway admin)"]
end
subgraph INFRA["Shared infrastructure"]
PG[("PostgreSQL<br/>User, Lead, Platform,<br/>HrPlatformAccount, JobPlatformPosting")]
REDIS[("Redis<br/>master_config + challenge tokens")]
Q["BullMQ emailQueue"]
LITELLM["LiteLLM gateway (external)"]
BOARDS["External job boards<br/>(google_jobs, indeed, ...)"]
end
MAS --> CORS
MM --> CORS
MH --> CORS
CORS --> ROUTES --> AC
ROUTES --> IC
AC --> AS
AC --> MAS_AUTH
AS --> PG
AS --> Q
Q --> ES
MAS_AUTH --> REDIS
AS --> PG
IC --> PG
PS --> PG
PS --> BOARDS
MGS --> PG
MGS --> LITELLM
Data model¶
The tenancy signal lives on User/Lead as plain columns; the Mr. Hire job-board family is a real entity cluster.
erDiagram
USER ||--o{ LEAD : "may generate cross-brand"
USER ||--o{ HR_PLATFORM_ACCOUNT : "connects"
PLATFORM ||--o{ HR_PLATFORM_ACCOUNT : "credentialed by"
PLATFORM ||--o{ JOB_PLATFORM_POSTING : "target of"
JOB_POST ||--o{ JOB_PLATFORM_POSTING : "published as"
HR_PLATFORM_ACCOUNT ||--o{ JOB_PLATFORM_POSTING : "used for"
USER {
uuid id PK
string email
string role
string signupSource "brand at signup"
boolean isActive
}
LEAD {
uuid id PK
uuid userId FK
string source "login brand"
string status
}
PLATFORM {
string id PK "google_jobs, indeed..."
string name
string method "automatic|oauth|api_key|manual"
string category "job_board|social|portal"
boolean isActive
jsonb authFields
}
HR_PLATFORM_ACCOUNT {
uuid id PK
uuid userId FK
string platformId FK
string credentials "AES-256 encrypted JSON"
boolean isConnected
string connectionStatus
}
JOB_PLATFORM_POSTING {
uuid id PK
uuid jobPostId FK
string platformId FK
uuid hrPlatformAccountId FK
string method
string status
string externalUrl
}
JOB_POST {
uuid id PK
string title
}
Notable enums / status fields:
- User.role: user, admin, finance, expert, sales, sales_head, external_hr, batch_lead, super_mentor, community_manager (src/types/UserTypes.ts). The old superadmin rows are migrated to admin at startup.
- Platform.method: automatic | oauth | api_key | manual.
- HrPlatformAccount.connectionStatus: disconnected | connected | expired | error.
- JobPlatformPosting.status: pending | posted | failed | manual_required.
API surface¶
Auth routes are mounted under /api (src/routes/index.ts → this.router.use('/api', this.authRoutes.router)). Job-board platform routes are mounted under /api (PlatformRoutes). LLM-gateway admin routes are mounted under /api/admin (AdminRoutes). The x-platform header is read inside handlers — there is no dedicated tenancy route.
Auth + master login (src/routes/auth.routes.ts, prefix /api)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| POST | /api/auth/signup |
public | Signup; persists signupSource from x-platform. |
| POST | /api/auth/verify-otp |
public | Verify OTP; queues brand-aware welcome email. |
| POST | /api/auth/login |
public | Password login; reads x-platform, gates mr-hire, may trigger master-password challenge. |
| POST | /api/auth/google |
public | Google sign-in; same x-platform branding + mr-hire gate. |
| POST | /api/auth/master-totp |
public (rate-limited) | Step 2 of master login — verify TOTP for a challenge token. |
| GET | /api/auth/master-totp/setup |
authed (Admin) | Get TOTP secret + QR for authenticator enrollment. |
| POST | /api/auth/master-totp/test |
authed (Admin) | Test a TOTP code. |
| GET | /api/auth/master-auth/status |
authed | Whether master password + TOTP are configured. |
| POST | /api/auth/master-auth/configure |
authed (Admin) | Set master password + TOTP secret (stored in Redis). |
| POST | /api/auth/master-auth/update-password |
authed (Admin) | Rotate master password only. |
| POST | /api/auth/master-auth/generate-secret |
authed (Admin) | Generate a fresh base32 TOTP secret. |
| DELETE | /api/auth/master-auth |
authed (Admin) | Delete master-auth configuration. |
Mr. Hire job-board platforms (src/routes/platform.routes.ts, prefix /api)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/careers/google-jobs/:id |
public | Google for Jobs JSON-LD for one posting. |
| GET | /api/careers/google-jobs |
public | JSON-LD for all postings. |
| GET | /api/careers/indeed-feed.xml |
public | Indeed XML feed. |
| GET | /api/hr/platforms |
authed | List active job boards. |
| GET | /api/hr/platform-accounts |
authed | List the HR user's connected accounts (credentials stripped). |
| POST | /api/hr/platform-accounts |
authed | Connect/encrypt credentials for a board. |
| PUT | /api/hr/platform-accounts/:id |
authed | Update credentials. |
| DELETE | /api/hr/platform-accounts/:id |
authed | Disconnect (clears credentials). |
| POST | /api/hr/job-posts/:id/publish |
authed | Publish a job to selected boards. |
| GET | /api/hr/job-posts/:id/platforms |
authed | Posting statuses for a job. |
| PATCH | /api/hr/job-posts/:id/platforms/:platformId |
authed | Mark a manual board as posted. |
| GET | /api/admin/platforms |
Admin | Full catalog (incl. inactive). |
| POST | /api/admin/platforms |
Admin | Create a board. |
| PUT | /api/admin/platforms/:id |
Admin | Update a board. |
| PATCH | /api/admin/platforms/:id/toggle |
Admin | Toggle active. |
MAS LLM Gateway admin (src/routes/admin.routes.ts, prefix /api/admin)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/admin/mas/llm-gateway/overview |
Admin | KPIs, daily activity, spend-by-model, top spenders. |
| GET | /api/admin/mas/llm-gateway/users |
Admin | Per-student gateway key + usage table. |
| GET | /api/admin/mas/llm-gateway/users/:userId |
Admin | One student's detail + recent queries. |
| PATCH | /api/admin/mas/llm-gateway/users/:userId/limits |
Admin | Update budget/RPM/TPM limits. |
| POST | /api/admin/mas/llm-gateway/users/:userId/block |
Admin | Block/unblock a key. |
| POST | /api/admin/mas/llm-gateway/users/:userId/key |
Admin | Mint a key for a user. |
| DELETE | /api/admin/mas/llm-gateway/users/:userId/key |
Admin | Revoke a key. |
| GET | /api/admin/mas/llm-gateway/logs |
Admin | Recent request logs. |
| GET | /api/admin/mas/llm-gateway/models |
Admin | Model catalog + pricing. |
User journeys¶
1. Platform resolution on a request (the core tenancy flow)¶
Every brand-aware endpoint resolves the platform the same way: read the header, collapse unknowns to the MAS default, and treat mr-hire as a special access gate. There is no middleware — resolution happens inline per controller.
sequenceDiagram
participant FE as Frontend
participant CORS as CORS middleware
participant Ctrl as Controller
participant Svc as Service
participant DB as PostgreSQL
FE->>CORS: request with header x-platform
Note over CORS: X-Platform is in allowedHeaders so the browser preflight passes
CORS->>Ctrl: forward request
Ctrl->>Ctrl: read req.headers x-platform as platformHeader
alt platformHeader equals mr-mentor
Note over Ctrl: platform becomes mr-mentor
else anything else
Note over Ctrl: platform becomes my-analytics-school the default
end
Ctrl->>Svc: call with resolved platform
Svc->>DB: read or write scoped data
DB-->>Svc: rows
Svc-->>Ctrl: result
Ctrl-->>FE: response branded for the platform
2. Signup with brand attribution + branded welcome email¶
The brand the user signed up on is stamped onto User.signupSource and drives the welcome email template. OTP send and email are fire-and-forget via the BullMQ emailQueue.
sequenceDiagram
participant FE as Frontend
participant AC as AuthController
participant AS as AuthService
participant Q as BullMQ emailQueue
participant EW as Email worker
participant DB as PostgreSQL
FE->>AC: POST /api/auth/signup with x-platform
AC->>AC: resolve platform from header
AC->>AS: signUp with platform
AS->>DB: insert User with signupSource set to platform
AS->>Q: queue create-token job
AS-->>AC: saved user
AC->>AS: sendOtp fire and forget
AC-->>FE: 200 signup successful OTP being sent
FE->>AC: POST /api/auth/verify-otp with x-platform
AC->>AS: verifyOtp with platform
AS->>DB: mark user verified
alt first verification
AS->>Q: queue welcome-email job with platform
Q->>EW: process job
EW->>EW: pick brand name MR Mentor or My Analytics School
EW-->>FE: branded welcome email delivered
end
AC-->>FE: 200 with JWT and refresh token
3. Cross-brand login generates a Sales lead¶
When a user authenticates on a brand that differs from their original signupSource, the backend records a Lead so Sales can pursue the cross-brand interest. This is skipped for the mr-hire portal (it is an internal HR tool, not a lead funnel) and only applies to plain USER role accounts.
sequenceDiagram
participant FE as Frontend
participant AC as AuthController
participant AS as AuthService
participant DB as PostgreSQL
FE->>AC: POST /api/auth/login with x-platform
AC->>AC: resolve platform from header
AC->>AS: login email and password
AS->>DB: verify credentials
AS-->>AC: success with user
alt platformHeader is mr-hire and role not HR or Admin
AC-->>FE: 403 access denied for Mr Hire
else allowed
AC->>AC: createCrossPlatformLead user signupSource and loginPlatform
alt signupSource equals loginPlatform
Note over AC: same brand so no lead created
else different brand and role is USER
AC->>DB: find existing Lead by userId and source
alt no existing lead
AC->>DB: insert Lead with source equal to login brand and status ACTIVE
end
end
AC->>AS: generate JWT and refresh token
AC-->>FE: 200 set token refreshToken and user cookies
end
4. Mr. Hire portal access gate¶
The mr-hire value is the one place tenancy enforces authorization. Only EXTERNAL_HR and ADMIN may pass; the check runs before any token or cookie is issued, on password login, Google login, and the master-TOTP path.
sequenceDiagram
participant FE as Mr Hire frontend
participant AC as AuthController
participant AS as AuthService
FE->>AC: POST /api/auth/login with x-platform mr-hire
AC->>AS: login email and password
AS-->>AC: success with user and role
alt role in HR or Admin allowed set
AC->>AS: issue JWT and refresh token
AC-->>FE: 200 logged into Mr Hire
else role not allowed
AC-->>FE: 403 only HR and Admin can access Mr Hire
end
5. Master login (cross-account impersonation, two-step)¶
Admins can log into any account using a single global master password plus a TOTP code. A normal login attempt that fails on password is re-checked against the master password; if it matches, the backend issues a short-lived challenge token, and the caller must complete TOTP to receive a real session. The successful master login is audit-logged.
sequenceDiagram
participant FE as Frontend
participant AC as AuthController
participant MAS as MasterAuthService
participant AS as AuthService
participant R as Redis
FE->>AC: POST /api/auth/login email and master password
AC->>AS: login
AS-->>AC: failure invalid password
AC->>MAS: isMasterPassword password
MAS-->>AC: true
AC->>AC: verify target user exists and is active
alt mr-hire portal and role not allowed
AC-->>FE: 403 access denied
else allowed
AC->>MAS: generateChallengeToken email
MAS->>R: store challenge with 5 minute TTL single use
AC-->>FE: 200 requiresTOTP true with challengeToken
FE->>AC: POST /api/auth/master-totp challengeToken email totpCode
AC->>MAS: verifyChallengeToken token
MAS->>R: read email for token
MAS-->>AC: email matches
AC->>MAS: verifyTOTP code
alt code invalid
AC-->>FE: 401 invalid TOTP code token still valid for retry
else code valid
AC->>MAS: consumeChallengeToken single use
MAS->>R: delete challenge key
AC->>AS: generate JWT and refresh token
AC->>AS: logMasterLogin audit record
AC-->>FE: 200 master login successful with session cookies
end
end
6. Publish a job to external boards (Mr. Hire Platform family)¶
Distinct from tenancy, but it is the other "platform" flow. An HR user publishes a JobPost to one or more boards; PlatformService marks automatic boards as posted immediately and everything else as manual_required, deduping on the unique jobPostId + platformId.
sequenceDiagram
participant FE as Mr Hire frontend
participant PC as PlatformController
participant PS as PlatformService
participant DB as PostgreSQL
FE->>PC: POST /api/hr/job-posts/:id/publish with platformIds
PC->>PS: publishToPlaftorms jobPostId userId platformIds
PS->>DB: load JobPost
loop each platformId
PS->>DB: load active Platform
PS->>DB: check existing JobPlatformPosting for dedupe
alt already exists
Note over PS: skip and return existing
else new
PS->>DB: load HrPlatformAccount for this board if connected
alt platform method is automatic
Note over PS: status becomes posted with postedAt now
else manual or credentialed
Note over PS: status becomes manual_required
end
PS->>DB: insert JobPlatformPosting
end
end
PS-->>PC: postings
PC-->>FE: 200 with posting statuses
Background jobs & async¶
emailQueue(BullMQ) — welcome emails and OTP-related mail are enqueued with the resolvedplatformvalue so the worker can pick the brand template (EmailQueue.types.tscarriesplatform: 'mr-mentor' | 'my-analytics-school'). Email send never blocks the auth response.databaseQueue— token creation on signup is queued (create-token), keeping signup fast.- Login hooks —
fireLoginHookscredits daily-login XP and re-evaluates badges asynchronously after a successful login; failures are swallowed so they never break auth. - Master-auth Redis state —
master_config:password_hashandmaster_config:totp_secretare long-lived Redis keys (admin-configurable, overriding env). Challenge tokens (master_challenge:<token>) are short-lived (5-minute TTL, single-use). - Socket.IO — real-time meeting traffic is brand-agnostic; tenancy does not influence socket events (inferred — no
x-platformhandling found insrc/socket.ts).
External integrations¶
| System | Used by | Env / config | Failure / fallback |
|---|---|---|---|
| Gmail SMTP (Nodemailer) | brand-aware emails | EMAIL_USER, EMAIL_PASS |
If unset, OTP send is skipped with a warning (not fatal). |
| Google OAuth | googleSignIn |
GOOGLE_CLIENT_ID/SECRET/REDIRECT_URI |
Invalid token → 400; blocked account → 403 before token issue. |
| Redis | master-auth secrets + challenge tokens | REDIS_HOST/PORT |
Master-auth falls back to env values if Redis is not ready; logs a warning. |
| LiteLLM gateway | MasGatewayService (Ask MAS admin) |
gateway config via LlmGatewayService |
MasGatewayService throws LLM_GATEWAY_NOT_CONFIGURED if unconfigured; individual reads degrade gracefully via a safe() wrapper. |
| External job boards | PlatformService job-board distribution |
per-board HrPlatformAccount.credentials (AES-256, key PLATFORM_ENCRYPTION_KEY) |
Non-automatic boards default to manual_required; missing/invalid creds never expose raw secrets. |
Feature flags / defaults:
- The PLATFORM_ENCRYPTION_KEY defaults to a hardcoded placeholder if unset — must be overridden in production (must be 32 bytes).
- MASTER_PASSWORD / MASTER_TOTP_SECRET are optional; if both are unset, master login is simply unavailable (isConfigured() returns false and the password→TOTP branch never fires).
Status lifecycles¶
HrPlatformAccount connection status¶
stateDiagram-v2
[*] --> disconnected
disconnected --> connected : connect with valid credentials
connected --> connected : update credentials
connected --> disconnected : disconnect clears credentials
connected --> expired : token or session lapses
connected --> error : verification failure with errorMessage
expired --> connected : reconnect
error --> connected : fix credentials
JobPlatformPosting status¶
stateDiagram-v2
[*] --> pending
pending --> posted : automatic board or marked posted
pending --> manual_required : manual or credentialed board
manual_required --> posted : HR marks as posted with externalUrl
pending --> failed : posting error with errorMessage
posted --> [*]
failed --> [*]
Master login challenge token¶
stateDiagram-v2
[*] --> issued : master password matched
issued --> consumed : TOTP verified single use deleted
issued --> retried : wrong TOTP token stays valid
retried --> consumed : correct TOTP
issued --> expired : 5 minute TTL elapsed
consumed --> [*]
expired --> [*]
Edge cases, limits & gotchas¶
- Header default collapses unknown brands to MAS. Only the exact string
mr-mentorselects that brand; anything else (including a typo or missing header) becomesmy-analytics-school.mr-hireis checked separately and never becomes a "brand" for branding/email purposes — it is purely an access gate. - No data isolation between brands. Tenancy is presentational + attribution only. One
Userrow serves all brands; there is no per-brand schema, row-level scoping, or separate DB. TreatsignupSource/Lead.sourceas tracking, not security. - The
mr-hiregate runs on every auth path. Password login, Google login, and master-TOTP all re-checkMR_HIRE_ALLOWED_ROLESbefore issuing tokens. Note the allowed-roles constant listsADMINtwice and does not include a separate superadmin (superadmin was merged intoadmin) — effectively HR + Admin. - Cross-brand lead creation is best-effort and narrow. It only fires for
role === USER, skips whensignupSource === loginPlatform, dedupes onuserId + source, and is wrapped so a failure never breaks login. Staff/admin logins never create leads. - Master login is god-mode. A single global password + TOTP grants a real session for any active account (subject to the
mr-hiregate). GuardMASTER_PASSWORD/MASTER_TOTP_SECRETcarefully; rotate via the admin endpoints (Redis overrides env). Successful use is audit-logged vialogMasterLogin; the master-TOTP route is rate-limited (totpRateLimit). - Two "platform" namespaces in one repo. Do not wire the
x-platformheader into thePlatformentity or vice versa — they are unrelated. ThePlatform/HrPlatformAccount/JobPlatformPostingfamily is Mr. Hire job-board distribution;MasGatewayServiceis the Ask MAS LLM dashboard. PLATFORM_ENCRYPTION_KEYplaceholder. Job-board credentials use a default key if the env var is unset — a production footgun. Always set a real 32-byte key.- CORS is permissive in development.
src/app.tsallows any origin whenNODE_ENV === 'development', plus an allowlist (CORS_ALLOWED_ORIGINS) and a few hardcoded Vercel origins in production.X-Platform(andX-Webhook-Secret) are explicitly inallowedHeaders, and a catch-allOPTIONSpreflight handler re-asserts them. Socket.IO CORS (per the repo guide) allows localhost:3000/3001/3002/8088 plus production URLs. PlatformService.publishToPlaftormsis a (preserved) typo in the method name — match it exactly when calling.- (Inferred) There is no
Platformtable representing tenancy and no per-tenant config object; brand behavior is entirely conditional logic in controllers/services keyed off the header string.