# Plan: CLI-based TOTP Generator for gautam@exulthealthcare.com

Date: 2026-04-10
Authorization: Gautam via iMessage 2026-04-10 10:46 UTC — "Spawn a subagent to setup your own authenticator via CLI"
Account: gautam@exulthealthcare.com (tenant owner, 707a7153-af93-4b65-ae01-bfa6febbffdb)
Goal: Register a SECOND software TOTP authenticator on the account (adds to, does not replace, the existing phone Microsoft Authenticator app) and store the seed locally so the agent can compute 6-digit codes on demand without interrupting Gautam.

## Option Analysis

### Option (a) — User-driven mysignins.microsoft.com flow [VIABLE, RECOMMENDED]
- URL: https://mysignins.microsoft.com/security-info
- Flow: Sign in as gautam@ → "Add sign-in method" → "Authenticator app" → "I want to use a different authenticator app" → Microsoft displays a QR code AND a "Can't scan image?" link that reveals the raw base32 secret + account label.
- We capture the secret string (not the QR image — simpler and exact), base32 is fed directly into any TOTP routine.
- Requires: a logged-in browser session as gautam@. Existing MFA still intercepts, so Gautam must approve the push on his phone once (just like the Azure portal flow already in progress in the main session's other tab).
- Tenant-side requirement: "Microsoft Authenticator" software OATH token method must be enabled in Entra ID → Security → Authentication methods. Default tenants have this on. If disabled, an admin (gautam@ is Global Admin) must enable it first.
- Result: a second registered method. Original phone Authenticator stays active as primary. Nothing is removed.

### Option (b) — Graph API `softwareOathMethods` [BLOCKED]
- Endpoint: `POST https://graph.microsoft.com/v1.0/users/{id}/authentication/softwareOathMethods`
- Required scope: `UserAuthenticationMethod.ReadWrite.All` (application or delegated)
- **Our app-only token scopes (verified via JWT decode 2026-04-10):**
  - Mail.ReadWrite
  - User.ReadWrite.All
  - Calendars.Read
  - User.Read.All
  - Mail.Read
- No `UserAuthenticationMethod.*` role present → endpoint will return 403.
- To unblock: admin (Gautam) would add `UserAuthenticationMethod.ReadWrite.All` application permission to the "Exult Agent Service" app (client_id 6725660a-f83a-4cb0-8892-14a223e0a701) in Entra ID → App registrations → API permissions → Add → Microsoft Graph → Application → UserAuthenticationMethod.ReadWrite.All → Grant admin consent. Still requires Gautam in the Azure portal, so it saves zero clicks vs option (a). Not recommended.

## Execution Plan (Option a)

### Prerequisites
1. Tool install (choose one, both acceptable):
   - `brew install oath-toolkit` → provides `oathtool`
   - OR rely on Python stdlib (hmac/hashlib/base64/struct) — already verified working with RFC 6238 test vector. Zero new deps. **Recommend stdlib** to keep install surface small.
2. Config dir: `mkdir -p /Users/agent/.config/exult && chmod 700 /Users/agent/.config/exult` (already exists per `ls`).
3. Confirm tenant allows software OATH: admin portal → Entra ID → Security → Authentication methods → Policies → "Third-party software OATH tokens" or "Microsoft Authenticator" → Enabled for All users. (If Gautam is already using Microsoft Authenticator push, this is already on.)

### Registration flow (requires coordination with main session)
Gautam or the main-session agent drives a Playwright/Chromium browser signed in as gautam@exulthealthcare.com. Steps:

1. `mcp__plugin_playwright_playwright__browser_navigate` → https://mysignins.microsoft.com/security-info
2. If not logged in: email → password (from 1Password `op://.../gautam@exulthealthcare.com`) → Microsoft Authenticator push to phone → Gautam taps approve.
3. `browser_click` on "Add sign-in method".
4. Select "Authenticator app" in the dropdown → Add.
5. Page will say "Start by getting the app" → Next → "Set up your account" shows the QR code.
6. Click "I want to use a different authenticator app" OR "Can't scan image?" — this reveals the text secret in the form:
   ```
   Account name: Microsoft:gautam@exulthealthcare.com
   Secret key:   XXXX XXXX XXXX XXXX XXXX XXXX XXXX
   ```
7. `browser_snapshot` or DOM extract to capture the secret string (strip spaces, uppercase).
8. Subagent writes the seed to disk:
   ```bash
   umask 077
   printf '%s' "$RAW_SECRET_NO_SPACES" > /Users/agent/.config/exult/totp-gautam.seed
   chmod 600 /Users/agent/.config/exult/totp-gautam.seed
   ```
9. Generate the CURRENT 6-digit code locally using the helper (next section) WITHOUT clicking Next yet.
10. In the browser, click "Next" — Microsoft prompts "Enter the code from your authenticator app". Type the 6-digit code from step 9. Submit.
11. Microsoft confirms "Authenticator app was successfully registered". A new method "Authenticator app" (distinct from the existing push one) appears in the method list.
12. Done. Close the page.

### Helper script: `/Users/agent/.config/exult/totp-gautam.sh`
Create via a separate subagent/command once the seed exists:
```bash
#!/usr/bin/env bash
# Emits the current 6-digit TOTP for gautam@exulthealthcare.com
set -euo pipefail
SEED_FILE="/Users/agent/.config/exult/totp-gautam.seed"
[[ -r "$SEED_FILE" ]] || { echo "missing seed: $SEED_FILE" >&2; exit 2; }
python3 - "$SEED_FILE" <<'PY'
import sys, hmac, hashlib, base64, struct, time
secret = open(sys.argv[1]).read().strip().replace(' ', '').upper()
secret += '=' * ((8 - len(secret) % 8) % 8)
key = base64.b32decode(secret)
counter = struct.pack('>Q', int(time.time()) // 30)
h = hmac.new(key, counter, hashlib.sha1).digest()
o = h[-1] & 0x0f
code = (struct.unpack('>I', h[o:o+4])[0] & 0x7fffffff) % 1000000
print(f'{code:06d}')
PY
```
Permissions: `chmod 700 /Users/agent/.config/exult/totp-gautam.sh`. Usage: `bash /Users/agent/.config/exult/totp-gautam.sh` → prints `123456`.

Optional oathtool variant (if oath-toolkit installed):
```bash
oathtool --totp -b "$(tr -d ' \n' < /Users/agent/.config/exult/totp-gautam.seed)"
```

### Verification
1. Run the helper twice, 31 seconds apart → codes differ.
2. Sign in to https://portal.azure.com as gautam@ in a fresh incognito → at MFA prompt, choose "Use verification code from the authenticator app" → enter helper output → should succeed.
3. Confirm original phone Authenticator still works (do a second login choosing the push method).

### Rollback
If anything goes wrong or the seed leaks:
1. mysignins.microsoft.com/security-info → find the second "Authenticator app" entry → Delete.
2. `shred -u /Users/agent/.config/exult/totp-gautam.seed` (or `rm -P` on macOS).
3. Original phone Authenticator is untouched throughout.

## Security Notes
- Seed file mode 0600, owned by `agent` user only.
- Directory `/Users/agent/.config/exult/` already holds `admin-credentials.json` so threat model is identical.
- This seed grants a factor-2 login for gautam@ (password still required). Treat as high-sensitivity.
- Do NOT commit the seed, do NOT log it, do NOT paste it to iMessage. Memory file references the path but never the value.
- Consider mirroring the seed into 1Password (`op item create --category=login --title="gautam@exult TOTP (agent)" ...`) as backup; skipped in this plan unless Gautam asks.

## Coordination Point
Main session is currently driving the Azure portal in a separate Playwright tab. Executing this plan opens a NEW tab to mysignins.microsoft.com. Main session must either (i) pause and let this subagent drive, or (ii) execute this plan itself after finishing the current Azure portal task. Do not run both concurrently — they share the same browser session cookies and will race the MFA prompt.

## Status
RESEARCH + PLAN ONLY. No browser actions taken. No files written outside this plan doc.
