Refactor refreshAuthToken, add tests for coverage

This commit is contained in:
Alex Holliday committed 2024-10-19 17:18:35 +08:00
1 parent 7c24136e6a
commit 7f95b061ed
2 files changed
+126 -118

No files matched your search

+44 -46
View File
@@ -184,54 +184,52 @@ const loginUser = async (req, res, next) => {
* @throws {Error} If there is an error during the process such as any of the token is not received
*/
const refreshAuthToken = async (req, res, next) => {
try {
// check for refreshToken
const refreshToken = req.headers["x-refresh-token"];
try {
// check for refreshToken
const refreshToken = req.headers["x-refresh-token"];
if (!refreshToken) {
// No refresh token provided
const error = new Error(errorMessages.NO_REFRESH_TOKEN);
error.status = 401;
error.service = SERVICE_NAME;
error.method = "refreshAuthToken";
return next(error);
}
if (!refreshToken) {
// No refresh token provided
const error = new Error(errorMessages.NO_REFRESH_TOKEN);
error.status = 401;
error.service = SERVICE_NAME;
error.method = "refreshAuthToken";
return next(error);
}
// Verify refresh token
const { refreshTokenSecret } = await req.settingsService.getSettings();
jwt.verify(refreshToken, refreshTokenSecret, async (refreshErr, refreshDecoded) => {
if (refreshErr) {
// Invalid or expired refresh token, trigger logout
const errorMessage =
refreshErr.name === "TokenExpiredError"
? errorMessages.EXPIRED_REFRESH_TOKEN
: errorMessages.INVALID_REFRESH_TOKEN;
const error = new Error(errorMessage);
error.status = 401;
error.service = SERVICE_NAME;
return next(error);
}
// Verify refresh token
const appSettings = await req.settingsService.getSettings();
const { refreshTokenSecret } = appSettings;
jwt.verify(refreshToken, refreshTokenSecret, async (refreshErr, refreshDecoded) => {
if (refreshErr) {
// Invalid or expired refresh token, trigger logout
const errorMessage =
refreshErr.name === "TokenExpiredError"
? errorMessages.EXPIRED_REFRESH_TOKEN
: errorMessages.INVALID_REFRESH_TOKEN;
const error = new Error(errorMessage);
error.status = 401;
error.service = SERVICE_NAME;
return next(error);
}
});
// Refresh token is valid and unexpired, generate new access token
const oldAuthToken = getTokenFromHeaders(req.headers);
const { jwtSecret } = await req.settingsService.getSettings();
const payloadData = jwt.verify(oldAuthToken, jwtSecret, { ignoreExpiration: true });
// delete old token related data
delete payloadData.iat;
delete payloadData.exp;
const newAuthToken = issueToken(payloadData, tokenType.ACCESS_TOKEN, appSettings);
// Refresh token is valid and unexpired, generate new access token
const oldAuthToken = getTokenFromHeaders(req.headers);
const { jwtSecret } = await req.settingsService.getSettings();
const payloadData = jwt.verify(oldAuthToken, jwtSecret, { ignoreExpiration: true });
// delete old token related data
delete payloadData.iat;
delete payloadData.exp;
const newAuthToken = issueToken(payloadData, tokenType.ACCESS_TOKEN, req.settingsService.getSettings());
return res.status(200).json({
success: true,
msg: successMessages.AUTH_TOKEN_REFRESHED,
data: { user: payloadData, token: newAuthToken, refreshToken: refreshToken },
});
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "refreshAuthToken"));
}
return res.status(200).json({
success: true,
msg: successMessages.AUTH_TOKEN_REFRESHED,
data: { user: payloadData, token: newAuthToken, refreshToken: refreshToken },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "refreshAuthToken"));
}
};
/**
@@ -516,7 +514,7 @@ export {
issueToken,
registerUser,
loginUser,
refreshAuthToken,
refreshAuthToken,
editUser,
checkSuperadminExists,
requestRecovery,
+82 -72
View File
@@ -2,7 +2,7 @@ import {
issueToken,
registerUser,
loginUser,
refreshAuthToken,
refreshAuthToken,
editUser,
checkSuperadminExists,
requestRecovery,
@@ -16,6 +16,7 @@ import { errorMessages, successMessages } from "../../utils/messages.js";
import sinon from "sinon";
import { getTokenFromHeaders, tokenType } from "../../utils/utils.js";
import logger from "../../utils/logger.js";
import e from "cors";
describe("Auth Controller - issueToken", () => {
let stub;
@@ -304,86 +305,95 @@ describe("Auth Controller - loginUser", () => {
});
});
describe('Auth Controller - refreshAuthToken', () => {
let req, res, next, issueTokenStub;
describe("Auth Controller - refreshAuthToken", () => {
let req, res, next, issueTokenStub;
beforeEach(() => {
req = {
headers: {
'x-refresh-token': 'valid_refresh_token',
authorization: 'Bearer old_auth_token',
},
settingsService: {
getSettings: sinon.stub().resolves({
jwtSecret: 'my_secret',
refreshTokenSecret: 'my_refresh_secret',
}),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
sinon.stub(jwt, 'verify');
issueTokenStub = sinon.stub().returns("new_auth_token");
sinon.replace({ issueToken }, "issueToken", issueTokenStub);
});
beforeEach(() => {
req = {
headers: {
"x-refresh-token": "valid_refresh_token",
authorization: "Bearer old_auth_token",
},
settingsService: {
getSettings: sinon.stub().resolves({
jwtSecret: "my_secret",
refreshTokenSecret: "my_refresh_secret",
}),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
sinon.stub(jwt, "verify");
afterEach(() => {
sinon.restore();
});
issueTokenStub = sinon.stub().returns("new_auth_token");
sinon.replace({ issueToken }, "issueToken", issueTokenStub);
});
it('should reject if no refresh token is provided', async () => {
delete req.headers['x-refresh-token'];
await refreshAuthToken(req, res, next);
afterEach(() => {
sinon.restore();
});
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal(errorMessages.NO_REFRESH_TOKEN);
expect(next.firstCall.args[0].status).to.equal(401);
});
it("should reject if no refresh token is provided", async () => {
delete req.headers["x-refresh-token"];
await refreshAuthToken(req, res, next);
it('should reject if the refresh token is invalid', async () => {
jwt.verify.yields(new Error('invalid token'));
await refreshAuthToken(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal(errorMessages.NO_REFRESH_TOKEN);
expect(next.firstCall.args[0].status).to.equal(401);
});
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal(errorMessages.INVALID_REFRESH_TOKEN);
expect(next.firstCall.args[0].status).to.equal(401);
});
it("should reject if the refresh token is invalid", async () => {
jwt.verify.yields(new Error("invalid token"));
await refreshAuthToken(req, res, next);
it('should reject if settingsService.getSettings fails', async () => {
req.settingsService.getSettings.rejects(new Error('settingsService.getSettings error'));
await refreshAuthToken(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal(errorMessages.INVALID_REFRESH_TOKEN);
expect(next.firstCall.args[0].status).to.equal(401);
});
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal('settingsService.getSettings error');
});
it("should reject if the refresh token is expired", async () => {
const error = new Error("Token expired");
error.name = "TokenExpiredError";
jwt.verify.yields(error);
await refreshAuthToken(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal(errorMessages.EXPIRED_REFRESH_TOKEN);
expect(next.firstCall.args[0].status).to.equal(401);
});
it('should generate a new auth token if the refresh token is valid', async () => {
const oldAuthToken = 'valid_old_auth_token';
const newAuthToken = 'new_generated_token';
const decodedPayload = { userId: '123', email: 'test@test.com' };
sinon.stub(getTokenFromHeaders).returns(oldAuthToken);
jwt.verify.callsFake((token, secret, callback) => {
if (token === 'valid_refresh_token') {
callback(null, {});
} else {
callback(new Error('Invalid token'));
}
});
await refreshAuthToken(req, res, next);
expect(res.status).to.have.been.calledWith(200);
expect(res.json).to.have.been.calledWith({
success: true,
msg: successMessages.AUTH_TOKEN_REFRESHED,
data: { user: decodedPayload, token: newAuthToken, refreshToken: 'valid_refresh_token' },
});
});
it("should reject if settingsService.getSettings fails", async () => {
req.settingsService.getSettings.rejects(
new Error("settingsService.getSettings error")
);
await refreshAuthToken(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("settingsService.getSettings error");
});
it("should generate a new auth token if the refresh token is valid", async () => {
const decodedPayload = { expiresIn: "60" };
jwt.verify.callsFake(() => {
return decodedPayload;
});
await refreshAuthToken(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
expect(
res.json.calledWith({
success: true,
msg: successMessages.AUTH_TOKEN_REFRESHED,
data: {
user: decodedPayload,
token: sinon.match.string,
refreshToken: "valid_refresh_token",
},
})
).to.be.true;
});
});
describe("Auth Controller - editUser", async () => {