vsmsconnect — API — auth

← all endpoints

POST/api/v1/auth/login auth: none

Validate credentials and issue a JWT access token plus an httpOnly refresh-token cookie. Returns the user and businessId (null if onboarding incomplete). Constant-time dummy hash verify on unknown emails to prevent timing-based enumeration.

handlers: AuthController.login

Inputs

namerequireddefaultdescription
email body
string (email)
yes—Account email address.
password body
string
yes—Plaintext password (min 1 char per Zod; argon2-verified against stored hash).

Outputs

200
APIResponseDataObject<LoginResponseDTO>
{ accessToken, refreshToken, businessId, user }. Refresh token also set as httpOnly cookie 'refreshToken' (sameSite strict, secure in non-dev).
401
APIError
AUTH_INVALID_CREDENTIALS (unknown email or wrong password) | AUTH_ACCOUNT_DEACTIVATED (user found but IsActive=0).
POST/api/v1/auth/register auth: none

Create the first user account. Hashes the password with argon2, creates a Users row with roles=[] and BusinessId=NULL, and fires AUTH_REGISTER + fire-and-forget welcome email. No tokens are issued — the client is expected to redirect to login.

handlers: AuthController.register

Inputs

namerequireddefaultdescription
firstName body
string
yes—Given name (min 1 char).
lastName body
string
yes—Family name (min 1 char).
username body
string
yes—Display username (min 1 char).
email body
string (email)
yes—Email address — must be globally unique.
password body
string
yes—Plaintext password (min 8 chars).
confirmPassword body
string
yes—Must equal password (Zod refine).

Outputs

201
APIResponseDataObject<RegisterResponseDTO>
{ message: 'Account created successfully' }.
409
APIError
AUTH_EMAIL_TAKEN — an account with this email already exists.
422
APIError
VALIDATION_ERROR — schema violation or passwords do not match.
POST/api/v1/auth/refresh auth: none

Rotate the refresh token. Reads the httpOnly refreshToken cookie (or body fallback for native clients), verifies RS256 signature, checks typ='refresh', re-fetches the user to detect deactivation and post-iat password changes, and issues a fresh access+refresh pair.

handlers: AuthController.refresh

Inputs

namerequireddefaultdescription
refreshToken body
string
no—Fallback for native clients whose OS HTTP stack does not reliably persist cookies. Cookie is the primary source.
refreshToken header
cookie
no—httpOnly refreshToken cookie set on login.

Outputs

200
APIResponseDataObject<RefreshResponseDTO>
{ accessToken, refreshToken, businessId, user }. New refresh-token cookie is set; the old one is invalidated by rotation.
401
APIError
AUTH_UNAUTHORIZED — missing/malformed/expired/tampered token, wrong typ, deactivated user, or token iat predates a password change.
POST/api/v1/auth/logout auth: none

Clear the refresh-token cookie. Idempotent — returns 200 even when the cookie is absent or malformed. Decodes (does not verify) the cookie to write an AUTH_LOGOUT audit event with the userId/businessId when present.

handlers: AuthController.logout

Inputs

No parameters.

Outputs

200
APIResponseDataObject<{ message: string }>
{ message: 'Logged out successfully' }. Refresh-token cookie is cleared.
GET/api/v1/auth/setup-status auth: none

Returns whether any user has been registered yet — used by the frontend to gate the registration form vs. invite-only flow.

handlers: AuthController.setupStatus

Inputs

No parameters.

Outputs

200
APIResponseDataObject<SetupStatusResponse>
{ isFirstUser: boolean } — true when Users count is 0.
POST/api/v1/auth/register-invited auth: none

Complete registration for a user who received an invite link. Validates the invite token in Redis (invite:{token}), creates the user with the invite's roles and organizationId, deletes the token (single-use), and fires AUTH_REGISTER + welcome email.

handlers: AuthController.registerInvited

Inputs

namerequireddefaultdescription
inviteToken body
uuid
yes—Invite token from the invite email link.
firstName body
string
yes—Given name (min 1 char).
lastName body
string
yes—Family name (min 1 char).
password body
string
yes—Plaintext password (min 8 chars).
confirmPassword body
string
yes—Must equal password (Zod refine).

Outputs

201
APIResponseDataObject<RegisterResponseDTO>
{ message: 'Account created successfully. Please log in.' }.
404
APIError
INVITE_INVALID — invite token absent or expired in Redis.
409
APIError
INVITE_ALREADY_USED — the email is already registered (token is also invalidated).
422
APIError
VALIDATION_ERROR — schema violation or passwords do not match.
POST/api/v1/auth/change-password auth: jwt

Verify the caller's current password, hash the new one, and issue a fresh access+refresh token pair. Sets Users.PasswordChangedAt to invalidate all existing refresh tokens. Rate-limited via 'default' bucket.

handlers: AuthController.changePassword

Inputs

namerequireddefaultdescription
currentPassword body
string
yes—Existing password (argon2-verified).
newPassword body
string
yes—New password (min 8 chars; must differ from currentPassword).
confirmPassword body
string
yes—Must equal newPassword (Zod refine).

Outputs

200
APIResponseDataObject<ChangePasswordResponseDTO>
{ accessToken, refreshToken }. Refresh-token cookie also rotated.
401
APIError
AUTH_CURRENT_PASSWORD_INCORRECT — current password did not match. AUTH_UNAUTHORIZED — no/invalid bearer token.
404
APIError
USER_NOT_FOUND — authenticated userId resolved to no row (unlikely).
422
APIError
VALIDATION_ERROR — newPassword === currentPassword or passwords do not match.
POST/api/v1/auth/forgot-password auth: none

Request a 6-digit OTP reset code. Always returns 200 with a generic message — silently no-ops for unknown emails (anti-enumeration). Generates a code, SHA-256-hashes it into PasswordResetTokens, and XADDs the raw code to the auth:password-reset Redis Stream for the email worker. Rate-limited via 'default' bucket.

handlers: AuthController.forgotPassword

Inputs

namerequireddefaultdescription
email body
string (email)
yes—Email address. Normalised to lowercase trim before lookup.

Outputs

200
APIResponseDataObject<ForgotPasswordResponseDTO>
{ message: 'If an account exists for this email, a reset code has been sent.' } — generic regardless of email validity.
422
APIError
VALIDATION_ERROR — schema violation.
POST/api/v1/auth/reset-password auth: none

Confirm a password reset. Validates the OTP against the stored SHA-256 hash, checks expiry, deletes the token (single-use), hashes the new password with argon2, and sets PasswordChangedAt to invalidate every existing refresh token. All failure modes return the same RESET_TOKEN_INVALID code. Rate-limited via 'default' bucket.

handlers: AuthController.resetPassword

Inputs

namerequireddefaultdescription
email body
string (email)
yes—Email address.
resetCode body
string
yes—6-digit OTP from the email.
newPassword body
string
yes—New password (min 8 chars).
confirmPassword body
string
yes—Must equal newPassword (Zod refine).

Outputs

200
APIResponseDataObject<ResetPasswordResponseDTO>
{ message: 'Password reset successfully. Please sign in.' }.
400
APIError
RESET_TOKEN_INVALID — unknown email, no active token, expired, or wrong code (all collapsed under one code for anti-enumeration).
422
APIError
VALIDATION_ERROR — schema violation or passwords do not match.