@oxyhq/core 3.0.0 → 3.2.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.
@@ -686,7 +686,14 @@ class AuthManager {
686
686
  * Get a valid access token, refreshing automatically if expired or expiring soon.
687
687
  */
688
688
  async getAccessToken() {
689
- const token = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
689
+ // In cookieOnly / cookie-restore flows the active access token lives only in
690
+ // memory (`_lastKnownAccessToken` + httpService) and is intentionally never
691
+ // written to JS storage — the cookieOnly contract forbids persisting tokens
692
+ // in JS-accessible storage. Fall back to the in-memory token when storage has
693
+ // none, otherwise getAccessToken returns null after every cold-boot/reload and
694
+ // standalone API clients (e.g. the Console axios client) send no Authorization
695
+ // header → 401 on every authed endpoint while `isAuthenticated` is still true.
696
+ const token = (await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN)) ?? this._lastKnownAccessToken;
690
697
  if (!token)
691
698
  return null;
692
699
  try {
@@ -697,7 +704,9 @@ class AuthManager {
697
704
  if (decoded.exp - now < buffer) {
698
705
  const refreshed = await this.refreshToken();
699
706
  if (refreshed) {
700
- return this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
707
+ // refreshToken() updates both storage and `_lastKnownAccessToken`;
708
+ // prefer storage but fall back to memory for the cookieOnly path.
709
+ return (await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN)) ?? this._lastKnownAccessToken;
701
710
  }
702
711
  }
703
712
  }
@@ -26,6 +26,7 @@ const mixins_1 = require("./mixins");
26
26
  * - **Karma**: Karma system
27
27
  * - **Assets**: File upload and asset management
28
28
  * - **Applications**: Application, membership, and credential management
29
+ * - **Workspaces**: Workspace and membership management
29
30
  * - **Location**: Location-based features
30
31
  * - **Analytics**: Analytics tracking
31
32
  * - **Devices**: Device management
@@ -7,12 +7,40 @@ function OxyServicesApplicationsMixin(Base) {
7
7
  constructor(...args) {
8
8
  super(...args);
9
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
+ }
10
29
  /**
11
30
  * List applications the current user is an active member of.
31
+ *
32
+ * @param workspaceId - Optional workspace `_id` to scope the listing to
33
+ * applications belonging to that workspace. When provided it is appended
34
+ * as a `workspaceId` query parameter (URL-encoded). The query string is
35
+ * part of the request path, so the response cache keys on it
36
+ * automatically — scoped and unscoped lists never collide.
12
37
  */
13
- async getApplications() {
38
+ async getApplications(workspaceId) {
14
39
  try {
15
- const res = await this.makeRequest('GET', '/applications', undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
40
+ const path = workspaceId
41
+ ? `/applications?workspaceId=${encodeURIComponent(workspaceId)}`
42
+ : '/applications';
43
+ const res = await this.makeRequest('GET', path, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
16
44
  return res.applications ?? [];
17
45
  }
18
46
  catch (error) {
@@ -169,7 +197,9 @@ function OxyServicesApplicationsMixin(Base) {
169
197
  }
170
198
  /**
171
199
  * Rotate a credential's secret. The new plaintext `secret` is returned
172
- * exactly ONCE.
200
+ * exactly ONCE, along with audit fields: `rotatedFrom` (the previous
201
+ * credentialId) and `graceExpiresAt` (ISO string for the grace window during
202
+ * which the old credential is still honoured).
173
203
  * @param applicationId - The application's Mongo `_id`.
174
204
  * @param credentialId - The credential's Mongo `_id`.
175
205
  */
@@ -475,6 +475,9 @@ function OxyServicesUtilityMixin(Base) {
475
475
  appId,
476
476
  appName: decoded.appName || 'unknown',
477
477
  scopes: Array.isArray(decoded.scopes) ? decoded.scopes : [],
478
+ ...(typeof decoded.credentialId === 'string' && decoded.credentialId.length > 0
479
+ ? { credentialId: decoded.credentialId }
480
+ : {}),
478
481
  };
479
482
  if (debug) {
480
483
  loggerUtils_1.logger.debug(`[oxy.auth] Service token OK app=${decoded.appName} delegateUser=${oxyUserId || '(none)'}`, {
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OxyServicesWorkspacesMixin = OxyServicesWorkspacesMixin;
4
+ const mixinHelpers_1 = require("./mixinHelpers");
5
+ function OxyServicesWorkspacesMixin(Base) {
6
+ return class extends Base {
7
+ constructor(...args) {
8
+ super(...args);
9
+ }
10
+ /**
11
+ * List workspaces the current user is an active member of.
12
+ */
13
+ async getWorkspaces() {
14
+ try {
15
+ const res = await this.makeRequest('GET', '/workspaces', undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
16
+ return res.workspaces ?? [];
17
+ }
18
+ catch (error) {
19
+ throw this.handleError(error);
20
+ }
21
+ }
22
+ /**
23
+ * Create a new team workspace. The caller becomes its `owner`.
24
+ * @param data - Workspace configuration.
25
+ */
26
+ async createWorkspace(data) {
27
+ try {
28
+ const res = await this.makeRequest('POST', '/workspaces', data, { cache: false });
29
+ return res.workspace;
30
+ }
31
+ catch (error) {
32
+ throw this.handleError(error);
33
+ }
34
+ }
35
+ /**
36
+ * Fetch a single workspace by id.
37
+ * @param workspaceId - The workspace's Mongo `_id`.
38
+ */
39
+ async getWorkspace(workspaceId) {
40
+ try {
41
+ const res = await this.makeRequest('GET', `/workspaces/${encodeURIComponent(workspaceId)}`, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.LONG });
42
+ return res.workspace;
43
+ }
44
+ catch (error) {
45
+ throw this.handleError(error);
46
+ }
47
+ }
48
+ /**
49
+ * Update a workspace's mutable fields.
50
+ * @param workspaceId - The workspace's Mongo `_id`.
51
+ * @param data - Subset of updatable fields.
52
+ */
53
+ async updateWorkspace(workspaceId, data) {
54
+ try {
55
+ const res = await this.makeRequest('PATCH', `/workspaces/${encodeURIComponent(workspaceId)}`, data, { cache: false });
56
+ return res.workspace;
57
+ }
58
+ catch (error) {
59
+ throw this.handleError(error);
60
+ }
61
+ }
62
+ /**
63
+ * Soft-delete a workspace (owner only).
64
+ * @param workspaceId - The workspace's Mongo `_id`.
65
+ */
66
+ async deleteWorkspace(workspaceId) {
67
+ try {
68
+ return await this.makeRequest('DELETE', `/workspaces/${encodeURIComponent(workspaceId)}`, undefined, { cache: false });
69
+ }
70
+ catch (error) {
71
+ throw this.handleError(error);
72
+ }
73
+ }
74
+ /**
75
+ * List members of a workspace.
76
+ * @param workspaceId - The workspace's Mongo `_id`.
77
+ */
78
+ async getWorkspaceMembers(workspaceId) {
79
+ try {
80
+ const res = await this.makeRequest('GET', `/workspaces/${encodeURIComponent(workspaceId)}/members`, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
81
+ return res.members ?? [];
82
+ }
83
+ catch (error) {
84
+ throw this.handleError(error);
85
+ }
86
+ }
87
+ /**
88
+ * Add a member to a workspace.
89
+ * @param workspaceId - The workspace's Mongo `_id`.
90
+ * @param data - Target user id and role (never `owner`).
91
+ */
92
+ async inviteWorkspaceMember(workspaceId, data) {
93
+ try {
94
+ const res = await this.makeRequest('POST', `/workspaces/${encodeURIComponent(workspaceId)}/members`, data, { cache: false });
95
+ return res.member;
96
+ }
97
+ catch (error) {
98
+ throw this.handleError(error);
99
+ }
100
+ }
101
+ /**
102
+ * Change a member's role.
103
+ * @param workspaceId - The workspace's Mongo `_id`.
104
+ * @param memberId - The member's Mongo `_id`.
105
+ * @param data - New role (never `owner`).
106
+ */
107
+ async updateWorkspaceMember(workspaceId, memberId, data) {
108
+ try {
109
+ const res = await this.makeRequest('PATCH', `/workspaces/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(memberId)}`, data, { cache: false });
110
+ return res.member;
111
+ }
112
+ catch (error) {
113
+ throw this.handleError(error);
114
+ }
115
+ }
116
+ /**
117
+ * Remove a member from a workspace.
118
+ * @param workspaceId - The workspace's Mongo `_id`.
119
+ * @param memberId - The member's Mongo `_id`.
120
+ */
121
+ async removeWorkspaceMember(workspaceId, memberId) {
122
+ try {
123
+ return await this.makeRequest('DELETE', `/workspaces/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(memberId)}`, undefined, { cache: false });
124
+ }
125
+ catch (error) {
126
+ throw this.handleError(error);
127
+ }
128
+ }
129
+ /**
130
+ * Transfer ownership of a workspace to another member (owner only).
131
+ * Demotes the current owner and promotes the target to `owner`.
132
+ * @param workspaceId - The workspace's Mongo `_id`.
133
+ * @param data - Target user id.
134
+ */
135
+ async transferWorkspaceOwnership(workspaceId, data) {
136
+ try {
137
+ return await this.makeRequest('POST', `/workspaces/${encodeURIComponent(workspaceId)}/transfer-ownership`, data, { cache: false });
138
+ }
139
+ catch (error) {
140
+ throw this.handleError(error);
141
+ }
142
+ }
143
+ };
144
+ }
@@ -21,6 +21,7 @@ const OxyServices_payment_1 = require("./OxyServices.payment");
21
21
  const OxyServices_karma_1 = require("./OxyServices.karma");
22
22
  const OxyServices_assets_1 = require("./OxyServices.assets");
23
23
  const OxyServices_applications_1 = require("./OxyServices.applications");
24
+ const OxyServices_workspaces_1 = require("./OxyServices.workspaces");
24
25
  const OxyServices_location_1 = require("./OxyServices.location");
25
26
  const OxyServices_analytics_1 = require("./OxyServices.analytics");
26
27
  const OxyServices_devices_1 = require("./OxyServices.devices");
@@ -65,6 +66,7 @@ const MIXIN_PIPELINE = [
65
66
  OxyServices_karma_1.OxyServicesKarmaMixin,
66
67
  OxyServices_assets_1.OxyServicesAssetsMixin,
67
68
  OxyServices_applications_1.OxyServicesApplicationsMixin,
69
+ OxyServices_workspaces_1.OxyServicesWorkspacesMixin,
68
70
  OxyServices_location_1.OxyServicesLocationMixin,
69
71
  OxyServices_analytics_1.OxyServicesAnalyticsMixin,
70
72
  OxyServices_devices_1.OxyServicesDevicesMixin,