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,163 @@
|
|
|
1
|
+
# Sign in with any OpenID Connect issuer (your SSO) on the Gateway
|
|
2
|
+
|
|
3
|
+
Read [README.md](README.md) first: the rules every setup follows.
|
|
4
|
+
|
|
5
|
+
Puts **Continue with SSO** (or a label the person picks, such as
|
|
6
|
+
`Continue with Okta`) on the Gateway's sign-in page, beside the password,
|
|
7
|
+
through the company's identity provider: Okta, Keycloak, Auth0, Authentik,
|
|
8
|
+
Google Workspace as an issuer, and the like. At the end the person has a
|
|
9
|
+
web-application client at their identity provider, its settings on the
|
|
10
|
+
Gateway, and has signed in through it once.
|
|
11
|
+
`pnpm everyapp signin show oidc --json` names this file as `setup`, and the
|
|
12
|
+
CLI prints `Guide: .claude/skills/everyapp/setup/signin-oidc.md`.
|
|
13
|
+
|
|
14
|
+
Who gets in doesn't change: the issuer only says who someone is, and only
|
|
15
|
+
an ID token with `email_verified: true` counts. A member signs in;
|
|
16
|
+
someone with a pending invitation for that exact address joins with the
|
|
17
|
+
invited role; everyone else is refused ("You don't have an account on this
|
|
18
|
+
gateway.") and nothing is created. The rules:
|
|
19
|
+
`docs/runbooks/sign-in-providers.md`, "Who gets in".
|
|
20
|
+
|
|
21
|
+
Not this file: Cloudflare Access for SaaS (also an OIDC issuer, but with
|
|
22
|
+
its own `CLOUDFLARE_ACCESS_OIDC_*` settings; [access.md](access.md) and
|
|
23
|
+
`docs/runbooks/cloudflare-access.md`). Microsoft Entra ID: prefer
|
|
24
|
+
[signin-microsoft.md](signin-microsoft.md), which handles Entra's
|
|
25
|
+
verified-email rules; Entra's ID tokens carry no `email_verified` claim
|
|
26
|
+
(not among its optional claims), so here every sign-in would be refused.
|
|
27
|
+
|
|
28
|
+
## Settings
|
|
29
|
+
|
|
30
|
+
| Name | Required | Flag | What it holds |
|
|
31
|
+
| --------------------------- | -------- | -------------------------------- | ----------------------------------------------------------------------------- |
|
|
32
|
+
| `OIDC_SIGNIN_ISSUER` | required | `--issuer <url>` | the issuer URL, `https://…`, which serves `/.well-known/openid-configuration` |
|
|
33
|
+
| `OIDC_SIGNIN_CLIENT_ID` | required | `--client-id <id>` | the client's ID; not a secret |
|
|
34
|
+
| `OIDC_SIGNIN_CLIENT_SECRET` | required | `--client-secret-from-env <VAR>` | the client's secret |
|
|
35
|
+
| `OIDC_SIGNIN_LABEL` | optional | `--label <text>` | the button text after "Continue with" (default `SSO`; 40 characters at most) |
|
|
36
|
+
|
|
37
|
+
Scopes asked for: `openid email profile`. The email is the ID token's,
|
|
38
|
+
else the userinfo endpoint's (for the same `sub` only), and counts only
|
|
39
|
+
with `email_verified: true`. ID tokens must be signed with RS256 (every
|
|
40
|
+
major issuer's default) and carry the sign-in's nonce.
|
|
41
|
+
|
|
42
|
+
## Costs and plans
|
|
43
|
+
|
|
44
|
+
Whatever the identity provider charges; the Gateway adds nothing. Most
|
|
45
|
+
include OIDC web clients on every plan (Okta, Auth0 and Authentik plans
|
|
46
|
+
not checked here: unverified).
|
|
47
|
+
|
|
48
|
+
## Only a person can
|
|
49
|
+
|
|
50
|
+
- Sign in to the identity provider's admin console and create the client.
|
|
51
|
+
- Assign the people (or groups) who may use it, where the provider asks.
|
|
52
|
+
- Copy the client secret off the screen and type it at the hidden prompt
|
|
53
|
+
of `pnpm everyapp signin add oidc`.
|
|
54
|
+
- Choose the button's label; sign in through it to check.
|
|
55
|
+
|
|
56
|
+
## Before you start
|
|
57
|
+
|
|
58
|
+
1. `pnpm everyapp status --json`: `data.installed` and `data.claimed` true;
|
|
59
|
+
`data.gatewayUrl` gives `<gateway host>`. Not installed or no owner:
|
|
60
|
+
[gateway.md](gateway.md) first. `data.access.on` true: see Verify 4.
|
|
61
|
+
2. `pnpm everyapp signin show oidc --json` (changes nothing; `Unknown
|
|
62
|
+
command signin`: `git pull` in the `every-app` folder). `data.status`
|
|
63
|
+
`off`: this file applies; `needs setup`: `data.missing` says what is
|
|
64
|
+
left (step 4); `on`: ask before replacing. `data.callbackUrl` is
|
|
65
|
+
`https://<gateway host>/api/auth/callback/oidc`.
|
|
66
|
+
3. Ask which identity provider, and the label they want on the button.
|
|
67
|
+
|
|
68
|
+
## Steps
|
|
69
|
+
|
|
70
|
+
1. **You:** give the person `data.callbackUrl`.
|
|
71
|
+
2. **Person:** in the identity provider's admin console, create an OpenID
|
|
72
|
+
Connect client: type web application (confidential, with a secret),
|
|
73
|
+
grant **authorization code**, sign-in redirect URI the exact URL from
|
|
74
|
+
step 1, scopes `openid email profile`. Assign it to the people or groups
|
|
75
|
+
who should sign in (Okta and Authentik refuse unassigned users). Note
|
|
76
|
+
the issuer URL the console shows. Usual forms (check each with step 3):
|
|
77
|
+
Okta `https://<org>.okta.com` or `https://<org>.okta.com/oauth2/default`;
|
|
78
|
+
Keycloak `https://<host>/realms/<realm>`; Auth0
|
|
79
|
+
`https://<tenant>.<region>.auth0.com/` (with the slash); Authentik
|
|
80
|
+
`https://<host>/application/o/<slug>/`; Google Workspace
|
|
81
|
+
`https://accounts.google.com` (a Web application client as in
|
|
82
|
+
[signin-google.md](signin-google.md), Internal audience). The page
|
|
83
|
+
shows the client ID and secret; keep it open.
|
|
84
|
+
3. **You:** check the issuer, nothing secret in it:
|
|
85
|
+
```sh
|
|
86
|
+
curl -s https://<issuer host and path>/.well-known/openid-configuration | jq -r .issuer
|
|
87
|
+
```
|
|
88
|
+
(an issuer ending in `/`: drop that slash before `/.well-known`). The
|
|
89
|
+
answer must equal the issuer URL exactly, trailing slash included; that
|
|
90
|
+
exact string is `--issuer`. HTML, nothing, or another string: the
|
|
91
|
+
wrong URL (Okta's org server and its `default` server are two issuers).
|
|
92
|
+
4. **Person:** in a second terminal in the `every-app` folder:
|
|
93
|
+
```sh
|
|
94
|
+
pnpm everyapp signin add oidc
|
|
95
|
+
```
|
|
96
|
+
It asks for the issuer and the client ID (shown as typed), the client
|
|
97
|
+
secret (hidden: pasting works, nothing is echoed) and the label (shown
|
|
98
|
+
as typed; Enter leaves the default, `SSO`), and writes them in one new Gateway Worker
|
|
99
|
+
version, live on the next request. They tell you when it's done.
|
|
100
|
+
Another way: in that terminal, the hidden read (bash or zsh:
|
|
101
|
+
`read -rs OIDC_SIGNIN_CLIENT_SECRET && export OIDC_SIGNIN_CLIENT_SECRET`;
|
|
102
|
+
fish: `read -s -x OIDC_SIGNIN_CLIENT_SECRET`), paste, Enter, then
|
|
103
|
+
`pnpm everyapp signin add oidc --issuer <issuer> --client-id <client ID> --label Okta --client-secret-from-env OIDC_SIGNIN_CLIENT_SECRET --json`
|
|
104
|
+
and `unset OIDC_SIGNIN_CLIENT_SECRET` (fish: `set -e OIDC_SIGNIN_CLIENT_SECRET`).
|
|
105
|
+
|
|
106
|
+
## Verify
|
|
107
|
+
|
|
108
|
+
1. `pnpm everyapp signin list --json`: the `oidc` entry has `status: "on"`
|
|
109
|
+
and `missing` empty.
|
|
110
|
+
2. The person's address at the identity provider must be a member's or
|
|
111
|
+
have a pending invitation: `pnpm everyapp workspace invite <email>`.
|
|
112
|
+
3. **Person:** private window, `https://<gateway host>/sign-in` →
|
|
113
|
+
**Continue with <label>** → the identity provider's login → the
|
|
114
|
+
Gateway, signed in. The issuer's discovery document is fetched at this
|
|
115
|
+
moment (and cached for an hour), not when the Gateway starts.
|
|
116
|
+
4. Behind Cloudflare Access: nothing else changes, but the identity
|
|
117
|
+
provider's address must be the one Access knows the person by.
|
|
118
|
+
5. Logs (README's Verify 4): `signin.config_problem` names a malformed
|
|
119
|
+
setting (an issuer that isn't an https URL, a label too long).
|
|
120
|
+
|
|
121
|
+
## If it fails
|
|
122
|
+
|
|
123
|
+
| error or symptom | meaning | what to do |
|
|
124
|
+
| --- | --- | --- |
|
|
125
|
+
| no button | a setting missing or malformed, or the version isn't live yet | `pnpm everyapp signin list --json`; logs' `signin.config_problem` |
|
|
126
|
+
| `SIGNIN_ISSUER_INVALID` | not an https URL, or it has a query, a fragment or credentials | the bare issuer URL from step 3 |
|
|
127
|
+
| `SETTING_VALUE_INVALID` | the label is over 40 characters | a shorter `--label` |
|
|
128
|
+
| "The sign-in provider couldn't be reached." (`SIGNIN_PROVIDER_UNAVAILABLE`) | the discovery document couldn't be fetched, or names another issuer than `OIDC_SIGNIN_ISSUER` | step 3; `signin add oidc --issuer <exact issuer> --json` (the rest is kept) |
|
|
129
|
+
| the provider's redirect URI error | the client's redirect URI isn't exactly `data.callbackUrl` | fix it at the provider |
|
|
130
|
+
| "The sign-in provider hasn't verified this email address." | the ID token lacks `email_verified: true` | the provider marks the user's email verified (Keycloak: the user's **Email verified**; Auth0: verify the email), or another provider |
|
|
131
|
+
| sign-in fails after the provider's login, logs mention the token's algorithm | the ID token isn't RS256 | switch the client's signing algorithm to RS256 |
|
|
132
|
+
| "You don't have an account on this gateway." | the address isn't a member's or invited | invite it exactly as the provider spells it |
|
|
133
|
+
| `SIGNIN_VALUE_REQUIRED` / `SECRET_EMPTY` | unattended, a required setting neither given nor set / the env variable empty in that shell | `data.rerun` with the flags / the hidden read again in the same terminal |
|
|
134
|
+
|
|
135
|
+
## Rerun, rotate, remove
|
|
136
|
+
|
|
137
|
+
- Rerun: `pnpm everyapp signin add oidc` any time; values given replace,
|
|
138
|
+
settings not given are kept (Enter at a prompt keeps it).
|
|
139
|
+
- Rotate: a new secret at the provider, then `pnpm everyapp signin add oidc`
|
|
140
|
+
with Enter at the other prompts, or the hidden read and
|
|
141
|
+
`pnpm everyapp signin add oidc --client-secret-from-env OIDC_SIGNIN_CLIENT_SECRET --json`.
|
|
142
|
+
Then revoke the old secret there.
|
|
143
|
+
- Back to the default label: `pnpm everyapp secret unset OIDC_SIGNIN_LABEL --json`
|
|
144
|
+
(`--yes` only when the person said so).
|
|
145
|
+
- Remove, only when asked: `pnpm everyapp signin remove oidc --json --yes`
|
|
146
|
+
(`--yes` only when they said so). Accounts made with it stay. Then
|
|
147
|
+
delete the client at the provider.
|
|
148
|
+
|
|
149
|
+
## Sources
|
|
150
|
+
|
|
151
|
+
- https://openid.net/specs/openid-connect-discovery-1_0.html (the
|
|
152
|
+
`/.well-known/openid-configuration` path, a trailing `/` removed first;
|
|
153
|
+
the document's `issuer` must be identical to the issuer URL)
|
|
154
|
+
- https://openid.net/specs/openid-connect-core-1_0.html (`email_verified`,
|
|
155
|
+
`nonce`, RS256 as the default ID token algorithm)
|
|
156
|
+
- https://learn.microsoft.com/en-us/entra/identity-platform/optional-claims-reference
|
|
157
|
+
(Entra's claims: `email`, `xms_edov`, `verified_primary_email`; no
|
|
158
|
+
`email_verified`)
|
|
159
|
+
- The issuer forms in step 2 are the providers' usual ones, not checked
|
|
160
|
+
against each provider's docs today (unverified); step 3 checks the real
|
|
161
|
+
one.
|
|
162
|
+
- `docs/runbooks/sign-in-providers.md` ("Any OpenID Connect issuer",
|
|
163
|
+
"Troubleshooting") and `packages/sdk/src/internal/signInProviders.ts`
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# Slack for the Gateway
|
|
2
|
+
|
|
3
|
+
One Slack app, registered once at api.slack.com, whose OAuth client the
|
|
4
|
+
Gateway holds. Open CRM uses it: an owner or admin connects their workspace
|
|
5
|
+
from **Settings → Connections**, and the CRM's team agents post to approved
|
|
6
|
+
channels, manage those channels and match members to Slack users by email.
|
|
7
|
+
At the end the person has a Slack app in their workspace, the Gateway holds
|
|
8
|
+
its Client ID and Client Secret, and **Connect Slack** in Open CRM completes.
|
|
9
|
+
|
|
10
|
+
## Settings
|
|
11
|
+
|
|
12
|
+
| Name | Required | What it holds |
|
|
13
|
+
| --------------------- | -------- | ------------------------------------------------------------------------------------------------------ |
|
|
14
|
+
| `SLACK_CLIENT_ID` | required | The app's **Client ID** (Basic Information → App Credentials), as shown. Public; apps get it for the authorize URL. |
|
|
15
|
+
| `SLACK_CLIENT_SECRET` | required | The app's **Client Secret** from the same section, as shown. The Gateway puts it on `oauth.v2.access` only. |
|
|
16
|
+
|
|
17
|
+
Set both: the Gateway offers Slack only when it holds the two.
|
|
18
|
+
|
|
19
|
+
## Costs and plans
|
|
20
|
+
|
|
21
|
+
As of October 2026: creating a Slack app is free, no card. It needs a Slack
|
|
22
|
+
workspace the person may add apps to; Slack's free plan allows 10 app
|
|
23
|
+
installs per workspace, custom apps included. `apps.manifest.create` is Tier
|
|
24
|
+
1 (about one call a minute); `chat.postMessage` allows about one message a
|
|
25
|
+
second per channel. Slack's 2025 limit on `conversations.history` (1 request
|
|
26
|
+
a minute, 15 objects) applies to apps sold outside the Marketplace, not to
|
|
27
|
+
an internal app installed in its own workspace.
|
|
28
|
+
|
|
29
|
+
## Only a person can
|
|
30
|
+
|
|
31
|
+
- Sign in to Slack and pick the workspace. Anyone can create the app; a
|
|
32
|
+
workspace that requires approved apps turns the install into a request a
|
|
33
|
+
Workspace Owner (or an app manager they named) approves first. That
|
|
34
|
+
approval is a wait: resume at Verify 3 once they say it is approved.
|
|
35
|
+
- Create the app in the browser, or generate the app configuration token
|
|
36
|
+
that lets you create it.
|
|
37
|
+
- Copy the Client ID and Client Secret off Basic Information (path A).
|
|
38
|
+
- Click **Allow** on Slack's consent page when connecting Open CRM.
|
|
39
|
+
|
|
40
|
+
## Before you start
|
|
41
|
+
|
|
42
|
+
1. `pnpm everyapp secret list crm --json`, from any directory (the id reads
|
|
43
|
+
the manifest Open CRM's last deploy stored; in its directory the id can
|
|
44
|
+
be left off), before the Slack side, not at Verify. The rule: read
|
|
45
|
+
`data.app.setup.settings`, `unmet` and `command`. The client is one
|
|
46
|
+
setting there, `id: "slack"`, `kind: "capability"`, `holder:
|
|
47
|
+
"gateway"`, `names: ["SLACK_CLIENT_ID", "SLACK_CLIENT_SECRET"]` (an
|
|
48
|
+
OAuth client's id and secret are the two names of one setting, set
|
|
49
|
+
only when both are), `need.kind` as the CRM's manifest declares it
|
|
50
|
+
(Open CRM: `optional`: it sits in `settings` with `set: false`, never
|
|
51
|
+
in `unmet`, the CRM opens without Slack and `command`,
|
|
52
|
+
`pnpm everyapp configure crm`, offers it after the required ones;
|
|
53
|
+
`required` would put it in `unmet`, close the CRM and have configure
|
|
54
|
+
ask for both names first), `setup: "setup/slack.md"` (this file). While
|
|
55
|
+
its `set` is false this file applies.
|
|
56
|
+
The same entry in `data.app.setup.settings` with `set: true`: ask
|
|
57
|
+
before replacing (a new app, or a regenerated secret). Not deployed
|
|
58
|
+
yet: `secret list crm` stops with `APP_NOT_FOUND`; a bare
|
|
59
|
+
`pnpm everyapp secret list --json` from the checkout still says whether
|
|
60
|
+
the two names are in `data.gateway.names` (ask before replacing), and
|
|
61
|
+
the rest of this item waits for the first deploy.
|
|
62
|
+
2. `pnpm everyapp status --json`: `data.installed` and `data.claimed` are
|
|
63
|
+
true; `data.appBaseDomain` is `<app base domain>` (the redirect URL to
|
|
64
|
+
register is `https://<app>.<app base domain>/integrations/slack/callback`,
|
|
65
|
+
deployed as `crm`: `https://crm.<app base domain>/integrations/slack/callback`);
|
|
66
|
+
Open CRM's entry in `data.apps[]` (its `id`, `crm` below) has
|
|
67
|
+
`"status": "active"`, `"bound": true`, the `workerName` Verify 4 needs
|
|
68
|
+
and, as `setup`, 1's `data.app.setup` again: what else the CRM needs.
|
|
69
|
+
Not deployed yet: the URL is the same, so the Slack side can go first;
|
|
70
|
+
Verify 3 needs the CRM deployed.
|
|
71
|
+
3. In 1's answer, `data.app.workers[].names` must hold
|
|
72
|
+
`TOKEN_ENCRYPTION_KEY`, the CRM's own secret its Slack connect needs (it
|
|
73
|
+
stores the tokens encrypted under it; optional in the manifest, so its
|
|
74
|
+
entry sits in `settings` with `holder: "app"`, never in `unmet`).
|
|
75
|
+
Unset: set it first, after the CRM's first deploy, at the hidden prompt
|
|
76
|
+
of `pnpm everyapp configure crm` (which offers it with the optional
|
|
77
|
+
ones) or `pnpm everyapp secret set TOKEN_ENCRYPTION_KEY --app crm`, from
|
|
78
|
+
anywhere, 32 or more random characters, never through the chat; without
|
|
79
|
+
it **Connect Slack** stops
|
|
80
|
+
before Slack opens (the table). Not deployed yet: set it right after its
|
|
81
|
+
first deploy, before Verify 3.
|
|
82
|
+
4. Ask: which workspace; whether a Slack app for this exists already at
|
|
83
|
+
https://api.slack.com/apps (reuse it: step 2a's second paragraph); and
|
|
84
|
+
whether they have `curl` and `jq` (path B).
|
|
85
|
+
5. Pick a path. **A**: they create the app from a manifest in the browser
|
|
86
|
+
and type the client at a hidden prompt. **B**: they generate an app
|
|
87
|
+
configuration token; `apps.manifest.create` returns the client, piped in.
|
|
88
|
+
|
|
89
|
+
## Steps
|
|
90
|
+
|
|
91
|
+
1. **You:** write the manifest to a file outside the repository, say
|
|
92
|
+
`/tmp/slack-manifest.json`, with `<app>` (Open CRM's `id`) and
|
|
93
|
+
`<app base domain>` filled in. Nothing in it is secret.
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"_metadata": { "major_version": 2, "minor_version": 1 },
|
|
98
|
+
"display_information": {
|
|
99
|
+
"name": "Open CRM",
|
|
100
|
+
"description": "Team agents post to your Slack channels from Open CRM."
|
|
101
|
+
},
|
|
102
|
+
"features": { "bot_user": { "display_name": "open-crm", "always_online": false } },
|
|
103
|
+
"oauth_config": {
|
|
104
|
+
"redirect_urls": ["https://<app>.<app base domain>/integrations/slack/callback"],
|
|
105
|
+
"scopes": {
|
|
106
|
+
"bot": ["users:read", "users:read.email", "channels:read", "groups:read", "chat:write", "chat:write.public", "im:write", "channels:join", "channels:manage", "groups:write", "channels:write.invites", "groups:write.invites"],
|
|
107
|
+
"user": ["channels:read", "channels:write", "groups:read", "groups:write"]
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"settings": { "org_deploy_enabled": false, "socket_mode_enabled": false, "token_rotation_enabled": false }
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Use these lists as written: they are Open CRM's own (its
|
|
115
|
+
`src/shared/slack.ts`, which matters only when a newer CRM changes them;
|
|
116
|
+
a CRM directory with no `src/` has nothing to compare, and needs none):
|
|
117
|
+
bot scopes to post, manage channels and match members by email; user
|
|
118
|
+
scopes so the installer can invite the bot to private channels. Its
|
|
119
|
+
authorize URL carries them as `scope` (bot) and `user_scope` (user), and
|
|
120
|
+
that URL decides what Slack asks the installer for: Slack's docs name
|
|
121
|
+
only `invalid_scope` (a scope that doesn't exist, or conflicting ones)
|
|
122
|
+
and `unapproved_scope` (a published Marketplace app requesting scopes
|
|
123
|
+
its review didn't cover) on the authorize URL. The manifest's lists are
|
|
124
|
+
what the console's own Install button and a reinstall request. The
|
|
125
|
+
registry (`packages/sdk/src/internal/capabilities.ts`, the `slack`
|
|
126
|
+
entry's `scopePolicy`, #425) groups them into sets an app lists in its
|
|
127
|
+
manifest entry (`everyapp deploy` refuses `slack` without `scopes`):
|
|
128
|
+
bot sets `channels.read`, `users.read`, `users.email`,
|
|
129
|
+
`messages.write`, `channels.join`, `channels.manage`, `files.write`,
|
|
130
|
+
user set `user-channels.manage` (the table in docs/capabilities.md,
|
|
131
|
+
"OAuth clients"). Open CRM lists all but `files.write`; the lists
|
|
132
|
+
above are the provider scopes behind its sets, less
|
|
133
|
+
`chat:write.customize` (in `messages.write`; the CRM doesn't ask for
|
|
134
|
+
it). The Gateway holds the CRM to its sets at `oauth.v2.access` (#411):
|
|
135
|
+
an entry without `scopes` is refused (`integration_scopes_undeclared`),
|
|
136
|
+
an installation carrying a scope outside them too
|
|
137
|
+
(`integration_scope_exceeded`: reinstall from the CRM); a CRM deployed
|
|
138
|
+
before its manifest carried `scopes` must redeploy before the Gateway
|
|
139
|
+
is updated from main (TODO.md "OAuth scopes (2026-10-24)", follow-up 0).
|
|
140
|
+
Slack's limits: `name` 35 characters, `description` 140; `display_name`
|
|
141
|
+
takes a-z, 0-9, `-`, `_` and `.` only. Step 3b validates the file first.
|
|
142
|
+
|
|
143
|
+
2a. **Person (path A):** open https://api.slack.com/apps, signed in to
|
|
144
|
+
Slack: **Create New App** (https://api.slack.com/apps?new_app=1 opens
|
|
145
|
+
that dialog) → **From an app manifest** (older dialogs: **From a
|
|
146
|
+
manifest**) → pick the workspace → **Next** → the **JSON** tab, paste
|
|
147
|
+
the file's content → **Next** (the review lists the name, the redirect
|
|
148
|
+
URL and the scopes) → **Create**. It lands on **Basic Information**,
|
|
149
|
+
whose **App Credentials** section shows **Client ID** and a hidden
|
|
150
|
+
**Client Secret**; 3a uses both.
|
|
151
|
+
Reusing an app: **OAuth & Permissions**, add the redirect URL under
|
|
152
|
+
**Redirect URLs** and save; under **Scopes**, add the bot scopes to **Bot
|
|
153
|
+
Token Scopes** (**Add an OAuth Scope**) and the user scopes to **User
|
|
154
|
+
Token Scopes**. Or, with path B's token, `apps.manifest.update` with
|
|
155
|
+
`app_id=` and the same manifest; it returns no credentials.
|
|
156
|
+
|
|
157
|
+
3a. **Person (path A):** in a second terminal, in the `every-app` folder:
|
|
158
|
+
`pnpm everyapp configure crm` (it asks for the client's two names, id
|
|
159
|
+
then secret, with hidden prompts, after the CRM's required settings;
|
|
160
|
+
the client is optional for Open CRM, so Yes to `Set the N optional
|
|
161
|
+
settings too?`, Enter skipping the others; with one of the two
|
|
162
|
+
names set it asks only for the other; or pick
|
|
163
|
+
`SLACK_CLIENT_ID + SLACK_CLIENT_SECRET` from the menu once the CRM is
|
|
164
|
+
ready) or `pnpm everyapp secret set SLACK_CLIENT_ID SLACK_CLIENT_SECRET`
|
|
165
|
+
for just these two (README's first way; no `--json`, so it prompts).
|
|
166
|
+
Paste the Client ID at the first prompt; click **Show** next to Client
|
|
167
|
+
Secret and paste it at the second. Nothing is echoed. Give 2a and 3a in
|
|
168
|
+
one message and wait once, until they say both values are in (a create
|
|
169
|
+
that failed, they report instead). Go to Verify.
|
|
170
|
+
|
|
171
|
+
2b. **Person (path B):** open https://api.slack.com/apps; below the list of
|
|
172
|
+
apps, under **Your App Configuration Tokens**, click **Generate Token**,
|
|
173
|
+
pick the workspace, **Generate**; copy the **Access Token** (it starts
|
|
174
|
+
`xoxe.xoxp-`, expires 12 hours later, and adds "Slack Tooling Tokens
|
|
175
|
+
Vendor" to the workspace). In their terminal, in the `every-app` folder,
|
|
176
|
+
read it in hidden (README's third way), never on a command line or in a
|
|
177
|
+
typed `export …=`, which the shell history keeps: bash or zsh
|
|
178
|
+
`read -rs SLACK_CONFIG_TOKEN && export SLACK_CONFIG_TOKEN`, fish
|
|
179
|
+
`read -s -x SLACK_CONFIG_TOKEN`; they paste the token and press Enter;
|
|
180
|
+
nothing is echoed. Wait until they say it is exported.
|
|
181
|
+
|
|
182
|
+
3b. **Person (path B)**, or **You** when they exported it before starting
|
|
183
|
+
you: one line at a time. `printf` keeps the token out of the process
|
|
184
|
+
list; `-d @-` reads it from stdin.
|
|
185
|
+
|
|
186
|
+
```sh
|
|
187
|
+
printf 'token=%s' "$SLACK_CONFIG_TOKEN" | curl -s -d @- --data-urlencode manifest@/tmp/slack-manifest.json https://slack.com/api/apps.manifest.validate
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Expect `{"ok":true,"errors":[]}`; `invalid_manifest` names each problem
|
|
191
|
+
in `errors[].pointer`. Then, once only (each run makes another app). The
|
|
192
|
+
answer carries three secrets (`client_secret`, `signing_secret`,
|
|
193
|
+
`verification_token`), so it goes to a file only you and `jq` read,
|
|
194
|
+
created private first, and removed on the same line as the last pipe:
|
|
195
|
+
|
|
196
|
+
```sh
|
|
197
|
+
install -m 600 /dev/null /tmp/slack-app.json
|
|
198
|
+
printf 'token=%s' "$SLACK_CONFIG_TOKEN" | curl -s -d @- --data-urlencode manifest@/tmp/slack-manifest.json https://slack.com/api/apps.manifest.create > /tmp/slack-app.json
|
|
199
|
+
jq -e '.ok' /tmp/slack-app.json && jq '.app_id' /tmp/slack-app.json
|
|
200
|
+
jq -r '.credentials.client_secret // empty' /tmp/slack-app.json | pnpm everyapp secret set SLACK_CLIENT_SECRET --json
|
|
201
|
+
jq -r '.credentials.client_id // empty' /tmp/slack-app.json | pnpm everyapp secret set SLACK_CLIENT_ID --json; rm -f /tmp/slack-app.json
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
README's second way. `jq -e '.ok'` printing `false`: `jq '.error'`
|
|
205
|
+
names why (step 1, or the name is taken); stop and
|
|
206
|
+
`rm -f /tmp/slack-app.json`. `// empty` keeps a missing field from
|
|
207
|
+
going in as the string `null`: the pipe is empty and `secret set` stops
|
|
208
|
+
with `SECRET_VALUE_REQUIRED`. Never print the file, and remove it even
|
|
209
|
+
when a step failed. Each `secret set` ends with a `done` event. Keep the
|
|
210
|
+
`app_id` for Verify.
|
|
211
|
+
|
|
212
|
+
## Verify
|
|
213
|
+
|
|
214
|
+
1. `pnpm everyapp secret list crm --json`, from anywhere: the `slack`
|
|
215
|
+
setting has `set: true` (optional for Open CRM, so it was never in
|
|
216
|
+
`data.app.setup.unmet`; a manifest that requires it has it leave
|
|
217
|
+
`unmet`, with `ready: true` and `command` null once nothing else is
|
|
218
|
+
missing); both names in `data.gateway.names`, `"set": true` in
|
|
219
|
+
`data.gateway.needed`, not in `data.gateway.missing`. Then
|
|
220
|
+
`pnpm everyapp status --json`: the `crm` entry's `setup.ready` is true
|
|
221
|
+
and `data.fixes` has no `APP_NEEDS_SETUP` for it (the Gateway opens the
|
|
222
|
+
CRM again).
|
|
223
|
+
2. Smoke check, in the person's terminal while `SLACK_CONFIG_TOKEN` is
|
|
224
|
+
exported (path B), after `export SLACK_APP_ID=<app_id>` (fish:
|
|
225
|
+
`set -x SLACK_APP_ID <app_id>`; the id isn't secret):
|
|
226
|
+
|
|
227
|
+
```sh
|
|
228
|
+
printf 'token=%s&app_id=%s' "$SLACK_CONFIG_TOKEN" "$SLACK_APP_ID" | curl -s -d @- https://slack.com/api/apps.manifest.export | jq '.manifest.oauth_config'
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Expected: `redirect_urls` holds exactly
|
|
232
|
+
`https://<app>.<app base domain>/integrations/slack/callback`, and
|
|
233
|
+
`scopes.bot` and `scopes.user` the lists in step 1. Then
|
|
234
|
+
`unset SLACK_CONFIG_TOKEN SLACK_APP_ID` (fish: `set -e`). Path A: the
|
|
235
|
+
same is on the app's **OAuth & Permissions** page.
|
|
236
|
+
3. The app, deployed and open: 1's `data.app.setup.ready` true. While
|
|
237
|
+
another required setting of the CRM is unmet, the Gateway sends a
|
|
238
|
+
browser to its setup dialog instead of the CRM (anything else gets 503
|
|
239
|
+
`app_needs_setup`): finish those first (each `unmet` entry's `setup`
|
|
240
|
+
names its file); until then 1 is the check of the Slack client. Then
|
|
241
|
+
at `https://<app>.<app base domain>`, **Settings →
|
|
242
|
+
Connections → Slack** (an owner or admin). With the client on the
|
|
243
|
+
Gateway the card has **Connect Slack**; without it the section reads
|
|
244
|
+
**Slack is not configured** and, for an owner or admin, shows the
|
|
245
|
+
redirect URL and `pnpm everyapp secret set SLACK_CLIENT_ID SLACK_CLIENT_SECRET`
|
|
246
|
+
(Verify 1 again; the same when the deployed CRM doesn't list `slack`,
|
|
247
|
+
or lists it without `scopes`).
|
|
248
|
+
**Connect Slack**: Slack's page names the app and the workspace and
|
|
249
|
+
lists the permissions; **Allow**; the CRM comes back connected and shows
|
|
250
|
+
the workspace. Otherwise its page says why, `?error=` naming one of
|
|
251
|
+
seven: `access_denied` (Cancel was clicked), `invalid_state` (the
|
|
252
|
+
approval is older than ten minutes, was used already, or was started by
|
|
253
|
+
someone else: start again), `forbidden` (not an owner or admin),
|
|
254
|
+
`rejected` (Slack sent another error, or no code: try again),
|
|
255
|
+
`exchange_failed` (the token call returned no token: Verify 4, then the
|
|
256
|
+
table), `failed` (the CRM couldn't store the tokens: its log has
|
|
257
|
+
`[slack] connect failed`), `not_configured` (local dev only: the CRM
|
|
258
|
+
has no Gateway binding).
|
|
259
|
+
4. Logs. Slack's error code is in Open CRM's Worker, not the Gateway's: the
|
|
260
|
+
Cloudflare dashboard, README's path with the CRM's Worker in place of
|
|
261
|
+
the Gateway's: **Workers & Pages** → the `crm` entry's `workerName` →
|
|
262
|
+
**Observability** (**Logs** on older accounts) → **Live** (Workers Logs keeps them; `cf workers` has
|
|
263
|
+
no tail, and `cf o11y telemetry query` needs a query body). On
|
|
264
|
+
`exchange_failed` the line `[slack] connect refused` carries `status`
|
|
265
|
+
and `reason`: Slack's code (`invalid_code`, `bad_redirect_uri`,
|
|
266
|
+
`invalid_client_id`, `bad_client_secret`: the table), or a Gateway
|
|
267
|
+
refusal that reached the callback (`status` 503, reason
|
|
268
|
+
`Service Unavailable`: `integration_not_configured`; 403, `Forbidden`:
|
|
269
|
+
`capability_not_requested`, `integration_scopes_undeclared` (the
|
|
270
|
+
deployed CRM lists `slack` without `scopes`) or
|
|
271
|
+
`integration_scope_exceeded` (the installation carries a scope outside
|
|
272
|
+
the CRM's sets: reinstall from the CRM)). The Gateway's own log (`every-app-gateway`,
|
|
273
|
+
same path) has one `integration.proxy.call` per token call:
|
|
274
|
+
`capability`, host, path, method, `grantType` (the request's
|
|
275
|
+
`grant_type`) and `status`; never the body, and Slack answers `200` with
|
|
276
|
+
`"ok": false` on a bad client, so it says only whether the call went
|
|
277
|
+
through.
|
|
278
|
+
|
|
279
|
+
## If it fails
|
|
280
|
+
|
|
281
|
+
| Error or symptom | Meaning | What to do |
|
|
282
|
+
| ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
283
|
+
| `SECRET_VALUE_REQUIRED` | Nothing was piped (path B: the field was missing, so `// empty` piped nothing) and there is no terminal to prompt at | `jq '.error' /tmp/slack-app.json`; or one of README's three ways (its `data.rerun` shows the `--from-env` form) |
|
|
284
|
+
| `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 3b, or the person's terminal (3a) |
|
|
285
|
+
| `SECRET_SEVERAL_NAMES` | A pipe or `--from-env` with two names | One name per call (the fix in `data.fix`) |
|
|
286
|
+
| `SECRET_NOT_DECLARED` with `data.didYouMean` | The name differs from a known one in case or underscores only | 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` |
|
|
287
|
+
| `SECRET_NOT_DECLARED` without it | A misspelt name; in Open CRM's directory its `data.fix` offers to add it to the CRM's own `secrets` | Retype the name from Settings above; never add a `SLACK_*` name to Open CRM's `secrets` |
|
|
288
|
+
| CRM: **Slack is not configured**, the `secret set` command, no **Connect Slack** | The Gateway holds no client, or one of the two names (a call would get `integration_not_configured`); or the deployed CRM doesn't list `slack` with `access: "write"` and `scopes` (`capability_not_requested`; without `scopes`, `integration_scopes_undeclared`) | `secret set` both, then Verify 1; or its `everyapp.config.ts` (`slack: { access: "write", scopes: [...], why }`), then `pnpm everyapp deploy --json` in its directory |
|
|
289
|
+
| CRM: `exchange_failed`; its log `[slack] connect refused` with `status` 503 or 403 | The Gateway refused between **Connect Slack** and the callback: the client was unset, or the manifest changed, meanwhile | The row above, then connect again |
|
|
290
|
+
| CRM: **Connect Slack** fails before Slack opens: "Connections aren't set up on this CRM yet" | The CRM's own `TOKEN_ENCRYPTION_KEY` is unset, or under 32 characters | 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 (Before you start 3); connect again |
|
|
291
|
+
| Slack's page: "redirect_uri did not match any configured URIs", then `Passed URI: …` | The URI the CRM sent isn't registered on the app exactly, or under a registered URL | Register the Passed URI, `https://<app>.<app base domain>/integrations/slack/callback` (https only, no `#`), under OAuth & Permissions → Redirect URLs; Verify 2 |
|
|
292
|
+
| `bad_redirect_uri` (token call) | `redirect_uri` differed between the authorize and token calls | The CRM sends its own host; check the app runs on `https://<app>.<app base domain>` |
|
|
293
|
+
| `invalid_scope` | A scope name doesn't exist, or bot and user scopes are mixed up | Compare with step 1; bot scopes go in `scope`, user scopes in `user_scope` |
|
|
294
|
+
| `invalid_client_id` | `SLACK_CLIENT_ID` is wrong, or another app's | Set it again from Basic Information → App Credentials |
|
|
295
|
+
| `bad_client_secret` | `SLACK_CLIENT_SECRET` is wrong or was regenerated | Set it again (Rotate, below) |
|
|
296
|
+
| `invalid_code` | The code was used already or is older than ten minutes; also when `SLACK_CLIENT_ID` is another app's (Slack checks the code before the client) | Connect again and **Allow**; a second failure: compare the Client ID on Basic Information |
|
|
297
|
+
| CRM: `access_denied` | The person clicked Cancel on Slack's page | Connect again and **Allow** |
|
|
298
|
+
| `invalid_team_for_non_distributed_app` | Installing into a workspace other than the one the app was created in | Create the app in that workspace, or **Manage Distribution → Activate Public Distribution** |
|
|
299
|
+
| `token_expired`, `invalid_auth`, `not_authed` from `apps.manifest.*` | The configuration token is past 12 hours, wrong, or missing from the body | Generate a new one (step 2b) |
|
|
300
|
+
| `invalid_manifest` with `errors[]` | The manifest breaks Slack's schema | Fix what each `pointer` names; validate again |
|
|
301
|
+
| CRM: "Slack refused: a permission is missing. Reconnect Slack." | The workspace's install predates a scope | Disconnect and connect again, so Slack asks for the new scopes |
|
|
302
|
+
|
|
303
|
+
## Rerun, rotate, remove
|
|
304
|
+
|
|
305
|
+
- **Rerun.** `configure` and `secret set` replace. Never run `apps.manifest.create` twice
|
|
306
|
+
for one app: update the existing one (step 2a's second paragraph, or
|
|
307
|
+
`apps.manifest.update`). A duplicate it made: with `SLACK_CONFIG_TOKEN`
|
|
308
|
+
and the duplicate's id exported, and only when the person said to delete
|
|
309
|
+
it (it is permanent),
|
|
310
|
+
`printf 'token=%s&app_id=%s' "$SLACK_CONFIG_TOKEN" "$SLACK_APP_ID" | curl -s -d @- https://slack.com/api/apps.manifest.delete`
|
|
311
|
+
(expect `{"ok":true}`). Path A, or a token past 12 hours: its **Basic
|
|
312
|
+
Information** page.
|
|
313
|
+
- **Rotate.** Basic Information → App Credentials → **Regenerate** next to
|
|
314
|
+
**Client Secret** (Slack's FAQ still says **Change secret**). The old
|
|
315
|
+
secret works for 24 hours unless revoked manually. The person sets
|
|
316
|
+
`SLACK_CLIENT_SECRET` again at the hidden prompt (step 3a); no API
|
|
317
|
+
returns the new secret.
|
|
318
|
+
- **Remove.** In Open CRM, **Settings → Connections → Disconnect Slack**.
|
|
319
|
+
Then `pnpm everyapp secret unset SLACK_CLIENT_ID SLACK_CLIENT_SECRET --json --yes`
|
|
320
|
+
(`--yes` only when the person said so). Then delete the app: the
|
|
321
|
+
`apps.manifest.delete` line above (path B, token still valid, person
|
|
322
|
+
said so) or https://api.slack.com/apps → the app → Basic Information; or
|
|
323
|
+
at least regenerate its secret: a client the Gateway no longer holds is
|
|
324
|
+
still a client.
|
|
325
|
+
|
|
326
|
+
## Sources
|
|
327
|
+
|
|
328
|
+
- https://docs.slack.dev/app-manifests/configuring-apps-with-app-manifests (manifest flow; configuration tokens)
|
|
329
|
+
- https://docs.slack.dev/reference/app-manifest (fields and limits)
|
|
330
|
+
- https://docs.slack.dev/reference/methods/apps.manifest.create, …/apps.manifest.validate, …/apps.manifest.export, …/apps.manifest.delete (permanent; `token` and `app_id`)
|
|
331
|
+
- https://docs.slack.dev/tools/bolt-python/tutorial/custom-steps-workflow-builder-new/ (the create-from-manifest clicks: Create New App, From an app manifest, Next, JSON, Create)
|
|
332
|
+
- https://docs.slack.dev/authentication/installing-with-oauth/ (authorize parameters, redirect rules: https, no `#`, a subdirectory matches; `invalid_scope`, `unapproved_scope`, `bad_redirect_uri`; the code expires after ten minutes)
|
|
333
|
+
- https://docs.slack.dev/legacy/legacy-authentication/ (`error=access_denied` on Cancel; codes exchanged once, expire 10 minutes)
|
|
334
|
+
- https://docs.slack.dev/reference/methods/oauth.v2.access/ (token-call errors)
|
|
335
|
+
- https://docs.slack.dev/reference/scopes/channels.write (User only) and …/channels.manage (Bot only)
|
|
336
|
+
- https://docs.slack.dev/distribution/ (own workspace only until **Activate Public Distribution**)
|
|
337
|
+
- https://slack.com/help/articles/1500009181142 (no setting stops members creating internal apps) and https://slack.com/help/articles/222386767 (app approval: Workspace Owners and app managers approve requests)
|
|
338
|
+
- https://docs.slack.dev/authentication/verifying-requests-from-slack/ (**Regenerate**; the old secret valid 24 hours unless revoked) and https://docs.slack.dev/faq/ (**Change secret**)
|
|
339
|
+
- https://docs.slack.dev/apis/web-api/rate-limits and https://docs.slack.dev/changelog/2025/05/29/rate-limit-changes-for-non-marketplace-apps
|
|
340
|
+
- https://slack.com/help/articles/27204752526611 (free plan: 10 app installs)
|
|
341
|
+
- https://community.n8n.io/t/slack-oauth-redirect-uri-did-not-match-any-configured-uris-self-hosted-on-windows/271936 (the redirect error's wording, with its `Passed URI:` line)
|
|
342
|
+
- https://developers.cloudflare.com/workers/observability/logs/real-time-logs/ (Workers & Pages → Overview → the Worker → Logs → Live)
|
|
343
|
+
- https://api.slack.com/apps (the console; `?new_app=1` opens the create dialog)
|
|
344
|
+
- Local, 2026-10-24: `packages/sdk/src/internal/capabilities.ts` (the `slack` entry: its `scopePolicy`, seven bot sets and one user set, #425), docs/capabilities.md ("OAuth clients": the scope sets table), TODO.md ("OAuth scopes (2026-10-24)", the Slack bullet)
|