@oxyhq/core 3.1.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.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/AuthManager.js +11 -2
- package/dist/cjs/OxyServices.js +1 -0
- package/dist/cjs/mixins/OxyServices.applications.js +30 -2
- package/dist/cjs/mixins/OxyServices.workspaces.js +144 -0
- package/dist/cjs/mixins/index.js +2 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/AuthManager.js +11 -2
- package/dist/esm/OxyServices.js +1 -0
- package/dist/esm/mixins/OxyServices.applications.js +30 -2
- package/dist/esm/mixins/OxyServices.workspaces.js +141 -0
- package/dist/esm/mixins/index.js +2 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/OxyServices.d.ts +1 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/mixins/OxyServices.applications.d.ts +78 -8
- package/dist/types/mixins/OxyServices.workspaces.d.ts +199 -0
- package/dist/types/mixins/index.d.ts +2 -1
- package/dist/types/models/interfaces.d.ts +9 -0
- package/package.json +1 -1
- package/src/AuthManager.ts +11 -2
- package/src/OxyServices.ts +1 -0
- package/src/__tests__/authManager.cookiePath.test.ts +49 -0
- package/src/index.ts +19 -0
- package/src/mixins/OxyServices.applications.ts +95 -3
- package/src/mixins/OxyServices.workspaces.ts +309 -0
- package/src/mixins/index.ts +3 -0
- package/src/models/interfaces.ts +9 -0
|
@@ -83,6 +83,7 @@ import { composeOxyServices } from './mixins';
|
|
|
83
83
|
* - **Karma**: Karma system
|
|
84
84
|
* - **Assets**: File upload and asset management
|
|
85
85
|
* - **Applications**: Application, membership, and credential management
|
|
86
|
+
* - **Workspaces**: Workspace and membership management
|
|
86
87
|
* - **Location**: Location-based features
|
|
87
88
|
* - **Analytics**: Analytics tracking
|
|
88
89
|
* - **Devices**: Device management
|
package/dist/types/index.d.ts
CHANGED
|
@@ -33,7 +33,8 @@ export type { ServiceApp, ServiceActingAsVerification } from './mixins/OxyServic
|
|
|
33
33
|
export type { CreateManagedAccountInput, ManagedAccountManager, ManagedAccount, } from './mixins/OxyServices.managedAccounts';
|
|
34
34
|
export type { ContactDiscoveryMatch, ContactDiscoveryResponse, } from './mixins/OxyServices.contacts';
|
|
35
35
|
export { OxyAppDataIdentifierError } from './mixins/OxyServices.appData';
|
|
36
|
-
export type { Application, ApplicationMember, ApplicationCredential, ApplicationRole, ApplicationType, ApplicationStatus, ApplicationMemberStatus, ApplicationCredentialType, ApplicationCredentialStatus, ApplicationEnvironment, CreateApplicationInput, UpdateApplicationInput, InviteApplicationMemberInput, UpdateApplicationMemberInput, TransferApplicationOwnershipInput, CreateApplicationCredentialInput, ApplicationCredentialWithSecret, RotateApplicationCredentialResult, ApplicationUsagePeriod, ApplicationUsageSummary, ApplicationUsageByDay, ApplicationUsageByEndpoint, ApplicationUsageStats, ApplicationSuccessResult, } from './mixins/OxyServices.applications';
|
|
36
|
+
export type { Application, PublicApplication, ApplicationMember, ApplicationCredential, ApplicationRole, ApplicationType, ApplicationStatus, ApplicationMemberStatus, ApplicationCredentialType, ApplicationCredentialStatus, ApplicationEnvironment, CreateApplicationInput, UpdateApplicationInput, InviteApplicationMemberInput, UpdateApplicationMemberInput, TransferApplicationOwnershipInput, CreateApplicationCredentialInput, ApplicationCredentialWithSecret, RotateApplicationCredentialResult, ApplicationUsagePeriod, ApplicationUsageSummary, ApplicationUsageByDay, ApplicationUsageByEndpoint, ApplicationUsageStats, ApplicationSuccessResult, } from './mixins/OxyServices.applications';
|
|
37
|
+
export type { Workspace, WorkspaceMember, WorkspaceRole, WorkspaceType, WorkspaceStatus, WorkspaceMemberStatus, CreateWorkspaceInput, UpdateWorkspaceInput, InviteWorkspaceMemberInput, UpdateWorkspaceMemberInput, TransferWorkspaceOwnershipInput, WorkspaceSuccessResult, } from './mixins/OxyServices.workspaces';
|
|
37
38
|
export { SessionSyncRequiredError, AuthenticationFailedError, ensureValidToken, isAuthenticationError, withAuthErrorHandling, authenticatedApiCall, } from './utils/authHelpers';
|
|
38
39
|
export type { HandleApiErrorOptions } from './utils/authHelpers';
|
|
39
40
|
export { mergeSessions, normalizeAndSortSessions, sessionsArraysEqual, } from './utils/sessionUtils';
|
|
@@ -49,12 +49,22 @@ export interface Application {
|
|
|
49
49
|
webhookUrl?: string;
|
|
50
50
|
devWebhookUrl?: string;
|
|
51
51
|
createdByUserId: string;
|
|
52
|
+
/**
|
|
53
|
+
* The workspace this application belongs to (workspace `_id`), or `null` for
|
|
54
|
+
* applications not owned by a workspace. Used by the console to scope apps to
|
|
55
|
+
* a workspace and to branch on workspace-derived access.
|
|
56
|
+
*/
|
|
57
|
+
workspaceId: string | null;
|
|
52
58
|
createdAt: string;
|
|
53
59
|
updatedAt: string;
|
|
54
60
|
/**
|
|
55
61
|
* The calling user's own membership in this application, embedded by the API
|
|
56
62
|
* on list (`GET /applications`) and detail (`GET /applications/:appId`)
|
|
57
63
|
* responses. Use `callerMembership.permissions` to gate UI affordances.
|
|
64
|
+
*
|
|
65
|
+
* When the caller's access is derived from a workspace membership rather than
|
|
66
|
+
* a direct application membership, the API returns a synthetic membership
|
|
67
|
+
* with `source: 'workspace'` and `_id: null`.
|
|
58
68
|
*/
|
|
59
69
|
callerMembership?: ApplicationMember;
|
|
60
70
|
}
|
|
@@ -63,7 +73,11 @@ export interface Application {
|
|
|
63
73
|
* on the server at write time.
|
|
64
74
|
*/
|
|
65
75
|
export interface ApplicationMember {
|
|
66
|
-
|
|
76
|
+
/**
|
|
77
|
+
* The membership's Mongo `_id`. `null` for a synthetic, workspace-derived
|
|
78
|
+
* membership (see {@link Application.callerMembership} and `source`).
|
|
79
|
+
*/
|
|
80
|
+
_id: string | null;
|
|
67
81
|
applicationId: string;
|
|
68
82
|
userId: string;
|
|
69
83
|
role: ApplicationRole;
|
|
@@ -71,6 +85,13 @@ export interface ApplicationMember {
|
|
|
71
85
|
invitedByUserId?: string;
|
|
72
86
|
joinedAt?: string;
|
|
73
87
|
status: ApplicationMemberStatus;
|
|
88
|
+
/**
|
|
89
|
+
* Origin of this membership. When `'workspace'`, the membership is synthetic
|
|
90
|
+
* and derived from the caller's workspace membership rather than a direct
|
|
91
|
+
* application membership (in which case `_id` is `null`). Absent or any other
|
|
92
|
+
* value indicates a direct application membership.
|
|
93
|
+
*/
|
|
94
|
+
source?: 'workspace';
|
|
74
95
|
createdAt: string;
|
|
75
96
|
updatedAt: string;
|
|
76
97
|
}
|
|
@@ -98,6 +119,38 @@ export interface ApplicationCredential {
|
|
|
98
119
|
createdAt: string;
|
|
99
120
|
updatedAt: string;
|
|
100
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Sanitized, PUBLIC application identity returned by the API when resolving a
|
|
124
|
+
* cross-app/OAuth client to a registered {@link Application}.
|
|
125
|
+
*
|
|
126
|
+
* Unlike {@link Application}, this shape carries NO sensitive or membership
|
|
127
|
+
* fields — it is safe to display unauthenticated in consent/authorize screens
|
|
128
|
+
* and device-flow approval UIs. The API resolves a `client_id` (OAuth
|
|
129
|
+
* credential public key) to the owning application and projects only the
|
|
130
|
+
* fields below. `id` is the application's `_id` as a string.
|
|
131
|
+
*/
|
|
132
|
+
export interface PublicApplication {
|
|
133
|
+
/** The application's Mongo `_id` as a string. */
|
|
134
|
+
id: string;
|
|
135
|
+
/** Human-readable application name shown to the user. */
|
|
136
|
+
name: string;
|
|
137
|
+
/** Optional short description of what the application does. */
|
|
138
|
+
description?: string;
|
|
139
|
+
/** Optional icon URL for the application. */
|
|
140
|
+
icon?: string;
|
|
141
|
+
/** Optional public website/homepage URL for the application. */
|
|
142
|
+
websiteUrl?: string;
|
|
143
|
+
/** Application classification (set by Oxy platform staff). */
|
|
144
|
+
type: ApplicationType;
|
|
145
|
+
/** Whether the application is an officially endorsed Oxy application. */
|
|
146
|
+
isOfficial: boolean;
|
|
147
|
+
/** Whether the application is an internal Oxy ecosystem application. */
|
|
148
|
+
isInternal: boolean;
|
|
149
|
+
/** OAuth scopes the application is configured to request. */
|
|
150
|
+
scopes: string[];
|
|
151
|
+
/** Optional display name of the developer/owner organisation. */
|
|
152
|
+
developerName?: string;
|
|
153
|
+
}
|
|
101
154
|
/** Input accepted by `createApplication`. Staff-only fields are not settable here. */
|
|
102
155
|
export interface CreateApplicationInput {
|
|
103
156
|
name: string;
|
|
@@ -106,6 +159,11 @@ export interface CreateApplicationInput {
|
|
|
106
159
|
icon?: string;
|
|
107
160
|
redirectUris?: string[];
|
|
108
161
|
scopes?: string[];
|
|
162
|
+
/**
|
|
163
|
+
* Optional workspace `_id` to create the app in. Omitted → API defaults to
|
|
164
|
+
* the caller's personal workspace.
|
|
165
|
+
*/
|
|
166
|
+
workspaceId?: string;
|
|
109
167
|
}
|
|
110
168
|
/** Input accepted by `updateApplication`. Staff-only fields are not settable here. */
|
|
111
169
|
export interface UpdateApplicationInput {
|
|
@@ -192,10 +250,27 @@ export interface ApplicationSuccessResult {
|
|
|
192
250
|
}
|
|
193
251
|
export declare function OxyServicesApplicationsMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
194
252
|
new (...args: any[]): {
|
|
253
|
+
/**
|
|
254
|
+
* Resolve an OAuth client identifier to the owning application's PUBLIC
|
|
255
|
+
* identity. No authentication required — the API returns only sanitized,
|
|
256
|
+
* display-safe metadata ({@link PublicApplication}). Use this to render the
|
|
257
|
+
* requesting application's name/icon in consent, authorize, and device-flow
|
|
258
|
+
* approval UIs before any session exists.
|
|
259
|
+
*
|
|
260
|
+
* @param clientId - The OAuth `client_id` (an active credential's public
|
|
261
|
+
* key). URL-encoded before being placed in the path.
|
|
262
|
+
*/
|
|
263
|
+
getPublicApplication(clientId: string): Promise<PublicApplication>;
|
|
195
264
|
/**
|
|
196
265
|
* List applications the current user is an active member of.
|
|
266
|
+
*
|
|
267
|
+
* @param workspaceId - Optional workspace `_id` to scope the listing to
|
|
268
|
+
* applications belonging to that workspace. When provided it is appended
|
|
269
|
+
* as a `workspaceId` query parameter (URL-encoded). The query string is
|
|
270
|
+
* part of the request path, so the response cache keys on it
|
|
271
|
+
* automatically — scoped and unscoped lists never collide.
|
|
197
272
|
*/
|
|
198
|
-
getApplications(): Promise<Application[]>;
|
|
273
|
+
getApplications(workspaceId?: string): Promise<Application[]>;
|
|
199
274
|
/**
|
|
200
275
|
* Create a new application. The caller becomes its `owner`.
|
|
201
276
|
* @param data - Application configuration. Staff-only fields are ignored.
|
|
@@ -329,12 +404,7 @@ export declare function OxyServicesApplicationsMixin<T extends typeof OxyService
|
|
|
329
404
|
healthCheck(): Promise<{
|
|
330
405
|
status: string;
|
|
331
406
|
users?: number;
|
|
332
|
-
timestamp
|
|
333
|
-
* Create a credential. The plaintext `secret` is returned exactly ONCE;
|
|
334
|
-
* the server stores only a hash and will never return it again.
|
|
335
|
-
* @param applicationId - The application's Mongo `_id`.
|
|
336
|
-
* @param data - Credential configuration.
|
|
337
|
-
*/: string;
|
|
407
|
+
timestamp?: string;
|
|
338
408
|
[key: string]: any;
|
|
339
409
|
}>;
|
|
340
410
|
};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspaces Methods Mixin
|
|
3
|
+
*
|
|
4
|
+
* Provides methods for managing Oxy workspaces and their members via the
|
|
5
|
+
* `/workspaces` API. A workspace is a multi-user container that owns
|
|
6
|
+
* applications and other resources: membership (with a role) grants
|
|
7
|
+
* permissions. A `personal` workspace is created implicitly for every user;
|
|
8
|
+
* `team` workspaces are created explicitly and can invite additional members.
|
|
9
|
+
*
|
|
10
|
+
* Reference workspaces by their Mongo `_id` and members by their member `_id`.
|
|
11
|
+
* Never by name or slug.
|
|
12
|
+
*/
|
|
13
|
+
import type { OxyServicesBase } from '../OxyServices.base';
|
|
14
|
+
/** Role a member holds within a workspace. */
|
|
15
|
+
export type WorkspaceRole = 'owner' | 'admin' | 'member' | 'viewer';
|
|
16
|
+
/** Workspace classification. A `personal` workspace is implicit per user. */
|
|
17
|
+
export type WorkspaceType = 'personal' | 'team';
|
|
18
|
+
/** Lifecycle status of a workspace. */
|
|
19
|
+
export type WorkspaceStatus = 'active' | 'deleted';
|
|
20
|
+
/** Membership lifecycle status. */
|
|
21
|
+
export type WorkspaceMemberStatus = 'active' | 'invited' | 'removed';
|
|
22
|
+
/**
|
|
23
|
+
* Client-facing WorkspaceMember shape. `permissions` is derived from `role`
|
|
24
|
+
* on the server at write time.
|
|
25
|
+
*/
|
|
26
|
+
export interface WorkspaceMember {
|
|
27
|
+
_id: string;
|
|
28
|
+
workspaceId: string;
|
|
29
|
+
userId: string;
|
|
30
|
+
role: WorkspaceRole;
|
|
31
|
+
permissions: string[];
|
|
32
|
+
invitedByUserId?: string | null;
|
|
33
|
+
joinedAt?: string | null;
|
|
34
|
+
status: WorkspaceMemberStatus;
|
|
35
|
+
createdAt: string;
|
|
36
|
+
updatedAt: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Client-facing Workspace shape returned by the `/workspaces` API. Mirrors the
|
|
40
|
+
* server `Workspace` model with `_id` as a string and dates serialized to ISO
|
|
41
|
+
* strings.
|
|
42
|
+
*/
|
|
43
|
+
export interface Workspace {
|
|
44
|
+
_id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
slug: string;
|
|
47
|
+
type: WorkspaceType;
|
|
48
|
+
description?: string | null;
|
|
49
|
+
icon?: string | null;
|
|
50
|
+
ownerId: string;
|
|
51
|
+
status: WorkspaceStatus;
|
|
52
|
+
createdAt: string;
|
|
53
|
+
updatedAt: string;
|
|
54
|
+
/**
|
|
55
|
+
* The calling user's own membership in this workspace, embedded by the API
|
|
56
|
+
* on list (`GET /workspaces`) and detail (`GET /workspaces/:id`) responses.
|
|
57
|
+
* Use `callerMembership.permissions` to gate UI affordances.
|
|
58
|
+
*/
|
|
59
|
+
callerMembership?: WorkspaceMember | null;
|
|
60
|
+
}
|
|
61
|
+
/** Input accepted by `createWorkspace`. */
|
|
62
|
+
export interface CreateWorkspaceInput {
|
|
63
|
+
name: string;
|
|
64
|
+
description?: string;
|
|
65
|
+
icon?: string;
|
|
66
|
+
}
|
|
67
|
+
/** Input accepted by `updateWorkspace`. */
|
|
68
|
+
export interface UpdateWorkspaceInput {
|
|
69
|
+
name?: string;
|
|
70
|
+
description?: string | null;
|
|
71
|
+
icon?: string | null;
|
|
72
|
+
}
|
|
73
|
+
/** Input accepted by `inviteWorkspaceMember`. The owner role cannot be invited. */
|
|
74
|
+
export interface InviteWorkspaceMemberInput {
|
|
75
|
+
userId: string;
|
|
76
|
+
role: Exclude<WorkspaceRole, 'owner'>;
|
|
77
|
+
}
|
|
78
|
+
/** Input accepted by `updateWorkspaceMember`. The owner role cannot be assigned. */
|
|
79
|
+
export interface UpdateWorkspaceMemberInput {
|
|
80
|
+
role: Exclude<WorkspaceRole, 'owner'>;
|
|
81
|
+
}
|
|
82
|
+
/** Input accepted by `transferWorkspaceOwnership`. */
|
|
83
|
+
export interface TransferWorkspaceOwnershipInput {
|
|
84
|
+
userId: string;
|
|
85
|
+
}
|
|
86
|
+
/** Result of a delete/remove/transfer operation. */
|
|
87
|
+
export interface WorkspaceSuccessResult {
|
|
88
|
+
success: boolean;
|
|
89
|
+
}
|
|
90
|
+
export declare function OxyServicesWorkspacesMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
91
|
+
new (...args: any[]): {
|
|
92
|
+
/**
|
|
93
|
+
* List workspaces the current user is an active member of.
|
|
94
|
+
*/
|
|
95
|
+
getWorkspaces(): Promise<Workspace[]>;
|
|
96
|
+
/**
|
|
97
|
+
* Create a new team workspace. The caller becomes its `owner`.
|
|
98
|
+
* @param data - Workspace configuration.
|
|
99
|
+
*/
|
|
100
|
+
createWorkspace(data: CreateWorkspaceInput): Promise<Workspace>;
|
|
101
|
+
/**
|
|
102
|
+
* Fetch a single workspace by id.
|
|
103
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
104
|
+
*/
|
|
105
|
+
getWorkspace(workspaceId: string): Promise<Workspace>;
|
|
106
|
+
/**
|
|
107
|
+
* Update a workspace's mutable fields.
|
|
108
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
109
|
+
* @param data - Subset of updatable fields.
|
|
110
|
+
*/
|
|
111
|
+
updateWorkspace(workspaceId: string, data: UpdateWorkspaceInput): Promise<Workspace>;
|
|
112
|
+
/**
|
|
113
|
+
* Soft-delete a workspace (owner only).
|
|
114
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
115
|
+
*/
|
|
116
|
+
deleteWorkspace(workspaceId: string): Promise<WorkspaceSuccessResult>;
|
|
117
|
+
/**
|
|
118
|
+
* List members of a workspace.
|
|
119
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
120
|
+
*/
|
|
121
|
+
getWorkspaceMembers(workspaceId: string): Promise<WorkspaceMember[]>;
|
|
122
|
+
/**
|
|
123
|
+
* Add a member to a workspace.
|
|
124
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
125
|
+
* @param data - Target user id and role (never `owner`).
|
|
126
|
+
*/
|
|
127
|
+
inviteWorkspaceMember(workspaceId: string, data: InviteWorkspaceMemberInput): Promise<WorkspaceMember>;
|
|
128
|
+
/**
|
|
129
|
+
* Change a member's role.
|
|
130
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
131
|
+
* @param memberId - The member's Mongo `_id`.
|
|
132
|
+
* @param data - New role (never `owner`).
|
|
133
|
+
*/
|
|
134
|
+
updateWorkspaceMember(workspaceId: string, memberId: string, data: UpdateWorkspaceMemberInput): Promise<WorkspaceMember>;
|
|
135
|
+
/**
|
|
136
|
+
* Remove a member from a workspace.
|
|
137
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
138
|
+
* @param memberId - The member's Mongo `_id`.
|
|
139
|
+
*/
|
|
140
|
+
removeWorkspaceMember(workspaceId: string, memberId: string): Promise<WorkspaceSuccessResult>;
|
|
141
|
+
/**
|
|
142
|
+
* Transfer ownership of a workspace to another member (owner only).
|
|
143
|
+
* Demotes the current owner and promotes the target to `owner`.
|
|
144
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
145
|
+
* @param data - Target user id.
|
|
146
|
+
*/
|
|
147
|
+
transferWorkspaceOwnership(workspaceId: string, data: TransferWorkspaceOwnershipInput): Promise<WorkspaceSuccessResult>;
|
|
148
|
+
httpService: import("../HttpService").HttpService;
|
|
149
|
+
cloudURL: string;
|
|
150
|
+
config: import("../OxyServices.base").OxyConfig;
|
|
151
|
+
__resetTokensForTests(): void;
|
|
152
|
+
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
153
|
+
getBaseURL(): string;
|
|
154
|
+
getSessionBaseUrl(): string;
|
|
155
|
+
getClient(): import("../HttpService").HttpService;
|
|
156
|
+
getMetrics(): {
|
|
157
|
+
totalRequests: number;
|
|
158
|
+
successfulRequests: number;
|
|
159
|
+
failedRequests: number;
|
|
160
|
+
cacheHits: number;
|
|
161
|
+
cacheMisses: number;
|
|
162
|
+
averageResponseTime: number;
|
|
163
|
+
};
|
|
164
|
+
clearCache(): void;
|
|
165
|
+
clearCacheEntry(key: string): void;
|
|
166
|
+
clearCacheByPrefix(prefix: string): number;
|
|
167
|
+
getCacheStats(): {
|
|
168
|
+
size: number;
|
|
169
|
+
hits: number;
|
|
170
|
+
misses: number;
|
|
171
|
+
hitRate: number;
|
|
172
|
+
};
|
|
173
|
+
getCloudURL(): string;
|
|
174
|
+
setTokens(accessToken: string, refreshToken?: string): void;
|
|
175
|
+
clearTokens(): void;
|
|
176
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
177
|
+
_cachedUserId: string | null | undefined;
|
|
178
|
+
_cachedAccessToken: string | null;
|
|
179
|
+
getCurrentUserId(): string | null;
|
|
180
|
+
hasValidToken(): boolean;
|
|
181
|
+
getAccessToken(): string | null;
|
|
182
|
+
setActingAs(userId: string | null): void;
|
|
183
|
+
getActingAs(): string | null;
|
|
184
|
+
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
185
|
+
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
186
|
+
maxRetries?: number;
|
|
187
|
+
retryDelay?: number;
|
|
188
|
+
authTimeoutMs?: number;
|
|
189
|
+
}): Promise<T_1>;
|
|
190
|
+
validate(): Promise<boolean>;
|
|
191
|
+
handleError(error: unknown): Error;
|
|
192
|
+
healthCheck(): Promise<{
|
|
193
|
+
status: string;
|
|
194
|
+
users?: number;
|
|
195
|
+
timestamp?: string;
|
|
196
|
+
[key: string]: any;
|
|
197
|
+
}>;
|
|
198
|
+
};
|
|
199
|
+
} & T;
|
|
@@ -17,6 +17,7 @@ import { OxyServicesPaymentMixin } from './OxyServices.payment';
|
|
|
17
17
|
import { OxyServicesKarmaMixin } from './OxyServices.karma';
|
|
18
18
|
import { OxyServicesAssetsMixin } from './OxyServices.assets';
|
|
19
19
|
import { OxyServicesApplicationsMixin } from './OxyServices.applications';
|
|
20
|
+
import { OxyServicesWorkspacesMixin } from './OxyServices.workspaces';
|
|
20
21
|
import { OxyServicesLocationMixin } from './OxyServices.location';
|
|
21
22
|
import { OxyServicesAnalyticsMixin } from './OxyServices.analytics';
|
|
22
23
|
import { OxyServicesDevicesMixin } from './OxyServices.devices';
|
|
@@ -36,7 +37,7 @@ import { OxyServicesAppDataMixin } from './OxyServices.appData';
|
|
|
36
37
|
* If you add a new mixin to `MIXIN_PIPELINE`, add it here too so its methods
|
|
37
38
|
* are visible without a cast.
|
|
38
39
|
*/
|
|
39
|
-
type AllMixinInstances = InstanceType<ReturnType<typeof OxyServicesAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFedCMMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPopupAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesRedirectAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSsoMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUserMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPrivacyMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLanguageMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPaymentMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesKarmaMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAssetsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesApplicationsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLocationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAnalyticsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDevicesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSecurityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFeaturesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesTopicsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesManagedAccountsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesContactsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAppDataMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUtilityMixin<typeof OxyServicesBase>>>;
|
|
40
|
+
type AllMixinInstances = InstanceType<ReturnType<typeof OxyServicesAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFedCMMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPopupAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesRedirectAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSsoMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUserMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPrivacyMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLanguageMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPaymentMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesKarmaMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAssetsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesApplicationsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesWorkspacesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLocationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAnalyticsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDevicesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSecurityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFeaturesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesTopicsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesManagedAccountsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesContactsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAppDataMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUtilityMixin<typeof OxyServicesBase>>>;
|
|
40
41
|
/**
|
|
41
42
|
* Constructor type for the fully composed mixin pipeline. Each mixin returns
|
|
42
43
|
* a new constructor that augments its input; reducing across the pipeline
|
|
@@ -17,6 +17,15 @@ export interface OxyConfig {
|
|
|
17
17
|
sessionBaseUrl?: string;
|
|
18
18
|
authWebUrl?: string;
|
|
19
19
|
authRedirectUri?: string;
|
|
20
|
+
/**
|
|
21
|
+
* The app's Oxy OAuth client id (ApplicationCredential publicKey).
|
|
22
|
+
*
|
|
23
|
+
* Identifies this app in OAuth authorize / consent flows (issue #214). Purely
|
|
24
|
+
* declarative: the SDK stores it on `OxyServices.config.clientId` for later
|
|
25
|
+
* OAuth-authorize use. It is unrelated to the cross-domain `/sso?client_id=…`
|
|
26
|
+
* bounce (which uses the RP origin, not this registered client id).
|
|
27
|
+
*/
|
|
28
|
+
clientId?: string;
|
|
20
29
|
enableCache?: boolean;
|
|
21
30
|
cacheTTL?: number;
|
|
22
31
|
enableRequestDeduplication?: boolean;
|
package/package.json
CHANGED
package/src/AuthManager.ts
CHANGED
|
@@ -851,7 +851,14 @@ export class AuthManager {
|
|
|
851
851
|
* Get a valid access token, refreshing automatically if expired or expiring soon.
|
|
852
852
|
*/
|
|
853
853
|
async getAccessToken(): Promise<string | null> {
|
|
854
|
-
|
|
854
|
+
// In cookieOnly / cookie-restore flows the active access token lives only in
|
|
855
|
+
// memory (`_lastKnownAccessToken` + httpService) and is intentionally never
|
|
856
|
+
// written to JS storage — the cookieOnly contract forbids persisting tokens
|
|
857
|
+
// in JS-accessible storage. Fall back to the in-memory token when storage has
|
|
858
|
+
// none, otherwise getAccessToken returns null after every cold-boot/reload and
|
|
859
|
+
// standalone API clients (e.g. the Console axios client) send no Authorization
|
|
860
|
+
// header → 401 on every authed endpoint while `isAuthenticated` is still true.
|
|
861
|
+
const token = (await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN)) ?? this._lastKnownAccessToken;
|
|
855
862
|
if (!token) return null;
|
|
856
863
|
|
|
857
864
|
try {
|
|
@@ -862,7 +869,9 @@ export class AuthManager {
|
|
|
862
869
|
if (decoded.exp - now < buffer) {
|
|
863
870
|
const refreshed = await this.refreshToken();
|
|
864
871
|
if (refreshed) {
|
|
865
|
-
|
|
872
|
+
// refreshToken() updates both storage and `_lastKnownAccessToken`;
|
|
873
|
+
// prefer storage but fall back to memory for the cookieOnly path.
|
|
874
|
+
return (await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN)) ?? this._lastKnownAccessToken;
|
|
866
875
|
}
|
|
867
876
|
}
|
|
868
877
|
}
|
package/src/OxyServices.ts
CHANGED
|
@@ -86,6 +86,7 @@ import { composeOxyServices } from './mixins';
|
|
|
86
86
|
* - **Karma**: Karma system
|
|
87
87
|
* - **Assets**: File upload and asset management
|
|
88
88
|
* - **Applications**: Application, membership, and credential management
|
|
89
|
+
* - **Workspaces**: Workspace and membership management
|
|
89
90
|
* - **Location**: Location-based features
|
|
90
91
|
* - **Analytics**: Analytics tracking
|
|
91
92
|
* - **Devices**: Device management
|
|
@@ -337,3 +337,52 @@ describe('AuthManager.initialize (cookieOnly)', () => {
|
|
|
337
337
|
expect(services.httpService.setTokens).not.toHaveBeenCalled();
|
|
338
338
|
});
|
|
339
339
|
});
|
|
340
|
+
|
|
341
|
+
describe('AuthManager.getAccessToken (cookieOnly in-memory fallback)', () => {
|
|
342
|
+
it('returns the in-memory token after a cold-boot cookie restore even though storage holds no token', async () => {
|
|
343
|
+
// Regression: the cookie restore path plants the active token ONLY in memory
|
|
344
|
+
// (`_lastKnownAccessToken` + httpService) and intentionally NEVER writes
|
|
345
|
+
// `oxy_access_token`. getAccessToken() must fall back to that in-memory token,
|
|
346
|
+
// otherwise standalone API clients reading `authManager.getAccessToken()` send
|
|
347
|
+
// no Authorization header → 401 on every authed endpoint after every reload.
|
|
348
|
+
const services = makeMockServices();
|
|
349
|
+
services.refreshAllSessions.mockResolvedValueOnce(TWO_ACCOUNTS);
|
|
350
|
+
const storage = new InMemoryStorage();
|
|
351
|
+
const manager = makeManager(services, storage);
|
|
352
|
+
|
|
353
|
+
await manager.restoreFromCookies();
|
|
354
|
+
|
|
355
|
+
// Storage was never touched for the access token (cookieOnly contract holds).
|
|
356
|
+
expect(storage.has('oxy_access_token')).toBe(false);
|
|
357
|
+
|
|
358
|
+
// getAccessToken still resolves the active slot's token from memory.
|
|
359
|
+
const token = await manager.getAccessToken();
|
|
360
|
+
expect(token).toBe(TOKEN_SLOT_0);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('returns null when neither storage nor the in-memory token is present', async () => {
|
|
364
|
+
const services = makeMockServices();
|
|
365
|
+
const storage = new InMemoryStorage();
|
|
366
|
+
const manager = makeManager(services, storage);
|
|
367
|
+
|
|
368
|
+
const token = await manager.getAccessToken();
|
|
369
|
+
expect(token).toBeNull();
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('prefers the storage token over the in-memory token when both are present', async () => {
|
|
373
|
+
const services = makeMockServices();
|
|
374
|
+
services.refreshAllSessions.mockResolvedValueOnce(TWO_ACCOUNTS);
|
|
375
|
+
const storage = new InMemoryStorage();
|
|
376
|
+
const manager = makeManager(services, storage);
|
|
377
|
+
|
|
378
|
+
// After restore the in-memory token is TOKEN_SLOT_0.
|
|
379
|
+
await manager.restoreFromCookies();
|
|
380
|
+
|
|
381
|
+
// Simulate a path that DID write storage (legacy/bearer flow). Storage wins.
|
|
382
|
+
const STORAGE_TOKEN = buildAccessToken({ sessionId: 'sess-storage', userId: 'user-storage', exp: 9999999999 });
|
|
383
|
+
storage.setItem('oxy_access_token', STORAGE_TOKEN);
|
|
384
|
+
|
|
385
|
+
const token = await manager.getAccessToken();
|
|
386
|
+
expect(token).toBe(STORAGE_TOKEN);
|
|
387
|
+
});
|
|
388
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -67,6 +67,7 @@ export { OxyAppDataIdentifierError } from './mixins/OxyServices.appData';
|
|
|
67
67
|
// ---------------------------------------------------------------------------
|
|
68
68
|
export type {
|
|
69
69
|
Application,
|
|
70
|
+
PublicApplication,
|
|
70
71
|
ApplicationMember,
|
|
71
72
|
ApplicationCredential,
|
|
72
73
|
ApplicationRole,
|
|
@@ -92,6 +93,24 @@ export type {
|
|
|
92
93
|
ApplicationSuccessResult,
|
|
93
94
|
} from './mixins/OxyServices.applications';
|
|
94
95
|
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Workspaces (multi-user containers: membership, roles)
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
export type {
|
|
100
|
+
Workspace,
|
|
101
|
+
WorkspaceMember,
|
|
102
|
+
WorkspaceRole,
|
|
103
|
+
WorkspaceType,
|
|
104
|
+
WorkspaceStatus,
|
|
105
|
+
WorkspaceMemberStatus,
|
|
106
|
+
CreateWorkspaceInput,
|
|
107
|
+
UpdateWorkspaceInput,
|
|
108
|
+
InviteWorkspaceMemberInput,
|
|
109
|
+
UpdateWorkspaceMemberInput,
|
|
110
|
+
TransferWorkspaceOwnershipInput,
|
|
111
|
+
WorkspaceSuccessResult,
|
|
112
|
+
} from './mixins/OxyServices.workspaces';
|
|
113
|
+
|
|
95
114
|
// ---------------------------------------------------------------------------
|
|
96
115
|
// Auth helpers (token refresh, error normalisation, retry policies)
|
|
97
116
|
// ---------------------------------------------------------------------------
|