# @bigscreen/auth

Everything to do with identity: validating that a request came from an authorised application, that it carries a live access token for the right user, and that the user has a suitable access policy for the operation they're asking for. Also owns account creation, password reset, email verification, Steam/Oculus login, and banned-word lists.

**Path:** [auth/](../../auth).
**Package name:** `@bigscreen/auth`.
**Entry:** [auth/index.ts](../../auth/index.ts).

## Exports

```ts
export { Auth } from "./Auth";
export * as AuthApi from "./AuthApi";
export * as AuthDatabase from './AuthDatabase';
export * as AuthSchemas from './Schemas';
export * as Bcrypt from "./Bcrypt";
export { BannedRoomWords } from "./data/BannedRoomWords";
export { NSFWRoomWords } from "./data/NSFWRoomWords";
export * as Email from "./Email";
export * as OculusAPI from "./Oculus";
export * as OAuthScopes from "./OAuthScopes";
export * as OAuthClientDatabase from "./OAuthClientDatabase";
export * as OAuthCodes from "./OAuthCodes";
export * as OAuthTokens from "./OAuthTokens";
```

Steam integration lives alongside the Oculus module; it's imported by `AuthApi` internally. The four `OAuth*` modules back the OAuth 2.0 authorization-server feature (plan 14); see [services/oauth.md](../services/oauth.md) for the end-to-end picture.

## Auth Flow

```mermaid
flowchart TB
    REQ["HTTP request<br/>Authorization: Bearer <app-key><br/>x-access-token: <JWT><br/>x-refresh-token: <token><br/>x-timestamp + x-nonce"] 
    REQ --> H1["Auth.AuthHandler<br/>.authorizeHttpRequest(req)"]
    H1 --> B1{app-key in<br/>allowed_apps<br/>for this apiName?}
    B1 -- no --> E1[AuthError 401<br/>bad bearer]
    B1 -- yes --> H2[attach req.authorizedApp]
    H2 --> NEED{does this route<br/>require an access token?}
    NEED -- no --> OK1[Continue]
    NEED -- yes --> H3[AuthApi.requiresValidAccessToken]
    H3 --> B2{x-access-token<br/>valid JWT?<br/>matches refresh +<br/>timestamp + nonce?}
    B2 -- no --> E2[AuthError 401<br/>bad access token]
    B2 -- yes --> H4[attach req.verifiedAccessToken]
    H4 --> POL{does this route<br/>require a policy?}
    POL -- no --> OK2[Continue]
    POL -- yes --> H5[AuthApi.getAccessPolicyHandler<br/>([AccessPolicy.X, ...])<br/>or requireScopeAndPolicy]
    H5 --> B3{JWT claims include<br/>at least one listed policy<br/>AND required scope if OAuth?}
    B3 -- no --> E3[AuthError 403]
    B3 -- yes --> OK3[Continue]
```

Key header names are defined in [AuthSchemas.HttpHeaders](../../auth/Schemas.ts) — services reference them symbolically, never by string literal.

On `apps/admin_api` there is a second admission path in parallel with the one above: a JWT-shaped bearer is treated as an **OAuth access token**, verified against the Redis whitelist, and cross-checked against the client + grant tables before the same downstream `requireScopeAndPolicy` middleware decides. See [services/oauth.md](../services/oauth.md) for the diagram.

## Access Policies

```mermaid
flowchart TB
    BD[BigscreenDefault<br/>regular user]
    MOD[Moderator]
    CMOD[CommunityModerator]
    ADM[Admin]
    SU[SuperUser]
    ACC_RO[AccountsReadOnly]
    REP[Reporting]

    FAB_RO[FabricatorReadOnly]
    FAB[Fabricator]
    FAB_ADM[FabricatorAdmin]

    INV[Inventory]
    KB[KBAdmin]

    BD --> MOD
    MOD --> ADM
    ADM --> SU
    CMOD --> MOD
    ACC_RO -.subset.-> ADM
    REP -.cross-cutting.-> REP

    FAB_RO --> FAB
    FAB --> FAB_ADM
    FAB_ADM --> SU

    INV -.independent.-> INV
    KB -.independent.-> KB
```

The arrows aren't strict inheritance — policies are claims on the JWT, and most admin endpoints just take a list of accepted policies. But conceptually this is the seniority gradient. The authoritative enum lives in [auth/Schemas.ts](../../auth/Schemas.ts).

Typical route gate (as used by admin-api):

```ts
api.post("/admin/order/:id/fulfil",
    verifyAdminRequest,
    AuthApi.getAccessPolicyHandler([
        AuthSchemas.AccessPolicy.Fabricator,
        AuthSchemas.AccessPolicy.FabricatorAdmin,
        AuthSchemas.AccessPolicy.SuperUser,
    ]),
    FabricatorAdminApi.fulfilOrder,
);
```

## Modules

### `Auth` (class)

`Auth.AuthHandler` is the per-API auth engine. Each service creates one or more:

```ts
const h = Auth.AuthHandler.createAuthHandler({ apiName: "cloud-api" });
```

The `apiName` selects which slice of the `allowed_apps` table / JSON this handler trusts. An app can present one bearer key for `cloud-api` and a different one for `cloud-admin-api` — tests see the full list at [tests/allowed_apps.json](../../tests/allowed_apps.json).

Key methods:

- `authorizeHttpRequest(req)` — validate bearer (app-key) only
- `verifyAccessToken(req)` — validate JWT (signature, expiry, refresh pairing, timestamp, nonce)

Errors surface as `Auth.AuthError` with an `httpCode` property.

### `AuthApi`

The set of request handlers used on `apps/api` (`/auth/*`):

- `createAccountFromEmailPassword`
- `createAccountFromSteamId`
- `createAccountFromOculusId`
- `handleEmailPasswordLogin`
- `renewAccessToken`
- `requiresValidAccessToken` — **middleware**, not a route; put it in front of any route that needs a logged-in user
- `getAccessPolicyHandler([...])` — **middleware factory**, returns a middleware that gates the route on the listed policies
- Account profile CRUD, password reset, email verification

### `AuthDatabase`

PostgreSQL access for accounts, login tokens, linked identities (Steam/Oculus), device history. Services rarely call this directly — `AuthApi` handlers are the usual interface.

### `AuthSchemas`

Types for every auth-facing object:

- `BigscreenAccount` (the canonical account record)
- `AccessTokenPayload` (what's in the JWT)
- `HttpHeaders` (all the named headers)
- `AccessPolicy` (the policy enum)

The monolithic TypeScript types here guide the whole auth contract. If you need a new claim on the JWT, this is where it starts.

### `Bcrypt`

Password-hash helpers. Uses `bcryptjs` (JS implementation — no native dependency).

### `Email`

Postmark integration. Sends password-reset, email-verification, and transactional emails. Template IDs are resolved by environment.

### `OculusAPI` & Steam

Third-party identity validation:

- **Oculus** — exchange a Meta user token for a verified Oculus ID, link or create an account
- **Steam** — classic Steam Web API auth for linking/creating accounts

Used by `AuthApi.createAccountFromOculusId` / `createAccountFromSteamId`.

### `BannedRoomWords` / `NSFWRoomWords`

Static word-lists shipped with the library. Consumed by name validators when users pick a room name or username.

### OAuth modules

These back the OAuth 2.0 authorization-server feature (plan 14). Used by `apps/api` for `/oauth/token` + `/.well-known/*`, by `apps/admin_api` for OAuth-bearer admission and `/admin/oauth/*` client CRUD, and by `webapps/arda` for the consent UI. See [services/oauth.md](../services/oauth.md) for the full picture.

- **`OAuthScopes`** — scope enum (`openid`, `profile`, `admin:all`, plus area pairs like `accounts:read/write`, `fabricator:read/write`, etc.) and `ScopeRequiredPolicies` — the policy-set required to grant each scope. `parseScopeString` / `isValidScope` / `AllScopes` for input handling.
- **`OAuthClientDatabase`** — Postgres CRUD for `oauth_clients`, `oauth_grants`, `oauth_audit_log`. Only confidential clients in v1. Secrets are stored bcrypted; plaintext is returned once on create / rotate. Also exposes the append-only `AuditEventType` enum.
- **`OAuthCodes`** — authorization-code storage under `oauth:code:<code>` in Redis with a 60 s TTL and a single atomic `GETDEL`-based redeem (`redeemAuthorizationCode`). `mintAuthorizationCode` persists the `(clientId, userId, redirectUri, scope, codeChallenge)` payload; arda and `apps/api` both read through this module.
- **`OAuthTokens`** — `/oauth/token` orchestration: `exchangeAuthorizationCode` (PKCE S256 verify → mint access + refresh) and `exchangeRefreshToken` (rotate refresh chain, detect replay → burn chain + revoke all user/client tokens). Scope on new tokens must be ⊆ the refresh's scope; widening is `invalid_scope`.

## Further reading

- End-to-end login sequence → [data-flows.md#1-user-login-emailpassword](../data-flows.md#1-user-login-emailpassword)
- OAuth 2.0 provider surface → [services/oauth.md](../services/oauth.md)
- How services wire the middleware into routes → [services/api.md](../services/api.md), [services/admin-api.md](../services/admin-api.md)
