@ganju/utils 0.0.4 → 0.0.6
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 +10 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +51 -0
- package/dist/index.d.ts +44 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/schema.d.ts +22 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +46 -0
- package/dist/vendorTimeZone.d.ts +57 -0
- package/dist/vendorTimeZone.d.ts.map +1 -0
- package/dist/vendorTimeZone.js +165 -0
- package/package.json +1 -1
- package/src/accessToken.ts +79 -0
- package/src/constants.ts +60 -0
- package/src/index.ts +26 -0
- package/src/schema.ts +50 -0
- package/src/vendorTimeZone.ts +171 -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
|
@@ -451,6 +451,7 @@ export declare const constants: {
|
|
|
451
451
|
CALCOM_API_VERSION_EVENT_TYPES: string;
|
|
452
452
|
CALCOM_API_VERSION_SLOTS: string;
|
|
453
453
|
CALCOM_API_VERSION_BOOKINGS: string;
|
|
454
|
+
CALCOM_API_VERSION_ME: string;
|
|
454
455
|
CALCOM_TOOL_KEY_PREFIX: string;
|
|
455
456
|
TAVILY_API_BASE: string;
|
|
456
457
|
TAVILY_SEARCH_DEPTH_BASIC: "basic";
|
|
@@ -618,6 +619,15 @@ export declare const constants: {
|
|
|
618
619
|
CLI_OAUTH_REDIRECT_PORTS: number[];
|
|
619
620
|
CLI_OAUTH_SCOPES: string[];
|
|
620
621
|
CLI_TOKEN_REFRESH_SKEW_SECONDS: number;
|
|
622
|
+
ACCESS_TOKEN_PREFIX: string;
|
|
623
|
+
ACCESS_TOKEN_BYTES: number;
|
|
624
|
+
ACCESS_TOKEN_HINT_CHARS: number;
|
|
625
|
+
ACCESS_TOKEN_NAME_MAX: number;
|
|
626
|
+
ACCESS_TOKEN_MAX_PER_PROJECT: number;
|
|
627
|
+
ACCESS_TOKEN_MAX_EXPIRY_DAYS: number;
|
|
628
|
+
ACCESS_TOKEN_LAST_USED_INTERVAL_MS: number;
|
|
629
|
+
ACCESS_TOKEN_UNSCOPED_PATHS: string[];
|
|
630
|
+
ACCESS_TOKEN_SCOPE_MESSAGE: string;
|
|
621
631
|
CUSTOM_CODE_LOGS_DEFAULT_LIMIT: number;
|
|
622
632
|
CUSTOM_CODE_LOGS_MAX_LIMIT: number;
|
|
623
633
|
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
|
@@ -877,6 +877,7 @@ const CALCOM_API_BASE = 'https://api.cal.com/v2';
|
|
|
877
877
|
const CALCOM_API_VERSION_EVENT_TYPES = '2024-06-14';
|
|
878
878
|
const CALCOM_API_VERSION_SLOTS = '2024-09-04';
|
|
879
879
|
const CALCOM_API_VERSION_BOOKINGS = '2026-02-25';
|
|
880
|
+
const CALCOM_API_VERSION_ME = '2024-06-14';
|
|
880
881
|
const CALCOM_TOOL_KEY_PREFIX = 'calcom-';
|
|
881
882
|
// Tavily web search. The key is validated against the live API before it is
|
|
882
883
|
// persisted (a minimal 1-result search), then stored as an artifact_credential
|
|
@@ -1439,6 +1440,46 @@ const ARTIFACT_SCOPE_PREFIX = 'artifact:';
|
|
|
1439
1440
|
// loopback matching, where the port is ignored for a 127.0.0.0/8 redirect, but
|
|
1440
1441
|
// that is one library's behaviour and this is a login that has already opened
|
|
1441
1442
|
// someone's browser by the time it would fail.
|
|
1443
|
+
// A personal access token — the durable credential a machine with no browser
|
|
1444
|
+
// uses, where an OAuth access token's one hour is not enough. Bound to one
|
|
1445
|
+
// project, because that is the unit a deploy pipeline works on: one repository,
|
|
1446
|
+
// one artifact, one credential in its CI settings.
|
|
1447
|
+
//
|
|
1448
|
+
// The prefix is part of the value rather than decoration: it is what lets the
|
|
1449
|
+
// middleware tell one of these from an OAuth token before it decides which
|
|
1450
|
+
// lookup to make, and it is what secret scanners match on when one leaks into a
|
|
1451
|
+
// repository. The rest is 32 random bytes, base64url — the token is the only
|
|
1452
|
+
// place the value ever exists in plaintext, since what is stored is its hash.
|
|
1453
|
+
const ACCESS_TOKEN_PREFIX = 'ganju_pat_';
|
|
1454
|
+
const ACCESS_TOKEN_BYTES = 32;
|
|
1455
|
+
// Enough of the secret to recognise a row by, and not enough to be worth
|
|
1456
|
+
// stealing. Shown in the dashboard and by `ganju token list` beside the name.
|
|
1457
|
+
const ACCESS_TOKEN_HINT_CHARS = 6;
|
|
1458
|
+
const ACCESS_TOKEN_NAME_MAX = 100;
|
|
1459
|
+
// A ceiling per project, so a compromised session cannot quietly mint an
|
|
1460
|
+
// unbounded set of credentials that each survive the session being ended.
|
|
1461
|
+
const ACCESS_TOKEN_MAX_PER_PROJECT = 20;
|
|
1462
|
+
// An expiry is optional, because a scheduled deploy that dies on a date nobody
|
|
1463
|
+
// wrote down is its own kind of outage — but a year is the longest we will
|
|
1464
|
+
// write one for.
|
|
1465
|
+
const ACCESS_TOKEN_MAX_EXPIRY_DAYS = 365;
|
|
1466
|
+
// `last_used_at` is a convenience, not an audit log, so it is written at most
|
|
1467
|
+
// this often per token rather than on every request. The question it answers —
|
|
1468
|
+
// "is anything still using this, or can I revoke it" — does not get a better
|
|
1469
|
+
// answer from minute-level precision, and the write would otherwise land on the
|
|
1470
|
+
// hot path of every CI request.
|
|
1471
|
+
const ACCESS_TOKEN_LAST_USED_INTERVAL_MS = 5 * 60 * 1000;
|
|
1472
|
+
// The only path a personal access token may reach without naming the project it
|
|
1473
|
+
// is scoped to, and only on GET. `/me` reports who the token is, which is how
|
|
1474
|
+
// the CLI confirms a machine is authenticated at all, and tells it nothing it
|
|
1475
|
+
// does not already hold.
|
|
1476
|
+
//
|
|
1477
|
+
// Everything else is refused, organization routes included — billing, members,
|
|
1478
|
+
// the model configs and the other projects are not what a deploy credential is
|
|
1479
|
+
// for. The list is deliberately this short: a route added later is closed by
|
|
1480
|
+
// omission rather than open by it.
|
|
1481
|
+
const ACCESS_TOKEN_UNSCOPED_PATHS = ['/me'];
|
|
1482
|
+
const ACCESS_TOKEN_SCOPE_MESSAGE = 'This token is scoped to a different project';
|
|
1442
1483
|
// Recent custom-tool invocations, as `ganju logs` reads them.
|
|
1443
1484
|
const CUSTOM_CODE_LOGS_DEFAULT_LIMIT = 20;
|
|
1444
1485
|
const CUSTOM_CODE_LOGS_MAX_LIMIT = 100;
|
|
@@ -2100,6 +2141,7 @@ exports.constants = {
|
|
|
2100
2141
|
CALCOM_API_VERSION_EVENT_TYPES,
|
|
2101
2142
|
CALCOM_API_VERSION_SLOTS,
|
|
2102
2143
|
CALCOM_API_VERSION_BOOKINGS,
|
|
2144
|
+
CALCOM_API_VERSION_ME,
|
|
2103
2145
|
CALCOM_TOOL_KEY_PREFIX,
|
|
2104
2146
|
TAVILY_API_BASE,
|
|
2105
2147
|
TAVILY_SEARCH_DEPTH_BASIC,
|
|
@@ -2267,6 +2309,15 @@ exports.constants = {
|
|
|
2267
2309
|
CLI_OAUTH_REDIRECT_PORTS: cliConstants_1.CLI_OAUTH_REDIRECT_PORTS,
|
|
2268
2310
|
CLI_OAUTH_SCOPES: cliConstants_1.CLI_OAUTH_SCOPES,
|
|
2269
2311
|
CLI_TOKEN_REFRESH_SKEW_SECONDS: cliConstants_1.CLI_TOKEN_REFRESH_SKEW_SECONDS,
|
|
2312
|
+
ACCESS_TOKEN_PREFIX,
|
|
2313
|
+
ACCESS_TOKEN_BYTES,
|
|
2314
|
+
ACCESS_TOKEN_HINT_CHARS,
|
|
2315
|
+
ACCESS_TOKEN_NAME_MAX,
|
|
2316
|
+
ACCESS_TOKEN_MAX_PER_PROJECT,
|
|
2317
|
+
ACCESS_TOKEN_MAX_EXPIRY_DAYS,
|
|
2318
|
+
ACCESS_TOKEN_LAST_USED_INTERVAL_MS,
|
|
2319
|
+
ACCESS_TOKEN_UNSCOPED_PATHS,
|
|
2320
|
+
ACCESS_TOKEN_SCOPE_MESSAGE,
|
|
2270
2321
|
CUSTOM_CODE_LOGS_DEFAULT_LIMIT,
|
|
2271
2322
|
CUSTOM_CODE_LOGS_MAX_LIMIT,
|
|
2272
2323
|
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>;
|
|
@@ -2138,6 +2161,7 @@ export declare const utils: {
|
|
|
2138
2161
|
CALCOM_API_VERSION_EVENT_TYPES: string;
|
|
2139
2162
|
CALCOM_API_VERSION_SLOTS: string;
|
|
2140
2163
|
CALCOM_API_VERSION_BOOKINGS: string;
|
|
2164
|
+
CALCOM_API_VERSION_ME: string;
|
|
2141
2165
|
CALCOM_TOOL_KEY_PREFIX: string;
|
|
2142
2166
|
TAVILY_API_BASE: string;
|
|
2143
2167
|
TAVILY_SEARCH_DEPTH_BASIC: "basic";
|
|
@@ -2305,6 +2329,15 @@ export declare const utils: {
|
|
|
2305
2329
|
CLI_OAUTH_REDIRECT_PORTS: number[];
|
|
2306
2330
|
CLI_OAUTH_SCOPES: string[];
|
|
2307
2331
|
CLI_TOKEN_REFRESH_SKEW_SECONDS: number;
|
|
2332
|
+
ACCESS_TOKEN_PREFIX: string;
|
|
2333
|
+
ACCESS_TOKEN_BYTES: number;
|
|
2334
|
+
ACCESS_TOKEN_HINT_CHARS: number;
|
|
2335
|
+
ACCESS_TOKEN_NAME_MAX: number;
|
|
2336
|
+
ACCESS_TOKEN_MAX_PER_PROJECT: number;
|
|
2337
|
+
ACCESS_TOKEN_MAX_EXPIRY_DAYS: number;
|
|
2338
|
+
ACCESS_TOKEN_LAST_USED_INTERVAL_MS: number;
|
|
2339
|
+
ACCESS_TOKEN_UNSCOPED_PATHS: string[];
|
|
2340
|
+
ACCESS_TOKEN_SCOPE_MESSAGE: string;
|
|
2308
2341
|
CUSTOM_CODE_LOGS_DEFAULT_LIMIT: number;
|
|
2309
2342
|
CUSTOM_CODE_LOGS_MAX_LIMIT: number;
|
|
2310
2343
|
BOT_GRANT_TYPE: string;
|
|
@@ -2386,6 +2419,10 @@ export declare const utils: {
|
|
|
2386
2419
|
languageFromHeader: (header?: string | null) => string;
|
|
2387
2420
|
slugifyTitle: (title: string) => string;
|
|
2388
2421
|
resourceUriFromTitle: (title: string) => string;
|
|
2422
|
+
accessTokenHint: (token: string) => string;
|
|
2423
|
+
hashAccessToken: (token: string) => Promise<string>;
|
|
2424
|
+
isAccessToken: (token: string) => boolean;
|
|
2425
|
+
mintAccessToken: () => Promise<MintedAccessToken>;
|
|
2389
2426
|
TOOL_CATALOG: readonly [{
|
|
2390
2427
|
readonly key: "gmail";
|
|
2391
2428
|
readonly title: "Gmail";
|
|
@@ -2842,8 +2879,14 @@ export declare const utils: {
|
|
|
2842
2879
|
buildReauthMetadata: (previous: Record<string, unknown> | null, reason: string) => Record<string, unknown>;
|
|
2843
2880
|
clearReauthMetadata: (previous: Record<string, unknown> | null) => Record<string, unknown> | null;
|
|
2844
2881
|
isCredentialNeedingReauth: (metadata: unknown) => boolean;
|
|
2882
|
+
isValidTimeZone: (value: unknown) => value is string;
|
|
2883
|
+
readCredentialTimeZone: (metadata: unknown) => string | null;
|
|
2884
|
+
credentialTimeZoneIsStale: (metadata: unknown, now?: number) => boolean;
|
|
2885
|
+
writeCredentialTimeZone: (previous: unknown, timeZone: string | null, now?: Date) => Record<string, unknown>;
|
|
2886
|
+
fetchGoogleCalendarTimeZone: (accessToken: string) => Promise<string | null>;
|
|
2887
|
+
fetchCalcomTimeZone: (apiKey: string) => Promise<string | null>;
|
|
2845
2888
|
PlanLimitError: typeof PlanLimitError;
|
|
2846
2889
|
isPlanLimitError: (error: unknown) => error is PlanLimitError;
|
|
2847
2890
|
};
|
|
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 };
|
|
2891
|
+
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
2892
|
//# 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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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aApHf,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;;;;;;;;;;;;;;;CAoQf,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");
|
|
@@ -44,6 +45,7 @@ const channelNotifier_1 = require("./channelNotifier");
|
|
|
44
45
|
const channelDebounce_1 = require("./channelDebounce");
|
|
45
46
|
const sources_1 = require("./sources");
|
|
46
47
|
const oauth_1 = require("./oauth");
|
|
48
|
+
const vendorTimeZone_1 = require("./vendorTimeZone");
|
|
47
49
|
const planLimitError_1 = require("./planLimitError");
|
|
48
50
|
exports.utils = {
|
|
49
51
|
Schema: schema_1.Schema,
|
|
@@ -113,6 +115,10 @@ exports.utils = {
|
|
|
113
115
|
languageFromHeader: localizeZodIssue_1.languageFromHeader,
|
|
114
116
|
slugifyTitle: slugifyTitle_1.slugifyTitle,
|
|
115
117
|
resourceUriFromTitle: resourceUri_1.resourceUriFromTitle,
|
|
118
|
+
accessTokenHint: accessToken_1.accessTokenHint,
|
|
119
|
+
hashAccessToken: accessToken_1.hashAccessToken,
|
|
120
|
+
isAccessToken: accessToken_1.isAccessToken,
|
|
121
|
+
mintAccessToken: accessToken_1.mintAccessToken,
|
|
116
122
|
TOOL_CATALOG: toolCatalog_1.TOOL_CATALOG,
|
|
117
123
|
TOOL_KEYS: toolCatalog_1.TOOL_KEYS,
|
|
118
124
|
describeCatalogTool: toolCatalog_1.describeCatalogTool,
|
|
@@ -152,6 +158,12 @@ exports.utils = {
|
|
|
152
158
|
buildReauthMetadata: oauth_1.buildReauthMetadata,
|
|
153
159
|
clearReauthMetadata: oauth_1.clearReauthMetadata,
|
|
154
160
|
isCredentialNeedingReauth: oauth_1.isCredentialNeedingReauth,
|
|
161
|
+
isValidTimeZone: vendorTimeZone_1.isValidTimeZone,
|
|
162
|
+
readCredentialTimeZone: vendorTimeZone_1.readCredentialTimeZone,
|
|
163
|
+
credentialTimeZoneIsStale: vendorTimeZone_1.credentialTimeZoneIsStale,
|
|
164
|
+
writeCredentialTimeZone: vendorTimeZone_1.writeCredentialTimeZone,
|
|
165
|
+
fetchGoogleCalendarTimeZone: vendorTimeZone_1.fetchGoogleCalendarTimeZone,
|
|
166
|
+
fetchCalcomTimeZone: vendorTimeZone_1.fetchCalcomTimeZone,
|
|
155
167
|
PlanLimitError: planLimitError_1.PlanLimitError,
|
|
156
168
|
isPlanLimitError: planLimitError_1.isPlanLimitError
|
|
157
169
|
};
|
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,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is this a time zone the runtime actually knows?
|
|
3
|
+
*
|
|
4
|
+
* Everything downstream — `Intl.DateTimeFormat`, Google's `start.timeZone`,
|
|
5
|
+
* Cal.com's `attendee.timeZone` — throws or 400s on a name it cannot resolve.
|
|
6
|
+
* A vendor returning something unexpected must degrade to "we don't know"
|
|
7
|
+
* rather than poison every later call with a value that cannot be used.
|
|
8
|
+
*/
|
|
9
|
+
export declare const isValidTimeZone: (value: unknown) => value is string;
|
|
10
|
+
/** The cached zone on a credential's metadata, or null. */
|
|
11
|
+
export declare const readCredentialTimeZone: (metadata: unknown) => string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Should we ask the vendor again?
|
|
14
|
+
*
|
|
15
|
+
* True when there is nothing cached, when the stamp is missing or unreadable,
|
|
16
|
+
* or when the TTL has passed. A cache with no stamp is treated as stale rather
|
|
17
|
+
* than as fresh-forever — the conservative direction, since the only cost is
|
|
18
|
+
* one request.
|
|
19
|
+
*/
|
|
20
|
+
export declare const credentialTimeZoneIsStale: (metadata: unknown, now?: number) => boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Merge a freshly read zone into a credential's metadata.
|
|
23
|
+
*
|
|
24
|
+
* Merges rather than replaces, because this column also carries the reauth
|
|
25
|
+
* markers — writing a bare `{ timeZone }` here would clear `needsReauth` and
|
|
26
|
+
* silently re-enable a connection the refresh path had flagged as broken.
|
|
27
|
+
*
|
|
28
|
+
* A null zone (the vendor could not tell us) still stamps the check, so a
|
|
29
|
+
* provider that never reports one is asked once a day rather than on every
|
|
30
|
+
* single call.
|
|
31
|
+
*/
|
|
32
|
+
export declare const writeCredentialTimeZone: (previous: unknown, timeZone: string | null, now?: Date) => Record<string, unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* The zone Google Calendar is configured in, from the primary calendar.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately NOT `GET /users/me/settings/timezone`, which is the more
|
|
37
|
+
* direct answer to "what did the user configure" and needs
|
|
38
|
+
* `calendar.settings.readonly` — a scope we do not request and could not add
|
|
39
|
+
* without sending every already-connected user back through consent. The
|
|
40
|
+
* primary calendar's zone is the same value in every case that matters, and
|
|
41
|
+
* `calendar.readonly` already covers it.
|
|
42
|
+
*
|
|
43
|
+
* Returns null on any failure. Not knowing the zone is a state the callers
|
|
44
|
+
* handle; a throw here would take a tool call or a chat turn with it.
|
|
45
|
+
*/
|
|
46
|
+
export declare const fetchGoogleCalendarTimeZone: (accessToken: string) => Promise<string | null>;
|
|
47
|
+
/**
|
|
48
|
+
* The zone on the connected Cal.com profile.
|
|
49
|
+
*
|
|
50
|
+
* This is the host's zone — the one their availability is written in — which
|
|
51
|
+
* is what "9am" means when the artifact owner or their bot says it. It is not
|
|
52
|
+
* the attendee's zone; see the booking handler for why we use it there anyway.
|
|
53
|
+
*
|
|
54
|
+
* Same null-on-failure contract as the Google reader above.
|
|
55
|
+
*/
|
|
56
|
+
export declare const fetchCalcomTimeZone: (apiKey: string) => Promise<string | null>;
|
|
57
|
+
//# sourceMappingURL=vendorTimeZone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vendorTimeZone.d.ts","sourceRoot":"","sources":["../src/vendorTimeZone.ts"],"names":[],"mappings":"AAiCA;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,MAQzD,CAAC;AAEF,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,GAAI,UAAU,OAAO,KAAG,MAAM,GAAG,IAInE,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,GACpC,UAAU,OAAO,EACjB,MAAK,MAAmB,KACvB,OAOF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,GAClC,UAAU,OAAO,EACjB,UAAU,MAAM,GAAG,IAAI,EACvB,MAAK,IAAiB,KACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAYxB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B,GACtC,aAAa,MAAM,KAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAiBvB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,MAAM,KACb,OAAO,CAAC,MAAM,GAAG,IAAI,CAkBvB,CAAC"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The time zone the user configured with the vendor, and where we keep it.
|
|
3
|
+
//
|
|
4
|
+
// Every scheduling question the model answers is a time-zone question first:
|
|
5
|
+
// "tomorrow at 9" is not an instant until you know whose 9 it is. We had two
|
|
6
|
+
// sources for that and neither worked. `artifact_tool.config.defaultTimeZone`
|
|
7
|
+
// is only written when the owner opens a dropdown and changes it, so in
|
|
8
|
+
// practice it is empty. The fallbacks underneath it were `undefined` (Google
|
|
9
|
+
// then uses the calendar's own zone, which only helps when the timestamp has no
|
|
10
|
+
// offset) and the string 'UTC' (Cal.com, which books the attendee in UTC and
|
|
11
|
+
// tells nobody).
|
|
12
|
+
//
|
|
13
|
+
// But the user already answered this question — in Google Calendar's settings,
|
|
14
|
+
// and in their Cal.com profile. So we ask the vendor instead of asking the
|
|
15
|
+
// owner again, and the answer becomes the default under any explicit choice.
|
|
16
|
+
//
|
|
17
|
+
// It is cached on `artifact_credential.metadata` because that is what it is a
|
|
18
|
+
// property of: the connected account, not the tool row. One artifact can have
|
|
19
|
+
// six calendar tools installed and they all share one connection, so the
|
|
20
|
+
// connection is the only place the answer belongs exactly once.
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.fetchCalcomTimeZone = exports.fetchGoogleCalendarTimeZone = exports.writeCredentialTimeZone = exports.credentialTimeZoneIsStale = exports.readCredentialTimeZone = exports.isValidTimeZone = void 0;
|
|
23
|
+
const constants_1 = require("./constants");
|
|
24
|
+
// Metadata keys on artifact_credential. Namespaced with a prefix that will not
|
|
25
|
+
// collide with the reauth markers written by the OAuth refresh path.
|
|
26
|
+
const TIME_ZONE_KEY = 'timeZone';
|
|
27
|
+
const TIME_ZONE_CHECKED_AT_KEY = 'timeZoneCheckedAt';
|
|
28
|
+
// How long a cached zone is trusted. A day, because this changes when somebody
|
|
29
|
+
// moves or travels — rarely, and never urgently. The cost of being briefly
|
|
30
|
+
// stale is one meeting in the old zone; the cost of a shorter TTL is a vendor
|
|
31
|
+
// round trip on the path of a tool call.
|
|
32
|
+
const TIME_ZONE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
33
|
+
/**
|
|
34
|
+
* Is this a time zone the runtime actually knows?
|
|
35
|
+
*
|
|
36
|
+
* Everything downstream — `Intl.DateTimeFormat`, Google's `start.timeZone`,
|
|
37
|
+
* Cal.com's `attendee.timeZone` — throws or 400s on a name it cannot resolve.
|
|
38
|
+
* A vendor returning something unexpected must degrade to "we don't know"
|
|
39
|
+
* rather than poison every later call with a value that cannot be used.
|
|
40
|
+
*/
|
|
41
|
+
const isValidTimeZone = (value) => {
|
|
42
|
+
if (typeof value !== 'string' || !value.trim())
|
|
43
|
+
return false;
|
|
44
|
+
try {
|
|
45
|
+
new Intl.DateTimeFormat('en-US', { timeZone: value.trim() });
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
exports.isValidTimeZone = isValidTimeZone;
|
|
53
|
+
/** The cached zone on a credential's metadata, or null. */
|
|
54
|
+
const readCredentialTimeZone = (metadata) => {
|
|
55
|
+
if (!metadata || typeof metadata !== 'object')
|
|
56
|
+
return null;
|
|
57
|
+
const value = metadata[TIME_ZONE_KEY];
|
|
58
|
+
return (0, exports.isValidTimeZone)(value) ? value.trim() : null;
|
|
59
|
+
};
|
|
60
|
+
exports.readCredentialTimeZone = readCredentialTimeZone;
|
|
61
|
+
/**
|
|
62
|
+
* Should we ask the vendor again?
|
|
63
|
+
*
|
|
64
|
+
* True when there is nothing cached, when the stamp is missing or unreadable,
|
|
65
|
+
* or when the TTL has passed. A cache with no stamp is treated as stale rather
|
|
66
|
+
* than as fresh-forever — the conservative direction, since the only cost is
|
|
67
|
+
* one request.
|
|
68
|
+
*/
|
|
69
|
+
const credentialTimeZoneIsStale = (metadata, now = Date.now()) => {
|
|
70
|
+
if (!(0, exports.readCredentialTimeZone)(metadata))
|
|
71
|
+
return true;
|
|
72
|
+
const raw = metadata[TIME_ZONE_CHECKED_AT_KEY];
|
|
73
|
+
if (typeof raw !== 'string')
|
|
74
|
+
return true;
|
|
75
|
+
const checkedAt = Date.parse(raw);
|
|
76
|
+
if (!Number.isFinite(checkedAt))
|
|
77
|
+
return true;
|
|
78
|
+
return now - checkedAt >= TIME_ZONE_TTL_MS;
|
|
79
|
+
};
|
|
80
|
+
exports.credentialTimeZoneIsStale = credentialTimeZoneIsStale;
|
|
81
|
+
/**
|
|
82
|
+
* Merge a freshly read zone into a credential's metadata.
|
|
83
|
+
*
|
|
84
|
+
* Merges rather than replaces, because this column also carries the reauth
|
|
85
|
+
* markers — writing a bare `{ timeZone }` here would clear `needsReauth` and
|
|
86
|
+
* silently re-enable a connection the refresh path had flagged as broken.
|
|
87
|
+
*
|
|
88
|
+
* A null zone (the vendor could not tell us) still stamps the check, so a
|
|
89
|
+
* provider that never reports one is asked once a day rather than on every
|
|
90
|
+
* single call.
|
|
91
|
+
*/
|
|
92
|
+
const writeCredentialTimeZone = (previous, timeZone, now = new Date()) => {
|
|
93
|
+
const base = previous && typeof previous === 'object'
|
|
94
|
+
? { ...previous }
|
|
95
|
+
: {};
|
|
96
|
+
if ((0, exports.isValidTimeZone)(timeZone)) {
|
|
97
|
+
base[TIME_ZONE_KEY] = timeZone.trim();
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
delete base[TIME_ZONE_KEY];
|
|
101
|
+
}
|
|
102
|
+
base[TIME_ZONE_CHECKED_AT_KEY] = now.toISOString();
|
|
103
|
+
return base;
|
|
104
|
+
};
|
|
105
|
+
exports.writeCredentialTimeZone = writeCredentialTimeZone;
|
|
106
|
+
/**
|
|
107
|
+
* The zone Google Calendar is configured in, from the primary calendar.
|
|
108
|
+
*
|
|
109
|
+
* Deliberately NOT `GET /users/me/settings/timezone`, which is the more
|
|
110
|
+
* direct answer to "what did the user configure" and needs
|
|
111
|
+
* `calendar.settings.readonly` — a scope we do not request and could not add
|
|
112
|
+
* without sending every already-connected user back through consent. The
|
|
113
|
+
* primary calendar's zone is the same value in every case that matters, and
|
|
114
|
+
* `calendar.readonly` already covers it.
|
|
115
|
+
*
|
|
116
|
+
* Returns null on any failure. Not knowing the zone is a state the callers
|
|
117
|
+
* handle; a throw here would take a tool call or a chat turn with it.
|
|
118
|
+
*/
|
|
119
|
+
const fetchGoogleCalendarTimeZone = async (accessToken) => {
|
|
120
|
+
try {
|
|
121
|
+
const response = await fetch(`${constants_1.constants.GOOGLE_CALENDAR_API_BASE}/calendars/primary`, {
|
|
122
|
+
headers: {
|
|
123
|
+
Authorization: `Bearer ${accessToken}`,
|
|
124
|
+
Accept: 'application/json'
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
if (!response.ok)
|
|
128
|
+
return null;
|
|
129
|
+
const payload = (await response.json());
|
|
130
|
+
return (0, exports.isValidTimeZone)(payload?.timeZone) ? payload.timeZone.trim() : null;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
exports.fetchGoogleCalendarTimeZone = fetchGoogleCalendarTimeZone;
|
|
137
|
+
/**
|
|
138
|
+
* The zone on the connected Cal.com profile.
|
|
139
|
+
*
|
|
140
|
+
* This is the host's zone — the one their availability is written in — which
|
|
141
|
+
* is what "9am" means when the artifact owner or their bot says it. It is not
|
|
142
|
+
* the attendee's zone; see the booking handler for why we use it there anyway.
|
|
143
|
+
*
|
|
144
|
+
* Same null-on-failure contract as the Google reader above.
|
|
145
|
+
*/
|
|
146
|
+
const fetchCalcomTimeZone = async (apiKey) => {
|
|
147
|
+
try {
|
|
148
|
+
const response = await fetch(`${constants_1.constants.CALCOM_API_BASE}/me`, {
|
|
149
|
+
headers: {
|
|
150
|
+
Authorization: `Bearer ${apiKey}`,
|
|
151
|
+
'cal-api-version': constants_1.constants.CALCOM_API_VERSION_ME,
|
|
152
|
+
Accept: 'application/json'
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
if (!response.ok)
|
|
156
|
+
return null;
|
|
157
|
+
const payload = (await response.json());
|
|
158
|
+
const value = payload?.data?.timeZone;
|
|
159
|
+
return (0, exports.isValidTimeZone)(value) ? value.trim() : null;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
exports.fetchCalcomTimeZone = fetchCalcomTimeZone;
|
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
|
@@ -1054,6 +1054,7 @@ const CALCOM_API_BASE = 'https://api.cal.com/v2';
|
|
|
1054
1054
|
const CALCOM_API_VERSION_EVENT_TYPES = '2024-06-14';
|
|
1055
1055
|
const CALCOM_API_VERSION_SLOTS = '2024-09-04';
|
|
1056
1056
|
const CALCOM_API_VERSION_BOOKINGS = '2026-02-25';
|
|
1057
|
+
const CALCOM_API_VERSION_ME = '2024-06-14';
|
|
1057
1058
|
|
|
1058
1059
|
const CALCOM_TOOL_KEY_PREFIX = 'calcom-';
|
|
1059
1060
|
|
|
@@ -1695,6 +1696,55 @@ const ARTIFACT_SCOPE_PREFIX = 'artifact:';
|
|
|
1695
1696
|
// that is one library's behaviour and this is a login that has already opened
|
|
1696
1697
|
// someone's browser by the time it would fail.
|
|
1697
1698
|
|
|
1699
|
+
// A personal access token — the durable credential a machine with no browser
|
|
1700
|
+
// uses, where an OAuth access token's one hour is not enough. Bound to one
|
|
1701
|
+
// project, because that is the unit a deploy pipeline works on: one repository,
|
|
1702
|
+
// one artifact, one credential in its CI settings.
|
|
1703
|
+
//
|
|
1704
|
+
// The prefix is part of the value rather than decoration: it is what lets the
|
|
1705
|
+
// middleware tell one of these from an OAuth token before it decides which
|
|
1706
|
+
// lookup to make, and it is what secret scanners match on when one leaks into a
|
|
1707
|
+
// repository. The rest is 32 random bytes, base64url — the token is the only
|
|
1708
|
+
// place the value ever exists in plaintext, since what is stored is its hash.
|
|
1709
|
+
const ACCESS_TOKEN_PREFIX = 'ganju_pat_';
|
|
1710
|
+
const ACCESS_TOKEN_BYTES = 32;
|
|
1711
|
+
|
|
1712
|
+
// Enough of the secret to recognise a row by, and not enough to be worth
|
|
1713
|
+
// stealing. Shown in the dashboard and by `ganju token list` beside the name.
|
|
1714
|
+
const ACCESS_TOKEN_HINT_CHARS = 6;
|
|
1715
|
+
|
|
1716
|
+
const ACCESS_TOKEN_NAME_MAX = 100;
|
|
1717
|
+
|
|
1718
|
+
// A ceiling per project, so a compromised session cannot quietly mint an
|
|
1719
|
+
// unbounded set of credentials that each survive the session being ended.
|
|
1720
|
+
const ACCESS_TOKEN_MAX_PER_PROJECT = 20;
|
|
1721
|
+
|
|
1722
|
+
// An expiry is optional, because a scheduled deploy that dies on a date nobody
|
|
1723
|
+
// wrote down is its own kind of outage — but a year is the longest we will
|
|
1724
|
+
// write one for.
|
|
1725
|
+
const ACCESS_TOKEN_MAX_EXPIRY_DAYS = 365;
|
|
1726
|
+
|
|
1727
|
+
// `last_used_at` is a convenience, not an audit log, so it is written at most
|
|
1728
|
+
// this often per token rather than on every request. The question it answers —
|
|
1729
|
+
// "is anything still using this, or can I revoke it" — does not get a better
|
|
1730
|
+
// answer from minute-level precision, and the write would otherwise land on the
|
|
1731
|
+
// hot path of every CI request.
|
|
1732
|
+
const ACCESS_TOKEN_LAST_USED_INTERVAL_MS = 5 * 60 * 1000;
|
|
1733
|
+
|
|
1734
|
+
// The only path a personal access token may reach without naming the project it
|
|
1735
|
+
// is scoped to, and only on GET. `/me` reports who the token is, which is how
|
|
1736
|
+
// the CLI confirms a machine is authenticated at all, and tells it nothing it
|
|
1737
|
+
// does not already hold.
|
|
1738
|
+
//
|
|
1739
|
+
// Everything else is refused, organization routes included — billing, members,
|
|
1740
|
+
// the model configs and the other projects are not what a deploy credential is
|
|
1741
|
+
// for. The list is deliberately this short: a route added later is closed by
|
|
1742
|
+
// omission rather than open by it.
|
|
1743
|
+
const ACCESS_TOKEN_UNSCOPED_PATHS = ['/me'];
|
|
1744
|
+
|
|
1745
|
+
const ACCESS_TOKEN_SCOPE_MESSAGE =
|
|
1746
|
+
'This token is scoped to a different project';
|
|
1747
|
+
|
|
1698
1748
|
// Recent custom-tool invocations, as `ganju logs` reads them.
|
|
1699
1749
|
const CUSTOM_CODE_LOGS_DEFAULT_LIMIT = 20;
|
|
1700
1750
|
const CUSTOM_CODE_LOGS_MAX_LIMIT = 100;
|
|
@@ -2429,6 +2479,7 @@ export const constants = {
|
|
|
2429
2479
|
CALCOM_API_VERSION_EVENT_TYPES,
|
|
2430
2480
|
CALCOM_API_VERSION_SLOTS,
|
|
2431
2481
|
CALCOM_API_VERSION_BOOKINGS,
|
|
2482
|
+
CALCOM_API_VERSION_ME,
|
|
2432
2483
|
CALCOM_TOOL_KEY_PREFIX,
|
|
2433
2484
|
TAVILY_API_BASE,
|
|
2434
2485
|
TAVILY_SEARCH_DEPTH_BASIC,
|
|
@@ -2596,6 +2647,15 @@ export const constants = {
|
|
|
2596
2647
|
CLI_OAUTH_REDIRECT_PORTS,
|
|
2597
2648
|
CLI_OAUTH_SCOPES,
|
|
2598
2649
|
CLI_TOKEN_REFRESH_SKEW_SECONDS,
|
|
2650
|
+
ACCESS_TOKEN_PREFIX,
|
|
2651
|
+
ACCESS_TOKEN_BYTES,
|
|
2652
|
+
ACCESS_TOKEN_HINT_CHARS,
|
|
2653
|
+
ACCESS_TOKEN_NAME_MAX,
|
|
2654
|
+
ACCESS_TOKEN_MAX_PER_PROJECT,
|
|
2655
|
+
ACCESS_TOKEN_MAX_EXPIRY_DAYS,
|
|
2656
|
+
ACCESS_TOKEN_LAST_USED_INTERVAL_MS,
|
|
2657
|
+
ACCESS_TOKEN_UNSCOPED_PATHS,
|
|
2658
|
+
ACCESS_TOKEN_SCOPE_MESSAGE,
|
|
2599
2659
|
CUSTOM_CODE_LOGS_DEFAULT_LIMIT,
|
|
2600
2660
|
CUSTOM_CODE_LOGS_MAX_LIMIT,
|
|
2601
2661
|
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,
|
|
@@ -211,6 +218,14 @@ import {
|
|
|
211
218
|
isCredentialNeedingReauth
|
|
212
219
|
} from './oauth';
|
|
213
220
|
import type { RefreshOAuthTokenInput, RefreshedOAuthToken } from './oauth';
|
|
221
|
+
import {
|
|
222
|
+
isValidTimeZone,
|
|
223
|
+
readCredentialTimeZone,
|
|
224
|
+
credentialTimeZoneIsStale,
|
|
225
|
+
writeCredentialTimeZone,
|
|
226
|
+
fetchGoogleCalendarTimeZone,
|
|
227
|
+
fetchCalcomTimeZone
|
|
228
|
+
} from './vendorTimeZone';
|
|
214
229
|
import { PlanLimitError, isPlanLimitError } from './planLimitError';
|
|
215
230
|
import type { PlanLimitDetails } from './planLimitError';
|
|
216
231
|
import type { PlanLimits } from './constants';
|
|
@@ -283,6 +298,10 @@ export const utils = {
|
|
|
283
298
|
languageFromHeader,
|
|
284
299
|
slugifyTitle,
|
|
285
300
|
resourceUriFromTitle,
|
|
301
|
+
accessTokenHint,
|
|
302
|
+
hashAccessToken,
|
|
303
|
+
isAccessToken,
|
|
304
|
+
mintAccessToken,
|
|
286
305
|
TOOL_CATALOG,
|
|
287
306
|
TOOL_KEYS,
|
|
288
307
|
describeCatalogTool,
|
|
@@ -322,11 +341,18 @@ export const utils = {
|
|
|
322
341
|
buildReauthMetadata,
|
|
323
342
|
clearReauthMetadata,
|
|
324
343
|
isCredentialNeedingReauth,
|
|
344
|
+
isValidTimeZone,
|
|
345
|
+
readCredentialTimeZone,
|
|
346
|
+
credentialTimeZoneIsStale,
|
|
347
|
+
writeCredentialTimeZone,
|
|
348
|
+
fetchGoogleCalendarTimeZone,
|
|
349
|
+
fetchCalcomTimeZone,
|
|
325
350
|
PlanLimitError,
|
|
326
351
|
isPlanLimitError
|
|
327
352
|
};
|
|
328
353
|
|
|
329
354
|
export type {
|
|
355
|
+
MintedAccessToken,
|
|
330
356
|
CustomCodeProject,
|
|
331
357
|
ProjectPathIssue,
|
|
332
358
|
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,
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// The time zone the user configured with the vendor, and where we keep it.
|
|
2
|
+
//
|
|
3
|
+
// Every scheduling question the model answers is a time-zone question first:
|
|
4
|
+
// "tomorrow at 9" is not an instant until you know whose 9 it is. We had two
|
|
5
|
+
// sources for that and neither worked. `artifact_tool.config.defaultTimeZone`
|
|
6
|
+
// is only written when the owner opens a dropdown and changes it, so in
|
|
7
|
+
// practice it is empty. The fallbacks underneath it were `undefined` (Google
|
|
8
|
+
// then uses the calendar's own zone, which only helps when the timestamp has no
|
|
9
|
+
// offset) and the string 'UTC' (Cal.com, which books the attendee in UTC and
|
|
10
|
+
// tells nobody).
|
|
11
|
+
//
|
|
12
|
+
// But the user already answered this question — in Google Calendar's settings,
|
|
13
|
+
// and in their Cal.com profile. So we ask the vendor instead of asking the
|
|
14
|
+
// owner again, and the answer becomes the default under any explicit choice.
|
|
15
|
+
//
|
|
16
|
+
// It is cached on `artifact_credential.metadata` because that is what it is a
|
|
17
|
+
// property of: the connected account, not the tool row. One artifact can have
|
|
18
|
+
// six calendar tools installed and they all share one connection, so the
|
|
19
|
+
// connection is the only place the answer belongs exactly once.
|
|
20
|
+
|
|
21
|
+
import { constants } from './constants';
|
|
22
|
+
|
|
23
|
+
// Metadata keys on artifact_credential. Namespaced with a prefix that will not
|
|
24
|
+
// collide with the reauth markers written by the OAuth refresh path.
|
|
25
|
+
const TIME_ZONE_KEY = 'timeZone';
|
|
26
|
+
const TIME_ZONE_CHECKED_AT_KEY = 'timeZoneCheckedAt';
|
|
27
|
+
|
|
28
|
+
// How long a cached zone is trusted. A day, because this changes when somebody
|
|
29
|
+
// moves or travels — rarely, and never urgently. The cost of being briefly
|
|
30
|
+
// stale is one meeting in the old zone; the cost of a shorter TTL is a vendor
|
|
31
|
+
// round trip on the path of a tool call.
|
|
32
|
+
const TIME_ZONE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Is this a time zone the runtime actually knows?
|
|
36
|
+
*
|
|
37
|
+
* Everything downstream — `Intl.DateTimeFormat`, Google's `start.timeZone`,
|
|
38
|
+
* Cal.com's `attendee.timeZone` — throws or 400s on a name it cannot resolve.
|
|
39
|
+
* A vendor returning something unexpected must degrade to "we don't know"
|
|
40
|
+
* rather than poison every later call with a value that cannot be used.
|
|
41
|
+
*/
|
|
42
|
+
export const isValidTimeZone = (value: unknown): value is string => {
|
|
43
|
+
if (typeof value !== 'string' || !value.trim()) return false;
|
|
44
|
+
try {
|
|
45
|
+
new Intl.DateTimeFormat('en-US', { timeZone: value.trim() });
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** The cached zone on a credential's metadata, or null. */
|
|
53
|
+
export const readCredentialTimeZone = (metadata: unknown): string | null => {
|
|
54
|
+
if (!metadata || typeof metadata !== 'object') return null;
|
|
55
|
+
const value = (metadata as Record<string, unknown>)[TIME_ZONE_KEY];
|
|
56
|
+
return isValidTimeZone(value) ? value.trim() : null;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Should we ask the vendor again?
|
|
61
|
+
*
|
|
62
|
+
* True when there is nothing cached, when the stamp is missing or unreadable,
|
|
63
|
+
* or when the TTL has passed. A cache with no stamp is treated as stale rather
|
|
64
|
+
* than as fresh-forever — the conservative direction, since the only cost is
|
|
65
|
+
* one request.
|
|
66
|
+
*/
|
|
67
|
+
export const credentialTimeZoneIsStale = (
|
|
68
|
+
metadata: unknown,
|
|
69
|
+
now: number = Date.now()
|
|
70
|
+
): boolean => {
|
|
71
|
+
if (!readCredentialTimeZone(metadata)) return true;
|
|
72
|
+
const raw = (metadata as Record<string, unknown>)[TIME_ZONE_CHECKED_AT_KEY];
|
|
73
|
+
if (typeof raw !== 'string') return true;
|
|
74
|
+
const checkedAt = Date.parse(raw);
|
|
75
|
+
if (!Number.isFinite(checkedAt)) return true;
|
|
76
|
+
return now - checkedAt >= TIME_ZONE_TTL_MS;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Merge a freshly read zone into a credential's metadata.
|
|
81
|
+
*
|
|
82
|
+
* Merges rather than replaces, because this column also carries the reauth
|
|
83
|
+
* markers — writing a bare `{ timeZone }` here would clear `needsReauth` and
|
|
84
|
+
* silently re-enable a connection the refresh path had flagged as broken.
|
|
85
|
+
*
|
|
86
|
+
* A null zone (the vendor could not tell us) still stamps the check, so a
|
|
87
|
+
* provider that never reports one is asked once a day rather than on every
|
|
88
|
+
* single call.
|
|
89
|
+
*/
|
|
90
|
+
export const writeCredentialTimeZone = (
|
|
91
|
+
previous: unknown,
|
|
92
|
+
timeZone: string | null,
|
|
93
|
+
now: Date = new Date()
|
|
94
|
+
): Record<string, unknown> => {
|
|
95
|
+
const base =
|
|
96
|
+
previous && typeof previous === 'object'
|
|
97
|
+
? { ...(previous as Record<string, unknown>) }
|
|
98
|
+
: {};
|
|
99
|
+
if (isValidTimeZone(timeZone)) {
|
|
100
|
+
base[TIME_ZONE_KEY] = timeZone.trim();
|
|
101
|
+
} else {
|
|
102
|
+
delete base[TIME_ZONE_KEY];
|
|
103
|
+
}
|
|
104
|
+
base[TIME_ZONE_CHECKED_AT_KEY] = now.toISOString();
|
|
105
|
+
return base;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The zone Google Calendar is configured in, from the primary calendar.
|
|
110
|
+
*
|
|
111
|
+
* Deliberately NOT `GET /users/me/settings/timezone`, which is the more
|
|
112
|
+
* direct answer to "what did the user configure" and needs
|
|
113
|
+
* `calendar.settings.readonly` — a scope we do not request and could not add
|
|
114
|
+
* without sending every already-connected user back through consent. The
|
|
115
|
+
* primary calendar's zone is the same value in every case that matters, and
|
|
116
|
+
* `calendar.readonly` already covers it.
|
|
117
|
+
*
|
|
118
|
+
* Returns null on any failure. Not knowing the zone is a state the callers
|
|
119
|
+
* handle; a throw here would take a tool call or a chat turn with it.
|
|
120
|
+
*/
|
|
121
|
+
export const fetchGoogleCalendarTimeZone = async (
|
|
122
|
+
accessToken: string
|
|
123
|
+
): Promise<string | null> => {
|
|
124
|
+
try {
|
|
125
|
+
const response = await fetch(
|
|
126
|
+
`${constants.GOOGLE_CALENDAR_API_BASE}/calendars/primary`,
|
|
127
|
+
{
|
|
128
|
+
headers: {
|
|
129
|
+
Authorization: `Bearer ${accessToken}`,
|
|
130
|
+
Accept: 'application/json'
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
if (!response.ok) return null;
|
|
135
|
+
const payload = (await response.json()) as { timeZone?: unknown };
|
|
136
|
+
return isValidTimeZone(payload?.timeZone) ? payload.timeZone.trim() : null;
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The zone on the connected Cal.com profile.
|
|
144
|
+
*
|
|
145
|
+
* This is the host's zone — the one their availability is written in — which
|
|
146
|
+
* is what "9am" means when the artifact owner or their bot says it. It is not
|
|
147
|
+
* the attendee's zone; see the booking handler for why we use it there anyway.
|
|
148
|
+
*
|
|
149
|
+
* Same null-on-failure contract as the Google reader above.
|
|
150
|
+
*/
|
|
151
|
+
export const fetchCalcomTimeZone = async (
|
|
152
|
+
apiKey: string
|
|
153
|
+
): Promise<string | null> => {
|
|
154
|
+
try {
|
|
155
|
+
const response = await fetch(`${constants.CALCOM_API_BASE}/me`, {
|
|
156
|
+
headers: {
|
|
157
|
+
Authorization: `Bearer ${apiKey}`,
|
|
158
|
+
'cal-api-version': constants.CALCOM_API_VERSION_ME,
|
|
159
|
+
Accept: 'application/json'
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
if (!response.ok) return null;
|
|
163
|
+
const payload = (await response.json()) as {
|
|
164
|
+
data?: { timeZone?: unknown };
|
|
165
|
+
};
|
|
166
|
+
const value = payload?.data?.timeZone;
|
|
167
|
+
return isValidTimeZone(value) ? value.trim() : null;
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
};
|