experimental-ash 0.63.0 → 0.64.0
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/CHANGELOG.md +10 -0
- package/dist/docs/public/advanced/auth-and-route-protection.mdx +26 -20
- package/dist/docs/public/channels/ash.mdx +5 -1
- package/dist/src/chunks/{use-ash-agent-CRWVA4i-.js → use-ash-agent-DzoSHUCb.js} +12 -7
- package/dist/src/chunks/{use-ash-agent-BQJLh7KU.js → use-ash-agent-KtjpWd5l.js} +12 -7
- package/dist/src/client/client-error.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
- package/dist/src/packages/ash-scaffold/src/web-template.js +5 -16
- package/dist/src/public/channels/auth.d.ts +45 -3
- package/dist/src/public/channels/auth.js +1 -1
- package/dist/src/public/channels/slack/inbound.d.ts +3 -1
- package/dist/src/public/channels/slack/inbound.js +1 -1
- package/dist/src/svelte/index.js +1 -1
- package/dist/src/svelte/use-ash-agent.js +1 -1
- package/dist/src/vue/index.js +1 -1
- package/dist/src/vue/use-ash-agent.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# experimental-ash
|
|
2
2
|
|
|
3
|
+
## 0.64.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 76d7a1f: Add `placeholderAuth()` for scaffolded Web Chat channels so production requests return a structured 401 when app auth has not been configured. Generated apps now show setup guidance instead of an internal channel failure, and `ClientError` surfaces structured Ash error messages directly.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 030c0d1: Fix Slack direct messages dropping file uploads. `onDirectMessage` now receives `file_share` messages with their attachments intact, matching the existing `onAppMention` behavior; system subtypes (edits, deletes, joins) and bot-authored echoes are still filtered.
|
|
12
|
+
|
|
3
13
|
## 0.63.0
|
|
4
14
|
|
|
5
15
|
### Minor Changes
|
|
@@ -18,13 +18,13 @@ These settings apply to:
|
|
|
18
18
|
- `POST /ash/v1/session/:sessionId`
|
|
19
19
|
- `GET /ash/v1/session/:sessionId/stream`
|
|
20
20
|
|
|
21
|
-
<CopyPrompt text="Protect the user's Ash HTTP routes through the channel layer. In Ash, route auth and IP policy live on channel factories, usually agent/channels/ash.ts with ashChannel auth config, not in agent.ts. Inspect the existing channel file and app auth code, replace any generated
|
|
21
|
+
<CopyPrompt text="Protect the user's Ash HTTP routes through the channel layer. In Ash, route auth and IP policy live on channel factories, usually agent/channels/ash.ts with ashChannel auth config, not in agent.ts. Inspect the existing channel file and app auth code, replace any generated placeholderAuth guardrail with the smallest AuthFn or helper composition, use helpers such as localDev, vercelOidc, none, httpBasic, jwtHmac, jwtEcdsa, or oidc as appropriate, keep secrets in environment variables, preserve localDev and vercelOidc behavior where useful, verify unauthenticated production browser requests are rejected with 401, and do not commit unless the user asks.">
|
|
22
22
|
Protect the user's Ash HTTP routes through the channel layer. In Ash, route auth and IP policy
|
|
23
23
|
live on channel factories, usually agent/channels/ash.ts with ashChannel auth config, not in
|
|
24
24
|
agent.ts. Inspect the existing channel file and app auth code, replace any generated
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
placeholderAuth guardrail with the smallest AuthFn or helper composition, use helpers such as
|
|
26
|
+
localDev, vercelOidc, none, httpBasic, jwtHmac, jwtEcdsa, or oidc as appropriate, keep secrets in
|
|
27
|
+
environment variables, preserve localDev and vercelOidc behavior where useful, verify
|
|
28
28
|
unauthenticated production browser requests are rejected with 401, and do not commit unless the
|
|
29
29
|
user asks.
|
|
30
30
|
</CopyPrompt>
|
|
@@ -32,31 +32,24 @@ These settings apply to:
|
|
|
32
32
|
## Generated Web Chat Auth
|
|
33
33
|
|
|
34
34
|
`pnpm create experimental-ash-agent` scaffolds `agent/channels/ash.ts` from the Web Chat example.
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
It permits Vercel OIDC and localhost requests, but it will not allow browser requests in
|
|
36
|
+
production until you replace the placeholder with your app's auth:
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
39
|
// agent/channels/ash.ts
|
|
40
40
|
import { ashChannel } from "experimental-ash/channels/ash";
|
|
41
|
-
import {
|
|
42
|
-
|
|
43
|
-
function exampleProductionAuth(): AuthFn<Request> {
|
|
44
|
-
return () => {
|
|
45
|
-
if (process.env.VERCEL_ENV === "production") {
|
|
46
|
-
throw new Error("Configure production auth in agent/channels/ash.ts.");
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
};
|
|
50
|
-
}
|
|
41
|
+
import { localDev, placeholderAuth, vercelOidc } from "experimental-ash/channels/auth";
|
|
51
42
|
|
|
52
43
|
export default ashChannel({
|
|
53
|
-
auth: [
|
|
44
|
+
auth: [localDev(), vercelOidc(), placeholderAuth()],
|
|
54
45
|
});
|
|
55
46
|
```
|
|
56
47
|
|
|
57
|
-
Replace `
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
Replace `placeholderAuth()` before a browser user submits a production request. It returns a
|
|
49
|
+
structured 401 in production so generated Web Chat apps can explain that auth is not configured
|
|
50
|
+
instead of showing an internal channel error. If you delete the authored file, Ash falls back to
|
|
51
|
+
its framework default `[localDev(), vercelOidc()]`; that default also does not accept browser
|
|
52
|
+
traffic in production.
|
|
60
53
|
|
|
61
54
|
## Walking The Auth Array
|
|
62
55
|
|
|
@@ -68,6 +61,19 @@ that default also does not accept browser-user traffic in production.
|
|
|
68
61
|
If every entry skips, the request is rejected with `401`. An empty array `auth: []` therefore
|
|
69
62
|
rejects every request.
|
|
70
63
|
|
|
64
|
+
To reject with a specific `401`, throw an `UnauthenticatedError`. To reject with a specific `403`,
|
|
65
|
+
throw a `ForbiddenError`. `routeAuth` turns those errors into HTTP responses; other thrown errors
|
|
66
|
+
still use the normal channel failure path.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { UnauthenticatedError } from "experimental-ash/channels/auth";
|
|
70
|
+
|
|
71
|
+
throw new UnauthenticatedError({
|
|
72
|
+
code: "authentication_required",
|
|
73
|
+
message: "Sign in to continue.",
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
71
77
|
To accept anonymous traffic, include `none()` as the final entry:
|
|
72
78
|
|
|
73
79
|
```ts
|
|
@@ -58,7 +58,11 @@ wire the Ash channel to your app's auth system, such as Clerk, Auth.js, or your
|
|
|
58
58
|
verification.
|
|
59
59
|
|
|
60
60
|
`pnpm create experimental-ash-agent` scaffolds an example `agent/channels/ash.ts` with a production
|
|
61
|
-
auth placeholder so you can replace it before exposing the API to real users.
|
|
61
|
+
auth placeholder so you can replace it before exposing the API to real users. The generated channel
|
|
62
|
+
permits Vercel OIDC and localhost requests and includes `placeholderAuth()`, which returns a
|
|
63
|
+
setup-focused 401 in production until you replace it with your app's auth. If you delete the
|
|
64
|
+
authored file, Ash falls back to `[localDev(), vercelOidc()]`; that default does not admit browser
|
|
65
|
+
users in production.
|
|
62
66
|
|
|
63
67
|
For the full auth model and helper list, see
|
|
64
68
|
[Auth and Route Protection](/docs/auth-and-route-protection).
|
|
@@ -17,13 +17,24 @@ function createAshContinueSessionRoutePath(sessionId) {
|
|
|
17
17
|
return `${ASH_ROUTE_PREFIX}/session/${encodeURIComponent(sessionId)}`;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/shared/guards.ts
|
|
22
|
+
function isObject(value) {
|
|
23
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
//#endregion
|
|
21
27
|
//#region src/client/client-error.ts
|
|
22
28
|
var ClientError = class extends Error {
|
|
23
29
|
status;
|
|
24
30
|
body;
|
|
25
31
|
constructor(status, body) {
|
|
26
|
-
|
|
32
|
+
let message = body || `Server returned ${status}.`;
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(body);
|
|
35
|
+
if (isObject(parsed) && typeof parsed.error === "string") message = parsed.error;
|
|
36
|
+
} catch {}
|
|
37
|
+
super(message);
|
|
27
38
|
this.name = "ClientError";
|
|
28
39
|
this.status = status;
|
|
29
40
|
this.body = body;
|
|
@@ -471,12 +482,6 @@ function encodeBasicCredentials(username, password) {
|
|
|
471
482
|
return btoa(binaryString);
|
|
472
483
|
}
|
|
473
484
|
|
|
474
|
-
//#endregion
|
|
475
|
-
//#region src/shared/guards.ts
|
|
476
|
-
function isObject(value) {
|
|
477
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
485
|
//#endregion
|
|
481
486
|
//#region src/shared/errors.ts
|
|
482
487
|
function toErrorMessage(error) {
|
|
@@ -17,13 +17,24 @@ function createAshContinueSessionRoutePath(sessionId) {
|
|
|
17
17
|
return `${ASH_ROUTE_PREFIX}/session/${encodeURIComponent(sessionId)}`;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/shared/guards.ts
|
|
22
|
+
function isObject(value) {
|
|
23
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
//#endregion
|
|
21
27
|
//#region src/client/client-error.ts
|
|
22
28
|
var ClientError = class extends Error {
|
|
23
29
|
status;
|
|
24
30
|
body;
|
|
25
31
|
constructor(status, body) {
|
|
26
|
-
|
|
32
|
+
let message = body || `Server returned ${status}.`;
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(body);
|
|
35
|
+
if (isObject(parsed) && typeof parsed.error === "string") message = parsed.error;
|
|
36
|
+
} catch {}
|
|
37
|
+
super(message);
|
|
27
38
|
this.name = "ClientError";
|
|
28
39
|
this.status = status;
|
|
29
40
|
this.body = body;
|
|
@@ -471,12 +482,6 @@ function encodeBasicCredentials(username, password) {
|
|
|
471
482
|
return btoa(binaryString);
|
|
472
483
|
}
|
|
473
484
|
|
|
474
|
-
//#endregion
|
|
475
|
-
//#region src/shared/guards.ts
|
|
476
|
-
function isObject(value) {
|
|
477
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
485
|
//#endregion
|
|
481
486
|
//#region src/shared/errors.ts
|
|
482
487
|
function toErrorMessage(error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var ClientError=class extends Error{status;body;constructor(e,t){
|
|
1
|
+
import{isObject}from"#shared/guards.js";var ClientError=class extends Error{status;body;constructor(e,t){let n=t||`Server returned ${e}.`;try{let e=JSON.parse(t);isObject(e)&&typeof e.error==`string`&&(n=e.error)}catch{}super(n),this.name=`ClientError`,this.status=e,this.body=t}};export{ClientError};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{ASH_PACKAGE_NAME}from"#internal/package-name.js";import{fileURLToPath}from"node:url";let cachedPackageInfo;const WORKFLOW_MODULE_ALIASES={"workflow/api":`src/compiled/@workflow/core/runtime.js`,"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`,"workflow/runtime":`src/compiled/@workflow/core/runtime.js`};function resolveFallbackPackageVersion(){return`0.
|
|
1
|
+
import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{ASH_PACKAGE_NAME}from"#internal/package-name.js";import{fileURLToPath}from"node:url";let cachedPackageInfo;const WORKFLOW_MODULE_ALIASES={"workflow/api":`src/compiled/@workflow/core/runtime.js`,"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`,"workflow/runtime":`src/compiled/@workflow/core/runtime.js`};function resolveFallbackPackageVersion(){return`0.64.0`}const FALLBACK_PACKAGE_INFO={name:ASH_PACKAGE_NAME,version:resolveFallbackPackageVersion()};function resolveCurrentModulePath(){return typeof __filename==`string`?__filename:resolveCurrentModulePathFromStack()}function resolveCurrentModulePathFromStack(){let e=Error.prepareStackTrace;try{Error.prepareStackTrace=(e,t)=>t;let e=Error().stack?.[0]?.getFileName();if(typeof e!=`string`||e.length===0)throw Error(`Failed to resolve the current module path from the stack trace.`);return e.startsWith(`file:`)?fileURLToPath(e):e}finally{Error.prepareStackTrace=e}}const require=createRequire(resolveCurrentModulePath());function isBuildOutputPackageRoot(e){return basename(e)===`dist`&&existsSync(join(dirname(e),`package.json`))}function resolvePackageBuildRoot(){let e=dirname(realpathSync(resolveCurrentModulePath()));for(;;){if(isBuildOutputPackageRoot(e))return e;let t=dirname(e);if(t===e)return null;e=t}}function findNearestPackageRoot(e){let t=e;for(;;){if(existsSync(join(t,`package.json`))&&!isBuildOutputPackageRoot(t))return t;let r=dirname(t);if(r===t)throw Error(`Failed to resolve package root from "${e}".`);t=r}}function resolvePackageRoot(){return findNearestPackageRoot(dirname(realpathSync(resolveCurrentModulePath())))}function tryResolvePackageRoot(){try{return resolvePackageRoot()}catch{return}}function rewriteSourceFilePathForBuild(e){return e.replace(/\.[cm]?tsx?$/,`.js`)}function resolvePackageSourceFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),e):join(t,rewriteSourceFilePathForBuild(e))}function resolvePackageSourceDirectoryPath(e){let t=resolvePackageBuildRoot();return join(t===null?resolvePackageRoot():t,e)}function resolvePackageDependencyPath(e){return require.resolve(e)}function resolvePackageCompiledFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),`.generated`,`compiled`,e.replace(/^src\/compiled\//,``)):join(t,e)}function normalizeInstalledPackageInfo(e){let t=e;if(!(typeof t.name!=`string`||typeof t.version!=`string`))return{name:t.name,version:t.version}}function tryReadInstalledPackageInfo(e,t){let n=normalizeInstalledPackageInfo(JSON.parse(readFileSync(e,`utf8`)));if(n?.name===t)return n}function resolveInstalledPackageInfo(){if(cachedPackageInfo)return cachedPackageInfo;let e=tryResolvePackageRoot(),t=e===void 0?void 0:tryReadInstalledPackageInfo(join(e,`package.json`),ASH_PACKAGE_NAME);if(t)return cachedPackageInfo=t,cachedPackageInfo;try{let e=tryReadInstalledPackageInfo(require.resolve(`${ASH_PACKAGE_NAME}/package.json`),ASH_PACKAGE_NAME);if(e)return cachedPackageInfo=e,cachedPackageInfo}catch{}return cachedPackageInfo={...FALLBACK_PACKAGE_INFO},cachedPackageInfo}function resolveWorkflowModulePath(e){if(e===`workflow`)return resolvePackageSourceFilePath(`src/internal/workflow/index.ts`);if(e===`workflow/internal/builtins`)return resolvePackageSourceFilePath(`src/internal/workflow/builtins.ts`);let t=WORKFLOW_MODULE_ALIASES[e];return t===void 0?require.resolve(e):resolvePackageCompiledFilePath(t)}export{resolveInstalledPackageInfo,resolvePackageDependencyPath,resolvePackageRoot,resolvePackageSourceDirectoryPath,resolvePackageSourceFilePath,resolveWorkflowModulePath};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"./files.js";import{PNPM_WORKSPACE_PATH,ensurePnpmWorkspacePolicy}from"./pnpm-workspace.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"./web-template.js";import"./project.js";import{patchPackageJson}from"./package-json.js";import{basename,join,resolve}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/ash/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`,`peerDependencies`,`optionalDependencies`],WEB_NEXT_CONFIG_PATH=`next.config.ts`,WEB_VERCEL_JSON_PATH=`vercel.json`,WEB_VERCEL_JSON_SCHEMA=`https://openapi.vercel.sh/vercel.json`,WEB_COMPETING_NEXT_CONFIG_PATHS=[`next.config.js`,`next.config.mjs`,WEB_NEXT_CONFIG_PATH,`next.config.mts`].filter(e=>e!==WEB_NEXT_CONFIG_PATH),WEB_DEFAULT_VERCEL_SERVICES={web:{entrypoint:`.`,framework:`nextjs`,routePrefix:`/`},ash:{buildCommand:`ash build`,entrypoint:`.`,framework:`ash`,routePrefix:`/_ash_internal/ash`}};function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readDependencyVersion(e,t){let n=JSON.parse(await readFile(e,`utf8`));if(!isJsonObject(n)||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){if(!await pathExists(e))return!1;let r=JSON.parse(await readFile(e,`utf8`));return isJsonObject(r)&&packageJsonHasDependency(r,t)}async function ensurePackageDependency(e,t,r){return!await pathExists(e)||await readDependencyVersion(e,t)===r?[]:(await patchPackageJson(e,{dependencies:{[t]:r}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{ashPackageVersion:e?.ashPackageVersion??`0.
|
|
1
|
+
import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"./files.js";import{PNPM_WORKSPACE_PATH,ensurePnpmWorkspacePolicy}from"./pnpm-workspace.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"./web-template.js";import"./project.js";import{patchPackageJson}from"./package-json.js";import{basename,join,resolve}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/ash/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`,`peerDependencies`,`optionalDependencies`],WEB_NEXT_CONFIG_PATH=`next.config.ts`,WEB_VERCEL_JSON_PATH=`vercel.json`,WEB_VERCEL_JSON_SCHEMA=`https://openapi.vercel.sh/vercel.json`,WEB_COMPETING_NEXT_CONFIG_PATHS=[`next.config.js`,`next.config.mjs`,WEB_NEXT_CONFIG_PATH,`next.config.mts`].filter(e=>e!==WEB_NEXT_CONFIG_PATH),WEB_DEFAULT_VERCEL_SERVICES={web:{entrypoint:`.`,framework:`nextjs`,routePrefix:`/`},ash:{buildCommand:`ash build`,entrypoint:`.`,framework:`ash`,routePrefix:`/_ash_internal/ash`}};function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readDependencyVersion(e,t){let n=JSON.parse(await readFile(e,`utf8`));if(!isJsonObject(n)||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){if(!await pathExists(e))return!1;let r=JSON.parse(await readFile(e,`utf8`));return isJsonObject(r)&&packageJsonHasDependency(r,t)}async function ensurePackageDependency(e,t,r){return!await pathExists(e)||await readDependencyVersion(e,t)===r?[]:(await patchPackageJson(e,{dependencies:{[t]:r}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{ashPackageVersion:e?.ashPackageVersion??`0.64.0`,aiPackageVersion:e?.aiPackageVersion??`7.0.0-canary.165`,nextPackageVersion:e?.nextPackageVersion??`16.2.6`,reactPackageVersion:e?.reactPackageVersion??`19.2.6`,reactDomPackageVersion:e?.reactDomPackageVersion??`19.2.6`,streamdownPackageVersion:e?.streamdownPackageVersion??`2.5.0`,zodPackageVersion:e?.zodPackageVersion??`4.4.3`,tsgoPackageVersion:e?.tsgoPackageVersion??`7.0.0-dev.20260523.1`,typesNodePackageVersion:e?.typesNodePackageVersion??`25.9.1`,typesReactPackageVersion:e?.typesReactPackageVersion??`19.2.15`,typesReactDomPackageVersion:e?.typesReactDomPackageVersion??`19.2.3`}}function formatAshDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}async function patchWebPackageJson(e,t){if(!await pathExists(e))return[];assertStampedVersion(`ashPackageVersion`,t.ashPackageVersion),assertStampedVersion(`aiPackageVersion`,t.aiPackageVersion),assertStampedVersion(`nextPackageVersion`,t.nextPackageVersion),assertStampedVersion(`reactPackageVersion`,t.reactPackageVersion),assertStampedVersion(`reactDomPackageVersion`,t.reactDomPackageVersion),assertStampedVersion(`streamdownPackageVersion`,t.streamdownPackageVersion),assertStampedVersion(`zodPackageVersion`,t.zodPackageVersion),assertStampedVersion(`tsgoPackageVersion`,t.tsgoPackageVersion),assertStampedVersion(`typesNodePackageVersion`,t.typesNodePackageVersion),assertStampedVersion(`typesReactPackageVersion`,t.typesReactPackageVersion),assertStampedVersion(`typesReactDomPackageVersion`,t.typesReactDomPackageVersion);let r={...WEB_APP_TEMPLATE_PACKAGE_JSON.dependencies,ai:t.aiPackageVersion,"experimental-ash":formatAshDependencySpecifier(t.ashPackageVersion),next:t.nextPackageVersion,react:t.reactPackageVersion,"react-dom":t.reactDomPackageVersion,streamdown:t.streamdownPackageVersion,zod:t.zodPackageVersion},i={...WEB_APP_TEMPLATE_PACKAGE_JSON.devDependencies,"@types/node":t.typesNodePackageVersion,"@types/react":t.typesReactPackageVersion,"@types/react-dom":t.typesReactDomPackageVersion,"@typescript/native-preview":t.tsgoPackageVersion},a=WEB_APP_TEMPLATE_PACKAGE_JSON.scripts;return await patchPackageJson(e,{dependencies:r,devDependencies:i,scripts:a}),[{path:e,dependencies:Object.keys(r),devDependencies:Object.keys(i),scripts:Object.keys(a)}]}function normalizeSlackConnectorSlug(e){return toSlackConnectorSlug((e.trim().replace(/^@/,``).split(`/`).at(-1)??``).toLowerCase().replace(/[^a-z0-9_-]+/g,`-`).replace(/^[^a-z0-9]+/,``).replace(/[^a-z0-9]+$/,``).slice(0,100).replace(/[^a-z0-9]+$/,``)||`my-agent`)}async function deriveSlackConnectorSlug(e,t){if(t!==void 0&&t.length>0&&t!==`.`)return normalizeSlackConnectorSlug(t);try{let t=await readFile(join(e,`package.json`),`utf8`),n=JSON.parse(t);if(typeof n.name==`string`&&n.name.length>0)return normalizeSlackConnectorSlug(n.name)}catch{}return normalizeSlackConnectorSlug(basename(resolve(e))||`my-agent`)}function buildSlackTemplate(e){return`import { connectSlackCredentials } from "@vercel/connect/ash";
|
|
2
2
|
import { slackChannel } from "experimental-ash/channels/slack";
|
|
3
3
|
|
|
4
4
|
export default slackChannel({
|
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
const WEB_APP_TEMPLATE_FILES={"agent/channels/ash.ts":`import { ashChannel } from "experimental-ash/channels/ash";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
// Replace with your real auth (Auth.js, Clerk, …): return a SessionAuthContext
|
|
5
|
-
// for signed-in users, or null to reject. Throws in production until you do.
|
|
6
|
-
function exampleProductionAuth(): AuthFn<Request> {
|
|
7
|
-
return () => {
|
|
8
|
-
if (process.env.VERCEL_ENV === "production") {
|
|
9
|
-
throw new Error(
|
|
10
|
-
"Configure production auth in agent/channels/ash.ts (e.g. Auth.js or Clerk).",
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
return null;
|
|
14
|
-
};
|
|
15
|
-
}
|
|
2
|
+
import { localDev, placeholderAuth, vercelOidc } from "experimental-ash/channels/auth";
|
|
16
3
|
|
|
17
4
|
export default ashChannel({
|
|
18
5
|
auth: [
|
|
@@ -20,8 +7,10 @@ export default ashChannel({
|
|
|
20
7
|
localDev(),
|
|
21
8
|
// Lets the Ash TUI and your Vercel deployments reach the deployed agent.
|
|
22
9
|
vercelOidc(),
|
|
23
|
-
//
|
|
24
|
-
|
|
10
|
+
// This placeholder will not allow browser requests in production.
|
|
11
|
+
// Replace it with your app's auth provider, like Auth.js or Clerk,
|
|
12
|
+
// or use none() for a public demo.
|
|
13
|
+
placeholderAuth(),
|
|
25
14
|
],
|
|
26
15
|
});
|
|
27
16
|
`,"app/_components/agent-chat.tsx":`"use client";
|
|
@@ -157,6 +157,32 @@ export interface UnauthorizedResponseOptions {
|
|
|
157
157
|
* headers (one per challenge), and a `{ ok: false, code, error }` body.
|
|
158
158
|
*/
|
|
159
159
|
export declare function createUnauthorizedResponse(opts?: UnauthorizedResponseOptions): Response;
|
|
160
|
+
/**
|
|
161
|
+
* Options accepted by auth error classes. The class chooses the HTTP status.
|
|
162
|
+
*/
|
|
163
|
+
export type AuthErrorOptions = Omit<UnauthorizedResponseOptions, "status">;
|
|
164
|
+
/**
|
|
165
|
+
* Error thrown by auth callbacks to reject a route with a structured 401
|
|
166
|
+
* response.
|
|
167
|
+
*
|
|
168
|
+
* `routeAuth` catches this error and returns its response. Other errors still
|
|
169
|
+
* propagate through the normal channel failure path.
|
|
170
|
+
*/
|
|
171
|
+
export declare class UnauthenticatedError extends Error {
|
|
172
|
+
readonly response: Response;
|
|
173
|
+
constructor(opts?: AuthErrorOptions);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Error thrown by auth callbacks to reject a route with a structured 403
|
|
177
|
+
* response.
|
|
178
|
+
*
|
|
179
|
+
* `routeAuth` catches this error and returns its response. Other errors still
|
|
180
|
+
* propagate through the normal channel failure path.
|
|
181
|
+
*/
|
|
182
|
+
export declare class ForbiddenError extends Error {
|
|
183
|
+
readonly response: Response;
|
|
184
|
+
constructor(opts?: AuthErrorOptions);
|
|
185
|
+
}
|
|
160
186
|
/**
|
|
161
187
|
* Route auth callback. Returned value semantics inside {@link routeAuth}:
|
|
162
188
|
*
|
|
@@ -164,9 +190,10 @@ export declare function createUnauthorizedResponse(opts?: UnauthorizedResponseOp
|
|
|
164
190
|
* - `null` or `undefined` skips to the next entry in the array.
|
|
165
191
|
*
|
|
166
192
|
* If every entry skips (including the degenerate `[]` case), the walker
|
|
167
|
-
* returns a 401. To
|
|
168
|
-
*
|
|
169
|
-
* {@link
|
|
193
|
+
* returns a 401. To reject with a specific response, throw an
|
|
194
|
+
* {@link UnauthenticatedError} or {@link ForbiddenError}. To accept anonymous
|
|
195
|
+
* traffic, include {@link none} as the final entry — it returns a synthetic
|
|
196
|
+
* anonymous {@link SessionAuthContext} that terminates the walk.
|
|
170
197
|
*/
|
|
171
198
|
export type AuthFn<TEvent = Request> = (event: TEvent) => SessionAuthContext | null | undefined | Promise<SessionAuthContext | null | undefined>;
|
|
172
199
|
/**
|
|
@@ -181,6 +208,21 @@ export type AuthFn<TEvent = Request> = (event: TEvent) => SessionAuthContext | n
|
|
|
181
208
|
* `routeAuth` rather than re-implement the walk.
|
|
182
209
|
*/
|
|
183
210
|
export declare function routeAuth(request: Request, auth: AuthFn<Request> | readonly AuthFn<Request>[]): Promise<SessionAuthContext | Response>;
|
|
211
|
+
/**
|
|
212
|
+
* Returns an {@link AuthFn} for scaffolded apps that makes unfinished
|
|
213
|
+
* production auth fail as an intentional 401 instead of an internal route
|
|
214
|
+
* error.
|
|
215
|
+
*
|
|
216
|
+
* Replace this before serving real users:
|
|
217
|
+
*
|
|
218
|
+
* ```ts
|
|
219
|
+
* ashChannel({ auth: [localDev(), vercelOidc(), placeholderAuth()] });
|
|
220
|
+
* ```
|
|
221
|
+
*
|
|
222
|
+
* In non-production environments it returns `null`, so the auth walk keeps
|
|
223
|
+
* the same local development behavior as any other skipped auth entry.
|
|
224
|
+
*/
|
|
225
|
+
export declare function placeholderAuth(): AuthFn<Request>;
|
|
184
226
|
/**
|
|
185
227
|
* Returns an {@link AuthFn} that accepts any request anonymously by
|
|
186
228
|
* producing a synthetic principal with `principalType: "anonymous"`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{decodeJwt}from"#compiled/jose/index.js";import{authenticateHttpBasicStrategy}from"#runtime/governance/auth/http-basic.js";import{authenticateJwtEcdsaStrategy}from"#runtime/governance/auth/jwt-ecdsa.js";import{authenticateJwtHmacStrategy}from"#runtime/governance/auth/jwt-hmac.js";import{authenticateOidcStrategy}from"#runtime/governance/auth/oidc.js";import{createRuntimeSessionAuthContext}from"#runtime/governance/auth/types.js";import{createRuntimeIpAllowList,isRuntimeIpAllowed}from"#runtime/governance/network/ip-allow-list.js";const vercelOidcLog=createLogger(`auth.vercel-oidc`);function verifyHttpBasic(e,t){if(e===null)return{ok:!1};let r=authenticateHttpBasicStrategy({authorization:e,strategy:{kind:`http-basic`,password:t.password,username:t.username}});return r.kind===`authenticated`?{ok:!0,sessionAuth:createRuntimeSessionAuthContext(r.principal)}:{ok:!1}}async function verifyJwtHmac(e,t){if(e===null||e.length===0)return{ok:!1};let n=await authenticateJwtHmacStrategy({strategy:{algorithm:t.algorithm,audiences:[...t.audiences],clockSkewSeconds:t.clockSkewSeconds??30,issuer:t.issuer,kind:`jwt-hmac`,secret:t.secret,...t.claims===void 0?{}:{claims:t.claims},...t.subjects===void 0?{}:{subjects:t.subjects}},token:e});return n.kind===`authenticated`?{ok:!0,sessionAuth:createRuntimeSessionAuthContext(n.principal)}:{ok:!1}}async function verifyJwtEcdsa(e,t){if(e===null||e.length===0)return{ok:!1};let n=await authenticateJwtEcdsaStrategy({strategy:{algorithm:t.algorithm,audiences:[...t.audiences],clockSkewSeconds:t.clockSkewSeconds??30,issuer:t.issuer,kind:`jwt-ecdsa`,publicKey:t.publicKey,...t.claims===void 0?{}:{claims:t.claims},...t.subjects===void 0?{}:{subjects:t.subjects}},token:e});return n.kind===`authenticated`?{ok:!0,sessionAuth:createRuntimeSessionAuthContext(n.principal)}:{ok:!1}}async function verifyOidc(e,t){let n=await runOidcVerification(e,{...t,acceptCurrentVercelProject:!1});return n.kind===`authenticated`?{ok:!0,sessionAuth:createRuntimeSessionAuthContext(n.principal)}:{ok:!1}}async function runOidcVerification(e,t){return e===null||e.length===0?{kind:`not-authenticated`}:await authenticateOidcStrategy({strategy:{acceptCurrentVercelProject:t.acceptCurrentVercelProject,audiences:[...t.audiences],clockSkewSeconds:t.clockSkewSeconds??30,discoveryUrl:t.discoveryUrl??`${t.issuer.replace(/\/$/,``)}/.well-known/openid-configuration`,issuer:t.issuer,kind:`oidc`,...t.claims===void 0?{}:{claims:t.claims},...t.subjects===void 0?{}:{subjects:t.subjects}},token:e})}function extractBearerToken(e){if(e===null)return null;let t=/^Bearer\s+(.+)$/i.exec(e)?.[1]?.trim();return t===void 0||t.length===0?null:t}function createIpAllowList(e){return createRuntimeIpAllowList(e)}function isIpAllowed(e,t){return e===null?!1:isRuntimeIpAllowed(e,t)}function createUnauthorizedResponse(e={}){let t=e.status??401,n=e.code??(t===403?`forbidden`:`unauthorized`),r=e.message??(t===403?`Forbidden.`:`Authorization is required for this route.`),i=e.challenges??[],a=new Headers({"cache-control":`no-store`});for(let e of i)a.append(`www-authenticate`,formatChallenge(e));return Response.json({code:n,error:r,ok:!1},{headers:a,status:t})}function formatChallenge(e){if(e.parameters===void 0||Object.keys(e.parameters).length===0)return e.scheme;let t=Object.entries(e.parameters).map(([e,t])=>`${e}="${escapeChallengeValue(t)}"`).join(`, `);return`${e.scheme} ${t}`}function escapeChallengeValue(e){return e.replaceAll(`\\`,`\\\\`).replaceAll(`"`,`\\"`)}async function routeAuth(e,t){let n=Array.isArray(t)?t:[t];for(let t of n){let n=await t(e);if(n)return n}return createUnauthorizedResponse({challenges:[{scheme:`Bearer`}]})}function none(){return()=>ANONYMOUS_SESSION_AUTH_CONTEXT}function localDev(){return e=>process.env.VERCEL&&process.env.VERCEL_ENV===`development`||isLoopbackRequest(e)?LOCAL_DEV_SESSION_AUTH_CONTEXT:null}const LOOPBACK_HOSTNAMES=new Set([`localhost`,`[::1]`]),LOOPBACK_IPV4_PREFIX=/^127\./;function isLoopbackRequest(e){let t;try{t=new URL(e.url).hostname}catch{return!1}return!!(LOOPBACK_HOSTNAMES.has(t)||LOOPBACK_IPV4_PREFIX.test(t)||t.endsWith(`.localhost`))}const ANONYMOUS_SESSION_AUTH_CONTEXT={attributes:{},authenticator:`none`,principalId:`anonymous`,principalType:`anonymous`},LOCAL_DEV_SESSION_AUTH_CONTEXT={attributes:{},authenticator:`local-dev`,principalId:`local-dev`,principalType:`local-dev`};async function verifyVercelOidc(e,t={}){if(e===null||e.length===0)return{ok:!1};let n=decodeUnverifiedJwtClaims(e);if(n===null)return vercelOidcLog.debug(`Rejected token that failed to decode as a JWT.`),{ok:!1};if(!n.issuer.startsWith(`https://oidc.vercel.com/`))return vercelOidcLog.debug(`Rejected token whose issuer is not a Vercel OIDC issuer.`,{issuer:n.issuer}),{ok:!1};if(n.audiences.length===0)return vercelOidcLog.debug(`Rejected token with no audience claim.`,{issuer:n.issuer}),{ok:!1};let r=await runOidcVerification(e,{acceptCurrentVercelProject:!0,audiences:n.audiences,issuer:n.issuer,subjects:t.subjects??[]});return r.kind===`authenticated`?(vercelOidcLog.debug(`Accepted Vercel OIDC token.`,{issuer:n.issuer,principalType:r.principal.principalType,subject:r.principal.subject}),{ok:!0,sessionAuth:createRuntimeSessionAuthContext(r.principal)}):(vercelOidcLog.debug(`Rejected Vercel OIDC token after verification.`,{audiences:n.audiences,issuer:n.issuer,reason:r.kind,subjectsConfigured:(t.subjects??[]).length>0,...r.kind===`misconfigured`?{detail:r.message}:{}}),{ok:!1})}function vercelSubject(e){assertVercelSubjectSegment(`teamSlug`,e.teamSlug),assertVercelSubjectSegment(`projectName`,e.projectName);let t=e.environment??`production`;if(t!==`production`&&t!==`preview`&&t!==`development`&&t!==`*`)throw Error(`vercelSubject: invalid environment ${JSON.stringify(t)}; expected "production", "preview", "development", or "*".`);return`owner:${e.teamSlug}:project:${e.projectName}:environment:${t}`}function assertVercelSubjectSegment(e,t){if(t.length===0)throw Error(`vercelSubject: ${e} must be a non-empty string.`);if(t.includes(`*`)||t.includes(`:`))throw Error(`vercelSubject: ${e} ${JSON.stringify(t)} may not contain ${t.includes(`:`)?`':'`:`'*'`}. Hand-write the subject string when wildcards are intentional.`)}function vercelOidc(e={}){return async t=>{let n=await verifyVercelOidc(extractBearerToken(t.headers.get(`authorization`)),e);return n.ok?n.sessionAuth:null}}function httpBasic(e){return t=>{let n=verifyHttpBasic(t.headers.get(`authorization`),e);return n.ok?n.sessionAuth:null}}function jwtHmac(e){return async t=>{let n=await verifyJwtHmac(extractBearerToken(t.headers.get(`authorization`)),e);return n.ok?n.sessionAuth:null}}function jwtEcdsa(e){return async t=>{let n=await verifyJwtEcdsa(extractBearerToken(t.headers.get(`authorization`)),e);return n.ok?n.sessionAuth:null}}function oidc(e){return async t=>{let n=await verifyOidc(extractBearerToken(t.headers.get(`authorization`)),e);return n.ok?n.sessionAuth:null}}function decodeUnverifiedJwtClaims(e){let n;try{n=decodeJwt(e)}catch{return null}return typeof n.iss!=`string`||n.iss.length===0?null:{audiences:typeof n.aud==`string`?[n.aud]:Array.isArray(n.aud)?n.aud.filter(e=>typeof e==`string`):[],issuer:n.iss}}export{createIpAllowList,createUnauthorizedResponse,extractBearerToken,httpBasic,isIpAllowed,jwtEcdsa,jwtHmac,localDev,none,oidc,routeAuth,vercelOidc,vercelSubject,verifyHttpBasic,verifyJwtEcdsa,verifyJwtHmac,verifyOidc,verifyVercelOidc};
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{decodeJwt}from"#compiled/jose/index.js";import{authenticateHttpBasicStrategy}from"#runtime/governance/auth/http-basic.js";import{authenticateJwtEcdsaStrategy}from"#runtime/governance/auth/jwt-ecdsa.js";import{authenticateJwtHmacStrategy}from"#runtime/governance/auth/jwt-hmac.js";import{authenticateOidcStrategy}from"#runtime/governance/auth/oidc.js";import{createRuntimeSessionAuthContext}from"#runtime/governance/auth/types.js";import{createRuntimeIpAllowList,isRuntimeIpAllowed}from"#runtime/governance/network/ip-allow-list.js";const vercelOidcLog=createLogger(`auth.vercel-oidc`);function verifyHttpBasic(e,t){if(e===null)return{ok:!1};let r=authenticateHttpBasicStrategy({authorization:e,strategy:{kind:`http-basic`,password:t.password,username:t.username}});return r.kind===`authenticated`?{ok:!0,sessionAuth:createRuntimeSessionAuthContext(r.principal)}:{ok:!1}}async function verifyJwtHmac(e,t){if(e===null||e.length===0)return{ok:!1};let n=await authenticateJwtHmacStrategy({strategy:{algorithm:t.algorithm,audiences:[...t.audiences],clockSkewSeconds:t.clockSkewSeconds??30,issuer:t.issuer,kind:`jwt-hmac`,secret:t.secret,...t.claims===void 0?{}:{claims:t.claims},...t.subjects===void 0?{}:{subjects:t.subjects}},token:e});return n.kind===`authenticated`?{ok:!0,sessionAuth:createRuntimeSessionAuthContext(n.principal)}:{ok:!1}}async function verifyJwtEcdsa(e,t){if(e===null||e.length===0)return{ok:!1};let n=await authenticateJwtEcdsaStrategy({strategy:{algorithm:t.algorithm,audiences:[...t.audiences],clockSkewSeconds:t.clockSkewSeconds??30,issuer:t.issuer,kind:`jwt-ecdsa`,publicKey:t.publicKey,...t.claims===void 0?{}:{claims:t.claims},...t.subjects===void 0?{}:{subjects:t.subjects}},token:e});return n.kind===`authenticated`?{ok:!0,sessionAuth:createRuntimeSessionAuthContext(n.principal)}:{ok:!1}}async function verifyOidc(e,t){let n=await runOidcVerification(e,{...t,acceptCurrentVercelProject:!1});return n.kind===`authenticated`?{ok:!0,sessionAuth:createRuntimeSessionAuthContext(n.principal)}:{ok:!1}}async function runOidcVerification(e,t){return e===null||e.length===0?{kind:`not-authenticated`}:await authenticateOidcStrategy({strategy:{acceptCurrentVercelProject:t.acceptCurrentVercelProject,audiences:[...t.audiences],clockSkewSeconds:t.clockSkewSeconds??30,discoveryUrl:t.discoveryUrl??`${t.issuer.replace(/\/$/,``)}/.well-known/openid-configuration`,issuer:t.issuer,kind:`oidc`,...t.claims===void 0?{}:{claims:t.claims},...t.subjects===void 0?{}:{subjects:t.subjects}},token:e})}function extractBearerToken(e){if(e===null)return null;let t=/^Bearer\s+(.+)$/i.exec(e)?.[1]?.trim();return t===void 0||t.length===0?null:t}function createIpAllowList(e){return createRuntimeIpAllowList(e)}function isIpAllowed(e,t){return e===null?!1:isRuntimeIpAllowed(e,t)}function createUnauthorizedResponse(e={}){let t=e.status??401,n=e.code??(t===403?`forbidden`:`unauthorized`),r=e.message??(t===403?`Forbidden.`:`Authorization is required for this route.`),i=e.challenges??[],a=new Headers({"cache-control":`no-store`});for(let e of i)a.append(`www-authenticate`,formatChallenge(e));return Response.json({code:n,error:r,ok:!1},{headers:a,status:t})}function formatChallenge(e){if(e.parameters===void 0||Object.keys(e.parameters).length===0)return e.scheme;let t=Object.entries(e.parameters).map(([e,t])=>`${e}="${escapeChallengeValue(t)}"`).join(`, `);return`${e.scheme} ${t}`}function escapeChallengeValue(e){return e.replaceAll(`\\`,`\\\\`).replaceAll(`"`,`\\"`)}var UnauthenticatedError=class extends Error{response;constructor(e={}){super(e.message??`Authorization is required for this route.`),this.name=`UnauthenticatedError`,this.response=createUnauthorizedResponse({...e,status:401})}},ForbiddenError=class extends Error{response;constructor(e={}){super(e.message??`Forbidden.`),this.name=`ForbiddenError`,this.response=createUnauthorizedResponse({...e,status:403})}};async function routeAuth(e,t){let n=Array.isArray(t)?t:[t];try{for(let t of n){let n=await t(e);if(n)return n}}catch(e){if(typeof e==`object`&&e&&`response`in e&&e.response instanceof Response)return e.response;throw e}return createUnauthorizedResponse({challenges:[{scheme:`Bearer`}]})}function placeholderAuth(){return()=>{if(process.env.VERCEL_ENV!==`production`)return null;throw new UnauthenticatedError({code:`ash_production_auth_not_configured`,message:`Production auth is not configured. Replace placeholderAuth() in agent/channels/ash.ts with your app's auth provider.`})}}function none(){return()=>ANONYMOUS_SESSION_AUTH_CONTEXT}function localDev(){return e=>process.env.VERCEL&&process.env.VERCEL_ENV===`development`||isLoopbackRequest(e)?LOCAL_DEV_SESSION_AUTH_CONTEXT:null}const LOOPBACK_HOSTNAMES=new Set([`localhost`,`[::1]`]),LOOPBACK_IPV4_PREFIX=/^127\./;function isLoopbackRequest(e){let t;try{t=new URL(e.url).hostname}catch{return!1}return!!(LOOPBACK_HOSTNAMES.has(t)||LOOPBACK_IPV4_PREFIX.test(t)||t.endsWith(`.localhost`))}const ANONYMOUS_SESSION_AUTH_CONTEXT={attributes:{},authenticator:`none`,principalId:`anonymous`,principalType:`anonymous`},LOCAL_DEV_SESSION_AUTH_CONTEXT={attributes:{},authenticator:`local-dev`,principalId:`local-dev`,principalType:`local-dev`};async function verifyVercelOidc(e,t={}){if(e===null||e.length===0)return{ok:!1};let n=decodeUnverifiedJwtClaims(e);if(n===null)return vercelOidcLog.debug(`Rejected token that failed to decode as a JWT.`),{ok:!1};if(!n.issuer.startsWith(`https://oidc.vercel.com/`))return vercelOidcLog.debug(`Rejected token whose issuer is not a Vercel OIDC issuer.`,{issuer:n.issuer}),{ok:!1};if(n.audiences.length===0)return vercelOidcLog.debug(`Rejected token with no audience claim.`,{issuer:n.issuer}),{ok:!1};let r=await runOidcVerification(e,{acceptCurrentVercelProject:!0,audiences:n.audiences,issuer:n.issuer,subjects:t.subjects??[]});return r.kind===`authenticated`?(vercelOidcLog.debug(`Accepted Vercel OIDC token.`,{issuer:n.issuer,principalType:r.principal.principalType,subject:r.principal.subject}),{ok:!0,sessionAuth:createRuntimeSessionAuthContext(r.principal)}):(vercelOidcLog.debug(`Rejected Vercel OIDC token after verification.`,{audiences:n.audiences,issuer:n.issuer,reason:r.kind,subjectsConfigured:(t.subjects??[]).length>0,...r.kind===`misconfigured`?{detail:r.message}:{}}),{ok:!1})}function vercelSubject(e){assertVercelSubjectSegment(`teamSlug`,e.teamSlug),assertVercelSubjectSegment(`projectName`,e.projectName);let t=e.environment??`production`;if(t!==`production`&&t!==`preview`&&t!==`development`&&t!==`*`)throw Error(`vercelSubject: invalid environment ${JSON.stringify(t)}; expected "production", "preview", "development", or "*".`);return`owner:${e.teamSlug}:project:${e.projectName}:environment:${t}`}function assertVercelSubjectSegment(e,t){if(t.length===0)throw Error(`vercelSubject: ${e} must be a non-empty string.`);if(t.includes(`*`)||t.includes(`:`))throw Error(`vercelSubject: ${e} ${JSON.stringify(t)} may not contain ${t.includes(`:`)?`':'`:`'*'`}. Hand-write the subject string when wildcards are intentional.`)}function vercelOidc(e={}){return async t=>{let n=await verifyVercelOidc(extractBearerToken(t.headers.get(`authorization`)),e);return n.ok?n.sessionAuth:null}}function httpBasic(e){return t=>{let n=verifyHttpBasic(t.headers.get(`authorization`),e);return n.ok?n.sessionAuth:null}}function jwtHmac(e){return async t=>{let n=await verifyJwtHmac(extractBearerToken(t.headers.get(`authorization`)),e);return n.ok?n.sessionAuth:null}}function jwtEcdsa(e){return async t=>{let n=await verifyJwtEcdsa(extractBearerToken(t.headers.get(`authorization`)),e);return n.ok?n.sessionAuth:null}}function oidc(e){return async t=>{let n=await verifyOidc(extractBearerToken(t.headers.get(`authorization`)),e);return n.ok?n.sessionAuth:null}}function decodeUnverifiedJwtClaims(e){let n;try{n=decodeJwt(e)}catch{return null}return typeof n.iss!=`string`||n.iss.length===0?null:{audiences:typeof n.aud==`string`?[n.aud]:Array.isArray(n.aud)?n.aud.filter(e=>typeof e==`string`):[],issuer:n.iss}}export{ForbiddenError,UnauthenticatedError,createIpAllowList,createUnauthorizedResponse,extractBearerToken,httpBasic,isIpAllowed,jwtEcdsa,jwtHmac,localDev,none,oidc,placeholderAuth,routeAuth,vercelOidc,vercelSubject,verifyHttpBasic,verifyJwtEcdsa,verifyJwtHmac,verifyOidc,verifyVercelOidc};
|
|
@@ -93,7 +93,9 @@ export declare function parseAppMentionEvent(envelope: SlackEventCallback): Slac
|
|
|
93
93
|
* Returns `null` when:
|
|
94
94
|
* - the envelope is not an IM `message` event,
|
|
95
95
|
* - required fields (channel id, ts) are missing,
|
|
96
|
-
* - the message carries a `subtype` (edits, deletes, joins, etc.)
|
|
96
|
+
* - the message carries a system `subtype` (edits, deletes, joins, etc.)
|
|
97
|
+
* other than `file_share` — file uploads are real user messages and
|
|
98
|
+
* must reach the handler with their attachments intact, or
|
|
97
99
|
* - the message was posted by a bot (`bot_id` set) — this prevents the
|
|
98
100
|
* bot's own DM replies from re-triggering the handler.
|
|
99
101
|
*/
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{slackMrkdwnToGfm}from"#public/channels/slack/mrkdwn.js";function parseAppMentionEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;return!t||t.type!==`app_mention`?null:buildSlackMessage(t,e.team_id)}function parseDirectMessageEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;if(!t||t.type!==`message`)return null;let n=t;return n.channel_type!==`im`||typeof n.subtype==`string`&&n.subtype.length>0
|
|
1
|
+
import{slackMrkdwnToGfm}from"#public/channels/slack/mrkdwn.js";function parseAppMentionEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;return!t||t.type!==`app_mention`?null:buildSlackMessage(t,e.team_id)}function parseDirectMessageEvent(e){if(e.type!==`event_callback`)return null;let t=e.event;if(!t||t.type!==`message`)return null;let n=t;return n.channel_type!==`im`||typeof n.subtype==`string`&&n.subtype.length>0&&n.subtype!==`file_share`||typeof n.bot_id==`string`&&n.bot_id.length>0?null:buildSlackMessage(n,e.team_id)}function buildSlackMessage(t,n){let r=typeof t.channel==`string`?t.channel:``,i=typeof t.ts==`string`?t.ts:``;if(!r||!i)return null;let a=typeof t.text==`string`?t.text:``,o=typeof t.thread_ts==`string`?t.thread_ts:i,s=typeof n==`string`?n:void 0;return{text:a,markdown:slackMrkdwnToGfm(a),ts:i,threadTs:o,channelId:r,teamId:s,author:parseAuthor(t),attachments:parseAttachments(t.files),raw:t}}function parseAuthor(e){let t=typeof e.user==`string`?e.user:``;if(t)return{userId:t,userName:typeof e.username==`string`?e.username:void 0,fullName:void 0,isBot:typeof e.bot_id==`string`&&e.bot_id.length>0,isMe:!1}}function parseAttachments(e){return Array.isArray(e)?e.map(toAttachment):[]}function toAttachment(e){let t=typeof e.mimetype==`string`?e.mimetype:void 0,n=typeof e.url_private==`string`?e.url_private:void 0;return{id:typeof e.id==`string`?e.id:``,type:inferAttachmentType(t),url:n,name:typeof e.name==`string`?e.name:void 0,mimeType:t,size:typeof e.size==`number`?e.size:void 0}}function inferAttachmentType(e){return e===void 0?`file`:e.startsWith(`image/`)?`image`:e.startsWith(`video/`)?`video`:e.startsWith(`audio/`)?`audio`:`file`}function formatSlackContextBlock(e){return[`<slack_context>`,`user_id: ${e.userId}`,...e.userName?[`user_name: ${e.userName}`]:[],...e.fullName?[`full_name: ${e.fullName}`]:[],`channel_id: ${e.channelId}`,`thread_ts: ${e.threadTs}`,...e.teamId?[`team_id: ${e.teamId}`]:[],`</slack_context>`].join(`
|
|
2
2
|
`)}export{formatSlackContextBlock,parseAppMentionEvent,parseDirectMessageEvent};
|
package/dist/src/svelte/index.js
CHANGED
package/dist/src/vue/index.js
CHANGED