@oxyhq/core 1.5.0 → 1.6.1
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/AuthManager.js +14 -1
- package/dist/cjs/HttpService.js +87 -69
- package/dist/cjs/OxyServices.base.js +5 -4
- package/dist/cjs/crypto/keyManager.js +1 -13
- package/dist/cjs/crypto/signatureService.js +7 -20
- package/dist/cjs/index.js +9 -1
- package/dist/cjs/mixins/OxyServices.analytics.js +2 -2
- package/dist/cjs/mixins/OxyServices.assets.js +14 -14
- package/dist/cjs/mixins/OxyServices.auth.js +19 -19
- package/dist/cjs/mixins/OxyServices.developer.js +6 -6
- package/dist/cjs/mixins/OxyServices.devices.js +7 -7
- package/dist/cjs/mixins/OxyServices.features.js +23 -23
- package/dist/cjs/mixins/OxyServices.fedcm.js +1 -1
- package/dist/cjs/mixins/OxyServices.karma.js +6 -6
- package/dist/cjs/mixins/OxyServices.location.js +2 -2
- package/dist/cjs/mixins/OxyServices.payment.js +6 -6
- package/dist/cjs/mixins/OxyServices.popup.js +1 -1
- package/dist/cjs/mixins/OxyServices.privacy.js +6 -6
- package/dist/cjs/mixins/OxyServices.security.js +3 -3
- package/dist/cjs/mixins/OxyServices.user.js +22 -22
- package/dist/cjs/mixins/OxyServices.utility.js +39 -10
- package/dist/cjs/utils/authHelpers.js +114 -0
- package/dist/cjs/utils/platform.js +14 -0
- package/dist/esm/AuthManager.js +14 -1
- package/dist/esm/HttpService.js +87 -69
- package/dist/esm/OxyServices.base.js +5 -4
- package/dist/esm/crypto/keyManager.js +1 -13
- package/dist/esm/crypto/signatureService.js +2 -15
- package/dist/esm/index.js +2 -0
- package/dist/esm/mixins/OxyServices.analytics.js +2 -2
- package/dist/esm/mixins/OxyServices.assets.js +14 -14
- package/dist/esm/mixins/OxyServices.auth.js +19 -19
- package/dist/esm/mixins/OxyServices.developer.js +6 -6
- package/dist/esm/mixins/OxyServices.devices.js +7 -7
- package/dist/esm/mixins/OxyServices.features.js +23 -23
- package/dist/esm/mixins/OxyServices.fedcm.js +1 -1
- package/dist/esm/mixins/OxyServices.karma.js +6 -6
- package/dist/esm/mixins/OxyServices.location.js +2 -2
- package/dist/esm/mixins/OxyServices.payment.js +6 -6
- package/dist/esm/mixins/OxyServices.popup.js +1 -1
- package/dist/esm/mixins/OxyServices.privacy.js +6 -6
- package/dist/esm/mixins/OxyServices.security.js +3 -3
- package/dist/esm/mixins/OxyServices.user.js +22 -22
- package/dist/esm/mixins/OxyServices.utility.js +39 -10
- package/dist/esm/utils/authHelpers.js +105 -0
- package/dist/esm/utils/platform.js +12 -0
- package/dist/types/HttpService.d.ts +4 -1
- package/dist/types/OxyServices.base.d.ts +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/mixins/OxyServices.analytics.d.ts +1 -1
- package/dist/types/mixins/OxyServices.assets.d.ts +1 -1
- package/dist/types/mixins/OxyServices.auth.d.ts +1 -1
- package/dist/types/mixins/OxyServices.developer.d.ts +1 -1
- package/dist/types/mixins/OxyServices.devices.d.ts +1 -1
- package/dist/types/mixins/OxyServices.features.d.ts +1 -1
- package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -1
- package/dist/types/mixins/OxyServices.karma.d.ts +1 -1
- package/dist/types/mixins/OxyServices.language.d.ts +1 -1
- package/dist/types/mixins/OxyServices.location.d.ts +1 -1
- package/dist/types/mixins/OxyServices.payment.d.ts +1 -1
- package/dist/types/mixins/OxyServices.popup.d.ts +1 -1
- package/dist/types/mixins/OxyServices.privacy.d.ts +1 -1
- package/dist/types/mixins/OxyServices.redirect.d.ts +1 -1
- package/dist/types/mixins/OxyServices.security.d.ts +1 -1
- package/dist/types/mixins/OxyServices.user.d.ts +1 -1
- package/dist/types/mixins/OxyServices.utility.d.ts +20 -6
- package/dist/types/utils/authHelpers.d.ts +57 -0
- package/dist/types/utils/platform.d.ts +8 -0
- package/package.json +1 -1
- package/src/AuthManager.ts +14 -1
- package/src/HttpService.ts +85 -67
- package/src/OxyServices.base.ts +5 -4
- package/src/crypto/keyManager.ts +1 -15
- package/src/crypto/signatureService.ts +2 -17
- package/src/index.ts +11 -0
- package/src/mixins/OxyServices.analytics.ts +2 -2
- package/src/mixins/OxyServices.assets.ts +14 -14
- package/src/mixins/OxyServices.auth.ts +19 -19
- package/src/mixins/OxyServices.developer.ts +6 -6
- package/src/mixins/OxyServices.devices.ts +7 -7
- package/src/mixins/OxyServices.features.ts +23 -23
- package/src/mixins/OxyServices.fedcm.ts +1 -1
- package/src/mixins/OxyServices.karma.ts +6 -6
- package/src/mixins/OxyServices.location.ts +2 -2
- package/src/mixins/OxyServices.payment.ts +6 -6
- package/src/mixins/OxyServices.popup.ts +1 -1
- package/src/mixins/OxyServices.privacy.ts +6 -6
- package/src/mixins/OxyServices.security.ts +3 -3
- package/src/mixins/OxyServices.user.ts +22 -22
- package/src/mixins/OxyServices.utility.ts +41 -11
- package/src/utils/authHelpers.ts +140 -0
- package/src/utils/platform.ts +14 -0
|
@@ -9,7 +9,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
9
9
|
*/
|
|
10
10
|
async getProfileByUsername(username) {
|
|
11
11
|
try {
|
|
12
|
-
return await this.makeRequest('GET', `/
|
|
12
|
+
return await this.makeRequest('GET', `/profiles/username/${username}`, undefined, {
|
|
13
13
|
cache: true,
|
|
14
14
|
cacheTTL: 5 * 60 * 1000, // 5 minutes cache for profiles
|
|
15
15
|
});
|
|
@@ -26,7 +26,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
26
26
|
const params = { query, ...pagination };
|
|
27
27
|
const searchParams = buildSearchParams(params);
|
|
28
28
|
const paramsObj = Object.fromEntries(searchParams.entries());
|
|
29
|
-
const response = await this.makeRequest('GET', '/
|
|
29
|
+
const response = await this.makeRequest('GET', '/profiles/search', paramsObj, {
|
|
30
30
|
cache: true,
|
|
31
31
|
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
32
32
|
});
|
|
@@ -71,7 +71,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
71
71
|
*/
|
|
72
72
|
async getProfileRecommendations() {
|
|
73
73
|
return this.withAuthRetry(async () => {
|
|
74
|
-
return await this.makeRequest('GET', '/
|
|
74
|
+
return await this.makeRequest('GET', '/profiles/recommendations', undefined, { cache: true });
|
|
75
75
|
}, 'getProfileRecommendations');
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
@@ -79,7 +79,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
79
79
|
*/
|
|
80
80
|
async getUserById(userId) {
|
|
81
81
|
try {
|
|
82
|
-
return await this.makeRequest('GET', `/
|
|
82
|
+
return await this.makeRequest('GET', `/users/${userId}`, undefined, {
|
|
83
83
|
cache: true,
|
|
84
84
|
cacheTTL: 5 * 60 * 1000, // 5 minutes cache
|
|
85
85
|
});
|
|
@@ -93,7 +93,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
93
93
|
*/
|
|
94
94
|
async getCurrentUser() {
|
|
95
95
|
return this.withAuthRetry(async () => {
|
|
96
|
-
return await this.makeRequest('GET', '/
|
|
96
|
+
return await this.makeRequest('GET', '/users/me', undefined, {
|
|
97
97
|
cache: true,
|
|
98
98
|
cacheTTL: 1 * 60 * 1000, // 1 minute cache for current user
|
|
99
99
|
});
|
|
@@ -105,7 +105,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
105
105
|
*/
|
|
106
106
|
async updateProfile(updates) {
|
|
107
107
|
try {
|
|
108
|
-
return await this.makeRequest('PUT', '/
|
|
108
|
+
return await this.makeRequest('PUT', '/users/me', updates, { cache: false });
|
|
109
109
|
}
|
|
110
110
|
catch (error) {
|
|
111
111
|
const errorAny = error;
|
|
@@ -131,7 +131,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
131
131
|
async getPrivacySettings(userId) {
|
|
132
132
|
try {
|
|
133
133
|
const id = userId || (await this.getCurrentUser()).id;
|
|
134
|
-
return await this.makeRequest('GET', `/
|
|
134
|
+
return await this.makeRequest('GET', `/privacy/${id}/privacy`, undefined, {
|
|
135
135
|
cache: true,
|
|
136
136
|
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
137
137
|
});
|
|
@@ -148,7 +148,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
148
148
|
async updatePrivacySettings(settings, userId) {
|
|
149
149
|
try {
|
|
150
150
|
const id = userId || (await this.getCurrentUser()).id;
|
|
151
|
-
return await this.makeRequest('PATCH', `/
|
|
151
|
+
return await this.makeRequest('PATCH', `/privacy/${id}/privacy`, settings, {
|
|
152
152
|
cache: false,
|
|
153
153
|
});
|
|
154
154
|
}
|
|
@@ -161,7 +161,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
161
161
|
*/
|
|
162
162
|
async requestAccountVerification(reason, evidence) {
|
|
163
163
|
try {
|
|
164
|
-
return await this.makeRequest('POST', '/
|
|
164
|
+
return await this.makeRequest('POST', '/users/verify/request', {
|
|
165
165
|
reason,
|
|
166
166
|
evidence,
|
|
167
167
|
}, { cache: false });
|
|
@@ -178,7 +178,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
178
178
|
// Use httpService for blob responses (it handles blob responses automatically)
|
|
179
179
|
const result = await this.getClient().request({
|
|
180
180
|
method: 'GET',
|
|
181
|
-
url: `/
|
|
181
|
+
url: `/users/me/data`,
|
|
182
182
|
params: { format },
|
|
183
183
|
cache: false,
|
|
184
184
|
});
|
|
@@ -195,7 +195,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
195
195
|
*/
|
|
196
196
|
async deleteAccount(password, confirmText) {
|
|
197
197
|
try {
|
|
198
|
-
return await this.makeRequest('DELETE', '/
|
|
198
|
+
return await this.makeRequest('DELETE', '/users/me', {
|
|
199
199
|
password,
|
|
200
200
|
confirmText,
|
|
201
201
|
}, { cache: false });
|
|
@@ -209,7 +209,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
209
209
|
*/
|
|
210
210
|
async followUser(userId) {
|
|
211
211
|
try {
|
|
212
|
-
return await this.makeRequest('POST', `/
|
|
212
|
+
return await this.makeRequest('POST', `/users/${userId}/follow`, undefined, { cache: false });
|
|
213
213
|
}
|
|
214
214
|
catch (error) {
|
|
215
215
|
throw this.handleError(error);
|
|
@@ -220,7 +220,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
220
220
|
*/
|
|
221
221
|
async unfollowUser(userId) {
|
|
222
222
|
try {
|
|
223
|
-
return await this.makeRequest('DELETE', `/
|
|
223
|
+
return await this.makeRequest('DELETE', `/users/${userId}/follow`, undefined, { cache: false });
|
|
224
224
|
}
|
|
225
225
|
catch (error) {
|
|
226
226
|
throw this.handleError(error);
|
|
@@ -231,7 +231,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
231
231
|
*/
|
|
232
232
|
async getFollowStatus(userId) {
|
|
233
233
|
try {
|
|
234
|
-
return await this.makeRequest('GET', `/
|
|
234
|
+
return await this.makeRequest('GET', `/users/${userId}/follow-status`, undefined, {
|
|
235
235
|
cache: true,
|
|
236
236
|
cacheTTL: 1 * 60 * 1000, // 1 minute cache
|
|
237
237
|
});
|
|
@@ -246,7 +246,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
246
246
|
async getUserFollowers(userId, pagination) {
|
|
247
247
|
try {
|
|
248
248
|
const params = buildPaginationParams(pagination || {});
|
|
249
|
-
const response = await this.makeRequest('GET', `/
|
|
249
|
+
const response = await this.makeRequest('GET', `/users/${userId}/followers`, params, {
|
|
250
250
|
cache: true,
|
|
251
251
|
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
252
252
|
});
|
|
@@ -266,7 +266,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
266
266
|
async getUserFollowing(userId, pagination) {
|
|
267
267
|
try {
|
|
268
268
|
const params = buildPaginationParams(pagination || {});
|
|
269
|
-
const response = await this.makeRequest('GET', `/
|
|
269
|
+
const response = await this.makeRequest('GET', `/users/${userId}/following`, params, {
|
|
270
270
|
cache: true,
|
|
271
271
|
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
272
272
|
});
|
|
@@ -285,7 +285,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
285
285
|
*/
|
|
286
286
|
async getNotifications() {
|
|
287
287
|
return this.withAuthRetry(async () => {
|
|
288
|
-
return await this.makeRequest('GET', '/
|
|
288
|
+
return await this.makeRequest('GET', '/notifications', undefined, {
|
|
289
289
|
cache: false, // Don't cache notifications - always get fresh data
|
|
290
290
|
});
|
|
291
291
|
}, 'getNotifications');
|
|
@@ -295,7 +295,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
295
295
|
*/
|
|
296
296
|
async getUnreadCount() {
|
|
297
297
|
try {
|
|
298
|
-
const res = await this.makeRequest('GET', '/
|
|
298
|
+
const res = await this.makeRequest('GET', '/notifications/unread-count', undefined, {
|
|
299
299
|
cache: false, // Don't cache unread count - always get fresh data
|
|
300
300
|
});
|
|
301
301
|
return res.count;
|
|
@@ -309,7 +309,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
309
309
|
*/
|
|
310
310
|
async createNotification(data) {
|
|
311
311
|
try {
|
|
312
|
-
return await this.makeRequest('POST', '/
|
|
312
|
+
return await this.makeRequest('POST', '/notifications', data, { cache: false });
|
|
313
313
|
}
|
|
314
314
|
catch (error) {
|
|
315
315
|
throw this.handleError(error);
|
|
@@ -320,7 +320,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
320
320
|
*/
|
|
321
321
|
async markNotificationAsRead(notificationId) {
|
|
322
322
|
try {
|
|
323
|
-
await this.makeRequest('PUT', `/
|
|
323
|
+
await this.makeRequest('PUT', `/notifications/${notificationId}/read`, undefined, { cache: false });
|
|
324
324
|
}
|
|
325
325
|
catch (error) {
|
|
326
326
|
throw this.handleError(error);
|
|
@@ -331,7 +331,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
331
331
|
*/
|
|
332
332
|
async markAllNotificationsAsRead() {
|
|
333
333
|
try {
|
|
334
|
-
await this.makeRequest('PUT', '/
|
|
334
|
+
await this.makeRequest('PUT', '/notifications/read-all', undefined, { cache: false });
|
|
335
335
|
}
|
|
336
336
|
catch (error) {
|
|
337
337
|
throw this.handleError(error);
|
|
@@ -342,7 +342,7 @@ export function OxyServicesUserMixin(Base) {
|
|
|
342
342
|
*/
|
|
343
343
|
async deleteNotification(notificationId) {
|
|
344
344
|
try {
|
|
345
|
-
await this.makeRequest('DELETE', `/
|
|
345
|
+
await this.makeRequest('DELETE', `/notifications/${notificationId}`, undefined, { cache: false });
|
|
346
346
|
}
|
|
347
347
|
catch (error) {
|
|
348
348
|
throw this.handleError(error);
|
|
@@ -16,7 +16,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
16
16
|
*/
|
|
17
17
|
async fetchLinkMetadata(url) {
|
|
18
18
|
try {
|
|
19
|
-
return await this.makeRequest('GET', '/
|
|
19
|
+
return await this.makeRequest('GET', '/link-metadata', { url }, {
|
|
20
20
|
cache: true,
|
|
21
21
|
cacheTTL: CACHE_TIMES.EXTRA_LONG,
|
|
22
22
|
});
|
|
@@ -31,25 +31,36 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
31
31
|
* Validates JWT tokens against the Oxy API and attaches user data to requests.
|
|
32
32
|
* Uses server-side session validation for security (not just JWT decode).
|
|
33
33
|
*
|
|
34
|
+
* **Design note — jwtDecode vs jwt.verify:**
|
|
35
|
+
* This middleware intentionally uses `jwtDecode()` (decode-only, no signature
|
|
36
|
+
* verification) for user tokens. This is by design, NOT a security gap:
|
|
37
|
+
* - Third-party apps using `oxy.auth()` don't have the Oxy JWT secret
|
|
38
|
+
* - Security comes from API-based session validation (`validateSession()`)
|
|
39
|
+
* which checks the session server-side on every request
|
|
40
|
+
* - Service tokens (type: 'service') DO use cryptographic HMAC verification
|
|
41
|
+
* via the `jwtSecret` option, since they are stateless
|
|
42
|
+
* - The backend's own `authMiddleware` uses `jwt.verify()` because it has
|
|
43
|
+
* direct access to `ACCESS_TOKEN_SECRET`
|
|
44
|
+
*
|
|
34
45
|
* @example
|
|
35
46
|
* ```typescript
|
|
36
47
|
* import { OxyServices } from '@oxyhq/core';
|
|
37
48
|
*
|
|
38
49
|
* const oxy = new OxyServices({ baseURL: 'https://api.oxy.so' });
|
|
39
50
|
*
|
|
40
|
-
* // Protect all routes under /
|
|
41
|
-
* app.use('/
|
|
51
|
+
* // Protect all routes under /protected
|
|
52
|
+
* app.use('/protected', oxy.auth());
|
|
42
53
|
*
|
|
43
54
|
* // Access user in route handler
|
|
44
|
-
* app.get('/
|
|
55
|
+
* app.get('/protected/me', (req, res) => {
|
|
45
56
|
* res.json({ userId: req.userId, user: req.user });
|
|
46
57
|
* });
|
|
47
58
|
*
|
|
48
59
|
* // Load full user profile from API
|
|
49
|
-
* app.use('/
|
|
60
|
+
* app.use('/admin', oxy.auth({ loadUser: true }));
|
|
50
61
|
*
|
|
51
62
|
* // Optional auth - attach user if present, don't block if absent
|
|
52
|
-
* app.use('/
|
|
63
|
+
* app.use('/public', oxy.auth({ optional: true }));
|
|
53
64
|
* ```
|
|
54
65
|
*
|
|
55
66
|
* @param options Optional configuration
|
|
@@ -83,6 +94,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
83
94
|
return next();
|
|
84
95
|
}
|
|
85
96
|
const error = {
|
|
97
|
+
error: 'MISSING_TOKEN',
|
|
86
98
|
message: 'Access token required',
|
|
87
99
|
code: 'MISSING_TOKEN',
|
|
88
100
|
status: 401
|
|
@@ -103,6 +115,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
103
115
|
return next();
|
|
104
116
|
}
|
|
105
117
|
const error = {
|
|
118
|
+
error: 'INVALID_TOKEN_FORMAT',
|
|
106
119
|
message: 'Invalid token format',
|
|
107
120
|
code: 'INVALID_TOKEN_FORMAT',
|
|
108
121
|
status: 401
|
|
@@ -122,6 +135,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
122
135
|
return next();
|
|
123
136
|
}
|
|
124
137
|
const error = {
|
|
138
|
+
error: 'SERVICE_TOKEN_NOT_CONFIGURED',
|
|
125
139
|
message: 'Service token verification not configured',
|
|
126
140
|
code: 'SERVICE_TOKEN_NOT_CONFIGURED',
|
|
127
141
|
status: 403
|
|
@@ -151,13 +165,22 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
151
165
|
throw new Error('Invalid signature');
|
|
152
166
|
}
|
|
153
167
|
}
|
|
154
|
-
catch {
|
|
168
|
+
catch (verifyError) {
|
|
169
|
+
const isSignatureError = verifyError instanceof Error &&
|
|
170
|
+
(verifyError.message === 'Invalid signature' || verifyError.message === 'Invalid token structure');
|
|
171
|
+
if (!isSignatureError) {
|
|
172
|
+
console.error('[oxy.auth] Unexpected error during service token verification:', verifyError);
|
|
173
|
+
const error = { error: 'AUTH_INTERNAL_ERROR', message: 'Internal authentication error', code: 'AUTH_INTERNAL_ERROR', status: 500 };
|
|
174
|
+
if (onError)
|
|
175
|
+
return onError(error);
|
|
176
|
+
return res.status(500).json(error);
|
|
177
|
+
}
|
|
155
178
|
if (optional) {
|
|
156
179
|
req.userId = null;
|
|
157
180
|
req.user = null;
|
|
158
181
|
return next();
|
|
159
182
|
}
|
|
160
|
-
const error = { message: 'Invalid service token signature', code: 'INVALID_SERVICE_TOKEN', status: 401 };
|
|
183
|
+
const error = { error: 'INVALID_SERVICE_TOKEN', message: 'Invalid service token signature', code: 'INVALID_SERVICE_TOKEN', status: 401 };
|
|
161
184
|
if (onError)
|
|
162
185
|
return onError(error);
|
|
163
186
|
return res.status(401).json(error);
|
|
@@ -169,7 +192,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
169
192
|
req.user = null;
|
|
170
193
|
return next();
|
|
171
194
|
}
|
|
172
|
-
const error = { message: 'Service token expired', code: 'TOKEN_EXPIRED', status: 401 };
|
|
195
|
+
const error = { error: 'TOKEN_EXPIRED', message: 'Service token expired', code: 'TOKEN_EXPIRED', status: 401 };
|
|
173
196
|
if (onError)
|
|
174
197
|
return onError(error);
|
|
175
198
|
return res.status(401).json(error);
|
|
@@ -181,7 +204,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
181
204
|
req.user = null;
|
|
182
205
|
return next();
|
|
183
206
|
}
|
|
184
|
-
const error = { message: 'Invalid service token: missing appId', code: 'INVALID_SERVICE_TOKEN', status: 401 };
|
|
207
|
+
const error = { error: 'INVALID_SERVICE_TOKEN', message: 'Invalid service token: missing appId', code: 'INVALID_SERVICE_TOKEN', status: 401 };
|
|
185
208
|
if (onError)
|
|
186
209
|
return onError(error);
|
|
187
210
|
return res.status(401).json(error);
|
|
@@ -208,6 +231,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
208
231
|
return next();
|
|
209
232
|
}
|
|
210
233
|
const error = {
|
|
234
|
+
error: 'INVALID_TOKEN_PAYLOAD',
|
|
211
235
|
message: 'Token missing user ID',
|
|
212
236
|
code: 'INVALID_TOKEN_PAYLOAD',
|
|
213
237
|
status: 401
|
|
@@ -224,6 +248,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
224
248
|
return next();
|
|
225
249
|
}
|
|
226
250
|
const error = {
|
|
251
|
+
error: 'TOKEN_EXPIRED',
|
|
227
252
|
message: 'Token expired',
|
|
228
253
|
code: 'TOKEN_EXPIRED',
|
|
229
254
|
status: 401
|
|
@@ -246,6 +271,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
246
271
|
return next();
|
|
247
272
|
}
|
|
248
273
|
const error = {
|
|
274
|
+
error: 'INVALID_SESSION',
|
|
249
275
|
message: 'Session invalid or expired',
|
|
250
276
|
code: 'INVALID_SESSION',
|
|
251
277
|
status: 401
|
|
@@ -280,6 +306,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
280
306
|
return next();
|
|
281
307
|
}
|
|
282
308
|
const error = {
|
|
309
|
+
error: 'SESSION_VALIDATION_ERROR',
|
|
283
310
|
message: 'Session validation failed',
|
|
284
311
|
code: 'SESSION_VALIDATION_ERROR',
|
|
285
312
|
status: 401
|
|
@@ -337,6 +364,8 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
337
364
|
* Returns a middleware function for Socket.IO that validates JWT tokens
|
|
338
365
|
* from the handshake auth object and attaches user data to the socket.
|
|
339
366
|
*
|
|
367
|
+
* Uses `jwtDecode()` + API session validation (same rationale as `auth()`).
|
|
368
|
+
*
|
|
340
369
|
* @example
|
|
341
370
|
* ```typescript
|
|
342
371
|
* import { OxyServices } from '@oxyhq/core';
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication helper utilities for common token validation
|
|
3
|
+
* and authentication error handling patterns.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Error thrown when session sync is required
|
|
7
|
+
*/
|
|
8
|
+
export class SessionSyncRequiredError extends Error {
|
|
9
|
+
constructor(message = 'Session needs to be synced. Please try again.') {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'SessionSyncRequiredError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Error thrown when authentication fails
|
|
16
|
+
*/
|
|
17
|
+
export class AuthenticationFailedError extends Error {
|
|
18
|
+
constructor(message = 'Authentication failed. Please sign in again.') {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'AuthenticationFailedError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Ensures a valid token exists before making authenticated API calls.
|
|
25
|
+
* If no valid token exists and an active session ID is available,
|
|
26
|
+
* attempts to refresh the token using the session.
|
|
27
|
+
*
|
|
28
|
+
* @throws {SessionSyncRequiredError} If the session needs to be synced (offline session)
|
|
29
|
+
*/
|
|
30
|
+
export async function ensureValidToken(oxyServices, activeSessionId) {
|
|
31
|
+
if (oxyServices.hasValidToken() || !activeSessionId) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
await oxyServices.getTokenBySession(activeSessionId);
|
|
36
|
+
}
|
|
37
|
+
catch (tokenError) {
|
|
38
|
+
const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
|
|
39
|
+
if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
|
|
40
|
+
throw new SessionSyncRequiredError();
|
|
41
|
+
}
|
|
42
|
+
throw tokenError;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Checks if an error is an authentication error (401 or auth-related message)
|
|
47
|
+
*/
|
|
48
|
+
export function isAuthenticationError(error) {
|
|
49
|
+
if (!error || typeof error !== 'object') {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const errorObj = error;
|
|
53
|
+
const errorMessage = errorObj.message || '';
|
|
54
|
+
const status = errorObj.status || errorObj.response?.status;
|
|
55
|
+
return (status === 401 ||
|
|
56
|
+
errorMessage.includes('Authentication required') ||
|
|
57
|
+
errorMessage.includes('Invalid or missing authorization header'));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Wraps an API call with authentication error handling.
|
|
61
|
+
* On auth failure, optionally attempts to sync the session and retry.
|
|
62
|
+
*
|
|
63
|
+
* @throws {AuthenticationFailedError} If authentication fails and cannot be recovered
|
|
64
|
+
*/
|
|
65
|
+
export async function withAuthErrorHandling(apiCall, options) {
|
|
66
|
+
try {
|
|
67
|
+
return await apiCall();
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (!isAuthenticationError(error)) {
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
if (options?.syncSession && options?.activeSessionId && options?.oxyServices) {
|
|
74
|
+
try {
|
|
75
|
+
await options.syncSession();
|
|
76
|
+
await options.oxyServices.getTokenBySession(options.activeSessionId);
|
|
77
|
+
return await apiCall();
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
throw new AuthenticationFailedError();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
throw new AuthenticationFailedError();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Combines token validation and auth error handling for a complete authenticated API call.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* return await authenticatedApiCall(
|
|
92
|
+
* oxyServices,
|
|
93
|
+
* activeSessionId,
|
|
94
|
+
* () => oxyServices.updateProfile(updates)
|
|
95
|
+
* );
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export async function authenticatedApiCall(oxyServices, activeSessionId, apiCall, syncSession) {
|
|
99
|
+
await ensureValidToken(oxyServices, activeSessionId);
|
|
100
|
+
return withAuthErrorHandling(apiCall, {
|
|
101
|
+
syncSession,
|
|
102
|
+
activeSessionId,
|
|
103
|
+
oxyServices,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
@@ -74,6 +74,18 @@ export function isIOS() {
|
|
|
74
74
|
export function isAndroid() {
|
|
75
75
|
return getPlatformOS() === 'android';
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Check if running in React Native
|
|
79
|
+
*/
|
|
80
|
+
export function isReactNative() {
|
|
81
|
+
return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if running in Node.js
|
|
85
|
+
*/
|
|
86
|
+
export function isNodeJS() {
|
|
87
|
+
return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
|
|
88
|
+
}
|
|
77
89
|
/**
|
|
78
90
|
* Set the platform OS explicitly
|
|
79
91
|
* Called by React Native entry point to register the platform
|
|
@@ -30,6 +30,8 @@ interface RequestConfig extends RequestOptions {
|
|
|
30
30
|
params?: Record<string, unknown>;
|
|
31
31
|
/** @internal Used to prevent infinite auth retry loops */
|
|
32
32
|
_isAuthRetry?: boolean;
|
|
33
|
+
/** @internal Used to prevent infinite CSRF retry loops */
|
|
34
|
+
_isCsrfRetry?: boolean;
|
|
33
35
|
}
|
|
34
36
|
/**
|
|
35
37
|
* Unified HTTP Service
|
|
@@ -46,6 +48,7 @@ export declare class HttpService {
|
|
|
46
48
|
private logger;
|
|
47
49
|
private config;
|
|
48
50
|
private tokenRefreshPromise;
|
|
51
|
+
private tokenRefreshCooldownUntil;
|
|
49
52
|
private _onTokenRefreshed;
|
|
50
53
|
private requestMetrics;
|
|
51
54
|
constructor(config: OxyConfig);
|
|
@@ -164,6 +167,6 @@ export declare class HttpService {
|
|
|
164
167
|
cacheMisses: number;
|
|
165
168
|
averageResponseTime: number;
|
|
166
169
|
};
|
|
167
|
-
|
|
170
|
+
__resetTokensForTests(): void;
|
|
168
171
|
}
|
|
169
172
|
export {};
|
|
@@ -11,7 +11,7 @@ export declare class OxyServicesBase {
|
|
|
11
11
|
cloudURL: string;
|
|
12
12
|
config: OxyConfig;
|
|
13
13
|
constructor(...args: any[]);
|
|
14
|
-
|
|
14
|
+
__resetTokensForTests(): void;
|
|
15
15
|
/**
|
|
16
16
|
* Make a request with all performance optimizations
|
|
17
17
|
* This is the main method for all API calls - ensures authentication and performance features
|
package/dist/types/index.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ export { DEFAULT_CIRCUIT_BREAKER_CONFIG, createCircuitBreakerState, calculateBac
|
|
|
43
43
|
export type { CircuitBreakerState, CircuitBreakerConfig } from './shared/utils/networkUtils';
|
|
44
44
|
export { isDev, debugLog, debugWarn, debugError, createDebugLogger, } from './shared/utils/debugUtils';
|
|
45
45
|
export { translate } from './i18n';
|
|
46
|
+
export { SessionSyncRequiredError, AuthenticationFailedError, ensureValidToken, isAuthenticationError, withAuthErrorHandling, authenticatedApiCall, } from './utils/authHelpers';
|
|
47
|
+
export type { HandleApiErrorOptions } from './utils/authHelpers';
|
|
46
48
|
export { mergeSessions, normalizeAndSortSessions, sessionsArraysEqual } from './utils/sessionUtils';
|
|
47
49
|
export { packageInfo } from './constants/version';
|
|
48
50
|
export * from './utils/apiUtils';
|
|
@@ -22,6 +22,7 @@ export declare function OxyServicesAnalyticsMixin<T extends typeof OxyServicesBa
|
|
|
22
22
|
httpService: import("../HttpService").HttpService;
|
|
23
23
|
cloudURL: string;
|
|
24
24
|
config: import("../OxyServices.base").OxyConfig;
|
|
25
|
+
__resetTokensForTests(): void;
|
|
25
26
|
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
26
27
|
getBaseURL(): string;
|
|
27
28
|
getClient(): import("../HttpService").HttpService;
|
|
@@ -64,5 +65,4 @@ export declare function OxyServicesAnalyticsMixin<T extends typeof OxyServicesBa
|
|
|
64
65
|
[key: string]: any;
|
|
65
66
|
}>;
|
|
66
67
|
};
|
|
67
|
-
__resetTokensForTests(): void;
|
|
68
68
|
} & T;
|
|
@@ -91,6 +91,7 @@ export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>
|
|
|
91
91
|
httpService: import("../HttpService").HttpService;
|
|
92
92
|
cloudURL: string;
|
|
93
93
|
config: import("../OxyServices.base").OxyConfig;
|
|
94
|
+
__resetTokensForTests(): void;
|
|
94
95
|
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
95
96
|
getBaseURL(): string;
|
|
96
97
|
getClient(): import("../HttpService").HttpService;
|
|
@@ -133,5 +134,4 @@ export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>
|
|
|
133
134
|
[key: string]: any;
|
|
134
135
|
}>;
|
|
135
136
|
};
|
|
136
|
-
__resetTokensForTests(): void;
|
|
137
137
|
} & T;
|
|
@@ -178,6 +178,7 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
|
|
|
178
178
|
httpService: import("../HttpService").HttpService;
|
|
179
179
|
cloudURL: string;
|
|
180
180
|
config: import("../OxyServices.base").OxyConfig;
|
|
181
|
+
__resetTokensForTests(): void;
|
|
181
182
|
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
182
183
|
getBaseURL(): string;
|
|
183
184
|
getClient(): import("../HttpService").HttpService;
|
|
@@ -220,5 +221,4 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
|
|
|
220
221
|
[key: string]: any;
|
|
221
222
|
}>;
|
|
222
223
|
};
|
|
223
|
-
__resetTokensForTests(): void;
|
|
224
224
|
} & T;
|
|
@@ -55,6 +55,7 @@ export declare function OxyServicesDeveloperMixin<T extends typeof OxyServicesBa
|
|
|
55
55
|
httpService: import("../HttpService").HttpService;
|
|
56
56
|
cloudURL: string;
|
|
57
57
|
config: import("../OxyServices.base").OxyConfig;
|
|
58
|
+
__resetTokensForTests(): void;
|
|
58
59
|
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
59
60
|
getBaseURL(): string;
|
|
60
61
|
getClient(): import("../HttpService").HttpService;
|
|
@@ -97,5 +98,4 @@ export declare function OxyServicesDeveloperMixin<T extends typeof OxyServicesBa
|
|
|
97
98
|
[key: string]: any;
|
|
98
99
|
}>;
|
|
99
100
|
};
|
|
100
|
-
__resetTokensForTests(): void;
|
|
101
101
|
} & T;
|
|
@@ -52,6 +52,7 @@ export declare function OxyServicesDevicesMixin<T extends typeof OxyServicesBase
|
|
|
52
52
|
httpService: import("../HttpService").HttpService;
|
|
53
53
|
cloudURL: string;
|
|
54
54
|
config: import("../OxyServices.base").OxyConfig;
|
|
55
|
+
__resetTokensForTests(): void;
|
|
55
56
|
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
56
57
|
getBaseURL(): string;
|
|
57
58
|
getClient(): import("../HttpService").HttpService;
|
|
@@ -94,5 +95,4 @@ export declare function OxyServicesDevicesMixin<T extends typeof OxyServicesBase
|
|
|
94
95
|
[key: string]: any;
|
|
95
96
|
}>;
|
|
96
97
|
};
|
|
97
|
-
__resetTokensForTests(): void;
|
|
98
98
|
} & T;
|
|
@@ -184,6 +184,7 @@ export declare function OxyServicesFeaturesMixin<T extends typeof OxyServicesBas
|
|
|
184
184
|
httpService: import("../HttpService").HttpService;
|
|
185
185
|
cloudURL: string;
|
|
186
186
|
config: import("../OxyServices.base").OxyConfig;
|
|
187
|
+
__resetTokensForTests(): void;
|
|
187
188
|
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
188
189
|
getBaseURL(): string;
|
|
189
190
|
getClient(): import("../HttpService").HttpService;
|
|
@@ -226,5 +227,4 @@ export declare function OxyServicesFeaturesMixin<T extends typeof OxyServicesBas
|
|
|
226
227
|
[key: string]: any;
|
|
227
228
|
}>;
|
|
228
229
|
};
|
|
229
|
-
__resetTokensForTests(): void;
|
|
230
230
|
} & T;
|
|
@@ -148,6 +148,7 @@ export declare function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(
|
|
|
148
148
|
httpService: import("../HttpService").HttpService;
|
|
149
149
|
cloudURL: string;
|
|
150
150
|
config: import("../OxyServices.base").OxyConfig;
|
|
151
|
+
__resetTokensForTests(): void;
|
|
151
152
|
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
152
153
|
getBaseURL(): string;
|
|
153
154
|
getClient(): import("../HttpService").HttpService;
|
|
@@ -197,6 +198,5 @@ export declare function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(
|
|
|
197
198
|
* Check if FedCM is supported in the current browser
|
|
198
199
|
*/
|
|
199
200
|
isFedCMSupported(): boolean;
|
|
200
|
-
__resetTokensForTests(): void;
|
|
201
201
|
} & T;
|
|
202
202
|
export { OxyServicesFedCMMixin as FedCMMixin };
|
|
@@ -41,6 +41,7 @@ export declare function OxyServicesKarmaMixin<T extends typeof OxyServicesBase>(
|
|
|
41
41
|
httpService: import("../HttpService").HttpService;
|
|
42
42
|
cloudURL: string;
|
|
43
43
|
config: import("../OxyServices.base").OxyConfig;
|
|
44
|
+
__resetTokensForTests(): void;
|
|
44
45
|
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
45
46
|
getBaseURL(): string;
|
|
46
47
|
getClient(): import("../HttpService").HttpService;
|
|
@@ -83,5 +84,4 @@ export declare function OxyServicesKarmaMixin<T extends typeof OxyServicesBase>(
|
|
|
83
84
|
[key: string]: any;
|
|
84
85
|
}>;
|
|
85
86
|
};
|
|
86
|
-
__resetTokensForTests(): void;
|
|
87
87
|
} & T;
|
|
@@ -37,6 +37,7 @@ export declare function OxyServicesLanguageMixin<T extends typeof OxyServicesBas
|
|
|
37
37
|
httpService: import("../HttpService").HttpService;
|
|
38
38
|
cloudURL: string;
|
|
39
39
|
config: import("../OxyServices.base").OxyConfig;
|
|
40
|
+
__resetTokensForTests(): void;
|
|
40
41
|
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
41
42
|
getBaseURL(): string;
|
|
42
43
|
getClient(): import("../HttpService").HttpService;
|
|
@@ -79,5 +80,4 @@ export declare function OxyServicesLanguageMixin<T extends typeof OxyServicesBas
|
|
|
79
80
|
[key: string]: any;
|
|
80
81
|
}>;
|
|
81
82
|
};
|
|
82
|
-
__resetTokensForTests(): void;
|
|
83
83
|
} & T;
|