@oxyhq/core 3.18.0 → 4.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.
Files changed (42) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/OxyServices.js +3 -2
  3. package/dist/cjs/mixins/OxyServices.accounts.js +480 -0
  4. package/dist/cjs/mixins/OxyServices.connectedApps.js +73 -0
  5. package/dist/cjs/mixins/OxyServices.utility.js +3 -2
  6. package/dist/cjs/mixins/index.js +9 -6
  7. package/dist/esm/.tsbuildinfo +1 -1
  8. package/dist/esm/OxyServices.js +3 -2
  9. package/dist/esm/mixins/OxyServices.accounts.js +477 -0
  10. package/dist/esm/mixins/OxyServices.connectedApps.js +70 -0
  11. package/dist/esm/mixins/OxyServices.utility.js +3 -2
  12. package/dist/esm/mixins/index.js +9 -6
  13. package/dist/types/.tsbuildinfo +1 -1
  14. package/dist/types/OxyServices.d.ts +3 -2
  15. package/dist/types/index.d.ts +2 -3
  16. package/dist/types/mixins/OxyServices.accounts.d.ts +642 -0
  17. package/dist/types/mixins/OxyServices.auth.d.ts +1 -1
  18. package/dist/types/mixins/OxyServices.connectedApps.d.ts +168 -0
  19. package/dist/types/mixins/OxyServices.utility.d.ts +6 -3
  20. package/dist/types/mixins/index.d.ts +3 -4
  21. package/package.json +2 -2
  22. package/src/OxyServices.ts +3 -2
  23. package/src/index.ts +33 -34
  24. package/src/mixins/OxyServices.accounts.ts +1079 -0
  25. package/src/mixins/OxyServices.auth.ts +1 -1
  26. package/src/mixins/OxyServices.connectedApps.ts +165 -0
  27. package/src/mixins/OxyServices.utility.ts +7 -4
  28. package/src/mixins/__tests__/accounts.test.ts +667 -0
  29. package/src/mixins/__tests__/connectedApps.test.ts +1 -1
  30. package/src/mixins/index.ts +11 -9
  31. package/dist/cjs/mixins/OxyServices.applications.js +0 -350
  32. package/dist/cjs/mixins/OxyServices.managedAccounts.js +0 -143
  33. package/dist/cjs/mixins/OxyServices.workspaces.js +0 -181
  34. package/dist/esm/mixins/OxyServices.applications.js +0 -347
  35. package/dist/esm/mixins/OxyServices.managedAccounts.js +0 -140
  36. package/dist/esm/mixins/OxyServices.workspaces.js +0 -178
  37. package/dist/types/mixins/OxyServices.applications.d.ts +0 -496
  38. package/dist/types/mixins/OxyServices.managedAccounts.d.ts +0 -145
  39. package/dist/types/mixins/OxyServices.workspaces.d.ts +0 -219
  40. package/src/mixins/OxyServices.applications.ts +0 -773
  41. package/src/mixins/OxyServices.managedAccounts.ts +0 -173
  42. package/src/mixins/OxyServices.workspaces.ts +0 -351
@@ -25,8 +25,9 @@ const mixins_1 = require("./mixins");
25
25
  * - **Payment**: Payment processing
26
26
  * - **Reputation**: Reputation system (Oxy Trust)
27
27
  * - **Assets**: File upload and asset management
28
- * - **Applications**: Application, membership, and credential management
29
- * - **Workspaces**: Workspace and membership management
28
+ * - **Accounts**: Unified account graph (tree, members, roles, bot credentials)
29
+ * and the applications owned within it (Application = OAuth client)
30
+ * - **Connected apps**: OAuth-consent surface (public app identity, grants)
30
31
  * - **Location**: Location-based features
31
32
  * - **Analytics**: Analytics tracking
32
33
  * - **Devices**: Device management
@@ -0,0 +1,480 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OxyServicesAccountsMixin = OxyServicesAccountsMixin;
4
+ const mixinHelpers_1 = require("./mixinHelpers");
5
+ function OxyServicesAccountsMixin(Base) {
6
+ return class extends Base {
7
+ constructor(...args) {
8
+ super(...args);
9
+ }
10
+ // =========================================================================
11
+ // Accounts
12
+ // =========================================================================
13
+ /**
14
+ * List the accounts the caller can access: their own personal (root)
15
+ * account, accounts they own, and accounts shared with them (including
16
+ * external organisations), plus the reachable subtree of each.
17
+ *
18
+ * @param opts - `{ tree: true }` requests the nested tree representation
19
+ * (`children` populated) instead of a flat list. The flag is appended to
20
+ * the path as `?tree=true`, so the response cache keys on it automatically —
21
+ * the flat and tree variants never collide.
22
+ */
23
+ async listAccounts(opts) {
24
+ try {
25
+ const path = opts?.tree ? '/accounts?tree=true' : '/accounts';
26
+ const res = await this.makeRequest('GET', path, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
27
+ return res.accounts ?? [];
28
+ }
29
+ catch (error) {
30
+ throw this.handleError(error);
31
+ }
32
+ }
33
+ /**
34
+ * Fetch a single account node by id.
35
+ * @param accountId - The account's Mongo `_id`.
36
+ */
37
+ async getAccount(accountId) {
38
+ try {
39
+ const res = await this.makeRequest('GET', `/accounts/${encodeURIComponent(accountId)}`, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.LONG });
40
+ return res.account;
41
+ }
42
+ catch (error) {
43
+ throw this.handleError(error);
44
+ }
45
+ }
46
+ /**
47
+ * Create a new (non-personal) account. The caller becomes its `owner`.
48
+ * @param data - Account configuration: kind, optional parent, and profile.
49
+ */
50
+ async createAccount(data) {
51
+ try {
52
+ const res = await this.makeRequest('POST', '/accounts', data, { cache: false });
53
+ // A new account changes the accessible forest — bust every cached list
54
+ // (flat + tree) so it appears on the next `listAccounts()` read.
55
+ this._invalidateAccountLists();
56
+ return res.account;
57
+ }
58
+ catch (error) {
59
+ throw this.handleError(error);
60
+ }
61
+ }
62
+ /**
63
+ * Update an account's mutable profile fields. Tree placement changes
64
+ * (reparenting) go through the dedicated move endpoint, not here.
65
+ * @param accountId - The account's Mongo `_id`.
66
+ * @param data - Subset of updatable profile fields.
67
+ */
68
+ async updateAccount(accountId, data) {
69
+ try {
70
+ const res = await this.makeRequest('PATCH', `/accounts/${encodeURIComponent(accountId)}`, data, { cache: false });
71
+ // Bust the cached detail and every list (which embeds account profile
72
+ // data) so neither serves the pre-update snapshot.
73
+ this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}`);
74
+ this._invalidateAccountLists();
75
+ return res.account;
76
+ }
77
+ catch (error) {
78
+ throw this.handleError(error);
79
+ }
80
+ }
81
+ /**
82
+ * Archive an account (soft delete). Named `archiveAccount` — NOT
83
+ * `deleteAccount`, which is reserved for the GDPR self-deletion flow on the
84
+ * user mixin (`OxyServices.user.ts`).
85
+ * @param accountId - The account's Mongo `_id`.
86
+ */
87
+ async archiveAccount(accountId) {
88
+ try {
89
+ const result = await this.makeRequest('DELETE', `/accounts/${encodeURIComponent(accountId)}`, undefined, { cache: false });
90
+ // Bust every cached representation of the archived account.
91
+ this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}`);
92
+ this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}/members`);
93
+ this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}/credentials`);
94
+ this._invalidateAccountLists();
95
+ return result;
96
+ }
97
+ catch (error) {
98
+ throw this.handleError(error);
99
+ }
100
+ }
101
+ /**
102
+ * List the direct child accounts of an account.
103
+ * @param accountId - The parent account's Mongo `_id`.
104
+ */
105
+ async listChildAccounts(accountId) {
106
+ try {
107
+ const res = await this.makeRequest('GET', `/accounts/${encodeURIComponent(accountId)}/children`, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
108
+ return res.accounts ?? [];
109
+ }
110
+ catch (error) {
111
+ throw this.handleError(error);
112
+ }
113
+ }
114
+ // =========================================================================
115
+ // Account members
116
+ // =========================================================================
117
+ /**
118
+ * List members of an account (direct membership rows on the account).
119
+ * @param accountId - The account's Mongo `_id`.
120
+ */
121
+ async listAccountMembers(accountId) {
122
+ try {
123
+ const res = await this.makeRequest('GET', `/accounts/${encodeURIComponent(accountId)}/members`, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
124
+ return res.members ?? [];
125
+ }
126
+ catch (error) {
127
+ throw this.handleError(error);
128
+ }
129
+ }
130
+ /**
131
+ * Add a member to an account.
132
+ * @param accountId - The account's Mongo `_id`.
133
+ * @param data - Target user's username or email and role (never `owner`).
134
+ * The server resolves `usernameOrEmail` to a personal account; an unknown
135
+ * value yields a 404 "User not found".
136
+ */
137
+ async inviteAccountMember(accountId, data) {
138
+ try {
139
+ const res = await this.makeRequest('POST', `/accounts/${encodeURIComponent(accountId)}/members`, data, { cache: false });
140
+ this._invalidateAccountMembership(accountId);
141
+ return res.member;
142
+ }
143
+ catch (error) {
144
+ throw this.handleError(error);
145
+ }
146
+ }
147
+ /**
148
+ * Change a member's role.
149
+ * @param accountId - The account's Mongo `_id`.
150
+ * @param memberId - The member's Mongo `_id`.
151
+ * @param data - New role (never `owner`).
152
+ */
153
+ async updateAccountMember(accountId, memberId, data) {
154
+ try {
155
+ const res = await this.makeRequest('PATCH', `/accounts/${encodeURIComponent(accountId)}/members/${encodeURIComponent(memberId)}`, data, { cache: false });
156
+ this._invalidateAccountMembership(accountId);
157
+ return res.member;
158
+ }
159
+ catch (error) {
160
+ throw this.handleError(error);
161
+ }
162
+ }
163
+ /**
164
+ * Remove a member from an account.
165
+ * @param accountId - The account's Mongo `_id`.
166
+ * @param memberId - The member's Mongo `_id`.
167
+ */
168
+ async removeAccountMember(accountId, memberId) {
169
+ try {
170
+ const result = await this.makeRequest('DELETE', `/accounts/${encodeURIComponent(accountId)}/members/${encodeURIComponent(memberId)}`, undefined, { cache: false });
171
+ this._invalidateAccountMembership(accountId);
172
+ return result;
173
+ }
174
+ catch (error) {
175
+ throw this.handleError(error);
176
+ }
177
+ }
178
+ /**
179
+ * Transfer ownership of an account to another member (owner only).
180
+ * @param accountId - The account's Mongo `_id`.
181
+ * @param data - Target user id.
182
+ */
183
+ async transferAccountOwnership(accountId, data) {
184
+ try {
185
+ const result = await this.makeRequest('POST', `/accounts/${encodeURIComponent(accountId)}/transfer-ownership`, data, { cache: false });
186
+ // Ownership change alters roles in the member list AND the detail, and
187
+ // can change which accounts the caller "owns" in the list view.
188
+ this._invalidateAccountMembership(accountId);
189
+ this._invalidateAccountLists();
190
+ return result;
191
+ }
192
+ catch (error) {
193
+ throw this.handleError(error);
194
+ }
195
+ }
196
+ // =========================================================================
197
+ // Bot (account) service credentials — /accounts/:id/credentials
198
+ // =========================================================================
199
+ /**
200
+ * List a bot account's service credentials. The response NEVER includes
201
+ * secrets.
202
+ * @param accountId - The account's Mongo `_id`.
203
+ */
204
+ async listAccountCredentials(accountId) {
205
+ try {
206
+ const res = await this.makeRequest('GET', `/accounts/${encodeURIComponent(accountId)}/credentials`, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
207
+ return res.credentials ?? [];
208
+ }
209
+ catch (error) {
210
+ throw this.handleError(error);
211
+ }
212
+ }
213
+ /**
214
+ * Create a service credential for a bot account. The plaintext `secret` is
215
+ * returned exactly ONCE; the server stores only a hash and will never return
216
+ * it again.
217
+ * @param accountId - The account's Mongo `_id`.
218
+ * @param data - Credential configuration (`type` is always `service`).
219
+ */
220
+ async createAccountCredential(accountId, data) {
221
+ try {
222
+ const result = await this.makeRequest('POST', `/accounts/${encodeURIComponent(accountId)}/credentials`, data, { cache: false });
223
+ this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}/credentials`);
224
+ return result;
225
+ }
226
+ catch (error) {
227
+ throw this.handleError(error);
228
+ }
229
+ }
230
+ /**
231
+ * Rotate a bot credential's secret. The new plaintext `secret` is returned
232
+ * exactly ONCE, along with audit fields: `rotatedFrom` (the previous
233
+ * credentialId) and `graceExpiresAt` (ISO string for the grace window during
234
+ * which the old credential is still honoured).
235
+ * @param accountId - The account's Mongo `_id`.
236
+ * @param credentialId - The credential's Mongo `_id`.
237
+ */
238
+ async rotateAccountCredential(accountId, credentialId) {
239
+ try {
240
+ const result = await this.makeRequest('POST', `/accounts/${encodeURIComponent(accountId)}/credentials/${encodeURIComponent(credentialId)}/rotate`, undefined, { cache: false });
241
+ // Rotation changes credential status/audit fields surfaced by the list.
242
+ this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}/credentials`);
243
+ return result;
244
+ }
245
+ catch (error) {
246
+ throw this.handleError(error);
247
+ }
248
+ }
249
+ /**
250
+ * Revoke a bot credential (`status='revoked'`). Revoked credentials can no
251
+ * longer authenticate.
252
+ * @param accountId - The account's Mongo `_id`.
253
+ * @param credentialId - The credential's Mongo `_id`.
254
+ */
255
+ async revokeAccountCredential(accountId, credentialId) {
256
+ try {
257
+ const result = await this.makeRequest('DELETE', `/accounts/${encodeURIComponent(accountId)}/credentials/${encodeURIComponent(credentialId)}`, undefined, { cache: false });
258
+ // Revocation flips the credential's status in the cached list.
259
+ this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}/credentials`);
260
+ return result;
261
+ }
262
+ catch (error) {
263
+ throw this.handleError(error);
264
+ }
265
+ }
266
+ // =========================================================================
267
+ // Applications owned by an account — /applications
268
+ // =========================================================================
269
+ /**
270
+ * List the applications owned by an account. Backed by
271
+ * `GET /applications?ownerAccountId=<id>`.
272
+ * @param accountId - The owning account's Mongo `_id`.
273
+ */
274
+ async listAccountApps(accountId) {
275
+ try {
276
+ const res = await this.makeRequest('GET', `/applications?ownerAccountId=${encodeURIComponent(accountId)}`, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
277
+ return res.applications ?? [];
278
+ }
279
+ catch (error) {
280
+ throw this.handleError(error);
281
+ }
282
+ }
283
+ /**
284
+ * Create a new application owned by an account.
285
+ * @param data - Application configuration. `ownerAccountId` defaults to the
286
+ * caller's personal account when omitted. Staff-only fields are ignored.
287
+ */
288
+ async createApp(data) {
289
+ try {
290
+ const res = await this.makeRequest('POST', '/applications', data, { cache: false });
291
+ // Bust every cached application list (per owning account) so the new app
292
+ // appears on the next `listAccountApps()` read.
293
+ this._invalidateAppLists();
294
+ return res.application;
295
+ }
296
+ catch (error) {
297
+ throw this.handleError(error);
298
+ }
299
+ }
300
+ /**
301
+ * Fetch a single application by id.
302
+ * @param applicationId - The application's Mongo `_id`.
303
+ */
304
+ async getApp(applicationId) {
305
+ try {
306
+ const res = await this.makeRequest('GET', `/applications/${encodeURIComponent(applicationId)}`, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.LONG });
307
+ return res.application;
308
+ }
309
+ catch (error) {
310
+ throw this.handleError(error);
311
+ }
312
+ }
313
+ /**
314
+ * Update an application's mutable fields.
315
+ * @param applicationId - The application's Mongo `_id`.
316
+ * @param data - Subset of updatable fields. Staff-only fields are ignored.
317
+ */
318
+ async updateApp(applicationId, data) {
319
+ try {
320
+ const res = await this.makeRequest('PATCH', `/applications/${encodeURIComponent(applicationId)}`, data, { cache: false });
321
+ // Bust the cached detail and every list (which embeds application fields).
322
+ this.clearCacheEntry(`GET:/applications/${encodeURIComponent(applicationId)}`);
323
+ this._invalidateAppLists();
324
+ return res.application;
325
+ }
326
+ catch (error) {
327
+ throw this.handleError(error);
328
+ }
329
+ }
330
+ /**
331
+ * Soft-delete an application.
332
+ * @param applicationId - The application's Mongo `_id`.
333
+ */
334
+ async deleteApp(applicationId) {
335
+ try {
336
+ const result = await this.makeRequest('DELETE', `/applications/${encodeURIComponent(applicationId)}`, undefined, { cache: false });
337
+ // Bust every cached representation of the deleted application.
338
+ this.clearCacheEntry(`GET:/applications/${encodeURIComponent(applicationId)}`);
339
+ this.clearCacheEntry(`GET:/applications/${encodeURIComponent(applicationId)}/credentials`);
340
+ this._invalidateAppLists();
341
+ return result;
342
+ }
343
+ catch (error) {
344
+ throw this.handleError(error);
345
+ }
346
+ }
347
+ // =========================================================================
348
+ // Application OAuth credentials — /applications/:appId/credentials
349
+ // =========================================================================
350
+ /**
351
+ * List an application's OAuth credentials. The response NEVER includes
352
+ * secrets.
353
+ * @param applicationId - The application's Mongo `_id`.
354
+ */
355
+ async listAppCredentials(applicationId) {
356
+ try {
357
+ const res = await this.makeRequest('GET', `/applications/${encodeURIComponent(applicationId)}/credentials`, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
358
+ return res.credentials ?? [];
359
+ }
360
+ catch (error) {
361
+ throw this.handleError(error);
362
+ }
363
+ }
364
+ /**
365
+ * Create an application credential. The plaintext `secret` is returned
366
+ * exactly ONCE; the server stores only a hash and will never return it again.
367
+ * @param applicationId - The application's Mongo `_id`.
368
+ * @param data - Credential configuration.
369
+ */
370
+ async createAppCredential(applicationId, data) {
371
+ try {
372
+ const result = await this.makeRequest('POST', `/applications/${encodeURIComponent(applicationId)}/credentials`, data, { cache: false });
373
+ this.clearCacheEntry(`GET:/applications/${encodeURIComponent(applicationId)}/credentials`);
374
+ return result;
375
+ }
376
+ catch (error) {
377
+ throw this.handleError(error);
378
+ }
379
+ }
380
+ /**
381
+ * Rotate an application credential's secret. The new plaintext `secret` is
382
+ * returned exactly ONCE, along with audit fields: `rotatedFrom` (the previous
383
+ * credentialId) and `graceExpiresAt` (ISO string for the grace window during
384
+ * which the old credential is still honoured).
385
+ * @param applicationId - The application's Mongo `_id`.
386
+ * @param credentialId - The credential's Mongo `_id`.
387
+ */
388
+ async rotateAppCredential(applicationId, credentialId) {
389
+ try {
390
+ const result = await this.makeRequest('POST', `/applications/${encodeURIComponent(applicationId)}/credentials/${encodeURIComponent(credentialId)}/rotate`, undefined, { cache: false });
391
+ // Rotation changes credential status/audit fields surfaced by the list.
392
+ this.clearCacheEntry(`GET:/applications/${encodeURIComponent(applicationId)}/credentials`);
393
+ return result;
394
+ }
395
+ catch (error) {
396
+ throw this.handleError(error);
397
+ }
398
+ }
399
+ /**
400
+ * Revoke an application credential (`status='revoked'`). Revoked credentials
401
+ * can no longer authenticate.
402
+ * @param applicationId - The application's Mongo `_id`.
403
+ * @param credentialId - The credential's Mongo `_id`.
404
+ */
405
+ async revokeAppCredential(applicationId, credentialId) {
406
+ try {
407
+ const result = await this.makeRequest('DELETE', `/applications/${encodeURIComponent(applicationId)}/credentials/${encodeURIComponent(credentialId)}`, undefined, { cache: false });
408
+ // Revocation flips the credential's status in the cached list.
409
+ this.clearCacheEntry(`GET:/applications/${encodeURIComponent(applicationId)}/credentials`);
410
+ return result;
411
+ }
412
+ catch (error) {
413
+ throw this.handleError(error);
414
+ }
415
+ }
416
+ /**
417
+ * Fetch usage statistics for an application.
418
+ * @param applicationId - The application's Mongo `_id`.
419
+ * @param period - Time window (defaults to the server default).
420
+ */
421
+ async getAppUsage(applicationId, period) {
422
+ try {
423
+ return await this.makeRequest('GET', `/applications/${encodeURIComponent(applicationId)}/usage`, period ? { period } : undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.SHORT });
424
+ }
425
+ catch (error) {
426
+ throw this.handleError(error);
427
+ }
428
+ }
429
+ // =========================================================================
430
+ // Cache-invalidation helpers
431
+ // =========================================================================
432
+ /**
433
+ * Bust every cached account list. `listAccounts({tree?})` keys the flat list
434
+ * as `GET:/accounts` and the tree variant as `GET:/accounts?tree=true` (the
435
+ * query string is part of the URL path). A change to the accessible forest
436
+ * (create/archive/ownership transfer) invalidates both, so we clear the
437
+ * unscoped entry plus every `?`-query variant via a prefix sweep. The prefix
438
+ * `GET:/accounts?` matches only the query-string list variants, never the
439
+ * `GET:/accounts/<id>…` detail/sub-resource keys.
440
+ *
441
+ * Internal helper (leading underscore); not part of the supported public
442
+ * surface. Public rather than `private` because mixins compose into an
443
+ * exported anonymous class, where TypeScript cannot represent a private
444
+ * member in the emitted declaration file (TS4094).
445
+ */
446
+ _invalidateAccountLists() {
447
+ this.clearCacheEntry('GET:/accounts');
448
+ this.clearCacheByPrefix('GET:/accounts?');
449
+ }
450
+ /**
451
+ * Bust the cached member list and detail for an account after a membership
452
+ * mutation. The member list (`listAccountMembers`) and the detail
453
+ * (`getAccount`, which can embed the caller's membership) both go stale when
454
+ * the member set or a member's role changes.
455
+ *
456
+ * Internal helper (leading underscore); see `_invalidateAccountLists` for why
457
+ * this is public rather than `private`.
458
+ */
459
+ _invalidateAccountMembership(accountId) {
460
+ this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}/members`);
461
+ this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}`);
462
+ }
463
+ /**
464
+ * Bust every cached application list. `listAccountApps(accountId)` keys each
465
+ * owner-scoped list as `GET:/applications?ownerAccountId=<id>` (the query
466
+ * string is part of the URL path). A change to any list (create/delete)
467
+ * invalidates them all, so we clear the unscoped entry plus every `?`-query
468
+ * variant via a prefix sweep. The prefix `GET:/applications?` matches only the
469
+ * query-string list variants, never the `GET:/applications/<id>…`
470
+ * detail/sub-resource keys.
471
+ *
472
+ * Internal helper (leading underscore); see `_invalidateAccountLists` for why
473
+ * this is public rather than `private`.
474
+ */
475
+ _invalidateAppLists() {
476
+ this.clearCacheEntry('GET:/applications');
477
+ this.clearCacheByPrefix('GET:/applications?');
478
+ }
479
+ };
480
+ }
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OxyServicesConnectedAppsMixin = OxyServicesConnectedAppsMixin;
4
+ const mixinHelpers_1 = require("./mixinHelpers");
5
+ function OxyServicesConnectedAppsMixin(Base) {
6
+ return class extends Base {
7
+ constructor(...args) {
8
+ super(...args);
9
+ }
10
+ /**
11
+ * Resolve an OAuth client identifier to the owning application's PUBLIC
12
+ * identity. No authentication required — the API returns only sanitized,
13
+ * display-safe metadata ({@link PublicApplication}). Use this to render the
14
+ * requesting application's name/icon in consent, authorize, and device-flow
15
+ * approval UIs before any session exists.
16
+ *
17
+ * @param clientId - The OAuth `client_id` (an active credential's public
18
+ * key). URL-encoded before being placed in the path.
19
+ */
20
+ async getPublicApplication(clientId) {
21
+ try {
22
+ const res = await this.makeRequest('GET', `/auth/oauth/client/${encodeURIComponent(clientId)}`, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
23
+ return res.application;
24
+ }
25
+ catch (error) {
26
+ throw this.handleError(error);
27
+ }
28
+ }
29
+ /**
30
+ * List the OAuth-authorized applications the current user has connected —
31
+ * the third-party apps the user granted access to via the consent flow.
32
+ * Each entry is a {@link ConnectedApp} carrying the application's display
33
+ * identity, the granted scopes, and when the grant was first made and last
34
+ * exercised. Requires an authenticated session.
35
+ *
36
+ * Backed by `GET /auth/grants`. The response is briefly cached
37
+ * (identity-scoped); {@link revokeAppGrant} busts that cache so a revoke is
38
+ * reflected on the next read.
39
+ */
40
+ async listConnectedApps() {
41
+ try {
42
+ return await this.makeRequest('GET', '/auth/grants', undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.SHORT });
43
+ }
44
+ catch (error) {
45
+ throw this.handleError(error);
46
+ }
47
+ }
48
+ /**
49
+ * Revoke the current user's grant for a connected application, identified by
50
+ * its application `_id` (a {@link ConnectedApp.applicationId}, NOT a
51
+ * credential/client id — keyed by application so the revocation survives
52
+ * credential rotation). After this the application can no longer act on the
53
+ * user's behalf until it is re-authorized.
54
+ *
55
+ * Backed by `DELETE /auth/grants/:applicationId`. On success the cached
56
+ * connected-apps list (`GET:/auth/grants`) is invalidated so the next
57
+ * {@link listConnectedApps} read reflects the removal.
58
+ *
59
+ * @param applicationId - The connected application's Mongo `_id`.
60
+ */
61
+ async revokeAppGrant(applicationId) {
62
+ try {
63
+ await this.makeRequest('DELETE', `/auth/grants/${applicationId}`, undefined, { cache: false });
64
+ // A revoke removes an entry from the user's connected-apps list; bust
65
+ // the cached `GET /auth/grants` so the next read re-fetches.
66
+ this.clearCacheEntry('GET:/auth/grants');
67
+ }
68
+ catch (error) {
69
+ throw this.handleError(error);
70
+ }
71
+ }
72
+ };
73
+ }
@@ -62,7 +62,8 @@ function OxyServicesUtilityMixin(Base) {
62
62
  this._serviceActingAsCache = new Map();
63
63
  }
64
64
  /**
65
- * Verify that a user is authorized to act as a managed account.
65
+ * Verify that a user is authorized to act as an account (direct membership
66
+ * or inherited via an ancestor). Backed by `GET /accounts/verify-acting-as`.
66
67
  * Results are cached in-memory for 5 minutes to avoid repeated API calls.
67
68
  *
68
69
  * @internal Used by the auth() middleware — not part of the public API
@@ -77,7 +78,7 @@ function OxyServicesUtilityMixin(Base) {
77
78
  }
78
79
  // Query the API
79
80
  try {
80
- const result = await this.makeRequest('GET', '/managed-accounts/verify', { accountId, userId }, { cache: false, retry: false, timeout: 5000 });
81
+ const result = await this.makeRequest('GET', '/accounts/verify-acting-as', { accountId, userId }, { cache: false, retry: false, timeout: 5000 });
81
82
  // Cache successful result for 5 minutes
82
83
  this._actingAsCache.set(cacheKey, {
83
84
  result: result && result.authorized ? result : null,
@@ -21,8 +21,8 @@ const OxyServices_language_1 = require("./OxyServices.language");
21
21
  const OxyServices_payment_1 = require("./OxyServices.payment");
22
22
  const OxyServices_reputation_1 = require("./OxyServices.reputation");
23
23
  const OxyServices_assets_1 = require("./OxyServices.assets");
24
- const OxyServices_applications_1 = require("./OxyServices.applications");
25
- const OxyServices_workspaces_1 = require("./OxyServices.workspaces");
24
+ const OxyServices_accounts_1 = require("./OxyServices.accounts");
25
+ const OxyServices_connectedApps_1 = require("./OxyServices.connectedApps");
26
26
  const OxyServices_location_1 = require("./OxyServices.location");
27
27
  const OxyServices_analytics_1 = require("./OxyServices.analytics");
28
28
  const OxyServices_devices_1 = require("./OxyServices.devices");
@@ -30,7 +30,6 @@ const OxyServices_security_1 = require("./OxyServices.security");
30
30
  const OxyServices_utility_1 = require("./OxyServices.utility");
31
31
  const OxyServices_features_1 = require("./OxyServices.features");
32
32
  const OxyServices_topics_1 = require("./OxyServices.topics");
33
- const OxyServices_managedAccounts_1 = require("./OxyServices.managedAccounts");
34
33
  const OxyServices_contacts_1 = require("./OxyServices.contacts");
35
34
  const OxyServices_appData_1 = require("./OxyServices.appData");
36
35
  const OxyServices_civic_1 = require("./OxyServices.civic");
@@ -70,15 +69,19 @@ const MIXIN_PIPELINE = [
70
69
  OxyServices_payment_1.OxyServicesPaymentMixin,
71
70
  OxyServices_reputation_1.OxyServicesReputationMixin,
72
71
  OxyServices_assets_1.OxyServicesAssetsMixin,
73
- OxyServices_applications_1.OxyServicesApplicationsMixin,
74
- OxyServices_workspaces_1.OxyServicesWorkspacesMixin,
72
+ // Unified account graph + the applications owned within it. The clean-cut
73
+ // replacement for the former managedAccounts + workspaces + applications
74
+ // (account-management) mixins.
75
+ OxyServices_accounts_1.OxyServicesAccountsMixin,
76
+ // OAuth-consent surface (public app identity + connected-app grants). Kept
77
+ // separate from account ownership.
78
+ OxyServices_connectedApps_1.OxyServicesConnectedAppsMixin,
75
79
  OxyServices_location_1.OxyServicesLocationMixin,
76
80
  OxyServices_analytics_1.OxyServicesAnalyticsMixin,
77
81
  OxyServices_devices_1.OxyServicesDevicesMixin,
78
82
  OxyServices_security_1.OxyServicesSecurityMixin,
79
83
  OxyServices_features_1.OxyServicesFeaturesMixin,
80
84
  OxyServices_topics_1.OxyServicesTopicsMixin,
81
- OxyServices_managedAccounts_1.OxyServicesManagedAccountsMixin,
82
85
  OxyServices_contacts_1.OxyServicesContactsMixin,
83
86
  OxyServices_appData_1.OxyServicesAppDataMixin,
84
87
  // Civic / Commons "Oxy ID" (public signed cards, Oxy ID QR payload)