@shipfox/api-auth 19.0.0 → 20.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +17 -2
- package/CHANGELOG.md +13 -0
- package/dist/core/cimd.d.ts +39 -0
- package/dist/core/cimd.d.ts.map +1 -0
- package/dist/core/cimd.js +392 -0
- package/dist/core/cimd.js.map +1 -0
- package/dist/core/errors.d.ts +14 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +26 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/oauth-client-resolver.d.ts +54 -0
- package/dist/core/oauth-client-resolver.d.ts.map +1 -0
- package/dist/core/oauth-client-resolver.js +154 -0
- package/dist/core/oauth-client-resolver.js.map +1 -0
- package/dist/core/oauth-client.d.ts +39 -0
- package/dist/core/oauth-client.d.ts.map +1 -0
- package/dist/core/oauth-client.js +236 -0
- package/dist/core/oauth-client.js.map +1 -0
- package/dist/db/agent-access-retention.d.ts +10 -0
- package/dist/db/agent-access-retention.d.ts.map +1 -0
- package/dist/db/agent-access-retention.js +11 -0
- package/dist/db/agent-access-retention.js.map +1 -0
- package/dist/db/agent-access.d.ts +100 -4
- package/dist/db/agent-access.d.ts.map +1 -1
- package/dist/db/agent-access.js +483 -45
- package/dist/db/agent-access.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics/instance.d.ts +1 -1
- package/dist/metrics/instance.d.ts.map +1 -1
- package/dist/metrics/instance.js.map +1 -1
- package/dist/presentation/routes/oauth.d.ts +9 -0
- package/dist/presentation/routes/oauth.d.ts.map +1 -0
- package/dist/presentation/routes/oauth.js +150 -0
- package/dist/presentation/routes/oauth.js.map +1 -0
- package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
- package/dist/presentation/routes/rate-limit.js +24 -0
- package/dist/presentation/routes/rate-limit.js.map +1 -1
- package/dist/temporal/activities/agent-access-retention.d.ts +13 -0
- package/dist/temporal/activities/agent-access-retention.d.ts.map +1 -0
- package/dist/temporal/activities/agent-access-retention.js +58 -0
- package/dist/temporal/activities/agent-access-retention.js.map +1 -0
- package/dist/temporal/activities/index.d.ts +5 -0
- package/dist/temporal/activities/index.d.ts.map +1 -0
- package/dist/temporal/activities/index.js +8 -0
- package/dist/temporal/activities/index.js.map +1 -0
- package/dist/temporal/constants.d.ts +2 -0
- package/dist/temporal/constants.d.ts.map +1 -0
- package/dist/temporal/constants.js +3 -0
- package/dist/temporal/constants.js.map +1 -0
- package/dist/temporal/workflows/agent-access-retention-cron.d.ts +2 -0
- package/dist/temporal/workflows/agent-access-retention-cron.d.ts.map +1 -0
- package/dist/temporal/workflows/agent-access-retention-cron.js +22 -0
- package/dist/temporal/workflows/agent-access-retention-cron.js.map +1 -0
- package/dist/temporal/workflows/index.bundle.js +25227 -0
- package/dist/temporal/workflows/index.bundle.meta.json +1 -0
- package/dist/temporal/workflows/index.d.ts +2 -0
- package/dist/temporal/workflows/index.d.ts.map +1 -0
- package/dist/temporal/workflows/index.js +3 -0
- package/dist/temporal/workflows/index.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +7 -4
- package/src/core/cimd.test.ts +213 -0
- package/src/core/cimd.ts +532 -0
- package/src/core/errors.ts +41 -0
- package/src/core/oauth-client-resolver.test.ts +198 -0
- package/src/core/oauth-client-resolver.ts +253 -0
- package/src/core/oauth-client.test.ts +157 -0
- package/src/core/oauth-client.ts +331 -0
- package/src/db/agent-access-retention.ts +10 -0
- package/src/db/agent-access.test.ts +697 -3
- package/src/db/agent-access.ts +953 -91
- package/src/index.test.ts +27 -0
- package/src/index.ts +76 -0
- package/src/metrics/instance.ts +5 -1
- package/src/presentation/routes/oauth.test.ts +133 -0
- package/src/presentation/routes/oauth.ts +144 -0
- package/src/presentation/routes/rate-limit.ts +12 -0
- package/src/temporal/activities/agent-access-retention.ts +76 -0
- package/src/temporal/activities/index.ts +5 -0
- package/src/temporal/constants.ts +1 -0
- package/src/temporal/workflows/agent-access-retention-cron.ts +23 -0
- package/src/temporal/workflows/index.ts +1 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OAUTH_READ_SCOPE,
|
|
3
|
+
type OAuthClientMetadataDocumentDto,
|
|
4
|
+
type OAuthDynamicClientRegistrationRequestDto,
|
|
5
|
+
oauthClientMetadataDocumentSchema,
|
|
6
|
+
oauthDynamicClientRegistrationRequestSchema,
|
|
7
|
+
} from '@shipfox/api-auth-dto';
|
|
8
|
+
import type {AgentClient} from './entities/agent-access.js';
|
|
9
|
+
import {
|
|
10
|
+
InvalidOAuthClientMetadataError,
|
|
11
|
+
InvalidOAuthConfigurationError,
|
|
12
|
+
OAuthRedirectUriNotRegisteredError,
|
|
13
|
+
} from './errors.js';
|
|
14
|
+
|
|
15
|
+
const URI_SUFFIX_PATTERN = /[/?#]/u;
|
|
16
|
+
const PORT_PATTERN = /^\d+$/u;
|
|
17
|
+
const BRACKETED_PORT_PATTERN = /^:\d+$/u;
|
|
18
|
+
|
|
19
|
+
function hasControlCharacter(value: string): boolean {
|
|
20
|
+
return [...value].some((character) => {
|
|
21
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
22
|
+
return codePoint < 0x20 || codePoint === 0x7f;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const OAUTH_CLIENT_ID_MAX_BYTES = 2048;
|
|
27
|
+
export const OAUTH_CLIENT_NAME_MAX_BYTES = 256;
|
|
28
|
+
export const OAUTH_REDIRECT_URI_MAX_BYTES = 2048;
|
|
29
|
+
export const OAUTH_REDIRECT_URI_MAX_COUNT = 10;
|
|
30
|
+
export const OAUTH_CIMD_CACHE_MAX_AGE_SECONDS = 15 * 60;
|
|
31
|
+
|
|
32
|
+
export type OAuthGrantType = 'authorization_code' | 'refresh_token';
|
|
33
|
+
|
|
34
|
+
export interface ValidatedOAuthClientMetadata {
|
|
35
|
+
clientId: string;
|
|
36
|
+
clientName: string;
|
|
37
|
+
redirectUris: string[];
|
|
38
|
+
grantTypes: OAuthGrantType[];
|
|
39
|
+
responseTypes: ['code'];
|
|
40
|
+
tokenEndpointAuthMethod: 'none';
|
|
41
|
+
scope: typeof OAUTH_READ_SCOPE;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function hasByteLengthAtMost(value: string, maxBytes: number): boolean {
|
|
45
|
+
return Buffer.byteLength(value, 'utf8') <= maxBytes;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function rejectInvalidMetadata(): never {
|
|
49
|
+
throw new InvalidOAuthClientMetadataError();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function rejectInvalidConfiguration(): never {
|
|
53
|
+
throw new InvalidOAuthConfigurationError();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseUrl(value: string): URL {
|
|
57
|
+
try {
|
|
58
|
+
return new URL(value);
|
|
59
|
+
} catch {
|
|
60
|
+
return rejectInvalidMetadata();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isLoopbackHostname(hostname: string): boolean {
|
|
65
|
+
const normalized = hostname.toLowerCase().replace(/^\[|\]$/gu, '');
|
|
66
|
+
return normalized === 'localhost' || normalized === '127.0.0.1' || normalized === '::1';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function validateUrlShape(url: URL): void {
|
|
70
|
+
if (
|
|
71
|
+
url.username ||
|
|
72
|
+
url.password ||
|
|
73
|
+
url.hash ||
|
|
74
|
+
url.hostname.length === 0 ||
|
|
75
|
+
hasControlCharacter(url.href)
|
|
76
|
+
) {
|
|
77
|
+
rejectInvalidMetadata();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Validates an OAuth redirect URI without making network assumptions. */
|
|
82
|
+
export function validateOAuthRedirectUri(value: string): URL {
|
|
83
|
+
if (
|
|
84
|
+
!hasByteLengthAtMost(value, OAUTH_REDIRECT_URI_MAX_BYTES) ||
|
|
85
|
+
value.length === 0 ||
|
|
86
|
+
value.trim() !== value ||
|
|
87
|
+
hasControlCharacter(value)
|
|
88
|
+
) {
|
|
89
|
+
return rejectInvalidMetadata();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const url = parseUrl(value);
|
|
93
|
+
validateUrlShape(url);
|
|
94
|
+
|
|
95
|
+
if (url.protocol === 'https:') return url;
|
|
96
|
+
if (url.protocol !== 'http:' || !isLoopbackHostname(url.hostname)) {
|
|
97
|
+
return rejectInvalidMetadata();
|
|
98
|
+
}
|
|
99
|
+
return url;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function isOAuthLoopbackRedirectUri(value: string): boolean {
|
|
103
|
+
try {
|
|
104
|
+
const url = validateOAuthRedirectUri(value);
|
|
105
|
+
return url.protocol === 'http:' && isLoopbackHostname(url.hostname);
|
|
106
|
+
} catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Redirect URI comparison is byte-for-byte for public HTTPS clients. Only a
|
|
113
|
+
* loopback HTTP client's port may vary, as required by native-app OAuth.
|
|
114
|
+
*/
|
|
115
|
+
export function oauthRedirectUriMatches(registered: string, requested: string): boolean {
|
|
116
|
+
try {
|
|
117
|
+
const registeredUrl = validateOAuthRedirectUri(registered);
|
|
118
|
+
const requestedUrl = validateOAuthRedirectUri(requested);
|
|
119
|
+
if (registered === requested) return true;
|
|
120
|
+
if (
|
|
121
|
+
registeredUrl.protocol !== 'http:' ||
|
|
122
|
+
requestedUrl.protocol !== 'http:' ||
|
|
123
|
+
!isLoopbackHostname(registeredUrl.hostname) ||
|
|
124
|
+
!isLoopbackHostname(requestedUrl.hostname)
|
|
125
|
+
) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return withoutLoopbackPort(registered) === withoutLoopbackPort(requested);
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function withoutLoopbackPort(value: string): string {
|
|
136
|
+
const authorityStart = value.indexOf('//') + 2;
|
|
137
|
+
const suffixOffset = value.slice(authorityStart).search(URI_SUFFIX_PATTERN);
|
|
138
|
+
const authorityEnd = suffixOffset === -1 ? value.length : authorityStart + suffixOffset;
|
|
139
|
+
const authority = value.slice(authorityStart, authorityEnd);
|
|
140
|
+
|
|
141
|
+
let authorityWithoutPort = authority;
|
|
142
|
+
if (authority.startsWith('[')) {
|
|
143
|
+
const closingBracket = authority.indexOf(']');
|
|
144
|
+
const port = closingBracket === -1 ? '' : authority.slice(closingBracket + 1);
|
|
145
|
+
if (closingBracket !== -1 && BRACKETED_PORT_PATTERN.test(port)) {
|
|
146
|
+
authorityWithoutPort = authority.slice(0, closingBracket + 1);
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
const colon = authority.lastIndexOf(':');
|
|
150
|
+
if (colon !== -1 && PORT_PATTERN.test(authority.slice(colon + 1))) {
|
|
151
|
+
authorityWithoutPort = authority.slice(0, colon);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return `${value.slice(0, authorityStart)}${authorityWithoutPort}${value.slice(authorityEnd)}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function assertOAuthRedirectUriRegistered(
|
|
159
|
+
redirectUris: readonly string[],
|
|
160
|
+
requested: string,
|
|
161
|
+
): void {
|
|
162
|
+
validateOAuthRedirectUri(requested);
|
|
163
|
+
if (!redirectUris.some((registered) => oauthRedirectUriMatches(registered, requested))) {
|
|
164
|
+
throw new OAuthRedirectUriNotRegisteredError();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Client ID Metadata Documents must be HTTPS URLs with a non-root path. */
|
|
169
|
+
export function validateOAuthClientId(value: string): URL {
|
|
170
|
+
if (
|
|
171
|
+
!hasByteLengthAtMost(value, OAUTH_CLIENT_ID_MAX_BYTES) ||
|
|
172
|
+
value.trim() !== value ||
|
|
173
|
+
hasControlCharacter(value)
|
|
174
|
+
) {
|
|
175
|
+
return rejectInvalidMetadata();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const url = parseUrl(value);
|
|
179
|
+
validateUrlShape(url);
|
|
180
|
+
if (url.protocol !== 'https:' || url.pathname === '/' || url.pathname.length === 0) {
|
|
181
|
+
return rejectInvalidMetadata();
|
|
182
|
+
}
|
|
183
|
+
return url;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Validates the public origin injected into the dormant route factory. */
|
|
187
|
+
export function validateOAuthPublicOrigin(value: string): string {
|
|
188
|
+
let url: URL;
|
|
189
|
+
try {
|
|
190
|
+
url = new URL(value);
|
|
191
|
+
} catch {
|
|
192
|
+
return rejectInvalidConfiguration();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (
|
|
196
|
+
(url.protocol !== 'https:' &&
|
|
197
|
+
!(url.protocol === 'http:' && isLoopbackHostname(url.hostname))) ||
|
|
198
|
+
value.trim() !== value ||
|
|
199
|
+
hasControlCharacter(value) ||
|
|
200
|
+
url.username ||
|
|
201
|
+
url.password ||
|
|
202
|
+
url.pathname !== '/' ||
|
|
203
|
+
url.search ||
|
|
204
|
+
url.hash ||
|
|
205
|
+
url.hostname.length === 0
|
|
206
|
+
) {
|
|
207
|
+
return rejectInvalidConfiguration();
|
|
208
|
+
}
|
|
209
|
+
return url.origin;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function validateByteBoundedString(value: string, maxBytes: number): void {
|
|
213
|
+
if (!hasByteLengthAtMost(value, maxBytes) || value.length === 0 || hasControlCharacter(value)) {
|
|
214
|
+
rejectInvalidMetadata();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function validateGrantTypes(value: readonly OAuthGrantType[] | undefined): OAuthGrantType[] {
|
|
219
|
+
const grantTypes = [...(value ?? ['authorization_code'])];
|
|
220
|
+
if (
|
|
221
|
+
grantTypes.length === 0 ||
|
|
222
|
+
grantTypes.length > 2 ||
|
|
223
|
+
new Set(grantTypes).size !== grantTypes.length ||
|
|
224
|
+
!grantTypes.includes('authorization_code') ||
|
|
225
|
+
grantTypes.some(
|
|
226
|
+
(grantType) => grantType !== 'authorization_code' && grantType !== 'refresh_token',
|
|
227
|
+
)
|
|
228
|
+
) {
|
|
229
|
+
rejectInvalidMetadata();
|
|
230
|
+
}
|
|
231
|
+
return grantTypes;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function validateResponseTypes(value: readonly 'code'[] | undefined): ['code'] {
|
|
235
|
+
const responseTypes = [...(value ?? ['code'])];
|
|
236
|
+
if (responseTypes.length !== 1 || responseTypes[0] !== 'code') rejectInvalidMetadata();
|
|
237
|
+
return ['code'];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function validateScope(value: string | undefined): typeof OAUTH_READ_SCOPE {
|
|
241
|
+
if (value !== undefined && value !== OAUTH_READ_SCOPE) rejectInvalidMetadata();
|
|
242
|
+
return OAUTH_READ_SCOPE;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function validateRedirectUris(redirectUris: readonly string[]): string[] {
|
|
246
|
+
if (
|
|
247
|
+
redirectUris.length === 0 ||
|
|
248
|
+
redirectUris.length > OAUTH_REDIRECT_URI_MAX_COUNT ||
|
|
249
|
+
new Set(redirectUris).size !== redirectUris.length
|
|
250
|
+
) {
|
|
251
|
+
rejectInvalidMetadata();
|
|
252
|
+
}
|
|
253
|
+
for (const redirectUri of redirectUris) validateOAuthRedirectUri(redirectUri);
|
|
254
|
+
return [...redirectUris];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function validateTokenEndpointAuthMethod(value: string | undefined): 'none' {
|
|
258
|
+
if (value !== undefined && value !== 'none') rejectInvalidMetadata();
|
|
259
|
+
return 'none';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function validateOAuthDynamicClientRegistration(
|
|
263
|
+
input: OAuthDynamicClientRegistrationRequestDto,
|
|
264
|
+
): Omit<ValidatedOAuthClientMetadata, 'clientId'> {
|
|
265
|
+
const parsed = oauthDynamicClientRegistrationRequestSchema.safeParse(input);
|
|
266
|
+
if (!parsed.success) rejectInvalidMetadata();
|
|
267
|
+
|
|
268
|
+
const value = parsed.data;
|
|
269
|
+
validateByteBoundedString(value.client_name, OAUTH_CLIENT_NAME_MAX_BYTES);
|
|
270
|
+
const grantTypes = validateGrantTypes(value.grant_types);
|
|
271
|
+
// Registered rows do not persist grant types yet. Keep the registration
|
|
272
|
+
// response and later resolution consistent until refresh-token support lands.
|
|
273
|
+
if (grantTypes.length !== 1 || grantTypes[0] !== 'authorization_code') {
|
|
274
|
+
rejectInvalidMetadata();
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
clientName: value.client_name,
|
|
278
|
+
redirectUris: validateRedirectUris(value.redirect_uris),
|
|
279
|
+
grantTypes,
|
|
280
|
+
responseTypes: validateResponseTypes(value.response_types),
|
|
281
|
+
tokenEndpointAuthMethod: validateTokenEndpointAuthMethod(value.token_endpoint_auth_method),
|
|
282
|
+
scope: validateScope(value.scope),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function validateOAuthClientMetadataDocument(
|
|
287
|
+
input: OAuthClientMetadataDocumentDto,
|
|
288
|
+
fetchedUrl: string,
|
|
289
|
+
): ValidatedOAuthClientMetadata {
|
|
290
|
+
const parsed = oauthClientMetadataDocumentSchema.safeParse(input);
|
|
291
|
+
if (!parsed.success || parsed.data.client_id !== fetchedUrl) rejectInvalidMetadata();
|
|
292
|
+
|
|
293
|
+
validateOAuthClientId(fetchedUrl);
|
|
294
|
+
const value = parsed.data;
|
|
295
|
+
validateByteBoundedString(value.client_name, OAUTH_CLIENT_NAME_MAX_BYTES);
|
|
296
|
+
return {
|
|
297
|
+
clientId: fetchedUrl,
|
|
298
|
+
clientName: value.client_name,
|
|
299
|
+
redirectUris: validateRedirectUris(value.redirect_uris),
|
|
300
|
+
grantTypes: validateGrantTypes(value.grant_types),
|
|
301
|
+
responseTypes: validateResponseTypes(value.response_types),
|
|
302
|
+
tokenEndpointAuthMethod: validateTokenEndpointAuthMethod(value.token_endpoint_auth_method),
|
|
303
|
+
scope: validateScope(value.scope),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function metadataForAgentClient(client: AgentClient): ValidatedOAuthClientMetadata {
|
|
308
|
+
validateByteBoundedString(client.clientId, OAUTH_CLIENT_ID_MAX_BYTES);
|
|
309
|
+
validateByteBoundedString(client.name, OAUTH_CLIENT_NAME_MAX_BYTES);
|
|
310
|
+
validateRedirectUris(client.redirectUris);
|
|
311
|
+
return {
|
|
312
|
+
clientId: client.clientId,
|
|
313
|
+
clientName: client.name,
|
|
314
|
+
redirectUris: [...client.redirectUris],
|
|
315
|
+
grantTypes: ['authorization_code'],
|
|
316
|
+
responseTypes: ['code'],
|
|
317
|
+
tokenEndpointAuthMethod: 'none',
|
|
318
|
+
scope: OAUTH_READ_SCOPE,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function assertOAuthClientMetadataMatchesRequest(params: {
|
|
323
|
+
metadata: ValidatedOAuthClientMetadata;
|
|
324
|
+
redirectUri?: string | undefined;
|
|
325
|
+
tokenEndpointAuthMethod?: string | undefined;
|
|
326
|
+
}): void {
|
|
327
|
+
if (params.redirectUri !== undefined) {
|
|
328
|
+
assertOAuthRedirectUriRegistered(params.metadata.redirectUris, params.redirectUri);
|
|
329
|
+
}
|
|
330
|
+
validateTokenEndpointAuthMethod(params.tokenEndpointAuthMethod);
|
|
331
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const AGENT_AUTHORIZATION_RETENTION_DAYS = 1;
|
|
2
|
+
export const AGENT_REFRESH_TOKEN_RETENTION_DAYS = 30;
|
|
3
|
+
export const AGENT_GRANT_RETENTION_DAYS = 90;
|
|
4
|
+
export const AGENT_PAT_RETENTION_DAYS = 90;
|
|
5
|
+
export const AGENT_CLIENT_RETENTION_DAYS = 30;
|
|
6
|
+
|
|
7
|
+
export const AGENT_ACCESS_RETENTION_BATCH_LIMIT = 200;
|
|
8
|
+
export const AGENT_ACCESS_RETENTION_MAX_ITERATIONS = 1_000;
|
|
9
|
+
export const AGENT_ACCESS_RETENTION_TIME_BUDGET_MS = 4 * 60_000;
|
|
10
|
+
export const AGENT_ACCESS_RETENTION_TIMEOUT_MARGIN_MS = 1_000;
|