propro-utils 1.4.72 → 1.4.74
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/README.md +61 -60
- package/package.json +1 -1
- package/src/client/index.js +42 -21
- package/src/index.js +65 -27
- package/src/server/index.js +215 -122
- package/src/server/index.test.js +288 -82
- package/src/server/middleware/{setAuthCookies.js → cookieUtils.js} +27 -2
- package/src/server/middleware/setAuthCookies.test.js +1 -1
package/README.md
CHANGED
|
@@ -1,88 +1,89 @@
|
|
|
1
1
|
# propro-utils
|
|
2
2
|
|
|
3
|
-
`propro-utils` is a comprehensive Node.js middleware designed for handling
|
|
3
|
+
`propro-utils` is a comprehensive Node.js middleware designed for handling authentication, authorization, and various utility functions for web applications. It provides a robust solution for both server-side and client-side authentication, profile management, and application settings.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **
|
|
7
|
+
- **Flexible Authentication**: Supports both server-side and client-side authentication.
|
|
8
|
+
- **Profile Management**: Includes routes for updating user profiles, passwords, emails, two-factor authentication, and avatars.
|
|
9
|
+
- **App Settings**: Provides a route for managing application-specific settings.
|
|
10
|
+
- **Token Refresh**: Handles token refreshing for maintaining user sessions.
|
|
11
|
+
- **Logout Functionality**: Implements secure user logout.
|
|
8
12
|
- **Configurable**: Offers a wide range of options for customizing the authentication process.
|
|
9
|
-
- **
|
|
10
|
-
- **Environment Validation**: Ensures critical environment variables are set.
|
|
11
|
-
- **Error Management**: Provides robust error handling during the authentication process.
|
|
13
|
+
- **Error Handling**: Provides robust error management during the authentication process.
|
|
12
14
|
|
|
13
15
|
## Installation
|
|
14
16
|
|
|
15
|
-
Install the middleware using
|
|
17
|
+
Install the middleware using `yarn`:
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
yarn add propro-utils
|
|
19
|
+
```bash
|
|
20
|
+
yarn add propro-utils
|
|
21
|
+
```
|
|
21
22
|
|
|
22
23
|
## Usage
|
|
23
24
|
|
|
24
|
-
After installing the middleware, you can import it in your
|
|
25
|
+
After installing the middleware, you can import and use it in your Express application:
|
|
25
26
|
|
|
26
27
|
```javascript
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Then, you can use the middleware in your server and client side code:
|
|
28
|
+
const express = require('express');
|
|
29
|
+
const AuthMiddleware = require('propro-utils');
|
|
30
|
+
const userSchema = require('./models/user');
|
|
31
31
|
|
|
32
|
-
```javascript
|
|
33
|
-
const express = require("express");
|
|
34
32
|
const app = express();
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// Add options
|
|
48
|
-
app.use(
|
|
49
|
-
proproAuthMiddleware({
|
|
50
|
-
useServerAuth: true,
|
|
51
|
-
serverOptions: {
|
|
52
|
-
validateUser: async (userId) => {
|
|
53
|
-
/* User validation logic */
|
|
54
|
-
},
|
|
55
|
-
secret = "RESTFULAPIs",
|
|
56
|
-
authUrl = process.env.AUTH_URL,
|
|
57
|
-
clientId = process.env.CLIENT_ID,
|
|
58
|
-
clientSecret = process.env.CLIENT_SECRET,
|
|
59
|
-
clientUrl = process.env.CLIENT_URL,
|
|
60
|
-
redirectUri = process.env.REDIRECT_URI,
|
|
61
|
-
onAuthFailRedirect: "/login",
|
|
62
|
-
additionalChecks: async (req) => {
|
|
63
|
-
/* Additional request checks */
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
useClientAuth: false,
|
|
67
|
-
clientOptions: {
|
|
68
|
-
// Client-side authentication options
|
|
69
|
-
},
|
|
70
|
-
})
|
|
33
|
+
|
|
34
|
+
const authMiddleware = new AuthMiddleware(
|
|
35
|
+
{
|
|
36
|
+
authUrl: process.env.AUTH_URL,
|
|
37
|
+
clientId: process.env.CLIENT_ID,
|
|
38
|
+
clientSecret: process.env.CLIENT_SECRET,
|
|
39
|
+
clientUrl: process.env.CLIENT_URL,
|
|
40
|
+
redirectUri: process.env.REDIRECT_URI,
|
|
41
|
+
appName: process.env.APP_NAME,
|
|
42
|
+
appUrl: process.env.APP_URL,
|
|
43
|
+
},
|
|
44
|
+
userSchema
|
|
71
45
|
);
|
|
46
|
+
|
|
47
|
+
app.use(authMiddleware.middleware());
|
|
48
|
+
|
|
49
|
+
// Your other routes and middleware
|
|
72
50
|
```
|
|
73
51
|
|
|
74
52
|
### Configuration Options
|
|
75
53
|
|
|
76
|
-
|
|
77
|
-
- serverOptions (object): Configuration options for server-side authentication.
|
|
78
|
-
- useClientAuth (boolean): Enable or disable client-side authentication.
|
|
79
|
-
- clientOptions (object): Configuration options for client-side authentication.
|
|
54
|
+
The `AuthMiddleware` constructor accepts an options object with the following properties:
|
|
80
55
|
|
|
81
|
-
|
|
56
|
+
- `secret`: The secret key used for authentication (default: 'RESTFULAPIs')
|
|
57
|
+
- `authUrl`: The authentication URL
|
|
58
|
+
- `clientId`: The client ID
|
|
59
|
+
- `clientSecret`: The client secret
|
|
60
|
+
- `clientUrl`: The client URL
|
|
61
|
+
- `redirectUri`: The redirect URI
|
|
62
|
+
- `appName`: The application name
|
|
63
|
+
- `appUrl`: The URL of the client application
|
|
82
64
|
|
|
83
|
-
|
|
65
|
+
## API Routes
|
|
66
|
+
|
|
67
|
+
The middleware sets up the following routes:
|
|
68
|
+
|
|
69
|
+
- `GET /api/auth`: Initiates the authentication process
|
|
70
|
+
- `GET /api/callback`: Handles the callback from the authentication server
|
|
71
|
+
- `POST /api/refreshToken`: Refreshes the authentication token
|
|
72
|
+
- `POST /api/logout`: Logs out the user
|
|
73
|
+
- `PATCH /api/profile`: Updates the user profile
|
|
74
|
+
- `PATCH /api/profile/password`: Updates the user's password
|
|
75
|
+
- `PATCH /api/profile/email`: Updates the user's email
|
|
76
|
+
- `PATCH /api/profile/2fa`: Manages two-factor authentication
|
|
77
|
+
- `PATCH /api/profile/avatar`: Updates the user's avatar
|
|
78
|
+
- `PATCH /api/app/settings`: Manages application settings
|
|
79
|
+
|
|
80
|
+
## Error Handling
|
|
81
|
+
|
|
82
|
+
The middleware includes comprehensive error handling for various scenarios, including missing tokens, failed requests, and server errors.
|
|
83
|
+
|
|
84
|
+
## Contributing
|
|
84
85
|
|
|
85
|
-
|
|
86
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
86
87
|
|
|
87
88
|
## License
|
|
88
89
|
|
package/package.json
CHANGED
package/src/client/index.js
CHANGED
|
@@ -1,24 +1,45 @@
|
|
|
1
|
-
|
|
1
|
+
// client/ClientAuth.js
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
axios.interceptors.request.use(function (config) {
|
|
5
|
-
const token = localStorage.getItem('token');
|
|
6
|
-
if (token) {
|
|
7
|
-
config.headers.Authorization = `Bearer ${token}`;
|
|
8
|
-
}
|
|
9
|
-
return config;
|
|
10
|
-
}, function (error) {
|
|
11
|
-
return Promise.reject(error);
|
|
12
|
-
});
|
|
3
|
+
const axios = require('axios');
|
|
13
4
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
localStorage.removeItem('token');
|
|
19
|
-
}
|
|
20
|
-
return Promise.reject(error);
|
|
21
|
-
});
|
|
22
|
-
};
|
|
5
|
+
class ClientAuth {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
}
|
|
23
9
|
|
|
24
|
-
|
|
10
|
+
middleware() {
|
|
11
|
+
return (req, res, next) => {
|
|
12
|
+
this.setupInterceptors();
|
|
13
|
+
next();
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
setupInterceptors() {
|
|
18
|
+
axios.interceptors.request.use(
|
|
19
|
+
function (config) {
|
|
20
|
+
const token = localStorage.getItem('token');
|
|
21
|
+
if (token) {
|
|
22
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
23
|
+
}
|
|
24
|
+
return config;
|
|
25
|
+
},
|
|
26
|
+
function (error) {
|
|
27
|
+
return Promise.reject(error);
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
axios.interceptors.response.use(
|
|
32
|
+
function (response) {
|
|
33
|
+
return response;
|
|
34
|
+
},
|
|
35
|
+
function (error) {
|
|
36
|
+
if (error.response && error.response.status === 401) {
|
|
37
|
+
localStorage.removeItem('token');
|
|
38
|
+
}
|
|
39
|
+
return Promise.reject(error);
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = ClientAuth;
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
const {
|
|
2
2
|
validateEnvironmentVariables,
|
|
3
3
|
} = require('./server/middleware/validateEnv');
|
|
4
|
+
const ServerAuth = require('./server/index');
|
|
5
|
+
const ClientAuth = require('./client/ClientAuth');
|
|
6
|
+
|
|
4
7
|
let _serverAuth, _clientAuth;
|
|
5
8
|
|
|
6
9
|
/**
|
|
@@ -43,31 +46,66 @@ let _serverAuth, _clientAuth;
|
|
|
43
46
|
* }, UserSchema));
|
|
44
47
|
* ```
|
|
45
48
|
*/
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
49
|
+
class ProProAuthMiddleware {
|
|
50
|
+
constructor(options = {}, userSchema) {
|
|
51
|
+
this.options = options;
|
|
52
|
+
this.userSchema = userSchema;
|
|
53
|
+
this.serverAuth = null;
|
|
54
|
+
this.clientAuth = null;
|
|
55
|
+
|
|
56
|
+
this.validateConfig();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
validateConfig() {
|
|
60
|
+
validateEnvironmentVariables([
|
|
61
|
+
'AUTH_URL',
|
|
62
|
+
'CLIENT_ID',
|
|
63
|
+
'CLIENT_SECRET',
|
|
64
|
+
'CLIENT_URL',
|
|
65
|
+
'REDIRECT_URI',
|
|
66
|
+
'APP_URL',
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
if (!this.options.useServerAuth && !this.options.useClientAuth) {
|
|
70
|
+
console.warn(
|
|
71
|
+
'Neither server-side nor client-side authentication is enabled.'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
initializeServerAuth() {
|
|
77
|
+
if (!this.serverAuth) {
|
|
78
|
+
this.serverAuth = new ServerAuth(
|
|
79
|
+
this.options.serverOptions,
|
|
80
|
+
this.userSchema
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return this.serverAuth.middleware();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
initializeClientAuth() {
|
|
87
|
+
if (!this.clientAuth) {
|
|
88
|
+
this.clientAuth = new ClientAuth(this.options.clientOptions);
|
|
71
89
|
}
|
|
72
|
-
|
|
73
|
-
}
|
|
90
|
+
return this.clientAuth.middleware();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
middleware() {
|
|
94
|
+
return (req, res, next) => {
|
|
95
|
+
try {
|
|
96
|
+
if (this.options.useServerAuth) {
|
|
97
|
+
return this.initializeServerAuth()(req, res, next);
|
|
98
|
+
} else if (this.options.useClientAuth) {
|
|
99
|
+
return this.initializeClientAuth()(req, res, next);
|
|
100
|
+
} else {
|
|
101
|
+
next();
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('Error in authentication middleware:', error);
|
|
105
|
+
next(error);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = ProProAuthMiddleware;
|
package/src/server/index.js
CHANGED
|
@@ -3,9 +3,9 @@ const {
|
|
|
3
3
|
exchangeToken,
|
|
4
4
|
formatRedirectUrl,
|
|
5
5
|
} = require('./middleware/verifyToken');
|
|
6
|
-
const { setAuthCookies } = require('./middleware/
|
|
6
|
+
const { setAuthCookies } = require('./middleware/cookieUtils');
|
|
7
7
|
const { checkIfUserExists } = require('../../middlewares/account_info');
|
|
8
|
-
const
|
|
8
|
+
const axios = require('axios');
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Middleware for handling authentication and authorization.
|
|
@@ -22,145 +22,238 @@ const { post } = require('axios');
|
|
|
22
22
|
* @param {Schema} [userSchema] - The user schema to perform the operations on.
|
|
23
23
|
* @returns {Function} - Express middleware function.
|
|
24
24
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
25
|
+
class AuthMiddleware {
|
|
26
|
+
constructor(options = {}, userSchema) {
|
|
27
|
+
this.options = {
|
|
28
|
+
secret: options.secret || 'RESTFULAPIs',
|
|
29
|
+
authUrl: options.authUrl || process.env.AUTH_URL,
|
|
30
|
+
clientId: options.clientId || process.env.CLIENT_ID,
|
|
31
|
+
clientSecret: options.clientSecret || process.env.CLIENT_SECRET,
|
|
32
|
+
clientUrl: options.clientUrl || process.env.CLIENT_URL,
|
|
33
|
+
redirectUri: options.redirectUri || process.env.REDIRECT_URI,
|
|
34
|
+
appName: options.appName || process.env.APP_NAME,
|
|
35
|
+
appUrl: options.appUrl || process.env.APP_URL,
|
|
36
|
+
};
|
|
37
|
+
this.userSchema = userSchema;
|
|
38
|
+
this.router = Router();
|
|
39
|
+
this.initializeRoutes();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
initializeRoutes() {
|
|
43
|
+
this.router.get('/api/auth', this.handleAuth);
|
|
44
|
+
this.router.get('/api/callback', this.handleCallback);
|
|
45
|
+
this.router.post('/api/refreshToken', this.handleRefreshToken);
|
|
46
|
+
this.router.post('/api/logout', this.handleLogout);
|
|
47
|
+
this.router.patch('/api/profile', this.handleProfileUpdate);
|
|
48
|
+
this.router.patch('/api/profile/password', this.handlePasswordUpdate);
|
|
49
|
+
this.router.patch('/api/profile/email', this.handleEmailUpdate);
|
|
50
|
+
this.router.patch('api/profile/2fa', this.handleTwoFactorAuth);
|
|
51
|
+
this.router.patch('/api/profile/avatar', this.handleAvatarUpdate);
|
|
52
|
+
this.router.patch('api/app/settings', this.handleAppSettings);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
handleAuth = (req, res) => {
|
|
56
|
+
const redirectUrl = this.constructRedirectUrl();
|
|
57
|
+
res.status(200).json({ redirectUrl });
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
handleCallback = async (req, res) => {
|
|
61
|
+
const { code } = req.query;
|
|
62
|
+
if (!code) {
|
|
63
|
+
return res.status(400).send('No code received');
|
|
64
|
+
}
|
|
65
|
+
|
|
40
66
|
try {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
67
|
+
const { tokens, account, redirectUrl } = await exchangeToken(
|
|
68
|
+
this.options.authUrl,
|
|
69
|
+
code,
|
|
70
|
+
this.options.clientId,
|
|
71
|
+
this.options.clientSecret,
|
|
72
|
+
this.options.redirectUri
|
|
73
|
+
);
|
|
46
74
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
clientUrl,
|
|
50
|
-
appName,
|
|
51
|
-
clientId,
|
|
52
|
-
redirectUri
|
|
53
|
-
);
|
|
54
|
-
return res.status(200).json({ redirectUrl });
|
|
55
|
-
}
|
|
75
|
+
const user = await checkIfUserExists(this.userSchema, account.accountId);
|
|
76
|
+
setAuthCookies(res, tokens, account, user, redirectUrl);
|
|
56
77
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
response = await post(
|
|
79
|
-
`${formatedAuthUrl}/api/v1/auth/refreshTokens`,
|
|
80
|
-
{
|
|
81
|
-
refreshToken,
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
params: {
|
|
85
|
-
actionType: 'refresh',
|
|
86
|
-
},
|
|
87
|
-
}
|
|
88
|
-
);
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.error('an error occur: ', error.message);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
let { account, access, refresh } = response.data;
|
|
94
|
-
|
|
95
|
-
if (!account || !access || !refresh) {
|
|
96
|
-
return res
|
|
97
|
-
.status(401)
|
|
98
|
-
.json({ error: 'Invalid or expired refresh token' });
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const user = await checkIfUserExists(userSchema, account.accountId);
|
|
102
|
-
|
|
103
|
-
setAuthCookies(res, { access, refresh }, account, user, appUrl);
|
|
78
|
+
res.redirect(formatRedirectUrl(redirectUrl));
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('Error in callback:', error);
|
|
81
|
+
res.status(500).send('Internal Server Error');
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
handleRefreshToken = async (req, res) => {
|
|
86
|
+
const refreshToken = req.cookies['x-refresh-token'];
|
|
87
|
+
if (!refreshToken) {
|
|
88
|
+
return res.status(401).json({
|
|
89
|
+
redirectUrl: this.constructRedirectUrl(),
|
|
90
|
+
error: 'No refresh token provided',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const response = await this.refreshTokens(refreshToken);
|
|
96
|
+
const { account, access, refresh } = response.data;
|
|
104
97
|
|
|
98
|
+
if (!account || !access || !refresh) {
|
|
105
99
|
return res
|
|
106
|
-
.status(
|
|
107
|
-
.json({
|
|
100
|
+
.status(401)
|
|
101
|
+
.json({ error: 'Invalid or expired refresh token' });
|
|
108
102
|
}
|
|
109
103
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
104
|
+
const user = await checkIfUserExists(this.userSchema, account.accountId);
|
|
105
|
+
setAuthCookies(
|
|
106
|
+
res,
|
|
107
|
+
{ access, refresh },
|
|
108
|
+
account,
|
|
109
|
+
user,
|
|
110
|
+
this.options.appUrl
|
|
111
|
+
);
|
|
115
112
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
);
|
|
113
|
+
res.status(200).json({ message: 'Token refreshed successfully' });
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error('Error refreshing token:', error);
|
|
116
|
+
res.status(401).json({ error: 'Failed to refresh token' });
|
|
117
|
+
}
|
|
118
|
+
};
|
|
123
119
|
|
|
124
|
-
|
|
120
|
+
handleLogout = async (req, res) => {
|
|
121
|
+
const refreshToken = req.cookies['x-refresh-token'];
|
|
122
|
+
if (!refreshToken) {
|
|
123
|
+
return res.status(401).json({ error: 'No refresh token provided' });
|
|
124
|
+
}
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
try {
|
|
127
|
+
await this.logoutUser(refreshToken);
|
|
128
|
+
clearAuthCookies(res);
|
|
129
|
+
res.status(200).json({ redirectUrl: this.constructRedirectUrl() });
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('Error logging out:', error);
|
|
132
|
+
res.status(500).json({ error: 'Failed to logout' });
|
|
133
|
+
}
|
|
134
|
+
};
|
|
127
135
|
|
|
128
|
-
|
|
129
|
-
|
|
136
|
+
refreshTokens = async refreshToken => {
|
|
137
|
+
const formattedAuthUrl = formatRedirectUrl(this.options.authUrl);
|
|
138
|
+
return axios.post(
|
|
139
|
+
`${formattedAuthUrl}/api/v1/auth/refreshTokens`,
|
|
140
|
+
{ refreshToken },
|
|
141
|
+
{ params: { actionType: 'refresh' } }
|
|
142
|
+
);
|
|
143
|
+
};
|
|
130
144
|
|
|
131
|
-
|
|
145
|
+
logoutUser = async refreshToken => {
|
|
146
|
+
const formattedAuthUrl = formatRedirectUrl(this.options.authUrl);
|
|
147
|
+
return axios.post(
|
|
148
|
+
`${formattedAuthUrl}/api/v1/auth/logout`,
|
|
149
|
+
{ refreshToken },
|
|
150
|
+
{ params: { actionType: 'refresh' } }
|
|
151
|
+
);
|
|
152
|
+
};
|
|
132
153
|
|
|
133
|
-
|
|
154
|
+
constructRedirectUrl() {
|
|
155
|
+
const urlToRedirect = formatRedirectUrl(this.options.clientUrl);
|
|
156
|
+
return `${urlToRedirect}/signin?response_type=code&appName=${
|
|
157
|
+
this.options.appName
|
|
158
|
+
}&client_id=${this.options.clientId}&redirect_uri=${encodeURIComponent(
|
|
159
|
+
this.options.redirectUri
|
|
160
|
+
)}`;
|
|
161
|
+
}
|
|
134
162
|
|
|
135
|
-
|
|
163
|
+
handleProfileUpdate = async (req, res) => {
|
|
164
|
+
try {
|
|
165
|
+
const response = await this.proxyToAuthServer(req, '/api/v1/profile');
|
|
166
|
+
res.status(response.status).json(response.data);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
this.handleProxyError(error, res);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
136
171
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
172
|
+
handlePasswordUpdate = async (req, res) => {
|
|
173
|
+
try {
|
|
174
|
+
const response = await this.proxyToAuthServer(
|
|
175
|
+
req,
|
|
176
|
+
'/api/v1/profile/password'
|
|
177
|
+
);
|
|
178
|
+
res.status(response.status).json(response.data);
|
|
142
179
|
} catch (error) {
|
|
143
|
-
|
|
144
|
-
res.status(401).send('Unauthorized: Invalid or expired token');
|
|
180
|
+
this.handleProxyError(error, res);
|
|
145
181
|
}
|
|
146
182
|
};
|
|
147
|
-
}
|
|
148
183
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
184
|
+
handleEmailUpdate = async (req, res) => {
|
|
185
|
+
try {
|
|
186
|
+
const response = await this.proxyToAuthServer(
|
|
187
|
+
req,
|
|
188
|
+
'/api/v1/profile/email'
|
|
189
|
+
);
|
|
190
|
+
res.status(response.status).json(response.data);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
this.handleProxyError(error, res);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
handleTwoFactorAuth = async (req, res) => {
|
|
197
|
+
try {
|
|
198
|
+
const response = await this.proxyToAuthServer(req, '/api/v1/profile/2fa');
|
|
199
|
+
res.status(response.status).json(response.data);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
this.handleProxyError(error, res);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
handleAvatarUpdate = async (req, res) => {
|
|
206
|
+
try {
|
|
207
|
+
const response = await this.proxyToAuthServer(
|
|
208
|
+
req,
|
|
209
|
+
'/api/v1/profile/avatar'
|
|
210
|
+
);
|
|
211
|
+
res.status(response.status).json(response.data);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
this.handleProxyError(error, res);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
proxyToAuthServer = async (req, path) => {
|
|
218
|
+
const accessToken = req.cookies['x-access-token'];
|
|
219
|
+
if (!accessToken) {
|
|
220
|
+
throw new Error('No access token provided');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const formattedAuthUrl = formatRedirectUrl(this.options.authUrl);
|
|
224
|
+
return axios({
|
|
225
|
+
method: req.method,
|
|
226
|
+
url: `${formattedAuthUrl}${path}`,
|
|
227
|
+
data: req.body,
|
|
228
|
+
headers: {
|
|
229
|
+
Authorization: `Bearer ${accessToken}`,
|
|
230
|
+
'Content-Type': 'application/json',
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
handleProxyError = (error, res) => {
|
|
236
|
+
console.error('Error proxying request to auth server:', error);
|
|
237
|
+
if (error.response) {
|
|
238
|
+
res.status(error.response.status).json(error.response.data);
|
|
239
|
+
} else {
|
|
240
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
handleAppSettings = async (req, res) => {
|
|
245
|
+
try {
|
|
246
|
+
const { settings } = req.body;
|
|
247
|
+
res.status(200).json({ message: 'App settings updated successfully' });
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error('Error updating app settings:', error);
|
|
250
|
+
res.status(500).json({ error: 'Failed to update app settings' });
|
|
251
|
+
}
|
|
252
|
+
};
|
|
160
253
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
254
|
+
middleware() {
|
|
255
|
+
return this.router;
|
|
256
|
+
}
|
|
164
257
|
}
|
|
165
258
|
|
|
166
|
-
module.exports =
|
|
259
|
+
module.exports = AuthMiddleware;
|
package/src/server/index.test.js
CHANGED
|
@@ -1,88 +1,294 @@
|
|
|
1
|
-
const
|
|
1
|
+
const request = require('supertest');
|
|
2
|
+
const express = require('express');
|
|
2
3
|
const axios = require('axios');
|
|
3
|
-
const
|
|
4
|
+
const AuthMiddleware = require('./AuthMiddleware');
|
|
5
|
+
const {
|
|
6
|
+
setAuthCookies,
|
|
7
|
+
clearAuthCookies,
|
|
8
|
+
} = require('./middleware/cookieUtils');
|
|
9
|
+
const {
|
|
10
|
+
exchangeToken,
|
|
11
|
+
formatRedirectUrl,
|
|
12
|
+
} = require('./middleware/verifyToken');
|
|
13
|
+
const { checkIfUserExists } = require('../../middlewares/account_info');
|
|
4
14
|
|
|
5
15
|
jest.mock('axios');
|
|
6
|
-
jest.mock('
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
jest.mock('./middleware/cookieUtils');
|
|
17
|
+
jest.mock('./middleware/verifyToken');
|
|
18
|
+
jest.mock('../../middlewares/account_info');
|
|
19
|
+
|
|
20
|
+
describe('AuthMiddleware', () => {
|
|
21
|
+
let app;
|
|
22
|
+
let authMiddleware;
|
|
23
|
+
let mockUserSchema;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
app = express();
|
|
27
|
+
mockUserSchema = {};
|
|
28
|
+
authMiddleware = new AuthMiddleware(
|
|
29
|
+
{
|
|
30
|
+
authUrl: 'http://auth.example.com',
|
|
31
|
+
clientId: 'test-client-id',
|
|
32
|
+
clientSecret: 'test-client-secret',
|
|
33
|
+
clientUrl: 'http://client.example.com',
|
|
34
|
+
redirectUri: 'http://client.example.com/callback',
|
|
35
|
+
appName: 'TestApp',
|
|
36
|
+
appUrl: 'http://app.example.com',
|
|
37
|
+
},
|
|
38
|
+
mockUserSchema
|
|
29
39
|
);
|
|
40
|
+
app.use(express.json());
|
|
41
|
+
app.use(authMiddleware.middleware());
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
jest.clearAllMocks();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('handleAuth', () => {
|
|
49
|
+
it('should return a redirect URL', async () => {
|
|
50
|
+
const response = await request(app).get('/api/auth');
|
|
51
|
+
expect(response.status).toBe(200);
|
|
52
|
+
expect(response.body).toHaveProperty('redirectUrl');
|
|
53
|
+
expect(response.body.redirectUrl).toContain(
|
|
54
|
+
'http://client.example.com/signin'
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('handleCallback', () => {
|
|
60
|
+
it('should handle successful token exchange', async () => {
|
|
61
|
+
const mockTokens = {
|
|
62
|
+
access: { token: 'access-token' },
|
|
63
|
+
refresh: { token: 'refresh-token' },
|
|
64
|
+
};
|
|
65
|
+
const mockAccount = { accountId: '123' };
|
|
66
|
+
const mockUser = { id: '456' };
|
|
67
|
+
|
|
68
|
+
exchangeToken.mockResolvedValue({
|
|
69
|
+
tokens: mockTokens,
|
|
70
|
+
account: mockAccount,
|
|
71
|
+
redirectUrl: 'http://app.example.com',
|
|
72
|
+
});
|
|
73
|
+
checkIfUserExists.mockResolvedValue(mockUser);
|
|
74
|
+
|
|
75
|
+
const response = await request(app).get('/api/callback?code=test-code');
|
|
76
|
+
|
|
77
|
+
expect(response.status).toBe(302); // Expecting a redirect
|
|
78
|
+
expect(response.header.location).toBe('http://app.example.com');
|
|
79
|
+
expect(setAuthCookies).toHaveBeenCalledWith(
|
|
80
|
+
expect.anything(),
|
|
81
|
+
mockTokens,
|
|
82
|
+
mockAccount,
|
|
83
|
+
mockUser,
|
|
84
|
+
'http://app.example.com'
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should handle missing code', async () => {
|
|
89
|
+
const response = await request(app).get('/api/callback');
|
|
90
|
+
expect(response.status).toBe(400);
|
|
91
|
+
expect(response.text).toBe('No code received');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should handle token exchange error', async () => {
|
|
95
|
+
exchangeToken.mockRejectedValue(new Error('Exchange failed'));
|
|
96
|
+
|
|
97
|
+
const response = await request(app).get('/api/callback?code=test-code');
|
|
98
|
+
|
|
99
|
+
expect(response.status).toBe(500);
|
|
100
|
+
expect(response.text).toBe('Internal Server Error');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('handleRefreshToken', () => {
|
|
105
|
+
it('should handle successful token refresh', async () => {
|
|
106
|
+
const mockRefreshToken = 'refresh-token';
|
|
107
|
+
const mockResponse = {
|
|
108
|
+
data: {
|
|
109
|
+
account: { accountId: '123' },
|
|
110
|
+
access: { token: 'new-access-token' },
|
|
111
|
+
refresh: { token: 'new-refresh-token' },
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
axios.post.mockResolvedValue(mockResponse);
|
|
116
|
+
checkIfUserExists.mockResolvedValue({ id: '456' });
|
|
117
|
+
|
|
118
|
+
const response = await request(app)
|
|
119
|
+
.post('/api/refreshToken')
|
|
120
|
+
.set('Cookie', [`x-refresh-token=${mockRefreshToken}`]);
|
|
121
|
+
|
|
122
|
+
expect(response.status).toBe(200);
|
|
123
|
+
expect(response.body).toEqual({
|
|
124
|
+
message: 'Token refreshed successfully',
|
|
125
|
+
});
|
|
126
|
+
expect(setAuthCookies).toHaveBeenCalled();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should handle missing refresh token', async () => {
|
|
130
|
+
const response = await request(app).post('/api/refreshToken');
|
|
131
|
+
|
|
132
|
+
expect(response.status).toBe(401);
|
|
133
|
+
expect(response.body).toHaveProperty(
|
|
134
|
+
'error',
|
|
135
|
+
'No refresh token provided'
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should handle invalid refresh token', async () => {
|
|
140
|
+
axios.post.mockResolvedValue({ data: {} });
|
|
141
|
+
|
|
142
|
+
const response = await request(app)
|
|
143
|
+
.post('/api/refreshToken')
|
|
144
|
+
.set('Cookie', ['x-refresh-token=invalid-token']);
|
|
145
|
+
|
|
146
|
+
expect(response.status).toBe(401);
|
|
147
|
+
expect(response.body).toEqual({
|
|
148
|
+
error: 'Invalid or expired refresh token',
|
|
149
|
+
});
|
|
150
|
+
});
|
|
30
151
|
});
|
|
31
|
-
});
|
|
32
152
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
153
|
+
describe('handleLogout', () => {
|
|
154
|
+
it('should handle successful logout', async () => {
|
|
155
|
+
const mockRefreshToken = 'refresh-token';
|
|
156
|
+
axios.post.mockResolvedValue({});
|
|
157
|
+
|
|
158
|
+
const response = await request(app)
|
|
159
|
+
.post('/api/logout')
|
|
160
|
+
.set('Cookie', [`x-refresh-token=${mockRefreshToken}`]);
|
|
161
|
+
|
|
162
|
+
expect(response.status).toBe(200);
|
|
163
|
+
expect(response.body).toHaveProperty('redirectUrl');
|
|
164
|
+
expect(clearAuthCookies).toHaveBeenCalled();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should handle missing refresh token', async () => {
|
|
168
|
+
const response = await request(app).post('/api/logout');
|
|
169
|
+
|
|
170
|
+
expect(response.status).toBe(401);
|
|
171
|
+
expect(response.body).toEqual({ error: 'No refresh token provided' });
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should handle logout error', async () => {
|
|
175
|
+
axios.post.mockRejectedValue(new Error('Logout failed'));
|
|
176
|
+
|
|
177
|
+
const response = await request(app)
|
|
178
|
+
.post('/api/logout')
|
|
179
|
+
.set('Cookie', ['x-refresh-token=test-token']);
|
|
180
|
+
|
|
181
|
+
expect(response.status).toBe(500);
|
|
182
|
+
expect(response.body).toEqual({ error: 'Failed to logout' });
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe('Profile Update Handlers', () => {
|
|
187
|
+
const testCases = [
|
|
188
|
+
{
|
|
189
|
+
method: 'handleProfileUpdate',
|
|
190
|
+
path: '/api/profile',
|
|
191
|
+
authPath: '/api/v1/profile',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
method: 'handlePasswordUpdate',
|
|
195
|
+
path: '/api/profile/password',
|
|
196
|
+
authPath: '/api/v1/profile/password',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
method: 'handleEmailUpdate',
|
|
200
|
+
path: '/api/profile/email',
|
|
201
|
+
authPath: '/api/v1/profile/email',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
method: 'handleTwoFactorAuth',
|
|
205
|
+
path: '/api/profile/2fa',
|
|
206
|
+
authPath: '/api/v1/profile/2fa',
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
method: 'handleAvatarUpdate',
|
|
210
|
+
path: '/api/profile/avatar',
|
|
211
|
+
authPath: '/api/v1/profile/avatar',
|
|
212
|
+
},
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
testCases.forEach(({ method, path, authPath }) => {
|
|
216
|
+
describe(method, () => {
|
|
217
|
+
it('should successfully proxy the request', async () => {
|
|
218
|
+
const mockResponse = {
|
|
219
|
+
status: 200,
|
|
220
|
+
data: { message: 'Update successful' },
|
|
221
|
+
};
|
|
222
|
+
axios.mockResolvedValue(mockResponse);
|
|
223
|
+
|
|
224
|
+
const response = await request(app)
|
|
225
|
+
.patch(path)
|
|
226
|
+
.set('Cookie', ['x-access-token=test-token'])
|
|
227
|
+
.send({ someData: 'testData' });
|
|
228
|
+
|
|
229
|
+
expect(response.status).toBe(200);
|
|
230
|
+
expect(response.body).toEqual({ message: 'Update successful' });
|
|
231
|
+
expect(axios).toHaveBeenCalledWith(
|
|
232
|
+
expect.objectContaining({
|
|
233
|
+
method: 'PATCH',
|
|
234
|
+
url: `http://auth.example.com${authPath}`,
|
|
235
|
+
data: { someData: 'testData' },
|
|
236
|
+
headers: expect.objectContaining({
|
|
237
|
+
Authorization: 'Bearer test-token',
|
|
238
|
+
'Content-Type': 'application/json',
|
|
239
|
+
}),
|
|
240
|
+
})
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should handle missing access token', async () => {
|
|
245
|
+
const response = await request(app).patch(path);
|
|
246
|
+
|
|
247
|
+
expect(response.status).toBe(500);
|
|
248
|
+
expect(response.body).toEqual({ error: 'Internal Server Error' });
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should handle proxy error', async () => {
|
|
252
|
+
axios.mockRejectedValue({
|
|
253
|
+
response: { status: 400, data: { error: 'Bad Request' } },
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const response = await request(app)
|
|
257
|
+
.patch(path)
|
|
258
|
+
.set('Cookie', ['x-access-token=test-token']);
|
|
259
|
+
|
|
260
|
+
expect(response.status).toBe(400);
|
|
261
|
+
expect(response.body).toEqual({ error: 'Bad Request' });
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('handleAppSettings', () => {
|
|
268
|
+
it('should successfully update app settings', async () => {
|
|
269
|
+
const response = await request(app)
|
|
270
|
+
.patch('/api/app/settings')
|
|
271
|
+
.send({ settings: { theme: 'dark' } });
|
|
272
|
+
|
|
273
|
+
expect(response.status).toBe(200);
|
|
274
|
+
expect(response.body).toEqual({
|
|
275
|
+
message: 'App settings updated successfully',
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should handle update error', async () => {
|
|
280
|
+
authMiddleware.handleAppSettings = jest
|
|
281
|
+
.fn()
|
|
282
|
+
.mockImplementation((req, res) => {
|
|
283
|
+
throw new Error('Update failed');
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const response = await request(app)
|
|
287
|
+
.patch('/api/app/settings')
|
|
288
|
+
.send({ settings: { theme: 'light' } });
|
|
289
|
+
|
|
290
|
+
expect(response.status).toBe(500);
|
|
291
|
+
expect(response.body).toEqual({ error: 'Failed to update app settings' });
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
});
|
|
@@ -15,13 +15,17 @@ const setAuthCookies = (res, tokens, account, user, appUrl) => {
|
|
|
15
15
|
const accessMaxAge =
|
|
16
16
|
new Date(tokens.access.expires).getTime() - currentDateTime.getTime();
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
let domain = appUrl ? new URL(appUrl).hostname : undefined;
|
|
19
|
+
|
|
20
|
+
if (domain.includes('mapmap.app')) {
|
|
21
|
+
domain = '.mapmap.app';
|
|
22
|
+
}
|
|
19
23
|
|
|
20
24
|
console.log('domain: ', domain);
|
|
21
25
|
|
|
22
26
|
const commonAttributes = {
|
|
23
27
|
// secure: process.env.NODE_ENV === 'production',
|
|
24
|
-
secure:
|
|
28
|
+
secure: true,
|
|
25
29
|
sameSite: 'None',
|
|
26
30
|
// path: '/',
|
|
27
31
|
domain: domain,
|
|
@@ -59,6 +63,27 @@ const setAuthCookies = (res, tokens, account, user, appUrl) => {
|
|
|
59
63
|
);
|
|
60
64
|
};
|
|
61
65
|
|
|
66
|
+
const clearAuthCookies = res => {
|
|
67
|
+
const commonAttributes = {
|
|
68
|
+
secure: true,
|
|
69
|
+
sameSite: 'None',
|
|
70
|
+
domain: process.env.APP_URL
|
|
71
|
+
? new URL(process.env.APP_URL).hostname
|
|
72
|
+
: undefined,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
[
|
|
76
|
+
'x-refresh-token',
|
|
77
|
+
'x-access-token',
|
|
78
|
+
'user',
|
|
79
|
+
'account',
|
|
80
|
+
'has_account_token',
|
|
81
|
+
].forEach(cookieName => {
|
|
82
|
+
res.clearCookie(cookieName, commonAttributes);
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
62
86
|
module.exports = {
|
|
63
87
|
setAuthCookies,
|
|
88
|
+
clearAuthCookies,
|
|
64
89
|
};
|