Skip to content

Security

The Security module manages the security state of a user account — email verification, password reset, and two-factor authentication (TOTP). All endpoints are grouped under a single SecurityController at /security. The verify-email and password-reset endpoints are @Public() (no Bearer needed) because they are called before/after login; the 2FA and get-security endpoints require Bearer JWT.

PropertyValue
Base URL{HOST}/v1
AuthBearer JWT (header Authorization) or access_token cookie · some endpoints are @Public()
Content-Typeapplication/json
Error envelope{ "message": string | string[], "statusCode": number, "error": string }
ValidationGlobal ValidationPipe · whitelist: true, forbidNonWhitelisted: true · unknown fields → 400
Related modulesauthentication, users, onboarding
Document versionv1 · 2026-05-20
AudienceInternal FE devs (mobile + web)

Three main sub-flows: (1) verify email after signup or when an invitation is accepted — an OTP/token is sent to the user’s email; (2) two-step password reset (initiate → confirm); (3) two-step 2FA TOTP (initiate to get a QR code → enable/disable with an authentication code). The GET /security endpoint is used by FE to read the current security status (verified, 2FA on/off, password change required).

MethodPathAuthSummary
GET/v1/securitybearerRead the security status of the logged-in user
POST/v1/security/verify-emailpublicVerify email + OTP (Public)
PATCH/v1/security/password-resetpublicInitiate password reset, send OTP to email (Public)
POST/v1/security/password-reset/confirmpublicConfirm password reset with OTP (Public)
POST/v1/security/initiate-2fabearerGenerate QR code for authenticator app pairing
POST/v1/security/enable-2fabearerEnable / disable 2FA with TOTP code

Retrieve a summary of the security status for the logged-in user: whether email has been verified, 2FA is enabled, and whether the password needs to be changed. Used by FE to display a badge / banner on the Settings page.

bearer
{
"status": "success",
"statusCode": 200,
"message": "security details retrieved successfully",
"data": {
"security": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "660e8400-e29b-41d4-a716-446655440111",
"is_two_factor_authentication_enabled": false,
"need_password_change": false,
"verified": true,
"created_at": "2026-01-10T08:30:00.000Z",
"updated_at": "2026-05-20T08:30:00.000Z"
}
}
}

The two_factor_authentication_secret field is deliberately not sent — its column is select: false on the entity. FE does not need (and is not allowed) to access that secret.

StatusWhen it occurs
401 UnauthorizedBearer/cookie token is invalid

POST /v1/security/verify-email public

Section titled “POST /v1/security/verify-email ”

Verify a user’s email via the previously sent OTP. Endpoint is @Public() — callable without Bearer. The email and otp/token parameters can be sent via body or query string; the query string wins if both are sent.

public
ParamTypeDefaultNotes
emailstringEmail of the user to be verified
otpstringOTP / verification token
FieldTypeRequiredNotes
emailstringyes (if no query email)Valid email format (IsEmail)
tokenstringyes (if no query otp)OTP / verification token
{
"email": "admin@moriafund.com",
"token": "12345678"
}
POST /v1/security/verify-email?email=admin@moriafund.com&otp=12345678
{
"statusCode": 200,
"message": "Email verified successfully"
}
StatusWhen it occurs
400 Bad RequestInvalid body · email or token/otp empty (in both body and query) · OTP mismatch / expired

PATCH /v1/security/password-reset public

Section titled “PATCH /v1/security/password-reset ”

Initiate password reset: the server sends an OTP to the user’s email. Endpoint is @Public() — used from the “Forgot Password” page before login.

public
FieldTypeRequiredNotes
emailstringyesValid email format (IsEmail)
{
"email": "user@moriafund.com"
}
{
"statusCode": 200,
"message": "Password reset OTP sent successfully"
}
StatusWhen it occurs
400 Bad RequestInvalid email / user not found
  • The server issues a password-reset OTP and sends an email to the user.
  • IP / user-agent from req are recorded for audit (rate-limit / anti-abuse).

POST /v1/security/password-reset/confirm public

Section titled “POST /v1/security/password-reset/confirm ”

Confirm password reset: the user sends OTP + new password. Endpoint is @Public(). The server verifies the OTP, then changes the password.

public
FieldTypeRequiredNotes
emailstringyesValid email format
tokenstringyesPassword-reset OTP sent to the email
passwordstringyesNew password · IsStrongPassword — min 8 chars + combination
{
"email": "user@moriafund.com",
"token": "123456",
"password": "Strong@8password"
}
{
"statusCode": 200,
"message": "Password reset successful"
}
StatusWhen it occurs
400 Bad RequestInvalid / expired OTP · weak password · email does not match
  • The user’s password hash is updated.
  • The server may revoke old sessions / set the need_password_change = false flag.
  • IP / user-agent are recorded for audit.

POST /v1/security/initiate-2fa bearer

Section titled “POST /v1/security/initiate-2fa ”

Generate a TOTP secret + QR code data URL for pairing with an authenticator app (Google Authenticator, Authy, etc.). Does not enable 2FA yet — the user must confirm with the enable-2fa endpoint.

bearer
{
"status": "success",
"statusCode": 200,
"message": "qrcode generated successfully",
"data": {
"qrcode": "data:image/png;base64,iVBORw0KGgoAAAANS..."
}
}

The qrcode field is a base64 PNG Data URL — FE can set it directly on <img src=...>. The secret encoded in the QR is stored encrypted on the server and is never sent in plaintext to FE.

StatusWhen it occurs
401 UnauthorizedBearer/cookie token is invalid

POST /v1/security/enable-2fa bearer

Section titled “POST /v1/security/enable-2fa ”

Enable or disable 2FA. To enable, the user enters the TOTP code from the authenticator app (result of scanning the QR from initiate-2fa). To disable, set off_two_fa: true — the TOTP code is still required by the DTO but is ignored by the service when off.

bearer
FieldTypeRequiredNotes
two_fa_authentication_codestringyes6-digit TOTP from authenticator app · IsNotEmpty
off_two_fabooleanoptionaltrue → the server calls disable2FA; false (or not sent) → enable. Default false.
{
"two_fa_authentication_code": "123456"
}
{
"two_fa_authentication_code": "123456",
"off_two_fa": true
}
{
"status": "success",
"statusCode": 200,
"message": "2fa enabled successfully"
}
StatusWhen it occurs
400 Bad RequestInvalid body · TOTP code empty / wrong format
401 UnauthorizedTOTP code does not match · Bearer/cookie token is invalid
  • Updates the is_two_factor_authentication_enabled flag in the users_security table.
  • IP / user-agent are recorded for audit (req is passed to the service).

  • user_id — 1-1 relation to Users
  • two_factor_authentication_secret — encrypted, select: false (never sent to FE)
  • is_two_factor_authentication_enabled — boolean
  • need_password_change — flag when admin forces user to change password
  • verified — email verification status
  • Verify email: 1 step — send email + OTP via body or query
  • Password reset: 2 steps — PATCH /password-reset → receive OTP via email → POST /password-reset/confirm
  • 2FA: 2 steps — POST /initiate-2fa (get QR) → scan in authenticator → POST /enable-2fa with TOTP code
{
"message": "Email and OTP/token are required.",
"statusCode": 400,
"error": "Bad Request"
}

message can be a string or an array of strings (multi-field validation error).

  • 400 body/query validation failed · invalid OTP/token
  • 401 wrong TOTP code · invalid Bearer token (for non-Public endpoints)
  • 500 internal — show a generic toast
  • After password-reset/confirm succeeds, redirect the user to the login page.
  • After enable-2fa succeeds, suggest the user re-login on another device (to test TOTP).
  • GET /security is safe to call often — use it to hydrate Settings page state.