# Appendix D — Complete Auth Flow

**Platform login, session, tenant resolution, POS register auth**

---

## Platform Login

**Route:** `POST /api/auth/login` or `POST /api/auth/oauth/login`

**File:** `backend/routes/auth.routes.js` (lines 53–232)

### Step-by-Step

1. **Validate** — email, password present
2. **Master pool** — `dbManager.getMasterPool(DATABASE_TYPES.MAIN)` → `ppos-cloudfresh-dev`
3. **Employee routing** — `getTenantForEmployee(email)` from `tenantEmployeeRouting.service.js`
   - Query master `tenant_employees` WHERE email = ?
   - If found: user is tenant employee → tenant_id, tenant_slug
4. **If tenant employee:**
   - `dbManager.getConnection(tenantSlug, DATABASE_TYPES.MAIN)` → tenant main DB
   - Query `Users` (or `users`) in tenant DB WHERE email = ?
   - Load tenant from master by tenantId
5. **If NOT tenant employee:**
   - Query master `Users` WHERE email = ?
   - Load tenant from master by user.tenantId
6. **Password** — `bcrypt.compare(password, user.password)`
7. **Scopes** — `getUserScopes(user)` — permissions
8. **Session** — `createUserSession(req, { id, email, role, orgId, tenantId, tenantSlug, tenant, scopes, ... })`
9. **Response** — `{ success: true, user: {...}, tenant: {...} }`

### Session Storage

- **Store:** `session-file-store` (or MySQL session store)
- **Path:** `./sessions` (file store)
- **Cookie:** `pawspos.sid`, httpOnly, maxAge 24h, sameSite `lax`
- **Secret:** `SESSION_SECRET` from env

### createUserSession (sessionAuth.js / mysqlSessionAuth.js)

- `req.session.user = { id, email, role, orgId, tenantId, tenantSlug, ... }`
- `req.session.tenant = { id, slug, orgId, plan, ... }`
- `req.user = req.session.user` (set by middleware)

---

## Frontend Post-Login

1. **AuthProvider** / **AuthProviderSimple** receives `{ user, tenant }` from `GET /api/auth/status`
2. **setTenantContext({ tenantId, tenantSlug, orgId })** → **localStorage**
3. **All subsequent API calls** from `client.js` (axios) send:
   - `X-Tenant-Slug`
   - `X-Org-Id`
   - `X-Tenant-ID`
   - Cookie: `pawspos.sid` (session)

---

## Tenant Resolution (Every Request)

**Middleware:** `backend/middleware/tenantResolver.js`

**Order (exact):**

1. **Already resolved** — `req.tenant.orgId` and `req.tenant.slug` exist → skip
2. **Master admin** — `user.role === 'master_admin'` → set tenant from user.orgId, `canAccessAllTenants: true`
3. **Session tenantSlug** — `sessionUser.tenantSlug` → use `req.session.tenant` or `Tenant.findOne({ slug })`
4. **Headers** (only if NO session) — `X-Tenant-Slug` or `X-Org-Id` → lookup tenant
5. **Session tenantId** — `Tenant.findByPk(sessionUser.tenantId)`
6. **Session companyId** — `Tenant.findOne({ companyId })`
7. **Session orgId** — legacy fallback
8. **API key** — `req.apiKey.orgId` or validate `X-Api-Key`
9. **Subdomain** — host subdomain → tenant by slug
10. **Dev fallback** — `DEFAULT_ORG_ID` if `DEV_ALLOW_TENANT_FALLBACK`
11. **Employee portal** — allow proceed without tenant for `/employee-portal/login`
12. **Otherwise** — 401 "missing auth"

**Result:** `req.tenant` = { orgId, tenantId, slug, plan, tenant }

**Note:** `orgId` and `tenantId` are both set from `tenant.id` (master Tenants table). In practice orgId === tenantId.

---

## POS Register Login (Email + PIN)

**Route:** `POST /api/pos/register/authenticate`

**File:** `backend/routes/pos-register.routes.js`

**Middleware:** `app.use('/api/pos', requireSessionAuth, tenantResolver, posRegisterRouter)` in `backend/server.js` — platform session is always required for this path.

**Critical:** Platform session is REQUIRED first. User must log in (email + password) before using POS register.

**Request body:**
```json
{
  "email": "john@acme.com",
  "pin": "1234"
}
```

**Flow:**
1. `req.tenant` from session (tenantResolver)
2. `getTenantConnection(tenantSlug, 'main')` → tenant main DB
3. Query `Staff` WHERE email = ? AND tenantId = ?
4. `bcrypt.compare(pin, staff.posPinHash)`
5. Return `{ success: true, staff }` — response does not mint a **new** session cookie; the existing platform session remains.

**Frontend:** `RegisterLoginScreen` uses `client.post('/api/pos/register/authenticate', { email, pin })`. Client sends session cookie + tenant headers. **`frontend/src/lib/posLocalPersistence.js`** persists the returned `staff` for refresh (PIN session keys scoped by tenant + device). Opening a shift uses **`POST /api/pos/register/open`** with **`staffId`** when the cashier authenticated via PIN so `register_shifts` records the correct staff row. **`GET /api/pos/register/status`** should include **`device_key`** and, when available, **`staff_id`** / **`staffId`** so ownership checks match PIN users. See [Appendix B — Register and shift before cart](./APPENDIX-B-COMPLETE-POS-FLOW.md#register-and-shift-before-cart-modern-pos).

---

## requireSessionAuth

**File:** `backend/middleware/sessionAuth.js`

- Checks `req.session.user` and `req.session.user.id`
- Sets `req.user = req.session.user`
- If missing → 401 "Unauthorized"

---

## optionalSessionAuth

- Always calls `next()`
- Does not block
- Used for kiosk/mobile where device auth may be primary

---

## requireFlexibleAuth

**File:** `backend/middleware/flexibleAuth.js`

- Accepts session OR API key OR other auth
- Used for staff, time routes

---

## Device Auth

**File:** `backend/middleware/deviceAuth.js`

- For `/api/kiosk`, `/api/mobile`
- Validates device key
- Sets device context on request
