Merge pull request #960 from Rushi1109/915-be-generate-refresh-token
Generate refresh token & add it to response
This commit is contained in:
6 files changed
+78
-21
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in new issue
Block a user