# Outlook Auth Reference

## Vendored upstream

The PKCE flow and encrypted token store are adapted from:

- **Repo:** [`nsakki55/outlook-mcp`](https://github.com/nsakki55/outlook-mcp)
- **Pinned commit:** `e49d8e68ac8d69db653c25803127dc3b98626cda`
- **License:** MIT

Adaptations:

- Per-account paths (`{ACCOUNTS_DIR}/{accountId}/secrets/outlook/`) instead of upstream's single-user `~/.outlook-mcp/`
- `keytar` fallback removed (Pi runs headless; no Secret Service)
- `node-persist` removed; single JSON blob written atomically (temp + rename)
- Loopback callback port is **ephemeral** via `server.listen(0)` (upstream uses fixed `49152`)
- No `xdg-open` browser auto-launch; auth URL is logged + returned to the operator for the VNC browser
- `outlook-account-register` awaits the full PKCE flow synchronously (5-min timeout)

To re-vendor at a newer SHA:

1. Fetch `tokenManager.ts` and `authManager.ts` from the new SHA.
2. Apply the adaptations above.
3. Update the **Pinned commit** line above.
4. Bump `@maxy/outlook` version.
5. Run the test suite.

## Entra app registration (one-time per Maxy install)

The plugin requires an Azure AD ("Entra ID") app the operator owns. Steps:

1. Sign in to the [Azure portal](https://portal.azure.com) with a Microsoft account.
2. Go to **Microsoft Entra ID → App registrations → New registration**.
3. **Name:** `Maxy Outlook` (or any name the operator prefers).
4. **Supported account types:** *Accounts in any organizational directory and personal Microsoft accounts*.
5. **Redirect URI** (Platform: *Mobile and desktop applications*): `http://localhost`
   - **Note:** no port is specified. Microsoft Identity Platform treats native-app loopback redirects with port-flexible matching, allowing the plugin to use an ephemeral port.
6. Click **Register**. Copy the **Application (client) ID** — this is `OUTLOOK_CLIENT_ID`.
7. Go to **API permissions → Add a permission → Microsoft Graph → Delegated permissions**, add:
   - `offline_access`
   - `User.Read`
   - `Mail.Read`
   - `Calendars.Read`
   - `Contacts.Read`
8. Click **Grant admin consent for <tenant>** if applicable. Personal accounts auto-consent at first sign-in.
9. Set the env var on the Pi:
   ```
   OUTLOOK_CLIENT_ID=<the Application (client) ID copied above>
   OUTLOOK_TENANT_ID=common   # or your tenant GUID for single-tenant
   ```

## PKCE flow (per-account)

Triggered by `outlook-account-register`. Sequence:

```
operator                                        plugin                              Microsoft
   |                                              |                                     |
   | outlook-account-register                     |                                     |
   |--------------------------------------------->|                                     |
   |                                              | bind 127.0.0.1:<ephemeral-port>     |
   |                                              | generate code_verifier (32 bytes)   |
   |                                              | code_challenge = SHA256(verifier)   |
   |                                              | state = randomBytes(16)             |
   |                                              |                                     |
   | { authUrl, status: "pending" }               |                                     |
   |<---------------------------------------------|                                     |
   |                                              |                                     |
   | open authUrl in VNC browser                  |                                     |
   |--------------------------------------------------------------------------------->  |
   |                                              |                                     |
   |                                  consent ←--------------------------------------- |
   |                                              |                                     |
   |                                              | GET /callback?code=...&state=...   |
   |                                              | <----------------------------------|
   |                                              |                                     |
   |                                              | verify state                        |
   |                                              | POST /token (code + verifier)       |
   |                                              | -----------------------------------> |
   |                                              | < tokens                             |
   |                                              | <----------------------------------- |
   |                                              | encrypt → write {tokens.enc}         |
   |                                              | GET /me  (record graphUserId)        |
   |                                              | -----------------------------------> |
   |                                              |                                     |
   | { status: "registered", graphUserId, ... }   |                                     |
   |<---------------------------------------------|                                     |
```

## Token store layout

Per-account, mode 0700 directory + mode 0600 files:

```
data/accounts/<accountId>/secrets/outlook/
├── .key          # 32 random bytes, base64-encoded — the AES-256-CBC key
└── tokens.enc    # AES-256-CBC ciphertext: "<iv-hex>:<cipher-hex>" of JSON blob
```

The JSON blob shape:

```typescript
{
  accessToken: string;
  refreshToken: string | null;
  accessTokenExpiry: number;        // unix ms
  refreshTokenExpiry: number;       // unix ms; ~90 days from issue
  lastRefresh: number;              // unix ms
  graphUserId: string | null;       // from /me.id
  scopes: string[];                 // granted scopes from token response
}
```

Atomic write: temp + rename. Either old or new tokens, never partial.

## Refresh

Triggered automatically when the access token is within `5 minutes` of expiry (`refreshThresholdMs`). Failure → terminal `token-refresh-failed`, tokens cleared, operator must re-register. Refresh tokens last 90 days.
