@ganju/utils 0.0.3 → 0.0.5

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.
@@ -0,0 +1,25 @@
1
+ /** The SHA-256 of a presented token, in the encoding the column stores. */
2
+ export declare const hashAccessToken: (token: string) => Promise<string>;
3
+ /**
4
+ * Cheap enough to run before the database is touched: a bearer token that does
5
+ * not carry the prefix is an OAuth token, and belongs on the other path.
6
+ */
7
+ export declare const isAccessToken: (token: string) => boolean;
8
+ /**
9
+ * What the dashboard shows beside a token's name.
10
+ *
11
+ * Enough to tell two rows apart when someone is deciding which to revoke, and
12
+ * deliberately taken from the *front* of the secret rather than the end: a
13
+ * prefix narrows a brute-force search by exactly as much as a suffix would, and
14
+ * a value people are used to seeing truncated at the end reads as complete when
15
+ * it is the end that is shown.
16
+ */
17
+ export declare const accessTokenHint: (token: string) => string;
18
+ export interface MintedAccessToken {
19
+ /** The only time this value exists. Returned to the caller, never stored. */
20
+ token: string;
21
+ tokenHash: string;
22
+ hint: string;
23
+ }
24
+ export declare const mintAccessToken: () => Promise<MintedAccessToken>;
25
+ //# sourceMappingURL=accessToken.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessToken.d.ts","sourceRoot":"","sources":["../src/accessToken.ts"],"names":[],"mappings":"AA+BA,2EAA2E;AAC3E,eAAO,MAAM,eAAe,GAAU,OAAO,MAAM,KAAG,OAAO,CAAC,MAAM,CAMnE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,KAAG,OACG,CAAC;AAElD;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,KAAG,MAI1B,CAAC;AAEvB,MAAM,WAAW,iBAAiB;IAChC,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,eAAe,QAAa,OAAO,CAAC,iBAAiB,CASjE,CAAC"}
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mintAccessToken = exports.accessTokenHint = exports.isAccessToken = exports.hashAccessToken = void 0;
4
+ const constants_1 = require("./constants");
5
+ /**
6
+ * Minting and recognising a personal access token.
7
+ *
8
+ * The value exists in plaintext exactly once — in the response to the request
9
+ * that created it — and what the database holds is its SHA-256. That is the
10
+ * property the whole credential rests on: a leaked backup, a stray log line, or
11
+ * a support engineer reading the row learns nothing they could present as the
12
+ * token, and there is no path in the product that can print one back, because
13
+ * there is nothing to print.
14
+ *
15
+ * SHA-256 rather than a password hash on purpose. A password is a low-entropy
16
+ * secret a person chose, so the cost of hashing it is what stands between a
17
+ * stolen table and the passwords in it; this is 32 bytes from a CSPRNG, where
18
+ * that cost buys nothing and would be paid on every authenticated request. It
19
+ * is also the hash `oauth_client.client_secret` already uses in this system, so
20
+ * there is one answer here to "how is a machine credential stored".
21
+ */
22
+ const HINT_SEPARATOR = '…';
23
+ const base64url = (bytes) => {
24
+ let binary = '';
25
+ for (const byte of bytes)
26
+ binary += String.fromCharCode(byte);
27
+ return btoa(binary)
28
+ .replace(/\+/g, '-')
29
+ .replace(/\//g, '_')
30
+ .replace(/=+$/, '');
31
+ };
32
+ /** The SHA-256 of a presented token, in the encoding the column stores. */
33
+ const hashAccessToken = async (token) => {
34
+ const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(token));
35
+ return base64url(new Uint8Array(digest));
36
+ };
37
+ exports.hashAccessToken = hashAccessToken;
38
+ /**
39
+ * Cheap enough to run before the database is touched: a bearer token that does
40
+ * not carry the prefix is an OAuth token, and belongs on the other path.
41
+ */
42
+ const isAccessToken = (token) => token.startsWith(constants_1.constants.ACCESS_TOKEN_PREFIX);
43
+ exports.isAccessToken = isAccessToken;
44
+ /**
45
+ * What the dashboard shows beside a token's name.
46
+ *
47
+ * Enough to tell two rows apart when someone is deciding which to revoke, and
48
+ * deliberately taken from the *front* of the secret rather than the end: a
49
+ * prefix narrows a brute-force search by exactly as much as a suffix would, and
50
+ * a value people are used to seeing truncated at the end reads as complete when
51
+ * it is the end that is shown.
52
+ */
53
+ const accessTokenHint = (token) => `${token.slice(0, constants_1.constants.ACCESS_TOKEN_PREFIX.length + constants_1.constants.ACCESS_TOKEN_HINT_CHARS)}${HINT_SEPARATOR}`;
54
+ exports.accessTokenHint = accessTokenHint;
55
+ const mintAccessToken = async () => {
56
+ const bytes = new Uint8Array(constants_1.constants.ACCESS_TOKEN_BYTES);
57
+ crypto.getRandomValues(bytes);
58
+ const token = `${constants_1.constants.ACCESS_TOKEN_PREFIX}${base64url(bytes)}`;
59
+ return {
60
+ token,
61
+ tokenHash: await (0, exports.hashAccessToken)(token),
62
+ hint: (0, exports.accessTokenHint)(token)
63
+ };
64
+ };
65
+ exports.mintAccessToken = mintAccessToken;
@@ -519,6 +519,10 @@ export declare const constants: {
519
519
  CUSTOM_CODE_MAX_FILE_PATH: number;
520
520
  CUSTOM_CODE_VERSION_STATUSES: ("draft" | "published" | "archived")[];
521
521
  CUSTOM_CODE_SCRIPT_NAME_PREFIX: string;
522
+ CUSTOM_CODE_SCRIPT_NAME_MAX: number;
523
+ CUSTOM_CODE_UPLOAD_SUFFIX_CHARS: number;
524
+ CUSTOM_CODE_SWEEP_GRACE_MS: number;
525
+ CUSTOM_CODE_SWEEP_MAX_DELETES: number;
522
526
  CUSTOM_CODE_PREVIEW_SCRIPT_SUFFIX: string;
523
527
  CUSTOM_CODE_PREVIEW_TOKEN_TTL_MS: number;
524
528
  CUSTOM_CODE_TEST_TIMEOUT_MS: number;
@@ -560,8 +564,8 @@ export declare const constants: {
560
564
  CUSTOM_CODE_BROKER_SERVICE_ENV: string;
561
565
  CUSTOM_CODE_COMPATIBILITY_DATE: string;
562
566
  CUSTOM_CODE_SCRIPT_CPU_MS: number;
563
- CUSTOM_CODE_SMOKE_TIMEOUT_MS: number;
564
- CUSTOM_CODE_SMOKE_INTERVAL_MS: number;
567
+ CUSTOM_CODE_REGISTER_TIMEOUT_MS: number;
568
+ CUSTOM_CODE_REGISTER_INTERVAL_MS: number;
565
569
  CUSTOM_CODE_MAX_LOGS: number;
566
570
  CUSTOM_CODE_MAX_LOG_LENGTH: number;
567
571
  CUSTOM_CODE_SEND_FILE_TARGET_GMAIL: "gmail";
@@ -614,6 +618,15 @@ export declare const constants: {
614
618
  CLI_OAUTH_REDIRECT_PORTS: number[];
615
619
  CLI_OAUTH_SCOPES: string[];
616
620
  CLI_TOKEN_REFRESH_SKEW_SECONDS: number;
621
+ ACCESS_TOKEN_PREFIX: string;
622
+ ACCESS_TOKEN_BYTES: number;
623
+ ACCESS_TOKEN_HINT_CHARS: number;
624
+ ACCESS_TOKEN_NAME_MAX: number;
625
+ ACCESS_TOKEN_MAX_PER_PROJECT: number;
626
+ ACCESS_TOKEN_MAX_EXPIRY_DAYS: number;
627
+ ACCESS_TOKEN_LAST_USED_INTERVAL_MS: number;
628
+ ACCESS_TOKEN_UNSCOPED_PATHS: string[];
629
+ ACCESS_TOKEN_SCOPE_MESSAGE: string;
617
630
  CUSTOM_CODE_LOGS_DEFAULT_LIMIT: number;
618
631
  CUSTOM_CODE_LOGS_MAX_LIMIT: number;
619
632
  BOT_GRANT_TYPE: string;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AA6JA,QAAA,MAAM,yBAAyB,EAAiB,UAAU,CAAC;AAC3D,QAAA,MAAM,+BAA+B,EAAuB,gBAAgB,CAAC;AAC7E,QAAA,MAAM,8BAA8B,EAAsB,eAAe,CAAC;AAC1E,QAAA,MAAM,wBAAwB,EAAgB,SAAS,CAAC;AAuCxD,QAAA,MAAM,sBAAsB,EAAiB,UAAU,CAAC;AACxD,QAAA,MAAM,4BAA4B,EAAuB,gBAAgB,CAAC;AAC1E,QAAA,MAAM,oBAAoB,EAAe,QAAQ,CAAC;AAClD,QAAA,MAAM,oBAAoB,EAAe,QAAQ,CAAC;AAmYlD,QAAA,MAAM,sBAAsB,EAAkB,WAAW,CAAC;AAC1D,QAAA,MAAM,mBAAmB,EAAe,QAAQ,CAAC;AAGjD,QAAA,MAAM,mBAAmB,EAAe,QAAQ,CAAC;AAkTjD,MAAM,MAAM,mBAAmB,GAC3B;IACE,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GACD;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9D;IACE,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GACD;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAi9BpE,UAAU,UAAU;IAElB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAGhC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAMjC,sBAAsB,EAAE,MAAM,CAAC;IAM/B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,OAAO,CAAC;IAKnB,eAAe,EAAE,OAAO,CAAC;IAOzB,gBAAgB,EAAE,OAAO,CAAC;IAK1B,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;IAG3C,gBAAgB,EAAE,MAAM,CAAC;IACzB,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAqKD,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eA74DhB,OAAO,yBAAyB,GAChC,OAAO,+BAA+B,GACtC,OAAO,8BAA8B,GACrC,OAAO,wBAAwB;eAC5B,MAAM;;;;;;;;;;;;;;;;;;eAsCT,OAAO,sBAAsB,GAC7B,OAAO,4BAA4B,GACnC,OAAO,oBAAoB,GAC3B,OAAO,oBAAoB;eACxB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA6YT,OAAO,mBAAmB,GAC1B,OAAO,mBAAmB,GAC1B,OAAO,sBAAsB;eAC1B,MAAM;eACN,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eA6NwB,MAAM,GAAG,MAAM;cAAQ,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiyDnE,CAAC"}
package/dist/constants.js CHANGED
@@ -1041,9 +1041,36 @@ const CUSTOM_CODE_MAIN_MODULE = 'index.js';
1041
1041
  // object read on every deploy.
1042
1042
  const CUSTOM_CODE_MAX_FILES = 25;
1043
1043
  const CUSTOM_CODE_MAX_FILE_PATH = 100;
1044
- // WfP script name: `artifact_<artifactId>`. The id, never the slug — slugs are
1045
- // user-editable and a rename would orphan the deployed script.
1044
+ // WfP script name: `artifact_<artifactId>_<upload>`. The id, never the slug —
1045
+ // slugs are user-editable and a rename would orphan the deployed script.
1046
+ //
1047
+ // The trailing segment is minted per upload rather than derived from anything,
1048
+ // which is the whole point: uploading over a name that already exists is not
1049
+ // read-your-writes, so a deploy that replaces a script can serve the previous
1050
+ // edition for up to half a minute. A name nothing has ever used cannot, and
1051
+ // costs nothing to mint. Everything a publish used to do to survive that race —
1052
+ // waiting on an edition marker, refusing with a 503, putting the previous bundle
1053
+ // back when validation failed — went with the reuse that caused it.
1046
1054
  const CUSTOM_CODE_SCRIPT_NAME_PREFIX = 'artifact_';
1055
+ // Worker names cap at 63 characters, and `artifact_<uuid>` already spends 45.
1056
+ // That leaves 17 for a separator and a suffix, so a second uuid does not fit and
1057
+ // neither does a hex-32 digest. Twelve hex characters is 48 bits against a
1058
+ // namespace holding at most a few hundred names for any one artifact — not a
1059
+ // collision worth checking for, and an upload to a name in use would fail loudly
1060
+ // rather than quietly serve the wrong code.
1061
+ const CUSTOM_CODE_SCRIPT_NAME_MAX = 63;
1062
+ const CUSTOM_CODE_UPLOAD_SUFFIX_CHARS = 12;
1063
+ // How long a superseded script stays in the namespace before the hourly sweep
1064
+ // may collect it.
1065
+ //
1066
+ // Deleting at publish time would race the thing it deletes: a tool call that
1067
+ // resolved the old pointer a moment earlier is still in flight, and the pointer
1068
+ // moving does not recall it. An hour is far longer than any call can take, and
1069
+ // the wait costs $0.02 per script per month against an allowance of 1,000.
1070
+ const CUSTOM_CODE_SWEEP_GRACE_MS = 60 * 60 * 1_000;
1071
+ // Deletes per sweep. A backlog drains over several hourly runs rather than
1072
+ // making one run unbounded — the same shape the retention purge uses.
1073
+ const CUSTOM_CODE_SWEEP_MAX_DELETES = 200;
1047
1074
  // A second script per artifact, `artifact_<id>_preview`, that the Test panel
1048
1075
  // deploys a draft into and calls.
1049
1076
  //
@@ -1228,15 +1255,17 @@ const CUSTOM_CODE_COMPATIBILITY_DATE = '2025-11-17';
1228
1255
  // tighter than our own workers' 30s — this is the technical cap that bounds
1229
1256
  // what one adversarial call can cost us, so an infinite loop in a customer's
1230
1257
  // tool is billed as five seconds rather than as whatever it wanted.
1231
- // How long publish waits for the edition it just uploaded to be the one the
1232
- // dispatcher answers with, and how often it asks.
1258
+ // How long a deploy waits for a freshly minted script name to become
1259
+ // dispatchable, and how often it asks.
1233
1260
  //
1234
- // Bounded rather than open-ended because this runs inside the publish request:
1235
- // past this, publishing would be a request nobody waits out. Exceeding it is
1236
- // reported as "try again" rather than published, which is the safe direction
1237
- // the alternative is advertising tools backed by a script that is not there yet.
1238
- const CUSTOM_CODE_SMOKE_TIMEOUT_MS = 20_000;
1239
- const CUSTOM_CODE_SMOKE_INTERVAL_MS = 1_000;
1261
+ // Every upload goes to a name that has never been used, which is
1262
+ // read-your-writes: ~2s end to end against the deployed namespace, against the
1263
+ // 20-41s a replacement could take. So this bounds how long a brand-new name
1264
+ // takes to register, never how long an old edition takes to stop answering
1265
+ // there is no old edition. It is short for that reason, and a script that
1266
+ // answers with the wrong edition now fails outright instead of being waited on.
1267
+ const CUSTOM_CODE_REGISTER_TIMEOUT_MS = 8_000;
1268
+ const CUSTOM_CODE_REGISTER_INTERVAL_MS = 500;
1240
1269
  const CUSTOM_CODE_SCRIPT_CPU_MS = 5_000;
1241
1270
  // The ctx.log() caps also live in ./sdkConstants — the buffer that enforces them
1242
1271
  // runs inside the isolate.
@@ -1410,6 +1439,46 @@ const ARTIFACT_SCOPE_PREFIX = 'artifact:';
1410
1439
  // loopback matching, where the port is ignored for a 127.0.0.0/8 redirect, but
1411
1440
  // that is one library's behaviour and this is a login that has already opened
1412
1441
  // someone's browser by the time it would fail.
1442
+ // A personal access token — the durable credential a machine with no browser
1443
+ // uses, where an OAuth access token's one hour is not enough. Bound to one
1444
+ // project, because that is the unit a deploy pipeline works on: one repository,
1445
+ // one artifact, one credential in its CI settings.
1446
+ //
1447
+ // The prefix is part of the value rather than decoration: it is what lets the
1448
+ // middleware tell one of these from an OAuth token before it decides which
1449
+ // lookup to make, and it is what secret scanners match on when one leaks into a
1450
+ // repository. The rest is 32 random bytes, base64url — the token is the only
1451
+ // place the value ever exists in plaintext, since what is stored is its hash.
1452
+ const ACCESS_TOKEN_PREFIX = 'ganju_pat_';
1453
+ const ACCESS_TOKEN_BYTES = 32;
1454
+ // Enough of the secret to recognise a row by, and not enough to be worth
1455
+ // stealing. Shown in the dashboard and by `ganju token list` beside the name.
1456
+ const ACCESS_TOKEN_HINT_CHARS = 6;
1457
+ const ACCESS_TOKEN_NAME_MAX = 100;
1458
+ // A ceiling per project, so a compromised session cannot quietly mint an
1459
+ // unbounded set of credentials that each survive the session being ended.
1460
+ const ACCESS_TOKEN_MAX_PER_PROJECT = 20;
1461
+ // An expiry is optional, because a scheduled deploy that dies on a date nobody
1462
+ // wrote down is its own kind of outage — but a year is the longest we will
1463
+ // write one for.
1464
+ const ACCESS_TOKEN_MAX_EXPIRY_DAYS = 365;
1465
+ // `last_used_at` is a convenience, not an audit log, so it is written at most
1466
+ // this often per token rather than on every request. The question it answers —
1467
+ // "is anything still using this, or can I revoke it" — does not get a better
1468
+ // answer from minute-level precision, and the write would otherwise land on the
1469
+ // hot path of every CI request.
1470
+ const ACCESS_TOKEN_LAST_USED_INTERVAL_MS = 5 * 60 * 1000;
1471
+ // The only path a personal access token may reach without naming the project it
1472
+ // is scoped to, and only on GET. `/me` reports who the token is, which is how
1473
+ // the CLI confirms a machine is authenticated at all, and tells it nothing it
1474
+ // does not already hold.
1475
+ //
1476
+ // Everything else is refused, organization routes included — billing, members,
1477
+ // the model configs and the other projects are not what a deploy credential is
1478
+ // for. The list is deliberately this short: a route added later is closed by
1479
+ // omission rather than open by it.
1480
+ const ACCESS_TOKEN_UNSCOPED_PATHS = ['/me'];
1481
+ const ACCESS_TOKEN_SCOPE_MESSAGE = 'This token is scoped to a different project';
1413
1482
  // Recent custom-tool invocations, as `ganju logs` reads them.
1414
1483
  const CUSTOM_CODE_LOGS_DEFAULT_LIMIT = 20;
1415
1484
  const CUSTOM_CODE_LOGS_MAX_LIMIT = 100;
@@ -2139,6 +2208,10 @@ exports.constants = {
2139
2208
  CUSTOM_CODE_MAX_FILE_PATH,
2140
2209
  CUSTOM_CODE_VERSION_STATUSES,
2141
2210
  CUSTOM_CODE_SCRIPT_NAME_PREFIX,
2211
+ CUSTOM_CODE_SCRIPT_NAME_MAX,
2212
+ CUSTOM_CODE_UPLOAD_SUFFIX_CHARS,
2213
+ CUSTOM_CODE_SWEEP_GRACE_MS,
2214
+ CUSTOM_CODE_SWEEP_MAX_DELETES,
2142
2215
  CUSTOM_CODE_PREVIEW_SCRIPT_SUFFIX,
2143
2216
  CUSTOM_CODE_PREVIEW_TOKEN_TTL_MS,
2144
2217
  CUSTOM_CODE_TEST_TIMEOUT_MS,
@@ -2180,8 +2253,8 @@ exports.constants = {
2180
2253
  CUSTOM_CODE_BROKER_SERVICE_ENV,
2181
2254
  CUSTOM_CODE_COMPATIBILITY_DATE,
2182
2255
  CUSTOM_CODE_SCRIPT_CPU_MS,
2183
- CUSTOM_CODE_SMOKE_TIMEOUT_MS,
2184
- CUSTOM_CODE_SMOKE_INTERVAL_MS,
2256
+ CUSTOM_CODE_REGISTER_TIMEOUT_MS,
2257
+ CUSTOM_CODE_REGISTER_INTERVAL_MS,
2185
2258
  CUSTOM_CODE_MAX_LOGS: sdkConstants_1.CUSTOM_CODE_MAX_LOGS,
2186
2259
  CUSTOM_CODE_MAX_LOG_LENGTH: sdkConstants_1.CUSTOM_CODE_MAX_LOG_LENGTH,
2187
2260
  CUSTOM_CODE_SEND_FILE_TARGET_GMAIL,
@@ -2234,6 +2307,15 @@ exports.constants = {
2234
2307
  CLI_OAUTH_REDIRECT_PORTS: cliConstants_1.CLI_OAUTH_REDIRECT_PORTS,
2235
2308
  CLI_OAUTH_SCOPES: cliConstants_1.CLI_OAUTH_SCOPES,
2236
2309
  CLI_TOKEN_REFRESH_SKEW_SECONDS: cliConstants_1.CLI_TOKEN_REFRESH_SKEW_SECONDS,
2310
+ ACCESS_TOKEN_PREFIX,
2311
+ ACCESS_TOKEN_BYTES,
2312
+ ACCESS_TOKEN_HINT_CHARS,
2313
+ ACCESS_TOKEN_NAME_MAX,
2314
+ ACCESS_TOKEN_MAX_PER_PROJECT,
2315
+ ACCESS_TOKEN_MAX_EXPIRY_DAYS,
2316
+ ACCESS_TOKEN_LAST_USED_INTERVAL_MS,
2317
+ ACCESS_TOKEN_UNSCOPED_PATHS,
2318
+ ACCESS_TOKEN_SCOPE_MESSAGE,
2237
2319
  CUSTOM_CODE_LOGS_DEFAULT_LIMIT,
2238
2320
  CUSTOM_CODE_LOGS_MAX_LIMIT,
2239
2321
  BOT_GRANT_TYPE,
@@ -24,18 +24,53 @@ export declare const mintCustomCodeToken: (payload: Omit<CustomCodeTokenPayload,
24
24
  */
25
25
  export declare const verifyCustomCodeToken: (token: string, secret: string, now?: number) => Promise<CustomCodeTokenPayload | null>;
26
26
  /**
27
- * The dispatch-namespace script name for an artifact: `artifact_<id>`.
27
+ * The legacy dispatch-namespace script name for an artifact: `artifact_<id>`.
28
28
  *
29
29
  * The id, never the slug — slugs are user-editable and a rename would orphan the
30
30
  * deployed script while the database still pointed at a live version.
31
+ *
32
+ * Nothing uploads to this name any more; every deploy mints its own. It survives
33
+ * as the fallback for a version published before `script_name` existed, whose
34
+ * bundle really is sitting under this name. Tightening a rule must never stop an
35
+ * already-published version from serving, because that failure is invisible to
36
+ * whoever owns it — the same reason the boot loop still accepts a stored tool key
37
+ * the current catalog no longer offers.
31
38
  */
32
39
  export declare const customCodeScriptName: (artifactId: string) => string;
33
40
  /**
34
- * The script name a test run deploys into: `artifact_<id>_preview`.
41
+ * The legacy preview script name: `artifact_<id>_preview`.
35
42
  *
36
- * A second script rather than a second version of the live one, because a test
37
- * must not be able to disturb what MCP clients are being served and the only
38
- * way to be certain of that is for it to run under a name nothing dispatches to.
43
+ * Kept for the same reason as the one above, and for one more: it is the prefix
44
+ * the sweep matches to recognise a preview script left behind by a test run that
45
+ * did not clean up after itself.
39
46
  */
40
47
  export declare const customCodePreviewScriptName: (artifactId: string) => string;
48
+ /**
49
+ * A dispatch-namespace name no upload has ever used:
50
+ * `artifact_<id>_<12 hex chars>`.
51
+ *
52
+ * Minted rather than derived, and that is the entire design. Uploading over an
53
+ * existing name is not read-your-writes — a replacement can serve the previous
54
+ * edition for tens of seconds — so a deploy that always writes to a new name is
55
+ * correct by construction rather than by waiting to see whether it worked.
56
+ *
57
+ * The suffix deliberately carries no meaning. The two candidates that did are
58
+ * both wrong: the bundle digest collides whenever a deploy reverts to bytes that
59
+ * shipped before, which is exactly what a rollback is, and the version id is one
60
+ * string across every re-upload of a single draft, which is every test run of it.
61
+ *
62
+ * Twelve hex characters is what fits. Worker names cap at 63 and
63
+ * `artifact_<uuid>` spends 45 of them.
64
+ */
65
+ export declare const customCodeUploadName: (artifactId: string) => string;
66
+ /**
67
+ * A preview name no test run has ever used:
68
+ * `artifact_<id>_preview_<12 hex chars>`.
69
+ *
70
+ * The sharper version of the same race. Every test used to deploy over one
71
+ * preview name, so a test could report the run before it — which reads as "my
72
+ * edit did nothing" from the one tool whose whole job is to say what an edit
73
+ * does. Nothing stores this: it is minted, used, and deleted inside one request.
74
+ */
75
+ export declare const customCodePreviewUploadName: (artifactId: string) => string;
41
76
  //# sourceMappingURL=customCodeToken.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"customCodeToken.d.ts","sourceRoot":"","sources":["../src/customCodeToken.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,sBAAsB;IAGrC,CAAC,EAAE,MAAM,CAAC;IACV,UAAU,EAAE,MAAM,CAAC;IAGnB,SAAS,EAAE,MAAM,CAAC;IAIlB,GAAG,EAAE,MAAM,CAAC;IAOZ,OAAO,CAAC,EAAE,OAAO,CAAC;IAGlB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA0BD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC9B,SAAS,IAAI,CAAC,sBAAsB,EAAE,GAAG,GAAG,KAAK,GAAG,KAAK,CAAC,GAAG;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,EACD,QAAQ,MAAM,EACd,WAAU,MAAmB,KAC5B,OAAO,CAAC,MAAM,CAmBhB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAChC,OAAO,MAAM,EACb,QAAQ,MAAM,EACd,MAAM,MAAM,KACX,OAAO,CAAC,sBAAsB,GAAG,IAAI,CA+DvC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,YAAY,MAAM,KAAG,MACE,CAAC;AAE7D;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,GAAI,YAAY,MAAM,KAAG,MACoB,CAAC"}
1
+ {"version":3,"file":"customCodeToken.d.ts","sourceRoot":"","sources":["../src/customCodeToken.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,sBAAsB;IAGrC,CAAC,EAAE,MAAM,CAAC;IACV,UAAU,EAAE,MAAM,CAAC;IAGnB,SAAS,EAAE,MAAM,CAAC;IAIlB,GAAG,EAAE,MAAM,CAAC;IAOZ,OAAO,CAAC,EAAE,OAAO,CAAC;IAGlB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA0BD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC9B,SAAS,IAAI,CAAC,sBAAsB,EAAE,GAAG,GAAG,KAAK,GAAG,KAAK,CAAC,GAAG;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,EACD,QAAQ,MAAM,EACd,WAAU,MAAmB,KAC5B,OAAO,CAAC,MAAM,CAmBhB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAChC,OAAO,MAAM,EACb,QAAQ,MAAM,EACd,MAAM,MAAM,KACX,OAAO,CAAC,sBAAsB,GAAG,IAAI,CA+DvC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,GAAI,YAAY,MAAM,KAAG,MACE,CAAC;AAE7D;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,GAAI,YAAY,MAAM,KAAG,MACoB,CAAC;AAEtF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,oBAAoB,GAAI,YAAY,MAAM,KAAG,MACR,CAAC;AAEnD;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,GAAI,YAAY,MAAM,KAAG,MACR,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.customCodePreviewScriptName = exports.customCodeScriptName = exports.verifyCustomCodeToken = exports.mintCustomCodeToken = void 0;
3
+ exports.customCodePreviewUploadName = exports.customCodeUploadName = exports.customCodePreviewScriptName = exports.customCodeScriptName = exports.verifyCustomCodeToken = exports.mintCustomCodeToken = void 0;
4
4
  const base64_1 = require("./base64");
5
5
  const constants_1 = require("./constants");
6
6
  const encoder = new TextEncoder();
@@ -97,19 +97,90 @@ const verifyCustomCodeToken = async (token, secret, now) => {
97
97
  };
98
98
  exports.verifyCustomCodeToken = verifyCustomCodeToken;
99
99
  /**
100
- * The dispatch-namespace script name for an artifact: `artifact_<id>`.
100
+ * The legacy dispatch-namespace script name for an artifact: `artifact_<id>`.
101
101
  *
102
102
  * The id, never the slug — slugs are user-editable and a rename would orphan the
103
103
  * deployed script while the database still pointed at a live version.
104
+ *
105
+ * Nothing uploads to this name any more; every deploy mints its own. It survives
106
+ * as the fallback for a version published before `script_name` existed, whose
107
+ * bundle really is sitting under this name. Tightening a rule must never stop an
108
+ * already-published version from serving, because that failure is invisible to
109
+ * whoever owns it — the same reason the boot loop still accepts a stored tool key
110
+ * the current catalog no longer offers.
104
111
  */
105
112
  const customCodeScriptName = (artifactId) => `${constants_1.constants.CUSTOM_CODE_SCRIPT_NAME_PREFIX}${artifactId}`;
106
113
  exports.customCodeScriptName = customCodeScriptName;
107
114
  /**
108
- * The script name a test run deploys into: `artifact_<id>_preview`.
115
+ * The legacy preview script name: `artifact_<id>_preview`.
109
116
  *
110
- * A second script rather than a second version of the live one, because a test
111
- * must not be able to disturb what MCP clients are being served and the only
112
- * way to be certain of that is for it to run under a name nothing dispatches to.
117
+ * Kept for the same reason as the one above, and for one more: it is the prefix
118
+ * the sweep matches to recognise a preview script left behind by a test run that
119
+ * did not clean up after itself.
113
120
  */
114
121
  const customCodePreviewScriptName = (artifactId) => `${(0, exports.customCodeScriptName)(artifactId)}${constants_1.constants.CUSTOM_CODE_PREVIEW_SCRIPT_SUFFIX}`;
115
122
  exports.customCodePreviewScriptName = customCodePreviewScriptName;
123
+ /**
124
+ * A dispatch-namespace name no upload has ever used:
125
+ * `artifact_<id>_<12 hex chars>`.
126
+ *
127
+ * Minted rather than derived, and that is the entire design. Uploading over an
128
+ * existing name is not read-your-writes — a replacement can serve the previous
129
+ * edition for tens of seconds — so a deploy that always writes to a new name is
130
+ * correct by construction rather than by waiting to see whether it worked.
131
+ *
132
+ * The suffix deliberately carries no meaning. The two candidates that did are
133
+ * both wrong: the bundle digest collides whenever a deploy reverts to bytes that
134
+ * shipped before, which is exactly what a rollback is, and the version id is one
135
+ * string across every re-upload of a single draft, which is every test run of it.
136
+ *
137
+ * Twelve hex characters is what fits. Worker names cap at 63 and
138
+ * `artifact_<uuid>` spends 45 of them.
139
+ */
140
+ const customCodeUploadName = (artifactId) => mintUploadName((0, exports.customCodeScriptName)(artifactId));
141
+ exports.customCodeUploadName = customCodeUploadName;
142
+ /**
143
+ * A preview name no test run has ever used:
144
+ * `artifact_<id>_preview_<12 hex chars>`.
145
+ *
146
+ * The sharper version of the same race. Every test used to deploy over one
147
+ * preview name, so a test could report the run before it — which reads as "my
148
+ * edit did nothing" from the one tool whose whole job is to say what an edit
149
+ * does. Nothing stores this: it is minted, used, and deleted inside one request.
150
+ */
151
+ const customCodePreviewUploadName = (artifactId) => mintUploadName((0, exports.customCodePreviewScriptName)(artifactId));
152
+ exports.customCodePreviewUploadName = customCodePreviewUploadName;
153
+ /**
154
+ * Append `_<hex>` to a base name, spending whatever the 63-character ceiling
155
+ * leaves and no more.
156
+ *
157
+ * The budget is genuinely tight, and the two names spend it differently:
158
+ *
159
+ * | name | base | separator | suffix | total |
160
+ * |---|---|---|---|---|
161
+ * | live | `artifact_<uuid>` = 45 | 1 | 12 | 58 |
162
+ * | preview | + `_preview` = 53 | 1 | 8 | 62 |
163
+ *
164
+ * Twelve hex characters is 48 bits and eight is 32, against a namespace holding
165
+ * a few hundred names for any one artifact — and a preview name lives for the
166
+ * seconds one test run takes. Neither is a collision worth checking for, and an
167
+ * upload to a name already in use fails loudly rather than quietly serving the
168
+ * wrong code, which is the failure that matters.
169
+ *
170
+ * The ceiling is asserted rather than assumed: it is one number away from being
171
+ * silently exceeded by a longer prefix, and a name Cloudflare refuses would
172
+ * surface as a failed publish with nothing explaining why.
173
+ */
174
+ const mintUploadName = (base) => {
175
+ const available = constants_1.constants.CUSTOM_CODE_SCRIPT_NAME_MAX - base.length - 1;
176
+ // Even, because each byte renders as two hex characters.
177
+ const chars = Math.min(constants_1.constants.CUSTOM_CODE_UPLOAD_SUFFIX_CHARS, available) & ~1;
178
+ if (chars < 4) {
179
+ throw new Error(`A dispatch script name based on "${base}" leaves no room for a unique suffix.`);
180
+ }
181
+ const bytes = new Uint8Array(chars / 2);
182
+ crypto.getRandomValues(bytes);
183
+ return `${base}_${Array.from(bytes)
184
+ .map(byte => byte.toString(16).padStart(2, '0'))
185
+ .join('')}`;
186
+ };
package/dist/index.d.ts CHANGED
@@ -15,6 +15,7 @@ import type { OAuthProviderConfig } from './oauthProviders';
15
15
  import type { ExposableResource } from './exposedResource';
16
16
  import type { CustomCodeProject, ProjectPathIssue } from './customCodeProject';
17
17
  import type { AttachmentResource, ResolvedAttachment, ResolveAttachmentResult } from './attachment';
18
+ import type { MintedAccessToken } from './accessToken';
18
19
  import type { CatalogGroup, CatalogTool, CatalogToolDescriptor, ToolGroupKey, ToolKey } from './toolCatalog';
19
20
  import type { Separator, ChunkMetadata, PreparedChunk } from './chunking';
20
21
  import type { RateLimitRetryOptions } from './retry';
@@ -868,6 +869,28 @@ export declare const utils: {
868
869
  userId: import("zod").ZodUUID;
869
870
  organizationId: import("zod").ZodUUID;
870
871
  }, import("zod/v4/core").$strip>;
872
+ ACCESS_TOKEN_CREATE: import("zod").ZodObject<{
873
+ name: import("zod").ZodString;
874
+ expiresInDays: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodNumber>>;
875
+ userId: import("zod").ZodUUID;
876
+ organizationId: import("zod").ZodUUID;
877
+ projectId: import("zod").ZodUUID;
878
+ }, import("zod/v4/core").$strip>;
879
+ ACCESS_TOKEN_CREATE_VIEW: import("zod").ZodObject<{
880
+ name: import("zod").ZodString;
881
+ expiresInDays: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodNumber>>;
882
+ }, import("zod/v4/core").$strip>;
883
+ ACCESS_TOKEN_LIST: import("zod").ZodObject<{
884
+ userId: import("zod").ZodUUID;
885
+ organizationId: import("zod").ZodUUID;
886
+ projectId: import("zod").ZodUUID;
887
+ }, import("zod/v4/core").$strip>;
888
+ ACCESS_TOKEN_REMOVE: import("zod").ZodObject<{
889
+ tokenId: import("zod").ZodUUID;
890
+ userId: import("zod").ZodUUID;
891
+ organizationId: import("zod").ZodUUID;
892
+ projectId: import("zod").ZodUUID;
893
+ }, import("zod/v4/core").$strip>;
871
894
  CHANNEL_CONFIG: import("zod").ZodObject<{
872
895
  debounceMs: import("zod").ZodOptional<import("zod").ZodNumber>;
873
896
  }, import("zod/v4/core").$loose>;
@@ -2206,6 +2229,10 @@ export declare const utils: {
2206
2229
  CUSTOM_CODE_MAX_FILE_PATH: number;
2207
2230
  CUSTOM_CODE_VERSION_STATUSES: ("draft" | "published" | "archived")[];
2208
2231
  CUSTOM_CODE_SCRIPT_NAME_PREFIX: string;
2232
+ CUSTOM_CODE_SCRIPT_NAME_MAX: number;
2233
+ CUSTOM_CODE_UPLOAD_SUFFIX_CHARS: number;
2234
+ CUSTOM_CODE_SWEEP_GRACE_MS: number;
2235
+ CUSTOM_CODE_SWEEP_MAX_DELETES: number;
2209
2236
  CUSTOM_CODE_PREVIEW_SCRIPT_SUFFIX: string;
2210
2237
  CUSTOM_CODE_PREVIEW_TOKEN_TTL_MS: number;
2211
2238
  CUSTOM_CODE_TEST_TIMEOUT_MS: number;
@@ -2247,8 +2274,8 @@ export declare const utils: {
2247
2274
  CUSTOM_CODE_BROKER_SERVICE_ENV: string;
2248
2275
  CUSTOM_CODE_COMPATIBILITY_DATE: string;
2249
2276
  CUSTOM_CODE_SCRIPT_CPU_MS: number;
2250
- CUSTOM_CODE_SMOKE_TIMEOUT_MS: number;
2251
- CUSTOM_CODE_SMOKE_INTERVAL_MS: number;
2277
+ CUSTOM_CODE_REGISTER_TIMEOUT_MS: number;
2278
+ CUSTOM_CODE_REGISTER_INTERVAL_MS: number;
2252
2279
  CUSTOM_CODE_MAX_LOGS: number;
2253
2280
  CUSTOM_CODE_MAX_LOG_LENGTH: number;
2254
2281
  CUSTOM_CODE_SEND_FILE_TARGET_GMAIL: "gmail";
@@ -2301,6 +2328,15 @@ export declare const utils: {
2301
2328
  CLI_OAUTH_REDIRECT_PORTS: number[];
2302
2329
  CLI_OAUTH_SCOPES: string[];
2303
2330
  CLI_TOKEN_REFRESH_SKEW_SECONDS: number;
2331
+ ACCESS_TOKEN_PREFIX: string;
2332
+ ACCESS_TOKEN_BYTES: number;
2333
+ ACCESS_TOKEN_HINT_CHARS: number;
2334
+ ACCESS_TOKEN_NAME_MAX: number;
2335
+ ACCESS_TOKEN_MAX_PER_PROJECT: number;
2336
+ ACCESS_TOKEN_MAX_EXPIRY_DAYS: number;
2337
+ ACCESS_TOKEN_LAST_USED_INTERVAL_MS: number;
2338
+ ACCESS_TOKEN_UNSCOPED_PATHS: string[];
2339
+ ACCESS_TOKEN_SCOPE_MESSAGE: string;
2304
2340
  CUSTOM_CODE_LOGS_DEFAULT_LIMIT: number;
2305
2341
  CUSTOM_CODE_LOGS_MAX_LIMIT: number;
2306
2342
  BOT_GRANT_TYPE: string;
@@ -2353,6 +2389,8 @@ export declare const utils: {
2353
2389
  verifyCustomCodeToken: (token: string, secret: string, now?: number) => Promise<CustomCodeTokenPayload | null>;
2354
2390
  customCodeScriptName: (artifactId: string) => string;
2355
2391
  customCodePreviewScriptName: (artifactId: string) => string;
2392
+ customCodeUploadName: (artifactId: string) => string;
2393
+ customCodePreviewUploadName: (artifactId: string) => string;
2356
2394
  oauthProviders: Record<string, OAuthProviderConfig>;
2357
2395
  resolveAttachment: (resource: AttachmentResource, readObject: (key: string) => Promise<ArrayBuffer | null>, verb?: "attach" | "upload") => Promise<ResolveAttachmentResult>;
2358
2396
  isExposedResource: (resource: ExposableResource) => boolean;
@@ -2380,6 +2418,10 @@ export declare const utils: {
2380
2418
  languageFromHeader: (header?: string | null) => string;
2381
2419
  slugifyTitle: (title: string) => string;
2382
2420
  resourceUriFromTitle: (title: string) => string;
2421
+ accessTokenHint: (token: string) => string;
2422
+ hashAccessToken: (token: string) => Promise<string>;
2423
+ isAccessToken: (token: string) => boolean;
2424
+ mintAccessToken: () => Promise<MintedAccessToken>;
2383
2425
  TOOL_CATALOG: readonly [{
2384
2426
  readonly key: "gmail";
2385
2427
  readonly title: "Gmail";
@@ -2839,5 +2881,5 @@ export declare const utils: {
2839
2881
  PlanLimitError: typeof PlanLimitError;
2840
2882
  isPlanLimitError: (error: unknown) => error is PlanLimitError;
2841
2883
  };
2842
- export type { CustomCodeProject, ProjectPathIssue, CatalogGroup, CatalogTool, CatalogToolDescriptor, ToolGroupKey, ToolKey, CalendarConfigField, JsonSchema, SchemaViolation, MimeAttachment, MimeMessageInput, GmailOperation, GmailSendRequest, GmailSendResponse, OutlookOperation, OutlookSendRequest, OutlookSendResponse, SlackOperation, SlackSendRequest, SlackSendResponse, SlackSendRemoteResourceRequest, TelegramSendRequest, TelegramSendResponse, TelegramSendRemoteResourceRequest, DiscordSendRequest, DiscordSendResponse, DiscordSendRemoteResourceRequest, WhatsappSendRequest, WhatsappSendResponse, WhatsappSendRemoteResourceRequest, EnvSource, Separator, ChunkMetadata, PreparedChunk, ExtractedDocument, ExtractedDocumentMetadata, ExtractedDocumentSource, RateLimitRetryOptions, QueueBatchLike, QueueMessageLike, ProcessQueueBatchHandlers, ChannelNotifier, ToolStatusEvent, BufferedChannelMessage, ChannelBufferEnvelope, ChannelBufferFlush, Source, ResourceUrlContext, SourceButton, RefreshOAuthTokenInput, RefreshedOAuthToken, HttpEndpointToolConfig, McpProxyToolConfig, McpProxyDiscoveredTool, McpProxyDiscoveredResource, McpProxyDiscoveredPrompt, McpProxyDiscovery, CustomCodeToolConfig, CustomCodeToolManifest, CustomCodeManifest, CustomCodeSendFile, CustomCodeCreateResource, CustomCodeTokenPayload, OAuthProviderConfig, AttachmentResource, ResolvedAttachment, ResolveAttachmentResult, ExposableResource, PlanLimitDetails, PlanLimits };
2884
+ export type { MintedAccessToken, CustomCodeProject, ProjectPathIssue, CatalogGroup, CatalogTool, CatalogToolDescriptor, ToolGroupKey, ToolKey, CalendarConfigField, JsonSchema, SchemaViolation, MimeAttachment, MimeMessageInput, GmailOperation, GmailSendRequest, GmailSendResponse, OutlookOperation, OutlookSendRequest, OutlookSendResponse, SlackOperation, SlackSendRequest, SlackSendResponse, SlackSendRemoteResourceRequest, TelegramSendRequest, TelegramSendResponse, TelegramSendRemoteResourceRequest, DiscordSendRequest, DiscordSendResponse, DiscordSendRemoteResourceRequest, WhatsappSendRequest, WhatsappSendResponse, WhatsappSendRemoteResourceRequest, EnvSource, Separator, ChunkMetadata, PreparedChunk, ExtractedDocument, ExtractedDocumentMetadata, ExtractedDocumentSource, RateLimitRetryOptions, QueueBatchLike, QueueMessageLike, ProcessQueueBatchHandlers, ChannelNotifier, ToolStatusEvent, BufferedChannelMessage, ChannelBufferEnvelope, ChannelBufferFlush, Source, ResourceUrlContext, SourceButton, RefreshOAuthTokenInput, RefreshedOAuthToken, HttpEndpointToolConfig, McpProxyToolConfig, McpProxyDiscoveredTool, McpProxyDiscoveredResource, McpProxyDiscoveredPrompt, McpProxyDiscovery, CustomCodeToolConfig, CustomCodeToolManifest, CustomCodeManifest, CustomCodeSendFile, CustomCodeCreateResource, CustomCodeTokenPayload, OAuthProviderConfig, AttachmentResource, ResolvedAttachment, ResolveAttachmentResult, ExposableResource, PlanLimitDetails, PlanLimits };
2843
2885
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAlGhB,CAAC;;;;;;;;;;;;;;;;;;;;;;;kBAjHkB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBA0DF,CAAC;oBAEd,CAAC;;;;aA0BkB,CAAC;mBAA8B,CAAC;WACvC,CAAC;gBACf,CAAC;gBAA4B,CAAA;;;mBAmBK,CAAC;oBACtC,CAAC;;;;;;;;;;;;;;;kBA1DmB,CAAC;;;;;;;sBAqCR,CAAC;;;;;;;;;CAuOf,CAAC;AAEF,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,YAAY,EACZ,OAAO,EACP,mBAAmB,EACnB,UAAU,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,8BAA8B,EAC9B,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EACjC,kBAAkB,EAClB,mBAAmB,EACnB,gCAAgC,EAChC,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EACjC,SAAS,EACT,SAAS,EACT,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,yBAAyB,EACzB,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,MAAM,EACN,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,wBAAwB,EACxB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACX,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA5Gf,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;kBAhHiB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBA0DF,CAAC;oBAEd,CAAC;;;;aA0BkB,CAAC;mBAA8B,CAAC;WACvC,CAAC;gBACf,CAAC;gBAA4B,CAAA;;;mBAoBK,CAAC;oBACjC,CAAC;;;;;;;;;;;;;;;kBA3Dc,CAAC;;;;;;;sBAqCR,CAAC;;;;;;;;;CAsPf,CAAC;AAEF,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,YAAY,EACZ,OAAO,EACP,mBAAmB,EACnB,UAAU,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,8BAA8B,EAC9B,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EACjC,kBAAkB,EAClB,mBAAmB,EACnB,gCAAgC,EAChC,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EACjC,SAAS,EACT,SAAS,EACT,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,yBAAyB,EACzB,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,MAAM,EACN,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,wBAAwB,EACxB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACX,CAAC"}
package/dist/index.js CHANGED
@@ -31,6 +31,7 @@ const languageCookieDomain_1 = require("./languageCookieDomain");
31
31
  const localizeZodIssue_1 = require("./localizeZodIssue");
32
32
  const slugifyTitle_1 = require("./slugifyTitle");
33
33
  const resourceUri_1 = require("./resourceUri");
34
+ const accessToken_1 = require("./accessToken");
34
35
  const toolCatalog_1 = require("./toolCatalog");
35
36
  const tallyUsageKinds_1 = require("./tallyUsageKinds");
36
37
  const formatFilename_1 = require("./formatFilename");
@@ -89,6 +90,8 @@ exports.utils = {
89
90
  verifyCustomCodeToken: customCodeToken_1.verifyCustomCodeToken,
90
91
  customCodeScriptName: customCodeToken_1.customCodeScriptName,
91
92
  customCodePreviewScriptName: customCodeToken_1.customCodePreviewScriptName,
93
+ customCodeUploadName: customCodeToken_1.customCodeUploadName,
94
+ customCodePreviewUploadName: customCodeToken_1.customCodePreviewUploadName,
92
95
  oauthProviders: oauthProviders_1.oauthProviders,
93
96
  resolveAttachment: attachment_1.resolveAttachment,
94
97
  isExposedResource: exposedResource_1.isExposedResource,
@@ -111,6 +114,10 @@ exports.utils = {
111
114
  languageFromHeader: localizeZodIssue_1.languageFromHeader,
112
115
  slugifyTitle: slugifyTitle_1.slugifyTitle,
113
116
  resourceUriFromTitle: resourceUri_1.resourceUriFromTitle,
117
+ accessTokenHint: accessToken_1.accessTokenHint,
118
+ hashAccessToken: accessToken_1.hashAccessToken,
119
+ isAccessToken: accessToken_1.isAccessToken,
120
+ mintAccessToken: accessToken_1.mintAccessToken,
114
121
  TOOL_CATALOG: toolCatalog_1.TOOL_CATALOG,
115
122
  TOOL_KEYS: toolCatalog_1.TOOL_KEYS,
116
123
  describeCatalogTool: toolCatalog_1.describeCatalogTool,
package/dist/schema.d.ts CHANGED
@@ -1255,6 +1255,28 @@ export declare const Schema: {
1255
1255
  userId: z.ZodUUID;
1256
1256
  organizationId: z.ZodUUID;
1257
1257
  }, z.core.$strip>;
1258
+ ACCESS_TOKEN_CREATE: z.ZodObject<{
1259
+ name: z.ZodString;
1260
+ expiresInDays: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
1261
+ userId: z.ZodUUID;
1262
+ organizationId: z.ZodUUID;
1263
+ projectId: z.ZodUUID;
1264
+ }, z.core.$strip>;
1265
+ ACCESS_TOKEN_CREATE_VIEW: z.ZodObject<{
1266
+ name: z.ZodString;
1267
+ expiresInDays: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
1268
+ }, z.core.$strip>;
1269
+ ACCESS_TOKEN_LIST: z.ZodObject<{
1270
+ userId: z.ZodUUID;
1271
+ organizationId: z.ZodUUID;
1272
+ projectId: z.ZodUUID;
1273
+ }, z.core.$strip>;
1274
+ ACCESS_TOKEN_REMOVE: z.ZodObject<{
1275
+ tokenId: z.ZodUUID;
1276
+ userId: z.ZodUUID;
1277
+ organizationId: z.ZodUUID;
1278
+ projectId: z.ZodUUID;
1279
+ }, z.core.$strip>;
1258
1280
  CHANNEL_CONFIG: z.ZodObject<{
1259
1281
  debounceMs: z.ZodOptional<z.ZodNumber>;
1260
1282
  }, z.core.$loose>;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA+qBxB,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0FrB,CAAC;AAyDN,QAAA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;iBAmCpB,CAAC;AAWH,QAAA,MAAM,kBAAkB;;;;;;;;;;iBA+CtB,CAAC;AAiCH,QAAA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAapB,CAAC;AAYH,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCxB,CAAC;AA4LH,QAAA,MAAM,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4DpC,CAAC;AA4BL,QAAA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAkDzB,CAAC;AAwGH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsGlB,CAAC;AAGF,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAG1E,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAIlE,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAMtE,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAGtE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAMtE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAIvE,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAC5C,OAAO,kCAAkC,CAC1C,CAAC;AAKF,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;CACtB;AAKD,MAAM,WAAW,0BAA0B;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAKD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;CAC1E;AAMD,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,KAAK,EAAE,sBAAsB,EAAE,CAAC;IAChC,SAAS,CAAC,EAAE,0BAA0B,EAAE,CAAC;IACzC,OAAO,CAAC,EAAE,wBAAwB,EAAE,CAAC;CACtC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA6tBxB,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0FrB,CAAC;AAyDN,QAAA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;iBAmCpB,CAAC;AAWH,QAAA,MAAM,kBAAkB;;;;;;;;;;iBA+CtB,CAAC;AAiCH,QAAA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAapB,CAAC;AAYH,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCxB,CAAC;AA4LH,QAAA,MAAM,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4DpC,CAAC;AA4BL,QAAA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAkDzB,CAAC;AAwGH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0GlB,CAAC;AAGF,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAG1E,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAIlE,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAMtE,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAGtE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAMtE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAIvE,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAC5C,OAAO,kCAAkC,CAC1C,CAAC;AAKF,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;CACtB;AAKD,MAAM,WAAW,0BAA0B;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAKD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;CAC1E;AAMD,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,KAAK,EAAE,sBAAsB,EAAE,CAAC;IAChC,SAAS,CAAC,EAAE,0BAA0B,EAAE,CAAC;IACzC,OAAO,CAAC,EAAE,wBAAwB,EAAE,CAAC;CACtC"}
package/dist/schema.js CHANGED
@@ -519,6 +519,48 @@ const ORGANIZATION_REMOVE_LLM = zod_1.z.object({
519
519
  userId: zod_1.z.uuid(),
520
520
  organizationId: zod_1.z.uuid()
521
521
  });
522
+ // Personal access tokens. `name` is required rather than optional because it is
523
+ // the only thing anyone has to go on when deciding whether revoking a row will
524
+ // break a deploy, and a list of unnamed credentials is a list nobody prunes.
525
+ //
526
+ // `expiresInDays` is what the caller sends; `expiresAt` is what the row stores.
527
+ // A duration is what someone actually decides ("ninety days"), and computing the
528
+ // date on the server means a clock the client got wrong cannot mint a token that
529
+ // outlives what was asked for. Null is a token that does not expire, which is a
530
+ // real answer for a scheduled deploy and is why it is spelled explicitly rather
531
+ // than reached by leaving the field out.
532
+ const ACCESS_TOKEN_CREATE = zod_1.z.object({
533
+ name: zod_1.z.string().trim().min(1).max(constants_1.constants.ACCESS_TOKEN_NAME_MAX),
534
+ expiresInDays: zod_1.z
535
+ .number()
536
+ .int()
537
+ .min(1)
538
+ .max(constants_1.constants.ACCESS_TOKEN_MAX_EXPIRY_DAYS)
539
+ .nullable()
540
+ .optional(),
541
+ userId: zod_1.z.uuid(),
542
+ organizationId: zod_1.z.uuid(),
543
+ projectId: zod_1.z.uuid()
544
+ });
545
+ const ACCESS_TOKEN_LIST = zod_1.z.object({
546
+ userId: zod_1.z.uuid(),
547
+ organizationId: zod_1.z.uuid(),
548
+ projectId: zod_1.z.uuid()
549
+ });
550
+ const ACCESS_TOKEN_REMOVE = zod_1.z.object({
551
+ tokenId: zod_1.z.uuid(),
552
+ userId: zod_1.z.uuid(),
553
+ organizationId: zod_1.z.uuid(),
554
+ projectId: zod_1.z.uuid()
555
+ });
556
+ // The ids the route already carries come off: the dashboard reads them from the
557
+ // URL it is on, so a form asking for them again is a second place for them to be
558
+ // wrong.
559
+ const ACCESS_TOKEN_CREATE_VIEW = ACCESS_TOKEN_CREATE.omit({
560
+ userId: true,
561
+ organizationId: true,
562
+ projectId: true
563
+ });
522
564
  const ORGANIZATION_CREATE_LLM_VIEW = ORGANIZATION_CREATE_LLM.omit({
523
565
  userId: true,
524
566
  organizationId: true
@@ -1345,6 +1387,10 @@ exports.Schema = {
1345
1387
  ORGANIZATION_UPDATE_LLM,
1346
1388
  ORGANIZATION_UPDATE_LLM_VIEW,
1347
1389
  ORGANIZATION_REMOVE_LLM,
1390
+ ACCESS_TOKEN_CREATE,
1391
+ ACCESS_TOKEN_CREATE_VIEW,
1392
+ ACCESS_TOKEN_LIST,
1393
+ ACCESS_TOKEN_REMOVE,
1348
1394
  CHANNEL_CONFIG,
1349
1395
  CHANNEL_CREATE,
1350
1396
  CHANNEL_CREATE_VIEW,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ganju/utils",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Shared constants, schemas and helpers for Ganju",
5
5
  "license": "Apache-2.0",
6
6
  "author": "MontoyaAndres <andresmontoyafcb@gmail.com>",
@@ -0,0 +1,79 @@
1
+ import { constants } from './constants';
2
+
3
+ /**
4
+ * Minting and recognising a personal access token.
5
+ *
6
+ * The value exists in plaintext exactly once — in the response to the request
7
+ * that created it — and what the database holds is its SHA-256. That is the
8
+ * property the whole credential rests on: a leaked backup, a stray log line, or
9
+ * a support engineer reading the row learns nothing they could present as the
10
+ * token, and there is no path in the product that can print one back, because
11
+ * there is nothing to print.
12
+ *
13
+ * SHA-256 rather than a password hash on purpose. A password is a low-entropy
14
+ * secret a person chose, so the cost of hashing it is what stands between a
15
+ * stolen table and the passwords in it; this is 32 bytes from a CSPRNG, where
16
+ * that cost buys nothing and would be paid on every authenticated request. It
17
+ * is also the hash `oauth_client.client_secret` already uses in this system, so
18
+ * there is one answer here to "how is a machine credential stored".
19
+ */
20
+
21
+ const HINT_SEPARATOR = '…';
22
+
23
+ const base64url = (bytes: Uint8Array): string => {
24
+ let binary = '';
25
+ for (const byte of bytes) binary += String.fromCharCode(byte);
26
+ return btoa(binary)
27
+ .replace(/\+/g, '-')
28
+ .replace(/\//g, '_')
29
+ .replace(/=+$/, '');
30
+ };
31
+
32
+ /** The SHA-256 of a presented token, in the encoding the column stores. */
33
+ export const hashAccessToken = async (token: string): Promise<string> => {
34
+ const digest = await crypto.subtle.digest(
35
+ 'SHA-256',
36
+ new TextEncoder().encode(token)
37
+ );
38
+ return base64url(new Uint8Array(digest));
39
+ };
40
+
41
+ /**
42
+ * Cheap enough to run before the database is touched: a bearer token that does
43
+ * not carry the prefix is an OAuth token, and belongs on the other path.
44
+ */
45
+ export const isAccessToken = (token: string): boolean =>
46
+ token.startsWith(constants.ACCESS_TOKEN_PREFIX);
47
+
48
+ /**
49
+ * What the dashboard shows beside a token's name.
50
+ *
51
+ * Enough to tell two rows apart when someone is deciding which to revoke, and
52
+ * deliberately taken from the *front* of the secret rather than the end: a
53
+ * prefix narrows a brute-force search by exactly as much as a suffix would, and
54
+ * a value people are used to seeing truncated at the end reads as complete when
55
+ * it is the end that is shown.
56
+ */
57
+ export const accessTokenHint = (token: string): string =>
58
+ `${token.slice(
59
+ 0,
60
+ constants.ACCESS_TOKEN_PREFIX.length + constants.ACCESS_TOKEN_HINT_CHARS
61
+ )}${HINT_SEPARATOR}`;
62
+
63
+ export interface MintedAccessToken {
64
+ /** The only time this value exists. Returned to the caller, never stored. */
65
+ token: string;
66
+ tokenHash: string;
67
+ hint: string;
68
+ }
69
+
70
+ export const mintAccessToken = async (): Promise<MintedAccessToken> => {
71
+ const bytes = new Uint8Array(constants.ACCESS_TOKEN_BYTES);
72
+ crypto.getRandomValues(bytes);
73
+ const token = `${constants.ACCESS_TOKEN_PREFIX}${base64url(bytes)}`;
74
+ return {
75
+ token,
76
+ tokenHash: await hashAccessToken(token),
77
+ hint: accessTokenHint(token)
78
+ };
79
+ };
package/src/constants.ts CHANGED
@@ -1239,10 +1239,40 @@ const CUSTOM_CODE_MAIN_MODULE = 'index.js';
1239
1239
  const CUSTOM_CODE_MAX_FILES = 25;
1240
1240
  const CUSTOM_CODE_MAX_FILE_PATH = 100;
1241
1241
 
1242
- // WfP script name: `artifact_<artifactId>`. The id, never the slug — slugs are
1243
- // user-editable and a rename would orphan the deployed script.
1242
+ // WfP script name: `artifact_<artifactId>_<upload>`. The id, never the slug —
1243
+ // slugs are user-editable and a rename would orphan the deployed script.
1244
+ //
1245
+ // The trailing segment is minted per upload rather than derived from anything,
1246
+ // which is the whole point: uploading over a name that already exists is not
1247
+ // read-your-writes, so a deploy that replaces a script can serve the previous
1248
+ // edition for up to half a minute. A name nothing has ever used cannot, and
1249
+ // costs nothing to mint. Everything a publish used to do to survive that race —
1250
+ // waiting on an edition marker, refusing with a 503, putting the previous bundle
1251
+ // back when validation failed — went with the reuse that caused it.
1244
1252
  const CUSTOM_CODE_SCRIPT_NAME_PREFIX = 'artifact_';
1245
1253
 
1254
+ // Worker names cap at 63 characters, and `artifact_<uuid>` already spends 45.
1255
+ // That leaves 17 for a separator and a suffix, so a second uuid does not fit and
1256
+ // neither does a hex-32 digest. Twelve hex characters is 48 bits against a
1257
+ // namespace holding at most a few hundred names for any one artifact — not a
1258
+ // collision worth checking for, and an upload to a name in use would fail loudly
1259
+ // rather than quietly serve the wrong code.
1260
+ const CUSTOM_CODE_SCRIPT_NAME_MAX = 63;
1261
+ const CUSTOM_CODE_UPLOAD_SUFFIX_CHARS = 12;
1262
+
1263
+ // How long a superseded script stays in the namespace before the hourly sweep
1264
+ // may collect it.
1265
+ //
1266
+ // Deleting at publish time would race the thing it deletes: a tool call that
1267
+ // resolved the old pointer a moment earlier is still in flight, and the pointer
1268
+ // moving does not recall it. An hour is far longer than any call can take, and
1269
+ // the wait costs $0.02 per script per month against an allowance of 1,000.
1270
+ const CUSTOM_CODE_SWEEP_GRACE_MS = 60 * 60 * 1_000;
1271
+
1272
+ // Deletes per sweep. A backlog drains over several hourly runs rather than
1273
+ // making one run unbounded — the same shape the retention purge uses.
1274
+ const CUSTOM_CODE_SWEEP_MAX_DELETES = 200;
1275
+
1246
1276
  // A second script per artifact, `artifact_<id>_preview`, that the Test panel
1247
1277
  // deploys a draft into and calls.
1248
1278
  //
@@ -1453,15 +1483,17 @@ const CUSTOM_CODE_COMPATIBILITY_DATE = '2025-11-17';
1453
1483
  // tighter than our own workers' 30s — this is the technical cap that bounds
1454
1484
  // what one adversarial call can cost us, so an infinite loop in a customer's
1455
1485
  // tool is billed as five seconds rather than as whatever it wanted.
1456
- // How long publish waits for the edition it just uploaded to be the one the
1457
- // dispatcher answers with, and how often it asks.
1486
+ // How long a deploy waits for a freshly minted script name to become
1487
+ // dispatchable, and how often it asks.
1458
1488
  //
1459
- // Bounded rather than open-ended because this runs inside the publish request:
1460
- // past this, publishing would be a request nobody waits out. Exceeding it is
1461
- // reported as "try again" rather than published, which is the safe direction
1462
- // the alternative is advertising tools backed by a script that is not there yet.
1463
- const CUSTOM_CODE_SMOKE_TIMEOUT_MS = 20_000;
1464
- const CUSTOM_CODE_SMOKE_INTERVAL_MS = 1_000;
1489
+ // Every upload goes to a name that has never been used, which is
1490
+ // read-your-writes: ~2s end to end against the deployed namespace, against the
1491
+ // 20-41s a replacement could take. So this bounds how long a brand-new name
1492
+ // takes to register, never how long an old edition takes to stop answering
1493
+ // there is no old edition. It is short for that reason, and a script that
1494
+ // answers with the wrong edition now fails outright instead of being waited on.
1495
+ const CUSTOM_CODE_REGISTER_TIMEOUT_MS = 8_000;
1496
+ const CUSTOM_CODE_REGISTER_INTERVAL_MS = 500;
1465
1497
 
1466
1498
  const CUSTOM_CODE_SCRIPT_CPU_MS = 5_000;
1467
1499
 
@@ -1663,6 +1695,55 @@ const ARTIFACT_SCOPE_PREFIX = 'artifact:';
1663
1695
  // that is one library's behaviour and this is a login that has already opened
1664
1696
  // someone's browser by the time it would fail.
1665
1697
 
1698
+ // A personal access token — the durable credential a machine with no browser
1699
+ // uses, where an OAuth access token's one hour is not enough. Bound to one
1700
+ // project, because that is the unit a deploy pipeline works on: one repository,
1701
+ // one artifact, one credential in its CI settings.
1702
+ //
1703
+ // The prefix is part of the value rather than decoration: it is what lets the
1704
+ // middleware tell one of these from an OAuth token before it decides which
1705
+ // lookup to make, and it is what secret scanners match on when one leaks into a
1706
+ // repository. The rest is 32 random bytes, base64url — the token is the only
1707
+ // place the value ever exists in plaintext, since what is stored is its hash.
1708
+ const ACCESS_TOKEN_PREFIX = 'ganju_pat_';
1709
+ const ACCESS_TOKEN_BYTES = 32;
1710
+
1711
+ // Enough of the secret to recognise a row by, and not enough to be worth
1712
+ // stealing. Shown in the dashboard and by `ganju token list` beside the name.
1713
+ const ACCESS_TOKEN_HINT_CHARS = 6;
1714
+
1715
+ const ACCESS_TOKEN_NAME_MAX = 100;
1716
+
1717
+ // A ceiling per project, so a compromised session cannot quietly mint an
1718
+ // unbounded set of credentials that each survive the session being ended.
1719
+ const ACCESS_TOKEN_MAX_PER_PROJECT = 20;
1720
+
1721
+ // An expiry is optional, because a scheduled deploy that dies on a date nobody
1722
+ // wrote down is its own kind of outage — but a year is the longest we will
1723
+ // write one for.
1724
+ const ACCESS_TOKEN_MAX_EXPIRY_DAYS = 365;
1725
+
1726
+ // `last_used_at` is a convenience, not an audit log, so it is written at most
1727
+ // this often per token rather than on every request. The question it answers —
1728
+ // "is anything still using this, or can I revoke it" — does not get a better
1729
+ // answer from minute-level precision, and the write would otherwise land on the
1730
+ // hot path of every CI request.
1731
+ const ACCESS_TOKEN_LAST_USED_INTERVAL_MS = 5 * 60 * 1000;
1732
+
1733
+ // The only path a personal access token may reach without naming the project it
1734
+ // is scoped to, and only on GET. `/me` reports who the token is, which is how
1735
+ // the CLI confirms a machine is authenticated at all, and tells it nothing it
1736
+ // does not already hold.
1737
+ //
1738
+ // Everything else is refused, organization routes included — billing, members,
1739
+ // the model configs and the other projects are not what a deploy credential is
1740
+ // for. The list is deliberately this short: a route added later is closed by
1741
+ // omission rather than open by it.
1742
+ const ACCESS_TOKEN_UNSCOPED_PATHS = ['/me'];
1743
+
1744
+ const ACCESS_TOKEN_SCOPE_MESSAGE =
1745
+ 'This token is scoped to a different project';
1746
+
1666
1747
  // Recent custom-tool invocations, as `ganju logs` reads them.
1667
1748
  const CUSTOM_CODE_LOGS_DEFAULT_LIMIT = 20;
1668
1749
  const CUSTOM_CODE_LOGS_MAX_LIMIT = 100;
@@ -2465,6 +2546,10 @@ export const constants = {
2465
2546
  CUSTOM_CODE_MAX_FILE_PATH,
2466
2547
  CUSTOM_CODE_VERSION_STATUSES,
2467
2548
  CUSTOM_CODE_SCRIPT_NAME_PREFIX,
2549
+ CUSTOM_CODE_SCRIPT_NAME_MAX,
2550
+ CUSTOM_CODE_UPLOAD_SUFFIX_CHARS,
2551
+ CUSTOM_CODE_SWEEP_GRACE_MS,
2552
+ CUSTOM_CODE_SWEEP_MAX_DELETES,
2468
2553
  CUSTOM_CODE_PREVIEW_SCRIPT_SUFFIX,
2469
2554
  CUSTOM_CODE_PREVIEW_TOKEN_TTL_MS,
2470
2555
  CUSTOM_CODE_TEST_TIMEOUT_MS,
@@ -2506,8 +2591,8 @@ export const constants = {
2506
2591
  CUSTOM_CODE_BROKER_SERVICE_ENV,
2507
2592
  CUSTOM_CODE_COMPATIBILITY_DATE,
2508
2593
  CUSTOM_CODE_SCRIPT_CPU_MS,
2509
- CUSTOM_CODE_SMOKE_TIMEOUT_MS,
2510
- CUSTOM_CODE_SMOKE_INTERVAL_MS,
2594
+ CUSTOM_CODE_REGISTER_TIMEOUT_MS,
2595
+ CUSTOM_CODE_REGISTER_INTERVAL_MS,
2511
2596
  CUSTOM_CODE_MAX_LOGS,
2512
2597
  CUSTOM_CODE_MAX_LOG_LENGTH,
2513
2598
  CUSTOM_CODE_SEND_FILE_TARGET_GMAIL,
@@ -2560,6 +2645,15 @@ export const constants = {
2560
2645
  CLI_OAUTH_REDIRECT_PORTS,
2561
2646
  CLI_OAUTH_SCOPES,
2562
2647
  CLI_TOKEN_REFRESH_SKEW_SECONDS,
2648
+ ACCESS_TOKEN_PREFIX,
2649
+ ACCESS_TOKEN_BYTES,
2650
+ ACCESS_TOKEN_HINT_CHARS,
2651
+ ACCESS_TOKEN_NAME_MAX,
2652
+ ACCESS_TOKEN_MAX_PER_PROJECT,
2653
+ ACCESS_TOKEN_MAX_EXPIRY_DAYS,
2654
+ ACCESS_TOKEN_LAST_USED_INTERVAL_MS,
2655
+ ACCESS_TOKEN_UNSCOPED_PATHS,
2656
+ ACCESS_TOKEN_SCOPE_MESSAGE,
2563
2657
  CUSTOM_CODE_LOGS_DEFAULT_LIMIT,
2564
2658
  CUSTOM_CODE_LOGS_MAX_LIMIT,
2565
2659
  BOT_GRANT_TYPE,
@@ -179,20 +179,100 @@ export const verifyCustomCodeToken = async (
179
179
  };
180
180
 
181
181
  /**
182
- * The dispatch-namespace script name for an artifact: `artifact_<id>`.
182
+ * The legacy dispatch-namespace script name for an artifact: `artifact_<id>`.
183
183
  *
184
184
  * The id, never the slug — slugs are user-editable and a rename would orphan the
185
185
  * deployed script while the database still pointed at a live version.
186
+ *
187
+ * Nothing uploads to this name any more; every deploy mints its own. It survives
188
+ * as the fallback for a version published before `script_name` existed, whose
189
+ * bundle really is sitting under this name. Tightening a rule must never stop an
190
+ * already-published version from serving, because that failure is invisible to
191
+ * whoever owns it — the same reason the boot loop still accepts a stored tool key
192
+ * the current catalog no longer offers.
186
193
  */
187
194
  export const customCodeScriptName = (artifactId: string): string =>
188
195
  `${constants.CUSTOM_CODE_SCRIPT_NAME_PREFIX}${artifactId}`;
189
196
 
190
197
  /**
191
- * The script name a test run deploys into: `artifact_<id>_preview`.
198
+ * The legacy preview script name: `artifact_<id>_preview`.
192
199
  *
193
- * A second script rather than a second version of the live one, because a test
194
- * must not be able to disturb what MCP clients are being served and the only
195
- * way to be certain of that is for it to run under a name nothing dispatches to.
200
+ * Kept for the same reason as the one above, and for one more: it is the prefix
201
+ * the sweep matches to recognise a preview script left behind by a test run that
202
+ * did not clean up after itself.
196
203
  */
197
204
  export const customCodePreviewScriptName = (artifactId: string): string =>
198
205
  `${customCodeScriptName(artifactId)}${constants.CUSTOM_CODE_PREVIEW_SCRIPT_SUFFIX}`;
206
+
207
+ /**
208
+ * A dispatch-namespace name no upload has ever used:
209
+ * `artifact_<id>_<12 hex chars>`.
210
+ *
211
+ * Minted rather than derived, and that is the entire design. Uploading over an
212
+ * existing name is not read-your-writes — a replacement can serve the previous
213
+ * edition for tens of seconds — so a deploy that always writes to a new name is
214
+ * correct by construction rather than by waiting to see whether it worked.
215
+ *
216
+ * The suffix deliberately carries no meaning. The two candidates that did are
217
+ * both wrong: the bundle digest collides whenever a deploy reverts to bytes that
218
+ * shipped before, which is exactly what a rollback is, and the version id is one
219
+ * string across every re-upload of a single draft, which is every test run of it.
220
+ *
221
+ * Twelve hex characters is what fits. Worker names cap at 63 and
222
+ * `artifact_<uuid>` spends 45 of them.
223
+ */
224
+ export const customCodeUploadName = (artifactId: string): string =>
225
+ mintUploadName(customCodeScriptName(artifactId));
226
+
227
+ /**
228
+ * A preview name no test run has ever used:
229
+ * `artifact_<id>_preview_<12 hex chars>`.
230
+ *
231
+ * The sharper version of the same race. Every test used to deploy over one
232
+ * preview name, so a test could report the run before it — which reads as "my
233
+ * edit did nothing" from the one tool whose whole job is to say what an edit
234
+ * does. Nothing stores this: it is minted, used, and deleted inside one request.
235
+ */
236
+ export const customCodePreviewUploadName = (artifactId: string): string =>
237
+ mintUploadName(customCodePreviewScriptName(artifactId));
238
+
239
+ /**
240
+ * Append `_<hex>` to a base name, spending whatever the 63-character ceiling
241
+ * leaves and no more.
242
+ *
243
+ * The budget is genuinely tight, and the two names spend it differently:
244
+ *
245
+ * | name | base | separator | suffix | total |
246
+ * |---|---|---|---|---|
247
+ * | live | `artifact_<uuid>` = 45 | 1 | 12 | 58 |
248
+ * | preview | + `_preview` = 53 | 1 | 8 | 62 |
249
+ *
250
+ * Twelve hex characters is 48 bits and eight is 32, against a namespace holding
251
+ * a few hundred names for any one artifact — and a preview name lives for the
252
+ * seconds one test run takes. Neither is a collision worth checking for, and an
253
+ * upload to a name already in use fails loudly rather than quietly serving the
254
+ * wrong code, which is the failure that matters.
255
+ *
256
+ * The ceiling is asserted rather than assumed: it is one number away from being
257
+ * silently exceeded by a longer prefix, and a name Cloudflare refuses would
258
+ * surface as a failed publish with nothing explaining why.
259
+ */
260
+ const mintUploadName = (base: string): string => {
261
+ const available = constants.CUSTOM_CODE_SCRIPT_NAME_MAX - base.length - 1;
262
+ // Even, because each byte renders as two hex characters.
263
+ const chars =
264
+ Math.min(constants.CUSTOM_CODE_UPLOAD_SUFFIX_CHARS, available) & ~1;
265
+
266
+ if (chars < 4) {
267
+ throw new Error(
268
+ `A dispatch script name based on "${base}" leaves no room for a unique suffix.`
269
+ );
270
+ }
271
+
272
+ const bytes = new Uint8Array(chars / 2);
273
+ crypto.getRandomValues(bytes);
274
+
275
+ return `${base}_${Array.from(bytes)
276
+ .map(byte => byte.toString(16).padStart(2, '0'))
277
+ .join('')}`;
278
+ };
package/src/index.ts CHANGED
@@ -102,7 +102,9 @@ import {
102
102
  mintCustomCodeToken,
103
103
  verifyCustomCodeToken,
104
104
  customCodeScriptName,
105
- customCodePreviewScriptName
105
+ customCodePreviewScriptName,
106
+ customCodeUploadName,
107
+ customCodePreviewUploadName
106
108
  } from './customCodeToken';
107
109
  import type { CustomCodeTokenPayload } from './customCodeToken';
108
110
  import { oauthProviders } from './oauthProviders';
@@ -133,6 +135,13 @@ import { languageCookieDomain } from './languageCookieDomain';
133
135
  import { localizeZodIssue, languageFromHeader } from './localizeZodIssue';
134
136
  import { slugifyTitle } from './slugifyTitle';
135
137
  import { resourceUriFromTitle } from './resourceUri';
138
+ import {
139
+ accessTokenHint,
140
+ hashAccessToken,
141
+ isAccessToken,
142
+ mintAccessToken
143
+ } from './accessToken';
144
+ import type { MintedAccessToken } from './accessToken';
136
145
  import {
137
146
  TOOL_CATALOG,
138
147
  TOOL_KEYS,
@@ -257,6 +266,8 @@ export const utils = {
257
266
  verifyCustomCodeToken,
258
267
  customCodeScriptName,
259
268
  customCodePreviewScriptName,
269
+ customCodeUploadName,
270
+ customCodePreviewUploadName,
260
271
  oauthProviders,
261
272
  resolveAttachment,
262
273
  isExposedResource,
@@ -279,6 +290,10 @@ export const utils = {
279
290
  languageFromHeader,
280
291
  slugifyTitle,
281
292
  resourceUriFromTitle,
293
+ accessTokenHint,
294
+ hashAccessToken,
295
+ isAccessToken,
296
+ mintAccessToken,
282
297
  TOOL_CATALOG,
283
298
  TOOL_KEYS,
284
299
  describeCatalogTool,
@@ -323,6 +338,7 @@ export const utils = {
323
338
  };
324
339
 
325
340
  export type {
341
+ MintedAccessToken,
326
342
  CustomCodeProject,
327
343
  ProjectPathIssue,
328
344
  CatalogGroup,
package/src/schema.ts CHANGED
@@ -603,6 +603,52 @@ const ORGANIZATION_REMOVE_LLM = z.object({
603
603
  organizationId: z.uuid()
604
604
  });
605
605
 
606
+ // Personal access tokens. `name` is required rather than optional because it is
607
+ // the only thing anyone has to go on when deciding whether revoking a row will
608
+ // break a deploy, and a list of unnamed credentials is a list nobody prunes.
609
+ //
610
+ // `expiresInDays` is what the caller sends; `expiresAt` is what the row stores.
611
+ // A duration is what someone actually decides ("ninety days"), and computing the
612
+ // date on the server means a clock the client got wrong cannot mint a token that
613
+ // outlives what was asked for. Null is a token that does not expire, which is a
614
+ // real answer for a scheduled deploy and is why it is spelled explicitly rather
615
+ // than reached by leaving the field out.
616
+ const ACCESS_TOKEN_CREATE = z.object({
617
+ name: z.string().trim().min(1).max(constants.ACCESS_TOKEN_NAME_MAX),
618
+ expiresInDays: z
619
+ .number()
620
+ .int()
621
+ .min(1)
622
+ .max(constants.ACCESS_TOKEN_MAX_EXPIRY_DAYS)
623
+ .nullable()
624
+ .optional(),
625
+ userId: z.uuid(),
626
+ organizationId: z.uuid(),
627
+ projectId: z.uuid()
628
+ });
629
+
630
+ const ACCESS_TOKEN_LIST = z.object({
631
+ userId: z.uuid(),
632
+ organizationId: z.uuid(),
633
+ projectId: z.uuid()
634
+ });
635
+
636
+ const ACCESS_TOKEN_REMOVE = z.object({
637
+ tokenId: z.uuid(),
638
+ userId: z.uuid(),
639
+ organizationId: z.uuid(),
640
+ projectId: z.uuid()
641
+ });
642
+
643
+ // The ids the route already carries come off: the dashboard reads them from the
644
+ // URL it is on, so a form asking for them again is a second place for them to be
645
+ // wrong.
646
+ const ACCESS_TOKEN_CREATE_VIEW = ACCESS_TOKEN_CREATE.omit({
647
+ userId: true,
648
+ organizationId: true,
649
+ projectId: true
650
+ });
651
+
606
652
  const ORGANIZATION_CREATE_LLM_VIEW = ORGANIZATION_CREATE_LLM.omit({
607
653
  userId: true,
608
654
  organizationId: true
@@ -1513,6 +1559,10 @@ export const Schema = {
1513
1559
  ORGANIZATION_UPDATE_LLM,
1514
1560
  ORGANIZATION_UPDATE_LLM_VIEW,
1515
1561
  ORGANIZATION_REMOVE_LLM,
1562
+ ACCESS_TOKEN_CREATE,
1563
+ ACCESS_TOKEN_CREATE_VIEW,
1564
+ ACCESS_TOKEN_LIST,
1565
+ ACCESS_TOKEN_REMOVE,
1516
1566
  CHANNEL_CONFIG,
1517
1567
  CHANNEL_CREATE,
1518
1568
  CHANNEL_CREATE_VIEW,