@oxyhq/core 1.11.12 → 1.11.14
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/CrossDomainAuth.js +3 -1
- package/dist/cjs/HttpService.js +214 -33
- package/dist/cjs/OxyServices.base.js +9 -0
- package/dist/cjs/OxyServices.js +8 -3
- package/dist/cjs/crypto/index.js +3 -1
- package/dist/cjs/crypto/keyManager.js +476 -172
- package/dist/cjs/crypto/polyfill.js +14 -65
- package/dist/cjs/crypto/recoveryPhrase.js +30 -11
- package/dist/cjs/crypto/signatureService.js +25 -60
- package/dist/cjs/i18n/locales/en-US.json +46 -1
- package/dist/cjs/i18n/locales/es-ES.json +46 -1
- package/dist/cjs/i18n/locales/locales/en-US.json +46 -1
- package/dist/cjs/i18n/locales/locales/es-ES.json +46 -1
- package/dist/cjs/index.js +10 -2
- package/dist/cjs/mixins/OxyServices.assets.js +9 -4
- package/dist/cjs/mixins/OxyServices.auth.js +147 -14
- package/dist/cjs/mixins/OxyServices.contacts.js +50 -0
- package/dist/cjs/mixins/OxyServices.features.js +0 -11
- package/dist/cjs/mixins/OxyServices.fedcm.js +4 -3
- package/dist/cjs/mixins/OxyServices.language.js +5 -36
- package/dist/cjs/mixins/OxyServices.redirect.js +6 -2
- package/dist/cjs/mixins/OxyServices.security.js +13 -2
- package/dist/cjs/mixins/OxyServices.user.js +59 -38
- package/dist/cjs/mixins/OxyServices.utility.js +416 -110
- package/dist/cjs/mixins/index.js +11 -3
- package/dist/cjs/utils/accountUtils.js +71 -2
- package/dist/cjs/utils/deviceManager.js +5 -36
- package/dist/cjs/utils/languageUtils.js +22 -0
- package/dist/cjs/utils/platformCrypto.js +165 -0
- package/dist/cjs/utils/platformCrypto.native.js +123 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/CrossDomainAuth.js +3 -1
- package/dist/esm/HttpService.js +215 -34
- package/dist/esm/OxyServices.base.js +9 -0
- package/dist/esm/OxyServices.js +8 -3
- package/dist/esm/crypto/index.js +1 -1
- package/dist/esm/crypto/keyManager.js +473 -138
- package/dist/esm/crypto/polyfill.js +14 -32
- package/dist/esm/crypto/recoveryPhrase.js +30 -11
- package/dist/esm/crypto/signatureService.js +25 -27
- package/dist/esm/i18n/locales/en-US.json +46 -1
- package/dist/esm/i18n/locales/es-ES.json +46 -1
- package/dist/esm/i18n/locales/locales/en-US.json +46 -1
- package/dist/esm/i18n/locales/locales/es-ES.json +46 -1
- package/dist/esm/index.js +4 -3
- package/dist/esm/mixins/OxyServices.assets.js +9 -4
- package/dist/esm/mixins/OxyServices.auth.js +145 -14
- package/dist/esm/mixins/OxyServices.contacts.js +47 -0
- package/dist/esm/mixins/OxyServices.features.js +0 -11
- package/dist/esm/mixins/OxyServices.fedcm.js +4 -3
- package/dist/esm/mixins/OxyServices.language.js +5 -3
- package/dist/esm/mixins/OxyServices.redirect.js +6 -2
- package/dist/esm/mixins/OxyServices.security.js +13 -2
- package/dist/esm/mixins/OxyServices.user.js +59 -38
- package/dist/esm/mixins/OxyServices.utility.js +416 -77
- package/dist/esm/mixins/index.js +11 -3
- package/dist/esm/utils/accountUtils.js +67 -1
- package/dist/esm/utils/deviceManager.js +5 -3
- package/dist/esm/utils/languageUtils.js +21 -0
- package/dist/esm/utils/platformCrypto.js +125 -0
- package/dist/esm/utils/platformCrypto.native.js +80 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +47 -3
- package/dist/types/OxyServices.base.d.ts +7 -0
- package/dist/types/OxyServices.d.ts +50 -7
- package/dist/types/crypto/index.d.ts +1 -1
- package/dist/types/crypto/keyManager.d.ts +110 -9
- package/dist/types/crypto/polyfill.d.ts +3 -1
- package/dist/types/crypto/recoveryPhrase.d.ts +31 -7
- package/dist/types/crypto/signatureService.d.ts +4 -0
- package/dist/types/index.d.ts +7 -5
- package/dist/types/mixins/OxyServices.analytics.d.ts +1 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +6 -10
- package/dist/types/mixins/OxyServices.auth.d.ts +82 -5
- package/dist/types/mixins/OxyServices.contacts.d.ts +99 -0
- package/dist/types/mixins/OxyServices.developer.d.ts +1 -0
- package/dist/types/mixins/OxyServices.devices.d.ts +1 -0
- package/dist/types/mixins/OxyServices.features.d.ts +2 -7
- package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -0
- package/dist/types/mixins/OxyServices.karma.d.ts +1 -0
- package/dist/types/mixins/OxyServices.language.d.ts +1 -0
- package/dist/types/mixins/OxyServices.location.d.ts +1 -0
- package/dist/types/mixins/OxyServices.managedAccounts.d.ts +1 -0
- package/dist/types/mixins/OxyServices.payment.d.ts +1 -0
- package/dist/types/mixins/OxyServices.popup.d.ts +1 -0
- package/dist/types/mixins/OxyServices.privacy.d.ts +1 -0
- package/dist/types/mixins/OxyServices.redirect.d.ts +1 -0
- package/dist/types/mixins/OxyServices.security.d.ts +1 -0
- package/dist/types/mixins/OxyServices.topics.d.ts +1 -0
- package/dist/types/mixins/OxyServices.user.d.ts +28 -11
- package/dist/types/mixins/OxyServices.utility.d.ts +145 -10
- package/dist/types/mixins/index.d.ts +52 -4
- package/dist/types/models/interfaces.d.ts +62 -3
- package/dist/types/utils/accountUtils.d.ts +41 -1
- package/dist/types/utils/languageUtils.d.ts +1 -0
- package/dist/types/utils/platformCrypto.d.ts +87 -0
- package/dist/types/utils/platformCrypto.native.d.ts +54 -0
- package/package.json +45 -2
- package/src/CrossDomainAuth.ts +12 -10
- package/src/HttpService.ts +251 -40
- package/src/OxyServices.base.ts +10 -0
- package/src/OxyServices.ts +26 -7
- package/src/crypto/__tests__/keyManager.test.ts +336 -0
- package/src/crypto/index.ts +6 -1
- package/src/crypto/keyManager.ts +529 -151
- package/src/crypto/polyfill.ts +14 -34
- package/src/crypto/recoveryPhrase.ts +56 -17
- package/src/crypto/signatureService.ts +25 -30
- package/src/i18n/locales/en-US.json +46 -1
- package/src/i18n/locales/es-ES.json +46 -1
- package/src/index.ts +19 -4
- package/src/mixins/OxyServices.assets.ts +15 -11
- package/src/mixins/OxyServices.auth.ts +175 -15
- package/src/mixins/OxyServices.contacts.ts +73 -0
- package/src/mixins/OxyServices.features.ts +2 -12
- package/src/mixins/OxyServices.fedcm.ts +4 -3
- package/src/mixins/OxyServices.language.ts +6 -4
- package/src/mixins/OxyServices.redirect.ts +6 -2
- package/src/mixins/OxyServices.security.ts +18 -8
- package/src/mixins/OxyServices.user.ts +72 -49
- package/src/mixins/OxyServices.utility.ts +562 -89
- package/src/mixins/__tests__/serviceAuth.test.ts +623 -0
- package/src/mixins/index.ts +58 -7
- package/src/models/interfaces.ts +65 -3
- package/src/utils/accountUtils.ts +82 -2
- package/src/utils/deviceManager.ts +7 -4
- package/src/utils/languageUtils.ts +23 -2
- package/src/utils/platformCrypto.native.ts +101 -0
- package/src/utils/platformCrypto.ts +145 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contact Discovery Mixin
|
|
3
|
+
*
|
|
4
|
+
* Privacy-preserving discovery of which address-book contacts are on Oxy.
|
|
5
|
+
*
|
|
6
|
+
* The client hashes emails and phones locally before calling the API.
|
|
7
|
+
* The server responds with only Oxy user IDs and the hashes that matched,
|
|
8
|
+
* so the consumer can map each match back to the local contact that
|
|
9
|
+
* produced it.
|
|
10
|
+
*
|
|
11
|
+
* Hashing rules (must match the server `utils/contactHash.ts` exactly):
|
|
12
|
+
* - SHA-256, hex-encoded, lowercase
|
|
13
|
+
* - Email: `value.trim().toLowerCase()` then digest
|
|
14
|
+
* - Phone: trim → keep a single leading "+" → strip non-digits → prepend "+"
|
|
15
|
+
* if missing → digest
|
|
16
|
+
*
|
|
17
|
+
* Mobile clients can compute these digests with `expo-crypto`'s
|
|
18
|
+
* `digestStringAsync(SHA256, value, { encoding: HEX })`. Web clients should
|
|
19
|
+
* use `SubtleCrypto.digest('SHA-256', ...)`.
|
|
20
|
+
*/
|
|
21
|
+
import type { OxyServicesBase } from '../OxyServices.base';
|
|
22
|
+
/** A single match returned by `POST /contacts/discover`. */
|
|
23
|
+
export interface ContactDiscoveryMatch {
|
|
24
|
+
/** Oxy user ID (MongoDB ObjectId hex string). */
|
|
25
|
+
userId: string;
|
|
26
|
+
/** The hashed identifier from the request that matched this user. */
|
|
27
|
+
hashedIdentifier: string;
|
|
28
|
+
/** Whether the match came from the email index or phone index. */
|
|
29
|
+
matchType: 'email' | 'phone';
|
|
30
|
+
}
|
|
31
|
+
/** Response shape of `POST /contacts/discover`. */
|
|
32
|
+
export interface ContactDiscoveryResponse {
|
|
33
|
+
matches: ContactDiscoveryMatch[];
|
|
34
|
+
}
|
|
35
|
+
export declare function OxyServicesContactsMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
36
|
+
new (...args: any[]): {
|
|
37
|
+
/**
|
|
38
|
+
* Discover which of the caller's contacts are on Oxy.
|
|
39
|
+
*
|
|
40
|
+
* @param hashedEmails - SHA-256 hex digests of normalized emails.
|
|
41
|
+
* @param hashedPhones - SHA-256 hex digests of normalized phone numbers.
|
|
42
|
+
* @returns Matches mapping each hashed identifier to the Oxy user ID it
|
|
43
|
+
* resolved to. Empty arrays are valid for either parameter, but at
|
|
44
|
+
* least one must be non-empty.
|
|
45
|
+
*
|
|
46
|
+
* The server enforces a 200-hash cap per channel per request — callers
|
|
47
|
+
* should batch larger address books client-side.
|
|
48
|
+
*/
|
|
49
|
+
discoverContacts(hashedEmails: string[], hashedPhones: string[]): Promise<ContactDiscoveryResponse>;
|
|
50
|
+
httpService: import("../HttpService").HttpService;
|
|
51
|
+
cloudURL: string;
|
|
52
|
+
config: import("../OxyServices.base").OxyConfig;
|
|
53
|
+
__resetTokensForTests(): void;
|
|
54
|
+
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
55
|
+
getBaseURL(): string;
|
|
56
|
+
getClient(): import("../HttpService").HttpService;
|
|
57
|
+
getMetrics(): {
|
|
58
|
+
totalRequests: number;
|
|
59
|
+
successfulRequests: number;
|
|
60
|
+
failedRequests: number;
|
|
61
|
+
cacheHits: number;
|
|
62
|
+
cacheMisses: number;
|
|
63
|
+
averageResponseTime: number;
|
|
64
|
+
};
|
|
65
|
+
clearCache(): void;
|
|
66
|
+
clearCacheEntry(key: string): void;
|
|
67
|
+
clearCacheByPrefix(prefix: string): number;
|
|
68
|
+
getCacheStats(): {
|
|
69
|
+
size: number;
|
|
70
|
+
hits: number;
|
|
71
|
+
misses: number;
|
|
72
|
+
hitRate: number;
|
|
73
|
+
};
|
|
74
|
+
getCloudURL(): string;
|
|
75
|
+
setTokens(accessToken: string, refreshToken?: string): void;
|
|
76
|
+
clearTokens(): void;
|
|
77
|
+
_cachedUserId: string | null | undefined;
|
|
78
|
+
_cachedAccessToken: string | null;
|
|
79
|
+
getCurrentUserId(): string | null;
|
|
80
|
+
hasValidToken(): boolean;
|
|
81
|
+
getAccessToken(): string | null;
|
|
82
|
+
setActingAs(userId: string | null): void;
|
|
83
|
+
getActingAs(): string | null;
|
|
84
|
+
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
85
|
+
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
86
|
+
maxRetries?: number;
|
|
87
|
+
retryDelay?: number;
|
|
88
|
+
authTimeoutMs?: number;
|
|
89
|
+
}): Promise<T_1>;
|
|
90
|
+
validate(): Promise<boolean>;
|
|
91
|
+
handleError(error: unknown): Error;
|
|
92
|
+
healthCheck(): Promise<{
|
|
93
|
+
status: string;
|
|
94
|
+
users?: number;
|
|
95
|
+
timestamp?: string;
|
|
96
|
+
[key: string]: any;
|
|
97
|
+
}>;
|
|
98
|
+
};
|
|
99
|
+
} & T;
|
|
@@ -177,10 +177,6 @@ export declare function OxyServicesFeaturesMixin<T extends typeof OxyServicesBas
|
|
|
177
177
|
* Get all available achievements
|
|
178
178
|
*/
|
|
179
179
|
getAllAchievements(): Promise<Achievement[]>;
|
|
180
|
-
/**
|
|
181
|
-
* Delete user account (requires password confirmation)
|
|
182
|
-
*/
|
|
183
|
-
deleteAccount(password: string): Promise<void>;
|
|
184
180
|
httpService: import("../HttpService").HttpService;
|
|
185
181
|
cloudURL: string;
|
|
186
182
|
config: import("../OxyServices.base").OxyConfig;
|
|
@@ -198,6 +194,7 @@ export declare function OxyServicesFeaturesMixin<T extends typeof OxyServicesBas
|
|
|
198
194
|
};
|
|
199
195
|
clearCache(): void;
|
|
200
196
|
clearCacheEntry(key: string): void;
|
|
197
|
+
clearCacheByPrefix(prefix: string): number;
|
|
201
198
|
getCacheStats(): {
|
|
202
199
|
size: number;
|
|
203
200
|
hits: number;
|
|
@@ -225,9 +222,7 @@ export declare function OxyServicesFeaturesMixin<T extends typeof OxyServicesBas
|
|
|
225
222
|
healthCheck(): Promise<{
|
|
226
223
|
status: string;
|
|
227
224
|
users?: number;
|
|
228
|
-
timestamp
|
|
229
|
-
* Get FAQs
|
|
230
|
-
*/: string;
|
|
225
|
+
timestamp?: string;
|
|
231
226
|
[key: string]: any;
|
|
232
227
|
}>;
|
|
233
228
|
};
|
|
@@ -174,6 +174,7 @@ export declare function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(
|
|
|
174
174
|
};
|
|
175
175
|
clearCache(): void;
|
|
176
176
|
clearCacheEntry(key: string): void;
|
|
177
|
+
clearCacheByPrefix(prefix: string): number;
|
|
177
178
|
getCacheStats(): {
|
|
178
179
|
size: number;
|
|
179
180
|
hits: number;
|
|
@@ -171,6 +171,7 @@ export declare function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBa
|
|
|
171
171
|
};
|
|
172
172
|
clearCache(): void;
|
|
173
173
|
clearCacheEntry(key: string): void;
|
|
174
|
+
clearCacheByPrefix(prefix: string): number;
|
|
174
175
|
getCacheStats(): {
|
|
175
176
|
size: number;
|
|
176
177
|
hits: number;
|
|
@@ -208,6 +208,7 @@ export declare function OxyServicesRedirectAuthMixin<T extends typeof OxyService
|
|
|
208
208
|
};
|
|
209
209
|
clearCache(): void;
|
|
210
210
|
clearCacheEntry(key: string): void;
|
|
211
|
+
clearCacheByPrefix(prefix: string): number;
|
|
211
212
|
getCacheStats(): {
|
|
212
213
|
size: number;
|
|
213
214
|
hits: number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* User Management Methods Mixin
|
|
3
3
|
*/
|
|
4
|
-
import type { User, Notification, SearchProfilesResponse } from '../models/interfaces';
|
|
4
|
+
import type { User, Notification, SearchProfilesResponse, PrivacySettings } from '../models/interfaces';
|
|
5
5
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
6
6
|
import { type PaginationParams } from '../utils/apiUtils';
|
|
7
7
|
export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
@@ -92,21 +92,30 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
|
|
|
92
92
|
*/
|
|
93
93
|
getCurrentUser(): Promise<User>;
|
|
94
94
|
/**
|
|
95
|
-
* Update user profile
|
|
96
|
-
*
|
|
95
|
+
* Update user profile.
|
|
96
|
+
*
|
|
97
|
+
* Invalidates the SDK-side response cache for every endpoint that
|
|
98
|
+
* returns the current user (`GET /users/me`, `GET /session/user/*`,
|
|
99
|
+
* `GET /users/<id>`, `GET /profiles/username/*`) so the next read
|
|
100
|
+
* doesn't return a stale snapshot. Without this, a follow-up
|
|
101
|
+
* `getUserBySession` call inside the 2-minute cache window can return
|
|
102
|
+
* the pre-update user — most visibly during onboarding, where it
|
|
103
|
+
* causes the username step to flicker back as if nothing was saved.
|
|
104
|
+
*
|
|
105
|
+
* TanStack Query handles offline queuing automatically.
|
|
97
106
|
*/
|
|
98
|
-
updateProfile(updates:
|
|
107
|
+
updateProfile(updates: Partial<User>): Promise<User>;
|
|
99
108
|
/**
|
|
100
109
|
* Get privacy settings for a user
|
|
101
110
|
* @param userId - The user ID (defaults to current user)
|
|
102
111
|
*/
|
|
103
|
-
getPrivacySettings(userId?: string): Promise<
|
|
112
|
+
getPrivacySettings(userId?: string): Promise<PrivacySettings>;
|
|
104
113
|
/**
|
|
105
114
|
* Update privacy settings
|
|
106
115
|
* @param settings - Partial privacy settings object
|
|
107
116
|
* @param userId - The user ID (defaults to current user)
|
|
108
117
|
*/
|
|
109
|
-
updatePrivacySettings(settings:
|
|
118
|
+
updatePrivacySettings(settings: Partial<PrivacySettings>, userId?: string): Promise<PrivacySettings>;
|
|
110
119
|
/**
|
|
111
120
|
* Request account verification
|
|
112
121
|
*/
|
|
@@ -119,11 +128,18 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
|
|
|
119
128
|
*/
|
|
120
129
|
downloadAccountData(format?: "json" | "csv"): Promise<Blob>;
|
|
121
130
|
/**
|
|
122
|
-
* Delete account permanently
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
* Delete account permanently.
|
|
132
|
+
*
|
|
133
|
+
* Signs `delete:{publicKey}:{timestamp}` with the locally-stored identity
|
|
134
|
+
* private key and submits the signature alongside the confirmation text
|
|
135
|
+
* (must equal the user's username). The signature is the cryptographic
|
|
136
|
+
* proof of ownership — only the device holding the private key can issue
|
|
137
|
+
* a valid signature, so no password is required.
|
|
138
|
+
*
|
|
139
|
+
* @param confirmText - Must equal the user's username (verified server-side)
|
|
140
|
+
* @throws If no identity is stored on this device, or signing fails
|
|
141
|
+
*/
|
|
142
|
+
deleteAccount(confirmText: string): Promise<{
|
|
127
143
|
message: string;
|
|
128
144
|
}>;
|
|
129
145
|
/**
|
|
@@ -203,6 +219,7 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
|
|
|
203
219
|
};
|
|
204
220
|
clearCache(): void;
|
|
205
221
|
clearCacheEntry(key: string): void;
|
|
222
|
+
clearCacheByPrefix(prefix: string): number;
|
|
206
223
|
getCacheStats(): {
|
|
207
224
|
size: number;
|
|
208
225
|
hits: number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApiError } from '../models/interfaces';
|
|
1
|
+
import type { ApiError, User } from '../models/interfaces';
|
|
2
2
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
3
3
|
/**
|
|
4
4
|
* Result from the managed-accounts verification endpoint.
|
|
@@ -9,11 +9,31 @@ interface ActingAsVerification {
|
|
|
9
9
|
role: 'owner' | 'admin' | 'editor';
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Result from the service-acting-as verification endpoint.
|
|
13
|
+
* Confirms that a given service app holds an active delegation grant for
|
|
14
|
+
* the supplied user, along with the explicit scope list the grant covers.
|
|
15
|
+
*
|
|
16
|
+
* The api side persists these via the `ServiceActingAs` model:
|
|
17
|
+
* { serviceAppId, userId, scopes: string[], grantedAt, expiresAt }
|
|
18
|
+
*
|
|
19
|
+
* The SDK never inspects the grant directly — it round-trips through
|
|
20
|
+
* `GET /internal/service-acting-as/verify?appId=...&userId=...` so the
|
|
21
|
+
* authoritative store stays server-side.
|
|
22
|
+
*/
|
|
23
|
+
export interface ServiceActingAsVerification {
|
|
24
|
+
authorized: boolean;
|
|
25
|
+
scopes: string[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Service app metadata attached to requests authenticated with service tokens.
|
|
29
|
+
* `scopes` reflects the scopes granted to the app at signup time (from the
|
|
30
|
+
* `DeveloperApp.scopes` field); route-level checks can require additional
|
|
31
|
+
* scope-narrowing via `requireScope()`.
|
|
13
32
|
*/
|
|
14
33
|
export interface ServiceApp {
|
|
15
34
|
appId: string;
|
|
16
35
|
appName: string;
|
|
36
|
+
scopes: string[];
|
|
17
37
|
}
|
|
18
38
|
/**
|
|
19
39
|
* Options for oxyClient.auth() middleware
|
|
@@ -22,7 +42,7 @@ interface AuthMiddlewareOptions {
|
|
|
22
42
|
/** Enable debug logging (default: false) */
|
|
23
43
|
debug?: boolean;
|
|
24
44
|
/** Custom error handler - receives error object, can return response */
|
|
25
|
-
onError?: (error: ApiError) =>
|
|
45
|
+
onError?: (error: ApiError) => unknown;
|
|
26
46
|
/** Load full user profile from API (default: false for performance) */
|
|
27
47
|
loadUser?: boolean;
|
|
28
48
|
/** Optional auth - attach user if token present but don't block (default: false) */
|
|
@@ -31,8 +51,24 @@ interface AuthMiddlewareOptions {
|
|
|
31
51
|
* JWT secret for verifying service token signatures locally.
|
|
32
52
|
* When provided, service tokens will be cryptographically verified.
|
|
33
53
|
* When omitted, service tokens will be rejected (secure default).
|
|
54
|
+
*
|
|
55
|
+
* **Migration note (>=1.11.14):** the Oxy API now signs service tokens
|
|
56
|
+
* with a dedicated `SERVICE_TOKEN_SECRET` distinct from `ACCESS_TOKEN_SECRET`.
|
|
57
|
+
* Pass that value here. If you keep passing the access-token secret you will
|
|
58
|
+
* still verify ALL signed-by-Oxy tokens (which is the whole class of bug
|
|
59
|
+
* H4 was supposed to prevent — DO NOT do that in production).
|
|
34
60
|
*/
|
|
35
61
|
jwtSecret?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Expected JWT issuer. Defaults to `'oxy-auth'`. Override only if you run
|
|
64
|
+
* a private fork of the Oxy auth server under a different `iss` claim.
|
|
65
|
+
*/
|
|
66
|
+
expectedIssuer?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Expected JWT audience. Defaults to `'oxy-api'`. Override only if your
|
|
69
|
+
* private fork mints tokens for a different audience.
|
|
70
|
+
*/
|
|
71
|
+
expectedAudience?: string;
|
|
36
72
|
}
|
|
37
73
|
export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
38
74
|
new (...args: any[]): {
|
|
@@ -41,6 +77,17 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
41
77
|
result: ActingAsVerification | null;
|
|
42
78
|
expiresAt: number;
|
|
43
79
|
}>;
|
|
80
|
+
/**
|
|
81
|
+
* In-memory cache for service-acting-as verification.
|
|
82
|
+
* Negative results are cached for 1min to avoid hammering the verify
|
|
83
|
+
* endpoint when a service is misconfigured; positive grants are cached
|
|
84
|
+
* for 5min to amortize the round-trip without holding stale grants too long.
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
_serviceActingAsCache: Map<string, {
|
|
88
|
+
result: ServiceActingAsVerification | null;
|
|
89
|
+
expiresAt: number;
|
|
90
|
+
}>;
|
|
44
91
|
/**
|
|
45
92
|
* Verify that a user is authorized to act as a managed account.
|
|
46
93
|
* Results are cached in-memory for 5 minutes to avoid repeated API calls.
|
|
@@ -48,6 +95,19 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
48
95
|
* @internal Used by the auth() middleware — not part of the public API
|
|
49
96
|
*/
|
|
50
97
|
verifyActingAs(userId: string, accountId: string): Promise<ActingAsVerification | null>;
|
|
98
|
+
/**
|
|
99
|
+
* Verify that a service app holds an active delegation grant authorising
|
|
100
|
+
* it to act on behalf of `userId`. Returns the grant (with allowed scopes)
|
|
101
|
+
* on success or `null` if no valid grant exists. Negative answers are
|
|
102
|
+
* cached briefly to protect the verify endpoint from misconfigured callers.
|
|
103
|
+
*
|
|
104
|
+
* Implemented as a per-instance Map keyed by `appId:userId`. Cached
|
|
105
|
+
* positive grants live for 5 minutes (acceptable staleness window for an
|
|
106
|
+
* impersonation grant); revocations propagate within that window.
|
|
107
|
+
*
|
|
108
|
+
* @internal Used by the auth() middleware — not part of the public API
|
|
109
|
+
*/
|
|
110
|
+
verifyServiceActingAs(appId: string, userId: string): Promise<ServiceActingAsVerification | null>;
|
|
51
111
|
/**
|
|
52
112
|
* Fetch link metadata
|
|
53
113
|
*/
|
|
@@ -70,9 +130,17 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
70
130
|
* - Security comes from API-based session validation (`validateSession()`)
|
|
71
131
|
* which checks the session server-side on every request
|
|
72
132
|
* - Service tokens (type: 'service') DO use cryptographic HMAC verification
|
|
73
|
-
* via the `jwtSecret` option, since they are stateless
|
|
133
|
+
* via the `jwtSecret` option, since they are stateless. Service tokens
|
|
134
|
+
* are additionally checked for `aud`, `iss`, and `type` claims to prevent
|
|
135
|
+
* cross-token-type confusion attacks.
|
|
74
136
|
* - The backend's own `authMiddleware` uses `jwt.verify()` because it has
|
|
75
|
-
* direct access to `ACCESS_TOKEN_SECRET
|
|
137
|
+
* direct access to `SERVICE_TOKEN_SECRET` / `ACCESS_TOKEN_SECRET`.
|
|
138
|
+
*
|
|
139
|
+
* **Service-token delegation (X-Oxy-User-Id):**
|
|
140
|
+
* When a service token is accompanied by `X-Oxy-User-Id`, the SDK calls
|
|
141
|
+
* `verifyServiceActingAs(appId, userId)` to confirm an explicit delegation
|
|
142
|
+
* grant exists before attaching `req.userId`. A missing/expired grant
|
|
143
|
+
* results in a 403 — there is no fail-open path.
|
|
76
144
|
*
|
|
77
145
|
* @example
|
|
78
146
|
* ```typescript
|
|
@@ -81,7 +149,7 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
81
149
|
* const oxy = new OxyServices({ baseURL: 'https://api.oxy.so' });
|
|
82
150
|
*
|
|
83
151
|
* // Protect all routes under /protected
|
|
84
|
-
* app.use('/protected', oxy.auth());
|
|
152
|
+
* app.use('/protected', oxy.auth({ jwtSecret: process.env.SERVICE_TOKEN_SECRET }));
|
|
85
153
|
*
|
|
86
154
|
* // Access user in route handler
|
|
87
155
|
* app.get('/protected/me', (req, res) => {
|
|
@@ -93,12 +161,15 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
93
161
|
*
|
|
94
162
|
* // Optional auth - attach user if present, don't block if absent
|
|
95
163
|
* app.use('/public', oxy.auth({ optional: true }));
|
|
164
|
+
*
|
|
165
|
+
* // Require a specific scope on a service-token-protected route
|
|
166
|
+
* app.use('/internal/files', oxy.serviceAuth({ jwtSecret: process.env.SERVICE_TOKEN_SECRET }), oxy.requireScope('files:write'));
|
|
96
167
|
* ```
|
|
97
168
|
*
|
|
98
169
|
* @param options Optional configuration
|
|
99
170
|
* @returns Express middleware function
|
|
100
171
|
*/
|
|
101
|
-
auth(options?: AuthMiddlewareOptions): (req:
|
|
172
|
+
auth(options?: AuthMiddlewareOptions): (req: AuthReq, res: AuthRes, next: AuthNext) => Promise<unknown>;
|
|
102
173
|
/**
|
|
103
174
|
* Socket.IO authentication middleware factory
|
|
104
175
|
*
|
|
@@ -125,7 +196,7 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
125
196
|
*/
|
|
126
197
|
authSocket(options?: {
|
|
127
198
|
debug?: boolean;
|
|
128
|
-
}): (socket:
|
|
199
|
+
}): (socket: SocketLike, next: (err?: Error) => void) => Promise<void>;
|
|
129
200
|
/**
|
|
130
201
|
* Express.js middleware that only allows service tokens.
|
|
131
202
|
* Use this for internal-only endpoints that should not be accessible
|
|
@@ -134,7 +205,7 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
134
205
|
* @example
|
|
135
206
|
* ```typescript
|
|
136
207
|
* // Protect internal endpoints
|
|
137
|
-
* app.use('/internal', oxy.serviceAuth());
|
|
208
|
+
* app.use('/internal', oxy.serviceAuth({ jwtSecret: process.env.SERVICE_TOKEN_SECRET }));
|
|
138
209
|
*
|
|
139
210
|
* app.post('/internal/trigger', (req, res) => {
|
|
140
211
|
* console.log('Service app:', req.serviceApp);
|
|
@@ -145,7 +216,30 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
145
216
|
serviceAuth(options?: {
|
|
146
217
|
debug?: boolean;
|
|
147
218
|
jwtSecret?: string;
|
|
148
|
-
|
|
219
|
+
expectedIssuer?: string;
|
|
220
|
+
expectedAudience?: string;
|
|
221
|
+
}): (req: AuthReq, res: AuthRes, next: AuthNext) => Promise<void>;
|
|
222
|
+
/**
|
|
223
|
+
* Express.js middleware that enforces a specific service-token scope.
|
|
224
|
+
*
|
|
225
|
+
* Mount AFTER `auth()` / `serviceAuth()` — relies on `req.serviceApp` and
|
|
226
|
+
* (when delegation is in effect) `req.serviceActingAs.scopes`. The scope
|
|
227
|
+
* is granted if EITHER list contains it, mirroring the OAuth2 model where
|
|
228
|
+
* the app's app-level scopes and the per-user delegated scopes both count.
|
|
229
|
+
*
|
|
230
|
+
* Requests authenticated as a regular user (no service token) are rejected
|
|
231
|
+
* with 403 — scope-protected endpoints are service-to-service by design.
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```typescript
|
|
235
|
+
* app.use(
|
|
236
|
+
* '/internal/files',
|
|
237
|
+
* oxy.serviceAuth({ jwtSecret: process.env.SERVICE_TOKEN_SECRET }),
|
|
238
|
+
* oxy.requireScope('files:write'),
|
|
239
|
+
* );
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
requireScope(scope: string): (req: AuthReq, res: AuthRes, next: AuthNext) => void;
|
|
149
243
|
httpService: import("../HttpService").HttpService;
|
|
150
244
|
cloudURL: string;
|
|
151
245
|
config: import("../OxyServices.base").OxyConfig;
|
|
@@ -163,6 +257,7 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
163
257
|
};
|
|
164
258
|
clearCache(): void;
|
|
165
259
|
clearCacheEntry(key: string): void;
|
|
260
|
+
clearCacheByPrefix(prefix: string): number;
|
|
166
261
|
getCacheStats(): {
|
|
167
262
|
size: number;
|
|
168
263
|
hits: number;
|
|
@@ -195,4 +290,44 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
195
290
|
}>;
|
|
196
291
|
};
|
|
197
292
|
} & T;
|
|
293
|
+
interface AuthReq {
|
|
294
|
+
method?: string;
|
|
295
|
+
path?: string;
|
|
296
|
+
headers: Record<string, string | string[] | undefined>;
|
|
297
|
+
query?: Record<string, unknown>;
|
|
298
|
+
userId?: string | null;
|
|
299
|
+
user?: User | null;
|
|
300
|
+
accessToken?: string;
|
|
301
|
+
sessionId?: string | null;
|
|
302
|
+
serviceApp?: ServiceApp;
|
|
303
|
+
serviceActingAs?: {
|
|
304
|
+
userId: string;
|
|
305
|
+
scopes: string[];
|
|
306
|
+
};
|
|
307
|
+
actingAs?: {
|
|
308
|
+
userId: string;
|
|
309
|
+
role: string;
|
|
310
|
+
};
|
|
311
|
+
originalUser?: {
|
|
312
|
+
id: string;
|
|
313
|
+
} & Partial<User>;
|
|
314
|
+
}
|
|
315
|
+
interface AuthRes {
|
|
316
|
+
status(code: number): AuthRes;
|
|
317
|
+
json(body: unknown): unknown;
|
|
318
|
+
}
|
|
319
|
+
type AuthNext = (err?: unknown) => void;
|
|
320
|
+
interface SocketLike {
|
|
321
|
+
handshake?: {
|
|
322
|
+
auth?: {
|
|
323
|
+
token?: string;
|
|
324
|
+
};
|
|
325
|
+
};
|
|
326
|
+
data?: Record<string, unknown>;
|
|
327
|
+
user?: {
|
|
328
|
+
id: string;
|
|
329
|
+
userId: string;
|
|
330
|
+
sessionId?: string | null;
|
|
331
|
+
};
|
|
332
|
+
}
|
|
198
333
|
export {};
|
|
@@ -4,7 +4,50 @@
|
|
|
4
4
|
* This module provides a clean way to compose all mixins
|
|
5
5
|
* and ensures consistent ordering for better maintainability
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
import { OxyServicesBase } from '../OxyServices.base';
|
|
8
|
+
import { OxyServicesAuthMixin } from './OxyServices.auth';
|
|
9
|
+
import { OxyServicesFedCMMixin } from './OxyServices.fedcm';
|
|
10
|
+
import { OxyServicesPopupAuthMixin } from './OxyServices.popup';
|
|
11
|
+
import { OxyServicesRedirectAuthMixin } from './OxyServices.redirect';
|
|
12
|
+
import { OxyServicesUserMixin } from './OxyServices.user';
|
|
13
|
+
import { OxyServicesPrivacyMixin } from './OxyServices.privacy';
|
|
14
|
+
import { OxyServicesLanguageMixin } from './OxyServices.language';
|
|
15
|
+
import { OxyServicesPaymentMixin } from './OxyServices.payment';
|
|
16
|
+
import { OxyServicesKarmaMixin } from './OxyServices.karma';
|
|
17
|
+
import { OxyServicesAssetsMixin } from './OxyServices.assets';
|
|
18
|
+
import { OxyServicesDeveloperMixin } from './OxyServices.developer';
|
|
19
|
+
import { OxyServicesLocationMixin } from './OxyServices.location';
|
|
20
|
+
import { OxyServicesAnalyticsMixin } from './OxyServices.analytics';
|
|
21
|
+
import { OxyServicesDevicesMixin } from './OxyServices.devices';
|
|
22
|
+
import { OxyServicesSecurityMixin } from './OxyServices.security';
|
|
23
|
+
import { OxyServicesUtilityMixin } from './OxyServices.utility';
|
|
24
|
+
import { OxyServicesFeaturesMixin } from './OxyServices.features';
|
|
25
|
+
import { OxyServicesTopicsMixin } from './OxyServices.topics';
|
|
26
|
+
import { OxyServicesManagedAccountsMixin } from './OxyServices.managedAccounts';
|
|
27
|
+
import { OxyServicesContactsMixin } from './OxyServices.contacts';
|
|
28
|
+
/**
|
|
29
|
+
* Instance shape of every mixin in the pipeline, intersected. The runtime
|
|
30
|
+
* `composeOxyServices()` produces a class whose instances expose all of
|
|
31
|
+
* these methods; we surface that to TypeScript via this intersection so the
|
|
32
|
+
* `extends` site in `OxyServices.ts` can avoid an `as any` cast.
|
|
33
|
+
*
|
|
34
|
+
* If you add a new mixin to `MIXIN_PIPELINE`, add it here too so its methods
|
|
35
|
+
* are visible without a cast.
|
|
36
|
+
*/
|
|
37
|
+
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 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 OxyServicesDeveloperMixin<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 OxyServicesUtilityMixin<typeof OxyServicesBase>>>;
|
|
38
|
+
/**
|
|
39
|
+
* Constructor type for the fully composed mixin pipeline. Each mixin returns
|
|
40
|
+
* a new constructor that augments its input; reducing across the pipeline
|
|
41
|
+
* yields an instance with every mixin's methods.
|
|
42
|
+
*/
|
|
43
|
+
export type ComposedOxyServicesConstructor = new (config: import('../OxyServices.base').OxyConfig) => AllMixinInstances;
|
|
44
|
+
/**
|
|
45
|
+
* A mixin function: takes a constructor and returns an augmented constructor.
|
|
46
|
+
* Each individual mixin uses a `<T extends typeof OxyServicesBase>` generic
|
|
47
|
+
* to preserve its specific augmentations, but those refinements are
|
|
48
|
+
* intentionally collapsed across the `reduce` call below.
|
|
49
|
+
*/
|
|
50
|
+
type MixinFunction = (Base: new (...args: unknown[]) => OxyServicesBase) => new (...args: unknown[]) => OxyServicesBase;
|
|
8
51
|
/**
|
|
9
52
|
* Mixin pipeline - applied in order from first to last.
|
|
10
53
|
*
|
|
@@ -22,9 +65,14 @@ declare const MIXIN_PIPELINE: MixinFunction[];
|
|
|
22
65
|
* Composes all OxyServices mixins using a pipeline pattern.
|
|
23
66
|
*
|
|
24
67
|
* This is equivalent to the nested calls but more readable and maintainable.
|
|
25
|
-
* Adding a new mixin:
|
|
68
|
+
* Adding a new mixin: add it to MIXIN_PIPELINE at the appropriate position
|
|
69
|
+
* AND extend `AllMixinInstances` so its methods are visible to consumers.
|
|
70
|
+
*
|
|
71
|
+
* The cast through `unknown` carries the runtime augmentation chain into the
|
|
72
|
+
* static type system. `Array.reduce` cannot track each mixin's generic
|
|
73
|
+
* refinement, so we assert the final shape exposed by all mixins together.
|
|
26
74
|
*
|
|
27
|
-
* @returns The fully composed OxyServices
|
|
75
|
+
* @returns The fully composed OxyServices constructor with all mixins applied
|
|
28
76
|
*/
|
|
29
|
-
export declare function composeOxyServices():
|
|
77
|
+
export declare function composeOxyServices(): ComposedOxyServicesConstructor;
|
|
30
78
|
export { MIXIN_PIPELINE };
|