@ganju/utils 0.0.4 → 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.
- package/dist/accessToken.d.ts +25 -0
- package/dist/accessToken.d.ts.map +1 -0
- package/dist/accessToken.js +65 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +49 -0
- package/dist/index.d.ts +37 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/schema.d.ts +22 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +46 -0
- package/package.json +1 -1
- package/src/accessToken.ts +79 -0
- package/src/constants.ts +58 -0
- package/src/index.ts +12 -0
- package/src/schema.ts +50 -0
|
@@ -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;
|
package/dist/constants.d.ts
CHANGED
|
@@ -618,6 +618,15 @@ export declare const constants: {
|
|
|
618
618
|
CLI_OAUTH_REDIRECT_PORTS: number[];
|
|
619
619
|
CLI_OAUTH_SCOPES: string[];
|
|
620
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;
|
|
621
630
|
CUSTOM_CODE_LOGS_DEFAULT_LIMIT: number;
|
|
622
631
|
CUSTOM_CODE_LOGS_MAX_LIMIT: number;
|
|
623
632
|
BOT_GRANT_TYPE: string;
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
|
package/dist/constants.js
CHANGED
|
@@ -1439,6 +1439,46 @@ const ARTIFACT_SCOPE_PREFIX = 'artifact:';
|
|
|
1439
1439
|
// loopback matching, where the port is ignored for a 127.0.0.0/8 redirect, but
|
|
1440
1440
|
// that is one library's behaviour and this is a login that has already opened
|
|
1441
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';
|
|
1442
1482
|
// Recent custom-tool invocations, as `ganju logs` reads them.
|
|
1443
1483
|
const CUSTOM_CODE_LOGS_DEFAULT_LIMIT = 20;
|
|
1444
1484
|
const CUSTOM_CODE_LOGS_MAX_LIMIT = 100;
|
|
@@ -2267,6 +2307,15 @@ exports.constants = {
|
|
|
2267
2307
|
CLI_OAUTH_REDIRECT_PORTS: cliConstants_1.CLI_OAUTH_REDIRECT_PORTS,
|
|
2268
2308
|
CLI_OAUTH_SCOPES: cliConstants_1.CLI_OAUTH_SCOPES,
|
|
2269
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,
|
|
2270
2319
|
CUSTOM_CODE_LOGS_DEFAULT_LIMIT,
|
|
2271
2320
|
CUSTOM_CODE_LOGS_MAX_LIMIT,
|
|
2272
2321
|
BOT_GRANT_TYPE,
|
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>;
|
|
@@ -2305,6 +2328,15 @@ export declare const utils: {
|
|
|
2305
2328
|
CLI_OAUTH_REDIRECT_PORTS: number[];
|
|
2306
2329
|
CLI_OAUTH_SCOPES: string[];
|
|
2307
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;
|
|
2308
2340
|
CUSTOM_CODE_LOGS_DEFAULT_LIMIT: number;
|
|
2309
2341
|
CUSTOM_CODE_LOGS_MAX_LIMIT: number;
|
|
2310
2342
|
BOT_GRANT_TYPE: string;
|
|
@@ -2386,6 +2418,10 @@ export declare const utils: {
|
|
|
2386
2418
|
languageFromHeader: (header?: string | null) => string;
|
|
2387
2419
|
slugifyTitle: (title: string) => string;
|
|
2388
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>;
|
|
2389
2425
|
TOOL_CATALOG: readonly [{
|
|
2390
2426
|
readonly key: "gmail";
|
|
2391
2427
|
readonly title: "Gmail";
|
|
@@ -2845,5 +2881,5 @@ export declare const utils: {
|
|
|
2845
2881
|
PlanLimitError: typeof PlanLimitError;
|
|
2846
2882
|
isPlanLimitError: (error: unknown) => error is PlanLimitError;
|
|
2847
2883
|
};
|
|
2848
|
-
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 };
|
|
2849
2885
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EACtB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,wBAAwB,EACxB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACzB,MAAM,UAAU,CAAC;AAElB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAyCvD,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EAClC,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EACV,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,8BAA8B,EAC/B,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACnB,gCAAgC,EACjC,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EAClC,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EACL,UAAU,EAGX,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAU9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAG5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAW3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,cAAc,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");
|
|
@@ -113,6 +114,10 @@ exports.utils = {
|
|
|
113
114
|
languageFromHeader: localizeZodIssue_1.languageFromHeader,
|
|
114
115
|
slugifyTitle: slugifyTitle_1.slugifyTitle,
|
|
115
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,
|
|
116
121
|
TOOL_CATALOG: toolCatalog_1.TOOL_CATALOG,
|
|
117
122
|
TOOL_KEYS: toolCatalog_1.TOOL_KEYS,
|
|
118
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>;
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
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
|
@@ -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
|
@@ -1695,6 +1695,55 @@ const ARTIFACT_SCOPE_PREFIX = 'artifact:';
|
|
|
1695
1695
|
// that is one library's behaviour and this is a login that has already opened
|
|
1696
1696
|
// someone's browser by the time it would fail.
|
|
1697
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
|
+
|
|
1698
1747
|
// Recent custom-tool invocations, as `ganju logs` reads them.
|
|
1699
1748
|
const CUSTOM_CODE_LOGS_DEFAULT_LIMIT = 20;
|
|
1700
1749
|
const CUSTOM_CODE_LOGS_MAX_LIMIT = 100;
|
|
@@ -2596,6 +2645,15 @@ export const constants = {
|
|
|
2596
2645
|
CLI_OAUTH_REDIRECT_PORTS,
|
|
2597
2646
|
CLI_OAUTH_SCOPES,
|
|
2598
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,
|
|
2599
2657
|
CUSTOM_CODE_LOGS_DEFAULT_LIMIT,
|
|
2600
2658
|
CUSTOM_CODE_LOGS_MAX_LIMIT,
|
|
2601
2659
|
BOT_GRANT_TYPE,
|
package/src/index.ts
CHANGED
|
@@ -135,6 +135,13 @@ import { languageCookieDomain } from './languageCookieDomain';
|
|
|
135
135
|
import { localizeZodIssue, languageFromHeader } from './localizeZodIssue';
|
|
136
136
|
import { slugifyTitle } from './slugifyTitle';
|
|
137
137
|
import { resourceUriFromTitle } from './resourceUri';
|
|
138
|
+
import {
|
|
139
|
+
accessTokenHint,
|
|
140
|
+
hashAccessToken,
|
|
141
|
+
isAccessToken,
|
|
142
|
+
mintAccessToken
|
|
143
|
+
} from './accessToken';
|
|
144
|
+
import type { MintedAccessToken } from './accessToken';
|
|
138
145
|
import {
|
|
139
146
|
TOOL_CATALOG,
|
|
140
147
|
TOOL_KEYS,
|
|
@@ -283,6 +290,10 @@ export const utils = {
|
|
|
283
290
|
languageFromHeader,
|
|
284
291
|
slugifyTitle,
|
|
285
292
|
resourceUriFromTitle,
|
|
293
|
+
accessTokenHint,
|
|
294
|
+
hashAccessToken,
|
|
295
|
+
isAccessToken,
|
|
296
|
+
mintAccessToken,
|
|
286
297
|
TOOL_CATALOG,
|
|
287
298
|
TOOL_KEYS,
|
|
288
299
|
describeCatalogTool,
|
|
@@ -327,6 +338,7 @@ export const utils = {
|
|
|
327
338
|
};
|
|
328
339
|
|
|
329
340
|
export type {
|
|
341
|
+
MintedAccessToken,
|
|
330
342
|
CustomCodeProject,
|
|
331
343
|
ProjectPathIssue,
|
|
332
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,
|