@oxyhq/core 4.0.1 → 5.1.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/HttpService.js +6 -18
- package/dist/cjs/OxyServices.base.js +0 -21
- package/dist/cjs/crypto/keyManager.js +7 -7
- package/dist/cjs/crypto/polyfill.js +6 -5
- package/dist/cjs/crypto/signatureService.js +44 -220
- package/dist/cjs/index.js +4 -8
- package/dist/cjs/mixins/OxyServices.accounts.js +90 -0
- package/dist/cjs/mixins/OxyServices.assets.js +68 -2
- package/dist/cjs/mixins/OxyServices.auth.js +3 -3
- package/dist/cjs/mixins/OxyServices.civic.js +3 -3
- package/dist/cjs/mixins/OxyServices.language.js +2 -2
- package/dist/cjs/mixins/OxyServices.utility.js +7 -95
- package/dist/cjs/utils/cacheKey.js +17 -19
- package/dist/cjs/utils/deviceManager.js +2 -2
- package/dist/cjs/utils/platform.js +0 -14
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +6 -18
- package/dist/esm/OxyServices.base.js +0 -21
- package/dist/esm/crypto/keyManager.js +4 -4
- package/dist/esm/crypto/polyfill.js +5 -4
- package/dist/esm/crypto/signatureService.js +39 -214
- package/dist/esm/index.js +1 -2
- package/dist/esm/mixins/OxyServices.accounts.js +90 -0
- package/dist/esm/mixins/OxyServices.assets.js +67 -1
- package/dist/esm/mixins/OxyServices.auth.js +1 -1
- package/dist/esm/mixins/OxyServices.civic.js +3 -3
- package/dist/esm/mixins/OxyServices.language.js +1 -1
- package/dist/esm/mixins/OxyServices.utility.js +6 -94
- package/dist/esm/utils/cacheKey.js +17 -19
- package/dist/esm/utils/deviceManager.js +1 -1
- package/dist/esm/utils/platform.js +0 -12
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +3 -6
- package/dist/types/OxyServices.base.d.ts +0 -17
- package/dist/types/crypto/polyfill.d.ts +2 -2
- package/dist/types/crypto/signatureService.d.ts +18 -84
- package/dist/types/index.d.ts +4 -5
- package/dist/types/mixins/OxyServices.accounts.d.ts +66 -5
- package/dist/types/mixins/OxyServices.analytics.d.ts +0 -2
- package/dist/types/mixins/OxyServices.appData.d.ts +0 -2
- package/dist/types/mixins/OxyServices.assets.d.ts +42 -3
- package/dist/types/mixins/OxyServices.auth.d.ts +0 -2
- package/dist/types/mixins/OxyServices.civic.d.ts +3 -5
- package/dist/types/mixins/OxyServices.connectedApps.d.ts +0 -2
- package/dist/types/mixins/OxyServices.contacts.d.ts +0 -2
- package/dist/types/mixins/OxyServices.devices.d.ts +0 -2
- package/dist/types/mixins/OxyServices.features.d.ts +0 -2
- package/dist/types/mixins/OxyServices.fedcm.d.ts +0 -2
- package/dist/types/mixins/OxyServices.identity.d.ts +8 -5
- package/dist/types/mixins/OxyServices.language.d.ts +0 -2
- package/dist/types/mixins/OxyServices.links.d.ts +0 -2
- package/dist/types/mixins/OxyServices.location.d.ts +0 -2
- package/dist/types/mixins/OxyServices.nodes.d.ts +0 -44
- package/dist/types/mixins/OxyServices.payment.d.ts +0 -2
- package/dist/types/mixins/OxyServices.privacy.d.ts +0 -2
- package/dist/types/mixins/OxyServices.redirect.d.ts +0 -2
- package/dist/types/mixins/OxyServices.reputation.d.ts +0 -2
- package/dist/types/mixins/OxyServices.security.d.ts +0 -2
- package/dist/types/mixins/OxyServices.silent.d.ts +0 -2
- package/dist/types/mixins/OxyServices.sso.d.ts +0 -2
- package/dist/types/mixins/OxyServices.topics.d.ts +0 -2
- package/dist/types/mixins/OxyServices.user.d.ts +0 -2
- package/dist/types/mixins/OxyServices.utility.d.ts +0 -32
- package/dist/types/models/interfaces.d.ts +18 -0
- package/dist/types/server/auth.d.ts +0 -6
- package/dist/types/server/index.d.ts +1 -1
- package/dist/types/utils/cacheKey.d.ts +6 -7
- package/dist/types/utils/platform.d.ts +0 -8
- package/package.json +4 -7
- package/src/HttpService.ts +6 -22
- package/src/OxyServices.base.ts +0 -23
- package/src/__tests__/httpServiceCache.test.ts +0 -19
- package/src/crypto/__tests__/keyManager.atomicity.test.ts +2 -1
- package/src/crypto/__tests__/keyManager.test.ts +9 -7
- package/src/crypto/__tests__/signChallengeShared.test.ts +2 -1
- package/src/crypto/__tests__/signedRecord.test.ts +37 -150
- package/src/crypto/keyManager.ts +28 -17
- package/src/crypto/polyfill.ts +5 -4
- package/src/crypto/signatureService.ts +67 -255
- package/src/index.ts +4 -3
- package/src/mixins/OxyServices.accounts.ts +136 -3
- package/src/mixins/OxyServices.assets.ts +96 -2
- package/src/mixins/OxyServices.auth.ts +1 -1
- package/src/mixins/OxyServices.civic.ts +6 -17
- package/src/mixins/OxyServices.identity.ts +8 -2
- package/src/mixins/OxyServices.language.ts +1 -1
- package/src/mixins/OxyServices.nodes.ts +1 -12
- package/src/mixins/OxyServices.utility.ts +6 -119
- package/src/mixins/__tests__/OxyServices.civic.test.ts +2 -2
- package/src/mixins/__tests__/OxyServices.serviceAssetMetadata.test.ts +116 -0
- package/src/mixins/__tests__/accounts.test.ts +120 -0
- package/src/models/interfaces.ts +19 -0
- package/src/server/auth.ts +0 -7
- package/src/server/index.ts +0 -1
- package/src/utils/__tests__/cacheKey.test.ts +0 -0
- package/src/utils/cacheKey.ts +16 -21
- package/src/utils/deviceManager.ts +1 -1
- package/src/utils/platform.ts +0 -14
- package/dist/cjs/crypto/canonicalJson.js +0 -107
- package/dist/cjs/utils/platformCrypto.js +0 -165
- package/dist/cjs/utils/platformCrypto.native.js +0 -123
- package/dist/esm/crypto/canonicalJson.js +0 -104
- package/dist/esm/utils/platformCrypto.js +0 -125
- package/dist/esm/utils/platformCrypto.native.js +0 -80
- package/dist/types/crypto/canonicalJson.d.ts +0 -44
- package/dist/types/utils/platformCrypto.d.ts +0 -87
- package/dist/types/utils/platformCrypto.native.d.ts +0 -54
- package/src/crypto/__tests__/canonicalJson.test.ts +0 -116
- package/src/crypto/canonicalJson.ts +0 -120
- package/src/utils/platformCrypto.native.ts +0 -101
- package/src/utils/platformCrypto.ts +0 -145
|
@@ -1,45 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User-Node Methods Mixin (self-sovereign identity layer — Fase 5 user nodes)
|
|
3
|
-
*
|
|
4
|
-
* The client surface for a user's personal data NODE — the decentralised store
|
|
5
|
-
* that holds an authentic copy of their signed-record chain. Commons drives all
|
|
6
|
-
* of this:
|
|
7
|
-
*
|
|
8
|
-
* - {@link OxyServicesNodesMixin.registerNode} registers (or re-registers) a
|
|
9
|
-
* SELF-HOSTED node. Registration is NOT a bespoke endpoint — it is a signed
|
|
10
|
-
* `type:'node'` v2 record (`collection: 'app.oxy.node'`, `rkey: 'self'`,
|
|
11
|
-
* last-writer-wins) published through the EXISTING `POST /identity/records`
|
|
12
|
-
* path; the server verifies it and materializes the operational
|
|
13
|
-
* {@link UserNodeStatus} cache as a side effect, so the registration's
|
|
14
|
-
* authority is the user's own signature, never an Oxy grant.
|
|
15
|
-
* - {@link OxyServicesNodesMixin.getMyNode} reads the caller's cached node
|
|
16
|
-
* status (`GET /nodes/me`) — the fast, stale-but-instant projection plus the
|
|
17
|
-
* live liveness badge Oxy maintains with background probes.
|
|
18
|
-
* - {@link OxyServicesNodesMixin.removeMyNode} revokes the registration
|
|
19
|
-
* (`DELETE /nodes/me`) so the node leaves the DID document and the liveness
|
|
20
|
-
* sweeps.
|
|
21
|
-
* - {@link OxyServicesNodesMixin.provisionManagedVault} asks Oxy to operate a
|
|
22
|
-
* MANAGED vault on the caller's behalf (`POST /nodes/managed`) — the
|
|
23
|
-
* "Create your vault" convenience for non-technical users (Oxy custodial-signs
|
|
24
|
-
* the node record; `managed:true, controller:'oxy'`).
|
|
25
|
-
* - {@link OxyServicesNodesMixin.notifyNodeIngest} sends an unauthenticated
|
|
26
|
-
* HINT (`POST /nodes/ingest/notify/:userId`) that a user's node has new
|
|
27
|
-
* records; the server fully re-verifies before ingesting, so the hint can
|
|
28
|
-
* never inject data.
|
|
29
|
-
*
|
|
30
|
-
* `registerNode` signs on the caller's per-subject hash chain with the on-device
|
|
31
|
-
* identity key (reusing {@link SignatureService.signRecordV2} — the same
|
|
32
|
-
* `ES256K-DER-SHA256` scheme + {@link signedRecordSigningInput} the identity and
|
|
33
|
-
* civic mixins use), so it is NATIVE-ONLY: it throws on web (where `KeyManager`
|
|
34
|
-
* has no key) and when no user is authenticated. Reading the node status,
|
|
35
|
-
* revoking, provisioning a managed vault, and sending an ingest hint are plain
|
|
36
|
-
* authenticated/public requests with no signing.
|
|
37
|
-
*
|
|
38
|
-
* The wire shapes here are API-INTERNAL (the F5 user-node surface is not yet a
|
|
39
|
-
* published `@oxyhq/contracts` schema), so {@link UserNodeStatus} mirrors the
|
|
40
|
-
* server's `serializeNode` projection exactly. Dates cross the wire as ISO
|
|
41
|
-
* strings.
|
|
42
|
-
*/
|
|
43
1
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
44
2
|
/** How Oxy and the node move records: the node pulls (default), or Oxy pushes. */
|
|
45
3
|
export type UserNodeMode = 'pull' | 'push';
|
|
@@ -223,8 +181,6 @@ export declare function OxyServicesNodesMixin<T extends typeof OxyServicesBase>(
|
|
|
223
181
|
hasValidToken(): boolean;
|
|
224
182
|
getAccessToken(): string | null;
|
|
225
183
|
getAccessTokenExpiry(): number | null;
|
|
226
|
-
setActingAs(userId: string | null): void;
|
|
227
|
-
getActingAs(): string | null;
|
|
228
184
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
229
185
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
230
186
|
maxRetries?: number;
|
|
@@ -100,8 +100,6 @@ export declare function OxyServicesPaymentMixin<T extends typeof OxyServicesBase
|
|
|
100
100
|
hasValidToken(): boolean;
|
|
101
101
|
getAccessToken(): string | null;
|
|
102
102
|
getAccessTokenExpiry(): number | null;
|
|
103
|
-
setActingAs(userId: string | null): void;
|
|
104
|
-
getActingAs(): string | null;
|
|
105
103
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
106
104
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
107
105
|
maxRetries?: number;
|
|
@@ -129,8 +129,6 @@ export declare function OxyServicesPrivacyMixin<T extends typeof OxyServicesBase
|
|
|
129
129
|
hasValidToken(): boolean;
|
|
130
130
|
getAccessToken(): string | null;
|
|
131
131
|
getAccessTokenExpiry(): number | null;
|
|
132
|
-
setActingAs(userId: string | null): void;
|
|
133
|
-
getActingAs(): string | null;
|
|
134
132
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
135
133
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
136
134
|
maxRetries?: number;
|
|
@@ -72,8 +72,6 @@ export declare function OxyServicesRedirectAuthMixin<T extends typeof OxyService
|
|
|
72
72
|
hasValidToken(): boolean;
|
|
73
73
|
getAccessToken(): string | null;
|
|
74
74
|
getAccessTokenExpiry(): number | null;
|
|
75
|
-
setActingAs(userId: string | null): void;
|
|
76
|
-
getActingAs(): string | null;
|
|
77
75
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
78
76
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
79
77
|
maxRetries?: number;
|
|
@@ -418,8 +418,6 @@ export declare function OxyServicesReputationMixin<T extends typeof OxyServicesB
|
|
|
418
418
|
hasValidToken(): boolean;
|
|
419
419
|
getAccessToken(): string | null;
|
|
420
420
|
getAccessTokenExpiry(): number | null;
|
|
421
|
-
setActingAs(userId: string | null): void;
|
|
422
|
-
getActingAs(): string | null;
|
|
423
421
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
424
422
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
425
423
|
maxRetries?: number;
|
|
@@ -67,8 +67,6 @@ export declare function OxyServicesSecurityMixin<T extends typeof OxyServicesBas
|
|
|
67
67
|
hasValidToken(): boolean;
|
|
68
68
|
getAccessToken(): string | null;
|
|
69
69
|
getAccessTokenExpiry(): number | null;
|
|
70
|
-
setActingAs(userId: string | null): void;
|
|
71
|
-
getActingAs(): string | null;
|
|
72
70
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
73
71
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
74
72
|
maxRetries?: number;
|
|
@@ -110,8 +110,6 @@ export declare function OxyServicesSilentAuthMixin<T extends typeof OxyServicesB
|
|
|
110
110
|
hasValidToken(): boolean;
|
|
111
111
|
getAccessToken(): string | null;
|
|
112
112
|
getAccessTokenExpiry(): number | null;
|
|
113
|
-
setActingAs(userId: string | null): void;
|
|
114
|
-
getActingAs(): string | null;
|
|
115
113
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
116
114
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
117
115
|
maxRetries?: number;
|
|
@@ -95,8 +95,6 @@ export declare function OxyServicesSsoMixin<T extends typeof OxyServicesBase>(Ba
|
|
|
95
95
|
hasValidToken(): boolean;
|
|
96
96
|
getAccessToken(): string | null;
|
|
97
97
|
getAccessTokenExpiry(): number | null;
|
|
98
|
-
setActingAs(userId: string | null): void;
|
|
99
|
-
getActingAs(): string | null;
|
|
100
98
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
101
99
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
102
100
|
maxRetries?: number;
|
|
@@ -93,8 +93,6 @@ export declare function OxyServicesTopicsMixin<T extends typeof OxyServicesBase>
|
|
|
93
93
|
hasValidToken(): boolean;
|
|
94
94
|
getAccessToken(): string | null;
|
|
95
95
|
getAccessTokenExpiry(): number | null;
|
|
96
|
-
setActingAs(userId: string | null): void;
|
|
97
|
-
getActingAs(): string | null;
|
|
98
96
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
99
97
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
100
98
|
maxRetries?: number;
|
|
@@ -364,8 +364,6 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
|
|
|
364
364
|
hasValidToken(): boolean;
|
|
365
365
|
getAccessToken(): string | null;
|
|
366
366
|
getAccessTokenExpiry(): number | null;
|
|
367
|
-
setActingAs(userId: string | null): void;
|
|
368
|
-
getActingAs(): string | null;
|
|
369
367
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
370
368
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
371
369
|
maxRetries?: number;
|
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
import type { ApiError, User } from '../models/interfaces';
|
|
2
2
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
3
|
-
/**
|
|
4
|
-
* Result from the account acting-as verification endpoint
|
|
5
|
-
* (`GET /accounts/verify-acting-as`). Indicates whether a user is authorized to
|
|
6
|
-
* act as a given account. The `account:act_as` capability is granted only to the
|
|
7
|
-
* `owner`, `admin`, and `editor` account roles.
|
|
8
|
-
*/
|
|
9
|
-
interface ActingAsVerification {
|
|
10
|
-
authorized: boolean;
|
|
11
|
-
role: 'owner' | 'admin' | 'editor';
|
|
12
|
-
}
|
|
13
3
|
/**
|
|
14
4
|
* Result from the service-acting-as verification endpoint.
|
|
15
5
|
* Confirms that a given service app holds an active delegation grant for
|
|
@@ -76,11 +66,6 @@ interface AuthMiddlewareOptions {
|
|
|
76
66
|
}
|
|
77
67
|
export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
78
68
|
new (...args: any[]): {
|
|
79
|
-
/** @internal In-memory cache for acting-as verification results (TTL: 5 min) */
|
|
80
|
-
_actingAsCache: Map<string, {
|
|
81
|
-
result: ActingAsVerification | null;
|
|
82
|
-
expiresAt: number;
|
|
83
|
-
}>;
|
|
84
69
|
/**
|
|
85
70
|
* In-memory cache for service-acting-as verification.
|
|
86
71
|
* Negative results are cached for 1min to avoid hammering the verify
|
|
@@ -92,14 +77,6 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
92
77
|
result: ServiceActingAsVerification | null;
|
|
93
78
|
expiresAt: number;
|
|
94
79
|
}>;
|
|
95
|
-
/**
|
|
96
|
-
* Verify that a user is authorized to act as an account (direct membership
|
|
97
|
-
* or inherited via an ancestor). Backed by `GET /accounts/verify-acting-as`.
|
|
98
|
-
* Results are cached in-memory for 5 minutes to avoid repeated API calls.
|
|
99
|
-
*
|
|
100
|
-
* @internal Used by the auth() middleware — not part of the public API
|
|
101
|
-
*/
|
|
102
|
-
verifyActingAs(userId: string, accountId: string): Promise<ActingAsVerification | null>;
|
|
103
80
|
/**
|
|
104
81
|
* Verify that a service app holds an active delegation grant authorising
|
|
105
82
|
* it to act on behalf of `userId`. Returns the grant (with allowed scopes)
|
|
@@ -281,8 +258,6 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
281
258
|
hasValidToken(): boolean;
|
|
282
259
|
getAccessToken(): string | null;
|
|
283
260
|
getAccessTokenExpiry(): number | null;
|
|
284
|
-
setActingAs(userId: string | null): void;
|
|
285
|
-
getActingAs(): string | null;
|
|
286
261
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
287
262
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
288
263
|
maxRetries?: number;
|
|
@@ -313,13 +288,6 @@ interface AuthReq {
|
|
|
313
288
|
userId: string;
|
|
314
289
|
scopes: string[];
|
|
315
290
|
};
|
|
316
|
-
actingAs?: {
|
|
317
|
-
userId: string;
|
|
318
|
-
role: string;
|
|
319
|
-
};
|
|
320
|
-
originalUser?: {
|
|
321
|
-
id: string;
|
|
322
|
-
} & Partial<User>;
|
|
323
291
|
}
|
|
324
292
|
interface AuthRes {
|
|
325
293
|
status(code: number): AuthRes;
|
|
@@ -447,6 +447,24 @@ export interface AssetUpdateVisibilityResponse {
|
|
|
447
447
|
updatedAt: string;
|
|
448
448
|
};
|
|
449
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* Minimal, service-token-scoped asset metadata returned by
|
|
452
|
+
* `POST /assets/service/by-ids`.
|
|
453
|
+
*
|
|
454
|
+
* Resolves an Oxy asset `id` to its content-addressed identity (`sha256`),
|
|
455
|
+
* MIME type, byte `size`, and storage `status`. Used by server-to-server
|
|
456
|
+
* callers (e.g. Mention's MTN Protocol blob-ref resolution) that hold a
|
|
457
|
+
* `files:read`-scoped service token rather than a user session. Unknown or
|
|
458
|
+
* deleted ids are omitted from the response (never error the whole batch),
|
|
459
|
+
* so the result may be shorter than the requested id list.
|
|
460
|
+
*/
|
|
461
|
+
export interface ServiceAssetMetadata {
|
|
462
|
+
id: string;
|
|
463
|
+
sha256: string;
|
|
464
|
+
mime: string;
|
|
465
|
+
size: number;
|
|
466
|
+
status: 'active' | 'trash';
|
|
467
|
+
}
|
|
450
468
|
/**
|
|
451
469
|
* Account storage usage (server-side usage, not local AsyncStorage)
|
|
452
470
|
*/
|
|
@@ -8,10 +8,6 @@ export interface OxyRequestUser {
|
|
|
8
8
|
avatar?: string;
|
|
9
9
|
[key: string]: unknown;
|
|
10
10
|
}
|
|
11
|
-
export interface OxyActingAsContext {
|
|
12
|
-
userId: string;
|
|
13
|
-
role: 'owner' | 'admin' | 'editor';
|
|
14
|
-
}
|
|
15
11
|
export interface OxyServiceAppContext {
|
|
16
12
|
appId: string;
|
|
17
13
|
appName: string;
|
|
@@ -25,8 +21,6 @@ export interface OxyServiceActingAsContext {
|
|
|
25
21
|
export interface OxyAuthRequest extends Request {
|
|
26
22
|
userId?: string | null;
|
|
27
23
|
user?: OxyRequestUser | null;
|
|
28
|
-
originalUser?: OxyRequestUser | null;
|
|
29
|
-
actingAs?: OxyActingAsContext;
|
|
30
24
|
accessToken?: string;
|
|
31
25
|
sessionId?: string | null;
|
|
32
26
|
serviceApp?: OxyServiceAppContext;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
17
|
export { createOptionalOxyAuth, createOxyAuthMiddleware, getOxyUserId, getRequiredOxyUserId, isOxyAuthenticated, requireOxyAuth, } from './auth';
|
|
18
|
-
export type {
|
|
18
|
+
export type { OxyAuthenticatedRequest, OxyAuthMiddlewareOptions, OxyAuthRequest, OxyRequestUser, OxyServiceActingAsContext, OxyServiceAppContext, } from './auth';
|
|
19
19
|
export { createOxyRateLimit } from './rateLimit';
|
|
20
20
|
export type { OxyRateLimitOptions } from './rateLimit';
|
|
21
21
|
export { assertSafePublicUrl, isBlockedIp, safeFetch, SsrfRejection, UpstreamError, ALLOWED_PORTS, ALLOWED_PROTOCOLS, BLOCKED_HOSTNAMES, DEFAULT_USER_AGENT, MAX_REDIRECTS, MAX_URL_LENGTH, UPSTREAM_HEADERS_TIMEOUT_MS, } from './safeFetch';
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Extracted from {@link HttpService} so the identity-tag derivation is a pure,
|
|
5
5
|
* independently testable function with no dependency on instance/token state.
|
|
6
|
-
* The HTTP service injects the live access token
|
|
7
|
-
*
|
|
6
|
+
* The HTTP service injects the live access token; everything here is
|
|
7
|
+
* referentially transparent given that input.
|
|
8
8
|
*/
|
|
9
9
|
/**
|
|
10
10
|
* Minimal JWT payload shape we read for cache scoping. The identity discriminator
|
|
@@ -57,11 +57,10 @@ export declare function fnv1a32(str: string): string;
|
|
|
57
57
|
*
|
|
58
58
|
* We use the decoded user id rather than the raw JWT so the token never lands
|
|
59
59
|
* in a cache key (no token leakage through any cache-key logging, no key bloat).
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
60
|
+
* Switching into a managed account mints a REAL new session whose access token
|
|
61
|
+
* carries the target account's id, so the identity tag changes naturally on a
|
|
62
|
+
* switch — there is no separate acting-as discriminator to fold in.
|
|
63
63
|
*
|
|
64
64
|
* @param accessToken The current bearer access token, or `null` when anonymous.
|
|
65
|
-
* @param actingAsUserId The active managed-account id, or `null`.
|
|
66
65
|
*/
|
|
67
|
-
export declare function computeIdentityTag(accessToken: string | null
|
|
66
|
+
export declare function computeIdentityTag(accessToken: string | null): string;
|
|
@@ -27,14 +27,6 @@ export declare function isIOS(): boolean;
|
|
|
27
27
|
* Check if running on Android
|
|
28
28
|
*/
|
|
29
29
|
export declare function isAndroid(): boolean;
|
|
30
|
-
/**
|
|
31
|
-
* Check if running in React Native
|
|
32
|
-
*/
|
|
33
|
-
export declare function isReactNative(): boolean;
|
|
34
|
-
/**
|
|
35
|
-
* Check if running in Node.js
|
|
36
|
-
*/
|
|
37
|
-
export declare function isNodeJS(): boolean;
|
|
38
30
|
/**
|
|
39
31
|
* Set the platform OS explicitly
|
|
40
32
|
* Called by React Native entry point to register the platform
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -43,10 +43,6 @@
|
|
|
43
43
|
},
|
|
44
44
|
"./package.json": "./package.json"
|
|
45
45
|
},
|
|
46
|
-
"react-native": {
|
|
47
|
-
"./dist/esm/utils/platformCrypto.js": "./dist/esm/utils/platformCrypto.native.js",
|
|
48
|
-
"./dist/cjs/utils/platformCrypto.js": "./dist/cjs/utils/platformCrypto.native.js"
|
|
49
|
-
},
|
|
50
46
|
"files": [
|
|
51
47
|
"dist",
|
|
52
48
|
"src"
|
|
@@ -98,14 +94,15 @@
|
|
|
98
94
|
}
|
|
99
95
|
},
|
|
100
96
|
"dependencies": {
|
|
101
|
-
"@oxyhq/contracts": "^0.
|
|
97
|
+
"@oxyhq/contracts": "^0.7.0",
|
|
98
|
+
"@oxyhq/protocol": "^0.1.1",
|
|
102
99
|
"bip39": "^3.1.0",
|
|
103
100
|
"buffer": "^6.0.3",
|
|
104
101
|
"elliptic": "^6.6.1",
|
|
105
102
|
"invariant": "^2.2.4",
|
|
106
103
|
"jwt-decode": "^4.0.0",
|
|
107
104
|
"socket.io-client": "^4.8.1",
|
|
108
|
-
"tldts": "^7.
|
|
105
|
+
"tldts": "^7.4.5",
|
|
109
106
|
"zod": "^3.25.64"
|
|
110
107
|
},
|
|
111
108
|
"peerDependencies": {
|
package/src/HttpService.ts
CHANGED
|
@@ -18,7 +18,8 @@ import { RequestDeduplicator, RequestQueue, SimpleLogger } from './utils/request
|
|
|
18
18
|
import { retryAsync } from './utils/asyncUtils';
|
|
19
19
|
import { handleHttpError } from './utils/errorUtils';
|
|
20
20
|
import { jwtDecode } from 'jwt-decode';
|
|
21
|
-
import { isNative,
|
|
21
|
+
import { isNative, getPlatformOS } from './utils/platform';
|
|
22
|
+
import { isReactNative } from '@oxyhq/protocol';
|
|
22
23
|
import { computeIdentityTag, fnv1a32 } from './utils/cacheKey';
|
|
23
24
|
import type { OxyConfig } from './models/interfaces';
|
|
24
25
|
|
|
@@ -238,9 +239,6 @@ export class HttpService {
|
|
|
238
239
|
*/
|
|
239
240
|
private _tokenChangeListeners = new Set<(accessToken: string | null) => void>();
|
|
240
241
|
|
|
241
|
-
// Acting-as identity for managed accounts
|
|
242
|
-
private _actingAsUserId: string | null = null;
|
|
243
|
-
|
|
244
242
|
// Performance monitoring
|
|
245
243
|
private requestMetrics = {
|
|
246
244
|
totalRequests: 0,
|
|
@@ -466,11 +464,6 @@ export class HttpService {
|
|
|
466
464
|
});
|
|
467
465
|
}
|
|
468
466
|
|
|
469
|
-
// Add X-Acting-As header for managed account identity delegation
|
|
470
|
-
if (this._actingAsUserId) {
|
|
471
|
-
headers['X-Acting-As'] = this._actingAsUserId;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
467
|
// Merge custom headers if provided
|
|
475
468
|
if (config.headers) {
|
|
476
469
|
Object.entries(config.headers).forEach(([key, value]) => {
|
|
@@ -820,12 +813,12 @@ export class HttpService {
|
|
|
820
813
|
* Derive a stable, non-sensitive identity discriminator for cache scoping.
|
|
821
814
|
*
|
|
822
815
|
* Thin instance wrapper over the pure {@link computeIdentityTag} helper —
|
|
823
|
-
* binds it to this instance's live access token
|
|
824
|
-
*
|
|
825
|
-
*
|
|
816
|
+
* binds it to this instance's live access token. See that function's docs for
|
|
817
|
+
* the full resolution contract (anon fallback, decoded `userId || id`,
|
|
818
|
+
* token-hash fallback for undecodable tokens).
|
|
826
819
|
*/
|
|
827
820
|
private computeIdentityTag(): string {
|
|
828
|
-
return computeIdentityTag(this.tokenStore.getAccessToken()
|
|
821
|
+
return computeIdentityTag(this.tokenStore.getAccessToken());
|
|
829
822
|
}
|
|
830
823
|
|
|
831
824
|
/**
|
|
@@ -1107,15 +1100,6 @@ export class HttpService {
|
|
|
1107
1100
|
return this.request<T>({ method: 'DELETE', url, ...config });
|
|
1108
1101
|
}
|
|
1109
1102
|
|
|
1110
|
-
// Acting-as identity management (managed accounts)
|
|
1111
|
-
setActingAs(userId: string | null): void {
|
|
1112
|
-
this._actingAsUserId = userId;
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
getActingAs(): string | null {
|
|
1116
|
-
return this._actingAsUserId;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
1103
|
// Token management
|
|
1120
1104
|
setTokens(accessToken: string): void {
|
|
1121
1105
|
this.tokenStore.setTokens(accessToken);
|
package/src/OxyServices.base.ts
CHANGED
|
@@ -335,29 +335,6 @@ export class OxyServicesBase {
|
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
/**
|
|
339
|
-
* Set the acting-as identity for managed accounts.
|
|
340
|
-
*
|
|
341
|
-
* When set, all subsequent API requests will include the `X-Acting-As` header,
|
|
342
|
-
* causing the server to attribute actions to the managed account. The
|
|
343
|
-
* authenticated user must be an authorized manager of the target account.
|
|
344
|
-
*
|
|
345
|
-
* Pass `null` to clear and revert to the authenticated user's own identity.
|
|
346
|
-
*
|
|
347
|
-
* @param userId - The managed account user ID, or null to clear
|
|
348
|
-
*/
|
|
349
|
-
public setActingAs(userId: string | null): void {
|
|
350
|
-
this.httpService.setActingAs(userId);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Get the current acting-as identity (managed account user ID), or null
|
|
355
|
-
* if operating as the authenticated user's own identity.
|
|
356
|
-
*/
|
|
357
|
-
public getActingAs(): string | null {
|
|
358
|
-
return this.httpService.getActingAs();
|
|
359
|
-
}
|
|
360
|
-
|
|
361
338
|
/**
|
|
362
339
|
* Wait for authentication to be ready
|
|
363
340
|
*
|
|
@@ -112,25 +112,6 @@ describe('HttpService identity-scoped response cache', () => {
|
|
|
112
112
|
expect(u2.profiles).toEqual(['for-user-2']);
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
-
it('partitions the cache by acting-as identity for the same bearer token', async () => {
|
|
116
|
-
const http = newService();
|
|
117
|
-
http.setTokens(makeJwt({ userId: 'owner-1' }));
|
|
118
|
-
|
|
119
|
-
// Acting as managed account A.
|
|
120
|
-
http.setActingAs('managed-a');
|
|
121
|
-
fetchMock.mockResolvedValueOnce(jsonResponse({ items: ['a-only'] }));
|
|
122
|
-
const a = await http.get<{ items: string[] }>('/some/managed-resource', { cache: true });
|
|
123
|
-
expect(a.items).toEqual(['a-only']);
|
|
124
|
-
|
|
125
|
-
// Acting as managed account B — different content, must miss the cache.
|
|
126
|
-
http.setActingAs('managed-b');
|
|
127
|
-
fetchMock.mockResolvedValueOnce(jsonResponse({ items: ['b-only'] }));
|
|
128
|
-
const b = await http.get<{ items: string[] }>('/some/managed-resource', { cache: true });
|
|
129
|
-
|
|
130
|
-
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
131
|
-
expect(b.items).toEqual(['b-only']);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
115
|
it('does NOT serve an authenticated cached response to an anonymous caller after clearTokens()', async () => {
|
|
135
116
|
const http = newService();
|
|
136
117
|
http.setTokens(makeJwt({ userId: 'user-1' }));
|
|
@@ -83,8 +83,9 @@ jest.mock(
|
|
|
83
83
|
{ virtual: true },
|
|
84
84
|
);
|
|
85
85
|
|
|
86
|
-
jest.mock('
|
|
86
|
+
jest.mock('@oxyhq/protocol', () => ({
|
|
87
87
|
__esModule: true,
|
|
88
|
+
...jest.requireActual('@oxyhq/protocol'),
|
|
88
89
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
89
90
|
loadExpoCrypto: async () => require('expo-crypto'),
|
|
90
91
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -66,14 +66,16 @@ jest.mock(
|
|
|
66
66
|
);
|
|
67
67
|
|
|
68
68
|
// Production code routes platform-specific module loads through
|
|
69
|
-
// `
|
|
70
|
-
// (`
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
|
|
69
|
+
// `@oxyhq/protocol`'s `platform/crypto`, which ships in two physical variants
|
|
70
|
+
// on disk (`crypto.ts` / `crypto.native.ts`) selected by the consumer's
|
|
71
|
+
// bundler. Jest runs on Node — it picks the default variant, which references
|
|
72
|
+
// Node's built-in `crypto`, not `expo-*`. For the test suite to exercise the
|
|
73
|
+
// RN code paths, we override only the platform loaders to delegate to the
|
|
74
|
+
// virtual `expo-*` modules registered above, keeping every other protocol
|
|
75
|
+
// export (canonical bytes, signing, the platform predicates) real.
|
|
76
|
+
jest.mock('@oxyhq/protocol', () => ({
|
|
76
77
|
__esModule: true,
|
|
78
|
+
...jest.requireActual('@oxyhq/protocol'),
|
|
77
79
|
loadExpoCrypto: async () => {
|
|
78
80
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
79
81
|
return require('expo-crypto');
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { ec as EC } from 'elliptic';
|
|
12
|
+
import { verifySignature } from '@oxyhq/protocol';
|
|
12
13
|
import { KeyManager } from '../keyManager';
|
|
13
14
|
import { SignatureService } from '../signatureService';
|
|
14
15
|
|
|
@@ -40,7 +41,7 @@ describe('SignatureService.signChallengeWithSharedKey', () => {
|
|
|
40
41
|
// uses, proving the format is unchanged and the shared key signed it.
|
|
41
42
|
const message = `auth:${sharedPublicKey}:chal-123:${result.timestamp}`;
|
|
42
43
|
await expect(
|
|
43
|
-
|
|
44
|
+
verifySignature(message, result.challenge, sharedPublicKey),
|
|
44
45
|
).resolves.toBe(true);
|
|
45
46
|
});
|
|
46
47
|
|