Skip to content

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-platform request header (mr-mentor | my-analytics-school | mr-hire). This is the subject of most of this doc. 2. Mr. Hire job-board distribution — the Platform TypeORM entity (google_jobs, indeed, linkedin, naukri, internshala, ...) used to publish job posts to external boards. See src/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 brandingEmailService.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.tsthis.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 resolved platform value so the worker can pick the brand template (EmailQueue.types.ts carries platform: '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 hooksfireLoginHooks credits daily-login XP and re-evaluates badges asynchronously after a successful login; failures are swallowed so they never break auth.
  • Master-auth Redis statemaster_config:password_hash and master_config:totp_secret are 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-platform handling found in src/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-mentor selects that brand; anything else (including a typo or missing header) becomes my-analytics-school. mr-hire is 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 User row serves all brands; there is no per-brand schema, row-level scoping, or separate DB. Treat signupSource/Lead.source as tracking, not security.
  • The mr-hire gate runs on every auth path. Password login, Google login, and master-TOTP all re-check MR_HIRE_ALLOWED_ROLES before issuing tokens. Note the allowed-roles constant lists ADMIN twice and does not include a separate superadmin (superadmin was merged into admin) — effectively HR + Admin.
  • Cross-brand lead creation is best-effort and narrow. It only fires for role === USER, skips when signupSource === loginPlatform, dedupes on userId + 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-hire gate). Guard MASTER_PASSWORD/MASTER_TOTP_SECRET carefully; rotate via the admin endpoints (Redis overrides env). Successful use is audit-logged via logMasterLogin; the master-TOTP route is rate-limited (totpRateLimit).
  • Two "platform" namespaces in one repo. Do not wire the x-platform header into the Platform entity or vice versa — they are unrelated. The Platform/HrPlatformAccount/JobPlatformPosting family is Mr. Hire job-board distribution; MasGatewayService is the Ask MAS LLM dashboard.
  • PLATFORM_ENCRYPTION_KEY placeholder. 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.ts allows any origin when NODE_ENV === 'development', plus an allowlist (CORS_ALLOWED_ORIGINS) and a few hardcoded Vercel origins in production. X-Platform (and X-Webhook-Secret) are explicitly in allowedHeaders, and a catch-all OPTIONS preflight handler re-asserts them. Socket.IO CORS (per the repo guide) allows localhost:3000/3001/3002/8088 plus production URLs.
  • PlatformService.publishToPlaftorms is a (preserved) typo in the method name — match it exactly when calling.
  • (Inferred) There is no Platform table representing tenancy and no per-tenant config object; brand behavior is entirely conditional logic in controllers/services keyed off the header string.