@oxyhq/core 3.18.1 → 4.0.1
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/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/OxyServices.js +3 -2
- package/dist/cjs/i18n/locales/en-US.json +391 -17
- package/dist/cjs/i18n/locales/es-ES.json +391 -17
- package/dist/cjs/i18n/locales/locales/en-US.json +391 -17
- package/dist/cjs/i18n/locales/locales/es-ES.json +391 -17
- package/dist/cjs/mixins/OxyServices.accounts.js +480 -0
- package/dist/cjs/mixins/OxyServices.connectedApps.js +73 -0
- package/dist/cjs/mixins/OxyServices.utility.js +3 -2
- package/dist/cjs/mixins/index.js +9 -6
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/OxyServices.js +3 -2
- package/dist/esm/i18n/locales/en-US.json +391 -17
- package/dist/esm/i18n/locales/es-ES.json +391 -17
- package/dist/esm/i18n/locales/locales/en-US.json +391 -17
- package/dist/esm/i18n/locales/locales/es-ES.json +391 -17
- package/dist/esm/mixins/OxyServices.accounts.js +477 -0
- package/dist/esm/mixins/OxyServices.connectedApps.js +70 -0
- package/dist/esm/mixins/OxyServices.utility.js +3 -2
- package/dist/esm/mixins/index.js +9 -6
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/OxyServices.d.ts +3 -2
- package/dist/types/index.d.ts +2 -3
- package/dist/types/mixins/OxyServices.accounts.d.ts +642 -0
- package/dist/types/mixins/OxyServices.auth.d.ts +1 -1
- package/dist/types/mixins/OxyServices.connectedApps.d.ts +168 -0
- package/dist/types/mixins/OxyServices.utility.d.ts +6 -3
- package/dist/types/mixins/index.d.ts +3 -4
- package/package.json +1 -1
- package/src/OxyServices.ts +3 -2
- package/src/i18n/locales/en-US.json +391 -17
- package/src/i18n/locales/es-ES.json +391 -17
- package/src/index.ts +33 -34
- package/src/mixins/OxyServices.accounts.ts +1079 -0
- package/src/mixins/OxyServices.auth.ts +1 -1
- package/src/mixins/OxyServices.connectedApps.ts +165 -0
- package/src/mixins/OxyServices.utility.ts +7 -4
- package/src/mixins/__tests__/accounts.test.ts +667 -0
- package/src/mixins/__tests__/connectedApps.test.ts +1 -1
- package/src/mixins/index.ts +11 -9
- package/dist/cjs/mixins/OxyServices.applications.js +0 -350
- package/dist/cjs/mixins/OxyServices.managedAccounts.js +0 -143
- package/dist/cjs/mixins/OxyServices.workspaces.js +0 -181
- package/dist/esm/mixins/OxyServices.applications.js +0 -347
- package/dist/esm/mixins/OxyServices.managedAccounts.js +0 -140
- package/dist/esm/mixins/OxyServices.workspaces.js +0 -178
- package/dist/types/mixins/OxyServices.applications.d.ts +0 -496
- package/dist/types/mixins/OxyServices.managedAccounts.d.ts +0 -145
- package/dist/types/mixins/OxyServices.workspaces.d.ts +0 -219
- package/src/mixins/OxyServices.applications.ts +0 -773
- package/src/mixins/OxyServices.managedAccounts.ts +0 -173
- package/src/mixins/OxyServices.workspaces.ts +0 -351
|
@@ -1,773 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Applications Methods Mixin
|
|
3
|
-
*
|
|
4
|
-
* Provides methods for managing Oxy applications, their members, and their
|
|
5
|
-
* credentials via the `/applications` API. An application is a multi-user
|
|
6
|
-
* entity: membership (with a role) grants permissions; credentials
|
|
7
|
-
* (public/confidential/service) carry OAuth client identifiers and the
|
|
8
|
-
* service-token API key material.
|
|
9
|
-
*
|
|
10
|
-
* Reference applications by their Mongo `_id` (`applicationId`) and credentials
|
|
11
|
-
* by their `credentialId`. Never by name.
|
|
12
|
-
*/
|
|
13
|
-
import type { OxyServicesBase } from '../OxyServices.base';
|
|
14
|
-
import { CACHE_TIMES } from './mixinHelpers';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Application classification. Set only by Oxy platform staff — never editable
|
|
18
|
-
* through the normal member-facing update path.
|
|
19
|
-
*/
|
|
20
|
-
export type ApplicationType = 'first_party' | 'third_party' | 'internal' | 'system';
|
|
21
|
-
|
|
22
|
-
/** Lifecycle status of an application. */
|
|
23
|
-
export type ApplicationStatus = 'active' | 'suspended' | 'deleted' | 'pending_review';
|
|
24
|
-
|
|
25
|
-
/** Role a member holds within an application. */
|
|
26
|
-
export type ApplicationRole = 'owner' | 'admin' | 'developer' | 'viewer' | 'billing';
|
|
27
|
-
|
|
28
|
-
/** Membership lifecycle status. */
|
|
29
|
-
export type ApplicationMemberStatus = 'active' | 'invited' | 'removed';
|
|
30
|
-
|
|
31
|
-
/** Credential kind. `service` credentials mint service tokens. */
|
|
32
|
-
export type ApplicationCredentialType = 'public' | 'confidential' | 'service';
|
|
33
|
-
|
|
34
|
-
/** Deployment environment a credential is scoped to. */
|
|
35
|
-
export type ApplicationEnvironment = 'development' | 'staging' | 'production';
|
|
36
|
-
|
|
37
|
-
/** Credential lifecycle status. */
|
|
38
|
-
export type ApplicationCredentialStatus = 'active' | 'deprecated' | 'revoked';
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Client-facing Application shape returned by the `/applications` API.
|
|
42
|
-
* Mirrors the server `Application` model with `_id` as a string and dates
|
|
43
|
-
* serialized to ISO strings.
|
|
44
|
-
*/
|
|
45
|
-
export interface Application {
|
|
46
|
-
_id: string;
|
|
47
|
-
name: string;
|
|
48
|
-
description?: string;
|
|
49
|
-
websiteUrl?: string;
|
|
50
|
-
icon?: string;
|
|
51
|
-
type: ApplicationType;
|
|
52
|
-
status: ApplicationStatus;
|
|
53
|
-
isOfficial: boolean;
|
|
54
|
-
isInternal: boolean;
|
|
55
|
-
capabilities: string[];
|
|
56
|
-
redirectUris: string[];
|
|
57
|
-
scopes: string[];
|
|
58
|
-
webhookUrl?: string;
|
|
59
|
-
devWebhookUrl?: string;
|
|
60
|
-
createdByUserId: string;
|
|
61
|
-
/**
|
|
62
|
-
* The workspace this application belongs to (workspace `_id`), or `null` for
|
|
63
|
-
* applications not owned by a workspace. Used by the console to scope apps to
|
|
64
|
-
* a workspace and to branch on workspace-derived access.
|
|
65
|
-
*/
|
|
66
|
-
workspaceId: string | null;
|
|
67
|
-
createdAt: string;
|
|
68
|
-
updatedAt: string;
|
|
69
|
-
/**
|
|
70
|
-
* The calling user's own membership in this application, embedded by the API
|
|
71
|
-
* on list (`GET /applications`) and detail (`GET /applications/:appId`)
|
|
72
|
-
* responses. Use `callerMembership.permissions` to gate UI affordances.
|
|
73
|
-
*
|
|
74
|
-
* When the caller's access is derived from a workspace membership rather than
|
|
75
|
-
* a direct application membership, the API returns a synthetic membership
|
|
76
|
-
* with `source: 'workspace'` and `_id: null`.
|
|
77
|
-
*/
|
|
78
|
-
callerMembership?: ApplicationMember;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Client-facing ApplicationMember shape. `permissions` is derived from `role`
|
|
83
|
-
* on the server at write time.
|
|
84
|
-
*/
|
|
85
|
-
export interface ApplicationMember {
|
|
86
|
-
/**
|
|
87
|
-
* The membership's Mongo `_id`. `null` for a synthetic, workspace-derived
|
|
88
|
-
* membership (see {@link Application.callerMembership} and `source`).
|
|
89
|
-
*/
|
|
90
|
-
_id: string | null;
|
|
91
|
-
applicationId: string;
|
|
92
|
-
userId: string;
|
|
93
|
-
role: ApplicationRole;
|
|
94
|
-
permissions: string[];
|
|
95
|
-
invitedByUserId?: string;
|
|
96
|
-
joinedAt?: string;
|
|
97
|
-
status: ApplicationMemberStatus;
|
|
98
|
-
/**
|
|
99
|
-
* Origin of this membership. When `'workspace'`, the membership is synthetic
|
|
100
|
-
* and derived from the caller's workspace membership rather than a direct
|
|
101
|
-
* application membership (in which case `_id` is `null`). Absent or any other
|
|
102
|
-
* value indicates a direct application membership.
|
|
103
|
-
*/
|
|
104
|
-
source?: 'workspace';
|
|
105
|
-
createdAt: string;
|
|
106
|
-
updatedAt: string;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Client-facing ApplicationCredential shape. The raw secret is NEVER part of
|
|
111
|
-
* this shape — it is returned exactly once, separately, at creation/rotation.
|
|
112
|
-
*/
|
|
113
|
-
export interface ApplicationCredential {
|
|
114
|
-
_id: string;
|
|
115
|
-
applicationId: string;
|
|
116
|
-
name: string;
|
|
117
|
-
publicKey: string;
|
|
118
|
-
type: ApplicationCredentialType;
|
|
119
|
-
environment: ApplicationEnvironment;
|
|
120
|
-
scopes: string[];
|
|
121
|
-
status: ApplicationCredentialStatus;
|
|
122
|
-
lastUsedAt?: string;
|
|
123
|
-
expiresAt?: string;
|
|
124
|
-
/**
|
|
125
|
-
* Audit link to the credential this one was rotated FROM. Populated by the
|
|
126
|
-
* API on credentials created via rotation; absent on original credentials.
|
|
127
|
-
*/
|
|
128
|
-
rotatedFromCredentialId?: string;
|
|
129
|
-
createdByUserId: string;
|
|
130
|
-
createdAt: string;
|
|
131
|
-
updatedAt: string;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Sanitized, PUBLIC application identity returned by the API when resolving a
|
|
136
|
-
* cross-app/OAuth client to a registered {@link Application}.
|
|
137
|
-
*
|
|
138
|
-
* Unlike {@link Application}, this shape carries NO sensitive or membership
|
|
139
|
-
* fields — it is safe to display unauthenticated in consent/authorize screens
|
|
140
|
-
* and device-flow approval UIs. The API resolves a `client_id` (OAuth
|
|
141
|
-
* credential public key) to the owning application and projects only the
|
|
142
|
-
* fields below. `id` is the application's `_id` as a string.
|
|
143
|
-
*/
|
|
144
|
-
export interface PublicApplication {
|
|
145
|
-
/** The application's Mongo `_id` as a string. */
|
|
146
|
-
id: string;
|
|
147
|
-
/** Human-readable application name shown to the user. */
|
|
148
|
-
name: string;
|
|
149
|
-
/** Optional short description of what the application does. */
|
|
150
|
-
description?: string;
|
|
151
|
-
/** Optional icon URL for the application. */
|
|
152
|
-
icon?: string;
|
|
153
|
-
/** Optional public website/homepage URL for the application. */
|
|
154
|
-
websiteUrl?: string;
|
|
155
|
-
/** Application classification (set by Oxy platform staff). */
|
|
156
|
-
type: ApplicationType;
|
|
157
|
-
/** Whether the application is an officially endorsed Oxy application. */
|
|
158
|
-
isOfficial: boolean;
|
|
159
|
-
/** Whether the application is an internal Oxy ecosystem application. */
|
|
160
|
-
isInternal: boolean;
|
|
161
|
-
/** OAuth scopes the application is configured to request. */
|
|
162
|
-
scopes: string[];
|
|
163
|
-
/** Optional display name of the developer/owner organisation. */
|
|
164
|
-
developerName?: string;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* A connected (OAuth-authorized) application from the current user's point of
|
|
169
|
-
* view: an application the user has granted access to via the consent flow.
|
|
170
|
-
*
|
|
171
|
-
* Returned by `GET /auth/grants` and rendered in the user-facing "Connected
|
|
172
|
-
* apps" management surface. Keyed by `applicationId` (the application's Mongo
|
|
173
|
-
* `_id`) rather than a credential/client id, so the grant — and a subsequent
|
|
174
|
-
* {@link OxyServicesApplicationsMixin.revokeAppGrant} — survive credential
|
|
175
|
-
* rotation. This is a display shape: it carries the application's name/logo and
|
|
176
|
-
* the granted scopes, never any membership or credential material.
|
|
177
|
-
*/
|
|
178
|
-
export interface ConnectedApp {
|
|
179
|
-
/** The connected application's Mongo `_id`. Use this to revoke the grant. */
|
|
180
|
-
applicationId: string;
|
|
181
|
-
/** Human-readable application name shown to the user. */
|
|
182
|
-
name: string;
|
|
183
|
-
/** Optional logo URL for the application. */
|
|
184
|
-
logoUrl?: string;
|
|
185
|
-
/** OAuth scopes the user has granted to the application. */
|
|
186
|
-
scopes: string[];
|
|
187
|
-
/** ISO timestamp of when the user first authorized the application. */
|
|
188
|
-
firstGrantedAt: string;
|
|
189
|
-
/** ISO timestamp of when the grant was last exercised. */
|
|
190
|
-
lastUsedAt: string;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/** Input accepted by `createApplication`. Staff-only fields are not settable here. */
|
|
194
|
-
export interface CreateApplicationInput {
|
|
195
|
-
name: string;
|
|
196
|
-
description?: string;
|
|
197
|
-
websiteUrl?: string;
|
|
198
|
-
icon?: string;
|
|
199
|
-
redirectUris?: string[];
|
|
200
|
-
scopes?: string[];
|
|
201
|
-
/**
|
|
202
|
-
* Optional workspace `_id` to create the app in. Omitted → API defaults to
|
|
203
|
-
* the caller's personal workspace.
|
|
204
|
-
*/
|
|
205
|
-
workspaceId?: string;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/** Input accepted by `updateApplication`. Staff-only fields are not settable here. */
|
|
209
|
-
export interface UpdateApplicationInput {
|
|
210
|
-
name?: string;
|
|
211
|
-
description?: string;
|
|
212
|
-
websiteUrl?: string;
|
|
213
|
-
icon?: string;
|
|
214
|
-
redirectUris?: string[];
|
|
215
|
-
scopes?: string[];
|
|
216
|
-
webhookUrl?: string;
|
|
217
|
-
devWebhookUrl?: string;
|
|
218
|
-
status?: ApplicationStatus;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/** Input accepted by `inviteApplicationMember`. The owner role cannot be invited. */
|
|
222
|
-
export interface InviteApplicationMemberInput {
|
|
223
|
-
/**
|
|
224
|
-
* The username or email of the user to invite. Resolved to a user server-side;
|
|
225
|
-
* an unknown value yields a 404 "User not found".
|
|
226
|
-
*/
|
|
227
|
-
usernameOrEmail: string;
|
|
228
|
-
role: Exclude<ApplicationRole, 'owner'>;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/** Input accepted by `updateApplicationMember`. */
|
|
232
|
-
export interface UpdateApplicationMemberInput {
|
|
233
|
-
role: ApplicationRole;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/** Input accepted by `transferApplicationOwnership`. */
|
|
237
|
-
export interface TransferApplicationOwnershipInput {
|
|
238
|
-
userId: string;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/** Input accepted by `createApplicationCredential`. */
|
|
242
|
-
export interface CreateApplicationCredentialInput {
|
|
243
|
-
name: string;
|
|
244
|
-
type: ApplicationCredentialType;
|
|
245
|
-
environment: ApplicationEnvironment;
|
|
246
|
-
scopes?: string[];
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/** Result of creating a credential — `secret` is returned ONCE. */
|
|
250
|
-
export interface ApplicationCredentialWithSecret {
|
|
251
|
-
credential: ApplicationCredential;
|
|
252
|
-
secret: string;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Result of rotating a credential. Extends the create result with audit fields:
|
|
257
|
-
* the new plaintext `secret` is returned ONCE, plus `rotatedFrom` (the previous
|
|
258
|
-
* credential's `credentialId`) and `graceExpiresAt` (ISO string marking when the
|
|
259
|
-
* old credential stops being honoured during the rotation grace window).
|
|
260
|
-
*/
|
|
261
|
-
export interface RotateApplicationCredentialResult extends ApplicationCredentialWithSecret {
|
|
262
|
-
/** The previous credential's `credentialId` that this rotation supersedes. */
|
|
263
|
-
rotatedFrom: string;
|
|
264
|
-
/** ISO timestamp at which the rotated-from credential's grace window ends. */
|
|
265
|
-
graceExpiresAt: string;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/** Time window for application usage statistics. */
|
|
269
|
-
export type ApplicationUsagePeriod = '24h' | '7d' | '30d' | '90d';
|
|
270
|
-
|
|
271
|
-
/** Aggregate totals for an application over the requested period. */
|
|
272
|
-
export interface ApplicationUsageSummary {
|
|
273
|
-
totalRequests: number;
|
|
274
|
-
totalTokens: number;
|
|
275
|
-
totalCredits: number;
|
|
276
|
-
avgResponseTime: number;
|
|
277
|
-
successfulRequests: number;
|
|
278
|
-
errorRequests: number;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/** Per-day usage bucket. `_id` is the day key (e.g. `YYYY-MM-DD`). */
|
|
282
|
-
export interface ApplicationUsageByDay {
|
|
283
|
-
_id: string;
|
|
284
|
-
requests: number;
|
|
285
|
-
tokens: number;
|
|
286
|
-
credits: number;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/** Per-endpoint usage bucket. `_id` is the endpoint identifier. */
|
|
290
|
-
export interface ApplicationUsageByEndpoint {
|
|
291
|
-
_id: string;
|
|
292
|
-
requests: number;
|
|
293
|
-
tokens: number;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/** Usage statistics for an application over a period. */
|
|
297
|
-
export interface ApplicationUsageStats {
|
|
298
|
-
summary: ApplicationUsageSummary;
|
|
299
|
-
byDay: ApplicationUsageByDay[];
|
|
300
|
-
byEndpoint: ApplicationUsageByEndpoint[];
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/** Result of a delete/remove/revoke/transfer operation. */
|
|
304
|
-
export interface ApplicationSuccessResult {
|
|
305
|
-
success: boolean;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
export function OxyServicesApplicationsMixin<T extends typeof OxyServicesBase>(Base: T) {
|
|
309
|
-
return class extends Base {
|
|
310
|
-
constructor(...args: any[]) {
|
|
311
|
-
super(...(args as [any]));
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Resolve an OAuth client identifier to the owning application's PUBLIC
|
|
316
|
-
* identity. No authentication required — the API returns only sanitized,
|
|
317
|
-
* display-safe metadata ({@link PublicApplication}). Use this to render the
|
|
318
|
-
* requesting application's name/icon in consent, authorize, and device-flow
|
|
319
|
-
* approval UIs before any session exists.
|
|
320
|
-
*
|
|
321
|
-
* @param clientId - The OAuth `client_id` (an active credential's public
|
|
322
|
-
* key). URL-encoded before being placed in the path.
|
|
323
|
-
*/
|
|
324
|
-
async getPublicApplication(clientId: string): Promise<PublicApplication> {
|
|
325
|
-
try {
|
|
326
|
-
const res = await this.makeRequest<{ application: PublicApplication }>(
|
|
327
|
-
'GET',
|
|
328
|
-
`/auth/oauth/client/${encodeURIComponent(clientId)}`,
|
|
329
|
-
undefined,
|
|
330
|
-
{ cache: true, cacheTTL: CACHE_TIMES.MEDIUM },
|
|
331
|
-
);
|
|
332
|
-
return res.application;
|
|
333
|
-
} catch (error) {
|
|
334
|
-
throw this.handleError(error);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* List the OAuth-authorized applications the current user has connected —
|
|
340
|
-
* the third-party apps the user granted access to via the consent flow.
|
|
341
|
-
* Each entry is a {@link ConnectedApp} carrying the application's display
|
|
342
|
-
* identity, the granted scopes, and when the grant was first made and last
|
|
343
|
-
* exercised. Requires an authenticated session.
|
|
344
|
-
*
|
|
345
|
-
* Backed by `GET /auth/grants`. The response is briefly cached
|
|
346
|
-
* (identity-scoped); {@link revokeAppGrant} busts that cache so a revoke is
|
|
347
|
-
* reflected on the next read.
|
|
348
|
-
*/
|
|
349
|
-
async listConnectedApps(): Promise<ConnectedApp[]> {
|
|
350
|
-
try {
|
|
351
|
-
return await this.makeRequest<ConnectedApp[]>(
|
|
352
|
-
'GET',
|
|
353
|
-
'/auth/grants',
|
|
354
|
-
undefined,
|
|
355
|
-
{ cache: true, cacheTTL: CACHE_TIMES.SHORT },
|
|
356
|
-
);
|
|
357
|
-
} catch (error) {
|
|
358
|
-
throw this.handleError(error);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Revoke the current user's grant for a connected application, identified by
|
|
364
|
-
* its application `_id` (a {@link ConnectedApp.applicationId}, NOT a
|
|
365
|
-
* credential/client id — keyed by application so the revocation survives
|
|
366
|
-
* credential rotation). After this the application can no longer act on the
|
|
367
|
-
* user's behalf until it is re-authorized.
|
|
368
|
-
*
|
|
369
|
-
* Backed by `DELETE /auth/grants/:applicationId`. On success the cached
|
|
370
|
-
* connected-apps list (`GET:/auth/grants`) is invalidated so the next
|
|
371
|
-
* {@link listConnectedApps} read reflects the removal.
|
|
372
|
-
*
|
|
373
|
-
* @param applicationId - The connected application's Mongo `_id`.
|
|
374
|
-
*/
|
|
375
|
-
async revokeAppGrant(applicationId: string): Promise<void> {
|
|
376
|
-
try {
|
|
377
|
-
await this.makeRequest<{ revoked: boolean }>(
|
|
378
|
-
'DELETE',
|
|
379
|
-
`/auth/grants/${applicationId}`,
|
|
380
|
-
undefined,
|
|
381
|
-
{ cache: false },
|
|
382
|
-
);
|
|
383
|
-
// A revoke removes an entry from the user's connected-apps list; bust
|
|
384
|
-
// the cached `GET /auth/grants` so the next read re-fetches.
|
|
385
|
-
this.clearCacheEntry('GET:/auth/grants');
|
|
386
|
-
} catch (error) {
|
|
387
|
-
throw this.handleError(error);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* List applications the current user is an active member of.
|
|
393
|
-
*
|
|
394
|
-
* @param workspaceId - Optional workspace `_id` to scope the listing to
|
|
395
|
-
* applications belonging to that workspace. When provided it is appended
|
|
396
|
-
* as a `workspaceId` query parameter (URL-encoded). The query string is
|
|
397
|
-
* part of the request path, so the response cache keys on it
|
|
398
|
-
* automatically — scoped and unscoped lists never collide.
|
|
399
|
-
*/
|
|
400
|
-
async getApplications(workspaceId?: string): Promise<Application[]> {
|
|
401
|
-
try {
|
|
402
|
-
const path = workspaceId
|
|
403
|
-
? `/applications?workspaceId=${encodeURIComponent(workspaceId)}`
|
|
404
|
-
: '/applications';
|
|
405
|
-
const res = await this.makeRequest<{ applications?: Application[] }>(
|
|
406
|
-
'GET',
|
|
407
|
-
path,
|
|
408
|
-
undefined,
|
|
409
|
-
{ cache: true, cacheTTL: CACHE_TIMES.MEDIUM },
|
|
410
|
-
);
|
|
411
|
-
return res.applications ?? [];
|
|
412
|
-
} catch (error) {
|
|
413
|
-
throw this.handleError(error);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Create a new application. The caller becomes its `owner`.
|
|
419
|
-
* @param data - Application configuration. Staff-only fields are ignored.
|
|
420
|
-
*/
|
|
421
|
-
async createApplication(data: CreateApplicationInput): Promise<Application> {
|
|
422
|
-
try {
|
|
423
|
-
const res = await this.makeRequest<{ application: Application }>(
|
|
424
|
-
'POST',
|
|
425
|
-
'/applications',
|
|
426
|
-
data,
|
|
427
|
-
{ cache: false },
|
|
428
|
-
);
|
|
429
|
-
// Bust every cached application list (unscoped + per-workspace) so the
|
|
430
|
-
// new application appears on the next `getApplications()` read.
|
|
431
|
-
this._invalidateApplicationLists();
|
|
432
|
-
return res.application;
|
|
433
|
-
} catch (error) {
|
|
434
|
-
throw this.handleError(error);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Fetch a single application by id.
|
|
440
|
-
* @param applicationId - The application's Mongo `_id`.
|
|
441
|
-
*/
|
|
442
|
-
async getApplication(applicationId: string): Promise<Application> {
|
|
443
|
-
try {
|
|
444
|
-
const res = await this.makeRequest<{ application: Application }>(
|
|
445
|
-
'GET',
|
|
446
|
-
`/applications/${applicationId}`,
|
|
447
|
-
undefined,
|
|
448
|
-
{ cache: true, cacheTTL: CACHE_TIMES.LONG },
|
|
449
|
-
);
|
|
450
|
-
return res.application;
|
|
451
|
-
} catch (error) {
|
|
452
|
-
throw this.handleError(error);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* Update an application's mutable fields.
|
|
458
|
-
* @param applicationId - The application's Mongo `_id`.
|
|
459
|
-
* @param data - Subset of updatable fields. Staff-only fields are ignored.
|
|
460
|
-
*/
|
|
461
|
-
async updateApplication(
|
|
462
|
-
applicationId: string,
|
|
463
|
-
data: UpdateApplicationInput,
|
|
464
|
-
): Promise<Application> {
|
|
465
|
-
try {
|
|
466
|
-
const res = await this.makeRequest<{ application: Application }>(
|
|
467
|
-
'PATCH',
|
|
468
|
-
`/applications/${applicationId}`,
|
|
469
|
-
data,
|
|
470
|
-
{ cache: false },
|
|
471
|
-
);
|
|
472
|
-
// Bust the cached detail and every list (which embeds application
|
|
473
|
-
// fields) so neither serves the pre-update snapshot.
|
|
474
|
-
this.clearCacheEntry(`GET:/applications/${applicationId}`);
|
|
475
|
-
this._invalidateApplicationLists();
|
|
476
|
-
return res.application;
|
|
477
|
-
} catch (error) {
|
|
478
|
-
throw this.handleError(error);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* Soft-delete an application (owner only).
|
|
484
|
-
* @param applicationId - The application's Mongo `_id`.
|
|
485
|
-
*/
|
|
486
|
-
async deleteApplication(applicationId: string): Promise<ApplicationSuccessResult> {
|
|
487
|
-
try {
|
|
488
|
-
const result = await this.makeRequest<ApplicationSuccessResult>(
|
|
489
|
-
'DELETE',
|
|
490
|
-
`/applications/${applicationId}`,
|
|
491
|
-
undefined,
|
|
492
|
-
{ cache: false },
|
|
493
|
-
);
|
|
494
|
-
// Bust every cached representation of the deleted application.
|
|
495
|
-
this.clearCacheEntry(`GET:/applications/${applicationId}`);
|
|
496
|
-
this.clearCacheEntry(`GET:/applications/${applicationId}/members`);
|
|
497
|
-
this.clearCacheEntry(`GET:/applications/${applicationId}/credentials`);
|
|
498
|
-
this._invalidateApplicationLists();
|
|
499
|
-
return result;
|
|
500
|
-
} catch (error) {
|
|
501
|
-
throw this.handleError(error);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* List members of an application.
|
|
507
|
-
* @param applicationId - The application's Mongo `_id`.
|
|
508
|
-
*/
|
|
509
|
-
async getApplicationMembers(applicationId: string): Promise<ApplicationMember[]> {
|
|
510
|
-
try {
|
|
511
|
-
const res = await this.makeRequest<{ members?: ApplicationMember[] }>(
|
|
512
|
-
'GET',
|
|
513
|
-
`/applications/${applicationId}/members`,
|
|
514
|
-
undefined,
|
|
515
|
-
{ cache: true, cacheTTL: CACHE_TIMES.MEDIUM },
|
|
516
|
-
);
|
|
517
|
-
return res.members ?? [];
|
|
518
|
-
} catch (error) {
|
|
519
|
-
throw this.handleError(error);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Add a member to an application.
|
|
525
|
-
* @param applicationId - The application's Mongo `_id`.
|
|
526
|
-
* @param data - Target user's username or email and role (never `owner`).
|
|
527
|
-
* The server resolves `usernameOrEmail` to a user; an unknown value yields
|
|
528
|
-
* a 404 "User not found".
|
|
529
|
-
*/
|
|
530
|
-
async inviteApplicationMember(
|
|
531
|
-
applicationId: string,
|
|
532
|
-
data: InviteApplicationMemberInput,
|
|
533
|
-
): Promise<ApplicationMember> {
|
|
534
|
-
try {
|
|
535
|
-
const res = await this.makeRequest<{ member: ApplicationMember }>(
|
|
536
|
-
'POST',
|
|
537
|
-
`/applications/${applicationId}/members`,
|
|
538
|
-
data,
|
|
539
|
-
{ cache: false },
|
|
540
|
-
);
|
|
541
|
-
this._invalidateApplicationMembership(applicationId);
|
|
542
|
-
return res.member;
|
|
543
|
-
} catch (error) {
|
|
544
|
-
throw this.handleError(error);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
/**
|
|
549
|
-
* Change a member's role.
|
|
550
|
-
* @param applicationId - The application's Mongo `_id`.
|
|
551
|
-
* @param memberId - The member's Mongo `_id`.
|
|
552
|
-
* @param data - New role.
|
|
553
|
-
*/
|
|
554
|
-
async updateApplicationMember(
|
|
555
|
-
applicationId: string,
|
|
556
|
-
memberId: string,
|
|
557
|
-
data: UpdateApplicationMemberInput,
|
|
558
|
-
): Promise<ApplicationMember> {
|
|
559
|
-
try {
|
|
560
|
-
const res = await this.makeRequest<{ member: ApplicationMember }>(
|
|
561
|
-
'PATCH',
|
|
562
|
-
`/applications/${applicationId}/members/${memberId}`,
|
|
563
|
-
data,
|
|
564
|
-
{ cache: false },
|
|
565
|
-
);
|
|
566
|
-
this._invalidateApplicationMembership(applicationId);
|
|
567
|
-
return res.member;
|
|
568
|
-
} catch (error) {
|
|
569
|
-
throw this.handleError(error);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
/**
|
|
574
|
-
* Remove a member from an application.
|
|
575
|
-
* @param applicationId - The application's Mongo `_id`.
|
|
576
|
-
* @param memberId - The member's Mongo `_id`.
|
|
577
|
-
*/
|
|
578
|
-
async removeApplicationMember(
|
|
579
|
-
applicationId: string,
|
|
580
|
-
memberId: string,
|
|
581
|
-
): Promise<ApplicationSuccessResult> {
|
|
582
|
-
try {
|
|
583
|
-
const result = await this.makeRequest<ApplicationSuccessResult>(
|
|
584
|
-
'DELETE',
|
|
585
|
-
`/applications/${applicationId}/members/${memberId}`,
|
|
586
|
-
undefined,
|
|
587
|
-
{ cache: false },
|
|
588
|
-
);
|
|
589
|
-
this._invalidateApplicationMembership(applicationId);
|
|
590
|
-
return result;
|
|
591
|
-
} catch (error) {
|
|
592
|
-
throw this.handleError(error);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
/**
|
|
597
|
-
* Transfer ownership of an application to another member (owner only).
|
|
598
|
-
* Demotes the current owner to `admin` and promotes the target to `owner`.
|
|
599
|
-
* @param applicationId - The application's Mongo `_id`.
|
|
600
|
-
* @param data - Target user id.
|
|
601
|
-
*/
|
|
602
|
-
async transferApplicationOwnership(
|
|
603
|
-
applicationId: string,
|
|
604
|
-
data: TransferApplicationOwnershipInput,
|
|
605
|
-
): Promise<ApplicationSuccessResult> {
|
|
606
|
-
try {
|
|
607
|
-
const result = await this.makeRequest<ApplicationSuccessResult>(
|
|
608
|
-
'POST',
|
|
609
|
-
`/applications/${applicationId}/transfer-ownership`,
|
|
610
|
-
data,
|
|
611
|
-
{ cache: false },
|
|
612
|
-
);
|
|
613
|
-
// Ownership change alters roles in the member list AND the detail, and
|
|
614
|
-
// can change which applications the caller "owns" in the list view.
|
|
615
|
-
this._invalidateApplicationMembership(applicationId);
|
|
616
|
-
this._invalidateApplicationLists();
|
|
617
|
-
return result;
|
|
618
|
-
} catch (error) {
|
|
619
|
-
throw this.handleError(error);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
/**
|
|
624
|
-
* List an application's credentials. The response NEVER includes secrets.
|
|
625
|
-
* @param applicationId - The application's Mongo `_id`.
|
|
626
|
-
*/
|
|
627
|
-
async getApplicationCredentials(applicationId: string): Promise<ApplicationCredential[]> {
|
|
628
|
-
try {
|
|
629
|
-
const res = await this.makeRequest<{ credentials?: ApplicationCredential[] }>(
|
|
630
|
-
'GET',
|
|
631
|
-
`/applications/${applicationId}/credentials`,
|
|
632
|
-
undefined,
|
|
633
|
-
{ cache: true, cacheTTL: CACHE_TIMES.MEDIUM },
|
|
634
|
-
);
|
|
635
|
-
return res.credentials ?? [];
|
|
636
|
-
} catch (error) {
|
|
637
|
-
throw this.handleError(error);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
* Create a credential. The plaintext `secret` is returned exactly ONCE;
|
|
643
|
-
* the server stores only a hash and will never return it again.
|
|
644
|
-
* @param applicationId - The application's Mongo `_id`.
|
|
645
|
-
* @param data - Credential configuration.
|
|
646
|
-
*/
|
|
647
|
-
async createApplicationCredential(
|
|
648
|
-
applicationId: string,
|
|
649
|
-
data: CreateApplicationCredentialInput,
|
|
650
|
-
): Promise<ApplicationCredentialWithSecret> {
|
|
651
|
-
try {
|
|
652
|
-
const result = await this.makeRequest<ApplicationCredentialWithSecret>(
|
|
653
|
-
'POST',
|
|
654
|
-
`/applications/${applicationId}/credentials`,
|
|
655
|
-
data,
|
|
656
|
-
{ cache: false },
|
|
657
|
-
);
|
|
658
|
-
this.clearCacheEntry(`GET:/applications/${applicationId}/credentials`);
|
|
659
|
-
return result;
|
|
660
|
-
} catch (error) {
|
|
661
|
-
throw this.handleError(error);
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
/**
|
|
666
|
-
* Rotate a credential's secret. The new plaintext `secret` is returned
|
|
667
|
-
* exactly ONCE, along with audit fields: `rotatedFrom` (the previous
|
|
668
|
-
* credentialId) and `graceExpiresAt` (ISO string for the grace window during
|
|
669
|
-
* which the old credential is still honoured).
|
|
670
|
-
* @param applicationId - The application's Mongo `_id`.
|
|
671
|
-
* @param credentialId - The credential's Mongo `_id`.
|
|
672
|
-
*/
|
|
673
|
-
async rotateApplicationCredential(
|
|
674
|
-
applicationId: string,
|
|
675
|
-
credentialId: string,
|
|
676
|
-
): Promise<RotateApplicationCredentialResult> {
|
|
677
|
-
try {
|
|
678
|
-
const result = await this.makeRequest<RotateApplicationCredentialResult>(
|
|
679
|
-
'POST',
|
|
680
|
-
`/applications/${applicationId}/credentials/${credentialId}/rotate`,
|
|
681
|
-
undefined,
|
|
682
|
-
{ cache: false },
|
|
683
|
-
);
|
|
684
|
-
// Rotation changes credential status/audit fields surfaced by the
|
|
685
|
-
// credentials list (`rotatedFrom`, grace window, new active credential).
|
|
686
|
-
this.clearCacheEntry(`GET:/applications/${applicationId}/credentials`);
|
|
687
|
-
return result;
|
|
688
|
-
} catch (error) {
|
|
689
|
-
throw this.handleError(error);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
/**
|
|
694
|
-
* Revoke a credential (`status='revoked'`). Revoked credentials can no
|
|
695
|
-
* longer authenticate.
|
|
696
|
-
* @param applicationId - The application's Mongo `_id`.
|
|
697
|
-
* @param credentialId - The credential's Mongo `_id`.
|
|
698
|
-
*/
|
|
699
|
-
async revokeApplicationCredential(
|
|
700
|
-
applicationId: string,
|
|
701
|
-
credentialId: string,
|
|
702
|
-
): Promise<ApplicationSuccessResult> {
|
|
703
|
-
try {
|
|
704
|
-
const result = await this.makeRequest<ApplicationSuccessResult>(
|
|
705
|
-
'DELETE',
|
|
706
|
-
`/applications/${applicationId}/credentials/${credentialId}`,
|
|
707
|
-
undefined,
|
|
708
|
-
{ cache: false },
|
|
709
|
-
);
|
|
710
|
-
// Revocation flips the credential's status in the cached list.
|
|
711
|
-
this.clearCacheEntry(`GET:/applications/${applicationId}/credentials`);
|
|
712
|
-
return result;
|
|
713
|
-
} catch (error) {
|
|
714
|
-
throw this.handleError(error);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
/**
|
|
719
|
-
* Fetch usage statistics for an application.
|
|
720
|
-
* @param applicationId - The application's Mongo `_id`.
|
|
721
|
-
* @param period - Time window (defaults to the server default).
|
|
722
|
-
*/
|
|
723
|
-
async getApplicationUsage(
|
|
724
|
-
applicationId: string,
|
|
725
|
-
period?: ApplicationUsagePeriod,
|
|
726
|
-
): Promise<ApplicationUsageStats> {
|
|
727
|
-
try {
|
|
728
|
-
return await this.makeRequest<ApplicationUsageStats>(
|
|
729
|
-
'GET',
|
|
730
|
-
`/applications/${applicationId}/usage`,
|
|
731
|
-
period ? { period } : undefined,
|
|
732
|
-
{ cache: true, cacheTTL: CACHE_TIMES.SHORT },
|
|
733
|
-
);
|
|
734
|
-
} catch (error) {
|
|
735
|
-
throw this.handleError(error);
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
/**
|
|
740
|
-
* Bust every cached application list. `getApplications(workspaceId?)` keys
|
|
741
|
-
* the unscoped list as `GET:/applications` and each workspace-scoped list as
|
|
742
|
-
* `GET:/applications?workspaceId=<id>` (the query string is part of the URL
|
|
743
|
-
* path). A change to list membership (create/delete/ownership transfer)
|
|
744
|
-
* invalidates all of them, so we clear the unscoped entry plus every
|
|
745
|
-
* `?workspaceId=` variant via a prefix sweep. The prefix `GET:/applications?`
|
|
746
|
-
* matches only the query-string list variants, never the `GET:/applications/<id>…`
|
|
747
|
-
* detail/sub-resource keys.
|
|
748
|
-
*
|
|
749
|
-
* Internal helper (leading underscore); not part of the supported public
|
|
750
|
-
* surface. Public rather than `private` because mixins compose into an
|
|
751
|
-
* exported anonymous class, where TypeScript cannot represent a private
|
|
752
|
-
* member in the emitted declaration file (TS4094).
|
|
753
|
-
*/
|
|
754
|
-
_invalidateApplicationLists(): void {
|
|
755
|
-
this.clearCacheEntry('GET:/applications');
|
|
756
|
-
this.clearCacheByPrefix('GET:/applications?');
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
/**
|
|
760
|
-
* Bust the cached member list and detail for an application after a
|
|
761
|
-
* membership mutation. The member list (`getApplicationMembers`) and the
|
|
762
|
-
* detail (`getApplication`, which can embed member counts) both go stale
|
|
763
|
-
* when the member set or a member's role changes.
|
|
764
|
-
*
|
|
765
|
-
* Internal helper (leading underscore); see `_invalidateApplicationLists`
|
|
766
|
-
* for why this is public rather than `private`.
|
|
767
|
-
*/
|
|
768
|
-
_invalidateApplicationMembership(applicationId: string): void {
|
|
769
|
-
this.clearCacheEntry(`GET:/applications/${applicationId}/members`);
|
|
770
|
-
this.clearCacheEntry(`GET:/applications/${applicationId}`);
|
|
771
|
-
}
|
|
772
|
-
};
|
|
773
|
-
}
|