@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
|
@@ -44,7 +44,7 @@ function OxyServicesPrivacyMixin(Base) {
|
|
|
44
44
|
*/
|
|
45
45
|
async getBlockedUsers() {
|
|
46
46
|
try {
|
|
47
|
-
return await this.makeRequest('GET', '/
|
|
47
|
+
return await this.makeRequest('GET', '/privacy/blocked', undefined, {
|
|
48
48
|
cache: true,
|
|
49
49
|
cacheTTL: 1 * 60 * 1000, // 1 minute cache
|
|
50
50
|
});
|
|
@@ -63,7 +63,7 @@ function OxyServicesPrivacyMixin(Base) {
|
|
|
63
63
|
if (!userId) {
|
|
64
64
|
throw new Error('User ID is required');
|
|
65
65
|
}
|
|
66
|
-
return await this.makeRequest('POST', `/
|
|
66
|
+
return await this.makeRequest('POST', `/privacy/blocked/${userId}`, undefined, {
|
|
67
67
|
cache: false,
|
|
68
68
|
});
|
|
69
69
|
}
|
|
@@ -81,7 +81,7 @@ function OxyServicesPrivacyMixin(Base) {
|
|
|
81
81
|
if (!userId) {
|
|
82
82
|
throw new Error('User ID is required');
|
|
83
83
|
}
|
|
84
|
-
return await this.makeRequest('DELETE', `/
|
|
84
|
+
return await this.makeRequest('DELETE', `/privacy/blocked/${userId}`, undefined, {
|
|
85
85
|
cache: false,
|
|
86
86
|
});
|
|
87
87
|
}
|
|
@@ -106,7 +106,7 @@ function OxyServicesPrivacyMixin(Base) {
|
|
|
106
106
|
*/
|
|
107
107
|
async getRestrictedUsers() {
|
|
108
108
|
try {
|
|
109
|
-
return await this.makeRequest('GET', '/
|
|
109
|
+
return await this.makeRequest('GET', '/privacy/restricted', undefined, {
|
|
110
110
|
cache: true,
|
|
111
111
|
cacheTTL: 1 * 60 * 1000, // 1 minute cache
|
|
112
112
|
});
|
|
@@ -125,7 +125,7 @@ function OxyServicesPrivacyMixin(Base) {
|
|
|
125
125
|
if (!userId) {
|
|
126
126
|
throw new Error('User ID is required');
|
|
127
127
|
}
|
|
128
|
-
return await this.makeRequest('POST', `/
|
|
128
|
+
return await this.makeRequest('POST', `/privacy/restricted/${userId}`, undefined, {
|
|
129
129
|
cache: false,
|
|
130
130
|
});
|
|
131
131
|
}
|
|
@@ -143,7 +143,7 @@ function OxyServicesPrivacyMixin(Base) {
|
|
|
143
143
|
if (!userId) {
|
|
144
144
|
throw new Error('User ID is required');
|
|
145
145
|
}
|
|
146
|
-
return await this.makeRequest('DELETE', `/
|
|
146
|
+
return await this.makeRequest('DELETE', `/privacy/restricted/${userId}`, undefined, {
|
|
147
147
|
cache: false,
|
|
148
148
|
});
|
|
149
149
|
}
|
|
@@ -23,7 +23,7 @@ function OxyServicesSecurityMixin(Base) {
|
|
|
23
23
|
params.offset = offset;
|
|
24
24
|
if (eventType)
|
|
25
25
|
params.eventType = eventType;
|
|
26
|
-
const response = await this.makeRequest('GET', '/
|
|
26
|
+
const response = await this.makeRequest('GET', '/security/activity', params, { cache: false });
|
|
27
27
|
return response;
|
|
28
28
|
}
|
|
29
29
|
catch (error) {
|
|
@@ -51,7 +51,7 @@ function OxyServicesSecurityMixin(Base) {
|
|
|
51
51
|
*/
|
|
52
52
|
async logPrivateKeyExported(deviceId) {
|
|
53
53
|
try {
|
|
54
|
-
await this.makeRequest('POST', '/
|
|
54
|
+
await this.makeRequest('POST', '/security/activity/private-key-exported', { deviceId }, { cache: false });
|
|
55
55
|
}
|
|
56
56
|
catch (error) {
|
|
57
57
|
// Don't throw - logging failures shouldn't break user flow
|
|
@@ -68,7 +68,7 @@ function OxyServicesSecurityMixin(Base) {
|
|
|
68
68
|
*/
|
|
69
69
|
async logBackupCreated(deviceId) {
|
|
70
70
|
try {
|
|
71
|
-
await this.makeRequest('POST', '/
|
|
71
|
+
await this.makeRequest('POST', '/security/activity/backup-created', { deviceId }, { cache: false });
|
|
72
72
|
}
|
|
73
73
|
catch (error) {
|
|
74
74
|
// Don't throw - logging failures shouldn't break user flow
|
|
@@ -12,7 +12,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
12
12
|
*/
|
|
13
13
|
async getProfileByUsername(username) {
|
|
14
14
|
try {
|
|
15
|
-
return await this.makeRequest('GET', `/
|
|
15
|
+
return await this.makeRequest('GET', `/profiles/username/${username}`, undefined, {
|
|
16
16
|
cache: true,
|
|
17
17
|
cacheTTL: 5 * 60 * 1000, // 5 minutes cache for profiles
|
|
18
18
|
});
|
|
@@ -29,7 +29,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
29
29
|
const params = { query, ...pagination };
|
|
30
30
|
const searchParams = (0, apiUtils_1.buildSearchParams)(params);
|
|
31
31
|
const paramsObj = Object.fromEntries(searchParams.entries());
|
|
32
|
-
const response = await this.makeRequest('GET', '/
|
|
32
|
+
const response = await this.makeRequest('GET', '/profiles/search', paramsObj, {
|
|
33
33
|
cache: true,
|
|
34
34
|
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
35
35
|
});
|
|
@@ -74,7 +74,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
74
74
|
*/
|
|
75
75
|
async getProfileRecommendations() {
|
|
76
76
|
return this.withAuthRetry(async () => {
|
|
77
|
-
return await this.makeRequest('GET', '/
|
|
77
|
+
return await this.makeRequest('GET', '/profiles/recommendations', undefined, { cache: true });
|
|
78
78
|
}, 'getProfileRecommendations');
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
@@ -82,7 +82,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
82
82
|
*/
|
|
83
83
|
async getUserById(userId) {
|
|
84
84
|
try {
|
|
85
|
-
return await this.makeRequest('GET', `/
|
|
85
|
+
return await this.makeRequest('GET', `/users/${userId}`, undefined, {
|
|
86
86
|
cache: true,
|
|
87
87
|
cacheTTL: 5 * 60 * 1000, // 5 minutes cache
|
|
88
88
|
});
|
|
@@ -96,7 +96,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
96
96
|
*/
|
|
97
97
|
async getCurrentUser() {
|
|
98
98
|
return this.withAuthRetry(async () => {
|
|
99
|
-
return await this.makeRequest('GET', '/
|
|
99
|
+
return await this.makeRequest('GET', '/users/me', undefined, {
|
|
100
100
|
cache: true,
|
|
101
101
|
cacheTTL: 1 * 60 * 1000, // 1 minute cache for current user
|
|
102
102
|
});
|
|
@@ -108,7 +108,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
108
108
|
*/
|
|
109
109
|
async updateProfile(updates) {
|
|
110
110
|
try {
|
|
111
|
-
return await this.makeRequest('PUT', '/
|
|
111
|
+
return await this.makeRequest('PUT', '/users/me', updates, { cache: false });
|
|
112
112
|
}
|
|
113
113
|
catch (error) {
|
|
114
114
|
const errorAny = error;
|
|
@@ -134,7 +134,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
134
134
|
async getPrivacySettings(userId) {
|
|
135
135
|
try {
|
|
136
136
|
const id = userId || (await this.getCurrentUser()).id;
|
|
137
|
-
return await this.makeRequest('GET', `/
|
|
137
|
+
return await this.makeRequest('GET', `/privacy/${id}/privacy`, undefined, {
|
|
138
138
|
cache: true,
|
|
139
139
|
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
140
140
|
});
|
|
@@ -151,7 +151,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
151
151
|
async updatePrivacySettings(settings, userId) {
|
|
152
152
|
try {
|
|
153
153
|
const id = userId || (await this.getCurrentUser()).id;
|
|
154
|
-
return await this.makeRequest('PATCH', `/
|
|
154
|
+
return await this.makeRequest('PATCH', `/privacy/${id}/privacy`, settings, {
|
|
155
155
|
cache: false,
|
|
156
156
|
});
|
|
157
157
|
}
|
|
@@ -164,7 +164,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
164
164
|
*/
|
|
165
165
|
async requestAccountVerification(reason, evidence) {
|
|
166
166
|
try {
|
|
167
|
-
return await this.makeRequest('POST', '/
|
|
167
|
+
return await this.makeRequest('POST', '/users/verify/request', {
|
|
168
168
|
reason,
|
|
169
169
|
evidence,
|
|
170
170
|
}, { cache: false });
|
|
@@ -181,7 +181,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
181
181
|
// Use httpService for blob responses (it handles blob responses automatically)
|
|
182
182
|
const result = await this.getClient().request({
|
|
183
183
|
method: 'GET',
|
|
184
|
-
url: `/
|
|
184
|
+
url: `/users/me/data`,
|
|
185
185
|
params: { format },
|
|
186
186
|
cache: false,
|
|
187
187
|
});
|
|
@@ -198,7 +198,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
198
198
|
*/
|
|
199
199
|
async deleteAccount(password, confirmText) {
|
|
200
200
|
try {
|
|
201
|
-
return await this.makeRequest('DELETE', '/
|
|
201
|
+
return await this.makeRequest('DELETE', '/users/me', {
|
|
202
202
|
password,
|
|
203
203
|
confirmText,
|
|
204
204
|
}, { cache: false });
|
|
@@ -212,7 +212,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
212
212
|
*/
|
|
213
213
|
async followUser(userId) {
|
|
214
214
|
try {
|
|
215
|
-
return await this.makeRequest('POST', `/
|
|
215
|
+
return await this.makeRequest('POST', `/users/${userId}/follow`, undefined, { cache: false });
|
|
216
216
|
}
|
|
217
217
|
catch (error) {
|
|
218
218
|
throw this.handleError(error);
|
|
@@ -223,7 +223,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
223
223
|
*/
|
|
224
224
|
async unfollowUser(userId) {
|
|
225
225
|
try {
|
|
226
|
-
return await this.makeRequest('DELETE', `/
|
|
226
|
+
return await this.makeRequest('DELETE', `/users/${userId}/follow`, undefined, { cache: false });
|
|
227
227
|
}
|
|
228
228
|
catch (error) {
|
|
229
229
|
throw this.handleError(error);
|
|
@@ -234,7 +234,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
234
234
|
*/
|
|
235
235
|
async getFollowStatus(userId) {
|
|
236
236
|
try {
|
|
237
|
-
return await this.makeRequest('GET', `/
|
|
237
|
+
return await this.makeRequest('GET', `/users/${userId}/follow-status`, undefined, {
|
|
238
238
|
cache: true,
|
|
239
239
|
cacheTTL: 1 * 60 * 1000, // 1 minute cache
|
|
240
240
|
});
|
|
@@ -249,7 +249,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
249
249
|
async getUserFollowers(userId, pagination) {
|
|
250
250
|
try {
|
|
251
251
|
const params = (0, apiUtils_1.buildPaginationParams)(pagination || {});
|
|
252
|
-
const response = await this.makeRequest('GET', `/
|
|
252
|
+
const response = await this.makeRequest('GET', `/users/${userId}/followers`, params, {
|
|
253
253
|
cache: true,
|
|
254
254
|
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
255
255
|
});
|
|
@@ -269,7 +269,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
269
269
|
async getUserFollowing(userId, pagination) {
|
|
270
270
|
try {
|
|
271
271
|
const params = (0, apiUtils_1.buildPaginationParams)(pagination || {});
|
|
272
|
-
const response = await this.makeRequest('GET', `/
|
|
272
|
+
const response = await this.makeRequest('GET', `/users/${userId}/following`, params, {
|
|
273
273
|
cache: true,
|
|
274
274
|
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
275
275
|
});
|
|
@@ -288,7 +288,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
288
288
|
*/
|
|
289
289
|
async getNotifications() {
|
|
290
290
|
return this.withAuthRetry(async () => {
|
|
291
|
-
return await this.makeRequest('GET', '/
|
|
291
|
+
return await this.makeRequest('GET', '/notifications', undefined, {
|
|
292
292
|
cache: false, // Don't cache notifications - always get fresh data
|
|
293
293
|
});
|
|
294
294
|
}, 'getNotifications');
|
|
@@ -298,7 +298,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
298
298
|
*/
|
|
299
299
|
async getUnreadCount() {
|
|
300
300
|
try {
|
|
301
|
-
const res = await this.makeRequest('GET', '/
|
|
301
|
+
const res = await this.makeRequest('GET', '/notifications/unread-count', undefined, {
|
|
302
302
|
cache: false, // Don't cache unread count - always get fresh data
|
|
303
303
|
});
|
|
304
304
|
return res.count;
|
|
@@ -312,7 +312,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
312
312
|
*/
|
|
313
313
|
async createNotification(data) {
|
|
314
314
|
try {
|
|
315
|
-
return await this.makeRequest('POST', '/
|
|
315
|
+
return await this.makeRequest('POST', '/notifications', data, { cache: false });
|
|
316
316
|
}
|
|
317
317
|
catch (error) {
|
|
318
318
|
throw this.handleError(error);
|
|
@@ -323,7 +323,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
323
323
|
*/
|
|
324
324
|
async markNotificationAsRead(notificationId) {
|
|
325
325
|
try {
|
|
326
|
-
await this.makeRequest('PUT', `/
|
|
326
|
+
await this.makeRequest('PUT', `/notifications/${notificationId}/read`, undefined, { cache: false });
|
|
327
327
|
}
|
|
328
328
|
catch (error) {
|
|
329
329
|
throw this.handleError(error);
|
|
@@ -334,7 +334,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
334
334
|
*/
|
|
335
335
|
async markAllNotificationsAsRead() {
|
|
336
336
|
try {
|
|
337
|
-
await this.makeRequest('PUT', '/
|
|
337
|
+
await this.makeRequest('PUT', '/notifications/read-all', undefined, { cache: false });
|
|
338
338
|
}
|
|
339
339
|
catch (error) {
|
|
340
340
|
throw this.handleError(error);
|
|
@@ -345,7 +345,7 @@ function OxyServicesUserMixin(Base) {
|
|
|
345
345
|
*/
|
|
346
346
|
async deleteNotification(notificationId) {
|
|
347
347
|
try {
|
|
348
|
-
await this.makeRequest('DELETE', `/
|
|
348
|
+
await this.makeRequest('DELETE', `/notifications/${notificationId}`, undefined, { cache: false });
|
|
349
349
|
}
|
|
350
350
|
catch (error) {
|
|
351
351
|
throw this.handleError(error);
|
|
@@ -52,7 +52,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
52
52
|
*/
|
|
53
53
|
async fetchLinkMetadata(url) {
|
|
54
54
|
try {
|
|
55
|
-
return await this.makeRequest('GET', '/
|
|
55
|
+
return await this.makeRequest('GET', '/link-metadata', { url }, {
|
|
56
56
|
cache: true,
|
|
57
57
|
cacheTTL: mixinHelpers_1.CACHE_TIMES.EXTRA_LONG,
|
|
58
58
|
});
|
|
@@ -67,25 +67,36 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
67
67
|
* Validates JWT tokens against the Oxy API and attaches user data to requests.
|
|
68
68
|
* Uses server-side session validation for security (not just JWT decode).
|
|
69
69
|
*
|
|
70
|
+
* **Design note — jwtDecode vs jwt.verify:**
|
|
71
|
+
* This middleware intentionally uses `jwtDecode()` (decode-only, no signature
|
|
72
|
+
* verification) for user tokens. This is by design, NOT a security gap:
|
|
73
|
+
* - Third-party apps using `oxy.auth()` don't have the Oxy JWT secret
|
|
74
|
+
* - Security comes from API-based session validation (`validateSession()`)
|
|
75
|
+
* which checks the session server-side on every request
|
|
76
|
+
* - Service tokens (type: 'service') DO use cryptographic HMAC verification
|
|
77
|
+
* via the `jwtSecret` option, since they are stateless
|
|
78
|
+
* - The backend's own `authMiddleware` uses `jwt.verify()` because it has
|
|
79
|
+
* direct access to `ACCESS_TOKEN_SECRET`
|
|
80
|
+
*
|
|
70
81
|
* @example
|
|
71
82
|
* ```typescript
|
|
72
83
|
* import { OxyServices } from '@oxyhq/core';
|
|
73
84
|
*
|
|
74
85
|
* const oxy = new OxyServices({ baseURL: 'https://api.oxy.so' });
|
|
75
86
|
*
|
|
76
|
-
* // Protect all routes under /
|
|
77
|
-
* app.use('/
|
|
87
|
+
* // Protect all routes under /protected
|
|
88
|
+
* app.use('/protected', oxy.auth());
|
|
78
89
|
*
|
|
79
90
|
* // Access user in route handler
|
|
80
|
-
* app.get('/
|
|
91
|
+
* app.get('/protected/me', (req, res) => {
|
|
81
92
|
* res.json({ userId: req.userId, user: req.user });
|
|
82
93
|
* });
|
|
83
94
|
*
|
|
84
95
|
* // Load full user profile from API
|
|
85
|
-
* app.use('/
|
|
96
|
+
* app.use('/admin', oxy.auth({ loadUser: true }));
|
|
86
97
|
*
|
|
87
98
|
* // Optional auth - attach user if present, don't block if absent
|
|
88
|
-
* app.use('/
|
|
99
|
+
* app.use('/public', oxy.auth({ optional: true }));
|
|
89
100
|
* ```
|
|
90
101
|
*
|
|
91
102
|
* @param options Optional configuration
|
|
@@ -119,6 +130,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
119
130
|
return next();
|
|
120
131
|
}
|
|
121
132
|
const error = {
|
|
133
|
+
error: 'MISSING_TOKEN',
|
|
122
134
|
message: 'Access token required',
|
|
123
135
|
code: 'MISSING_TOKEN',
|
|
124
136
|
status: 401
|
|
@@ -139,6 +151,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
139
151
|
return next();
|
|
140
152
|
}
|
|
141
153
|
const error = {
|
|
154
|
+
error: 'INVALID_TOKEN_FORMAT',
|
|
142
155
|
message: 'Invalid token format',
|
|
143
156
|
code: 'INVALID_TOKEN_FORMAT',
|
|
144
157
|
status: 401
|
|
@@ -158,6 +171,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
158
171
|
return next();
|
|
159
172
|
}
|
|
160
173
|
const error = {
|
|
174
|
+
error: 'SERVICE_TOKEN_NOT_CONFIGURED',
|
|
161
175
|
message: 'Service token verification not configured',
|
|
162
176
|
code: 'SERVICE_TOKEN_NOT_CONFIGURED',
|
|
163
177
|
status: 403
|
|
@@ -187,13 +201,22 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
187
201
|
throw new Error('Invalid signature');
|
|
188
202
|
}
|
|
189
203
|
}
|
|
190
|
-
catch {
|
|
204
|
+
catch (verifyError) {
|
|
205
|
+
const isSignatureError = verifyError instanceof Error &&
|
|
206
|
+
(verifyError.message === 'Invalid signature' || verifyError.message === 'Invalid token structure');
|
|
207
|
+
if (!isSignatureError) {
|
|
208
|
+
console.error('[oxy.auth] Unexpected error during service token verification:', verifyError);
|
|
209
|
+
const error = { error: 'AUTH_INTERNAL_ERROR', message: 'Internal authentication error', code: 'AUTH_INTERNAL_ERROR', status: 500 };
|
|
210
|
+
if (onError)
|
|
211
|
+
return onError(error);
|
|
212
|
+
return res.status(500).json(error);
|
|
213
|
+
}
|
|
191
214
|
if (optional) {
|
|
192
215
|
req.userId = null;
|
|
193
216
|
req.user = null;
|
|
194
217
|
return next();
|
|
195
218
|
}
|
|
196
|
-
const error = { message: 'Invalid service token signature', code: 'INVALID_SERVICE_TOKEN', status: 401 };
|
|
219
|
+
const error = { error: 'INVALID_SERVICE_TOKEN', message: 'Invalid service token signature', code: 'INVALID_SERVICE_TOKEN', status: 401 };
|
|
197
220
|
if (onError)
|
|
198
221
|
return onError(error);
|
|
199
222
|
return res.status(401).json(error);
|
|
@@ -205,7 +228,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
205
228
|
req.user = null;
|
|
206
229
|
return next();
|
|
207
230
|
}
|
|
208
|
-
const error = { message: 'Service token expired', code: 'TOKEN_EXPIRED', status: 401 };
|
|
231
|
+
const error = { error: 'TOKEN_EXPIRED', message: 'Service token expired', code: 'TOKEN_EXPIRED', status: 401 };
|
|
209
232
|
if (onError)
|
|
210
233
|
return onError(error);
|
|
211
234
|
return res.status(401).json(error);
|
|
@@ -217,7 +240,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
217
240
|
req.user = null;
|
|
218
241
|
return next();
|
|
219
242
|
}
|
|
220
|
-
const error = { message: 'Invalid service token: missing appId', code: 'INVALID_SERVICE_TOKEN', status: 401 };
|
|
243
|
+
const error = { error: 'INVALID_SERVICE_TOKEN', message: 'Invalid service token: missing appId', code: 'INVALID_SERVICE_TOKEN', status: 401 };
|
|
221
244
|
if (onError)
|
|
222
245
|
return onError(error);
|
|
223
246
|
return res.status(401).json(error);
|
|
@@ -244,6 +267,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
244
267
|
return next();
|
|
245
268
|
}
|
|
246
269
|
const error = {
|
|
270
|
+
error: 'INVALID_TOKEN_PAYLOAD',
|
|
247
271
|
message: 'Token missing user ID',
|
|
248
272
|
code: 'INVALID_TOKEN_PAYLOAD',
|
|
249
273
|
status: 401
|
|
@@ -260,6 +284,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
260
284
|
return next();
|
|
261
285
|
}
|
|
262
286
|
const error = {
|
|
287
|
+
error: 'TOKEN_EXPIRED',
|
|
263
288
|
message: 'Token expired',
|
|
264
289
|
code: 'TOKEN_EXPIRED',
|
|
265
290
|
status: 401
|
|
@@ -282,6 +307,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
282
307
|
return next();
|
|
283
308
|
}
|
|
284
309
|
const error = {
|
|
310
|
+
error: 'INVALID_SESSION',
|
|
285
311
|
message: 'Session invalid or expired',
|
|
286
312
|
code: 'INVALID_SESSION',
|
|
287
313
|
status: 401
|
|
@@ -316,6 +342,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
316
342
|
return next();
|
|
317
343
|
}
|
|
318
344
|
const error = {
|
|
345
|
+
error: 'SESSION_VALIDATION_ERROR',
|
|
319
346
|
message: 'Session validation failed',
|
|
320
347
|
code: 'SESSION_VALIDATION_ERROR',
|
|
321
348
|
status: 401
|
|
@@ -373,6 +400,8 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
373
400
|
* Returns a middleware function for Socket.IO that validates JWT tokens
|
|
374
401
|
* from the handshake auth object and attaches user data to the socket.
|
|
375
402
|
*
|
|
403
|
+
* Uses `jwtDecode()` + API session validation (same rationale as `auth()`).
|
|
404
|
+
*
|
|
376
405
|
* @example
|
|
377
406
|
* ```typescript
|
|
378
407
|
* import { OxyServices } from '@oxyhq/core';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Authentication helper utilities for common token validation
|
|
4
|
+
* and authentication error handling patterns.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.AuthenticationFailedError = exports.SessionSyncRequiredError = void 0;
|
|
8
|
+
exports.ensureValidToken = ensureValidToken;
|
|
9
|
+
exports.isAuthenticationError = isAuthenticationError;
|
|
10
|
+
exports.withAuthErrorHandling = withAuthErrorHandling;
|
|
11
|
+
exports.authenticatedApiCall = authenticatedApiCall;
|
|
12
|
+
/**
|
|
13
|
+
* Error thrown when session sync is required
|
|
14
|
+
*/
|
|
15
|
+
class SessionSyncRequiredError extends Error {
|
|
16
|
+
constructor(message = 'Session needs to be synced. Please try again.') {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'SessionSyncRequiredError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.SessionSyncRequiredError = SessionSyncRequiredError;
|
|
22
|
+
/**
|
|
23
|
+
* Error thrown when authentication fails
|
|
24
|
+
*/
|
|
25
|
+
class AuthenticationFailedError extends Error {
|
|
26
|
+
constructor(message = 'Authentication failed. Please sign in again.') {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = 'AuthenticationFailedError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.AuthenticationFailedError = AuthenticationFailedError;
|
|
32
|
+
/**
|
|
33
|
+
* Ensures a valid token exists before making authenticated API calls.
|
|
34
|
+
* If no valid token exists and an active session ID is available,
|
|
35
|
+
* attempts to refresh the token using the session.
|
|
36
|
+
*
|
|
37
|
+
* @throws {SessionSyncRequiredError} If the session needs to be synced (offline session)
|
|
38
|
+
*/
|
|
39
|
+
async function ensureValidToken(oxyServices, activeSessionId) {
|
|
40
|
+
if (oxyServices.hasValidToken() || !activeSessionId) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
await oxyServices.getTokenBySession(activeSessionId);
|
|
45
|
+
}
|
|
46
|
+
catch (tokenError) {
|
|
47
|
+
const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
|
|
48
|
+
if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
|
|
49
|
+
throw new SessionSyncRequiredError();
|
|
50
|
+
}
|
|
51
|
+
throw tokenError;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Checks if an error is an authentication error (401 or auth-related message)
|
|
56
|
+
*/
|
|
57
|
+
function isAuthenticationError(error) {
|
|
58
|
+
if (!error || typeof error !== 'object') {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const errorObj = error;
|
|
62
|
+
const errorMessage = errorObj.message || '';
|
|
63
|
+
const status = errorObj.status || errorObj.response?.status;
|
|
64
|
+
return (status === 401 ||
|
|
65
|
+
errorMessage.includes('Authentication required') ||
|
|
66
|
+
errorMessage.includes('Invalid or missing authorization header'));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Wraps an API call with authentication error handling.
|
|
70
|
+
* On auth failure, optionally attempts to sync the session and retry.
|
|
71
|
+
*
|
|
72
|
+
* @throws {AuthenticationFailedError} If authentication fails and cannot be recovered
|
|
73
|
+
*/
|
|
74
|
+
async function withAuthErrorHandling(apiCall, options) {
|
|
75
|
+
try {
|
|
76
|
+
return await apiCall();
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (!isAuthenticationError(error)) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
if (options?.syncSession && options?.activeSessionId && options?.oxyServices) {
|
|
83
|
+
try {
|
|
84
|
+
await options.syncSession();
|
|
85
|
+
await options.oxyServices.getTokenBySession(options.activeSessionId);
|
|
86
|
+
return await apiCall();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
throw new AuthenticationFailedError();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
throw new AuthenticationFailedError();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Combines token validation and auth error handling for a complete authenticated API call.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* return await authenticatedApiCall(
|
|
101
|
+
* oxyServices,
|
|
102
|
+
* activeSessionId,
|
|
103
|
+
* () => oxyServices.updateProfile(updates)
|
|
104
|
+
* );
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
async function authenticatedApiCall(oxyServices, activeSessionId, apiCall, syncSession) {
|
|
108
|
+
await ensureValidToken(oxyServices, activeSessionId);
|
|
109
|
+
return withAuthErrorHandling(apiCall, {
|
|
110
|
+
syncSession,
|
|
111
|
+
activeSessionId,
|
|
112
|
+
oxyServices,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
@@ -12,6 +12,8 @@ exports.isWeb = isWeb;
|
|
|
12
12
|
exports.isNative = isNative;
|
|
13
13
|
exports.isIOS = isIOS;
|
|
14
14
|
exports.isAndroid = isAndroid;
|
|
15
|
+
exports.isReactNative = isReactNative;
|
|
16
|
+
exports.isNodeJS = isNodeJS;
|
|
15
17
|
exports.setPlatformOS = setPlatformOS;
|
|
16
18
|
/**
|
|
17
19
|
* Detect the current platform without importing react-native
|
|
@@ -82,6 +84,18 @@ function isIOS() {
|
|
|
82
84
|
function isAndroid() {
|
|
83
85
|
return getPlatformOS() === 'android';
|
|
84
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if running in React Native
|
|
89
|
+
*/
|
|
90
|
+
function isReactNative() {
|
|
91
|
+
return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Check if running in Node.js
|
|
95
|
+
*/
|
|
96
|
+
function isNodeJS() {
|
|
97
|
+
return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
|
|
98
|
+
}
|
|
85
99
|
/**
|
|
86
100
|
* Set the platform OS explicitly
|
|
87
101
|
* Called by React Native entry point to register the platform
|
package/dist/esm/AuthManager.js
CHANGED
|
@@ -243,7 +243,7 @@ export class AuthManager {
|
|
|
243
243
|
// Use session-based token endpoint which handles auto-refresh server-side
|
|
244
244
|
const response = await httpService.request({
|
|
245
245
|
method: 'GET',
|
|
246
|
-
url: `/
|
|
246
|
+
url: `/session/token/${sessionId}`,
|
|
247
247
|
cache: false,
|
|
248
248
|
retry: false,
|
|
249
249
|
});
|
|
@@ -296,6 +296,19 @@ export class AuthManager {
|
|
|
296
296
|
clearTimeout(this.refreshTimer);
|
|
297
297
|
this.refreshTimer = null;
|
|
298
298
|
}
|
|
299
|
+
// Invalidate current session on the server (best-effort)
|
|
300
|
+
try {
|
|
301
|
+
const sessionJson = await this.storage.getItem(STORAGE_KEYS.SESSION);
|
|
302
|
+
if (sessionJson) {
|
|
303
|
+
const session = JSON.parse(sessionJson);
|
|
304
|
+
if (session.sessionId && typeof this.oxyServices.logoutSession === 'function') {
|
|
305
|
+
await this.oxyServices.logoutSession(session.sessionId);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
// Best-effort: don't block local signout on network failure
|
|
311
|
+
}
|
|
299
312
|
// Revoke FedCM credential if supported
|
|
300
313
|
try {
|
|
301
314
|
const services = this.oxyServices;
|