Merge pull request #960 from Rushi1109/915-be-generate-refresh-token

Generate refresh token & add it to response
This commit is contained in:
Alexander Holliday authored and GitHub committed 2024-10-16 19:40:59 +08:00
commit 7d8ffcd4a5
6 files changed
+78 -21

No files matched your search

+15 -9
View File
@@ -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,
+11 -1
View File
@@ -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,
+2
View File
@@ -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,
+43 -10
View File
@@ -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;
+6
View File
@@ -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,
};
+1 -1
View File
@@ -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:
<table><thead><tr><th width="239">ENV Variable Name</th><th width="149">Required/Optional</th><th width="116">Type</th><th>Description</th><th>Accepted Values</th></tr></thead><tbody><tr><td>CLIENT_HOST</td><td>Required</td><td><code>string</code></td><td>Frontend Host</td><td></td></tr><tr><td>JWT_SECRET</td><td>Required</td><td><code>string</code></td><td>JWT secret</td><td></td></tr><tr><td>DB_TYPE</td><td>Optional</td><td><code>string</code></td><td>Specify DB to use</td><td><code>MongoDB | FakeDB</code></td></tr><tr><td>DB_CONNECTION_STRING</td><td>Required</td><td><code>string</code></td><td>Specifies URL for MongoDB Database</td><td></td></tr><tr><td>PORT</td><td>Optional</td><td><code>integer</code></td><td>Specifies Port for Server</td><td></td></tr><tr><td>LOGIN_PAGE_URL</td><td>Required</td><td><code>string</code></td><td>Login url to be used in emailing service</td><td></td></tr><tr><td>REDIS_HOST</td><td>Required</td><td><code>string</code></td><td>Host address for Redis database</td><td></td></tr><tr><td>REDIS_PORT</td><td>Required</td><td><code>integer</code></td><td>Port for Redis database</td><td></td></tr><tr><td>TOKEN_TTL</td><td>Optional</td><td><code>string</code></td><td>Time for token to live</td><td>In vercel/ms format https://github.com/vercel/ms</td></tr><tr><td>PAGESPEED_API_KEY</td><td>Optional</td><td><code>string</code></td><td>API Key for PageSpeed requests</td><td></td></tr><tr><td>SYSTEM_EMAIL_HOST</td><td>Required</td><td><code>string</code></td><td>Host to send System Emails From</td><td></td></tr><tr><td>SYSTEM_EMAIL_PORT</td><td>Required</td><td><code>number</code></td><td>Port for System Email Host</td><td></td></tr><tr><td>SYSTEM_EMAIL_ADDRESS</td><td>Required</td><td><code>string</code></td><td>System Email Address</td><td></td></tr><tr><td>SYSTEM_EMAIL_PASSWORD</td><td>Required</td><td><code>string</code></td><td>System Email Password</td><td></td></tr></tbody></table>
<table><thead><tr><th width="239">ENV Variable Name</th><th width="149">Required/Optional</th><th width="116">Type</th><th>Description</th><th>Accepted Values</th></tr></thead><tbody><tr><td>CLIENT_HOST</td><td>Required</td><td><code>string</code></td><td>Frontend Host</td><td></td></tr><tr><td>JWT_SECRET</td><td>Required</td><td><code>string</code></td><td>JWT secret</td><td></td></tr><tr><td>REFRESH_TOKEN_SECRET</td><td>Required</td><td><code>string</code></td><td>Refresh JWT secret</td><td></td></tr><tr><td>DB_TYPE</td><td>Optional</td><td><code>string</code></td><td>Specify DB to use</td><td><code>MongoDB | FakeDB</code></td></tr><tr><td>DB_CONNECTION_STRING</td><td>Required</td><td><code>string</code></td><td>Specifies URL for MongoDB Database</td><td></td></tr><tr><td>PORT</td><td>Optional</td><td><code>integer</code></td><td>Specifies Port for Server</td><td></td></tr><tr><td>LOGIN_PAGE_URL</td><td>Required</td><td><code>string</code></td><td>Login url to be used in emailing service</td><td></td></tr><tr><td>REDIS_HOST</td><td>Required</td><td><code>string</code></td><td>Host address for Redis database</td><td></td></tr><tr><td>REDIS_PORT</td><td>Required</td><td><code>integer</code></td><td>Port for Redis database</td><td></td></tr><tr><td>TOKEN_TTL</td><td>Optional</td><td><code>string</code></td><td>Time for token to live</td><td>In vercel/ms format https://github.com/vercel/ms</td></tr><tr><td>REFRESH_TOKEN_TTL</td><td>Optional</td><td><code>string</code></td><td>Time for refresh token to live</td><td></td></tr><tr><td>PAGESPEED_API_KEY</td><td>Optional</td><td><code>string</code></td><td>API Key for PageSpeed requests</td><td></td></tr><tr><td>SYSTEM_EMAIL_HOST</td><td>Required</td><td><code>string</code></td><td>Host to send System Emails From</td><td></td></tr><tr><td>SYSTEM_EMAIL_PORT</td><td>Required</td><td><code>number</code></td><td>Port for System Email Host</td><td></td></tr><tr><td>SYSTEM_EMAIL_ADDRESS</td><td>Required</td><td><code>string</code></td><td>System Email Address</td><td></td></tr><tr><td>SYSTEM_EMAIL_PASSWORD</td><td>Required</td><td><code>string</code></td><td>System Email Password</td><td></td></tr></tbody></table>
---