@open-everywhere/provider-slack 0.1.3 → 0.1.4
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/agent/access/slack-attachment-fetch.ts +3 -1
- package/agent/app-manifest.ts +48 -10
- package/agent/channels/slack-commands.ts +5 -1
- package/agent/channels/slack-events.ts +13 -4
- package/agent/channels/slack-front.ts +21 -6
- package/agent/channels/slack-read.ts +9 -3
- package/agent/channels/slack-setup-page.ts +20 -0
- package/agent/channels/slack-web-api.ts +34 -7
- package/agent/channels/slack.ts +11 -3
- package/agent/contributions.ts +1 -0
- package/agent/docs-generated.ts +1 -1
- package/agent/ops.ts +29 -61
- package/agent/register.ts +27 -1
- package/agent/setup/claim.ts +343 -0
- package/agent/setup/credentials.ts +265 -0
- package/agent/setup/eve-credentials.ts +77 -0
- package/agent/setup/install-state.ts +248 -0
- package/agent/setup/page.ts +579 -0
- package/agent/setup/slack-api-setup.ts +176 -0
- package/agent/status.ts +81 -0
- package/dist/agent/access/slack-attachment-fetch.d.ts.map +1 -1
- package/dist/agent/access/slack-attachment-fetch.js +3 -1
- package/dist/agent/access/slack-attachment-fetch.js.map +1 -1
- package/dist/agent/app-manifest.d.ts +4 -4
- package/dist/agent/app-manifest.d.ts.map +1 -1
- package/dist/agent/app-manifest.js +48 -10
- package/dist/agent/app-manifest.js.map +1 -1
- package/dist/agent/channels/slack-commands.d.ts.map +1 -1
- package/dist/agent/channels/slack-commands.js +5 -1
- package/dist/agent/channels/slack-commands.js.map +1 -1
- package/dist/agent/channels/slack-events.d.ts +1 -1
- package/dist/agent/channels/slack-events.d.ts.map +1 -1
- package/dist/agent/channels/slack-events.js +13 -4
- package/dist/agent/channels/slack-events.js.map +1 -1
- package/dist/agent/channels/slack-front.d.ts +1 -1
- package/dist/agent/channels/slack-front.d.ts.map +1 -1
- package/dist/agent/channels/slack-front.js +20 -5
- package/dist/agent/channels/slack-front.js.map +1 -1
- package/dist/agent/channels/slack-read.d.ts +6 -1
- package/dist/agent/channels/slack-read.d.ts.map +1 -1
- package/dist/agent/channels/slack-read.js +9 -3
- package/dist/agent/channels/slack-read.js.map +1 -1
- package/dist/agent/channels/slack-setup-page.d.ts +3 -0
- package/dist/agent/channels/slack-setup-page.d.ts.map +1 -0
- package/dist/agent/channels/slack-setup-page.js +13 -0
- package/dist/agent/channels/slack-setup-page.js.map +1 -0
- package/dist/agent/channels/slack-web-api.d.ts +3 -0
- package/dist/agent/channels/slack-web-api.d.ts.map +1 -1
- package/dist/agent/channels/slack-web-api.js +26 -6
- package/dist/agent/channels/slack-web-api.js.map +1 -1
- package/dist/agent/channels/slack.d.ts.map +1 -1
- package/dist/agent/channels/slack.js +11 -3
- package/dist/agent/channels/slack.js.map +1 -1
- package/dist/agent/contributions.d.ts +5 -0
- package/dist/agent/contributions.d.ts.map +1 -1
- package/dist/agent/contributions.js +1 -0
- package/dist/agent/contributions.js.map +1 -1
- package/dist/agent/docs-generated.js +1 -1
- package/dist/agent/docs-generated.js.map +1 -1
- package/dist/agent/ops.d.ts +1 -1
- package/dist/agent/ops.d.ts.map +1 -1
- package/dist/agent/ops.js +29 -47
- package/dist/agent/ops.js.map +1 -1
- package/dist/agent/register.d.ts.map +1 -1
- package/dist/agent/register.js +26 -2
- package/dist/agent/register.js.map +1 -1
- package/dist/agent/setup/claim.d.ts +73 -0
- package/dist/agent/setup/claim.d.ts.map +1 -0
- package/dist/agent/setup/claim.js +298 -0
- package/dist/agent/setup/claim.js.map +1 -0
- package/dist/agent/setup/credentials.d.ts +56 -0
- package/dist/agent/setup/credentials.d.ts.map +1 -0
- package/dist/agent/setup/credentials.js +214 -0
- package/dist/agent/setup/credentials.js.map +1 -0
- package/dist/agent/setup/eve-credentials.d.ts +22 -0
- package/dist/agent/setup/eve-credentials.d.ts.map +1 -0
- package/dist/agent/setup/eve-credentials.js +73 -0
- package/dist/agent/setup/eve-credentials.js.map +1 -0
- package/dist/agent/setup/install-state.d.ts +139 -0
- package/dist/agent/setup/install-state.d.ts.map +1 -0
- package/dist/agent/setup/install-state.js +196 -0
- package/dist/agent/setup/install-state.js.map +1 -0
- package/dist/agent/setup/page.d.ts +29 -0
- package/dist/agent/setup/page.d.ts.map +1 -0
- package/dist/agent/setup/page.js +460 -0
- package/dist/agent/setup/page.js.map +1 -0
- package/dist/agent/setup/slack-api-setup.d.ts +62 -0
- package/dist/agent/setup/slack-api-setup.d.ts.map +1 -0
- package/dist/agent/setup/slack-api-setup.js +138 -0
- package/dist/agent/setup/slack-api-setup.js.map +1 -0
- package/dist/agent/status.d.ts +4 -0
- package/dist/agent/status.d.ts.map +1 -0
- package/dist/agent/status.js +79 -0
- package/dist/agent/status.js.map +1 -0
- package/package.json +36 -3
package/dist/agent/ops.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ops.js","sourceRoot":"","sources":["../../agent/ops.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"ops.js","sourceRoot":"","sources":["../../agent/ops.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,4FAA4F;AAC5F,+FAA+F;AAC/F,+FAA+F;AAC/F,uEAAuE;AACvE,EAAE;AACF,+FAA+F;AAC/F,8FAA8F;AAC9F,EAAE;AACF,iGAAiG;AACjG,kEAAkE;AAClE,EAAE;AACF,kGAAkG;AAClG,mGAAmG;AACnG,6EAA6E;AAC7E,iGAAiG;AACjG,4FAA4F;AAC5F,gGAAgG;AAChG,0DAA0D;AAC1D,EAAE;AACF,mGAAmG;AACnG,qGAAqG;AACrG,qGAAqG;AACrG,kGAAkG;AAClG,mGAAmG;AACnG,oGAAoG;AACpG,gDAAgD;AAEhD,OAAO,EAAE,0BAA0B,EAAE,8BAA8B,EAAE,MAAM,gDAAgD,CAAC;AAC5H,OAAO,EACL,qBAAqB,EACrB,0BAA0B,EAC1B,wBAAwB,EACxB,kBAAkB,EAClB,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAE3B,mFAAmF;AACnF,MAAM,UAAU,gBAAgB;IAC9B,iGAAiG;IACjG,uFAAuF;IACvF,qEAAqE;IACrE,0BAA0B,CAAC,OAAO,EAAE;QAClC,MAAM,EAAE,MAAM;QACd,UAAU,EAAE,qBAAqB;QACjC,KAAK,EAAE,wBAAwB;QAC/B,OAAO,EAAE,0BAA0B;QACnC,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC;QAClD,UAAU,EAAE,kBAAkB;KAC/B,CAAC,CAAC;IACH,uGAAuG;IACvG,sGAAsG;IACtG,+FAA+F;IAC/F,0FAA0F;IAC1F,kGAAkG;IAClG,8BAA8B,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC;AACtF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../agent/register.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../agent/register.ts"],"names":[],"mappings":"AA+EA;;+DAE+D;AAC/D,wBAAgB,qBAAqB,IAAI,IAAI,CAuF5C"}
|
package/dist/agent/register.js
CHANGED
|
@@ -25,9 +25,15 @@
|
|
|
25
25
|
// internal-channel gate, the DM-partner membership verdict, the
|
|
26
26
|
// readable-channel list, and the channel object the routine/webhook
|
|
27
27
|
// dispatchers `receive()` into) — core channels/slack-bridge.ts
|
|
28
|
-
// • ops arms —
|
|
29
|
-
|
|
28
|
+
// • ops arms — the paste-ready app manifest + telemetry creds probe (./ops.ts)
|
|
29
|
+
// • setup-code boot notice — logs the one-time `/setup` claim code while Slack is not installed
|
|
30
|
+
// (OPE-2231; ./setup/claim.ts)
|
|
31
|
+
// • setup bridge arms — the install record core's status reader asks for, and the reopen the
|
|
32
|
+
// `reopen_slack_setup` control verb calls (OPE-2231)
|
|
33
|
+
// • status reporter — the `surfaces.slack` arm of GET /eve/v1/oe/status (./status.ts)
|
|
34
|
+
import { registerIdentityMapper, registerCapabilityManifest, registerSurfaceParity, registerProviderEvents, registerPostArm, registerWakeOriginCodec, registerReadAdapter, registerSeatCatchUp, registerReactionSurface, registerBotDisplayName, registerAccountDisplayName, registerAccountDirectory, registerSlackBridge, registerProviderDocs, registerWebhookVerifier, registerBootNotice, registerSurfaceStatusReporter, } from "@open-everywhere/core/agent/adapter-api.js";
|
|
30
35
|
import { PROVIDER_DOCS } from "./docs-generated.js";
|
|
36
|
+
import { slackSurfaceStatus } from "./status.js";
|
|
31
37
|
import { SLACK_SURFACE_PARITY } from "./surface-parity.js";
|
|
32
38
|
import { SLACK_PROVIDER_EVENTS } from "./provider-events.js";
|
|
33
39
|
import { bindSlack, partnerFactsFromInfo } from "./channels/slack-identity.js";
|
|
@@ -40,6 +46,8 @@ import { slackForm, fetchUserDisplayName, fetchChannelDisplayName, fetchUserProf
|
|
|
40
46
|
import slackChannel from "./channels/slack.js";
|
|
41
47
|
import { slackWebhookVerifier } from "./channels/slack-webhook-verifier.js";
|
|
42
48
|
import { registerSlackOps } from "./ops.js";
|
|
49
|
+
import { announceSlackSetupCode } from "./setup/claim.js";
|
|
50
|
+
import { reopenSlackSetup, slackInstallState } from "./setup/install-state.js";
|
|
43
51
|
/** Perform ALL of the slack bundle's registrations. Idempotent (each register call replaces);
|
|
44
52
|
* cheap and side-effect-free beyond the registry writes (every live Slack call inside the
|
|
45
53
|
* registered arms resolves its token lazily at call time). */
|
|
@@ -102,8 +110,18 @@ export function registerSlackProvider() {
|
|
|
102
110
|
confirmChannelInternal,
|
|
103
111
|
partnerFactsFromInfo,
|
|
104
112
|
channel: slackChannel,
|
|
113
|
+
// THE TWO OPE-2231 SETUP ARMS. `installState` is what core's status reader (and OPE-2230's
|
|
114
|
+
// status endpoint through it) asks for — ids and timestamps, never a credential. `reopenSetup`
|
|
115
|
+
// is the admin recovery path the `reopen_slack_setup` control verb calls. Both ride the bridge
|
|
116
|
+
// because core must not import this bundle, and both fail closed to "no Slack install" when a
|
|
117
|
+
// bundle registers neither.
|
|
118
|
+
installState: slackInstallState,
|
|
119
|
+
reopenSetup: reopenSlackSetup,
|
|
105
120
|
});
|
|
106
121
|
registerSlackOps();
|
|
122
|
+
// The deployment-status surface's slack arm (OPE-2230) — HERE and not in ops.ts, because it
|
|
123
|
+
// reads the operational store and ops.ts is the light-door half the standalone MCP server loads.
|
|
124
|
+
registerSurfaceStatusReporter("slack", () => slackSurfaceStatus());
|
|
107
125
|
// Webhook-verifier plane (OPE-164, populated for slack by OPE-197): Slack's `v0=` HMAC scheme, so
|
|
108
126
|
// a GENERIC hook can declare `source: "slack"` and be verified the way Slack signs. Changes no
|
|
109
127
|
// existing route — `/eve/v1/slack` and `/eve/v1/slack/commands` keep their own inline gate over
|
|
@@ -114,5 +132,11 @@ export function registerSlackProvider() {
|
|
|
114
132
|
// compiled into ./docs-generated.ts because docs/ is not on disk at runtime). De-select the bundle
|
|
115
133
|
// and the module leaves the import graph; registerProviderDocs re-checks the selection gate anyway.
|
|
116
134
|
registerProviderDocs("slack", PROVIDER_DOCS);
|
|
135
|
+
// THE SETUP-CODE BOOT NOTICE (OPE-2231, core boot/notices.ts). While no Slack install is recorded,
|
|
136
|
+
// the deployment logs a one-time setup code so whoever can read its logs can claim `/setup`. The
|
|
137
|
+
// notice is REGISTERED here — a map write, as cheap as every line above — and RUN by the app's boot
|
|
138
|
+
// checks, because minting the code reads the encrypted vault and this function is sync. Silent once
|
|
139
|
+
// an install exists: a deployment past setup keeps no live code in its logs.
|
|
140
|
+
registerBootNotice("slack-setup-code", announceSlackSetupCode);
|
|
117
141
|
}
|
|
118
142
|
//# sourceMappingURL=register.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../agent/register.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,mGAAmG;AACnG,kGAAkG;AAClG,gGAAgG;AAChG,iGAAiG;AACjG,EAAE;AACF,wBAAwB;AACxB,0FAA0F;AAC1F,mFAAmF;AACnF,+FAA+F;AAC/F,2FAA2F;AAC3F,+FAA+F;AAC/F,0FAA0F;AAC1F,2FAA2F;AAC3F,wEAAwE;AACxE,mGAAmG;AACnG,4EAA4E;AAC5E,6FAA6F;AAC7F,gFAAgF;AAChF,6FAA6F;AAC7F,yFAAyF;AACzF,4FAA4F;AAC5F,+FAA+F;AAC/F,gGAAgG;AAChG,6FAA6F;AAC7F,iGAAiG;AACjG,6FAA6F;AAC7F,+FAA+F;AAE/F,OAAO,EACL,sBAAsB,EACtB,0BAA0B,EAC1B,qBAAqB,EACrB,sBAAsB,EACtB,eAAe,EACf,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,0BAA0B,EAC1B,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACpB,uBAAuB,
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../agent/register.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type EncryptedKvVault } from "@open-everywhere/core/agent/adapter-api.js";
|
|
2
|
+
import { type SlackInstallStateDeps } from "./install-state.js";
|
|
3
|
+
/** The cookie the page sets once the code matches. `__Host-` pins it to this exact host with no
|
|
4
|
+
* domain and a `/` path, which a subdomain cannot overwrite. */
|
|
5
|
+
export declare const SETUP_COOKIE_NAME = "__Host-oe-setup";
|
|
6
|
+
/** How long a claimed run stays claimed. Long enough to walk the page and Slack's OAuth consent,
|
|
7
|
+
* short enough that an unattended browser on a shared machine is not a standing claim. */
|
|
8
|
+
export declare const SETUP_COOKIE_TTL_MS: number;
|
|
9
|
+
/** Injectable seams for tests. `now` is the clock the cookie's expiry is read against. */
|
|
10
|
+
export interface SlackClaimDeps extends SlackInstallStateDeps {
|
|
11
|
+
readonly vault?: EncryptedKvVault;
|
|
12
|
+
readonly now?: () => number;
|
|
13
|
+
/** Override `process.env` — the lock reads it to close on an env-configured deployment. */
|
|
14
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
15
|
+
/**
|
|
16
|
+
* Use the lock-verdict cache even though a vault is injected. TEST SEAM ONLY.
|
|
17
|
+
*
|
|
18
|
+
* 🔴 SAME REASON AS `SlackCredentialDeps.memo`. An injected vault normally bypasses the cache, which
|
|
19
|
+
* the suite leans on — a test writes an install record and the next call sees it. That also made
|
|
20
|
+
* {@link LOCK_TTL_MS} undrivable by any test, and a TTL nobody can drive is a claim nobody has
|
|
21
|
+
* checked. This one gates a PUBLIC route, so it is the last place to take that on faith. Nothing in
|
|
22
|
+
* `agent/**` passes it.
|
|
23
|
+
*/
|
|
24
|
+
readonly lockCache?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** Test seam: forget cached claim secrets. */
|
|
27
|
+
export declare function resetSlackClaimSecretCache(): void;
|
|
28
|
+
/**
|
|
29
|
+
* The deployment's setup code, minting it on first use. SECRET — the ONLY place it may be rendered
|
|
30
|
+
* is the boot log line in {@link announceSlackSetupCode}.
|
|
31
|
+
*/
|
|
32
|
+
export declare function slackSetupCode(deps?: SlackClaimDeps): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Is `/setup` OPEN right now? True only while NO install is recorded.
|
|
35
|
+
*
|
|
36
|
+
* Every route on the page calls this FIRST and 404s on false, so a locked deployment's `/setup` is
|
|
37
|
+
* indistinguishable from a deployment that never had the page — the same posture
|
|
38
|
+
* `credentials/intake-page.ts` takes on a disarmed vault.
|
|
39
|
+
*/
|
|
40
|
+
export declare function slackSetupOpen(deps?: SlackClaimDeps): Promise<boolean>;
|
|
41
|
+
/** Test seam: forget the cached lock verdict. */
|
|
42
|
+
export declare function resetSlackSetupLockCache(): void;
|
|
43
|
+
/**
|
|
44
|
+
* The boot notice (core `boot/notices.ts`): log the setup code ONCE per process, while the page is
|
|
45
|
+
* open. Silent whenever {@link slackSetupOpen} is false — an install is recorded, OR this deployment
|
|
46
|
+
* is configured by environment variable and would ignore anything a run stored. Both cases would be
|
|
47
|
+
* emitting a live claim credential for a page that will refuse every request.
|
|
48
|
+
*
|
|
49
|
+
* 🔴 THIS IS THE ONE PLACE THE CODE IS RENDERED. It goes to the log and nowhere else: not to the
|
|
50
|
+
* audit detail (`appendAudit` details are read back by admin views), not into an error message, not
|
|
51
|
+
* into the page. Minting it is also what this call does first, so the code exists from the first
|
|
52
|
+
* boot rather than from the first visit.
|
|
53
|
+
*/
|
|
54
|
+
export declare function announceSlackSetupCode(deps?: SlackClaimDeps): Promise<void>;
|
|
55
|
+
/** Mint the signed cookie VALUE for a run that just presented the right code. CAPABILITY-BEARING. */
|
|
56
|
+
export declare function mintSetupCookie(deps?: SlackClaimDeps): Promise<string>;
|
|
57
|
+
/** Verify a presented cookie value. True only for a well-formed, correctly-signed, unexpired one. */
|
|
58
|
+
export declare function verifySetupCookie(value: string | undefined, deps?: SlackClaimDeps): Promise<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* Does `presented` equal the deployment's setup code? CONSTANT-TIME, length-prechecked.
|
|
61
|
+
*
|
|
62
|
+
* Never reports WHY it said no, and the caller never tells the holder either: a wrong code and a
|
|
63
|
+
* right code on a locked deployment render the same page.
|
|
64
|
+
*/
|
|
65
|
+
export declare function setupCodeMatches(presented: string, deps?: SlackClaimDeps): Promise<boolean>;
|
|
66
|
+
/** The `Set-Cookie` header value for a claimed run. `HttpOnly` (no script reads it), `Secure` (the
|
|
67
|
+
* page is HTTPS-only), `SameSite=Lax` — Lax rather than Strict because the run RETURNS from Slack's
|
|
68
|
+
* OAuth consent as a top-level cross-site GET, which Strict would strip the cookie from, breaking
|
|
69
|
+
* the callback for the very person who claimed the run. */
|
|
70
|
+
export declare function setupCookieHeader(value: string): string;
|
|
71
|
+
/** Read the setup cookie out of a request's `Cookie` header, or `undefined`. */
|
|
72
|
+
export declare function setupCookieFrom(req: Request): string | undefined;
|
|
73
|
+
//# sourceMappingURL=claim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim.d.ts","sourceRoot":"","sources":["../../../agent/setup/claim.ts"],"names":[],"mappings":"AA0DA,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,4CAA4C,CAAC;AAEpD,OAAO,EAAiE,KAAK,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAG/H;iEACiE;AACjE,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAEnD;2FAC2F;AAC3F,eAAO,MAAM,mBAAmB,QAAiB,CAAC;AASlD,0FAA0F;AAC1F,MAAM,WAAW,cAAe,SAAQ,qBAAqB;IAC3D,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,2FAA2F;IAC3F,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAmDD,8CAA8C;AAC9C,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAQD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAE/E;AAID;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAyChF;AAQD,iDAAiD;AACjD,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOrF;AA0BD,qGAAqG;AACrG,wBAAsB,eAAe,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAKhF;AAED,qGAAqG;AACrG,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAyB9G;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAIrG;AAED;;;4DAG4D;AAC5D,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAWhE"}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
// WHO MAY RUN `/setup` (OPE-2231) — the claim, the cookie, and the lock.
|
|
2
|
+
//
|
|
3
|
+
// THE PROBLEM. `/setup` hands whoever completes it a Slack app wired to THIS deployment and a bot
|
|
4
|
+
// token for the workspace that installs it. An unclaimed setup page is first-come: the first
|
|
5
|
+
// stranger to find a fresh deployment owns the agent. So the page needs an identity assertion before
|
|
6
|
+
// it will talk, and the deployment has nothing to assert one with — no users, no admins, no surface
|
|
7
|
+
// (Slack is what setup is for), and no operator-set secret on the hosted path, where the one env
|
|
8
|
+
// value the deploy button asks for is the credential encryption key.
|
|
9
|
+
//
|
|
10
|
+
// THE ANSWER, AND IT IS THE RATIFIED ONE (OPE-2231; "No other auth design"). The deployment proves
|
|
11
|
+
// it owns its own LOGS, which is a thing only its operator can read:
|
|
12
|
+
//
|
|
13
|
+
// 1. On boot, while no install is recorded, the deployment mints a one-time SETUP CODE and logs it
|
|
14
|
+
// once (`vercel logs` / `docker logs`; a coding agent reads it). The code lives in the vault, so
|
|
15
|
+
// every instance logs the same one and a restart does not invalidate a code in use.
|
|
16
|
+
// 2. `/setup` asks for the code once. A match sets a short-lived signed COOKIE; every later step
|
|
17
|
+
// in the run presents the cookie, so the code is typed once and never rides a URL.
|
|
18
|
+
// 3. The page LOCKS once an install is recorded (install-state.ts) and answers 404 from then on.
|
|
19
|
+
// An admin reopens it with the control call (`reopen_slack_setup`), never by presenting a code.
|
|
20
|
+
//
|
|
21
|
+
// 🔴 WHAT THIS IS AND IS NOT WORTH. The trust root is READ ACCESS TO THE DEPLOYMENT'S LOGS — a
|
|
22
|
+
// PLATFORM permission (`vercel logs`, `docker logs`), not any role this product defines. It is the
|
|
23
|
+
// right root for the hosted path, because whoever holds it can already read every secret the platform
|
|
24
|
+
// holds. It is honestly weaker than an account login in one way worth stating: a platform where log
|
|
25
|
+
// access is granted more widely than deploy access grants setup to everyone with log access, for as
|
|
26
|
+
// long as the page is open. Note which permission that is — it is NOT the OE `admin` role that the
|
|
27
|
+
// reopen verb requires, and the two sets need not overlap.
|
|
28
|
+
//
|
|
29
|
+
// TWO THINGS BOUND THAT WINDOW, and both are load-bearing:
|
|
30
|
+
// • the page closes at the first successful install, and {@link announceSlackSetupCode} stops
|
|
31
|
+
// EMITTING a code from then on. The logs are append-only history, so a post-install deployment's
|
|
32
|
+
// OLD log lines still contain the code it emitted before the install — which is why the INSTALL
|
|
33
|
+
// ITSELF destroys that code and the claim cookie key (`setup/page.ts`, before it records the
|
|
34
|
+
// install), and why a reopen rotates both BEFORE it deletes the record (`reopenSlackSetup`, which
|
|
35
|
+
// throws rather than opening the page if it cannot). The strings in those old log lines are dead
|
|
36
|
+
// the moment the install completes, and a reopen can only ever be entered with a code minted
|
|
37
|
+
// after it.
|
|
38
|
+
// • the lock FAILS CLOSED. Only an absent install record opens the page; a store fault, an
|
|
39
|
+
// unparseable row or a record from a future build all keep it shut. See {@link slackSetupOpen}.
|
|
40
|
+
// • the page is also closed on a deployment configured the OLD way, by environment variable. That
|
|
41
|
+
// one is not a nicety: without it every existing deployment in the world would upgrade into an
|
|
42
|
+
// open setup page it never asked for. See {@link slackSetupOpen}.
|
|
43
|
+
//
|
|
44
|
+
// 🔴 THE CODE IS A SECRET AND THE LOG LINE IS ITS ONLY RENDERING. It is never put in an error, never
|
|
45
|
+
// echoed back by the form, never written to the audit detail, and never returned by any function
|
|
46
|
+
// here but the two that exist to mint or compare it. A failed code attempt renders the same generic
|
|
47
|
+
// page a right-but-locked attempt does.
|
|
48
|
+
//
|
|
49
|
+
// WHY A SECOND COOKIE CODEC AND NOT core's PAGE-TOKEN CODEC. `credentials/page-token.ts` signs a
|
|
50
|
+
// capability bound to ONE ROW in `credential_intake` — its claim set is `{v, aud, org, id, rb, exp}`
|
|
51
|
+
// and `id`/`rb` are the row and the principal the act is attributed to. A setup cookie binds no row
|
|
52
|
+
// and no principal (there is no principal yet — that is what setup produces), rides a `Set-Cookie`
|
|
53
|
+
// rather than a URL, and is presented many times rather than consumed once. Bending the row codec
|
|
54
|
+
// around that would mean two meanings for `id` and a THIRD audience in a closed union whose N×N
|
|
55
|
+
// replay matrix is a suite (`webhook-reveal-test.ts`), for a token that has no row to replay onto.
|
|
56
|
+
// So: its own codec, its own key, and the two cannot verify each other's tokens in either direction
|
|
57
|
+
// because neither key is the other's and neither payload satisfies the other's shape check.
|
|
58
|
+
import { encryptedKvVault, } from "@open-everywhere/core/agent/adapter-api.js";
|
|
59
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
60
|
+
import { SLACK_COOKIE_KEY_REF, SLACK_SETUP_CODE_REF, readInstallRecord } from "./install-state.js";
|
|
61
|
+
import { slackSecretEnvPresent } from "./credentials.js";
|
|
62
|
+
/** The cookie the page sets once the code matches. `__Host-` pins it to this exact host with no
|
|
63
|
+
* domain and a `/` path, which a subdomain cannot overwrite. */
|
|
64
|
+
export const SETUP_COOKIE_NAME = "__Host-oe-setup";
|
|
65
|
+
/** How long a claimed run stays claimed. Long enough to walk the page and Slack's OAuth consent,
|
|
66
|
+
* short enough that an unattended browser on a shared machine is not a standing claim. */
|
|
67
|
+
export const SETUP_COOKIE_TTL_MS = 30 * 60 * 1000;
|
|
68
|
+
/** The setup code's length in bytes before encoding — 16 bytes is 128 bits, which is not guessable
|
|
69
|
+
* and still short enough for a human to copy out of a log line in one go. */
|
|
70
|
+
const SETUP_CODE_BYTES = 16;
|
|
71
|
+
/** The cookie HMAC key's length in bytes. Nobody types this one, so it is a full 256-bit key. */
|
|
72
|
+
const COOKIE_KEY_BYTES = 32;
|
|
73
|
+
function vaultOf(deps) {
|
|
74
|
+
return deps.vault ?? encryptedKvVault();
|
|
75
|
+
}
|
|
76
|
+
// --- mint-on-first-use, into the Slack app's own namespace ------------------------------------------
|
|
77
|
+
//
|
|
78
|
+
// WHY NOT core's `getOrMintSelfSecret`. That function's contract is "an env var of the same NAME is
|
|
79
|
+
// the announced on-ramp", which is right for a secret an operator might already hold — and wrong for
|
|
80
|
+
// these two. Nobody sets a setup code or a cookie key by hand: there is nothing to migrate from, and
|
|
81
|
+
// an env name for an auto-minted value is a knob whose only use is to weaken it. So these mint into
|
|
82
|
+
// `slack-app:` beside the rest of the bundle's material, with no env name at all, and this helper
|
|
83
|
+
// carries the SAME race discipline that function documents: a per-key lock, a read-back rather than
|
|
84
|
+
// trust in the local candidate, and no memo of a fresh mint (the vault is last-write-wins with no
|
|
85
|
+
// put-if-absent, so two cold processes can briefly diverge and the next read converges).
|
|
86
|
+
async function mintedSecret(ref, bytes, vault, now, cache) {
|
|
87
|
+
// 🔴 AN EXISTING ROW IS MEMOISED BRIEFLY, A FRESH MINT NEVER IS, and both halves matter.
|
|
88
|
+
//
|
|
89
|
+
// WHY CACHE AT ALL. Both callers sit on the UNAUTHENTICATED `/setup` route: `verifySetupCookie`
|
|
90
|
+
// resolves the cookie key for any request carrying a non-empty cookie, and `setupCodeMatches` resolves
|
|
91
|
+
// the code for every claim POST. Uncached, that is one `stored_credential` SELECT plus one decrypt per
|
|
92
|
+
// request for as long as the page is open — the same exposure `LOCK_TTL_MS` and the credential
|
|
93
|
+
// resolver's `MISS_TTL_MS` exist to remove, left open on the one route most exposed to it.
|
|
94
|
+
//
|
|
95
|
+
// WHY NOT A FRESH MINT. A mint reflects only what THIS process wrote, not what a concurrent racer's
|
|
96
|
+
// later write did (the vault is last-write-wins with no put-if-absent). Caching it would pin a value
|
|
97
|
+
// that lost the race; leaving it uncached means the next resolution re-reads and converges.
|
|
98
|
+
const cached = cache ? secretCache.get(ref) : undefined;
|
|
99
|
+
if (cached !== undefined && cached.until > now())
|
|
100
|
+
return cached.value;
|
|
101
|
+
return vault.withLock(ref, async () => {
|
|
102
|
+
const stored = await vault.read(ref);
|
|
103
|
+
if (stored !== null) {
|
|
104
|
+
if (cache)
|
|
105
|
+
secretCache.set(ref, { value: stored, until: now() + SECRET_TTL_MS });
|
|
106
|
+
return stored;
|
|
107
|
+
}
|
|
108
|
+
const minted = randomBytes(bytes).toString("base64url");
|
|
109
|
+
await vault.write(ref, minted, { connector: "slack", createdBy: "host:slack-setup" });
|
|
110
|
+
const confirmed = await vault.read(ref);
|
|
111
|
+
return confirmed ?? minted;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/** How long a read-back claim secret is reused. Matched to {@link LOCK_TTL_MS}: a reopen's rotation has
|
|
115
|
+
* to take effect without a restart, and five seconds is the staleness this page already accepts. */
|
|
116
|
+
const SECRET_TTL_MS = 5_000;
|
|
117
|
+
/** Read-back claim secrets and when each stops being trusted. Bypassed for an injected vault. */
|
|
118
|
+
const secretCache = new Map();
|
|
119
|
+
/** Test seam: forget cached claim secrets. */
|
|
120
|
+
export function resetSlackClaimSecretCache() {
|
|
121
|
+
secretCache.clear();
|
|
122
|
+
}
|
|
123
|
+
/** The HMAC key for the claim cookie, minting it on first use. Never logged, never rendered. A full
|
|
124
|
+
* 32 bytes: it keys an HMAC rather than being typed by a human, so there is no reason to be short. */
|
|
125
|
+
async function cookieKey(deps) {
|
|
126
|
+
return mintedSecret(SLACK_COOKIE_KEY_REF, COOKIE_KEY_BYTES, vaultOf(deps), deps.now ?? Date.now, deps.vault === undefined);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The deployment's setup code, minting it on first use. SECRET — the ONLY place it may be rendered
|
|
130
|
+
* is the boot log line in {@link announceSlackSetupCode}.
|
|
131
|
+
*/
|
|
132
|
+
export async function slackSetupCode(deps = {}) {
|
|
133
|
+
return mintedSecret(SLACK_SETUP_CODE_REF, SETUP_CODE_BYTES, vaultOf(deps), deps.now ?? Date.now, deps.vault === undefined);
|
|
134
|
+
}
|
|
135
|
+
// --- the lock ---------------------------------------------------------------------------------------
|
|
136
|
+
/**
|
|
137
|
+
* Is `/setup` OPEN right now? True only while NO install is recorded.
|
|
138
|
+
*
|
|
139
|
+
* Every route on the page calls this FIRST and 404s on false, so a locked deployment's `/setup` is
|
|
140
|
+
* indistinguishable from a deployment that never had the page — the same posture
|
|
141
|
+
* `credentials/intake-page.ts` takes on a disarmed vault.
|
|
142
|
+
*/
|
|
143
|
+
export async function slackSetupOpen(deps = {}) {
|
|
144
|
+
// The cached verdict, while it is still inside its TTL. Skipped for an injected vault (a test
|
|
145
|
+
// driving a specific store state), and the env check below is a property read either way.
|
|
146
|
+
const cacheApplies = deps.vault === undefined || deps.lockCache === true;
|
|
147
|
+
if (cacheApplies && lockCache !== null && lockCache.until > (deps.now ?? Date.now)())
|
|
148
|
+
return lockCache.open;
|
|
149
|
+
// 🔴 AN ENV-CONFIGURED DEPLOYMENT IS CLOSED, and this arm is not a convenience — without it the
|
|
150
|
+
// whole upgrade population gets an OPEN setup page and a live claim code in its logs, forever.
|
|
151
|
+
//
|
|
152
|
+
// The lock is "an install record exists". A deployment that was configured the old way — the two
|
|
153
|
+
// env vars, set by hand — has never run `/setup`, so it has no record. Before this check, landing
|
|
154
|
+
// this feature would have opened `/setup` on every such deployment and logged a one-time code on
|
|
155
|
+
// every cold start indefinitely, with no install ever arriving to close it. Anyone who could read
|
|
156
|
+
// the deployment's logs could then install the app into a workspace THEY control, overwrite the
|
|
157
|
+
// stored credentials, lock the page behind themselves, and have `slackInstallFacts()` publish their
|
|
158
|
+
// workspace as this deployment's install.
|
|
159
|
+
//
|
|
160
|
+
// Closing on env presence costs nothing a real `/setup` operator wants: the resolver is env-first,
|
|
161
|
+
// so a deployment with either var set would IGNORE everything a run stored anyway — the page would
|
|
162
|
+
// have been a no-op with a credential write. An operator who wants to migrate onto the store unsets
|
|
163
|
+
// the vars and redeploys, which is what the runbook says and what makes the run meaningful.
|
|
164
|
+
// Screen 2's shadow notice still covers the case where one var is set after an install.
|
|
165
|
+
const env = slackSecretEnvPresent({ ...(deps.env ? { env: deps.env } : {}) });
|
|
166
|
+
if (env.botToken || env.signingSecret)
|
|
167
|
+
return false;
|
|
168
|
+
// 🔴 ONLY AN ABSENT ROW OPENS THE PAGE, and the earlier draft got this backwards. It read through
|
|
169
|
+
// `slackInstallState`, which collapses a store fault, a malformed row and an unknown record version
|
|
170
|
+
// into `undefined` — so any of those REOPENED `/setup`. That is not a safe default here: the setup
|
|
171
|
+
// code sits in the deployment's log lines from before the install, so a reopen is a window for
|
|
172
|
+
// whoever read them. The cost of failing closed is a page briefly unavailable during a store
|
|
173
|
+
// outage, recoverable by a retry or the control call; the other direction is not recoverable.
|
|
174
|
+
// `readInstallRecord` tells the three cases apart — see its doc comment.
|
|
175
|
+
const read = await readInstallRecord(deps);
|
|
176
|
+
const open = read.kind === "absent";
|
|
177
|
+
// Cached briefly, for the same reason the credential resolver caches a miss: this predicate runs on
|
|
178
|
+
// EVERY request to a PUBLIC route, so an uncached read let anyone who knew the host drive one
|
|
179
|
+
// `stored_credential` SELECT per request. A few seconds is enough to collapse that while keeping the
|
|
180
|
+
// page responsive to a `/setup` run or a reopen that just changed the answer.
|
|
181
|
+
if (cacheApplies)
|
|
182
|
+
lockCache = { open, until: (deps.now ?? Date.now)() + LOCK_TTL_MS };
|
|
183
|
+
return open;
|
|
184
|
+
}
|
|
185
|
+
/** How long the lock verdict is reused. Short, because a reopen must take effect without a restart. */
|
|
186
|
+
const LOCK_TTL_MS = 5_000;
|
|
187
|
+
/** The last lock verdict and when it stops being trusted. Bypassed entirely for an injected vault. */
|
|
188
|
+
let lockCache = null;
|
|
189
|
+
/** Test seam: forget the cached lock verdict. */
|
|
190
|
+
export function resetSlackSetupLockCache() {
|
|
191
|
+
lockCache = null;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* The boot notice (core `boot/notices.ts`): log the setup code ONCE per process, while the page is
|
|
195
|
+
* open. Silent whenever {@link slackSetupOpen} is false — an install is recorded, OR this deployment
|
|
196
|
+
* is configured by environment variable and would ignore anything a run stored. Both cases would be
|
|
197
|
+
* emitting a live claim credential for a page that will refuse every request.
|
|
198
|
+
*
|
|
199
|
+
* 🔴 THIS IS THE ONE PLACE THE CODE IS RENDERED. It goes to the log and nowhere else: not to the
|
|
200
|
+
* audit detail (`appendAudit` details are read back by admin views), not into an error message, not
|
|
201
|
+
* into the page. Minting it is also what this call does first, so the code exists from the first
|
|
202
|
+
* boot rather than from the first visit.
|
|
203
|
+
*/
|
|
204
|
+
export async function announceSlackSetupCode(deps = {}) {
|
|
205
|
+
if (!(await slackSetupOpen(deps)))
|
|
206
|
+
return;
|
|
207
|
+
const code = await slackSetupCode(deps);
|
|
208
|
+
console.log(`[open-everywhere:slack-setup] Slack is not installed on this deployment. Open /setup and enter this one-time setup code: ${code}\n` +
|
|
209
|
+
`[open-everywhere:slack-setup] The code stops being logged once the Slack install completes, and /setup locks at that point.`);
|
|
210
|
+
}
|
|
211
|
+
function b64urlEncode(buf) {
|
|
212
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
213
|
+
}
|
|
214
|
+
function b64urlDecode(s) {
|
|
215
|
+
// Reject anything outside the base64url alphabet BEFORE decoding (Buffer is lax), so a malformed
|
|
216
|
+
// cookie fails cleanly rather than decoding to attacker-shaped bytes.
|
|
217
|
+
if (!/^[A-Za-z0-9_-]*$/.test(s))
|
|
218
|
+
return Buffer.alloc(0);
|
|
219
|
+
return Buffer.from(s.replace(/-/g, "+").replace(/_/g, "/"), "base64");
|
|
220
|
+
}
|
|
221
|
+
function sign(secret, payloadB64) {
|
|
222
|
+
return b64urlEncode(createHmac("sha256", secret).update(payloadB64, "utf8").digest());
|
|
223
|
+
}
|
|
224
|
+
/** Mint the signed cookie VALUE for a run that just presented the right code. CAPABILITY-BEARING. */
|
|
225
|
+
export async function mintSetupCookie(deps = {}) {
|
|
226
|
+
const now = (deps.now ?? Date.now)();
|
|
227
|
+
const claims = { v: 1, exp: now + SETUP_COOKIE_TTL_MS };
|
|
228
|
+
const payloadB64 = b64urlEncode(Buffer.from(JSON.stringify(claims), "utf8"));
|
|
229
|
+
return `${payloadB64}.${sign(await cookieKey(deps), payloadB64)}`;
|
|
230
|
+
}
|
|
231
|
+
/** Verify a presented cookie value. True only for a well-formed, correctly-signed, unexpired one. */
|
|
232
|
+
export async function verifySetupCookie(value, deps = {}) {
|
|
233
|
+
if (typeof value !== "string")
|
|
234
|
+
return false;
|
|
235
|
+
const dot = value.indexOf(".");
|
|
236
|
+
if (dot <= 0 || dot === value.length - 1)
|
|
237
|
+
return false;
|
|
238
|
+
const payloadB64 = value.slice(0, dot);
|
|
239
|
+
const sigB64 = value.slice(dot + 1);
|
|
240
|
+
const expected = sign(await cookieKey(deps), payloadB64);
|
|
241
|
+
const a = Buffer.from(sigB64);
|
|
242
|
+
const b = Buffer.from(expected);
|
|
243
|
+
if (a.length !== b.length || !timingSafeEqual(a, b))
|
|
244
|
+
return false;
|
|
245
|
+
let parsed;
|
|
246
|
+
try {
|
|
247
|
+
const raw = b64urlDecode(payloadB64);
|
|
248
|
+
if (raw.length === 0)
|
|
249
|
+
return false;
|
|
250
|
+
parsed = JSON.parse(raw.toString("utf8"));
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
256
|
+
return false;
|
|
257
|
+
const c = parsed;
|
|
258
|
+
if (c.v !== 1)
|
|
259
|
+
return false;
|
|
260
|
+
if (typeof c.exp !== "number" || !Number.isFinite(c.exp))
|
|
261
|
+
return false;
|
|
262
|
+
return c.exp > (deps.now ?? Date.now)();
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Does `presented` equal the deployment's setup code? CONSTANT-TIME, length-prechecked.
|
|
266
|
+
*
|
|
267
|
+
* Never reports WHY it said no, and the caller never tells the holder either: a wrong code and a
|
|
268
|
+
* right code on a locked deployment render the same page.
|
|
269
|
+
*/
|
|
270
|
+
export async function setupCodeMatches(presented, deps = {}) {
|
|
271
|
+
const a = Buffer.from(presented.trim());
|
|
272
|
+
const b = Buffer.from(await slackSetupCode(deps));
|
|
273
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
274
|
+
}
|
|
275
|
+
/** The `Set-Cookie` header value for a claimed run. `HttpOnly` (no script reads it), `Secure` (the
|
|
276
|
+
* page is HTTPS-only), `SameSite=Lax` — Lax rather than Strict because the run RETURNS from Slack's
|
|
277
|
+
* OAuth consent as a top-level cross-site GET, which Strict would strip the cookie from, breaking
|
|
278
|
+
* the callback for the very person who claimed the run. */
|
|
279
|
+
export function setupCookieHeader(value) {
|
|
280
|
+
return `${SETUP_COOKIE_NAME}=${value}; Path=/; Max-Age=${Math.floor(SETUP_COOKIE_TTL_MS / 1000)}; HttpOnly; Secure; SameSite=Lax`;
|
|
281
|
+
}
|
|
282
|
+
/** Read the setup cookie out of a request's `Cookie` header, or `undefined`. */
|
|
283
|
+
export function setupCookieFrom(req) {
|
|
284
|
+
const header = req.headers.get("cookie");
|
|
285
|
+
if (!header)
|
|
286
|
+
return undefined;
|
|
287
|
+
for (const part of header.split(";")) {
|
|
288
|
+
const eq = part.indexOf("=");
|
|
289
|
+
if (eq <= 0)
|
|
290
|
+
continue;
|
|
291
|
+
if (part.slice(0, eq).trim() !== SETUP_COOKIE_NAME)
|
|
292
|
+
continue;
|
|
293
|
+
const value = part.slice(eq + 1).trim();
|
|
294
|
+
return value.length > 0 ? value : undefined;
|
|
295
|
+
}
|
|
296
|
+
return undefined;
|
|
297
|
+
}
|
|
298
|
+
//# sourceMappingURL=claim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim.js","sourceRoot":"","sources":["../../../agent/setup/claim.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type EncryptedKvVault } from "@open-everywhere/core/agent/adapter-api.js";
|
|
2
|
+
/** Which home answered. Reported by {@link resolveSlackSecret} for the suite and the notice. */
|
|
3
|
+
export type SlackSecretSource = "env" | "store";
|
|
4
|
+
/** Injectable seams for tests — the live defaults are the real vault and `process.env`. */
|
|
5
|
+
export interface SlackCredentialDeps {
|
|
6
|
+
readonly vault?: EncryptedKvVault;
|
|
7
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
8
|
+
/** The clock the hit and miss TTLs are read against. Tests advance it to prove a cached value stops
|
|
9
|
+
* being reused; the live default is `Date.now`. */
|
|
10
|
+
readonly now?: () => number;
|
|
11
|
+
/**
|
|
12
|
+
* Use the per-process memo even though a vault is injected. TEST SEAM ONLY.
|
|
13
|
+
*
|
|
14
|
+
* 🔴 WHY IT HAS TO EXIST. An injected vault normally bypasses the memo, which is a contract the suite
|
|
15
|
+
* leans on — a test rewrites a row and the next call sees it. But that also made {@link HIT_TTL_MS}
|
|
16
|
+
* UNTESTABLE: the only path that memoises was the one no test can hand a store to. A TTL nobody can
|
|
17
|
+
* drive is a claim nobody has checked, and this one is the fix for a real defect (see the header), so
|
|
18
|
+
* the seam is the smaller cost. Nothing in `agent/**` passes it.
|
|
19
|
+
*/
|
|
20
|
+
readonly memo?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** A resolved secret and the home it came from. */
|
|
23
|
+
export interface ResolvedSlackSecret {
|
|
24
|
+
readonly value: string;
|
|
25
|
+
readonly source: SlackSecretSource;
|
|
26
|
+
}
|
|
27
|
+
/** Test seam: forget memoised store resolutions, hits and misses alike. */
|
|
28
|
+
export declare function resetSlackCredentialMemo(): void;
|
|
29
|
+
/** Test seam: forget which names have already logged the shadowing notice, and which have been
|
|
30
|
+
* checked at all. */
|
|
31
|
+
export declare function resetSlackCredentialNotices(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Resolve one Slack secret: env if set, else the credential store, else `undefined`.
|
|
34
|
+
*
|
|
35
|
+
* `undefined` is a real answer, not an error — it means this deployment has not been through `/setup`
|
|
36
|
+
* and holds no env var either. Each caller fails in its own way (the front route 401s; an outbound
|
|
37
|
+
* call throws the message eve's primitive always threw), which is what keeps this function free of
|
|
38
|
+
* any opinion about what a missing credential should do.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveSlackSecret(envName: string, fromEnv: string, ref: string, deps?: SlackCredentialDeps): Promise<ResolvedSlackSecret | undefined>;
|
|
41
|
+
/** The app's request-signing secret, or `undefined` when this deployment holds none. */
|
|
42
|
+
export declare function slackSigningSecret(deps?: SlackCredentialDeps): Promise<string | undefined>;
|
|
43
|
+
/** The workspace bot token, or `undefined` when this deployment holds none. */
|
|
44
|
+
export declare function slackBotToken(deps?: SlackCredentialDeps): Promise<string | undefined>;
|
|
45
|
+
/**
|
|
46
|
+
* Is `name`'s env var set, for the ONE caller that needs to ask without resolving — the `/setup`
|
|
47
|
+
* page's "this env var will shadow what you are about to store" notice.
|
|
48
|
+
*
|
|
49
|
+
* Unrolled rather than looped over the two names for the same reason {@link resolveSlackSecret} takes
|
|
50
|
+
* its env value as a parameter: the gate resolves a const key and nothing else.
|
|
51
|
+
*/
|
|
52
|
+
export declare function slackSecretEnvPresent(deps?: SlackCredentialDeps): {
|
|
53
|
+
readonly signingSecret: boolean;
|
|
54
|
+
readonly botToken: boolean;
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../../agent/setup/credentials.ts"],"names":[],"mappings":"AAoDA,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAQrG,gGAAgG;AAChG,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,OAAO,CAAC;AAEhD,2FAA2F;AAC3F,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACjC;wDACoD;IACpD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B;;;;;;;;OAQG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,mDAAmD;AACnD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;CACpC;AA8BD,2EAA2E;AAC3E,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C;AASD;sBACsB;AACtB,wBAAgB,2BAA2B,IAAI,IAAI,CAGlD;AAgBD;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CA4D1C;AAcD,wFAAwF;AACxF,wBAAsB,kBAAkB,CAAC,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAIpG;AAED,+EAA+E;AAC/E,wBAAsB,aAAa,CAAC,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAI/F;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,mBAAwB,GAAG;IAAE,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAMrI"}
|