propro-utils 1.7.31 → 1.7.37
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/package-lock.json +9951 -0
- package/package.json +1 -1
- package/src/server/index.js +26 -5
- package/src/server/middleware/verifyToken.js +33 -25
package/package.json
CHANGED
package/src/server/index.js
CHANGED
|
@@ -251,13 +251,18 @@ class AuthMiddleware {
|
|
|
251
251
|
account: { accountId: account.accountId, email: account.email },
|
|
252
252
|
};
|
|
253
253
|
} catch (error) {
|
|
254
|
-
|
|
254
|
+
const status = error?.response?.status;
|
|
255
|
+
console.error(`Token refresh failed after ${Date.now() - startTime}ms (status: ${status}):`, error.message);
|
|
256
|
+
|
|
257
|
+
// Immediately clean up lock on failure to prevent blocking
|
|
258
|
+
this.refreshLocks.delete(lockKey);
|
|
259
|
+
|
|
255
260
|
throw error;
|
|
256
261
|
} finally {
|
|
257
|
-
// Clean up lock after 30 seconds
|
|
262
|
+
// Clean up lock after 30 seconds for successful requests
|
|
258
263
|
setTimeout(() => {
|
|
259
264
|
this.refreshLocks.delete(lockKey);
|
|
260
|
-
console.log('Refresh lock cleaned up');
|
|
265
|
+
console.log('Refresh lock cleaned up (delayed)');
|
|
261
266
|
}, 30000);
|
|
262
267
|
}
|
|
263
268
|
})();
|
|
@@ -270,11 +275,27 @@ class AuthMiddleware {
|
|
|
270
275
|
res.status(200).json(result);
|
|
271
276
|
} catch (error) {
|
|
272
277
|
console.error('Error refreshing token:', error);
|
|
278
|
+
|
|
279
|
+
const status = error?.response?.status || 401;
|
|
273
280
|
const errorMessage = error?.response?.data?.message || error?.message || 'Failed to refresh token';
|
|
274
|
-
|
|
281
|
+
|
|
282
|
+
// Pass through rate limit status
|
|
283
|
+
if (status === 429) {
|
|
284
|
+
const retryAfter = error?.response?.headers?.['retry-after'];
|
|
285
|
+
return res.status(429).json({
|
|
286
|
+
error: 'Too many requests',
|
|
287
|
+
message: 'Rate limit exceeded. Please try again later.',
|
|
288
|
+
retryAfter: retryAfter ? parseInt(retryAfter) : 900,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Handle 401 unauthorized
|
|
293
|
+
res.status(status).json({
|
|
275
294
|
error: 'Failed to refresh token',
|
|
276
295
|
message: errorMessage,
|
|
277
|
-
details:
|
|
296
|
+
details: status === 401
|
|
297
|
+
? 'Your session has expired. Please log in again.'
|
|
298
|
+
: 'Your session could not be refreshed. Please log in again.',
|
|
278
299
|
});
|
|
279
300
|
}
|
|
280
301
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const axios = require(
|
|
2
|
-
const jwt = require(
|
|
1
|
+
const axios = require("axios");
|
|
2
|
+
const jwt = require("jsonwebtoken");
|
|
3
3
|
const tokenCache = new Map();
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -8,18 +8,26 @@ const tokenCache = new Map();
|
|
|
8
8
|
* @param {string} secret - The secret used to sign the JWT.
|
|
9
9
|
* @returns {promise<object|null>} - The decoded payload of the JWT if it is valid, or null if it is not valid.
|
|
10
10
|
*/
|
|
11
|
-
async function verifyJWT(token, secret =
|
|
11
|
+
async function verifyJWT(token, secret = "thisisasamplesecret") {
|
|
12
|
+
if (!secret) {
|
|
13
|
+
// Require JWT_SECRET from environment variables
|
|
14
|
+
if (!process.env.JWT_SECRET) {
|
|
15
|
+
throw new Error("JWT_SECRET environment variable is required");
|
|
16
|
+
}
|
|
17
|
+
secret = process.env.JWT_SECRET;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
try {
|
|
13
21
|
return jwt.verify(token, secret);
|
|
14
22
|
} catch (err) {
|
|
15
|
-
try{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
}catch(e){
|
|
21
|
-
return null
|
|
23
|
+
try {
|
|
24
|
+
if (err.name === "TokenExpiredError") {
|
|
25
|
+
const newTokenData = await callTokenEndpoint(token);
|
|
26
|
+
return newTokenData ? jwt?.verify?.(newTokenData, secret) : null;
|
|
22
27
|
}
|
|
28
|
+
} catch (e) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
23
31
|
return null;
|
|
24
32
|
}
|
|
25
33
|
}
|
|
@@ -59,22 +67,22 @@ async function exchangeToken(
|
|
|
59
67
|
try {
|
|
60
68
|
const formattedRedirectUri = formatRedirectUrl(authUrl);
|
|
61
69
|
const response = await axios.post(
|
|
62
|
-
formattedRedirectUri +
|
|
70
|
+
formattedRedirectUri + "/api/v1/auth/authorize",
|
|
63
71
|
{
|
|
64
|
-
grantType:
|
|
72
|
+
grantType: "authorization_code",
|
|
65
73
|
code: code,
|
|
66
74
|
redirectUri: redirectUri,
|
|
67
75
|
clientId: clientId,
|
|
68
76
|
clientSecret: clientSecret,
|
|
69
77
|
},
|
|
70
78
|
{
|
|
71
|
-
headers: {
|
|
79
|
+
headers: { "Content-Type": "application/json" },
|
|
72
80
|
}
|
|
73
81
|
);
|
|
74
82
|
|
|
75
83
|
return response.data;
|
|
76
84
|
} catch (error) {
|
|
77
|
-
console.error(
|
|
85
|
+
console.error("Error exchanging token:", error);
|
|
78
86
|
return null;
|
|
79
87
|
}
|
|
80
88
|
}
|
|
@@ -84,11 +92,11 @@ async function exchangeToken(
|
|
|
84
92
|
* @param {Array} requiredPermissions - Array of required permissions for the user
|
|
85
93
|
* @returns {Function} - Express middleware function
|
|
86
94
|
*/
|
|
87
|
-
const VerifyAccount = requiredPermissions => {
|
|
95
|
+
const VerifyAccount = (requiredPermissions) => {
|
|
88
96
|
return async (req, res, next) => {
|
|
89
|
-
const accessToken = req.headers.authorization?.split(
|
|
97
|
+
const accessToken = req.headers.authorization?.split(" ")[1];
|
|
90
98
|
if (!accessToken) {
|
|
91
|
-
return res.status(401).json({ error:
|
|
99
|
+
return res.status(401).json({ error: "Access token is required" });
|
|
92
100
|
}
|
|
93
101
|
|
|
94
102
|
// Check if token is in cache
|
|
@@ -100,7 +108,7 @@ const VerifyAccount = requiredPermissions => {
|
|
|
100
108
|
try {
|
|
101
109
|
const decoded = jwt.verify(accessToken, process.env.JWT_SECRET);
|
|
102
110
|
if (!isValid(decoded, requiredPermissions)) {
|
|
103
|
-
return res.status(403).json({ error:
|
|
111
|
+
return res.status(403).json({ error: "Invalid permissions" });
|
|
104
112
|
}
|
|
105
113
|
tokenCache.set(accessToken, decoded);
|
|
106
114
|
req.user = decoded;
|
|
@@ -117,14 +125,14 @@ const VerifyAccount = requiredPermissions => {
|
|
|
117
125
|
);
|
|
118
126
|
|
|
119
127
|
if (!isValid(userResponse.data, requiredPermissions)) {
|
|
120
|
-
|
|
128
|
+
return res.status(403).json({ error: "Invalid permissions" });
|
|
121
129
|
}
|
|
122
130
|
|
|
123
131
|
tokenCache.set(accessToken, userResponse.data);
|
|
124
132
|
req.user = userResponse.data;
|
|
125
133
|
return next();
|
|
126
134
|
} catch (networkError) {
|
|
127
|
-
return res.status(401).json({ error:
|
|
135
|
+
return res.status(401).json({ error: "Error validating token" });
|
|
128
136
|
}
|
|
129
137
|
}
|
|
130
138
|
};
|
|
@@ -137,7 +145,7 @@ const VerifyAccount = requiredPermissions => {
|
|
|
137
145
|
* @returns {boolean} - Returns true if the decoded token has all the required permissions, false otherwise.
|
|
138
146
|
*/
|
|
139
147
|
function isValid(decodedToken, requiredPermissions) {
|
|
140
|
-
return requiredPermissions.every(permission =>
|
|
148
|
+
return requiredPermissions.every((permission) =>
|
|
141
149
|
decodedToken.permissions.includes(permission)
|
|
142
150
|
);
|
|
143
151
|
}
|
|
@@ -152,12 +160,12 @@ function formatRedirectUrl(redirectUrl) {
|
|
|
152
160
|
let urlToRedirect;
|
|
153
161
|
|
|
154
162
|
if (
|
|
155
|
-
!redirectUrl.startsWith(
|
|
156
|
-
!redirectUrl.startsWith(
|
|
163
|
+
!redirectUrl.startsWith("http://") &&
|
|
164
|
+
!redirectUrl.startsWith("https://")
|
|
157
165
|
) {
|
|
158
166
|
if (
|
|
159
|
-
redirectUrl.includes(
|
|
160
|
-
redirectUrl.includes(
|
|
167
|
+
redirectUrl.includes("localhost") |
|
|
168
|
+
redirectUrl.includes("host.docker.internal")
|
|
161
169
|
) {
|
|
162
170
|
urlToRedirect = `http://${redirectUrl}`;
|
|
163
171
|
} else {
|