@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.
Files changed (130) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/CrossDomainAuth.js +3 -1
  3. package/dist/cjs/HttpService.js +214 -33
  4. package/dist/cjs/OxyServices.base.js +9 -0
  5. package/dist/cjs/OxyServices.js +8 -3
  6. package/dist/cjs/crypto/index.js +3 -1
  7. package/dist/cjs/crypto/keyManager.js +476 -172
  8. package/dist/cjs/crypto/polyfill.js +14 -65
  9. package/dist/cjs/crypto/recoveryPhrase.js +30 -11
  10. package/dist/cjs/crypto/signatureService.js +25 -60
  11. package/dist/cjs/i18n/locales/en-US.json +46 -1
  12. package/dist/cjs/i18n/locales/es-ES.json +46 -1
  13. package/dist/cjs/i18n/locales/locales/en-US.json +46 -1
  14. package/dist/cjs/i18n/locales/locales/es-ES.json +46 -1
  15. package/dist/cjs/index.js +10 -2
  16. package/dist/cjs/mixins/OxyServices.assets.js +9 -4
  17. package/dist/cjs/mixins/OxyServices.auth.js +147 -14
  18. package/dist/cjs/mixins/OxyServices.contacts.js +50 -0
  19. package/dist/cjs/mixins/OxyServices.features.js +0 -11
  20. package/dist/cjs/mixins/OxyServices.fedcm.js +4 -3
  21. package/dist/cjs/mixins/OxyServices.language.js +5 -36
  22. package/dist/cjs/mixins/OxyServices.redirect.js +6 -2
  23. package/dist/cjs/mixins/OxyServices.security.js +13 -2
  24. package/dist/cjs/mixins/OxyServices.user.js +59 -38
  25. package/dist/cjs/mixins/OxyServices.utility.js +416 -110
  26. package/dist/cjs/mixins/index.js +11 -3
  27. package/dist/cjs/utils/accountUtils.js +71 -2
  28. package/dist/cjs/utils/deviceManager.js +5 -36
  29. package/dist/cjs/utils/languageUtils.js +22 -0
  30. package/dist/cjs/utils/platformCrypto.js +165 -0
  31. package/dist/cjs/utils/platformCrypto.native.js +123 -0
  32. package/dist/esm/.tsbuildinfo +1 -1
  33. package/dist/esm/CrossDomainAuth.js +3 -1
  34. package/dist/esm/HttpService.js +215 -34
  35. package/dist/esm/OxyServices.base.js +9 -0
  36. package/dist/esm/OxyServices.js +8 -3
  37. package/dist/esm/crypto/index.js +1 -1
  38. package/dist/esm/crypto/keyManager.js +473 -138
  39. package/dist/esm/crypto/polyfill.js +14 -32
  40. package/dist/esm/crypto/recoveryPhrase.js +30 -11
  41. package/dist/esm/crypto/signatureService.js +25 -27
  42. package/dist/esm/i18n/locales/en-US.json +46 -1
  43. package/dist/esm/i18n/locales/es-ES.json +46 -1
  44. package/dist/esm/i18n/locales/locales/en-US.json +46 -1
  45. package/dist/esm/i18n/locales/locales/es-ES.json +46 -1
  46. package/dist/esm/index.js +4 -3
  47. package/dist/esm/mixins/OxyServices.assets.js +9 -4
  48. package/dist/esm/mixins/OxyServices.auth.js +145 -14
  49. package/dist/esm/mixins/OxyServices.contacts.js +47 -0
  50. package/dist/esm/mixins/OxyServices.features.js +0 -11
  51. package/dist/esm/mixins/OxyServices.fedcm.js +4 -3
  52. package/dist/esm/mixins/OxyServices.language.js +5 -3
  53. package/dist/esm/mixins/OxyServices.redirect.js +6 -2
  54. package/dist/esm/mixins/OxyServices.security.js +13 -2
  55. package/dist/esm/mixins/OxyServices.user.js +59 -38
  56. package/dist/esm/mixins/OxyServices.utility.js +416 -77
  57. package/dist/esm/mixins/index.js +11 -3
  58. package/dist/esm/utils/accountUtils.js +67 -1
  59. package/dist/esm/utils/deviceManager.js +5 -3
  60. package/dist/esm/utils/languageUtils.js +21 -0
  61. package/dist/esm/utils/platformCrypto.js +125 -0
  62. package/dist/esm/utils/platformCrypto.native.js +80 -0
  63. package/dist/types/.tsbuildinfo +1 -1
  64. package/dist/types/HttpService.d.ts +47 -3
  65. package/dist/types/OxyServices.base.d.ts +7 -0
  66. package/dist/types/OxyServices.d.ts +50 -7
  67. package/dist/types/crypto/index.d.ts +1 -1
  68. package/dist/types/crypto/keyManager.d.ts +110 -9
  69. package/dist/types/crypto/polyfill.d.ts +3 -1
  70. package/dist/types/crypto/recoveryPhrase.d.ts +31 -7
  71. package/dist/types/crypto/signatureService.d.ts +4 -0
  72. package/dist/types/index.d.ts +7 -5
  73. package/dist/types/mixins/OxyServices.analytics.d.ts +1 -0
  74. package/dist/types/mixins/OxyServices.assets.d.ts +6 -10
  75. package/dist/types/mixins/OxyServices.auth.d.ts +82 -5
  76. package/dist/types/mixins/OxyServices.contacts.d.ts +99 -0
  77. package/dist/types/mixins/OxyServices.developer.d.ts +1 -0
  78. package/dist/types/mixins/OxyServices.devices.d.ts +1 -0
  79. package/dist/types/mixins/OxyServices.features.d.ts +2 -7
  80. package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -0
  81. package/dist/types/mixins/OxyServices.karma.d.ts +1 -0
  82. package/dist/types/mixins/OxyServices.language.d.ts +1 -0
  83. package/dist/types/mixins/OxyServices.location.d.ts +1 -0
  84. package/dist/types/mixins/OxyServices.managedAccounts.d.ts +1 -0
  85. package/dist/types/mixins/OxyServices.payment.d.ts +1 -0
  86. package/dist/types/mixins/OxyServices.popup.d.ts +1 -0
  87. package/dist/types/mixins/OxyServices.privacy.d.ts +1 -0
  88. package/dist/types/mixins/OxyServices.redirect.d.ts +1 -0
  89. package/dist/types/mixins/OxyServices.security.d.ts +1 -0
  90. package/dist/types/mixins/OxyServices.topics.d.ts +1 -0
  91. package/dist/types/mixins/OxyServices.user.d.ts +28 -11
  92. package/dist/types/mixins/OxyServices.utility.d.ts +145 -10
  93. package/dist/types/mixins/index.d.ts +52 -4
  94. package/dist/types/models/interfaces.d.ts +62 -3
  95. package/dist/types/utils/accountUtils.d.ts +41 -1
  96. package/dist/types/utils/languageUtils.d.ts +1 -0
  97. package/dist/types/utils/platformCrypto.d.ts +87 -0
  98. package/dist/types/utils/platformCrypto.native.d.ts +54 -0
  99. package/package.json +45 -2
  100. package/src/CrossDomainAuth.ts +12 -10
  101. package/src/HttpService.ts +251 -40
  102. package/src/OxyServices.base.ts +10 -0
  103. package/src/OxyServices.ts +26 -7
  104. package/src/crypto/__tests__/keyManager.test.ts +336 -0
  105. package/src/crypto/index.ts +6 -1
  106. package/src/crypto/keyManager.ts +529 -151
  107. package/src/crypto/polyfill.ts +14 -34
  108. package/src/crypto/recoveryPhrase.ts +56 -17
  109. package/src/crypto/signatureService.ts +25 -30
  110. package/src/i18n/locales/en-US.json +46 -1
  111. package/src/i18n/locales/es-ES.json +46 -1
  112. package/src/index.ts +19 -4
  113. package/src/mixins/OxyServices.assets.ts +15 -11
  114. package/src/mixins/OxyServices.auth.ts +175 -15
  115. package/src/mixins/OxyServices.contacts.ts +73 -0
  116. package/src/mixins/OxyServices.features.ts +2 -12
  117. package/src/mixins/OxyServices.fedcm.ts +4 -3
  118. package/src/mixins/OxyServices.language.ts +6 -4
  119. package/src/mixins/OxyServices.redirect.ts +6 -2
  120. package/src/mixins/OxyServices.security.ts +18 -8
  121. package/src/mixins/OxyServices.user.ts +72 -49
  122. package/src/mixins/OxyServices.utility.ts +562 -89
  123. package/src/mixins/__tests__/serviceAuth.test.ts +623 -0
  124. package/src/mixins/index.ts +58 -7
  125. package/src/models/interfaces.ts +65 -3
  126. package/src/utils/accountUtils.ts +82 -2
  127. package/src/utils/deviceManager.ts +7 -4
  128. package/src/utils/languageUtils.ts +23 -2
  129. package/src/utils/platformCrypto.native.ts +101 -0
  130. 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;
@@ -69,6 +69,7 @@ export declare function OxyServicesDeveloperMixin<T extends typeof OxyServicesBa
69
69
  };
70
70
  clearCache(): void;
71
71
  clearCacheEntry(key: string): void;
72
+ clearCacheByPrefix(prefix: string): number;
72
73
  getCacheStats(): {
73
74
  size: number;
74
75
  hits: number;
@@ -66,6 +66,7 @@ export declare function OxyServicesDevicesMixin<T extends typeof OxyServicesBase
66
66
  };
67
67
  clearCache(): void;
68
68
  clearCacheEntry(key: string): void;
69
+ clearCacheByPrefix(prefix: string): number;
69
70
  getCacheStats(): {
70
71
  size: number;
71
72
  hits: number;
@@ -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;
@@ -55,6 +55,7 @@ export declare function OxyServicesKarmaMixin<T extends typeof OxyServicesBase>(
55
55
  };
56
56
  clearCache(): void;
57
57
  clearCacheEntry(key: string): void;
58
+ clearCacheByPrefix(prefix: string): number;
58
59
  getCacheStats(): {
59
60
  size: number;
60
61
  hits: number;
@@ -51,6 +51,7 @@ export declare function OxyServicesLanguageMixin<T extends typeof OxyServicesBas
51
51
  };
52
52
  clearCache(): void;
53
53
  clearCacheEntry(key: string): void;
54
+ clearCacheByPrefix(prefix: string): number;
54
55
  getCacheStats(): {
55
56
  size: number;
56
57
  hits: number;
@@ -34,6 +34,7 @@ export declare function OxyServicesLocationMixin<T extends typeof OxyServicesBas
34
34
  };
35
35
  clearCache(): void;
36
36
  clearCacheEntry(key: string): void;
37
+ clearCacheByPrefix(prefix: string): number;
37
38
  getCacheStats(): {
38
39
  size: number;
39
40
  hits: number;
@@ -91,6 +91,7 @@ export declare function OxyServicesManagedAccountsMixin<T extends typeof OxyServ
91
91
  };
92
92
  clearCache(): void;
93
93
  clearCacheEntry(key: string): void;
94
+ clearCacheByPrefix(prefix: string): number;
94
95
  getCacheStats(): {
95
96
  size: number;
96
97
  hits: number;
@@ -81,6 +81,7 @@ export declare function OxyServicesPaymentMixin<T extends typeof OxyServicesBase
81
81
  };
82
82
  clearCache(): void;
83
83
  clearCacheEntry(key: string): void;
84
+ clearCacheByPrefix(prefix: string): number;
84
85
  getCacheStats(): {
85
86
  size: number;
86
87
  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;
@@ -92,6 +92,7 @@ export declare function OxyServicesPrivacyMixin<T extends typeof OxyServicesBase
92
92
  };
93
93
  clearCache(): void;
94
94
  clearCacheEntry(key: string): void;
95
+ clearCacheByPrefix(prefix: string): number;
95
96
  getCacheStats(): {
96
97
  size: number;
97
98
  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;
@@ -48,6 +48,7 @@ export declare function OxyServicesSecurityMixin<T extends typeof OxyServicesBas
48
48
  };
49
49
  clearCache(): void;
50
50
  clearCacheEntry(key: string): void;
51
+ clearCacheByPrefix(prefix: string): number;
51
52
  getCacheStats(): {
52
53
  size: number;
53
54
  hits: number;
@@ -74,6 +74,7 @@ export declare function OxyServicesTopicsMixin<T extends typeof OxyServicesBase>
74
74
  };
75
75
  clearCache(): void;
76
76
  clearCacheEntry(key: string): void;
77
+ clearCacheByPrefix(prefix: string): number;
77
78
  getCacheStats(): {
78
79
  size: number;
79
80
  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
- * TanStack Query handles offline queuing automatically
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: Record<string, any>): Promise<User>;
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<any>;
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: Record<string, any>, userId?: string): Promise<any>;
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
- * @param password - User password for confirmation
124
- * @param confirmText - Confirmation text (usually username)
125
- */
126
- deleteAccount(password: string, confirmText: string): Promise<{
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
- * Service app metadata attached to requests authenticated with service tokens
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) => any;
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: any, res: any, next: any) => Promise<any>;
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: any, next: (err?: Error) => void) => Promise<void>;
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
- }): (req: any, res: any, next: any) => Promise<void>;
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
- type MixinFunction = (Base: any) => any;
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: just add it to MIXIN_PIPELINE at the appropriate position.
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 class with all mixins applied
75
+ * @returns The fully composed OxyServices constructor with all mixins applied
28
76
  */
29
- export declare function composeOxyServices(): any;
77
+ export declare function composeOxyServices(): ComposedOxyServicesConstructor;
30
78
  export { MIXIN_PIPELINE };