@rubytech/create-maxy-code 0.1.465 → 0.1.467

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,345 @@
1
+ # Google Auth Reference
2
+
3
+ ## Status: Phase 0 spike — documentation-backed, not yet token-verified
4
+
5
+ This is the Phase 0 deliverable: the OAuth-flow verdict for a per-account
6
+ **Google Calendar** plugin on a headless install where the operator consents from a phone
7
+ over WhatsApp, with no browser on the box.
8
+
9
+ Scope note: this plugin is **calendar-only**. Mail stays on the `email` plugin's IMAP/SMTP
10
+ path; there is no Gmail API surface. That choice is deliberate and it is what keeps this
11
+ plugin out of Google's restricted-scope security review (see
12
+ ["Verification"](#verification-the-light-kind-not-the-security-assessment)). Contacts
13
+ (People API, read-only) is a possible later addition on the same footing but is not in this
14
+ plugin.
15
+
16
+ Every claim below is grounded in Google's **published** behaviour, cited inline. What is
17
+ **not** yet done is the live token test the task requires: a real token issued (or refused)
18
+ by an actual Google OAuth client for the Calendar scope. Until that runs
19
+ (["Live test that closes the Phase 0 gate"](#live-test-that-closes-the-phase-0-gate)),
20
+ this document is a prediction, not a proof. Do **not** build the register/register-poll
21
+ path against it before the live test confirms the verdict.
22
+
23
+ What is decided from published behaviour:
24
+
25
+ - The **device-authorization grant is rejected** for this plugin. Google's device flow
26
+ serves only a fixed allowlist of scopes; Calendar is not on it.
27
+ - The flow is therefore an **operator-assisted authorization-code (PKCE) flow**, and it
28
+ needs a redirect method, because Google's out-of-band ("paste the code") flow is fully
29
+ blocked.
30
+ - The recommended redirect method is a **hosted HTTPS callback on the brand's existing
31
+ Cloudflare tunnel domain** (a "Web application" client), with a **loopback + manual
32
+ code relay** (a "Desktop app" client) as the secret-less fallback.
33
+ - **Verification is the light kind.** Calendar is a *sensitive* scope, not a *restricted*
34
+ one, so a brand's app needs only OAuth brand/consent-screen verification, never the
35
+ annual third-party security assessment.
36
+
37
+ This diverges from Outlook on one axis: Outlook's device-code flow is secret-less, and the
38
+ recommended Google flow holds a per-brand client secret. That divergence is forced by
39
+ Google, not chosen; the reasoning is below.
40
+
41
+ ---
42
+
43
+ ## Verdict 1: device-code is rejected (scope allowlist)
44
+
45
+ Outlook uses Microsoft's `/devicecode` grant so the operator enters a code on their own
46
+ phone and never needs a browser on the box. The first question for Google was whether the
47
+ same grant works.
48
+
49
+ It does not, for the Calendar scope. Google's OAuth 2.0 flow for TV and limited-input
50
+ devices is supported only for a fixed allowlist. Verbatim from Google's documentation
51
+ ([OAuth 2.0 for TV and Limited-Input Device Applications](https://developers.google.com/identity/protocols/oauth2/limited-input-device)):
52
+
53
+ > The OAuth 2.0 flow for devices is supported only for the following scopes:
54
+ >
55
+ > **OpenID Connect, Google Sign-In:** `email`, `openid`, `profile`
56
+ > **Drive API:** `https://www.googleapis.com/auth/drive.appdata`, `https://www.googleapis.com/auth/drive.file`
57
+ > **YouTube API:** `https://www.googleapis.com/auth/youtube`, `https://www.googleapis.com/auth/youtube.readonly`
58
+
59
+ The Calendar scope (`https://www.googleapis.com/auth/calendar`) is absent. A device-flow
60
+ authorization request that includes it is refused. The device-code grant cannot represent
61
+ this plugin's calendar.
62
+
63
+ **The live test still owes the verbatim refusal.** The task's gate is a real refusal from a
64
+ real client, not this allowlist. The live test below issues a `/device/code` request for
65
+ the Calendar scope against a real client of type "TV and Limited Input" and records the
66
+ exact error body. Expected: a `403`/`invalid_scope`-class refusal naming the scope. The
67
+ allowlist above is the reason to expect it; the recorded response is the proof.
68
+
69
+ ## Verdict 2: OOB ("paste a code") is dead — a redirect is mandatory
70
+
71
+ The other headless-friendly pattern is out-of-band: Google shows the operator a code on a
72
+ web page, the operator pastes it back, no redirect URI. That flow is gone. Google blocked
73
+ new OOB use on 28 Feb 2022 and blocked all existing clients on 31 Jan 2023
74
+ ([OOB migration guide](https://developers.google.com/identity/protocols/oauth2/resources/oob-migration);
75
+ [Google Developers Blog](https://developers.googleblog.com/making-google-oauth-interactions-safer-by-using-more-secure-oauth-flows/)).
76
+ A `redirect_uri=urn:ietf:wg:oauth:2.0:oob` request is refused.
77
+
78
+ So consent must land on a real `redirect_uri`. For an installed/desktop app Google's named
79
+ replacement is the **loopback IP redirect** (`http://127.0.0.1:<port>` or
80
+ `http://localhost:<port>`), and it "requires you to be listening on a local web server
81
+ ... to receive the authorization code" (OOB migration guide). Google's migration guidance
82
+ covers mobile, Chrome, web, and desktop clients; it gives **no** cross-device pattern for
83
+ a headless box where consent happens on a separate phone. That gap is the design problem
84
+ this plugin has to solve itself.
85
+
86
+ ## Verdict 3: the flow — operator-assisted authorization-code (PKCE)
87
+
88
+ Because the operator's browser is on a phone and the plugin is on the box, the redirect
89
+ that carries the `code` and the process that exchanges it live on different machines. Two
90
+ concrete flows bridge that. The recommendation is Option B primary, Option A as the
91
+ secret-less fallback.
92
+
93
+ ### Option B (recommended): hosted HTTPS callback on the brand tunnel domain
94
+
95
+ Client type **Web application**. A stable authorized redirect URI on the install's existing
96
+ Cloudflare **public** host, served by the always-running `platform/ui` server (the same
97
+ process that runs the Outlook auto-poll dispatcher):
98
+
99
+ ```
100
+ https://<brand-public-host>/api/google/oauth/callback
101
+ ```
102
+
103
+ **The `/api/` prefix is mandatory, not cosmetic.** The public host is not a general web
104
+ server: it admits only an allowlist of path prefixes and 404s everything else
105
+ (`platform/ui/server/public-host-gate.ts`, `PUBLIC_ALLOWED_PREFIXES`). A bare
106
+ `/google/oauth/callback` would 404 before reaching any route. Phase 2 must add
107
+ `/api/google/` to that allowlist in the same commit as the callback route.
108
+
109
+ The public host is also the correct *class* of host, and there is an exact in-repo
110
+ precedent. QuickBooks already solved this identical problem, and the allowlist records why:
111
+
112
+ > QuickBooks OAuth consent callback. Intuit redirects the operator's browser here with no
113
+ > admin cookie; the route authenticates by validating the OAuth `state` nonce, not the
114
+ > remote-auth gate (same model as `/api/telegram/`).
115
+
116
+ Google's redirect has the same shape: the client's phone browser arrives with no admin
117
+ cookie, so the route authenticates on the `state` nonce rather than the remote-auth gate.
118
+ Mirror `/api/quickbooks/` rather than inventing a new pattern.
119
+
120
+ ```
121
+ operator (phone) plugin / platform-ui (box) Google
122
+ | | |
123
+ | google-account-register | |
124
+ |--------------------------------------->| build PKCE (verifier+challenge), |
125
+ | | state; write pending-oauth.enc |
126
+ | | build consent URL (access_type= |
127
+ | | offline, prompt=consent, calendar |
128
+ | | scope, redirect=<tunnel>/cb) |
129
+ | { status:"pending", consentUrl } | |
130
+ |<---------------------------------------| |
131
+ | open consentUrl on phone, sign in, | |
132
+ | consent -------------------------------------------------------------------> |
133
+ | | GET /api/google/oauth/callback?code&state <-- redirect
134
+ | | verify state, exchange code+verifier |
135
+ | | (+client secret) ---------------> |
136
+ | | < tokens (access+refresh) |
137
+ | | GET userinfo → account email/id |
138
+ | | encrypt → accounts/<id>/tokens.enc |
139
+ | | clear pending |
140
+ | google-account-register-poll → { registered, email } |
141
+ |<---------------------------------------| |
142
+ ```
143
+
144
+ The operator sees a clean "connected, return to WhatsApp" page; the code is captured
145
+ server-side, never relayed by hand. `register-poll` reports success from local state,
146
+ exactly like Outlook's poll. This is the better ergonomic match for a non-technical
147
+ operator on WhatsApp, which is the whole reason device-code was wanted.
148
+
149
+ Cost: a Web-application client carries a **real client secret** and a per-brand authorized
150
+ redirect URI. The secret is confidential and lives in the brand/account secret store, never
151
+ bundled into the installer. This is the one divergence from Outlook.
152
+
153
+ ### Option A (fallback): loopback client + manual code relay
154
+
155
+ Client type **Desktop app**, redirect `http://127.0.0.1:<port>`. No hosted endpoint, no
156
+ per-brand redirect URI beyond the fixed loopback, and the installed-app secret Google
157
+ issues for desktop clients is documented as non-confidential.
158
+
159
+ The operator opens the consent URL on the phone and consents; Google redirects the phone
160
+ browser to `http://localhost:<port>/?code=...`, which fails to load (nothing listens on the
161
+ phone). The operator copies the `code` value out of the address bar of the failed page and
162
+ sends it back via a `google-account-register-submit` tool; the plugin exchanges it with the
163
+ stored PKCE verifier.
164
+
165
+ Cost: extracting a `code` from a failed-load address bar on a phone is fragile — some mobile
166
+ browsers hide the URL of a failed navigation. Use this only where a brand has no usable
167
+ hosted callback. The live test measures whether this UX is acceptable before it is relied on.
168
+
169
+ ### Why the recommendation, and what the live test decides
170
+
171
+ Option B wins on operator UX and robustness at the cost of a per-brand secret. Option A
172
+ keeps provisioning minimal and secret-light at the cost of a fragile mobile step. The live
173
+ test resolves the open question — that Option B's hosted callback round-trips a real token
174
+ for the Calendar scope — before any register code is built. If the tunnel-hosted callback
175
+ is not viable for a given brand, Option A is the recorded fallback.
176
+
177
+ ---
178
+
179
+ ## Client-id / secret model per brand
180
+
181
+ One vendor-owned Google Cloud OAuth client per brand serves every install and every account
182
+ on that brand, exactly as `outlookClientId` does. The client id ships as the brand default
183
+ `brands/<brand>/brand.json#googleClientId`, stamped onto the session-manager unit env as
184
+ `GOOGLE_CLIENT_ID` and inherited down the spawn chain (manager unit env → PTY → MCP child).
185
+
186
+ Divergence from Outlook: the recommended Web-application flow (Option B) also needs a
187
+ **client secret**. It ships **not** as a plaintext brand default but is held in the brand's
188
+ secret store and stamped as `GOOGLE_CLIENT_SECRET` the same way Cloudflare tokens are held
189
+ (never written to a project tree, never echoed). A brand that ships `googleClientId` empty
190
+ has the plugin inert with an actionable signal (`clientIdConfigured=false`), register
191
+ refuses, and no operator action makes it work until the vendor client exists — mirroring
192
+ Outlook's inert behaviour.
193
+
194
+ ---
195
+
196
+ ## Vendor provisioning runbook (one-time per brand)
197
+
198
+ Run once per brand to mint the client whose id goes in that brand's `brand.json` and whose
199
+ secret goes in the brand secret store. Not an end-user step. Each brand is a separate
200
+ follow-up once the plugin lands (mirroring Outlook 1479/1485).
201
+
202
+ 1. Sign in to the [Google Cloud console](https://console.cloud.google.com) with the brand's
203
+ Google account. Create (or select) one project named for the brand and its Google
204
+ integration, e.g. `SiteDesk Google`.
205
+ 2. Enable the **Google Calendar API** (APIs & Services → Library). That is the only API this
206
+ plugin calls.
207
+ 3. Configure the **OAuth consent screen**: user type **External**, app name and support
208
+ email set to the brand's, and add the single scope below. Publishing status starts at
209
+ **Testing** and moves to **In production** once brand verification is done (see below).
210
+ 4. Create the **OAuth client**:
211
+ - **Option B (recommended):** application type **Web application**. Add the authorized
212
+ redirect URI `https://<brand-public-host>/api/google/oauth/callback` (the `/api/`
213
+ prefix is required — see Option B above). Copy the **Client ID**
214
+ (→ `brand.json#googleClientId`) and **Client secret** (→ brand secret store as
215
+ `GOOGLE_CLIENT_SECRET`; never paste a secret into a chat transcript or the repo).
216
+ - **Option A (fallback):** application type **Desktop app**. Copy the **Client ID**.
217
+ 5. Set `googleClientId` in the brand's `brand.json` and re-bundle:
218
+ ```jsonc
219
+ // brands/<brand>/brand.json
220
+ "googleClientId": "<the Client ID copied above>"
221
+ ```
222
+
223
+ ### Scope
224
+
225
+ One scope, chosen to match the Outlook calendar tool surface (read plus create, update,
226
+ cancel, respond, free-busy), no broader:
227
+
228
+ | Purpose | Scope | Class |
229
+ |---|---|---|
230
+ | Identity (email address, sign-in) | `openid`, `email`, `profile` | non-sensitive |
231
+ | Calendar read + event create/update/cancel/respond/free-busy | `https://www.googleapis.com/auth/calendar` | sensitive |
232
+
233
+ Notes:
234
+
235
+ - `calendar` (full) is the simplest choice for read plus all the control operations. The
236
+ narrower alternative is `calendar.events` + `calendar.readonly` + `calendar.freebusy`; the
237
+ live test can confirm the narrower set is sufficient before Phase 2 fixes it. All Calendar
238
+ scopes are *sensitive*, not *restricted*, so the narrower choice does not change the
239
+ verification path.
240
+ - A refresh token requires `access_type=offline` plus `prompt=consent` on the consent URL;
241
+ this is a request parameter, not a scope.
242
+
243
+ ### Verification: the light kind, not the security assessment
244
+
245
+ The Calendar scope is **sensitive**, not restricted. Google requires a production app that
246
+ uses sensitive scopes to pass OAuth **brand/consent-screen verification** (own the domain,
247
+ Google reviews the app name, logo, and scope justification). It does **not** require the
248
+ annual third-party security assessment — that applies only to *restricted* scopes such as
249
+ the Gmail read/modify family, which this plugin does not use. Dropping the Gmail surface is
250
+ exactly what keeps a brand off the expensive review.
251
+
252
+ The two publishing states, and what each costs:
253
+
254
+ - **Testing** (no verification): works immediately for up to **100 named test accounts**,
255
+ each of which sees the unverified-app warning and re-consents about **every 7 days**
256
+ (Google caps refresh-token lifetime while an app is in Testing). Fine for a pilot,
257
+ including the first `sitedesk-code` account.
258
+ - **In production + brand-verified** (the light verification, no cost, no security firm,
259
+ typically days to a couple of weeks): the 100-account cap lifts, the warning is gone, and
260
+ refresh tokens are long-lived. This is the real deployment state and the target for each
261
+ brand. Start it early per brand, because Google's review is the slow part.
262
+
263
+ ---
264
+
265
+ ## Token store and pending-state layout (informs Phase 1/2, not built here)
266
+
267
+ The store mirrors Outlook's per-account encrypted layout: one AES-256-CBC `.key` per
268
+ account, `tokens.enc` under `accounts/<id>/`, mode 0700 dirs and 0600 files, atomic
269
+ temp+rename writes. This plugin is one Google account per registration, so there is no
270
+ multi-mailbox nesting to mirror.
271
+
272
+ ```
273
+ data/accounts/<accountId>/secrets/google/
274
+ ├── .key # 32 random bytes, base64 — per-account AES-256-CBC key
275
+ ├── accounts/
276
+ │ └── <userId>/tokens.enc # AES-256-CBC token JSON blob per registered Google account
277
+ └── pending-oauth.enc # transient: PKCE verifier + state between register and callback/submit
278
+ ```
279
+
280
+ Two divergences from Outlook are required and must be honoured in Phase 2:
281
+
282
+ - **Pending state is a PKCE verifier + `state`, not a device code.** Register writes it;
283
+ the callback (Option B) or `register-submit` (Option A) reads and clears it. Single-slot
284
+ per account, refuses a second concurrent register with an `in-progress` signal, cleared
285
+ on any terminal outcome.
286
+ - **The concurrent-refresh race must not be reproduced.** Outlook's token writer used a
287
+ fixed `.tmp` name and could wipe valid tokens when two refreshes raced. The
288
+ Google writer uses a unique temp name per write and a refresh catch that never clears
289
+ valid tokens on an IO error. A losing concurrent writer must not wipe credentials.
290
+
291
+ Refresh triggers automatically within 5 minutes of access-token expiry, mirroring Outlook.
292
+ The 7-day Testing-status refresh-token expiry above is separate: it is Google forcing
293
+ re-consent until the brand is verified, not a refresh failure, and the health signal must
294
+ name it distinctly.
295
+
296
+ ---
297
+
298
+ ## Live test that closes the Phase 0 gate
299
+
300
+ Run this once a real Google OAuth client exists, before building the register path. It
301
+ converts the predictions above into recorded facts.
302
+
303
+ **Prerequisite: add test users.** A newly created client sits in **Testing**, where consent
304
+ is refused for anyone not on the consent screen's test-user list — including the operator.
305
+ Add the testing Google account (and any client account used for the trial) before attempting
306
+ step 2, or consent dead-ends with an access-denied screen that looks like a flow bug.
307
+
308
+ **The round-trip does not require the callback route to exist.** The route is Phase 2 code,
309
+ and Phase 0 must not build it. Google still redirects the browser to the callback URL with
310
+ `?code=...` in the query string; the page 404s (the allowlist entry is not added yet) but
311
+ the address bar carries the code. Copying it from there and exchanging it by hand proves
312
+ everything the gate asks about, with no plugin code written.
313
+
314
+ 1. **Authorization-code round-trip (Verdict 3, Option B) — the load-bearing step.** With the
315
+ Web-application client, open the consent URL for the Calendar scope with
316
+ `access_type=offline` and `prompt=consent`, consent as a listed test user, then copy the
317
+ `code` value from the address bar of the 404'd callback page. Exchange it against
318
+ `https://oauth2.googleapis.com/token` with the client id, client secret, the same
319
+ `redirect_uri`, and `grant_type=authorization_code`. Confirm the response carries a
320
+ **refresh token** and the granted `scope` includes the Calendar scope. Run this on the
321
+ device, where the client secret already lives; never paste the secret into a transcript.
322
+ Record the granted scope and the presence of a refresh token (never the tokens
323
+ themselves).
324
+ 2. **Device-code refusal (Verdict 1) — optional, confirmatory only.** Verdict 1 already
325
+ rests on Google's published allowlist, which does not list any Calendar scope. Confirming
326
+ it empirically needs a *second*, throwaway client of type "TV and Limited Input", since a
327
+ Web-application client cannot exercise the device grant. Worth doing only if the recorded
328
+ refusal is wanted for the file; it changes no decision, because step 1 succeeding already
329
+ establishes the flow being built.
330
+ 3. **Narrow-scope check (optional).** Repeat step 1 with `calendar.events` +
331
+ `calendar.readonly` + `calendar.freebusy` instead of `calendar`, and confirm event
332
+ create/update/cancel/respond/free-busy all work, to decide the narrower scope for Phase 2.
333
+
334
+ Paste the recorded outcome into this section. Once step 1 is recorded, the Phase 0 gate is
335
+ met and Phases 1–2 (the plugin build) proceed against the confirmed flow.
336
+
337
+ ### Provisioned clients
338
+
339
+ | Brand | Client type | Redirect URI | Client ID | Status |
340
+ |---|---|---|---|---|
341
+ | `sitedesk-code` | Web application | `https://public.sitedesk.online/api/google/oauth/callback` | `1032860640027-p61l59fnog4mad5o7grginpaps9jnu8c.apps.googleusercontent.com` | Testing — test users pending, round-trip not yet run |
342
+
343
+ The public host was confirmed live and routing to the `platform/ui` server (it serves the
344
+ brand's public page). Secrets are not recorded here; the client secret lives only in the
345
+ brand secret store on the device.
@@ -1 +1 @@
1
- {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"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"}
@@ -3043,6 +3043,11 @@ export function buildHttpApp(deps) {
3043
3043
  brandedNeo4jUri: process.env.NEO4J_URI ?? '',
3044
3044
  brandedNeo4jPassword: process.env.NEO4J_PASSWORD ?? '',
3045
3045
  sliceToken,
3046
+ // Task 1764 — stamp CLAUDE_CODE_REMOTE_ENVIRONMENT_TYPE for operator
3047
+ // surfaces so SendUserFile survives a bridgeless resume, but never for
3048
+ // role='public' spawns (the public session-end review), which Task 1724
3049
+ // scoped out. Mirrors pty-spawner's `role !== 'public'` exclusion.
3050
+ remoteControl: rcRole !== 'public',
3046
3051
  // Task 1466 — deliver the computed house-admin scope into the child env
3047
3052
  // (and thus its installed-plugin MCP children); null on non-house / public
3048
3053
  // / specialist spawns leaves the var unset.