Skip to content

Authentication & Security

Detailed documentation of the authentication system, token lifecycle, OAuth flows, partner auth, and authorization model.


Architecture Overview

The system uses a hybrid JWT + AES-256 encryption scheme for token security, with Passport.js handling OAuth flows for social login providers.

Layer Technology Purpose
Token Encryption JWT + CryptoJS AES Double-layer token protection
OAuth Passport.js (6 strategies) Social login/account connection
Partner Auth Plain JWT (issuer/audience) Third-party API access
Session express-session + Passport OAuth flow state
Authorization checkuseraccount() Account-level access control

Token Generation & Verification

File: helper/tokenGenerator.js

Encryption (encryptData)

1. JSON.stringify(payload)
2. CryptoJS.AES.encrypt(jsonString, JWT_SECRET_KEY) → ciphertext
3. jwt.sign({ data: ciphertext }, JWT_SECRET_KEY, { expiresIn }) → token

The result is a JWT whose payload contains AES-encrypted data. This provides: - Tamper detection (JWT signature) - Payload confidentiality (AES encryption)

Decryption (decryptData)

1. jwt.verify(token, JWT_SECRET_KEY) → { data: ciphertext }
2. CryptoJS.AES.decrypt(ciphertext, JWT_SECRET_KEY) → jsonString
3. JSON.parse(jsonString) → payload object

Token Payload Structure

{
  userId: number,          // tMember.id
  role: number,            // tMember.role_type
  defaultPaymentId: number, // Payment provider (2 = Paddle)
  accountId: number,       // Active account context
  isPersonnel: boolean     // Staff/personnel flag
}

Token Lifetimes

Token Type Expiry Use Case
User login JWT_EXPIRES_IN env var (e.g., 7d) Main API access
OAuth callback 10 minutes Temporary token during OAuth redirect
Partner API 1 hour Partner endpoint access
Email verification JWT_EXPIRES_IN Account activation links

Auth Middleware

File: middlewares/auth.js

Applied globally to routes/auth.js via router.use(auth).

Flow

  1. Extract token from request header (TOKEN_HEADER_KEY env var)
  2. Check in-memory revocation set
  3. Call decryptData(token) — verifies JWT signature + decrypts AES payload
  4. On success: set decoded fields on res object
  5. On failure: return 401 with plain string "unauthorized access" (not the standard envelope shape — see error-handling.md §Middleware Errors)

Note: parallel auth path in methods.js. methods.js exports ensureToken, an alternative to this middleware. It hardcodes the header name to "authorization" rather than reading TOKEN_HEADER_KEY, returns 403 (not 401), and uses sendStatus (no body). The two implementations are independent — neither calls the other. Routes choose which to apply on a per-route basis. This dual-path pattern is itself a finding flagged in enterprise-readiness §4.1.

Fields Set on res

Field Source Description
res.userId JWT payload Authenticated user ID
res.role JWT payload User role type
res.defaultPaymentId JWT payload Payment provider ID
res.accountId Accountid request header Active account context
res.isPersonnel JWT payload Staff flag

Important: Auth data is set on res, not req. Route handlers destructure from res.

Token Revocation

File: helper/revokeToken.js

  • In-memory Set() checked before token decryption
  • Populated on logout
  • Not persisted (lost on server restart)
  • No TTL cleanup

Login Endpoints

Standard Login (POST /login)

File: routes/routes.js (~line 3024–3086)

  1. Query tMember by email (sid) and password
  2. Generate access token: {userId}_{timestamp}_{userId}
  3. Return user data with token

Security note — plaintext login token. The login token is a plaintext concatenation of the user ID and a timestamp, with no AES encryption, no JWT signature, and no integrity check. This is inconsistent with the rest of the auth model (which uses the JWT+AES encryption from § Token Generation), and inconsistent with what the Auth Middleware section describes (which calls decryptData expecting a JWT-AES wrapper).

Two implications: - Anyone reading this token in any log, analytics pipeline, or cookie store can extract the user ID and forge a token by guessing a timestamp. - There is an internal inconsistency: if middlewares/auth.js runs decryptData on a plaintext token, decryption will fail. Either there is a separate code path that handles login tokens (not yet identified), or login tokens cannot be used with the standard auth middleware. Investigate before relying on this section.

Action items: treat as a security finding for the backlog. Reconcile the login-token format with the rest of the auth model — either encrypt login tokens like all others, or document a clear exception for whatever code path consumes them.

Admin Login (POST /AdminsigninPOST /Adminlogin)

  1. Find user by email
  2. Generate OTP (3 bytes hex random)
  3. Store OTP in tMember.access_token
  4. Send OTP via email
  5. User submits OTP to /Adminlogin for verification

Registration (POST /register)

  1. Check username uniqueness
  2. Create tMember record
  3. Generate encrypted token with { userId, defaultPaymentId: 2 }
  4. Send email verification link (separate token with type: 'emailVerification')
  5. Create trial subscription in tStripe (14-day default)

OAuth Flows (Passport.js)

Files: middlewares/passport.js, routes/social.js

Supported Providers

Provider Strategy Client ID Env Callback URL Scopes
Google passport-google-oauth20 GOOGLE_CLIENT_ID /social/google/callback profile, email
GitHub passport-github2 GITHUB_CLIENT_ID /social/github/callback user:email
Facebook passport-facebook FACEBOOK_APP_ID /social/facebook/callback emails, displayName, picture
LinkedIn passport-linkedin-oauth2 LINKEDIN_CLIENT_ID /social/linkedin/callback openid, profile, r_organization_social, w_member_social, etc.
Twitter @superfaceai/passport-twitter-oauth2 TWITTER_CLIENT_ID /social/twitter/callback tweet.read, users.read, offline.access
TikTok passport-tiktok-auth TIKTOK_CLIENT_ID /social/tiktok/callback user.info.basic, video.upload, video.publish

OAuth Flow Pattern

1. GET /social/{provider}
   → Passport redirects to provider authorization page

2. User authorizes at provider

3. GET /social/{provider}/callback
   → Passport exchanges code for tokens
   → Extract profile data (name, email, photo, tokens)
   → encryptData(userData, '10m')
   → Redirect to: {APP_URL}/socialAuth?user={encrypted_token}

4. Frontend decrypts token and establishes session

Provider ID Constants (helper/constants.js)

Provider ID Used In
Facebook 1 tMemberAuth.provider_Id
Twitter 2 tMemberAuth.provider_Id
LinkedIn 4 tMemberAuth.provider_Id
TikTok 6 tMemberAuth.provider_Id

Token Storage for Connected Accounts

OAuth access/refresh tokens are stored in tMemberAuth: - accessToken — current access token - refresh_token — for token renewal - token_status — validity state - Provider-specific JSON fields: FbAccounts, IgAccounts, LnAccounts, TwtAccounts


Partner API Authentication

File: routes/partnerAuth.js

Authentication (POST /partnerAuth/partnerAuthentication)

Input: { partnerId, secretKey }

  1. Validate against tPartnerAuth table
  2. Generate plain JWT (no AES layer):
    jwt.sign(
      { partnerId, partnerName, iat },
      JWT_SECRET_KEY,
      { expiresIn: '1h', issuer: 'someli-api', audience: 'partner-auth' }
    )
    
  3. Store token and expiry in tPartnerAuth.pToken
  4. Return token with 3600s expiry

Partner User Registration (POST /partnerAuth/registerUser)

Headers: Authorization: Bearer {partner_jwt}

  1. Verify partner JWT (issuer/audience claims)
  2. Generate random 12-char password
  3. Hash with bcrypt (salt rounds: 10)
  4. Create tMember, tAccounts, tAccountMembers, tSubscriptions
  5. Return login URL: {partner.appUrl}/#login?token={encrypted_user_token}

Single Sign-On (POST /partnerAuth/singleSignOn)

Headers: Authorization: Bearer {partner_jwt}

  1. Verify partner JWT
  2. Find user, get all active accounts
  3. Generate encrypted token with { userId, role, defaultPaymentId, accountIds[] }
  4. Return login URL

Partner Sign-In (POST /partnerAuth/partnerSignIn)

  1. Find partner by name
  2. Generate JWT signed with partner's secretKey (HS256)
  3. Payload: { partner, timestamp, user_data, redirect_url }
  4. Return redirect URL with token

Alternative Auth: methods.js

Function: ensureToken

Simpler JWT verification used on GET / in routes/routes.js: - Extracts Bearer token from Authorization header - Verifies JWT signature only (no AES decryption) - Returns 403 on invalid token


Authorization Model

Account Access Check

Function: checkuseraccount(memberId, accountId) in helper/helper.js

Two-tier authorization:

  1. Regular members: Check tAccountMembers where memberId + accountId + isDeleted=0 + active=1
  2. Personnel: If not found above, check tPersonnelAccounts where personnelId + accountId + isDeleted=0 + active=1

Returns true if user has access, false otherwise. Called by most authenticated route handlers.

Role Model

Role ID Name Context
0 Admin Full account access
1 Owner Equivalent to admin
2 Manager/Collaborator Reduced permissions
3 Contributor Limited permissions
4 Designer Design-specific access (env: DESIGNER)
5 User Member Standard member (env: USER_MEMBER)
6 Account Manager Management role (env: ACCOUNT_MANAGER)
11 Disabled/Archived Excluded from operations

Role fields: - tMember.role_type — User's base role - tAccountMembers.roleType — Role within specific account

Personnel System

The isPersonnel flag distinguishes staff from regular users: - Set in JWT payload during token generation - Personnel access tracked in tPersonnelAccounts (separate from tAccountMembers) - Allows cross-account access for support/management


Session Configuration

File: server.js

app.use(session({
  secret: SESSION_SECRET,  // env var, default: "change-me"
  resave: false,
  saveUninitialized: true
}));
app.use(passport.initialize());
app.use(passport.session());

Passport serialization stores the entire user object in session (simple approach, no database lookup on deserialization).


Security Environment Variables

Variable Purpose
JWT_SECRET_KEY Signs JWT + encrypts AES payload
JWT_EXPIRES_IN Default token expiry (e.g., 7d)
TOKEN_HEADER_KEY Header name for token extraction
SESSION_SECRET Express session secret

Request Authentication Flow

┌──────────┐         ┌──────────────┐         ┌─────────────┐
│  Client  │         │ Auth Middleware│         │Route Handler│
└────┬─────┘         └──────┬───────┘         └──────┬──────┘
     │                       │                        │
     │── Request + Token ──▶│                        │
     │   (Authorization hdr) │                        │
     │                       │── Check revocation ──▶ │
     │                       │── JWT verify ────────▶ │
     │                       │── AES decrypt ───────▶ │
     │                       │                        │
     │                       │── Set res.userId ─────▶│
     │                       │── Set res.accountId ──▶│
     │                       │── Set res.role ───────▶│
     │                       │── next() ────────────▶│
     │                       │                        │
     │                       │                     ┌──┴──┐
     │                       │                     │Check │
     │                       │                     │account│
     │                       │                     │access │
     │                       │                     └──┬──┘
     │◀──────────── Response ─────────────────────────│

Known Security Considerations

Issue Location Impact
Plaintext password storage POST /register Passwords readable if DB compromised
Plaintext password comparison POST /login No timing-attack protection
SQL string interpolation Multiple route handlers Injection risk
In-memory token revocation helper/revokeToken.js Revocations lost on restart
No rate limiting Login endpoints Brute-force vulnerable
Predictable network token format POST /login (network) {userId}_{timestamp}_{userId} pattern

Note: Partner auth (routes/partnerAuth.js) correctly uses bcrypt for password hashing — this pattern should be extended to all user registration/login flows.