propro-utils 1.3.45 → 1.3.47

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.
@@ -1,39 +1,86 @@
1
- require("dotenv").config();
2
- const axios = require("axios");
3
- const {getOrSetCache} = require("../utils/redis");
1
+ require('dotenv').config();
2
+ const axios = require('axios');
3
+ const { getOrSetCache } = require('../utils/redis');
4
+ const { checkIfUserExists } = require('./account_info');
4
5
 
5
- const authValidation = (redisClient, requiredPermissions = []) => {
6
- return async (req, res, next) => {
7
- try {
8
- const accessToken = req.cookies['x-access-token'] || req.headers.authorization?.split(" ")[1];
6
+ /**
7
+ * Middleware for authenticating and authorizing API requests.
8
+ * It validates an access token and checks for required permissions using both a Redis cache
9
+ * and propro authentication service.
10
+ *
11
+ * @param {object} redisClient - The Redis client used for caching permission data.
12
+ * @param {object} userSchema - The user schema/model object.
13
+ * @param {string[]} [requiredPermissions=[]] - An array of permissions required to access the endpoint.
14
+ * This function first attempts to retrieve the account's permissions from the cache.
15
+ * If the cache is empty or expired, it fetches permissions from propro authentication service
16
+ * and updates the cache. If the access token is invalid or does not grant the required permissions,
17
+ * the request is rejected.
18
+ *
19
+ * @returns {function} A middleware function that takes Express.js request (req), response (res),
20
+ * and next function parameters. The middleware validates the access token and permissions.
21
+ * If validation is successful, it adds the account ID and req.user to the request object (req.account) and
22
+ * calls `next()` to pass control to the next middleware. If validation fails, it responds with
23
+ * an error message and a 403 status code.
24
+ *
25
+ * Usage of the middleware requires an environment variable `AUTH_URL` to be set, pointing to the
26
+ * authentication service's URL. The function leverages async/await for asynchronous operations
27
+ * and tries to handle errors gracefully, reporting them through the next middleware in the chain.
28
+ *
29
+ * @example
30
+ * // Applying authValidation middleware
31
+ * const redisClient = require('./redisClient');
32
+ * app.use('/api/protected-route', authValidation(redisClient, ['admin', 'user']), (req, res) => {
33
+ * res.json({ message: 'You have access to protected data' });
34
+ * });
35
+ */
36
+ const authValidation = (redisClient, userSchema, requiredPermissions = []) => {
37
+ return async (req, res, next) => {
38
+ try {
39
+ const accessToken =
40
+ req.cookies['x-access-token'] ||
41
+ req.headers.authorization?.split(' ')[1];
9
42
 
10
- if (!accessToken) {
11
- return res.status(403).json({ error: "Access token is required" });
12
- }
43
+ if (!accessToken) {
44
+ return res.status(403).json({ error: 'Access token is required' });
45
+ }
13
46
 
14
- const fetchPermission = async () => {
15
- const response = await axios.post(`${process.env.AUTH_URL}/api/v1/auth/validateToken`, {
16
- accessToken: accessToken,
17
- requiredPermissions: requiredPermissions
18
- });
19
- return response.data;
20
- }
21
- const cacheKey = `account:permissions:${accessToken}`;
22
- const { accountId, validPermissions } = await getOrSetCache(redisClient, cacheKey, fetchPermission, 1800);
47
+ const fetchPermission = async () => {
48
+ const response = await axios.post(
49
+ `${process.env.AUTH_URL}/api/v1/auth/validateToken`,
50
+ {
51
+ accessToken: accessToken,
52
+ requiredPermissions: requiredPermissions,
53
+ }
54
+ );
55
+ return response.data;
56
+ };
57
+ const cacheKey = `account:permissions:${accessToken}`;
58
+ const { accountId, validPermissions } = await getOrSetCache(
59
+ redisClient,
60
+ cacheKey,
61
+ fetchPermission,
62
+ 1800
63
+ );
23
64
 
24
- if (!validPermissions) {
25
- return res.status(403).json({ error: "Invalid permissions" });
26
- }
65
+ if (!validPermissions) {
66
+ return res.status(403).json({ error: 'Invalid permissions' });
67
+ }
27
68
 
28
- req.account = accountId;
29
- next();
30
- } catch (error) {
31
- if (error.response && error.response.status) {
32
- next(new Error(error.response.data.message));
33
- }
34
- next(new Error('Error validating token'));
35
- }
36
- };
69
+ req.account = accountId;
70
+
71
+ const user = await checkIfUserExists(userSchema, accountId);
72
+ if (!user) {
73
+ return res.status(403).json({ error: 'User not found' });
74
+ }
75
+ req.user = user.userId;
76
+ next();
77
+ } catch (error) {
78
+ if (error.response && error.response.status) {
79
+ next(new Error(error.response.data.message));
80
+ }
81
+ next(new Error('Error validating token'));
82
+ }
83
+ };
37
84
  };
38
85
 
39
- module.exports = authValidation;
86
+ module.exports = authValidation;
@@ -1,6 +1,6 @@
1
- require("dotenv").config();
2
- const axios = require("axios");
3
- const {getOrSetCache} = require("../utils/redis");
1
+ require('dotenv').config();
2
+ const axios = require('axios');
3
+ const { getOrSetCache } = require('../utils/redis');
4
4
  const { v4: uuidv4 } = require('uuid');
5
5
 
6
6
  /**
@@ -14,62 +14,70 @@ const { v4: uuidv4 } = require('uuid');
14
14
  * @throws {Error} - If there is an error retrieving the account profile data or validating the token
15
15
  */
16
16
  const getAccountProfile = async (redisClient, userSchema, accountId) => {
17
- try {
18
- const accessToken = req.cookies['x-access-token'] || req.headers.authorization?.split(" ")[1];
17
+ try {
18
+ const accessToken =
19
+ req.cookies['x-access-token'] || req.headers.authorization?.split(' ')[1];
19
20
 
20
- if (!accessToken) {
21
- throw new Error("Access token is required");
22
- }
23
-
24
- const fetchPermission = async () => {
25
- const response = await axios.get(`${process.env.AUTH_URL}/api/v1/account/profile`, {
26
- headers: {
27
- Authorization: `Bearer ${accessToken}`,
28
- },
29
- params: {
30
- accountId
31
- }
32
- });
33
- return response.data;
34
- }
35
- const cacheKey = `account:info:${accountId}`;
36
- const { profileData } = await getOrSetCache(redisClient, cacheKey, fetchPermission, 1800);
21
+ if (!accessToken) {
22
+ throw new Error('Access token is required');
23
+ }
37
24
 
38
- if (!profileData) {
39
- throw new Error("Invalid permissions");
40
- }
25
+ const fetchPermission = async () => {
26
+ const response = await axios.get(
27
+ `${process.env.AUTH_URL}/api/v1/account/profile`,
28
+ {
29
+ headers: {
30
+ Authorization: `Bearer ${accessToken}`,
31
+ },
32
+ params: {
33
+ accountId,
34
+ },
35
+ }
36
+ );
37
+ return response.data;
38
+ };
39
+ const cacheKey = `account:info:${accountId}`;
40
+ const { profileData } = await getOrSetCache(
41
+ redisClient,
42
+ cacheKey,
43
+ fetchPermission,
44
+ 1800
45
+ );
41
46
 
42
- return profileData;
47
+ if (!profileData) {
48
+ throw new Error('Invalid permissions');
49
+ }
43
50
 
44
- } catch (error) {
45
- if (error.response && error.response.status) {
46
- throw new Error(error.response.data.message);
47
- }
48
- throw new Error('Error validating token');
49
- }
51
+ return profileData;
52
+ } catch (error) {
53
+ if (error.response && error.response.status) {
54
+ throw new Error(error.response.data.message);
55
+ }
56
+ throw new Error('Error validating token');
57
+ }
50
58
  };
51
59
 
52
-
53
60
  /**
54
61
  * Checks if a user exists based on the given account ID.
55
62
  * If the user does not exist, creates a new user with the given account ID.
56
63
  *
57
64
  * @param {Schema} userSchema - The user schema to perform the operations on.
58
65
  * @param {string} accountId - The account ID of the user to check/create.
59
- * @returns {Promise<void>} - A promise that resolves once the check/create operation is done.
66
+ * @returns {Promise<user>} - A promise that resolves once the check/create operation is done.
60
67
  */
61
68
  const checkIfUserExists = async (userSchema, accountId) => {
62
- const user = await userSchema.findOne({ accountId });
63
- if (!user) {
64
- await userSchema.create({
65
- accountId,
66
- id: uuidv4(),
67
- verified: false,
68
- });
69
- }
69
+ const user = await userSchema.findOne({ accountId });
70
+ if (!user) {
71
+ await userSchema.create({
72
+ accountId,
73
+ id: uuidv4(),
74
+ verified: false,
75
+ });
76
+ }
77
+ return user;
70
78
  };
71
79
 
72
80
  module.exports = {
73
- getAccountProfile,
74
- checkIfUserExists
75
- }
81
+ getAccountProfile,
82
+ checkIfUserExists,
83
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "propro-utils",
3
- "version": "1.3.45",
3
+ "version": "1.3.47",
4
4
  "description": "Auth middleware for propro-auth",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -1,9 +1,7 @@
1
- require("dotenv").config();
2
- const {
3
- exchangeToken,
4
- } = require("./middleware/verifyToken");
5
- const {checkIfUserExists} = require("../../middlewares/account_info");
6
- const {post} = require("axios");
1
+ require('dotenv').config();
2
+ const { exchangeToken } = require('./middleware/verifyToken');
3
+ const { checkIfUserExists } = require('../../middlewares/account_info');
4
+ const { post } = require('axios');
7
5
 
8
6
  /**
9
7
  * Middleware for handling authentication and authorization.
@@ -20,120 +18,135 @@ const {post} = require("axios");
20
18
  * @returns {Function} - Express middleware function.
21
19
  */
22
20
  function proproAuthMiddleware(options = {}, userSchema) {
23
- const {
24
- secret = "RESTFULAPIs",
25
- authUrl = process.env.AUTH_URL,
26
- clientId = process.env.CLIENT_ID,
27
- clientSecret = process.env.CLIENT_SECRET,
28
- clientUrl = process.env.CLIENT_URL,
29
- redirectUri = process.env.REDIRECT_URI,
30
- appName = process.env.APP_NAME,
31
- } = options;
32
-
33
- return async (req, res, next) => {
34
- try {
35
- if (!["/api/auth", "/api/callback", "/api/refreshToken"].includes(req.path)) {
36
- return next();
37
- }
38
-
39
- if (req.path === "/api/auth") {
40
- const authClientUrl = `${clientUrl}/signin`;
41
- const redirectUrl = `${authClientUrl}?response_type=code&appName=${appName}&client_id=${clientId}&redirect_uri=${encodeURIComponent(
42
- redirectUri
43
- )}`;
44
- return res.status(200).json({redirectUrl});
45
- }
46
-
47
- if (req.path === "/api/refreshToken") {
48
- const refreshToken = req.cookies["x-refresh-token"];
49
- if (!refreshToken) {
50
- const authClientUrl = `${clientUrl}/signin`;
51
- const redirectUrl = `${authClientUrl}?response_type=code&appName=${appName}&client_id=${clientId}&redirect_uri=${encodeURIComponent(
52
- redirectUri
53
- )}`;
54
- return res.status(401).json({redirectUrl, error: "No refresh token provided"});
55
- }
56
-
57
- const response = await post(`${authUrl}/api/v1/auth/refreshTokens`, {
58
- refreshToken
59
- }, {
60
- params: {
61
- actionType: "refresh",
62
- }
63
- });
64
-
65
-
66
- const {account, access, refresh} = response.data;
67
-
68
-
69
- if (!account || !access || !refresh) {
70
- return res.status(401).json({error: "Invalid or expired refresh token"});
71
- }
72
-
73
- const currentDateTime = new Date();
74
-
75
- const refreshMaxAge = new Date(refresh.expires).getTime() - currentDateTime.getTime();
76
-
77
- res.cookie("x-refresh-token", refresh.token, {
78
- httpOnly: true,
79
- secure: process.env.NODE_ENV === "production",
80
- maxAge: refreshMaxAge
81
- });
82
-
83
- const accessMaxAge = new Date(access.expires).getTime() - currentDateTime.getTime();
84
-
85
- res.cookie("x-access-token", access.token, {
86
- httpOnly: true,
87
- secure: process.env.NODE_ENV === "production",
88
- maxAge: accessMaxAge
89
- });
90
-
91
- return res.status(200).json({message: "Token refreshed successfully"});
92
- }
93
-
94
- if (req.path === "/api/callback") {
95
- const code = req.query.code;
96
- if (!code) {
97
- return res.status(400).send("No code received");
98
- }
99
-
100
- const {tokens, account, redirectUrl} = await exchangeToken(
101
- authUrl,
102
- code,
103
- clientId,
104
- clientSecret,
105
- redirectUri
106
- );
107
-
108
- await checkIfUserExists(userSchema, account.accountId);
109
-
110
- const currentDateTime = new Date();
111
-
112
- const refreshMaxAge = new Date(tokens.refresh.expires).getTime() - currentDateTime.getTime();
113
-
114
- res.cookie("x-refresh-token", tokens.refresh.token, {
115
- httpOnly: true,
116
- secure: process.env.NODE_ENV === "production",
117
- maxAge: refreshMaxAge
118
- });
119
-
120
- const accessMaxAge = new Date(tokens.access.expires).getTime() - currentDateTime.getTime();
121
-
122
- res.cookie("x-access-token", tokens.access.token, {
123
- httpOnly: true,
124
- secure: process.env.NODE_ENV === "production",
125
- maxAge: accessMaxAge
126
- });
127
-
128
- const urlToRedirect = `http://${redirectUrl}/`;
129
-
130
- return res.redirect(urlToRedirect);
131
- }
132
- } catch (error) {
133
- // console.error("Error in proproAuthMiddleware:", error);
134
- res.status(401).send("Unauthorized: Invalid or expired token");
21
+ const {
22
+ secret = 'RESTFULAPIs',
23
+ authUrl = process.env.AUTH_URL,
24
+ clientId = process.env.CLIENT_ID,
25
+ clientSecret = process.env.CLIENT_SECRET,
26
+ clientUrl = process.env.CLIENT_URL,
27
+ redirectUri = process.env.REDIRECT_URI,
28
+ appName = process.env.APP_NAME,
29
+ } = options;
30
+
31
+ return async (req, res, next) => {
32
+ try {
33
+ if (
34
+ !['/api/auth', '/api/callback', '/api/refreshToken'].includes(req.path)
35
+ ) {
36
+ return next();
37
+ }
38
+
39
+ if (req.path === '/api/auth') {
40
+ const authClientUrl = `${clientUrl}/signin`;
41
+ const redirectUrl = `${authClientUrl}?response_type=code&appName=${appName}&client_id=${clientId}&redirect_uri=${encodeURIComponent(
42
+ redirectUri
43
+ )}`;
44
+ return res.status(200).json({ redirectUrl });
45
+ }
46
+
47
+ if (req.path === '/api/refreshToken') {
48
+ const refreshToken = req.cookies['x-refresh-token'];
49
+ if (!refreshToken) {
50
+ const authClientUrl = `${clientUrl}/signin`;
51
+ const redirectUrl = `${authClientUrl}?response_type=code&appName=${appName}&client_id=${clientId}&redirect_uri=${encodeURIComponent(
52
+ redirectUri
53
+ )}`;
54
+ return res
55
+ .status(401)
56
+ .json({ redirectUrl, error: 'No refresh token provided' });
135
57
  }
136
- };
58
+
59
+ const response = await post(
60
+ `${authUrl}/api/v1/auth/refreshTokens`,
61
+ {
62
+ refreshToken,
63
+ },
64
+ {
65
+ params: {
66
+ actionType: 'refresh',
67
+ },
68
+ }
69
+ );
70
+
71
+ const { account, access, refresh } = response.data;
72
+
73
+ if (!account || !access || !refresh) {
74
+ return res
75
+ .status(401)
76
+ .json({ error: 'Invalid or expired refresh token' });
77
+ }
78
+
79
+ const currentDateTime = new Date();
80
+
81
+ const refreshMaxAge =
82
+ new Date(refresh.expires).getTime() - currentDateTime.getTime();
83
+
84
+ res.cookie('x-refresh-token', refresh.token, {
85
+ httpOnly: true,
86
+ secure: process.env.NODE_ENV === 'production',
87
+ maxAge: refreshMaxAge,
88
+ });
89
+
90
+ const accessMaxAge =
91
+ new Date(access.expires).getTime() - currentDateTime.getTime();
92
+
93
+ res.cookie('x-access-token', access.token, {
94
+ httpOnly: true,
95
+ secure: process.env.NODE_ENV === 'production',
96
+ maxAge: accessMaxAge,
97
+ });
98
+
99
+ return res
100
+ .status(200)
101
+ .json({ message: 'Token refreshed successfully' });
102
+ }
103
+
104
+ if (req.path === '/api/callback') {
105
+ const code = req.query.code;
106
+ if (!code) {
107
+ return res.status(400).send('No code received');
108
+ }
109
+
110
+ const { tokens, account, redirectUrl } = await exchangeToken(
111
+ authUrl,
112
+ code,
113
+ clientId,
114
+ clientSecret,
115
+ redirectUri
116
+ );
117
+
118
+ await checkIfUserExists(userSchema, account.accountId);
119
+
120
+ const currentDateTime = new Date();
121
+
122
+ const refreshMaxAge =
123
+ new Date(tokens.refresh.expires).getTime() -
124
+ currentDateTime.getTime();
125
+
126
+ res.cookie('x-refresh-token', tokens.refresh.token, {
127
+ httpOnly: true,
128
+ secure: process.env.NODE_ENV === 'production',
129
+ maxAge: refreshMaxAge,
130
+ });
131
+
132
+ const accessMaxAge =
133
+ new Date(tokens.access.expires).getTime() - currentDateTime.getTime();
134
+
135
+ res.cookie('x-access-token', tokens.access.token, {
136
+ httpOnly: true,
137
+ secure: process.env.NODE_ENV === 'production',
138
+ maxAge: accessMaxAge,
139
+ });
140
+
141
+ const urlToRedirect = `http://${redirectUrl}/`;
142
+
143
+ return res.redirect(urlToRedirect);
144
+ }
145
+ } catch (error) {
146
+ // console.error("Error in proproAuthMiddleware:", error);
147
+ res.status(401).send('Unauthorized: Invalid or expired token');
148
+ }
149
+ };
137
150
  }
138
151
 
139
- module.exports = proproAuthMiddleware;
152
+ module.exports = proproAuthMiddleware;