Skip to content

Authentication & Authorization Middleware

Prism has eight auth middlewares (grouped into six logical layers — see Security Overview). Each guards a route family, checks a specific credential, validates against a specific datastore, and fails closed. This page documents each one against its source file, then gives a decision table and a selection flowchart.

Validation legend: ✅ verified against source · ❔ representative, verify · ⚠ security finding

All of these run after the pre-auth pipeline (bodyParser → CORS → traceIdMiddlewarerequestLogger). See Request Lifecycle.


1. authMiddleware — staff / web + mobile

File: middlewares/authMiddleware.js ✅

Credential: token header (primary) or password in body (bcrypt fallback). Branches on the source header (web default, else "mobile").

Datastore: MySQL webPool (config/mysqlPool.js). One join across usersrolesaddo_account_mappingaddo_businessbusiness_category, filtered by u.userId=? AND b.id=? and all status=1.

Validation order (web branch, authMiddleware.js:44-78): 1. Load the user+business row (returns full business profile used downstream). 2. isValid if token header === users.apiToken (current device token). 3. Else, if a token was supplied, check auth_tokens for a live multi-session token bound to that userId (older devices keep working after apiToken rotates). A token cannot be replayed against a different account. 4. Only if token auth fails entirely, fall back to bcrypt: inputs.password === db_password || bcrypt.compare(...). The expensive bcrypt never runs on normal token calls (deliberate perf fix — see the in-file comment).

Sets on req: req.body.created_by = userName (used by controllers for audit fields).

Failure: 403 invalid credentials on bad creds / no row; 500 fail-closed on any thrown error (DB down, pool exhausted) — authMiddleware.js:134-143.

Route families: /crm_api, /crm_api/v2 (selected), /stats (all), and most prism_routes (campaign_tag, digital_bill, loyalty_rewards staff routes).

⚠ password is read from the body (and, per requestLogger, may arrive in the query string). See findings.


2. adminMiddleware — admin only

File: middlewares/adminMiddleware.js ✅

Credential: token header or password body.

Datastore: MySQL via a fresh single connection per request (config/mysqlConnection.js), not the pool — the connection is explicitly .end()ed on every exit path.

Check: Gates on inputs.business_id == '1' (adminMiddleware.js:9) ⚠ — the only thing that distinguishes "admin". Then the same user+business join as authMiddleware; isValid = token === apiToken || password === db_password || bcrypt.compare(...).

Sets on req: req.body.created_by = userName.

Failure: 403 invalid credentials (bad creds) or 403 not authorized (business_id ≠ 1).

Route families: admin endpoints within /crm_api.

⚠ Authorization is a hardcoded numeric check, not role-based. See Remediation.


3. appMiddleware — customer loyalty app

File: middlewares/appMiddleware.js ✅

Credential: contactMappingId + token + parent_id, all in the body.

Datastore: MySQL webPool. SELECT id FROM addo_contacts_mapping WHERE id=? AND token=? AND businessId=? AND status=1 (parent_id maps to businessId).

Sets on req: nothing — presence of a matching row is the gate.

Failure: 403 invalid credentials; 500 fail-closed on error.

Route families: customer-facing loyalty routes in prism_routes/loyalty_rewards_routes.js — e.g. getCustomerLoyaltyDetails, viewAllRewards, viewExpRedeemedRewards.


4. crm_offer_middleware — POS / 3rd-party integration

File: middlewares/crm_offer_middleware.js ✅

Credential: Authorization header (raw token, not Bearer-prefixed).

Datastore: MongoDB authorization_token collection — findOne({ token: String(headers.authorization) }).

Sets on req: req.parent_business_id = auth_token.parent_business_id — this is the multi-tenancy anchor for integrator calls (see tenancy).

Failure: 401 with the canonical integration error shape { status: 0, error_code: "uE_101", error_message: "Authentication Failed" }.

Route families: all of /integration/v1 (prismIntegration.js), the POS/loyalty routes in injestion.js (/injestion/rista, /api/ingestion/orderPush, /api/checkLoyaltyRewards, /api/redeemLoyalty, /api/generateOTP, /api/revokeLoyalty), and getWalletDetails in loyalty_rewards.


5. jwtMiddleware / jwtWhatsappMiddleware — internal JWT

Files: middlewares/jwtMiddleware.js, middlewares/jwtWhatsappMiddleware.js ✅

Credential: Authorization: Bearer <jwt> — split on space, take [1].

Validation: jwt.verify(token, secret, { algorithms: ["HS256"] }). The secret is a base64-decoded hardcoded string (jwtMiddleware.js:5, jwtWhatsappMiddleware.js:5) ⚠ — the two middlewares use different secrets.

Sets on req: req.user = decoded (CRM JWT payload: user_id, customer_number, parent_id; WhatsApp JWT payload: parent_id).

Failure: 401 Unauthorized (no token) or 403 Invalid token (verify fails).

Route families: prism_routes/whatsapp_login_routes.js (/check/whatsapp/login uses jwtWhatsappMiddleware).

⚠ Hardcoded HS256 secrets → token forgery. Rotate + env-inject. See findings.


6. injestion_middleware / thirdPartyMiddleware — static token

Files: middlewares/injestion_middleware.js, middlewares/thirdPartyMiddleware.js ✅

Credential: token header, compared against a single hardcoded string (injestion_middleware.js:8, thirdPartyMiddleware.js:6) ⚠.

Datastore: none — pure string compare.

Failure: 401 token in required (missing) or 401 invalid token (mismatch).

Route families: /injestion/ls_center, /sync/crm/token (injestion_middleware); occasional custom integrations (thirdPartyMiddleware).


7. Which middleware protects which route family

Route family Base path Middleware Credential → datastore
CRM staff (web/mobile) /crm_api, /crm_api/v2 authMiddleware (many routes open ⚠) token/password → MySQL users+auth_tokens
Admin within /crm_api adminMiddleware token/password + business_id==1 → MySQL (single conn)
Dashboard metrics /stats authMiddleware (all 12 routes) token/password → MySQL
Customer loyalty app prism_routes/loyalty_rewards appMiddleware contactMappingId+token+parent_id → MySQL addo_contacts_mapping
POS / 3rd-party integration /integration/v1, parts of /injestion crm_offer_middleware Authorization → MongoDB authorization_token
Order ingestion (protected) /injestion/ls_center, /sync/crm/token injestion_middleware static token header
Order ingestion (open) /injestion/uengage, /injestion/petpooja, /injestion/posist, etc. (none) ⚠
Posist loyalty /posist/v1 (none) ⚠
WhatsApp login prism_routes/whatsapp_login jwtWhatsappMiddleware Bearer JWT (HS256)

⚠ Several high-value routes have no middleware — notably open ingestion endpoints (/injestion/uengage, /injestion/petpooja, /injestion/posist, /injestion/salon/order), the entire /posist/v1 family, and customerController.getCustomerDetails. Ingestion controllers rely on payload-embedded tokens (crm_token) rather than middleware. Treat these as unauthenticated at the edge and verify tenant scoping inside the controller.

8. Auth-selection flowchart

flowchart TD
    R[Route matched] --> BP[["Pre-auth pipeline:<br/>bodyParser · CORS(*) ·<br/>traceIdMiddleware · requestLogger"]]
    BP --> Q{Which route family?}

    Q -->|/integration/v1 · /injestion/rista ·<br/>/api/* loyalty| CO[crm_offer_middleware]
    CO --> COc{Authorization header in<br/>Mongo authorization_token?}
    COc -->|no| CO401[401 uE_101]
    COc -->|yes| COok[set req.parent_business_id → controller]

    Q -->|/crm_api · /crm_api/v2 · /stats ·<br/>staff prism_routes| AM[authMiddleware]
    AM --> AMc{token=apiToken OR<br/>token in auth_tokens OR<br/>bcrypt password?}
    AMc -->|no| AM403[403 invalid credentials]
    AMc -->|error| AM500[500 fail-closed]
    AMc -->|yes| AMok[set req.body.created_by → controller]

    Q -->|admin within /crm_api| AD[adminMiddleware]
    AD --> ADc{business_id == 1<br/>AND creds valid?}
    ADc -->|no| AD403[403 not authorized /<br/>invalid credentials]
    ADc -->|yes| ADok[set req.body.created_by → controller]

    Q -->|customer loyalty app| AP[appMiddleware]
    AP --> APc{contactMappingId+token+parent_id<br/>in addo_contacts_mapping status=1?}
    APc -->|no| AP403[403 invalid credentials]
    APc -->|yes| APok[controller]

    Q -->|/injestion/ls_center ·<br/>/sync/crm/token| IM[injestion_middleware]
    IM --> IMc{token header == static token?}
    IMc -->|no| IM401[401 invalid token]
    IMc -->|yes| IMok[controller]

    Q -->|whatsapp login| JW[jwtWhatsappMiddleware]
    JW --> JWc{Bearer JWT verifies HS256?}
    JWc -->|no| JW403[401/403]
    JWc -->|yes| JWok[set req.user → controller]

    Q -->|open ingestion · /posist/v1 ·<br/>some /crm_api| NONE[no middleware ⚠️]
    NONE --> NC[controller relies on<br/>payload token / tenant filter]