# HelixtaHR integration contract (Phase 1)

What the HelixtaHR side must provide so the Meetings module can authenticate users, keep organisations apart and remove users who lose access within 5 minutes. Built by the HelixtaHR developer assigned by Amit (requested: Surinder Chahal). Everything here lives in `app/integrations/helixtahr/`; the core never depends on these names.

## 0. What already exists (from the HelixtaHR Postman collection)

- `POST /api/v1/login` returns `data.ai_assist_jwt_token` (plus a Sanctum `token`, which we don't use).
- The Helix AI service calls HelixtaHR endpoints under `/api/v1/ai/...` (e.g. `/ai/leave/balance`, `/ai/leave/apply`, `/ai/regularization/approve`) with `Authorization: Bearer <ai_assist_jwt_token>`; HelixtaHR acts as that user. **Meetings follows the same pattern with its own token and path:** `meeting_jwt_token` on `/api/v1/meetings/...`.

### Token format (`meeting_jwt_token`, delivered 3 Nov 2026)

Our own token, returned by `POST /api/v1/login` next to `ai_assist_jwt_token`. We do not accept the Helix AI token.

- JWT, `alg: HS256`, lifetime 30 days (`exp - iat`), issued by HelixtaHR's login (Laravel jwt-auth).
- Example payload (test server, values illustrative):
```json
{
  "iss": "https://erp-api14.studyineurope.xyz/api/v1/login",
  "iat": 1793701107, "exp": 1796293107, "nbf": 1793701107,
  "jti": "<unique>", "sub": "674", "prv": "<library hash, same for everyone>",
  "company_id": 26, "company_name": "Mehar by Rhysley",
  "role_id": "5", "role_name": "manager", "user_type": "1",
  "user_id": 674, "emp_no": "geo2", "name": "<user name>",
  "token_type": "meeting"
}
```
- **`token_type: "meeting"`** marks it for Meetings (instead of an `aud` claim). The issuer is the same as the Helix AI token, so this check is what tells them apart.
- **Organisation = `company_id`.** Not `company_name` (can change), not `prv` (identical for all users).
- `role_id` values differ between environments → map roles by `role_name`. Org admin = `admin`, `superadmin`.
- `user_type` is not significant (Surinder, 3 Nov); we ignore it.
- Signed with its own secret, not Helix AI's (Surinder, 3 Nov).

### Our verification rules
HS256 only; verify signature with the Meetings token secret; require and check `iss` (per environment), `exp`, `nbf` (30 s leeway), `sub`; require `token_type == "meeting"`, `company_id`, `name`; take user and organisation only from verified claims; token only in the `Authorization` header.

## Meetings allowed (company enablement, delivered 3 Nov 2026)

Set per company in Rhysley's CRM as one of its HelixtaHR usages. Called by the Meetings backend with the **user's own token**; the company comes from the token.

```
GET /api/v1/meetings/allowed
Authorization: Bearer <user's meeting_jwt_token>

200 OK
{ "meetings_allowed": true }
```
- Must return the current CRM value, not the value at login.
- Called fresh at Join now and in the re-check; other requests reuse the answer up to 60 s.
- First measured response: 855 ms in Postman (target well under 300 ms; re-measure).

## Removing users who lose access (decided 3 Nov 2026)

**No separate status endpoint and no notices from HelixtaHR in Phase 1.** Our backend re-checks every connected or waiting user at least every 4 minutes (and fresh at every join) by calling `/api/v1/meetings/allowed` with that user's own token:

```
GET /api/v1/meetings/allowed
Authorization: Bearer <user's meeting_jwt_token>

200 OK   { "meetings_allowed": true, "role_name": "manager" }
401      token invalid, expired, invalidated (signed out everywhere), or user deactivated
```
- `401` → the user lost access → removed. `false` → the company lost Meetings → removed.
- 401 for a deactivated user: confirmed by Surinder (3 Nov).
- `role_name` is the user's **current** role (added by Surinder 3 Nov). Each new session takes the org-admin status from it, since the token's role is fixed for 30 days; if it's missing, the token's role is used.
- Removal is therefore within 5 minutes, not immediate.

## Meetings menu item

Add "Meetings" to the HelixtaHR left navigation (`site_menu`), opening the Meetings interface. How it is shown (frame vs subdomain page) depends on HLD decision 11.

## Values we need per environment (test, live)

| Value | Used for |
|---|---|
| HS256 secret for `meeting_jwt_token` | Verifying tokens |
| Issuer (`iss`) value | Mapping token → HelixtaHR adapter |
| HelixtaHR API base URL | Calling `/api/v1/meetings/allowed` |

Secrets go only into server configuration (`.env` locally, secret store on servers). Never into Git, Jira or chat.

## Phase 2 (not now)
Guest invitations, interviews and scheduled meetings created from HelixtaHR, rescheduling and Host changes, webhooks from Meetings to HelixtaHR (incl. link-disabled), Permanent Meeting Rooms.
