Session
๐ Overviewโ
The session service is the core of DashClicks authentication. It validates user credentials, exchanges OAuth grants with the router service, persists ApiSession documents, and hydrates session status responses with cross-service data (Stripe, Twilio, announcements, loyalty tiers, and more). It also enforces strict guardrails for SSO and impersonation workflows to prevent privilege escalation while enabling support staff to access sub-accounts.
File Path: internal/api/v1/auth/services/session.service.js
๐ Data Flowโ
flowchart TD
A[๐ฅ Login Request] --> B[Lookup user by email or phone]
B --> C{Credentials valid?}
C -->|โ No| Z[Throw badRequest('Invalid login credentials provided')]
C -->|โ
Yes| D[Fetch active account]
D --> E{Account banned?}
E -->|โ
Yes| Z2[Throw forbidden('UNAUTHORIZED_ACCOUNT')]
E -->|โ No| F[Build OAuth grant payload]
F --> G[Persist ApiGrant]
G --> H[Exchange code for tokens]
H --> I[Create ApiSession]
I --> J[๐ค Return new session]
๐พ Database Operationsโ
Collections Usedโ
_usersโ Loads credential hashes, scope assignments, announcements, and metadata.- Operations: Read, Update
- Model:
shared/models/user.js
_accountsโ Fetches account branding, billing state, domains, and parent relationships.- Operations: Read, Update
- Model:
shared/models/account.js
_api.sessionsโ Primary session store for access and refresh tokens.- Operations: Create, Read, Delete
- Model:
shared/models/api-session.js
_api.refresh.tokensโ Holds refresh tokens that are cleaned up during logout.- Operations: Delete
- Model:
shared/models/api-refresh-token.js
_api.grantsโ Stores short-lived OAuth grants consumed during login/SSO.- Operations: Create, Delete
- Model:
shared/models/api-grant.js
_api.scopesโ Supplies scope lists for owner and impersonated sessions.- Operations: Read
- Model:
shared/models/api-scope.js
_store.subscriptions,_store.invoices,_store.pricesโ Used to assemble subscription and plan information instatus.- Operations: Aggregate, Read
- Model:
shared/models/store-subscription.js,store-invoice.js
- Additional collections:
twilio.numbers,sendgrid.keys,announcements,_store.promo.codes,conversations,agency.websites, andprojects-dashboard-preferences.
๐ง Business Logic & Functionsโ
Core Functionsโ
login({ account, email, password, remember, geo, lang, timezone, phone, headers, connection, shared, platform })โ
Purpose: Authenticates a user by email or phone, exchanges OAuth grants, and persists a new ApiSession document decorated with metadata.
Parameters:
account(String|ObjectId) โ Target account ID for the login attempt.email(String) โ Email credential; optional when using phone.password(String) โ Plain-text password supplied by the user.remember(Boolean) โ Whether to extend session lifetime (affects expiration math).geo,lang,timezone(Objects/Strings) โ Metadata stored insession.metadata.platform; currently not persisted.phone(String) โ Alternate credential used whenemailis omitted.headers(Object) โ Request headers, expected to includex-client-id,x-forwarded-for, anduser-agent.connection(Object) โ Node connection, used to determine originating IP.shared(Object) โ Optional payload controlling funnel cloning; forwarded toprocessSharedat the controller layer.platform(String) โ Allows marking sessions for theclient_dashboard.
Returns: Promise<ApiSessionDocument> โ Newly created Mongoose document representing the session.
Business Logic Flow:
- User Lookup
- Fetches the user by
emailorphone, including the password hash. - Throws
badRequestif no matching user is found.
- Fetches the user by
- Password Verification
- Uses
verifyHashto compare the provided password against the stored hash. - Rejects invalid credentials with
badRequest.
- Uses
- Account Verification
- Loads the account, including branding and parent account details.
- Rejects banned accounts (or those whose parent is banned) with
forbidden('UNAUTHORIZED_ACCOUNT')and logs a warning.
- Scope Resolution
- Determines OAuth scopes: owners get all scopes, non-owners merge personal scope with
users.me.
- Determines OAuth scopes: owners get all scopes, non-owners merge personal scope with
- OAuth Grant Exchange
- Persists a short-lived
ApiGrantsigned withAPP_MISC_SECRET. - Exchanges the grant for access/refresh tokens via Axios call to
http://localhost:${PORT}/v1/auth/oauth/token.
- Persists a short-lived
- Session Creation
- Calculates session expiration (longer when
rememberistrue). - Stores the session payload in
_api.sessions, including IP address, user agent, and platform metadata.
- Calculates session expiration (longer when
- Cleanup on Failure
- If any part of the token exchange fails, deletes the temporary
ApiGrantto avoid leaks.
- If any part of the token exchange fails, deletes the temporary
Error Handling:
- Throws
badRequestfor invalid credentials. - Throws
forbidden('UNAUTHORIZED_ACCOUNT')when the account or parent is banned. - Propagates Axios or token service errors upward after cleaning up the grant.
Dependencies:
verifyHash,getApiApp,ApiGrant,ApiScope,axios, and environment variablesAPP_MISC_SECRET,PORT.
Example Usage:
const session = await sessionService.login({
account: req.body.account,
email: req.body.email,
password: req.body.password,
remember: req.body.remember,
headers: req.headers,
connection: req.connection,
platform: req.body.platform,
});
// session._id is returned to the controller and stored in x-session-id
Side Effects:
- โ ๏ธ Creates
ApiGrantandApiSessiondocuments. - โ ๏ธ Makes HTTP call to
/v1/auth/oauth/token.
logout(session_id)โ
Purpose: Terminates an existing session and removes the corresponding refresh token.
Parameters:
session_id(String|ObjectId) โ ID from thex-session-idheader.
Returns: Promise<void>
Business Logic Flow:
- Attempts to delete the
ApiSessionby ID. - If a session document existed, removes the linked refresh token (
ApiRefreshToken) based onsession.token.refresh_token.
Error Handling:
- No errors are thrown if the session does not exist; the operation is idempotent.
Side Effects:
- โ ๏ธ Deletes rows from
_api.sessionsand_api.refresh.tokens.
status(req)โ
Purpose: Validates the current session, hydrates the response with user, account, billing, communications, announcements, and platform metadata, and surfaces redirect hints.
Parameters:
req(Express Request) โ Must containheaders['x-session-id']and potentiallyheaders['x-redirect-to'].
Returns: Promise<{ status: String, data: Object }>
statusโ Output fromverifySession(expectedVALID_SESSION).dataโ Rich payload consumed by UI clients.
Business Logic Flow:
- Session Validation
- Loads the session and passes it to
verifySession(session)(req). - Throws
notAuthorizedif validation fails.
- Loads the session and passes it to
- User and Account Fetch
- Retrieves full user and account documents (with multiple populates) once the session is valid.
- Stripe Customer & Subscription Handling
- Optionally verifies Stripe customer setup and creates introductory promo codes when required.
- Builds
accountPlanby aggregating active software subscriptions and retrieving invoice failure reasons.
- Communication Config
- Collects Twilio numbers, SendGrid configuration, and determines dashboard preferences when
platform === 'client_dashboard'.
- Collects Twilio numbers, SendGrid configuration, and determines dashboard preferences when
- Announcements & Promo Codes
- Builds announcement payloads combining promotion codes and dashboard announcements and updates user records.
- Analytics & Loyalty
- Computes lifetime and monthly revenue, conversation records, loyalty tier, and other analytics fields.
- Response Assembly
- Returns a large object with
user,account,sessionmetadata, optional redirects, and branding assets.
- Returns a large object with
Error Handling:
notAuthorized(status)when the session is invalid or missing required dashboard preferences.- Several
try/catchblocks guard optional external calls (Stripe, SendGrid, loyalty tier). Errors are logged and suppressed to keep the session flow resilient.
Dependencies:
- Extensive set of models (
StoreSubscription,StoreInvoice,TwilioNumber,SendgridToken,Announcement,StorePromoCode,Conversation,AgencyWebsite, etc.) and utilities (getLoyaltyProgramTier,verifyStripeCustomerSetup,tierOverride,createPromocode).
Example Usage:
const { status, data } = await sessionService.status(req);
// If status !== 'VALID_SESSION', the controller throws. Otherwise, data is sent to the client.
Side Effects:
- โ ๏ธ May create conversations if they do not exist.
- โ ๏ธ May create promo codes and update account or user documents while refreshing metadata.
- โ ๏ธ Updates user announcements array and account intro coupon flags.
sso({ user, currAcc, account, platform, SSOUser, impersonate, headers, connection, redirect })โ
Purpose: Generates a short-lived SSO session that allows users to access another account, optionally impersonating a user within that account.
Parameters:
user(Object) โ Authenticated user initiating SSO.currAcc(Object) โ Current account context (req.auth.account).account(String|ObjectId) โ Target account for SSO.platform(String) โ Optional platform label forwarded to the session metadata.SSOUser(String|ObjectId) โ Optional target user when impersonating.impersonate(Boolean) โ Indicates whether the session should impersonateSSOUser.headers,connectionโ Used for OAuth exchange and IP logging.redirect(String) โ Optional redirect path encoded into the SSO token.
Returns: Promise<{ domain: String, ssoToken: String }>
Business Logic Flow:
- Eligibility Checks
- Prevents impersonation of the current account.
- Ensures the target account exists and SSO is enabled.
- Permission Enforcement
- For impersonation, verifies that the initiator has SSO permissions or is an owner.
- Resolves the
SSOUser(defaults to account owner when not specified) and constructs scope list identical tologin. - For non-impersonated support SSO, ensures the current account is main and the selected account belongs to it.
- OAuth Grant Exchange
- Creates an
ApiGrantsimilar tologin. - Requests tokens using
CLIENT_REDIRECT_URIand the router service.
- Creates an
- Session Creation
- Persists a new
ApiSessionflagged withsso: trueand metadata describing impersonation context.
- Persists a new
- Token Wrapping
- Signs an SSO token (expires in 15 minutes) containing the session ID, target account, and redirect hint.
- Returns the destination domain (custom, DashClicks, or parent domain fallback).
Error Handling:
- Uses
forbiddenandnotFoundextensively to enforce access rules. - Relies on the same OAuth error handling as
login.
Dependencies:
getApiApp,ApiGrant,ApiScope,jwt, and domain selection helpergetActiveDomain.
Example Usage:
const { domain, ssoToken } = await sessionService.sso({
user: req.auth.user,
currAcc: req.auth.account,
account: req.body.account,
impersonate: req.body.impersonate,
headers: req.headers,
connection: req.connection,
redirect: req.body.redirect,
});
Side Effects:
- โ ๏ธ Adds SSO
ApiSessiondocuments that expire after 8 hours. - โ ๏ธ Generates JWTs signed with
APP_SECRETcontaining redirect hints.
ssoVerify(d)โ
Purpose: Validates the short-lived SSO token and ensures the embedded session is active and bound to the specified account.
Parameters:
d(String) โ JWT token produced bysso.
Returns: Promise<{ session: String, account: String, redirect?: String }>
Business Logic Flow:
- Verifies the token using
APP_SECRET. - Confirms the target account is active.
- Ensures the referenced
ApiSessionexists and matches the account. - Returns the decoded payload (
session,account, optionalredirect).
Error Handling:
- Throws
notAuthorized('Invalid token')when JWT verification fails. - Throws
notFound('Active account not found')ornotAuthorized('Credentials in token are invalid')when lookups fail.
Side Effects:
- None.
๐ Integration Pointsโ
Internal Dependenciesโ
- Controllers:
session.controller.jsinvokes each function and handles HTTP responses. - Utilities:
processSharedruns afterloginvia the controller, cloning funnels whenshared.funnel_idis provided.
External Servicesโ
- Router OAuth Endpoint โ
/v1/auth/oauth/tokenfor code exchange. - Stripe API โ Customer verification, subscription lookup, and charge inspection.
- SendGrid โ Determining configuration state for account communications.
- Twilio โ Checking purchased numbers and A2P registrations.
- Axios โ HTTP client used for the OAuth exchange.
๐จ Data Transformation Pipelineโ
graph LR
A[๐ฅ Session ID] --> B[Load ApiSession]
B --> C{verifySession}
C -->|Invalid| D[Throw notAuthorized]
C -->|Valid| E[Fetch user + account]
E --> F[Enrich billing and comms data]
F --> G[Assemble announcements]
G --> H[Calculate loyalty + analytics]
H --> I[Compile response payload]
I --> J[๐ค Return status + data]
๐งช Edge Cases & Special Handlingโ
Case 1: Banned account loginโ
Condition: Either the account or its parent has banned: true.
Handling: Logs the event and throws forbidden('UNAUTHORIZED_ACCOUNT') to stop authentication.
Case 2: Client dashboard session without preferencesโ
Condition: platform === 'client_dashboard' but no dashboard preference record exists for the user.
Handling: Throws notAuthorized('INVALID_SESSION') to prevent unauthorized access.
Case 3: Duplicate funnel cloningโ
Condition: Login request contains shared.funnel_id that is already pending for the account.
Handling: processShared logs an error and aborts cloning, leaving the session intact.
Case 4: OAuth exchange failureโ
Condition: Router service rejects the authorization code.
Handling: Deletes the temporary ApiGrant and rethrows the error so the controller responds with the upstream error.
Case 5: SSO impersonation disabledโ
Condition: Initiating user lacks user.sso.impersonate and is not an owner.
Handling: forbidden('SSO impersonation disabled for user') prevents impersonation sessions.
โ ๏ธ Important Notesโ
- ๐ Security: Session tokens are signed externally; this service stores them but relies on Router for signature validation.
- ๐จ Performance:
statusperforms numerous sequential queries. Cache heavy lookups (Stripe, SendGrid) upstream where possible if the endpoint becomes hot. - ๐ก Observability: Failures are logged via
loggerwithinitiatortags (internal/auth/login, etc.) for easier tracing.