everyapp 0.2.2 → 0.3.0-rc.1
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/README.md +6 -77
- package/bin/everyapp.js +235 -0
- package/dist/access-F3GKBKAW.js +1 -0
- package/dist/activity-4MFMH5JP.js +12 -0
- package/dist/apps-63K2DGVT.js +9 -0
- package/dist/chunk-2UFJRKEE.js +4 -0
- package/dist/chunk-3AAHHUJC.js +11 -0
- package/dist/chunk-3Z53CH4F.js +6 -0
- package/dist/chunk-5ZTFC6DK.js +1 -0
- package/dist/chunk-6O3GJS5H.js +15 -0
- package/dist/chunk-74EST4VW.js +2 -0
- package/dist/chunk-7LG3F4FF.js +10 -0
- package/dist/chunk-AUDGOIHE.js +1 -0
- package/dist/chunk-BHMRY53V.js +2 -0
- package/dist/chunk-DQWOXSL3.js +32 -0
- package/dist/chunk-E3EPK27P.js +10 -0
- package/dist/chunk-E7EUXUVD.js +1 -0
- package/dist/chunk-ECLJ5IAL.js +126 -0
- package/dist/chunk-EQX4GKJ2.js +4 -0
- package/dist/chunk-EUNGJMJ6.js +20 -0
- package/dist/chunk-F6IH47GT.js +4 -0
- package/dist/chunk-G64S77GC.js +33 -0
- package/dist/chunk-GD2JZMD3.js +1 -0
- package/dist/chunk-GRFPP4UW.js +7 -0
- package/dist/chunk-IA534DGW.js +10 -0
- package/dist/chunk-IG26HYRR.js +45 -0
- package/dist/chunk-IZEOAOKL.js +6 -0
- package/dist/chunk-JYKRUS5C.js +1 -0
- package/dist/chunk-KMAOZRV4.js +18 -0
- package/dist/chunk-MC56CUQI.js +7 -0
- package/dist/chunk-MDORVBPD.js +1 -0
- package/dist/chunk-N4E5YE4X.js +5 -0
- package/dist/chunk-NJFWEYX7.js +1 -0
- package/dist/chunk-OEOVNJAN.js +1 -0
- package/dist/chunk-OVKVV6MK.js +2 -0
- package/dist/chunk-PL6EORDJ.js +15 -0
- package/dist/chunk-PMPIMHKG.js +6 -0
- package/dist/chunk-QZWSSWMP.js +22 -0
- package/dist/chunk-R4CGF622.js +2 -0
- package/dist/chunk-RFVMR32Y.js +1 -0
- package/dist/chunk-RQFHVU3Y.js +25 -0
- package/dist/chunk-SAT3PGZU.js +57 -0
- package/dist/chunk-SPPR6WCI.js +1 -0
- package/dist/chunk-TPWSGRNT.js +30 -0
- package/dist/chunk-VVQRET35.js +2 -0
- package/dist/chunk-VXJQGJ7Y.js +2 -0
- package/dist/chunk-WEM3WLOC.js +1 -0
- package/dist/chunk-Y3VIFPN5.js +1 -0
- package/dist/chunk-YJBYZWSP.js +22 -0
- package/dist/chunk-ZYL2ZHCF.js +98 -0
- package/dist/configure-2KMHM22T.js +8 -0
- package/dist/create-ELEJMZBS.js +22 -0
- package/dist/db-MZIAKGBN.js +9 -0
- package/dist/delete-H47KPJSB.js +3 -0
- package/dist/deploy-HGFJLLUO.js +1 -0
- package/dist/dev-62VA7UEM.js +86 -0
- package/dist/email-NNFD3VVB.js +10 -0
- package/dist/events-JDY76EG6.js +15 -0
- package/dist/groups-NQFXDZZP.js +8 -0
- package/dist/index.js +92 -1
- package/dist/install-JTZWRZIL.js +1 -0
- package/dist/login-P6LYQQWP.js +1 -0
- package/dist/mcp-NCFWEBMD.js +1 -0
- package/dist/provider-WVBI2VED.js +13 -0
- package/dist/release-6FYSPXXA.js +1 -0
- package/dist/restore-XBU7DOJV.js +3 -0
- package/dist/secret-ML5EXS3O.js +1 -0
- package/dist/setup-IEXCISLP.js +1 -0
- package/dist/status-OPLPKJ4U.js +4 -0
- package/dist/teardown-3JMMLK6O.js +22 -0
- package/dist/update-37ZDATLR.js +1 -0
- package/dist/workspace-DAFZVYQ2.js +21 -0
- package/package.json +30 -27
- package/skills/build-every-app/SKILL.md +199 -0
- package/skills/build-every-app/patterns.md +134 -0
- package/skills/everyapp/SKILL.md +292 -0
- package/skills/everyapp/mcp.md +336 -0
- package/skills/everyapp/security-review.md +61 -0
- package/skills/everyapp/setup/README.md +263 -0
- package/skills/everyapp/setup/access.md +178 -0
- package/skills/everyapp/setup/context.md +117 -0
- package/skills/everyapp/setup/dataforseo.md +287 -0
- package/skills/everyapp/setup/email.md +366 -0
- package/skills/everyapp/setup/gateway.md +243 -0
- package/skills/everyapp/setup/google.md +360 -0
- package/skills/everyapp/setup/microsoft-365.md +281 -0
- package/skills/everyapp/setup/openai.md +318 -0
- package/skills/everyapp/setup/openrouter.md +309 -0
- package/skills/everyapp/setup/providers.md +353 -0
- package/skills/everyapp/setup/remote-mcp.md +199 -0
- package/skills/everyapp/setup/slack.md +344 -0
- package/skills/everyapp/setup/workers-ai.md +138 -0
- package/bin/__everyapp_bash_complete.js +0 -12
- package/dist/bash-complete.js +0 -3
- package/dist/chunk-7BOQJFI5.js +0 -8
- package/dist/chunk-7YVJFS4A.js +0 -1
- package/dist/chunk-BIXKPCDH.js +0 -2
- package/dist/chunk-DLGPLHBH.js +0 -47
- package/dist/chunk-FD6K2YA7.js +0 -9
- package/dist/chunk-GAJEQYJH.js +0 -39
- package/dist/chunk-O2KDOP5R.js +0 -13
- package/dist/chunk-OI2MBGBG.js +0 -4
- package/dist/chunk-POSDLD3G.js +0 -8
- package/dist/chunk-RB5D2R45.js +0 -1
- package/dist/chunk-U4LIQ5VR.js +0 -1
- package/dist/chunk-VEYSNPDH.js +0 -3
- package/dist/chunk-VP6SKW44.js +0 -7
- package/dist/chunk-WO3NE7ZU.js +0 -7
- package/dist/chunk-XTO2VTPY.js +0 -6
- package/dist/chunk-YJ2DKIZS.js +0 -1
- package/dist/create-DYQ3NBWJ.js +0 -22
- package/dist/deploy-PYTCMDGV.js +0 -7
- package/dist/deploy-RMAHOA4Z.js +0 -55
- package/dist/dev-SAE237FS.js +0 -0
- package/dist/generateConfig-46Z3ERF3.js +0 -1
- package/dist/login-B6EF3SMX.js +0 -1
- package/dist/logout-XMKZBLNV.js +0 -1
- package/dist/remoteD1Shell-A67JMO3H.js +0 -6
- package/dist/setupLocal-TZJUW5ES.js +0 -6
- package/gateway/every-app-gateway-build.tar.gz +0 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
# Google (Gmail, Calendar, Search Console and Analytics OAuth client) for the Gateway
|
|
2
|
+
|
|
3
|
+
One OAuth client in a Google Cloud project the person owns, held by the
|
|
4
|
+
Gateway, lets each member of an app connect their own Google account, read
|
|
5
|
+
only: Gmail, Google Calendar, Search Console and Google Analytics, whichever
|
|
6
|
+
of the four an app declares. The Gateway completes Google's token endpoint
|
|
7
|
+
with the client; the app builds the authorize URL from the public client
|
|
8
|
+
id, keeps the member's tokens and reads Google's APIs itself. Open CRM (app
|
|
9
|
+
id `crm`) uses it today, for Gmail and Calendar: Settings → Connections →
|
|
10
|
+
Google Workspace (`/settings/connections/google`). OpenSEO still reads
|
|
11
|
+
Search Console and Analytics with its own Google client and moves onto this
|
|
12
|
+
one after launch (TODO.md "OAuth scopes (2026-10-24)", follow-up 2): until
|
|
13
|
+
an app declares those two, nothing here enables them. At the end the person
|
|
14
|
+
has a Cloud project with the APIs behind the declared sets enabled (for
|
|
15
|
+
Open CRM: the Gmail API and the Google Calendar API), a consent screen
|
|
16
|
+
listing their scopes, a Web application client whose redirect URI is the
|
|
17
|
+
app's callback, and both settings on the Gateway. A Gateway-held token broker for
|
|
18
|
+
organization Google accounts is not built (the direction is in `docs/hosted-vision.md`); the scope sets
|
|
19
|
+
below come from `docs/design/2026-10-oauth-scopes.md`.
|
|
20
|
+
|
|
21
|
+
## Settings
|
|
22
|
+
|
|
23
|
+
| Name | Required | What it holds |
|
|
24
|
+
| -------------------------------- | -------- | --------------------------------------------------------------------------------- |
|
|
25
|
+
| `GOOGLE_WORKSPACE_CLIENT_ID` | required | The client's id, ending in `.apps.googleusercontent.com`; public, not a secret |
|
|
26
|
+
| `GOOGLE_WORKSPACE_CLIENT_SECRET` | required | The client's secret, shown once at creation; starts with `GOCSPX-` in practice (secret scanners key on the prefix; Google does not document the format) |
|
|
27
|
+
|
|
28
|
+
Both belong to the `google-workspace` capability (`packages/sdk/src/internal/capabilities.ts`,
|
|
29
|
+
label "Google (Gmail, Calendar, Search Console and Analytics)"). Since #398
|
|
30
|
+
(2026-10-24) the client carries five read-only scope sets, each one
|
|
31
|
+
provider scope: `identity` (`openid`, `email`, `profile`), `gmail.read`
|
|
32
|
+
(`https://www.googleapis.com/auth/gmail.readonly`), `calendar.read`
|
|
33
|
+
(`…/auth/calendar.readonly`), `search-console.read`
|
|
34
|
+
(`…/auth/webmasters.readonly`) and `analytics.read`
|
|
35
|
+
(`…/auth/analytics.readonly`); no write set exists (the table in
|
|
36
|
+
`docs/capabilities.md`, "OAuth clients"). The console rule: enable the
|
|
37
|
+
APIs and allow on the consent screen the union of the provider scopes
|
|
38
|
+
behind the sets of every app you approve, not the registry's full list. A
|
|
39
|
+
scope no app asks for needn't be on the consent screen, and Google's
|
|
40
|
+
verification follows what is listed: `gmail.readonly` is restricted;
|
|
41
|
+
`webmasters.readonly` and `analytics.readonly` are sensitive (unverified:
|
|
42
|
+
Google publishes no list, the Data Access page labels each scope once
|
|
43
|
+
added). For the launch that is Open CRM: `gmail.readonly` and
|
|
44
|
+
`calendar.readonly`, with the Gmail API and the Google Calendar API
|
|
45
|
+
enabled. Add `webmasters.readonly` and `analytics.readonly`, and enable the
|
|
46
|
+
Search Console API and the Analytics Admin and Data APIs, only when an app
|
|
47
|
+
declares those sets (OpenSEO will, after launch).
|
|
48
|
+
|
|
49
|
+
An app names its sets in its manifest,
|
|
50
|
+
`"google-workspace": { access: "write", scopes: ["gmail.read", "calendar.read"], why }`
|
|
51
|
+
(#414; `everyapp deploy` refuses the entry without `scopes`), and the
|
|
52
|
+
Gateway holds it to them at every token exchange and refresh (#411, the
|
|
53
|
+
design's §5): a token whose granted scopes exceed the sets is refused
|
|
54
|
+
(`integration_scope_exceeded`, 403), so apps must not send
|
|
55
|
+
`include_granted_scopes=true` (Google then makes the token cover every
|
|
56
|
+
scope the person has granted the Cloud project, and a wider grant to
|
|
57
|
+
another app on the same project gets the narrower app refused until they
|
|
58
|
+
reconnect without the flag); a deployed manifest without `scopes` is
|
|
59
|
+
refused too (`integration_scopes_undeclared`, 403). Open CRM declares
|
|
60
|
+
both sets and sends no flag; a CRM deployed before that must redeploy
|
|
61
|
+
before the Gateway is updated from main (TODO.md "OAuth scopes
|
|
62
|
+
(2026-10-24)", follow-up 0). Redirect URI:
|
|
63
|
+
`https://<app>.<app base domain>/integrations/google/callback` for every
|
|
64
|
+
app that lists the capability; for Open CRM,
|
|
65
|
+
`https://crm.<app base domain>/integrations/google/callback`.
|
|
66
|
+
|
|
67
|
+
## Costs and plans
|
|
68
|
+
|
|
69
|
+
As of October 2026: a Google Cloud project is free and needs no billing
|
|
70
|
+
account for this. Standard use of the Gmail API and the Google Calendar API
|
|
71
|
+
is free; Google plans to charge above the quota limits later in 2026. Limits:
|
|
72
|
+
Gmail 80,000,000 quota units per day per project, 6,000 per minute per user;
|
|
73
|
+
Calendar 1,000,000 requests per day per project, 600 per minute per user;
|
|
74
|
+
100 live refresh tokens per Google Account per client (the oldest is dropped
|
|
75
|
+
silently). The Search Console API and the Analytics Data API list quotas
|
|
76
|
+
and no price (Search Console 1,200 search-analytics queries per minute per
|
|
77
|
+
site and per user; Analytics Data 200,000 tokens per property per day on a
|
|
78
|
+
standard property): they matter only once an app declares those sets.
|
|
79
|
+
*Internal* needs the project in a Google Workspace or Cloud
|
|
80
|
+
Identity organization: Workspace Starter lists at about $7 per user per
|
|
81
|
+
month, or Cloud Identity Free costs nothing and creates the organization
|
|
82
|
+
once the person verifies a domain they own (a DNS TXT record, recognized
|
|
83
|
+
within 72 hours; then continue at step 2). *External* with `gmail.readonly`
|
|
84
|
+
needs Google's restricted-scope verification (several weeks of review, by
|
|
85
|
+
email) plus a yearly CASA security assessment by a third-party assessor:
|
|
86
|
+
Google's Health page quotes $500 to $4,500 depending on the app's
|
|
87
|
+
complexity, and 2 to 3 weeks for the lower tier, 4 to 6 for the higher
|
|
88
|
+
(the CASA page now calls them AL1 and AL2). Sensitive scopes alone (Search
|
|
89
|
+
Console and Analytics without `gmail.read`) need the verification and no
|
|
90
|
+
assessment.
|
|
91
|
+
|
|
92
|
+
## Only a person can
|
|
93
|
+
|
|
94
|
+
- Sign in to Google, create the Cloud project and pick its organization.
|
|
95
|
+
- Accept the Google API Services User Data Policy on the consent screen.
|
|
96
|
+
- Create the OAuth client and download its JSON: no API or gcloud command
|
|
97
|
+
creates a Web application client (step 6).
|
|
98
|
+
- Add test users, publish the app, submit verification, pay an assessor.
|
|
99
|
+
- Click Connect in Open CRM and approve the consent screen with their account.
|
|
100
|
+
- Allow the app in the Workspace admin console when `admin_policy_enforced`.
|
|
101
|
+
|
|
102
|
+
## Before you start
|
|
103
|
+
|
|
104
|
+
1. `pnpm everyapp secret list crm --json`, from any directory (the id reads
|
|
105
|
+
the manifest Open CRM's last deploy stored; in its directory the id can
|
|
106
|
+
be left off), one call, before any Google step. The rule: read
|
|
107
|
+
`data.app.setup.settings`, `unmet` and `command`. The client is one
|
|
108
|
+
setting there, `id: "google-workspace"`, `kind: "capability"`, `holder:
|
|
109
|
+
"gateway"`, `names: ["GOOGLE_WORKSPACE_CLIENT_ID",
|
|
110
|
+
"GOOGLE_WORKSPACE_CLIENT_SECRET"]` (an OAuth client's id and secret are
|
|
111
|
+
the two names of one setting, set only when both are), `need.kind` as
|
|
112
|
+
the CRM's manifest declares it (Open CRM: `optional`: it sits in
|
|
113
|
+
`settings` with `set: false`, never in `unmet`, the CRM opens without
|
|
114
|
+
Gmail and Calendar and `command`, `pnpm everyapp configure crm`, offers
|
|
115
|
+
it after the required ones; `required` would put it in `unmet`, close
|
|
116
|
+
the CRM and have configure ask for both names first), `setup:
|
|
117
|
+
"setup/google.md"` (this file). While its `set` is false this file
|
|
118
|
+
applies. The same entry in
|
|
119
|
+
`data.app.setup.settings` with `set: true`: ask before replacing (a new
|
|
120
|
+
secret for the same client is harmless, see Rotate; a different client
|
|
121
|
+
id makes every member reconnect Gmail and Calendar).
|
|
122
|
+
`TOKEN_ENCRYPTION_KEY`, the CRM's own secret that connecting needs, is
|
|
123
|
+
in `settings` too (`holder: "app"`; optional, so never in `unmet`): the
|
|
124
|
+
CRM's admin sets it after the CRM's first deploy, at the hidden prompt
|
|
125
|
+
of `pnpm everyapp configure crm` (which offers it with the optional
|
|
126
|
+
ones) or `pnpm everyapp secret set TOKEN_ENCRYPTION_KEY --app crm`, from
|
|
127
|
+
anywhere, 32 or more random characters (the CRM refuses shorter); a
|
|
128
|
+
missing key shows at step 9, the table's last row. `APP_NOT_FOUND`:
|
|
129
|
+
deploy Open CRM first (its callback must answer before anyone
|
|
130
|
+
connects).
|
|
131
|
+
2. `pnpm everyapp status --json`: `data.installed` and `data.claimed` true,
|
|
132
|
+
`data.appBaseDomain` is `<app base domain>` (step 1 builds the redirect
|
|
133
|
+
URI from it), and `data.apps[]` has `id: "crm"` with `status: "active"`
|
|
134
|
+
and `bound: true`. Its `setup` is 1's `data.app.setup` again and says
|
|
135
|
+
what else the CRM needs; not installed, or no owner:
|
|
136
|
+
[gateway.md](gateway.md) first.
|
|
137
|
+
3. Ask whether a Cloud project or a Web client exists for this already:
|
|
138
|
+
then step 6 adds the redirect URI to it. Never reuse OpenSEO's verified
|
|
139
|
+
client (the design doc's rule). The sets the apps declare decide steps
|
|
140
|
+
3 and 5: today Open CRM, `gmail.read` and `calendar.read`; Search
|
|
141
|
+
Console and Analytics only for an app that declares them.
|
|
142
|
+
4. Ask whether every member who will connect is in one Google Workspace
|
|
143
|
+
organization. Yes: *Internal*, no verification. No: *External* (step 7).
|
|
144
|
+
A member on a gmail.com account is outside any organization: *External*.
|
|
145
|
+
Wait for both answers before giving the steps: they decide step 2's
|
|
146
|
+
Location, step 4's Audience and whether step 7 applies.
|
|
147
|
+
|
|
148
|
+
## Steps
|
|
149
|
+
|
|
150
|
+
Once the audience is known, steps 2 to 6 (and 7 for *External*) can go to
|
|
151
|
+
the person in one message, with step 1's URI; then wait for the file
|
|
152
|
+
before step 8.
|
|
153
|
+
|
|
154
|
+
1. **You:** work out the redirect URI from `data.appBaseDomain`:
|
|
155
|
+
`https://crm.<app base domain>/integrations/google/callback`. Open CRM's
|
|
156
|
+
Settings → Connections → Google Workspace page, signed in as an owner or
|
|
157
|
+
admin (members see no setup rows), shows the same value under "Redirect
|
|
158
|
+
URI" while the Gateway holds no client. Give it to the person for step 6.
|
|
159
|
+
2. **Person:** signed in to Google with the Workspace account (a gmail.com
|
|
160
|
+
account shows no organization), create the project at
|
|
161
|
+
https://console.cloud.google.com/projectcreate: Project name (for example
|
|
162
|
+
`Every App`), Location → Browse → the Workspace organization (needed for
|
|
163
|
+
*Internal*; "No organization" otherwise) → Create. Done when the
|
|
164
|
+
project's dashboard opens with its name in the top bar. With gcloud
|
|
165
|
+
(checked against the reference today, not run here): the person runs
|
|
166
|
+
`gcloud auth login` (opens a browser), then
|
|
167
|
+
`gcloud projects create every-app-gateway-<4 digits> --name="Every App" --set-as-default`
|
|
168
|
+
(any unused id: ids are global and never reusable, so a taken one is
|
|
169
|
+
refused; add `--organization=ORGANIZATION_ID` for *Internal*, the number
|
|
170
|
+
from `gcloud organizations list`).
|
|
171
|
+
3. **Person:** enable the APIs behind the declared sets; for Open CRM, two:
|
|
172
|
+
open
|
|
173
|
+
https://console.cloud.google.com/apis/enableflow;apiid=gmail.googleapis.com
|
|
174
|
+
→ Enable, then
|
|
175
|
+
https://console.cloud.google.com/apis/enableflow;apiid=calendar-json.googleapis.com
|
|
176
|
+
→ Enable (or APIs & Services → Library → Google Workspace → each API →
|
|
177
|
+
Enable). Done when each shows "API Enabled". With gcloud (checked against
|
|
178
|
+
the reference): `gcloud services enable gmail.googleapis.com calendar-json.googleapis.com`.
|
|
179
|
+
Only for an app that declares `search-console.read` or `analytics.read`,
|
|
180
|
+
the same with `searchconsole.googleapis.com` (Google Search Console
|
|
181
|
+
API), `analyticsadmin.googleapis.com` (Google Analytics Admin API) and
|
|
182
|
+
`analyticsdata.googleapis.com` (Google Analytics Data API): the service
|
|
183
|
+
ids are the APIs' own hosts (their discovery documents, Sources).
|
|
184
|
+
4. **Person:** configure the consent screen at
|
|
185
|
+
https://console.cloud.google.com/auth/overview → Get Started. App
|
|
186
|
+
Information: App name (what members see, for example `Every App`), User
|
|
187
|
+
support email → Next. Audience: *Internal* (only that Workspace's
|
|
188
|
+
accounts, no verification) or *External* → Next. Contact Information: an
|
|
189
|
+
email for Google's notices → Next. Finish: agree to the User Data Policy
|
|
190
|
+
→ Continue → Create. Done when the Google Auth platform overview shows
|
|
191
|
+
the app.
|
|
192
|
+
5. **Person:** add the scopes on the Data Access page,
|
|
193
|
+
https://console.cloud.google.com/auth/scopes → Add or remove scopes →
|
|
194
|
+
filter `gmail.readonly` and `calendar.readonly`, tick both → Update →
|
|
195
|
+
Save; `webmasters.readonly` and `analytics.readonly` too, only for an
|
|
196
|
+
app that declares those sets, never a scope no app asks for. Only the
|
|
197
|
+
scopes of enabled APIs are listed: a missing one means step 3, or paste
|
|
198
|
+
its URL under "Manually add scopes". `gmail.readonly` is a restricted
|
|
199
|
+
scope, `calendar.readonly` a sensitive one (the page labels the other
|
|
200
|
+
two once added). Done when each is listed under "Your restricted
|
|
201
|
+
scopes" or "Your sensitive scopes".
|
|
202
|
+
6. **Person:** create the client at https://console.cloud.google.com/auth/clients
|
|
203
|
+
→ Create client → Application type: Web application → Name (for example
|
|
204
|
+
`Every App Gateway`) → Authorized redirect URIs → Add URI → paste the exact
|
|
205
|
+
URI from step 1 → Create. No Authorized JavaScript origins. A dialog shows
|
|
206
|
+
the Client ID and the Client secret once (afterwards only its last four
|
|
207
|
+
characters). Click Download JSON; browsers save it as
|
|
208
|
+
`client_secret_….json`, in `~/Downloads` unless set otherwise, outside
|
|
209
|
+
the repository. The person tells you it is there, and where when it
|
|
210
|
+
isn't in `~/Downloads`. No command does this: the IAP OAuth Admin API that
|
|
211
|
+
created clients was shut down on 2026-03-19, and
|
|
212
|
+
`gcloud iam oauth-clients create` makes Workforce Identity Federation
|
|
213
|
+
clients, not Google Account clients. URI changes take 5 minutes to a few
|
|
214
|
+
hours to apply.
|
|
215
|
+
7. **Person,** *External* only: https://console.cloud.google.com/auth/audience
|
|
216
|
+
→ Test users → Add users → each member's Google email (up to 100) → Save.
|
|
217
|
+
In Testing every consent expires after 7 days: members reconnect weekly.
|
|
218
|
+
Publish app moves to In production: the unverified-app screen and a
|
|
219
|
+
lifetime cap of 100 new users until verification (plus the CASA
|
|
220
|
+
assessment for `gmail.readonly`) passes. Verification (after Publish
|
|
221
|
+
app: Prepare for Verification → Submit for Verification) needs a home
|
|
222
|
+
page and a privacy policy on a domain the person owns, listed under
|
|
223
|
+
Authorized domains and verified in Search Console by a project owner or
|
|
224
|
+
editor, a demo video of the consent flow and a written scope
|
|
225
|
+
justification; Google answers by email over several weeks. Until then
|
|
226
|
+
stay in Testing (weekly reconnects, 100 test users); the run resumes at
|
|
227
|
+
Verify when Google's mail comes. Skip for *Internal*.
|
|
228
|
+
8. **You,** only after the person says the file is there: the README's "a
|
|
229
|
+
command produced it" way, pipe each value from the file, never read the
|
|
230
|
+
file yourself. `ls ~/Downloads/client_secret_*.json` (or the folder they
|
|
231
|
+
named): more than one file, ask which (the newest:
|
|
232
|
+
`ls -t ~/Downloads/client_secret_*.json | head -1`), and use that one
|
|
233
|
+
path below, never the glob (`jq` prints one value per matching file).
|
|
234
|
+
`jq -e 'has("web")' <path>` (`true`; `false` is a Desktop client's file:
|
|
235
|
+
step 6 again). Then
|
|
236
|
+
`jq -r .web.client_id <path> | pnpm everyapp secret set GOOGLE_WORKSPACE_CLIENT_ID --json`
|
|
237
|
+
and
|
|
238
|
+
`jq -r .web.client_secret <path> | pnpm everyapp secret set GOOGLE_WORKSPACE_CLIENT_SECRET --json`.
|
|
239
|
+
Each ends with a `done` event whose `data.gateway.names` holds the name
|
|
240
|
+
(`client_id`, `client_secret`, `redirect_uris`, `auth_uri` and
|
|
241
|
+
`token_uri` are all mandatory under `web`). Then tell the person the
|
|
242
|
+
file still holds the secret and ask them to delete it (or ask before
|
|
243
|
+
running `rm` on it).
|
|
244
|
+
Alternative, the person types it: in their own terminal in the every-app
|
|
245
|
+
folder, `pnpm everyapp configure crm` (it asks for the client's two
|
|
246
|
+
names, id then secret, with hidden prompts, after the CRM's required
|
|
247
|
+
settings; the client is optional for Open CRM, so Yes to `Set the N
|
|
248
|
+
optional settings too?`, Enter skipping the others; with one of
|
|
249
|
+
the two names set it asks only for the other; or pick
|
|
250
|
+
`GOOGLE_WORKSPACE_CLIENT_ID + GOOGLE_WORKSPACE_CLIENT_SECRET` from the
|
|
251
|
+
menu once the CRM is ready) or
|
|
252
|
+
`pnpm everyapp secret set GOOGLE_WORKSPACE_CLIENT_ID GOOGLE_WORKSPACE_CLIENT_SECRET`
|
|
253
|
+
(two hidden prompts, just these), then they tell you.
|
|
254
|
+
9. **Person,** once `data.app.setup.ready` is true: while another required
|
|
255
|
+
setting of the CRM is unmet, the Gateway shows the CRM as "Needs setup"
|
|
256
|
+
and opens its setup dialog instead of the app, so finish those first
|
|
257
|
+
(each `unmet` entry's `setup` names its file); the Google client itself
|
|
258
|
+
is verified by Verify 1 meanwhile. Then open
|
|
259
|
+
https://crm.<app base domain>/settings/connections/google
|
|
260
|
+
(Settings → Connections → Google Workspace) → Connect → choose the
|
|
261
|
+
account → (Testing: "Google hasn't verified this app" → Advanced → Go to
|
|
262
|
+
Every App (unsafe)) → Allow. Done when the page shows the "Connected"
|
|
263
|
+
badge with their address. Wait for that.
|
|
264
|
+
|
|
265
|
+
## Verify
|
|
266
|
+
|
|
267
|
+
1. `pnpm everyapp secret list crm --json`, from anywhere: the
|
|
268
|
+
`google-workspace` setting has `set: true` (optional for Open CRM, so
|
|
269
|
+
it was never in `data.app.setup.unmet`; a manifest that requires it has
|
|
270
|
+
it leave `unmet`, with `ready: true` and `command` null once nothing
|
|
271
|
+
else is missing); both names in `data.gateway.names`,
|
|
272
|
+
their `data.gateway.needed` entries `"set": true`, and
|
|
273
|
+
`data.gateway.missing` doesn't list them. Then
|
|
274
|
+
`pnpm everyapp status --json`: the `crm` entry's `setup.ready` is true
|
|
275
|
+
and `data.fixes` has no `APP_NEEDS_SETUP` for it (the Gateway opens the
|
|
276
|
+
CRM again).
|
|
277
|
+
2. Smoke check, in the person's browser. 2 and 3 need the CRM open: 1's
|
|
278
|
+
`data.app.setup.ready` true. While another required setting is unmet,
|
|
279
|
+
the Gateway sends a browser to its setup dialog instead of the CRM
|
|
280
|
+
(anything else gets 503 `app_needs_setup`): finish those first (each
|
|
281
|
+
`unmet` entry's `setup` names its file); until then 1 is the check of
|
|
282
|
+
the Google client. Then, signed in as an owner or admin (no secret
|
|
283
|
+
involved): Open CRM's Settings → Connections → Google Workspace shows a
|
|
284
|
+
Connect button and the "Not connected" badge instead of "Not
|
|
285
|
+
configured" with the "Redirect URI" and "Command" rows (a plain member
|
|
286
|
+
never sees those rows). The button means the Gateway answered
|
|
287
|
+
`ready: true` with the client id for `google-workspace`.
|
|
288
|
+
3. App check: step 9. Google's account chooser means the client id and
|
|
289
|
+
redirect URI match; the "Connected" badge means the Gateway completed
|
|
290
|
+
`POST oauth2.googleapis.com/token` with the secret. Mail and meetings
|
|
291
|
+
appear on companies after the first sync.
|
|
292
|
+
4. Logs: the dashboard, README's path:
|
|
293
|
+
**Workers & Pages** → `every-app-gateway` → **Observability** (**Logs** on older accounts) → **Live**
|
|
294
|
+
(the stored Workers Logs are on the same page); filter
|
|
295
|
+
`capability: "google-workspace"`. Each token call is one
|
|
296
|
+
`integration.proxy.call` line with `grantType` (`authorization_code` on
|
|
297
|
+
connect, `refresh_token` on refresh) and `status`, never the body.
|
|
298
|
+
|
|
299
|
+
## If it fails
|
|
300
|
+
|
|
301
|
+
| Error or symptom | Meaning | What to do |
|
|
302
|
+
| --- | --- | --- |
|
|
303
|
+
| `SECRET_VALUE_REQUIRED` | Nothing was piped and there is no terminal | Use the pipe in step 8 or the person's terminal |
|
|
304
|
+
| `SECRET_VALUE_REQUIRED` from `pnpm everyapp configure crm --json` | Nothing was piped (with `--from-env`, stdin isn't read), no variable of a missing name was found, and there is no terminal; the message names what the CRM still needs (this client is optional for Open CRM, so never among them) | The pipes in step 8, or the person's terminal (step 8's alternative) |
|
|
305
|
+
| `SECRET_SEVERAL_NAMES` | A pipe or `--from-env` with two names | One `secret set` per name |
|
|
306
|
+
| `SECRET_NOT_DECLARED` with `data.didYouMean` | Name typed wrong | rerun with the name in `data.didYouMean` and the same value source (`--from-env VAR --json`, or the same pipe); `data.rerun` carries only the names, so run as is it stops with `SECRET_VALUE_REQUIRED` |
|
|
307
|
+
| `has("web")` prints `false`; `jq -r .web.client_id` prints `null` | A Desktop client's file (`installed` key); `null` must never be piped | Create a Web application client; unset anything set from it |
|
|
308
|
+
| App: "Not configured", "Redirect URI" and "Command" rows, no Connect | The Gateway's status for `google-workspace` answers `ready: false`: it holds no client (or one name only), or the deployed manifest lists the client without `scopes` | Step 8 (or add `scopes` and deploy); then reload |
|
|
309
|
+
| `integration_not_configured` (503) in the app's logs | A token call while the Gateway holds no client, or the name is wrong | `secret list --json`; set both names |
|
|
310
|
+
| `capability_not_requested` (403) | The deployed manifest doesn't list `google-workspace` | Add `"google-workspace": { access: "write", scopes: ["gmail.read", "calendar.read"], why }` to everyapp.config.ts, deploy |
|
|
311
|
+
| `integration_scopes_undeclared` (403) | The deployed manifest lists the client without `scopes` (a deploy from before #414) | Add `scopes: ["gmail.read", "calendar.read"]` to the entry, deploy |
|
|
312
|
+
| `integration_scope_exceeded` (403) | Google granted more than the app's sets: the app asked for a scope outside them, or sent `include_granted_scopes=true` and the member had granted a wider scope to another app on this project | The app asks for only the status's scopes without the flag and the member connects again, or the set is added to everyapp.config.ts and deployed; the token was dropped, nothing to revoke |
|
|
313
|
+
| `integration_path_not_allowed` (403) | The app called something other than `POST /token` through the Gateway | App bug: API calls go to Google directly |
|
|
314
|
+
| Google "Error 400: redirect_uri_mismatch" | The URI on the client differs from step 1 | Fix it on the client; wait up to a few hours |
|
|
315
|
+
| Google "Error 401: invalid_client" | Client id or secret wrong, or secret disabled | Rotate (below) and set both again |
|
|
316
|
+
| Google "Error 403: access_denied" | The member is not a test user (Testing) | Add them in step 7, retry |
|
|
317
|
+
| App: "You didn't give Google access." (`error=access_denied` in the address bar) | The member clicked Cancel on the consent screen | Connect again |
|
|
318
|
+
| App: "Google returned an error. Try again." (`error=exchange_failed`) | Google refused with any code but a decline (`admin_policy_enforced`, `org_internal`, a failed token exchange); the CRM's logs say `[mailbox] code exchange failed` with a reason | Read the code in the CRM's logs or on Google's page, then the rows below |
|
|
319
|
+
| Google "Error 400: admin_policy_enforced" | The Workspace admin restricts the scopes or the app | A Workspace admin (Service Settings privilege): Admin console → Security → Access and data control → API controls (admin.google.com/ac/owl) → Manage App Access → Configure new app → search the client id → Continue → Trusted → Continue → Finish |
|
|
320
|
+
| Google "Error 403: org_internal" | *Internal* audience, account outside the organization | Use a Workspace account, or switch to *External* |
|
|
321
|
+
| "Google hasn't verified this app" | *External*, unverified, sensitive or restricted scopes | Advanced → Go to Every App (unsafe), or verify |
|
|
322
|
+
| Sync stops after 7 days, `invalid_grant` | *External* in Testing: consent expired | Reconnect; publish and verify to stop it |
|
|
323
|
+
| `403 accessNotConfigured` ("has not been used in project") | Gmail or Calendar API not enabled in that project | Step 3 in the project the error names; wait a minute |
|
|
324
|
+
| Google "Error 401: deleted_client" | The client was deleted (30-day restore window) | Restore it under Deleted credentials, or make a new one |
|
|
325
|
+
| Connect fails before Google opens | Open CRM's `TOKEN_ENCRYPTION_KEY` unset | Set it after the CRM's first deploy: `pnpm everyapp configure crm` (which asks for it) or `pnpm everyapp secret set TOKEN_ENCRYPTION_KEY --app crm` from anywhere (in the CRM's directory the flag can be left off), the value in one of README's three ways, 32 or more random characters; connect again |
|
|
326
|
+
|
|
327
|
+
## Rerun, rotate, remove
|
|
328
|
+
|
|
329
|
+
- Rerun: every step is safe to repeat. `configure` and `secret set`
|
|
330
|
+
replace; the console shows what exists. After an interruption start at
|
|
331
|
+
Verify and continue at the first step that isn't done.
|
|
332
|
+
- Rotate the secret, never the id: a new client id invalidates every
|
|
333
|
+
member's refresh token. Person: Clients → the client → Add secret (two at
|
|
334
|
+
most; both work until one is disabled); the new secret is shown once, and
|
|
335
|
+
they type it at the hidden prompt in their terminal
|
|
336
|
+
(`pnpm everyapp secret set GOOGLE_WORKSPACE_CLIENT_SECRET`). You verify
|
|
337
|
+
with `secret list --json`; they test Connect; then they Disable the old
|
|
338
|
+
secret, test again, and Delete it.
|
|
339
|
+
- Remove: `pnpm everyapp secret unset GOOGLE_WORKSPACE_CLIENT_ID GOOGLE_WORKSPACE_CLIENT_SECRET --json`
|
|
340
|
+
(add `--yes` only when the person said so); every connected mailbox stops
|
|
341
|
+
syncing. Then at Google: Clients → tick the client → Delete (restorable
|
|
342
|
+
for 30 days; Google also deletes clients unused for six months).
|
|
343
|
+
|
|
344
|
+
## Sources
|
|
345
|
+
|
|
346
|
+
- https://developers.google.com/workspace/guides/create-project (step 2)
|
|
347
|
+
- https://developers.google.com/workspace/guides/enable-apis (step 3)
|
|
348
|
+
- https://developers.google.com/workspace/guides/configure-oauth-consent (step 4)
|
|
349
|
+
- https://support.google.com/cloud/answer/15549135 (Data Access: only enabled APIs' scopes listed, Manually add scopes; step 5)
|
|
350
|
+
- https://support.google.com/cloud/answer/15549257 (clients: secret shown once, two at most, Add secret, 30-day restore, six-month inactivity; steps 6 and 8, Rotate, Remove)
|
|
351
|
+
- https://support.google.com/cloud/answer/15549945 (Audience: Internal needs an organization, External, Testing's 100 users and 7 days, Publish app; step 7)
|
|
352
|
+
- https://support.google.com/cloud/answer/13464321 (verification: home page, privacy policy, Search Console, demo video, scope justification; restricted scopes alone add the yearly assessment; step 7, Costs and plans)
|
|
353
|
+
- https://support.google.com/cloud/answer/9110914 (a scope's category is labeled in the console once added; step 5)
|
|
354
|
+
- https://developers.google.com/identity/protocols/oauth2/web-server (`include_granted_scopes`: the token covers every scope the person granted the project; the response's `scope`; Settings)
|
|
355
|
+
- https://searchconsole.googleapis.com/$discovery/rest?version=v1, https://analyticsdata.googleapis.com/$discovery/rest?version=v1beta, https://analyticsadmin.googleapis.com/$discovery/rest?version=v1beta (service names and the scopes each API takes; step 3)
|
|
356
|
+
- https://developers.google.com/webmaster-tools/limits and https://developers.google.com/analytics/devguides/reporting/data/v1/quotas (quotas; Costs and plans)
|
|
357
|
+
- `docs/design/2026-10-oauth-scopes.md` (the sets, §4; the token-endpoint check and its codes, §5; §13 what was built), TODO.md "OAuth scopes (2026-10-24)" (Search Console and Analytics on this client; Open CRM's and OpenSEO's follow-ups) and `docs/capabilities.md` "OAuth clients" (the generated sets table; Settings)
|
|
358
|
+
- https://support.google.com/accounts/answer/16668185 (Google's sign-in error codes; If it fails)
|
|
359
|
+
- https://docs.cloud.google.com/iap/docs/programmatic-oauth-clients (the API that created clients, shut down 2026-03-19; step 6)
|
|
360
|
+
- https://developers.google.com/health/app-verification (assessor fees; Costs and plans)
|