# ADR-018: External Client Authentication Uses Company Code + Access Code, Not Employee PIN

## Status

Accepted, 2026-06-25.

## Status History

```yaml
status_history:
  - date: 2026-06-25
    status: Proposed
    changed_by: hkl
    reason: Formalising separate auth model for external clients vs internal employees
    changed_via: adr-kit (360lm)
  - date: 2026-06-25
    status: Accepted
    changed_by: hkl
    reason: /client/ PWA live with this auth model; employee PIN model unchanged
    changed_via: adr-kit (360lm)
```

## Context

360lm has two distinct user classes: (1) internal employees who use hub PIN auth (ADR-011, ADR-012), and (2) external clients (brand managers, client companies) who need to view activity reports and recce submissions for their campaigns. External clients do not have employee records in the system, do not use the hub, and should not be able to access employee-facing screens. They need a separate, self-service login path that does not require admin PIN assignment.

## Decision

The `/client/` PWA uses a two-tab login model entirely separate from the hub:

- **Tab 1 — Client ID + PIN:** Client accounts stored in `client.accounts` table. Login: client ID + numeric PIN. Used for named client contacts with a direct account.
- **Tab 2 — Company Code (3-step):** Company code → access code → person name + access code. Used for brand/company-level access without individual accounts. Writes an `lm360-activity-session` and redirects to `/activity/`.

Neither tab uses the hub or `hub.verify_pin`. The client session is a separate session key from the employee hub session. Client PWA supports `?next=` redirect (same pattern as ADR-001).

**Decision Maker:** hkl

## Alternatives Considered

- **Give external clients employee PIN accounts.** Rejected: mixes external clients into the employee roster; clients would appear in HR, expense, and tour assignment dropdowns; no way to limit their access to only their own campaign data without complex RLS.
- **Use hub login for clients with a `client` role.** Rejected: hub is designed for employees; adding a client role to hub would require filtering all hub tiles, KPI dashboards, and session reads to exclude clients — high risk of data leakage; cleaner separation is a dedicated `/client/` PWA with its own auth.
- **OAuth (Google login for clients).** Rejected: requires clients to have Google accounts and the company to register a GCP OAuth app; over-engineered for the current number of clients (< 10 companies). // ponytail: upgrade trigger=client count > 50 or clients require SSO via their own identity provider
- **Magic link via email.** Rejected: requires email infrastructure and reliable delivery; some client contacts may not have monitored email; company code model is faster for field staff who share a tablet to show the client.

## Consequences

**Positive:**
- Complete isolation: client session cannot access employee data; employee session cannot be confused with client session.
- Company code model allows field staff to log in on behalf of a client on a shared tablet without the client having an account.
- No hub dependency — client auth works even if hub is temporarily unavailable.

**Negative / Trade-offs:**
- Two auth systems to maintain (hub employee session + client session).
- Client session key (`lm360-activity-session`) must never clash with employee session key.
- New client-facing PWAs must read `lm360-activity-session`, not hub session — easy to get wrong.

**Risks and mitigations:**
- Developer reads wrong session key in a client-facing PWA: accesses employee data or gets null session. Mitigated: client session key is explicitly named `lm360-activity-session`; hub session key is different — check which one the PWA should use before writing auth code.
- Company code guessable: mitigated by access code (second factor) + codes are alphanumeric and rotated per campaign.

## Related Decisions

- ADR-011 (PIN auth) — employee auth model this decision diverges from.
- ADR-012 (hub as SSO) — hub is explicitly NOT used for client auth.
- ADR-001 (hub ?next= redirect) — client PWA uses same ?next= pattern but redirects to /client/, not /hub/.

## References

- `memory/project_arch.md` — /client/ PWA description, two-tab login
- `client/index.html` — client auth implementation
- `activity/index.html` — reads lm360-activity-session set by client login
