

# **WORKFORCEOS**

**FRONTEND GUIDE FOR AI CODING AGENTS - PART 3 - Verification Management**

This document is a part of a REST API guide for the workforceos project.
It is designed for AI agents that will generate frontend code to consume the project's backend.

This document includes the verification processes for the autheitcation flow. Please read it carefully and implement all requirements described here.

The project has 1 auth service, 1 notification service, 1 BFF service, and 10 business services, plus other helper services such as bucket and realtime. In this document you will be informed only about the auth service. 

Each service is a separate microservice application and listens for HTTP requests at different service URLs.

Services may be deployed to the preview server, staging server, or production server. Therefore, each service has 3 access URLs.
The frontend application must support all deployment environments during development, and the user should be able to select the target API server on the home page.

## Accessing the backend

Each backend service has its own URL for each deployment environment. Users may want to test the frontend in one of the three deployments—preview, staging, or production. Please ensure that the home page includes a deployment server selection option so that, as the frontend coding agent, you can set the base URL for all services.

For the auth service, the base URLs are:

* **Preview:** `https://workforceos.prw.mindbricks.com/auth-api`
* **Staging:** `https://workforceos-stage.mindbricks.co/auth-api`
* **Production:** `https://workforceos.mindbricks.co/auth-api`

Any request that requires login must include a valid token in the Bearer authorization header.



## Multi-Tenancy Management

**THIS APPLICATION IS MULTI-TENANT**

This application is mult-tanant, it means; as a SaaS application, it isolates each tenant-data from each other. The tenants are called `company` and a data object with the same name exist to store and manage the tenant information. The `company` data instances are referenced from other data objects or entities as `companyId`. Any data object which is in tenant level (some data objects may still stay in SaaS level) has a `companyId` property which attaches it to a `company` tenant. But this value is added to the data object instance in the time of creation by the system according to the current `company` user is navigating. 

For the human readability each `company` has also got a `companyCodename` which is a unique form of its name. Any api call to the application should claim its target `company` tenant, so it should provide `companyCodename` parameter at one of the following request locations.

```js
// In header with special header name
headers["mbx-company-codename"] = "babil",

// or in query with ( _company ) parameter

const url = `${serviceUrl}/v1/users/${userId}?_company=babil`

// or in post body with ( _company ) parameter
const body = {
  //...
  _company: "babil",
  //...
}
```

When no `companyCodename` is given in the related parameters, application will assume that the access targets the root target, the Saas level. The `companyCodename` of SaaS level is always `root`. When no `companyCodename` is specified, `root` is assumed as the current `companyCodename`.

Note that, logins and registrations are all tenant scoped. If any a tenant-lvel api is login-required then it will check for a `company` specific token according to the target `company` defined in a related parameter with its codename. The application creates the cookies with `companyCodename` prefixes but it is recommended for the frontend application to populate the bearer header with the user's `company` related token.

Not that all `company` tenants are managed in the same backend services, however a `company` tanant frontend should be specific to one `company`. Frontend should have a technical way to understand which `company` is targeted by the user, for production frontend application this should be managed by the subdomains that represents the codename of the company like,

````
https://babil.storeCreator.com
````

Using the subdomain, frontend will distinguish that the codename is `babil` and will atatch it to all tenant-level api calls.

The url may also be used for the SaaS directly with a more general wording like,

````
https://www.storeCreator.com
````

However, this subdomain management may not be handled easily in the AI frontend builders that they use their own preview urls, an other url structure should be handled to simulate the subdomain behaviour for tenant forwarding.

It may be like,

````
https://{somePreviewUrlOfABuilderPlatform}/babil/login
````

In some way, frontend should provide a simple and practical way to the user to target a specific tenant.

### Sample Company Tenant

The applcation backend also includes a sample tenant created with a sample tenant owner, who has the same login identifier and password as the superadmin. This sample is created in the backend to be able to test the multi-tenant behaviour of the frontend at the beginning.

The sample company has a codename `babil` and can be targetd by attaching this codename to the API calls. So whwn the front end home page is built, homepages both for the SaaS and tenant shoul be created and ready to be tested.





## After User Registration

Frontend should also be aware of verification after any login attempt. 
The login request may return a 401 or 403 with the error codes that indicates the verification needs.

```json
{
  //...
  "errCode": "EmailVerificationNeeded",
  // or
  "errCode": "MobileVerificationNeeded",
}
```

## Email Verification

In the registration response, check the `emailVerificationNeeded` property in the response root. If it is `true`, start the email verification flow.

After the login process, if you receive an HTTP error and the response contains an `errCode` with the value `EmailVerificationNeeded`, start the email verification flow.

1. Call the email verification `start` route of the backend (described below) with the user's email. The backend will send a secret code to the provided email address. **The backend can send the email if the architect has configured a real mail service or SMTP server. During development, the backend also returns the secret code to the frontend. You can read this code from the `secretCode` property of the response.**
2. The secret code in the email will be a 6-digit code. Provide an input page so the user can paste this code into the frontend application. Navigate to this input page after starting the verification process. **If the `secretCode` is sent to the frontend for testing, display it on the input page so the user can copy and paste it.**
3. The `start` response includes a `codeIndex` property. Display its value on the input page so the user can match the index in the message with the one on the screen.
4. When the user submits the code, complete the email verification using the `complete` route of the backend (described below) with the user's email and the secret code.
5. After a successful email verification response, please check the response object to have the property 'mobileVerificationNeeded' as `true`, if so navigate to the mobile verification flow as described below. 
**If no mobile verification is needed then just navigate the login page.** 

Below are the `start` and `complete` routes for email verification. These are system routes and therefore are not versioned.

#### `POST /verification-services/email-verification/start`

**Purpose:**
Starts email verification by generating and sending a secret code.

| Parameter | Type   | Required | Description                    |
| --------- | ------ | -------- | ------------------------------ |
| `email`   | String | Yes      | User's email address to verify |

**Example Request**

```json
{ "email": "user@example.com" }
```

**Success Response**

```json
{
  "status": "OK",
  "codeIndex": 1,
  "timeStamp": 1784578660000,
  "date": "Mon Jul 20 2026 23:17:40 GMT+0300 (GMT+03:00)",
  "expireTime": 86400,
  "verificationType": "byLink",
  "secretCode": "123456",
  "userId": "user-uuid"
}
```

> ⚠️ In production, `secretCode` is **not** returned — it is only sent via email.

**Error Responses**

* `400 Bad Request`: Already verified
* `403 Forbidden`: Too many attempts (rate limit)

---

#### `POST /verification-services/email-verification/complete`

**Purpose:**
Completes verification using the received code.

| Parameter    | Type   | Required | Description       |
| ------------ | ------ | -------- | ----------------- |
| `email`      | String | Yes      | User's email      |
| `secretCode` | String | Yes      | Verification code |

**Success Response**

```json
{
  "status": "OK",
  "isVerified": true,
  "email": "user@email.com",
  "userId": "user-uuid"
}
```

**Error Responses**

* `403 Forbidden`: Code expired or mismatched
* `404 Not Found`: No verification in progress

---




## Resetting Password

Users can reset their forgotten passwords without a login required, through email verification.
To be able to start a password reset flow, users will click on the "Reset Password" link in the login page.





## Password Reset By Email Flow

1. Call the password reset by email verification `start` route of the backend (described below) with the user's email. The backend will send a secret code to the provided email address. **The backend can send the email if the architect has configured a real mail service or SMTP server. During development, the backend also returns the secret code to the frontend. You can read this code from the `secretCode` property of the response.**
2. The secret code in the email will be a 6-digit code. Provide an input page so the user can paste this code into the frontend application. Navigate to this input page after starting the verification process. **If the `secretCode` is sent to the frontend for testing, display it on the input page so the user can copy and paste it.**
3. The `start` response includes a `codeIndex` property. Display its value on the input page so the user can match the index in the message with the one on the screen.
4. The input page should also include a double input area for the user to enter and confirm their new password.
5. When the user submits the code and the new password, complete the password reset by email using the `complete` route of the backend (described below) with the user's email, the secret code and new password.
6. After a successful verification response, navigate to the login page.

Below are the `start` and `complete` routes for password reset by email verification. These are system routes and therefore are not versioned.

#### POST `/verification-services/password-reset-by-email/start`
  
  **Purpose**:  
  Starts the password reset process by generating and sending a secret verification code.
  
  #### Request Body
  
  | Parameter | Type   | Required | Description                         |
  |-----------|--------|----------|-------------------------------------|
  | email     | String | Yes      | The email address of the user       |
  
  ```json
  {
    "email": "user@example.com"
  }
  ```
  
  **Success Response**
  
  Returns secret code details (only in development environment) and confirmation that the verification step has been started.
  
  ```json
  {
    "userId": "user-uuid",
    "email": "user@example.com",
    "codeIndex": 1,
    "secretCode": "123456", 
    "timeStamp": 1765484354,
    "expireTime": 86400,
    "date": "2024-04-29T10:00:00.000Z",
    "verificationType": "byLink",
  }
  ```
  
  ⚠️ In production, the secret code is only sent via email and not exposed in the API response.
  
  **Error Responses**
  
  - `401 NotAuthenticated`: Email address not found or not associated with a user.
  - `403 Forbidden`: Sending a code too frequently (spam prevention).
  
  ---
  
  #### POST `/verification-services/password-reset-by-email/complete`
  
  **Purpose**:  
  Completes the password reset process by validating the secret code and updating the user's password.
  
  #### Request Body
  
  | Parameter   | Type   | Required | Description                                  |
  |-------------|--------|----------|----------------------------------------------|
  | email       | String | Yes      | The email address of the user                |
  | secretCode  | String | Yes      | The code received via email                  |
  | password    | String | Yes      | The new password the user wants to set       |
  
  ```json
  {
    "email": "user@example.com",
    "secretCode": "123456",
    "password": "newSecurePassword123"
  }
  ```
  
  **Success Response**
  
  ```json
  {
    "userId": "user-uuid",
    "email": "user@example.com",
    "isVerified": true
  }
  ```
  
  **Error Responses**
  
  - `403 Forbidden`:
    - Secret code mismatch
    - Secret code expired
    - No ongoing verification found

  ---






## Two-Factor Authentication (2FA)

**This project has email two-factor authentication enabled.** 2FA is different from email/mobile verification: verification proves ownership during registration (one-time), while **2FA runs on every login** as an additional security layer.

### How 2FA Works After Login

When a user logs in successfully, the login response includes `accessToken`, `userId`, `sessionId`, and all session data. However, when 2FA is active, the response **also** contains one or both of these flags:

* `sessionNeedsEmail2FA: true` — email 2FA is required

**When any of these flags are `true`, the session is NOT fully authorized.** The `accessToken` is valid only for calling the 2FA verification endpoints. All other protected API calls will return `403 Forbidden` with error code `EmailTwoFactorNeeded` or `MobileTwoFactorNeeded` until 2FA is completed.

### 2FA Frontend Flow

1. After a successful login, check the response for `sessionNeedsEmail2FA` or `sessionNeedsMobile2FA` (login returns 200 with the full session — `accessToken`, `userId`, `sessionId` and all user fields — *plus* the 2FA flag).
2. If either flag is `true`, **do not treat the user as authenticated and do not navigate to the app home**. The token from `useLogin` is already stored automatically; it's a *partial* token though — the backend's `enforce2FA(session)` will reject every protected call with `EmailTwoFactorNeeded` / `MobileTwoFactorNeeded` 403 until the 2FA flag is cleared. Only the 2FA verification endpoints (and `/currentuser`) accept it as-is.
3. Navigate the user to a **2FA verification page** instead of `/`.
4. On the 2FA page, immediately call the 2FA `start` endpoint (described below). The user / session are read from the auth header — no `userId` or `sessionId` in the payload. This triggers sending the verification code to the user's email.
5. Display a 6-digit code input. **If the response contains `secretCode` (test/development mode), display it on the page so the user can copy and paste it.**
6. The `start` response includes a `codeIndex` property. Display its value on the page so the user can match the index in the message with the one on the screen.
7. When the user submits the code, call the 2FA `complete` endpoint with just `secretCode` in the body (the auth header carries the user / session identity).
8. On success, the `complete` endpoint returns the **updated session object** with the 2FA flag cleared. Now set the user as fully authenticated and navigate to the main application page.
9. Provide a "Resend Code" button with a **60-second cooldown** to prevent spam.
10. Provide a "Cancel" option that discards the partial session and returns the user to the login page.


### Email 2FA Endpoints

#### `POST /verification-services/email-2factor-verification/start`

**Purpose:**
Starts email-based 2FA by generating and sending a verification code to the user's email.

| Parameter   | Type   | Required | Description                    |
| ----------- | ------ | -------- | ------------------------------ |
| `userId`    | String | Yes      | The user's ID                  |
| `sessionId` | String | Yes      | The current session ID         |

**Example Request**

```json
{
  "userId": "user-uuid",
  "sessionId": "session-uuid"
}
```

**Success Response**

```json
{
  "status": "OK",
  "sessionId": "session-uuid",
  "userId": "user-uuid",
  "codeIndex": 1,
  "timeStamp": 1784578660000,
  "date": "Mon Jul 20 2026 23:17:40 GMT+0300",
  "expireTime": 86400,
  "verificationType": "byCode",

  // in testMode only
  "secretCode": "123456"
}
```

> ⚠️ In production, `secretCode` is **not** returned — it is only sent via email.

**Error Responses**

* `403 Forbidden`: Code resend attempted before cooldown (60s)
* `401 Unauthorized`: Session not found

---

#### `POST /verification-services/email-2factor-verification/complete`

**Purpose:**
Completes email 2FA by validating the code and clearing the session 2FA flag.

| Parameter    | Type   | Required | Description                   |
| ------------ | ------ | -------- | ----------------------------- |
| `userId`     | String | Yes      | The user's ID                 |
| `sessionId`  | String | Yes      | The session ID                |
| `secretCode` | String | Yes      | Verification code from email  |

**Success Response**

Returns the updated session with `sessionNeedsEmail2FA: false`:

```json
{
  "sessionId": "session-uuid",
  "userId": "user-uuid",
  "email": "user@example.com",
  "fullname": "John Doe",
  "roleId": "user",
  "sessionNeedsEmail2FA": false,
  "accessToken": "jwt-token",
  "...": "..."
}
```

**Error Responses**

* `403 Forbidden`: Code mismatch or expired
* `403 Forbidden`: No ongoing verification found
* `401 Unauthorized`: Session does not exist

---

### Important 2FA Notes

* **One code per session**: Only one active verification code exists per session at a time.
* **Resend throttling**: Code requests are throttled — wait at least 60 seconds between resend attempts.
* **Code expiration**: Codes expire after 86400 seconds.
* **Session stays valid**: The `accessToken` from login remains the same throughout the 2FA flow — you do not get a new token. The `complete` response returns the same session with the 2FA flag cleared.
* **`/currentuser` works during 2FA**: The `/currentuser` endpoint does **not** enforce 2FA, so it can be called during the 2FA flow. However, all other protected endpoints will return `403`.



## Navigating the Login Page
Please note that since this application is multitenant, the login page that will be navigated to after a verification process, should be aware of the tenant company. 
If this login navigate is after a user registration (or after a post-registration verification) to a company, then keep the active company as the original and navigate user to the selected company.
If this login navigate is after a company registration (or after a tenant post-registration verification), 
then you have a new company other than the `root` one, so you should make this new tenant as the selected one and navigate to its login page.
If this login is after a post-login verification, then keep the original company that the previous login attempt is made to and navigate to the same company either it is the `root` or andy registered `company`.
 


** Please dont forget to arrange the code to be able to navigate to the verification pages both after registrations and login attempts if verification is needed.**
  

**After this prompt, the user may give you new instructions to update your first output or provide subsequent prompts about the project.**