# User registration flow

How a new user joins the WCG Platform: they fill in a form, confirm by email, choose a password, then sign in.

This guide is written for **everyone** (product, QA, support) and **developers** (implementation and ops).  
It covers the current end-to-end public flow: **registration, email verification, password setup, login, designation-based role assignment, and how roles / rights should be used for API access after login**.

> **Document maintenance**  
> Update this file whenever registration behavior changes (API fields, role resolution, migrations, email flow, login gates, or rights seeding).  
> Primary code touchpoints: `data_fetchers/class.UserData.php`, `common/register_email_verification.php`, `doi_platform_bo_api/database/migrations/*designation_role*`.

---

## At a glance (plain language)

| Step | What the user does | What happens behind the scenes |
|------|--------------------|--------------------------------|
| **1. Sign up** | Completes the registration form on the website (name, email, department, designation, etc.) | The system saves a **pending** account, assigns a **platform role from designation mapping**, and sends a verification email. The user **cannot sign in yet**. |
| **2. Check email** | Opens the email *“WCG Platform - Verify your registration”* and clicks the link | The link opens a **separate secure page** (not the main app) to choose a password. |
| **3. Set password** | Enters the **OTP** from the email, chooses and confirms a new password, then clicks **Save and continue** | The account is **activated**. A green success screen appears with **Go to login**. |
| **4. Sign in** | Clicks **Go to login**, then signs in with **the same email** and **the password they just chose** | The main application accepts the login and returns the user token / rights payload. |
| **5. Access modules / APIs** | Uses the application after login | Access should be controlled by **API rights / permissions**, with frontend visibility only reflecting what the backend allows. |

**Important for users**

- Use the **same email address** on the form, in the email link, and when logging in (check spelling).
- The email includes an **OTP** (8-character code). They must enter it on the set-password page, then choose their login password.
- The OTP is **not** for everyday sign-in — only the password they choose on the verification page counts after step 3.
- If no email arrives within a few minutes, check spam or ask support (registration and password-reset mail is sent immediately via SMTP).

---

## What users see at each step

### Step 1 — Registration form (main website)

- User fills: first name, last name, email, organisation details, department, designation, etc.
- Allowed email domains are controlled by administrators (e.g. only certain `@company.com` domains).
- On success: message that a verification email was sent.

### Step 2 — Verification email

- **Subject:** `WCG Platform - Verify your registration`
- Contains an **OTP** (8-character code) and a **verification link** (do not share; both are personal).

### Step 3 — Set password page (from the link)

- Opens in the browser on the **API host** (may look slightly different from the main site branding, but is official).
- User enters the **OTP** from the email, then chooses a password that meets the rules (length, upper/lower case, number, special character).
- After **Save and continue:** green **Registration complete** screen and a **Go to login** button (the site does not jump away automatically).

### Step 4 — Login (main website)

- User opens the normal login page and enters **email + password**.
- Login only works after step 3 is finished.

### Step 5 — Access after login

- User lands in the application with a token from the login service.
- Access should be decided by **role / right checks in backend APIs**.
- Frontend menus can hide or show modules for usability, but they must **not** be the only access-control layer.

---

## Common issues (support / QA)

| What the user reports | Likely cause | What to check |
|----------------------|--------------|---------------|
| “I never got the email” | SMTP or spam filter | Mail config (`config_mail.php`); spam folder |
| “The link gives 404” | Wrong server URL | Link should include `/api/` on dev/QA/live; see technical section |
| “Username or password incorrect” after registering | Email typo or wrong password | Email in database must match login email exactly; password is the one set on the verification page |
| “Your account is inactive” after registering | Password step not completed yet | `tbl_user_master.is_active` is still `0` until password step is finished |
| “User is not verified” | Legacy / environment-specific behavior | Check `tbl_register.reg_status` and which login path is active on that environment |
| “Email domain not allowed” | Domain not in allow-list | `registration_allowed_email_domains` in platform settings |
| “No platform role is configured for the selected designation” | No active mapping for that designation | `tbl_designation_role_mapping` + `tbl_role_master` (see designation → role section) |
| User has wrong permissions after login | Mapping missing; fallback role used | Check application logs for `registerUser: no designation-role mapping`; verify mapping seed ran |

---

## Technical reference

*The sections below are for developers and system administrators.*

### System overview

Three main pieces interact:

| Piece | Role |
|-------|------|
| **Frontend SPA** | Registration form; later, login screen |
| **doi_gov_api** | Saves registration, resolves role from designation, sends verification email immediately, hosts the set-password page |
| **doi_public_pages_api** | Handles **login** after registration is complete |

**Flow in short:** the main website submits registration to **doi_gov_api** → role is resolved from designation mapping → verification email is sent via SMTP → the user opens the link on **doi_gov_api** to set a password → the account becomes active → the user signs in on the main website via **doi_public_pages_api** → the system returns token + rights and the application should enforce access at API level.

### Account states (database)

| Status | `tbl_register.reg_status` | Can log in? |
|--------|---------------------------|-------------|
| Registered, no password yet | `Initiated` | No |
| Registration finished | `Success` | Yes (if `is_active = 1`) |

| `tbl_user_master` | After form submit | After password page |
|-------------------|-------------------|---------------------|
| `is_active` | `0` | `1` |
| `isActive` | `0` | `1` |
| `password` | Empty until verification | MD5 hash of password chosen on verification page |
| `pwd_expiry_dt` | Set on insert | Updated again on password completion |
| `role_id` | Set at registration from designation mapping | Unchanged |

| `tbl_register` | After form submit | After password page |
|----------------|-------------------|---------------------|
| `otp_encrypted` | Encrypted OTP (AES via `encrypt_decrypt`) | `NULL` (cleared on success) |
| `reg_code` | In verification link token | Unchanged |
| `verify_count` | `NULL` / not used yet | Incremented on success |
| `verify_time` | `NULL` | Set on success |

---

### Prerequisites (deployment)

**Migrations (doi_platform_bo_api)**

| Migration | Purpose |
|-----------|---------|
| `2026_07_20_110504_create_tbl_designation_role_mapping_table.php` | Creates `tbl_designation_role_mapping` junction table |
| `2026_07_20_110505_seed_tbl_designation_role_mapping.php` | Seeds initial designation → platform role mappings |

**Other migrations** — from `incremental.sql`: `tbl_register.otp_encrypted`, `registration_allowed_email_domains`, etc. (see [email-queue.md](./email-queue.md) for other queued mail).

**Rights seeding on `tbl_user_master`:**

- `tr_user_right_association_insert` — copies `tbl_role_right_association` into `tbl_user_right_association` when a user is created (including `registerUser`).
- A `tr_user_master_after_delete` trigger is **not confirmed in the current repository SQL** and should not be assumed during deployment / cleanup discussions.

If the **insert** trigger is missing on a server, the registration may still create the user, but the user-right seed from the assigned role will be incomplete.

**Email delivery** — registration and forgot-password mail is sent immediately via `mailTo()` / SMTP (not `tbl_email_queue`). Other features (e.g. evaluation reminders) may still use the queue; see [email-queue.md](./email-queue.md).

**Config** (`config/<env>/config.php`):

| Constant | Purpose |
|----------|---------|
| `API_URL` | Host for verification links |
| `HOSTENV` | `local` → links without `/api/`; otherwise links use `/api/` |
| `PLATFORM_FRONTEND_LINK_BASE` | URL for **Go to login** button |
| Mail include | `config/<env>/config_mail.php` |

**Allowed domains** — `tbl_platform_config.registration_allowed_email_domains` as JSON, e.g. `["cioinfotech.com","pet.com"]`.

If this config is empty or invalid, public registration is effectively unavailable.

---

### API: load form data

**Service:** `userdata` · **Operation:** `getRegistrationDropdownData` · **Auth:** Public

Returns: `departments`, `userDesignations`, `userDesignationSpecifics`, `userType`.

Designations come from lookup `tbl_lookup.dev_name = user_designation` (display label **Designation**).

---

### API: submit registration

**Service:** `userdata` · **Operation:** `registerUser` · **Handler:** `UserData::registerPublicUser()`

**Required fields:** `first_name`, `last_name`, `email`, `org_type`, `user_type`, `department_id`, `designation_data_key`

**Optional:** `phone_number`, `gender` (default `M`), `designation_specific_data_key` (empty string = designation-only mapping)

**Server (transaction):** validate email + domain → validate designation → resolve registration role from mapping → insert user (inactive) → insert user details → insert `tbl_register` (`Initiated`) → send verification email (`mailTo`).

**Current persistence behavior**

| Field | Required by API | Persisted today |
|------|------------------|-----------------|
| `first_name` | Yes | Yes |
| `last_name` | Yes | Yes |
| `email` | Yes | Yes |
| `designation_data_key` | Yes | Yes (`tbl_user_master.designation_datakey`) |
| `designation_specific_data_key` | Optional | Yes (may be empty) |
| `gender` | Optional | Yes |
| `phone_number` | Optional | Yes |
| `org_type` | Yes | No |
| `user_type` | Yes | No |
| `department_id` | Yes | No |

So the current flow **collects** `org_type`, `user_type`, and `department_id`, but `registerUser` does **not** persist them yet.

**Success example:**

```json
{
  "status": "ok",
  "message": "User saved successfully and verification email sent.",
  "data": { "user_id": 123, "reg_id": 456, "role_id": 12, "email": "user@example.com" }
}
```

**Error codes:** `err201` validation · `err202` email exists · `err204` failure · `err205` email could not be sent (SMTP)

> `role_id` in the response is the resolved platform role stored on `tbl_user_master.role_id`. It drives post-login rights via the DB trigger and `PermissionService`.

---

### Designation → role mapping (registration)

Registration assigns **one** `tbl_user_master.role_id` based on the user's selected designation. Mapping is stored in `tbl_designation_role_mapping` (managed in **doi_platform_bo_api** migrations).

#### Lookup sources

| Concept | Table / field |
|---------|----------------|
| Designation options | `tbl_lookup` where `dev_name = user_designation` |
| Designation keys | `tbl_lookup_detail.data_key` (e.g. `user_designation_8`) |
| Designation label | `tbl_lookup_detail.display_name` (e.g. `Project Manager`) |
| Platform roles | `tbl_role_master` where `role_type` is `platform` or `global` |

#### Mapping table (`tbl_designation_role_mapping`)

| Column | Purpose |
|--------|---------|
| `designation_data_key` | FK-style reference to `tbl_lookup_detail.data_key` (designation) |
| `designation_specific_data_key` | Optional sub-designation key; `''` = applies to all specifics / designation-only phase |
| `role_id` | FK to `tbl_role_master.role_id` |
| `is_default` | `1` = preferred row when multiple roles map to the same designation scope |
| `priority` | Tie-breaker when multiple rows share the same `is_default`; **lower number wins** |
| `is_active` | `1` = mapping used at registration |

**When `priority` matters:** only when **one designation** has **multiple active role rows**. It does not rank different designations against each other. With a single mapping per designation (current seed), `priority` has no effect.

#### Resolution order (`UserData::resolveRegistrationRoleIdFromDesignation`)

1. If `designation_specific_data_key` is sent → lookup mapping for `(designation_data_key, designation_specific_data_key)`
2. Else → lookup mapping for `(designation_data_key, '')` (designation-only)
3. If no mapping found → **fallback** to platform role `Stage Gate Data Entry`
4. If fallback is used → warning written to application log (`registerUser: no designation-role mapping...`)
5. If resolved `role_id` is still `0` → API returns `err201` (“No platform role is configured for the selected designation.”)

**Role type filter at lookup:** only `platform` and `global` roles from `tbl_role_master` are considered (`REGISTRATION_ALLOWED_ROLE_TYPES`). Project-scoped roles (e.g. project `Engineer`) are excluded from registration assignment.

**Winner selection SQL order:** `is_default DESC`, `priority ASC`, `mapping_id ASC`, `LIMIT 1`.

#### Seeded mappings (initial)

Seed migration resolves rows by **human-readable labels** (portable across environments), then stores `data_key` + `role_id`:

| Designation (`display_name`) | Platform role (`role_name`) |
|------------------------------|----------------------------|
| Project Manager | Project Manager |
| Head of Department | Monitor & Evaluate |
| Assistant Director | Approver |

Seed file: `doi_platform_bo_api/database/migrations/2026_07_20_110505_seed_tbl_designation_role_mapping.php`

To add mappings on a new environment, add rows to the seed migration (or a new seed migration) using `designation_display_name` + `role_name` + `role_type` — **do not hardcode** `role_id` or `data_key` in application code.

#### Tables touched by registration (role path)

| Table | Role in flow |
|-------|----------------|
| `tbl_lookup` / `tbl_lookup_detail` | Validate designation; source of `designation_data_key` |
| `tbl_designation_role_mapping` | Designation → role resolution |
| `tbl_role_master` | Target platform role |
| `tbl_user_master` | Stores resolved `role_id` |
| `tbl_role_right_association` | Default rights for the role |
| `tbl_user_right_association` | User rights seeded by trigger on insert |

---

### Verification email (technical)

- **Link:** `{base}/complete-registration.php?code={encrypted_token}`
- **Token contains:** registration code, email, type `register`
- **OTP:** included in the same email and required on the password page
- **URL base:**

| `HOSTENV` | Example |
|-----------|---------|
| `local` | `https://api.example.com/complete-registration.php?code=...` |
| `dev` / `qa` / `live` | `https://api.example.com/api/complete-registration.php?code=...` |

---

### Forgot-password email (technical)

- **Operation:** `forgetPassword` · **Handler:** `UserData::requestForgotPassword()`
- **Delivery:** immediate SMTP via `mailTo()` (not queued)
- **Link:** `{base}/reset-password.php?code={encrypted_token}` (type `forgot_password` in token)

---

### Set password page (technical)

| Item | Value |
|------|--------|
| URL | `api/complete-registration.php` |
| UI class | `services/Registration/RegistrationPasswordPage.php` |
| POST fields | `code`, `csrf_token`, `otp`, `new_password`, `confirm_password` |
| Storage | `md5(trim(plain_password))` — must match login `pwd` from frontend |
| On success | `reg_status = Success`, `is_active = 1` |

**Additional rules**

- Password must meet complexity rules from `validateRegistrationNewPassword()`.
- Password must be different from the OTP sent in the email.
- Browser page requires a valid session CSRF token.

**Optional API alternative:** `userdata` / `verify-register-code` with `code`, `otp`, `new_password`, and `confirm_password` (same logic as the page).

Accepted aliases in API payload:

- `reg_ver_code` instead of `code`
- `temp_password` / `temp_pass` instead of `otp`

---

### Login (technical)

**Service:** `auth` on **doi_public_pages_api** (`api/auth.php`, `op: login`)

All must be true:

1. Email matches `tbl_user_master.email` (registration stores lowercase).
2. `password` matches MD5 sent as `pwd`.
3. `is_active = 1`
4. `tbl_register.reg_status = 'Success'` for `reg_type = register`

Tokens: [token-auth-readme.md](./token-auth-readme.md).

**Current nuance**

- For newly registered users who have not completed password setup, login normally fails first because `is_active = 0`.
- `doi_public_pages_api/api/auth.php` still contains an older branch for `reg_status = Initiated`, but that is not the primary path for the current public registration flow.

---

### Public API operations

| Operation | Purpose |
|-----------|---------|
| `getRegistrationDropdownData` | Form dropdowns |
| `registerUser` | Start registration |
| `verify-register-code` | Complete via API instead of browser page |
| `forgetPassword` | Separate reset flow (`reset-password.php`) |

Listed in `api/root_api.php` `$public_op` (no auth token).

---

## Roles, Rights, And API Access

### What happens today after registration

| Item | Current behavior |
|------|------------------|
| Registration role | Resolved in `UserData::resolveRegistrationRoleIdFromDesignation()` |
| Primary path | `tbl_designation_role_mapping` by `designation_data_key` (+ optional `designation_specific_data_key`) |
| Fallback role | `Stage Gate Data Entry` with `role_type = platform` when no mapping exists |
| Role storage | `tbl_user_master.role_id` (single role per user today) |
| Rights seed | DB trigger `tr_user_right_association_insert` |
| Role-right source | `tbl_role_right_association` |
| User-right table | `tbl_user_right_association` |
| Effective rights at runtime | `PermissionService` |
| Rights payload during login | Loaded from `vw_user_rights` in `doi_public_pages_api` |

### How `PermissionService` works

`PermissionService::hasAccess($userId, $rightKey, $operation)`:

1. loads the user's `role_id`
2. checks `tbl_user_right_association` first for the requested `right_key`
3. if no user-specific row is found, falls back to `tbl_role_right_association`
4. returns `true` only if the requested operation (`create`, `edit`, `view`, `delete`, `submit`, `review`, `approve`) is enabled

This means the platform already has the core pieces needed for **API-level access control**.

### Recommended complete flow for registration + roles + permissions

1. user self-registers and selects a designation
2. system resolves platform role from designation mapping (or fallback)
3. DB trigger seeds the user's effective rights from the role
4. user completes verification and activation
5. login returns token and current rights context
6. each protected API checks rights using backend permission rules
7. frontend menus and screens reflect access, but do not replace backend enforcement

### Recommended access-control direction

The preferred model is **API-level access**, not only module-level visibility.

Why:

- UI hiding alone is not secure
- direct API calls must still be blocked
- backend is the right source of truth for authorization
- existing rights tables and `PermissionService` already support this direction

### What this means in practice

For each protected business area:

1. define a stable `right_key` in `tbl_right_master`
2. map default permissions in `tbl_role_right_association`
3. ensure users inherit those rights through role assignment / seed trigger
4. check access in the API before executing business logic
5. use frontend rights only to improve UX (show / hide navigation, buttons, pages)

### Current gap to be aware of

The registration flow assigns roles today, but the project still needs consistent enforcement and documentation of **which APIs map to which rights**. That is the main pending area, not the registration mechanics themselves.

**Future enhancements (not implemented yet):**

- BO admin CRUD for `tbl_designation_role_mapping`
- `designation_specific_data_key` mappings per sub-designation
- Multi-role users (`tbl_user_roles` + permission union) instead of single `role_id`

---

### Source files

| File | Role |
|------|------|
| `api/userdata.php` | API routing |
| `data_fetchers/class.UserData.php` | Registration logic, designation → role resolution |
| `common/register_email_verification.php` | Tokens, URLs, password completion |
| `api/complete-registration.php` | Page entry |
| `services/Registration/RegistrationPasswordPage.php` | HTML UI |
| `common/mailer.php` | `mailTo()` — direct SMTP for registration / forgot-password |
| `common/class.PermissionService.php` | Runtime permission evaluation |
| `doi_platform_bo_api/database/migrations/2026_07_20_110504_create_tbl_designation_role_mapping_table.php` | Mapping table schema |
| `doi_platform_bo_api/database/migrations/2026_07_20_110505_seed_tbl_designation_role_mapping.php` | Initial designation → role seed data |

---

### Troubleshooting (technical)

| Symptom | Check |
|---------|--------|
| No email | `config_mail.php`, SMTP logs, spam folder |
| 404 on link | `HOSTENV`, `/api` prefix, file deployed |
| Login fails with correct password | Email exact match in DB; `reg_status = Success` |
| Login fails with inactive account | Password setup not completed; `is_active` still `0` |
| Domain rejected | `registration_allowed_email_domains` |
| User logs in but cannot use some APIs | Check `role_id`, trigger-seeded rows, `tbl_role_right_association`, `tbl_user_right_association`, and API right checks |
| Wrong role after registration | `tbl_designation_role_mapping` for user's `designation_data_key`; confirm seed migration ran |
| All new users get `Stage Gate Data Entry` | Mapping missing or inactive; check logs for fallback message |
| `No platform role is configured` | No mapping and fallback role `Stage Gate Data Entry` missing/inactive in `tbl_role_master` |

**Quick SQL check (example):**

```sql
SELECT tld.display_name AS designation,
       rm.role_name,
       rm.role_type,
       drm.is_default,
       drm.priority,
       drm.is_active
FROM tbl_designation_role_mapping drm
JOIN tbl_lookup_detail tld ON tld.data_key = drm.designation_data_key
JOIN tbl_role_master rm ON rm.role_id = drm.role_id
WHERE drm.designation_specific_data_key = ''
ORDER BY tld.display_name;
```

---

## Known gaps / pending alignment

- `org_type`, `user_type`, and `department_id` are required at registration time but are not currently persisted by `registerUser`.
- Fallback to `Stage Gate Data Entry` remains for unmapped designations (interim safety net; remove once all designations are mapped).
- The exact mapping of **every module / API to a specific `right_key`** still needs to be finalized and documented consistently.
- Legacy login / verification behavior still exists in parts of the login service and should be reviewed separately if full cleanup is desired.
- No back-office UI yet to manage designation → role mappings (migrations / DB only).

## Out of scope

- Admin-created users (legacy / non-public registration behavior)
- Full session/token lifecycle beyond login prerequisites
