Password
๐ Overviewโ
The password service is responsible for both halves of the password lifecycle: issuing reset emails and accepting new credentials. It coordinates JWT token generation, email delivery via SendGrid, Wasabi-hosted assets, and database updates that invalidate stale sessions. Every branch is carefully guarded with badRequest/notFound errors so invalid tokens or stale requests stop early without revealing user state.
File Path: internal/api/v1/auth/services/password.service.js
๐ Data Flowโ
flowchart TD
A[๐ฅ Reset Request] --> B{Type}
B -->|email| C[Load account]
B -->|reset| C
C --> D[Find active user]
D -->|email| E[Generate JWT + store reset token]
E --> F[Build email payload]
F --> G[Send email via sendEmail]
D -->|reset| H[Validate stored reset token]
H --> I[Hash new password]
I --> J[Update user + clear token]
J --> K[Delete active API sessions]
K --> L[๐ค Return status]
๐พ Database Operationsโ
Collections Usedโ
_accountsโ Confirms account status and supplies branding metadata for emails.- Operations: Read, Update
- Model:
shared/models/account.js
_usersโ Stores reset tokens and hashed passwords.- Operations: Read, Update
- Model:
shared/models/user.js
_api.sessionsโ Cleared after a password change/reset to force re-authentication.- Operations: Delete Many
- Model:
shared/models/api-session.js
๐ง Business Logic & Functionsโ
Core Functionsโ
reset({ type, email, account, password, token, auth })โ
Purpose: Drives both the reset-email flow and the actual password change once the user submits a valid token.
Parameters:
type(String) โ Either'email'to send a reset email or'reset'to submit a new password.email(String) โ User email required for the email request path.account(String|ObjectId) โ Account ID used to scope user lookup.password(String) โ New password submitted with theresettype.token(String) โ JWT token issued during the email step, required forreset.auth(Object) โ Current authenticated context, used to inherit branding colors when sending emails.
Returns: Promise<{ message: String, additional_info?: Object }>
messageโ EitherPASSWORD_RESET_EMAIL_SENTorPASSWORD_RESET.additional_infoโ Raw response fromsendEmail(email path only).
Business Logic Flow:
- Token Validation (reset path)
- Verifies the provided JWT using
APP_SECRET, extractingaccountandemailfrom the payload. - Rejects invalid or expired tokens with
badRequest.
- Verifies the provided JWT using
- Account Lookup
- Fetches the target account with business branding to personalize the email.
- Throws
notFoundif the account is inactive or missing.
- User Lookup
- Finds an active user with matching account/email, selecting
reset_tokenandverifiedfields. - Throws
notFoundwhen no active user is found.
- Finds an active user with matching account/email, selecting
- Email Path (
type === 'email')- Signs a new JWT and stores it as
reset_tokenon the user. - Resolves the account logo from Wasabi (if present) and builds a SendGrid template payload.
- Sends the email using
sendEmail; logs and throwsbadRequestif SendGrid returns an error.
- Signs a new JWT and stores it as
- Reset Path (
type === 'reset')- Confirms that the stored
reset_tokenmatches the submittedtoken. - Hashes the new password with a random salt using
newHash. - Updates the user with the new hash, clears the reset token, and stamps
verifiedif needed. - Deletes all
ApiSessionrecords for the user and clears pendingAccount.tokenvalues.
- Confirms that the stored
Error Handling:
- Uses
badRequestfor invalid JWT tokens, mismatched reset tokens, or failed email dispatches. - Uses
notFoundwhen the account or user cannot be located, or when no reset token exists.
Dependencies:
sendEmail,getActiveDomain,Wasabi, andloggerfor outbound communications.jwtfor short-lived token creation/verification.
Example Usage:
await passwordService.reset({
type: 'email',
email: 'owner@example.com',
account: accountId,
auth: req.auth,
});
// -> { message: 'PASSWORD_RESET_EMAIL_SENT', additional_info: {...} }
Side Effects:
- โ ๏ธ Writes
reset_tokento the user document. - โ ๏ธ Sends transactional email via SendGrid.
- โ ๏ธ Clears API sessions and account tokens when passwords are changed.
change({ current_password, new_password, auth })โ
Purpose: Allows an authenticated user to rotate their password from within the platform.
Parameters:
current_password(String) โ Existing password provided by the user.new_password(String) โ Replacement password to _store.auth(Object) โ Injected authentication context containing the current user document.
Returns: Promise<void>
Business Logic Flow:
- Verification
- Compares the supplied
current_passwordagainstauth.user.passwordusingverifyHash. - Throws
badRequestif the hash comparison fails.
- Compares the supplied
- Hashing
- Generates a new salt and hashes the
new_passwordwithnewHash.
- Generates a new salt and hashes the
- Persistence
- Updates the user document with the new hash.
- Purges every
ApiSessionlinked to the user to enforce reauthentication on all devices.
Error Handling:
badRequest('The provided password was incorrect')when the current password hash check fails.
Dependencies:
verifyHash,newHash, andApiSession.deleteMany.
Example Usage:
await passwordService.change({
current_password: 'oldPass123!',
new_password: 'StrongerPass456!',
auth: req.auth,
});
Side Effects:
- โ ๏ธ Logs the user out everywhere by deleting their API sessions.
๐ Integration Pointsโ
Internal Dependenciesโ
internal/api/v1/auth/controllers/password.controller.jsโ Callsresetandchangedirectly after request validation.internal/api/v1/auth/routes/password.routes.jsโ Applies Joi validation (schemas/password.js) and access guards before delegation.
External Servicesโ
- SendGrid โ Sends password reset emails via
sendEmailand templated-f506f1632d514fab84babdcfcc092150. - Wasabi โ Supplies public URLs for stored branding assets.
๐จ Data Transformation Pipelineโ
graph LR
A[๐ฅ Inputs] --> B[Validate token or current password]
B --> C[Fetch account & user]
C --> D{Branch}
D -->|Email| E[Generate JWT reset token]
E --> F[Send transactional email]
D -->|Reset| G[Hash new password]
G --> H[Persist updates]
H --> I[Clear ApiSession documents]
I --> J[๐ค Final response]
๐งช Edge Cases & Special Handlingโ
Case 1: Expired or tampered reset tokenโ
Condition: JWT verification throws because the token is expired or modified.
Handling: badRequest('The reset token provided is not valid.') is raised and no database changes occur.
Case 2: Account pending verification tokenโ
Condition: Account document has a legacy token field from onboarding.
Handling: When a password reset succeeds the service clears account.token and stamps verified to prevent future reuse.
Case 3: SendGrid outageโ
Condition: sendEmail returns { error }.
Handling: Logs the payload and error, then throws badRequest('The reset password email could not be sent.') so callers can retry later.
โ ๏ธ Important Notesโ
- ๐ Security: All passwords are rehashed with a fresh salt every time to mitigate rainbow-table attacks.
- ๐จ Session Hygiene: Both
resetandchangetriggerApiSession.deleteManyto avoid stale refresh tokens. - ๐ก Branding: Email theming respects
auth.account.branding.colors.primarywhen available, maintaining white-label consistency.