@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.
- package/package.json +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js +51 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +1 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +22 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/src/__tests__/cf-exec.test.ts +64 -0
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +28 -0
- package/payload/platform/plugins/google/references/auth.md +345 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +5 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +8 -0
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +15 -0
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/server/{chunk-BBSPFN45.js → chunk-FOYHK255.js} +20 -0
- package/payload/server/server.js +11 -4
- package/payload/server/{src-YLGAZY36.js → src-52M7SECF.js} +1 -1
|
@@ -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.
|