337 lines
12 KiB
Markdown
337 lines
12 KiB
Markdown
---
|
|
title: Authentication API
|
|
tags: [api, auth, reference]
|
|
---
|
|
|
|
# Authentication API
|
|
|
|
All endpoints are mounted under `/api/auth/*` in `backend/src/app.ts`. The routes file is [`backend/src/services/auth/authRoutes.ts`](../../backend/src/services/auth/authRoutes.ts) and the WebAuthn sub-routes are in [`passkeyRoutes.ts`](../../backend/src/services/auth/passkeyRoutes.ts). Controller logic lives in [`authController.ts`](../../backend/src/services/auth/authController.ts) and [`authService.ts`](../../backend/src/services/auth/authService.ts).
|
|
|
|
Two distinct identities are involved: a [[User]] (`models/User.ts`) and a [[TempVerification]] document that holds pending registration data until the email code is confirmed. Tokens are signed JWTs (access + refresh) created in `authService`. See [[Authentication Flow]] for the high-level lifecycle diagram.
|
|
|
|
## Registration
|
|
|
|
### POST /api/auth/register
|
|
|
|
**Description:** Start a new registration. Creates a [[TempVerification]] document and emails an 8-digit verification code. The actual [[User]] is only created once the code is verified.
|
|
**Auth required:** No
|
|
**Request body:**
|
|
```ts
|
|
{
|
|
email: string;
|
|
firstName?: string; // default "کاربر"
|
|
lastName?: string; // default "جدید"
|
|
role?: "buyer" | "seller"; // default "buyer"
|
|
password?: string; // accepted now or at verify-email-code time
|
|
referralCode?: string; // optional, links to referrer for [[Points API]]
|
|
}
|
|
```
|
|
**Response 200:**
|
|
```json
|
|
{ "success": true, "message": "Verification code sent ...", "data": { "email": "x@y.z" } }
|
|
```
|
|
**Errors:** `400` validation, `409 USER_EXISTS` if email already registered.
|
|
**Side effects:**
|
|
- Upserts a `TempVerification` row.
|
|
- Sends `emailService.sendVerificationCodeEmail`.
|
|
- No socket emission yet (user does not exist).
|
|
**Source:** `authController.register`
|
|
|
|
### POST /api/auth/verify-email-code
|
|
|
|
**Description:** Confirms the registration code. Creates the [[User]], deletes the `TempVerification`, processes any referral, issues JWT tokens, and starts the session in Redis.
|
|
**Auth required:** No
|
|
**Request body:**
|
|
```ts
|
|
{
|
|
email: string;
|
|
code: string; // 8 digits
|
|
password?: string; // required if not provided at register
|
|
}
|
|
```
|
|
**Response 200:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Email verified",
|
|
"data": {
|
|
"user": { "_id": "...", "email": "...", "role": "buyer", ... },
|
|
"tokens": { "accessToken": "...", "refreshToken": "..." }
|
|
}
|
|
}
|
|
```
|
|
**Errors:** `400` invalid/expired code, `404` no pending verification, `409` email already taken.
|
|
**Side effects:**
|
|
- Creates [[User]], deletes `TempVerification`.
|
|
- Calls [[Points API]] referral hook when `referralCode` was supplied (emits `referral-signup` to `user-<referrerId>`).
|
|
- Stores refresh token in `user.refreshTokens`.
|
|
- Welcome email via `emailService.sendWelcomeEmail`.
|
|
|
|
### GET /api/auth/verify-email/:token
|
|
|
|
**Description:** Legacy URL-based verification (link in email). Marks `isEmailVerified=true`.
|
|
**Auth required:** No
|
|
**Response 200:** `{ "success": true, "message": "Email verified successfully" }`
|
|
**Errors:** `400` invalid/expired token.
|
|
|
|
### POST /api/auth/resend-verification
|
|
|
|
**Description:** Re-issues the 8-digit code for a pending or unverified user.
|
|
**Auth required:** No
|
|
**Request body:** `{ email: string }`
|
|
**Response 200:** `{ "success": true, "message": "Verification code resent" }`
|
|
**Errors:** `400` invalid email, `404` no user/temp record, `429` rate-limited (Redis).
|
|
|
|
### POST /api/auth/force-verify-user
|
|
|
|
**Description:** Development-only helper to mark a user verified without going through email.
|
|
**Auth required:** No (intended for dev only — gate with env in prod)
|
|
|
|
## Login & sessions
|
|
|
|
### POST /api/auth/login
|
|
|
|
**Description:** Email/password login. Validates credentials with bcrypt, signs JWT pair, stores refresh token on user, returns sanitized user object.
|
|
**Auth required:** No
|
|
**Request body:**
|
|
```ts
|
|
{
|
|
email: string;
|
|
password: string;
|
|
}
|
|
```
|
|
**Response 200:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Login successful",
|
|
"data": {
|
|
"user": { "_id": "...", "email": "...", "role": "buyer", "firstName": "..." },
|
|
"tokens": { "accessToken": "...", "refreshToken": "..." }
|
|
}
|
|
}
|
|
```
|
|
**Errors:**
|
|
- `400` validation
|
|
- `401` invalid credentials
|
|
- `403` email not verified
|
|
- `423` account locked (after repeated failures, tracked in Redis via `rateLimitService`)
|
|
**Side effects:**
|
|
- Updates `user.lastLoginAt`.
|
|
- Pushes refresh token onto `user.refreshTokens`.
|
|
- Redis session start via `sessionService`.
|
|
|
|
### POST /api/auth/telegram
|
|
|
|
**Description:** First-class Telegram authentication. Accepts Telegram Mini App `initData` or a Telegram Login Widget payload, verifies the Telegram signature server-side, and signs the user into Amanat without requiring email or password.
|
|
**Auth required:** No
|
|
**Request body:**
|
|
```ts
|
|
// Mini App
|
|
{ initData: string; role?: "buyer" | "seller" }
|
|
|
|
// Login Widget
|
|
{ loginWidget: { id: string; first_name?: string; username?: string; auth_date: string; hash: string }; role?: "buyer" | "seller" }
|
|
```
|
|
**Response 200/201:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"user": { "_id": "...", "authProvider": "telegram", "telegramVerified": true },
|
|
"tokens": { "accessToken": "...", "refreshToken": "..." },
|
|
"isNewUser": true,
|
|
"telegram": { "userId": "10001", "username": "alice", "source": "miniapp" }
|
|
}
|
|
}
|
|
```
|
|
**Errors:** `400` missing payload, `401` invalid/stale signature, `403` blocked Telegram account or inactive Amanat account, `409 TELEGRAM_REPLAY` reused Mini App `initData`, `429` rate-limited.
|
|
**Side effects:**
|
|
- Creates a Telegram-only [[User]] when no active `TelegramLink` exists. The user has no email, `authProvider: "telegram"`, and `telegramVerified: true`.
|
|
- Upserts `TelegramLink` for the Telegram ID and updates last-seen metadata.
|
|
- Stores the refresh token on the user document.
|
|
- Does not expose phone numbers; Telegram phone data is not requested or persisted.
|
|
|
|
### POST /api/auth/refresh-token
|
|
|
|
**Description:** Exchanges a refresh token for a new access token. Rotates the refresh token.
|
|
**Auth required:** No (refresh token in body)
|
|
**Request body:** `{ refreshToken: string }`
|
|
**Response 200:** `{ "success": true, "data": { "tokens": { "accessToken": "...", "refreshToken": "..." } } }`
|
|
**Errors:** `401` token expired / not present in user record, `403` user disabled.
|
|
|
|
### POST /api/auth/logout
|
|
|
|
**Description:** Removes the current refresh token from the user record and clears the Redis session.
|
|
**Auth required:** Bearer JWT
|
|
**Response 200:** `{ "success": true, "message": "Logged out" }`
|
|
**Side effects:** `redisService` session removed.
|
|
|
|
## Google OAuth
|
|
|
|
### POST /api/auth/google/signup
|
|
|
|
**Description:** Verifies a Google ID token, creates a new [[User]] (no password), optionally links a referral, returns JWT tokens.
|
|
**Auth required:** No
|
|
**Request body:**
|
|
```ts
|
|
{
|
|
googleToken: string;
|
|
role?: "buyer" | "seller";
|
|
referralCode?: string;
|
|
}
|
|
```
|
|
**Response 200:** Same shape as `verify-email-code` response.
|
|
**Errors:** `400` invalid Google token, `409` email already registered (suggest sign-in instead).
|
|
|
|
### POST /api/auth/google/signin
|
|
|
|
**Description:** Verifies a Google ID token and signs in an existing user. Will not create a new account.
|
|
**Auth required:** No
|
|
**Request body:** `{ googleToken: string }`
|
|
**Response 200:** `{ success, data: { user, tokens } }`
|
|
**Errors:** `400` invalid token, `404` no user with that Google email.
|
|
|
|
## Passkey / WebAuthn
|
|
|
|
Routes are nested under `/api/auth/` via `passkeyRoutes`. Service: `passkeyService.ts`.
|
|
|
|
### POST /api/auth/passkey/authenticate/challenge
|
|
|
|
**Description:** Generates a sign-in challenge that the browser/authenticator will sign. No `userId` required — the assertion will identify the user.
|
|
**Auth required:** No
|
|
**Response 200:** `{ "success": true, "challenge": { /* PublicKeyCredentialRequestOptions */ } }`
|
|
|
|
### POST /api/auth/passkey/authenticate
|
|
|
|
**Description:** Verifies the WebAuthn assertion and, on success, returns a JWT pair.
|
|
**Auth required:** No
|
|
**Request body:** `{ challenge, assertion }` (assertion is the browser's `navigator.credentials.get()` output).
|
|
**Response 200:** `{ "success": true, "userId": "...", "user": { ... }, "tokens": { ... } }`
|
|
**Errors:** `400` missing fields, `404` `Passkey not found`, `500` verification error.
|
|
|
|
### POST /api/auth/passkey/register/challenge
|
|
|
|
**Description:** Generates a registration challenge for the authenticated user.
|
|
**Auth required:** Bearer JWT
|
|
**Response 200:** `{ "success": true, "challenge": { /* PublicKeyCredentialCreationOptions */ } }`
|
|
|
|
### POST /api/auth/passkey/register
|
|
|
|
**Description:** Verifies a new passkey registration and stores the credential on the user.
|
|
**Auth required:** Bearer JWT
|
|
**Request body:** `{ challenge, credential }`
|
|
**Response 200:** `{ "success": true, "message": "Passkey registered successfully" }`
|
|
|
|
### GET /api/auth/passkey/list
|
|
|
|
**Description:** Returns the calling user's registered passkeys (id, label, created date).
|
|
**Auth required:** Bearer JWT
|
|
**Response 200:** `{ "success": true, "passkeys": [...] }`
|
|
|
|
### DELETE /api/auth/passkey/:passkeyId
|
|
|
|
**Description:** Removes a passkey by id.
|
|
**Auth required:** Bearer JWT
|
|
**Response 200:** `{ "success": true, "message": "Passkey removed successfully" }`
|
|
|
|
## Password management
|
|
|
|
### POST /api/auth/request-password-reset
|
|
|
|
**Description:** Generates a reset token, stores it on the user, and emails a reset link plus a numeric code.
|
|
**Auth required:** No
|
|
**Request body:** `{ email: string }`
|
|
**Response 200:** `{ "success": true, "message": "Password reset email sent" }` (always returns success to avoid email enumeration).
|
|
**Side effects:** `emailService.sendPasswordResetEmail`; rate-limited per IP via Redis.
|
|
|
|
### POST /api/auth/reset-password
|
|
|
|
**Description:** Sets a new password using a token from the reset email. Wipes refresh tokens.
|
|
**Auth required:** No
|
|
**Request body:**
|
|
```ts
|
|
{
|
|
token: string;
|
|
password: string; // 6+ chars, mixed case + digit
|
|
}
|
|
```
|
|
**Response 200:** `{ "success": true, "message": "Password updated" }`
|
|
**Errors:** `400` invalid/expired token or weak password.
|
|
|
|
### POST /api/auth/reset-password-with-code
|
|
|
|
**Description:** Alternative reset flow using a numeric code instead of a tokenised URL.
|
|
**Auth required:** No
|
|
**Request body:** `{ email, code, password }`
|
|
**Response 200:** `{ "success": true }`
|
|
|
|
### POST /api/auth/change-password
|
|
|
|
**Description:** Authenticated password change.
|
|
**Auth required:** Bearer JWT
|
|
**Request body:**
|
|
```ts
|
|
{
|
|
currentPassword: string;
|
|
newPassword: string; // 6+ chars, mixed case + digit
|
|
}
|
|
```
|
|
**Response 200:** `{ "success": true, "message": "Password updated" }`
|
|
**Errors:** `400` validation, `401` wrong current password.
|
|
**Side effects:** Clears `user.refreshTokens` (forces re-login on other devices).
|
|
|
|
## Current user / profile
|
|
|
|
### GET /api/auth/profile
|
|
|
|
**Description:** Returns the full sanitized [[User]] document for the caller.
|
|
**Auth required:** Bearer JWT
|
|
**Response 200:** `{ "success": true, "data": { /* User */ } }`
|
|
|
|
### PUT /api/auth/profile (and POST /api/auth/update-profile)
|
|
|
|
**Description:** Updates the caller's profile (first/last name, phone, bio, website, language/currency preferences).
|
|
**Auth required:** Bearer JWT
|
|
**Request body:** Partial profile, see `updateProfileValidation`:
|
|
```ts
|
|
{
|
|
firstName?: string; // 2-50
|
|
lastName?: string; // 2-50
|
|
profile?: {
|
|
phone?: string; // E.164-ish
|
|
bio?: string; // <=500
|
|
website?: string; // URL
|
|
};
|
|
preferences?: {
|
|
language?: "en" | "fa" | "ar";
|
|
currency?: "USD" | "EUR" | "IRR" | "AED";
|
|
};
|
|
}
|
|
```
|
|
**Response 200:** Updated user.
|
|
**Errors:** `400` validation.
|
|
|
|
## Account deletion
|
|
|
|
### DELETE /api/auth/account
|
|
|
|
**Description:** Permanently deletes the caller's account after re-authenticating with password.
|
|
**Auth required:** Bearer JWT
|
|
**Request body:** `{ password: string }`
|
|
**Response 200:** `{ "success": true, "message": "Account deleted" }`
|
|
**Errors:** `401` bad password.
|
|
**Side effects:** Removes [[User]] document, clears Redis session, cascades configured by `dataCleanupService`.
|
|
|
|
## Error codes summary
|
|
|
|
| HTTP | App code | Meaning |
|
|
| --- | --- | --- |
|
|
| 400 | `Validation Error` | `express-validator` rejected the body |
|
|
| 401 | — | Bad credentials / missing token |
|
|
| 403 | — | Email not verified or insufficient role |
|
|
| 409 | `USER_EXISTS` | Email already in use |
|
|
| 423 | — | Account temporarily locked after failed logins |
|
|
|
|
See [[Error Codes]] for the global error shape.
|