diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js
index 136a05a7a..0f386bc4b 100644
--- a/Server/controllers/authController.js
+++ b/Server/controllers/authController.js
@@ -12,7 +12,7 @@ require("dotenv").config();
const { errorMessages, successMessages } = require("../utils/messages");
const jwt = require("jsonwebtoken");
const SERVICE_NAME = "AuthController";
-const { getTokenFromHeaders } = require("../utils/utils");
+const { getTokenFromHeaders, tokenType } = require("../utils/utils");
const crypto = require("crypto");
const { handleValidationError, handleError } = require("./controllerUtils");
@@ -20,14 +20,18 @@ const { handleValidationError, handleError } = require("./controllerUtils");
* Creates and returns JWT token with an arbitrary payload
* @function
* @param {Object} payload
+ * @param {tokenType} typeOfToken - Whether to generate refresh token with long TTL or access token with short TTL.
* @param {Object} appSettings
* @returns {String}
* @throws {Error}
*/
-const issueToken = (payload, appSettings) => {
+const issueToken = (payload, typeOfToken, appSettings) => {
try {
- const tokenTTL = appSettings.jwtTTL ? appSettings.jwtTTL : "2h";
- return jwt.sign(payload, appSettings.jwtSecret, { expiresIn: tokenTTL });
+ const tokenTTL = (typeOfToken === tokenType.REFRESH_TOKEN) ? (appSettings?.refreshTokenTTL?? "7d") : (appSettings?.jwtTTL?? "2h");
+ const tokenSecret = (typeOfToken === tokenType.REFRESH_TOKEN) ? appSettings?.refreshTokenSecret : appSettings?.jwtSecret;
+ const payloadData = (typeOfToken === tokenType.REFRESH_TOKEN) ? {} : payload;
+
+ return jwt.sign(payloadData, tokenSecret, { expiresIn: tokenTTL });
} catch (error) {
throw handleError(error, SERVICE_NAME, "issueToken");
}
@@ -80,7 +84,8 @@ const registerUser = async (req, res, next) => {
const appSettings = await req.settingsService.getSettings();
- const token = issueToken(userForToken, appSettings);
+ const token = issueToken(userForToken, tokenType.ACCESS_TOKEN, appSettings);
+ const refreshToken = issueToken({}, tokenType.REFRESH_TOKEN, appSettings);
req.emailService
.buildAndSendEmail(
@@ -99,7 +104,7 @@ const registerUser = async (req, res, next) => {
return res.status(200).json({
success: true,
msg: successMessages.AUTH_CREATE_USER,
- data: { user: newUser, token: token },
+ data: { user: newUser, token: token, refreshToken: refreshToken },
});
} catch (error) {
console.log("ERROR", error);
@@ -146,14 +151,15 @@ const loginUser = async (req, res, next) => {
// Happy path, return token
const appSettings = await req.settingsService.getSettings();
- const token = issueToken(userWithoutPassword, appSettings);
+ const token = issueToken(userWithoutPassword, tokenType.ACCESS_TOKEN, appSettings);
+ const refreshToken = issueToken({}, tokenType.REFRESH_TOKEN, appSettings);
// reset avatar image
userWithoutPassword.avatarImage = user.avatarImage;
return res.status(200).json({
success: true,
msg: successMessages.AUTH_LOGIN_USER,
- data: { user: userWithoutPassword, token: token },
+ data: { user: userWithoutPassword, token: token, refreshToken: refreshToken },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "loginController"));
@@ -360,7 +366,7 @@ const resetPassword = async (req, res, next) => {
const user = await req.db.resetPassword(req, res);
const appSettings = await req.settingsService.getSettings();
- const token = issueToken(user._doc, appSettings);
+ const token = issueToken(user._doc, tokenType.ACCESS_TOKEN, appSettings);
res.status(200).json({
success: true,
msg: successMessages.AUTH_RESET_PASSWORD,
diff --git a/Server/db/models/AppSettings.js b/Server/db/models/AppSettings.js
index f95d58f4b..bbd863af8 100644
--- a/Server/db/models/AppSettings.js
+++ b/Server/db/models/AppSettings.js
@@ -22,6 +22,11 @@ const AppSettingsSchema = mongoose.Schema(
required: true,
default: "my_secret",
},
+ refreshTokenSecret: {
+ type: String,
+ required: true,
+ default: "my_refresh_secret",
+ },
dbType: {
type: String,
required: true,
@@ -44,7 +49,12 @@ const AppSettingsSchema = mongoose.Schema(
jwtTTL: {
type: String,
required: true,
- default: "99d",
+ default: "2h",
+ },
+ refreshTokenTTL: {
+ type: String,
+ required: true,
+ default: "7d",
},
pagespeedApiKey: {
type: String,
diff --git a/Server/service/settingsService.js b/Server/service/settingsService.js
index 24e890d64..764c24db3 100644
--- a/Server/service/settingsService.js
+++ b/Server/service/settingsService.js
@@ -6,11 +6,13 @@ const envConfig = {
apiBaseUrl: undefined,
clientHost: process.env.CLIENT_HOST,
jwtSecret: process.env.JWT_SECRET,
+ refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET,
dbType: process.env.DB_TYPE,
dbConnectionString: process.env.DB_CONNECTION_STRING,
redisHost: process.env.REDIS_HOST,
redisPort: process.env.REDIS_PORT,
jwtTTL: process.env.TOKEN_TTL,
+ refreshTokenTTL: process.env.REFRESH_TOKEN_TTL,
pagespeedApiKey: process.env.PAGESPEED_API_KEY,
systemEmailHost: process.env.SYSTEM_EMAIL_HOST,
systemEmailPort: process.env.SYSTEM_EMAIL_PORT,
diff --git a/Server/tests/controllers/authController.test.js b/Server/tests/controllers/authController.test.js
index 842959415..c33d13fcd 100644
--- a/Server/tests/controllers/authController.test.js
+++ b/Server/tests/controllers/authController.test.js
@@ -14,29 +14,61 @@ const jwt = require("jsonwebtoken");
const { errorMessages, successMessages } = require("../../utils/messages");
const sinon = require("sinon");
const logger = require("../../utils/logger");
+const { tokenType } = require("../../utils/utils");
describe("Auth Controller - issueToken", () => {
+ let stub;
+
+ afterEach(() => {
+ sinon.restore(); // Restore stubs after each test
+ });
+
it("should reject with an error if jwt.sign fails", () => {
const error = new Error("jwt.sign error");
stub = sinon.stub(jwt, "sign").throws(error);
const payload = { id: "123" };
const appSettings = { jwtSecret: "my_secret" };
- expect(() => issueToken(payload, appSettings)).to.throw(error);
- stub.restore();
+ expect(() => issueToken(payload, tokenType.ACCESS_TOKEN, appSettings)).to.throw(error);
});
- it("should return a token if jwt.sign is successful and appSettings.jtwTTL is not defined", () => {
+ it("should return a token if jwt.sign is successful and appSettings.jwtTTL is not defined", () => {
const payload = { id: "123" };
const appSettings = { jwtSecret: "my_secret" };
- const token = issueToken(payload, appSettings);
- expect(token).to.be.a("string");
+ const expectedToken = "mockToken";
+
+ stub = sinon.stub(jwt, "sign").returns(expectedToken);
+ const token = issueToken(payload, tokenType.ACCESS_TOKEN, appSettings);
+ expect(token).to.equal(expectedToken);
});
it("should return a token if jwt.sign is successful and appSettings.jwtTTL is defined", () => {
const payload = { id: "123" };
const appSettings = { jwtSecret: "my_secret", jwtTTL: "1s" };
- const token = issueToken(payload, appSettings);
- expect(token).to.be.a("string");
+ const expectedToken = "mockToken";
+
+ stub = sinon.stub(jwt, "sign").returns(expectedToken);
+ const token = issueToken(payload, tokenType.ACCESS_TOKEN, appSettings);
+ expect(token).to.equal(expectedToken);
+ });
+
+ it("should return a refresh token if jwt.sign is successful and appSettings.refreshTokenTTL is not defined", () => {
+ const payload = {};
+ const appSettings = { refreshTokenSecret: "my_refresh_secret" };
+ const expectedToken = "mockRefreshToken";
+
+ stub = sinon.stub(jwt, "sign").returns(expectedToken);
+ const token = issueToken(payload, tokenType.REFRESH_TOKEN, appSettings);
+ expect(token).to.equal(expectedToken);
+ });
+
+ it("should return a refresh token if jwt.sign is successful and appSettings.refreshTokenTTL is defined", () => {
+ const payload = {};
+ const appSettings = { refreshTokenSecret: "my_refresh_secret", refreshTokenTTL: "7d" };
+ const expectedToken = "mockRefreshToken";
+
+ stub = sinon.stub(jwt, "sign").returns(expectedToken);
+ const token = issueToken(payload, tokenType.REFRESH_TOKEN, appSettings);
+ expect(token).to.equal(expectedToken);
});
});
@@ -61,6 +93,7 @@ describe("Auth Controller - registerUser", () => {
settingsService: {
getSettings: sinon.stub().resolves({
jwtSecret: "my_secret",
+ refreshTokenSecret: "my_refresh_secret",
}),
},
emailService: {
@@ -140,7 +173,6 @@ describe("Auth Controller - registerUser", () => {
req.db.checkSuperadmin.resolves(false);
req.db.updateAppSettings.resolves();
req.db.insertUser.returns({ _id: "123" });
- req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
req.emailService.buildAndSendEmail.rejects(new Error("emailService error"));
await registerUser(req, res, next);
expect(logger.error.calledOnce).to.be.true;
@@ -151,7 +183,6 @@ describe("Auth Controller - registerUser", () => {
req.db.checkSuperadmin.resolves(false);
req.db.updateAppSettings.resolves();
req.db.insertUser.returns(user);
- req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
req.emailService.buildAndSendEmail.resolves("message-id");
await registerUser(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
@@ -159,7 +190,7 @@ describe("Auth Controller - registerUser", () => {
res.json.calledWith({
success: true,
msg: successMessages.AUTH_CREATE_USER,
- data: { user, token: sinon.match.string },
+ data: { user, token: sinon.match.string, refreshToken: sinon.match.string },
})
).to.be.true;
expect(next.notCalled).to.be.true;
@@ -176,6 +207,7 @@ describe("Auth Controller - loginUser", () => {
settingsService: {
getSettings: sinon.stub().resolves({
jwtSecret: "my_secret",
+ refreshTokenSecret: "my_refresh_token",
}),
},
};
@@ -220,6 +252,7 @@ describe("Auth Controller - loginUser", () => {
avatarImage: undefined,
},
token: sinon.match.string,
+ refreshToken: sinon.match.string,
},
})
).to.be.true;
diff --git a/Server/utils/utils.js b/Server/utils/utils.js
index 4331a4610..f839eb464 100644
--- a/Server/utils/utils.js
+++ b/Server/utils/utils.js
@@ -27,7 +27,13 @@ const getTokenFromHeaders = (headers) => {
return parts[1];
};
+const tokenType = Object.freeze({
+ ACCESS_TOKEN: "Access token",
+ REFRESH_TOKEN: "Refresh token"
+});
+
module.exports = {
ParseBoolean,
getTokenFromHeaders,
+ tokenType,
};
diff --git a/guides/users-guide/quickstart.md b/guides/users-guide/quickstart.md
index 6d2ec2242..6bc6975c4 100644
--- a/guides/users-guide/quickstart.md
+++ b/guides/users-guide/quickstart.md
@@ -79,7 +79,7 @@ Make sure you change the directory to the specified directories, as paths in com
Configure the server with the following environmental variables:
-
| ENV Variable Name | Required/Optional | Type | Description | Accepted Values |
|---|
| CLIENT_HOST | Required | string | Frontend Host | |
| JWT_SECRET | Required | string | JWT secret | |
| DB_TYPE | Optional | string | Specify DB to use | MongoDB | FakeDB |
| DB_CONNECTION_STRING | Required | string | Specifies URL for MongoDB Database | |
| PORT | Optional | integer | Specifies Port for Server | |
| LOGIN_PAGE_URL | Required | string | Login url to be used in emailing service | |
| REDIS_HOST | Required | string | Host address for Redis database | |
| REDIS_PORT | Required | integer | Port for Redis database | |
| TOKEN_TTL | Optional | string | Time for token to live | In vercel/ms format https://github.com/vercel/ms |
| PAGESPEED_API_KEY | Optional | string | API Key for PageSpeed requests | |
| SYSTEM_EMAIL_HOST | Required | string | Host to send System Emails From | |
| SYSTEM_EMAIL_PORT | Required | number | Port for System Email Host | |
| SYSTEM_EMAIL_ADDRESS | Required | string | System Email Address | |
| SYSTEM_EMAIL_PASSWORD | Required | string | System Email Password | |
+| ENV Variable Name | Required/Optional | Type | Description | Accepted Values |
|---|
| CLIENT_HOST | Required | string | Frontend Host | |
| JWT_SECRET | Required | string | JWT secret | |
| REFRESH_TOKEN_SECRET | Required | string | Refresh JWT secret | |
| DB_TYPE | Optional | string | Specify DB to use | MongoDB | FakeDB |
| DB_CONNECTION_STRING | Required | string | Specifies URL for MongoDB Database | |
| PORT | Optional | integer | Specifies Port for Server | |
| LOGIN_PAGE_URL | Required | string | Login url to be used in emailing service | |
| REDIS_HOST | Required | string | Host address for Redis database | |
| REDIS_PORT | Required | integer | Port for Redis database | |
| TOKEN_TTL | Optional | string | Time for token to live | In vercel/ms format https://github.com/vercel/ms |
| REFRESH_TOKEN_TTL | Optional | string | Time for refresh token to live | |
| PAGESPEED_API_KEY | Optional | string | API Key for PageSpeed requests | |
| SYSTEM_EMAIL_HOST | Required | string | Host to send System Emails From | |
| SYSTEM_EMAIL_PORT | Required | number | Port for System Email Host | |
| SYSTEM_EMAIL_ADDRESS | Required | string | System Email Address | |
| SYSTEM_EMAIL_PASSWORD | Required | string | System Email Password | |
---