everyapp 0.2.2 → 0.3.0-rc.2
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/LICENSE +21 -0
- package/README.md +6 -77
- package/bin/everyapp.js +235 -0
- package/dist/access-LGEN3ABJ.js +1 -0
- package/dist/activity-RSD4D66F.js +12 -0
- package/dist/apps-4RCFXT74.js +13 -0
- package/dist/chunk-23HJ5I2A.js +2 -0
- package/dist/chunk-2Y5Q6A5K.js +6 -0
- package/dist/chunk-4JRIS6S6.js +1 -0
- package/dist/chunk-5PTNYZK6.js +4 -0
- package/dist/chunk-6OGVO7QK.js +6 -0
- package/dist/chunk-6PGUCTJ6.js +11 -0
- package/dist/chunk-AGTCKFRB.js +22 -0
- package/dist/chunk-BHMRY53V.js +2 -0
- package/dist/chunk-CB4U7H2G.js +4 -0
- package/dist/chunk-CCWBPAKH.js +4 -0
- package/dist/chunk-CZTEDWZ2.js +22 -0
- package/dist/chunk-CZXHKPRZ.js +2 -0
- package/dist/chunk-E3EPK27P.js +10 -0
- package/dist/chunk-EGEWRFVY.js +1 -0
- package/dist/chunk-ELK7QZ2M.js +1 -0
- package/dist/chunk-FJJSSVET.js +33 -0
- package/dist/chunk-GKBPYVP3.js +25 -0
- package/dist/chunk-H6QQMY7B.js +18 -0
- package/dist/chunk-H7PDL3X5.js +1 -0
- package/dist/chunk-HOUHG236.js +20 -0
- package/dist/chunk-IFCPALVC.js +46 -0
- package/dist/chunk-IUOBIB5M.js +30 -0
- package/dist/chunk-J35DDCNU.js +1 -0
- package/dist/chunk-J455FAEN.js +7 -0
- package/dist/chunk-JYKRUS5C.js +1 -0
- package/dist/chunk-LXYMDZER.js +1 -0
- package/dist/chunk-LYNBZ7RV.js +57 -0
- package/dist/chunk-M2QQ2UD7.js +132 -0
- package/dist/chunk-N4E5YE4X.js +5 -0
- package/dist/chunk-N5GQLGEC.js +2 -0
- package/dist/chunk-NFU4YFXB.js +1 -0
- package/dist/chunk-OM2HDWVP.js +1 -0
- package/dist/chunk-ONLNJXVQ.js +32 -0
- package/dist/chunk-OP4DYXYI.js +2 -0
- package/dist/chunk-PNLU5U3P.js +1 -0
- package/dist/chunk-PS7S74DG.js +6 -0
- package/dist/chunk-RMF4YGD6.js +98 -0
- package/dist/chunk-T2GD7RHS.js +10 -0
- package/dist/chunk-TLRM3P7V.js +15 -0
- package/dist/chunk-W3JCQHHU.js +1 -0
- package/dist/chunk-WOVS3GV5.js +10 -0
- package/dist/chunk-YHJZMJYZ.js +1 -0
- package/dist/chunk-ZECBN24L.js +15 -0
- package/dist/chunk-ZEXVHB22.js +2 -0
- package/dist/chunk-ZFCUH5PK.js +7 -0
- package/dist/configure-F7UZCJUG.js +8 -0
- package/dist/create-2E5HVWYK.js +22 -0
- package/dist/db-HEQVTDTM.js +9 -0
- package/dist/delete-XCSIQIRS.js +3 -0
- package/dist/deploy-3QOZEZHC.js +1 -0
- package/dist/dev-JL3ZP5BT.js +86 -0
- package/dist/email-RLZFUFUN.js +10 -0
- package/dist/events-2V5VRBWP.js +15 -0
- package/dist/groups-HDG5DVFY.js +8 -0
- package/dist/index.js +97 -1
- package/dist/install-Z5FYXUYM.js +1 -0
- package/dist/login-KHJBC64Z.js +1 -0
- package/dist/mcp-EFDUKJ5V.js +1 -0
- package/dist/provider-EOI5PTFH.js +13 -0
- package/dist/release-74J5ZIL3.js +1 -0
- package/dist/restore-FFGYV7NO.js +3 -0
- package/dist/secret-7FKKTLHB.js +1 -0
- package/dist/setup-PAKRCAFO.js +1 -0
- package/dist/signin-AP3D4XM4.js +12 -0
- package/dist/status-RMWNHOEK.js +4 -0
- package/dist/teardown-E4JNKAZJ.js +22 -0
- package/dist/update-DWVZFNPS.js +1 -0
- package/dist/workspace-CJC5I66L.js +21 -0
- package/package.json +19 -16
- package/skills/build-every-app/SKILL.md +199 -0
- package/skills/build-every-app/patterns.md +140 -0
- package/skills/everyapp/SKILL.md +292 -0
- package/skills/everyapp/mcp.md +350 -0
- package/skills/everyapp/security-review.md +61 -0
- package/skills/everyapp/setup/README.md +274 -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 +1253 -0
- package/skills/everyapp/setup/remote-mcp.md +199 -0
- package/skills/everyapp/setup/signin-apple.md +178 -0
- package/skills/everyapp/setup/signin-cloudflare.md +148 -0
- package/skills/everyapp/setup/signin-github.md +139 -0
- package/skills/everyapp/setup/signin-google.md +178 -0
- package/skills/everyapp/setup/signin-microsoft.md +183 -0
- package/skills/everyapp/setup/signin-oidc.md +163 -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,281 @@
|
|
|
1
|
+
# Microsoft 365 (Outlook OAuth client) for the Gateway
|
|
2
|
+
|
|
3
|
+
An OAuth client registered in Microsoft Entra, held by the Gateway, so that
|
|
4
|
+
every member of an organization can connect their own Outlook mailbox (work
|
|
5
|
+
or school account, or a personal Microsoft account) to Open CRM (app id
|
|
6
|
+
`crm`), which reads their mail, never sends. The Gateway completes
|
|
7
|
+
Microsoft's token endpoint with the client; the app keeps each member's
|
|
8
|
+
tokens. At the end the person has an app registration named for the CRM, a
|
|
9
|
+
client id and one client secret (which expires), and `MICROSOFT_365_CLIENT_ID`
|
|
10
|
+
and `MICROSOFT_365_CLIENT_SECRET` set on the Gateway.
|
|
11
|
+
|
|
12
|
+
## Settings
|
|
13
|
+
|
|
14
|
+
| Name | Required | What it holds |
|
|
15
|
+
| ----------------------------- | -------- | ------------------------------------------------------------------------------------------ |
|
|
16
|
+
| `MICROSOFT_365_CLIENT_ID` | required | The registration's **Application (client) ID** (a GUID, shown on its Overview page). Not a secret. |
|
|
17
|
+
| `MICROSOFT_365_CLIENT_SECRET` | required | A client secret's **Value** (Certificates & secrets), shown once when created; expires in at most 24 months. |
|
|
18
|
+
|
|
19
|
+
Fixed by the registry (`capabilities.ts`, `microsoft-365`, labelled **Microsoft 365 (Outlook mail and calendar)** in
|
|
20
|
+
errors and logs): the token endpoint the Gateway completes is
|
|
21
|
+
`POST https://login.microsoftonline.com/common/oauth2/v2.0/token`, the multi-tenant `common` one only, so the
|
|
22
|
+
registration MUST support "any organizational directory and personal Microsoft accounts" (manifest
|
|
23
|
+
`signInAudience: AzureADandPersonalMicrosoftAccount`); the app sends people to
|
|
24
|
+
`https://login.microsoftonline.com/common/oauth2/v2.0/authorize`, Open CRM asking for `offline_access User.Read Mail.Read`;
|
|
25
|
+
the redirect URI is `https://<app>.<app base domain>/integrations/microsoft/callback`, for Open CRM
|
|
26
|
+
`https://crm.<app base domain>/integrations/microsoft/callback`.
|
|
27
|
+
|
|
28
|
+
The client carries three scope sets, each one Graph delegated permission, all read-only: `identity` (`User.Read`),
|
|
29
|
+
`mail.read` (`Mail.Read`) and `calendar.read` (`Calendars.Read`); `offline_access` (the refresh token) is added to
|
|
30
|
+
every app's list whatever its sets (`alwaysRequested`; `requestedScopes()` in `packages/sdk/src/internal/oauthScopes.ts`),
|
|
31
|
+
so the registry's whole checklist reads `offline_access User.Read Mail.Read Calendars.Read` (docs/capabilities.md,
|
|
32
|
+
"OAuth clients"). An app names its sets in its manifest entry, `"microsoft-365": { access: "write", scopes: ["identity", "mail.read"], why }`
|
|
33
|
+
(#414; `everyapp deploy` refuses the entry without `scopes`), and the Gateway holds it to them at every token exchange
|
|
34
|
+
and refresh (#411; docs/design/2026-10-oauth-scopes.md §3 and §5), replacing the request's `scope` with the sets'
|
|
35
|
+
scopes plus `offline_access`: Open CRM's is `offline_access User.Read Mail.Read`. An entry deployed without `scopes` is
|
|
36
|
+
refused at the token endpoint (`integration_scopes_undeclared`); such a CRM must redeploy before the Gateway is updated
|
|
37
|
+
from main (TODO.md "OAuth scopes (2026-10-24)", follow-up 0).
|
|
38
|
+
The registration's delegated permissions are the union behind the sets of every app you approve
|
|
39
|
+
(TODO.md, "Setup skills (2026-10-24)"), not the registry's whole list: for the launch, Open CRM alone, `User.Read`,
|
|
40
|
+
`Mail.Read` and `offline_access`; `Calendars.Read` joins only when an app declares `calendar.read`.
|
|
41
|
+
|
|
42
|
+
## Costs and plans
|
|
43
|
+
|
|
44
|
+
As of October 2026: registering an app and its client secrets cost nothing (Microsoft Entra ID Free). A tenant is
|
|
45
|
+
needed to register in: a Microsoft 365 or Azure subscriber already has one (every tenant is an Entra tenant). A
|
|
46
|
+
solopreneur without Microsoft 365 creates an Azure free account (a phone number, a non-prepaid card for identity
|
|
47
|
+
verification that is not charged beyond a temporary hold of about one dollar, and a Microsoft or GitHub account); its
|
|
48
|
+
**Default Directory** is the tenant. After 30 days the Azure subscription is disabled unless moved to pay-as-you-go
|
|
49
|
+
(nothing is charged); Entra ID Free needs no subscription, and Microsoft's page says nothing about the directory:
|
|
50
|
+
support answers say the tenant and its registrations stay (unverified). The account that registers needs at least the Application Developer role (the
|
|
51
|
+
tenant's creator is Global Administrator). A personal Microsoft account alone, with no tenant, cannot register.
|
|
52
|
+
Members who connect need a mailbox: Exchange Online (a Microsoft 365 plan with mail) or an outlook.com account.
|
|
53
|
+
Microsoft Graph's Outlook limit is 10,000 requests per 10 minutes and four concurrent requests, per app per mailbox;
|
|
54
|
+
a `429` carries `Retry-After`, which Open CRM honors.
|
|
55
|
+
|
|
56
|
+
## Only a person can
|
|
57
|
+
|
|
58
|
+
- Sign in to Microsoft: the Entra admin center, or the browser approval `az login` opens.
|
|
59
|
+
- Create the Azure free account, add the card, and pick the tenant when the sign-in sees several.
|
|
60
|
+
- Copy the client secret's **Value** off the screen (it is shown once) and type it at the hidden prompt.
|
|
61
|
+
- Connect their mailbox in Open CRM and accept Microsoft's consent screen. Register in the tenant the members' work
|
|
62
|
+
mailboxes live in: a member from another organization's tenant sees "Need admin approval" (`AADSTS90094`) by
|
|
63
|
+
Microsoft's default (a new multitenant app without a verified publisher, asking for more than sign-in), and that
|
|
64
|
+
organization's admin consents (the If-it-fails row); personal Microsoft accounts are not affected. Publisher
|
|
65
|
+
verification, which lifts this, needs a Partner Center account and a custom publisher domain, and a registration
|
|
66
|
+
made under a personal Microsoft account can't get it.
|
|
67
|
+
|
|
68
|
+
## Before you start
|
|
69
|
+
|
|
70
|
+
1. `pnpm everyapp secret list crm --json`, from any directory (the id reads the manifest Open CRM's last deploy
|
|
71
|
+
stored; in its directory the id can be left off), one call, before any Microsoft step. The rule: read
|
|
72
|
+
`data.app.setup.settings`, `unmet` and `command`. The client is one setting there, `id: "microsoft-365"`,
|
|
73
|
+
`kind: "capability"`, `holder: "gateway"`, `names: ["MICROSOFT_365_CLIENT_ID", "MICROSOFT_365_CLIENT_SECRET"]`
|
|
74
|
+
(an OAuth client's id and secret are the two names of one setting, set only when both are), `need.kind` as the
|
|
75
|
+
CRM's manifest declares it (Open CRM: `optional`: it sits in `settings` with `set: false`, never in `unmet`, the
|
|
76
|
+
CRM opens without Outlook and `command`, `pnpm everyapp configure crm`, offers it after the required ones;
|
|
77
|
+
`required` would put it in `unmet`, close the CRM and have configure ask for both names first), `setup:
|
|
78
|
+
"setup/microsoft-365.md"` (this file). While its `set` is false this file
|
|
79
|
+
applies. The same entry in `data.app.setup.settings` with `set: true`: ask before
|
|
80
|
+
replacing; a value can't be read back, so replacing means a new secret in Entra. The same answer says whether
|
|
81
|
+
the CRM's own `TOKEN_ENCRYPTION_KEY` is set (`holder: "app"`; optional, so in `settings`, never in `unmet`): it
|
|
82
|
+
must be set before anyone can connect (after the CRM's first deploy, at the hidden prompt of
|
|
83
|
+
`pnpm everyapp configure crm`, which offers it with the optional ones, or of
|
|
84
|
+
`pnpm everyapp secret set TOKEN_ENCRYPTION_KEY --app crm`, from anywhere; 32 or more random characters), so
|
|
85
|
+
settle it now, not after the set.
|
|
86
|
+
2. `pnpm everyapp status --json`: `data.installed` and `data.claimed` true; `data.appBaseDomain` is `<app base domain>`;
|
|
87
|
+
`data.apps[]` has `"id": "crm"` with `"status": "active"`, `"bound": true`, the `hostname` the redirect URI
|
|
88
|
+
goes on (step 1) and, as `setup`, 1's `data.app.setup` again: what else the CRM needs. No `crm`: deploy Open
|
|
89
|
+
CRM first (from its directory, `pnpm everyapp deploy --json`); the registration can be made before that, but
|
|
90
|
+
a connect only completes once `crm.<app base domain>` serves `/integrations/microsoft/callback`.
|
|
91
|
+
3. An existing registration: the person opens
|
|
92
|
+
https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade and looks for one named
|
|
93
|
+
for the CRM. Reuse it (steps 3 to 5 check its settings) rather than registering twice.
|
|
94
|
+
4. The console path below is the default. The `az` alternatives need the Azure CLI: `az version` says whether it
|
|
95
|
+
is there. Missing: don't install it on your own; offer `brew update && brew install azure-cli` only if the
|
|
96
|
+
person wants the `az` path and says yes.
|
|
97
|
+
|
|
98
|
+
## Steps
|
|
99
|
+
|
|
100
|
+
One Person step per message: give step 2, wait for the words it ends with, then step 3, and so on. Not a checklist
|
|
101
|
+
to send at once: step 2 settles the tenant and step 3 the account type (and gives the id step 6 needs) before any
|
|
102
|
+
secret exists.
|
|
103
|
+
|
|
104
|
+
1. **You:** Work out the redirect URI from `pnpm everyapp status --json`: `https://` + the `hostname` of app `crm`
|
|
105
|
+
+ `/integrations/microsoft/callback` (the same URI Open CRM shows owners and admins on Settings → Connections →
|
|
106
|
+
Microsoft 365). Give it to the person with the registration name to use, `Open CRM`.
|
|
107
|
+
2. **Person:** Sign in at https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade
|
|
108
|
+
with the account that administers the organization's tenant (or the Microsoft account of the Azure free
|
|
109
|
+
account). Several tenants: the **Settings** (gear) icon in the top menu switches; pick the one the
|
|
110
|
+
organization's mailboxes live in. They see the **App registrations** list (older menus: **Identity →
|
|
111
|
+
Applications → App registrations**; current: **Entra ID → App registrations**). Wait for "I see App
|
|
112
|
+
registrations".
|
|
113
|
+
*Alternative, with `az`:* `az login --allow-no-subscriptions` (opens the browser; `--use-device-code` when it
|
|
114
|
+
can't; `--tenant <tenant id>` to pick one). The person approves the sign-in; wait for `az` to print the account.
|
|
115
|
+
3. **Person:** Click **New registration**. **Name:** `Open CRM` (people see it on the consent screen). **Supported
|
|
116
|
+
account types:** the option for any organization plus personal accounts, labelled **Any Entra ID Tenant +
|
|
117
|
+
Personal Microsoft accounts** (older wording: **Accounts in any organizational directory (Any Microsoft Entra
|
|
118
|
+
directory - Multitenant) and personal Microsoft accounts (such as Skype, Xbox)**); never "Single tenant": the
|
|
119
|
+
Gateway completes only the `common` endpoint. **Redirect URI (optional):** platform **Web**, paste the URI from
|
|
120
|
+
step 1 (no field on the form: after registering, **Authentication → Add a platform → Web → Redirect URIs →
|
|
121
|
+
Configure**). Click **Register**. They see the **Overview** page with **Application (client) ID**; that id is
|
|
122
|
+
not a secret, they may paste it in the chat. Wait for the id.
|
|
123
|
+
*Alternative:* `az ad app create --display-name "Open CRM" --sign-in-audience AzureADandPersonalMicrosoftAccount --web-redirect-uris "https://crm.<app base domain>/integrations/microsoft/callback" --query appId -o tsv`
|
|
124
|
+
prints the id; then `az ad sp create --id <client id>` (the portal creates this service principal with the
|
|
125
|
+
registration; `az ad app create` does not, and the smoke check in Verify needs it).
|
|
126
|
+
4. **Person:** In the registration, **API permissions → Add a permission → Microsoft Graph → Delegated
|
|
127
|
+
permissions**; search and tick `User.Read` (added automatically at registration), `Mail.Read` and
|
|
128
|
+
`offline_access`: the union behind Open CRM's sets (`identity`, `mail.read`, plus `offline_access`). Tick
|
|
129
|
+
`Calendars.Read` as well only when an app you approve declares `calendar.read` (Settings, above). Click **Add
|
|
130
|
+
permissions**. They see the three under **Configured permissions**, each with
|
|
131
|
+
**Admin consent required: No**. The app asks for these three on its authorize URL, so members of this tenant and
|
|
132
|
+
personal accounts consent themselves; registering them is what **Grant admin consent for <tenant>** covers
|
|
133
|
+
(members from other tenants need it by Microsoft's default: Only a person can, above). Wait for the three rows.
|
|
134
|
+
*Alternative:* `az ad app permission add --id <client id> --api 00000003-0000-0000-c000-000000000000 --api-permissions e1fe6dd8-ba31-4d61-89e7-88639da4683d=Scope 570282fd-fa5c-430d-a7fd-fc8dc98a9dca=Scope 7427e0e9-2fba-42fe-b0c0-848c9e6a8182=Scope`
|
|
135
|
+
(Microsoft Graph's app id; the delegated ids of `User.Read`, `Mail.Read`, `offline_access`, each verified in
|
|
136
|
+
Graph's permissions reference; `Calendars.Read`, when an app needs it, is `465a38f9-76ea-45b9-9f34-9e8b0d4b0b42=Scope`,
|
|
137
|
+
admin consent not required, personal accounts supported). Ignore its hint to run `permission grant`: people consent at connect time.
|
|
138
|
+
5. **Person:** **Certificates & secrets → Client secrets → New client secret**. **Description:** `Every App
|
|
139
|
+
Gateway`; **Expires:** 24 months is the maximum (Microsoft recommends less; put the date in the calendar).
|
|
140
|
+
Click **Add**. The row's **Value** column is the secret, shown only while they stay on the page. Then, in a
|
|
141
|
+
second terminal in the `every-app` folder: `pnpm everyapp secret set MICROSOFT_365_CLIENT_SECRET` (the hidden
|
|
142
|
+
prompt; paste; nothing is echoed). README's first way: the person types it. Wait until they see `Set MICROSOFT_365_CLIENT_SECRET on the Gateway`
|
|
143
|
+
and `Live now on <gateway host>. No deploy needed.`
|
|
144
|
+
*Alternative, with `az`, README's second way (a command produced it; never run the `az` part on its own):*
|
|
145
|
+
`az ad app credential reset --id <client id> --years 2 --display-name "Every App Gateway" --append --query password -o tsv | pnpm everyapp secret set MICROSOFT_365_CLIENT_SECRET --json`
|
|
146
|
+
`--append` keeps existing secrets (without it the command deletes them all first). Look for `"type": "done"`.
|
|
147
|
+
6. **You:** Set the client id (not a secret): `echo "<client id>" | pnpm everyapp secret set MICROSOFT_365_CLIENT_ID --json`,
|
|
148
|
+
or from `az`: `az ad app list --display-name "Open CRM" --query "[0].appId" -o tsv | pnpm everyapp secret set MICROSOFT_365_CLIENT_ID --json`.
|
|
149
|
+
Look for `"type": "done"`. One value per pipe (`SECRET_SEVERAL_NAMES` otherwise). Both at once only at the
|
|
150
|
+
person's terminal: `pnpm everyapp configure crm` (it asks for the two names, id then secret, with hidden
|
|
151
|
+
prompts, after the CRM's required settings; the client is optional for Open CRM, so Yes to `Set the N optional
|
|
152
|
+
settings too?`, Enter skipping the others; with one of the two names set it asks only for the other; or pick `MICROSOFT_365_CLIENT_ID + MICROSOFT_365_CLIENT_SECRET` from
|
|
153
|
+
the menu once the CRM is ready) or `pnpm everyapp secret set MICROSOFT_365_CLIENT_ID MICROSOFT_365_CLIENT_SECRET`
|
|
154
|
+
(two hidden prompts); either covers step 5's set too. The change is live at once, no deploy.
|
|
155
|
+
7. **You:** Verify, below.
|
|
156
|
+
|
|
157
|
+
## Verify
|
|
158
|
+
|
|
159
|
+
1. `pnpm everyapp secret list crm --json`, from anywhere: the `microsoft-365` setting has `set: true` (optional
|
|
160
|
+
for Open CRM, so it was never in `data.app.setup.unmet`; a manifest that requires it has it leave `unmet`, with
|
|
161
|
+
`ready: true` and `command` null once nothing else is missing); both
|
|
162
|
+
names in `data.gateway.names`, `"set": true` in `data.gateway.needed`, and not in `data.gateway.missing`.
|
|
163
|
+
Then `pnpm everyapp status --json`: the `crm` entry's `setup.ready` is true and `data.fixes` has no
|
|
164
|
+
`APP_NEEDS_SETUP` for it (the Gateway opens the CRM again).
|
|
165
|
+
2. Smoke check, only if the person still has the secret in a variable they exported in their own terminal
|
|
166
|
+
(README's third way) and says so; otherwise skip to 3. Never ask them to copy the secret again for this: the
|
|
167
|
+
**Value** was shown once, at step 5, and 3 exercises the pair through the Gateway anyway. (They `unset` the
|
|
168
|
+
variable after.) It checks the id and secret pair against the registration's home tenant (its **Directory
|
|
169
|
+
(tenant) ID** on Overview, or a verified domain of the tenant):
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
printf '%s' "$MS_CLIENT_SECRET" | curl -s -X POST "https://login.microsoftonline.com/<tenant id>/oauth2/v2.0/token" -d "client_id=<client id>" -d "grant_type=client_credentials" -d "scope=https://graph.microsoft.com/.default" --data-urlencode "client_secret@/dev/stdin"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`printf` is a shell builtin and curl URL-encodes the secret it reads from stdin, so the value is never on a
|
|
176
|
+
command line (the process list would show it). Expected: JSON with `access_token` (the pair is right; this token carries no permissions and is thrown away).
|
|
177
|
+
`error_description` starting `AADSTS7000215`: wrong secret; `AADSTS7000222`: expired; `AADSTS700016`: the id
|
|
178
|
+
isn't a registration in that tenant; `AADSTS7000229`: no service principal yet (the If-it-fails row).
|
|
179
|
+
3. The app, open: 1's `data.app.setup.ready` true. While another required setting of the CRM is unmet, the Gateway
|
|
180
|
+
sends a browser to its setup dialog instead of the CRM (anything else gets 503 `app_needs_setup`): finish those
|
|
181
|
+
first (each `unmet` entry's `setup` names its file); until then 1 and 2 are the checks of the client. Then a
|
|
182
|
+
member opens Open CRM → **Settings → Connections → Microsoft 365** and clicks **Connect**. Microsoft
|
|
183
|
+
signs them in, shows a consent screen for Open CRM with its permissions in Microsoft's words (reading mail,
|
|
184
|
+
signing in and reading the profile, and **Maintain access to data you have given it access to**; the scope names
|
|
185
|
+
don't appear, and the publisher shows as **Unverified**, which is expected), and returns them to the page, which
|
|
186
|
+
says the mailbox is connected and starts syncing. A **Not configured** badge and no **Connect** button means the
|
|
187
|
+
Gateway doesn't hold both settings (an owner or admin also sees a card with the redirect URI and
|
|
188
|
+
`pnpm everyapp secret set MICROSOFT_365_CLIENT_ID MICROSOFT_365_CLIENT_SECRET`): step 1 of this section.
|
|
189
|
+
4. The Gateway's logs, when the connect fails after consent: the dashboard, README's path:
|
|
190
|
+
**Workers & Pages** → `every-app-gateway` → **Observability** (**Logs** on older accounts) → **Live**
|
|
191
|
+
(the Gateway deploys with Workers Logs on, so the stored logs are on the same page). `cf workers` has no
|
|
192
|
+
tail; Workers Logs are under `cf o11y telemetry`: `cf o11y telemetry query --help` (a query over them) and
|
|
193
|
+
`cf o11y telemetry live-tail --help` (prepares a live tail; `--script-id` names the Worker, `every-app-gateway`). Look for
|
|
194
|
+
`integration.proxy.call` with `"capability": "microsoft-365"`, its `grantType` (`authorization_code` on connect,
|
|
195
|
+
`refresh_token` later) and `status`: `200` is a token issued; `400` or `401` is Microsoft refusing, and the body
|
|
196
|
+
is never logged, so read the AADSTS code on the CRM's page. `integration.proxy.upstream_failed` is Microsoft
|
|
197
|
+
unreachable.
|
|
198
|
+
|
|
199
|
+
## If it fails
|
|
200
|
+
|
|
201
|
+
| Error or symptom | Meaning | What to do |
|
|
202
|
+
| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
203
|
+
| `integration_not_configured` (Gateway 503), or the CRM's **Not configured** badge with no **Connect** button (owners and admins: the card with the redirect URI and the `secret set` command) | The Gateway holds neither or only one of the two settings, or a name differs; the badge alone also means the deployed manifest lists `microsoft-365` without `scopes` (add them, deploy) | `pnpm everyapp secret list --json`; set what is missing (steps 5 and 6) |
|
|
204
|
+
| `capability_not_requested` (403) | The deployed manifest doesn't list `microsoft-365` (an entry without `scopes` is `integration_scopes_undeclared` instead) | Add `"microsoft-365": { access: "write", scopes: ["identity", "mail.read"], why }` to `everyapp.config.ts`, deploy again |
|
|
205
|
+
| `integration_agent_not_allowed` (403) | An agent (MCP) tried to connect or refresh | The person connects in the app |
|
|
206
|
+
| `integration_upstream_failed` (502), log `integration.proxy.upstream_failed` | The Gateway couldn't reach login.microsoftonline.com (the CRM says "Microsoft returned an error. Try again.") | Try again |
|
|
207
|
+
| `integration_path_not_allowed` (403), `integration_body_invalid` (400), `integration_body_too_large` (413), `integration_scope_not_allowed` (403: the request's `scope` names a scope outside the CRM's sets) | The app's request (an endpoint other than `/common/oauth2/v2.0/token`, a body it doesn't take, or a scope it doesn't declare), not this setup | Check the app's version |
|
|
208
|
+
| `integration_scope_exceeded` (403) on a connect or a refresh | Two apps on this one registration and one person: Microsoft can issue a token carrying every scope the person consented to either app, and the Gateway refuses the narrower app's exchange rather than narrowing it (docs/design/2026-10-oauth-scopes.md §9; its log line `integration.proxy.scope_refused` names the excess) | The member removes the app at https://myapplications.microsoft.com and connects again; for good, one registration per app, which needs its own registry entry (TODO.md "OAuth scopes (2026-10-24)") |
|
|
209
|
+
| `SECRET_VALUE_REQUIRED` | Nothing was piped and there is no terminal | One of README's three ways; `data.rerun` shows the `--from-env` form |
|
|
210
|
+
| `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 steps 5 and 6, or the person's terminal (step 6) |
|
|
211
|
+
| `SECRET_SEVERAL_NAMES` | Two names with one piped or `--from-env` value | One `secret set` per name |
|
|
212
|
+
| `SECRET_EMPTY` | The `--from-env` variable isn't exported in this shell | The person exports it in the terminal that runs the command |
|
|
213
|
+
| `SECRET_NOT_DECLARED` with `data.didYouMean` | The name is misspelled (case or underscores) | 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` |
|
|
214
|
+
| `CONFIRMATION_REQUIRED` from `secret unset --json` | No `--yes`, and `--json` never asks | That is the ask: show it; its `data.rerun` adds `--yes` (`data.authorize`), run it only if the person says so |
|
|
215
|
+
| `AADSTS50011` redirect URI mismatch | The URI on the registration isn't exactly `https://crm.<app base domain>/integrations/microsoft/callback` | **Authentication → Platform configurations → Add URI**, platform Web, exact case; wait 3 to 5 minutes |
|
|
216
|
+
| `AADSTS50194` /common not supported | The registration is single-tenant | **Authentication → Supported account types**, choose the organizations + personal option (manifest `signInAudience`) |
|
|
217
|
+
| `AADSTS700016` application not found | `MICROSOFT_365_CLIENT_ID` isn't a registration's id, or the registration is personal-only / single-tenant | Compare with **Application (client) ID** on Overview; fix the audience; set the id again |
|
|
218
|
+
| `AADSTS7000229` missing service principal (the smoke check; not in Microsoft's AADSTS reference) | The registration was made with `az` and has no Enterprise application in its tenant yet; the secret is fine, so don't rotate it | `az ad sp create --id <client id>`, or on Overview the **Create Service Principal** link (where **Managed application in local directory** would be); smoke check again |
|
|
219
|
+
| `AADSTS50020` user does not exist in tenant | The account signed in belongs to no tenant the registration accepts | Fix the audience as above; the member signs in with their mailbox's account |
|
|
220
|
+
| `AADSTS7000215` invalid client secret | The Gateway's secret is wrong (a typo at the prompt, or not the **Value** column) | New secret (Rotate, below), set it again |
|
|
221
|
+
| `AADSTS7000222` client secret expired | The secret's expiry passed; every connect and refresh fails | Rotate, below; members reconnect when the CRM says NEEDS_RECONNECT |
|
|
222
|
+
| `AADSTS90094` "Need admin approval", the CRM's `access_denied` | Microsoft's default for a member outside the registration's tenant: a new multitenant app without a verified publisher (risk-based step-up consent) | That organization's admin opens the admin-consent link on the member's CRM page, or Entra → **Enterprise apps → Open CRM → Security → Permissions → Grant admin consent**; or register in the members' tenant |
|
|
223
|
+
| `AADSTS65001`, the CRM's `access_denied` | Consent hasn't been granted for that tenant (the default for members outside the registration's tenant, or a tenant that lets only admins consent) | As above |
|
|
224
|
+
| `AADSTS65004` | The member declined the consent screen | Connect again |
|
|
225
|
+
| `AADSTS90002` tenant not found | The admin-consent link was built from an email domain the tenant hasn't verified | The admin consents from Entra (previous row) |
|
|
226
|
+
| `AADSTS700054` id_token not enabled | The authorize request asked for an ID token; Open CRM asks for a code | Something other than the CRM built the URL; check the app's version |
|
|
227
|
+
| `invalid_grant` on refresh: `AADSTS70008` / `AADSTS700082` (unused 90 days), `AADSTS50173` (password reset or tokens revoked), `AADSTS65001` (consent withdrawn); `AADSTS70000` is another refresh-token failure | The refresh token stopped working | The CRM marks the connection NEEDS_RECONNECT; the member connects again |
|
|
228
|
+
| CRM: `email_doesn't_match` | The mailbox's address isn't the member's Gateway email | Sign in to Microsoft with the matching account |
|
|
229
|
+
| CRM: "confirm this is an active Exchange Online mailbox" | The account has no mailbox (no mail license) | A licensed mailbox or an outlook.com account |
|
|
230
|
+
| CRM: **Connect** fails before Microsoft opens: "Connections aren't set up on this CRM yet" | The CRM's own `TOKEN_ENCRYPTION_KEY` is unset, or under 32 characters (the one key its Google, Microsoft and Slack connects share) | 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; connect again |
|
|
231
|
+
|
|
232
|
+
## Rerun, rotate, remove
|
|
233
|
+
|
|
234
|
+
- **Rerun.** Safe at any step: `configure` and `secret set` replace; the registration's pages show what exists (one registration,
|
|
235
|
+
not two: look in App registrations first). Interrupted: start from Verify and continue at the first step that
|
|
236
|
+
isn't done.
|
|
237
|
+
- **Rotate the secret** before it expires (the registration's **Certificates & secrets** page shows the dates).
|
|
238
|
+
This audience allows at most two client secrets at once: look there first (or
|
|
239
|
+
`az ad app credential list --id <client id>`) and, with two already, delete the expired or unused one. Then add the second (step 5, either path; with `az`,
|
|
240
|
+
keep `--append`), set it (`secret set MICROSOFT_365_CLIENT_SECRET`), verify, then delete the old one: the trash
|
|
241
|
+
icon on its row, or `az ad app credential list --id <client id>` for its `keyId` and
|
|
242
|
+
`az ad app credential delete --id <client id> --key-id <key id>`. Members' connections carry on; the client id
|
|
243
|
+
never changes, so `MICROSOFT_365_CLIENT_ID` stays.
|
|
244
|
+
- **Remove.** `pnpm everyapp secret unset MICROSOFT_365_CLIENT_ID MICROSOFT_365_CLIENT_SECRET --json` (with
|
|
245
|
+
`--json` it never asks: without `--yes` it stops with `CONFIRMATION_REQUIRED`, whose `data.rerun` adds `--yes`;
|
|
246
|
+
run that only when the person said so). Then revoke on Microsoft's side: delete the secret (as
|
|
247
|
+
above), or the whole registration (Overview → **Delete** → confirm, as at least Cloud Application
|
|
248
|
+
Administrator; or `az ad app delete --id <client id>`). Microsoft has no token revocation endpoint: members'
|
|
249
|
+
mailbox tokens stop at their next refresh, and each member withdraws the consent from their own Microsoft
|
|
250
|
+
account at https://myapplications.microsoft.com (the CRM's page links it).
|
|
251
|
+
|
|
252
|
+
## Sources
|
|
253
|
+
|
|
254
|
+
- https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app (registration path, account type labels, client id)
|
|
255
|
+
- https://learn.microsoft.com/en-us/entra/identity-platform/supported-accounts-validation (`signInAudience` values, two-secret limit)
|
|
256
|
+
- https://learn.microsoft.com/en-us/entra/identity-platform/reply-url (redirect URI rules, AADSTS50011)
|
|
257
|
+
- https://learn.microsoft.com/en-us/troubleshoot/azure/entra/entra-id/app-integration/error-code-aadsts50011-redirect-uri-mismatch
|
|
258
|
+
- https://learn.microsoft.com/en-us/entra/identity-platform/how-to-add-credentials (client secret steps, 24-month maximum, shown once)
|
|
259
|
+
- https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-configure-app-access-web-apis (API permissions clicks, admin consent button)
|
|
260
|
+
- https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc (`offline_access` must be requested; refresh tokens)
|
|
261
|
+
- https://learn.microsoft.com/en-us/entra/identity-platform/permissions-consent-overview (dynamic consent for delegated permissions)
|
|
262
|
+
- https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-user-consent (default user consent; the settings path)
|
|
263
|
+
- https://learn.microsoft.com/en-us/graph/permissions-reference (`User.Read`, `Mail.Read`, `offline_access`, `Calendars.Read` ids; admin consent not required; `Mail.Read` and `Calendars.Read` for personal accounts)
|
|
264
|
+
- https://learn.microsoft.com/en-us/graph/throttling-limits (Outlook service limits)
|
|
265
|
+
- https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow (the smoke check's request and answers)
|
|
266
|
+
- https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes (AADSTS codes)
|
|
267
|
+
- https://learn.microsoft.com/en-us/entra/identity-platform/howto-remove-app (deleting a registration)
|
|
268
|
+
- https://learn.microsoft.com/en-us/entra/fundamentals/what-is-entra and https://learn.microsoft.com/en-us/entra/fundamentals/create-new-tenant (who has a tenant; free tenants)
|
|
269
|
+
- https://azure.microsoft.com/en-us/pricing/purchase-options/azure-account (card for verification with a temporary hold, not charged; disabled after 30 days unless upgraded)
|
|
270
|
+
- https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals (the service principal: automatic in the portal, a separate step through the API; created on consent)
|
|
271
|
+
- https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-risk-based-step-up-consent and https://learn.microsoft.com/en-us/entra/identity-platform/publisher-verification-overview (AADSTS90094 for unverified multitenant apps; who can be verified)
|
|
272
|
+
- https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/grant-admin-consent (the Enterprise apps path; the adminconsent URL)
|
|
273
|
+
- https://learn.microsoft.com/en-us/entra/identity-platform/application-consent-experience ("Unverified" on the consent prompt)
|
|
274
|
+
- https://learn.microsoft.com/en-us/entra/identity-platform/refresh-tokens (90-day lifetime, revocation)
|
|
275
|
+
- https://knowledge.cribl.io/microsoft-47/office-365-activity-source-fails-with-aadsts7000229-service-principal-missing-from-azure-tenant-2323 (AADSTS7000229's text and the portal fix)
|
|
276
|
+
- https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-macos, https://learn.microsoft.com/en-us/cli/azure/authenticate-azure-cli-interactively, https://learn.microsoft.com/en-us/cli/azure/reference-index (`az login` flags)
|
|
277
|
+
- https://learn.microsoft.com/en-us/cli/azure/ad/app, https://learn.microsoft.com/en-us/cli/azure/ad/app/credential, https://learn.microsoft.com/en-us/cli/azure/ad/app/permission (Azure CLI 2.91, flags as used above)
|
|
278
|
+
- https://developers.cloudflare.com/workers/observability/logs/real-time-logs/ and https://developers.cloudflare.com/workers/observability/logs/workers-logs/ (the dashboard's Logs and Observability pages)
|
|
279
|
+
- `packages/sdk/src/internal/capabilities.ts` (`microsoft-365`: the label, the `common` token endpoint, the three scope sets, `alwaysRequested`) and `packages/sdk/src/internal/oauthScopes.ts` (`requestedScopes()`); `docs/capabilities.md`, "OAuth clients" (the generated scope tables)
|
|
280
|
+
- `docs/design/2026-10-oauth-scopes.md` (§3 the manifest's `scopes`, §4 the Microsoft policy, §5 the token-endpoint check and `integration_scope_exceeded`, §9 the shared-client limit) and TODO.md, "OAuth scopes (2026-10-24)" (the decisions; Open CRM's redeploy) and "Setup skills (2026-10-24)" (the console-side union rule); the manifest field and the check are #414 and #411, merged
|
|
281
|
+
- Open CRM's `src/shared/mailbox.ts` (`REQUESTED_SCOPES.microsoft`) and `src/client/features/mailbox/connectionCopy.ts` (the myapplications.microsoft.com link)
|