Skip to content

Commit 420be8d

Browse files
committed
test(auth): add tests for jwtAuthHandler
1 parent 407cb85 commit 420be8d

File tree

3 files changed

+89
-4
lines changed

3 files changed

+89
-4
lines changed

src/service/passport/jwtAuthHandler.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1-
const jwtAuthHandler = () => {
1+
const { assignRoles, validateJwt } = require('./jwtUtils');
2+
3+
/**
4+
* Middleware function to handle JWT authentication.
5+
* @param {*} overrideConfig optional configuration to override the default JWT configuration (e.g. for testing)
6+
* @returns {Function} the middleware function
7+
*/
8+
const jwtAuthHandler = (overrideConfig = null) => {
29
return async (req, res, next) => {
3-
const apiAuthMethods = require('../../config').getAPIAuthMethods();
10+
const apiAuthMethods =
11+
overrideConfig
12+
? [{ type: "jwt", jwtConfig: overrideConfig }]
13+
: require('../../config').getAPIAuthMethods();
14+
415
const jwtAuthMethod = apiAuthMethods.find((method) => method.type.toLowerCase() === "jwt");
516
if (!jwtAuthMethod) {
617
return next();

src/service/passport/jwtUtils.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ async function getJwks(authorityUrl) {
2626
* @param {*} authorityUrl the OIDC authority URL
2727
* @param {*} clientID the OIDC client ID
2828
* @param {*} expectedAudience the expected audience for the token
29+
* @param {*} getJwksInject the getJwks function to use (for dependency injection). Defaults to the built-in getJwks function.
2930
* @return {Promise<object>} the verified payload or an error
3031
*/
31-
async function validateJwt(token, authorityUrl, clientID, expectedAudience) {
32+
async function validateJwt(token, authorityUrl, clientID, expectedAudience, getJwksInject = getJwks) {
3233
try {
33-
const jwks = await getJwks(authorityUrl);
34+
const jwks = await getJwksInject(authorityUrl);
3435

3536
const decodedHeader = await jwt.decode(token, { complete: true });
3637
if (!decodedHeader || !decodedHeader.header || !decodedHeader.header.kid) {

test/testJwtAuthHandler.test.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,76 @@ describe('assignRoles', () => {
111111
});
112112
});
113113

114+
describe('jwtAuthHandler', () => {
115+
let req, res, next, jwtConfig, validVerifyResponse;
116+
117+
beforeEach(() => {
118+
req = { header: sinon.stub(), isAuthenticated: sinon.stub(), user: {} };
119+
res = { status: sinon.stub().returnsThis(), send: sinon.stub() };
120+
next = sinon.stub();
121+
122+
jwtConfig = {
123+
clientID: 'client-id',
124+
authorityURL: 'https://accounts.google.com',
125+
expectedAudience: 'expected-audience',
126+
roleMapping: { 'admin': { 'admin': 'admin' } }
127+
};
128+
129+
validVerifyResponse = {
130+
header: { kid: '123' },
131+
azp: 'client-id',
132+
sub: 'user123',
133+
admin: 'admin'
134+
};
135+
});
136+
137+
afterEach(() => {
138+
sinon.restore();
139+
});
140+
141+
it('should call next if user is authenticated', async () => {
142+
req.isAuthenticated.returns(true);
143+
await jwtAuthHandler()(req, res, next);
144+
expect(next.calledOnce).to.be.true;
145+
});
146+
147+
it('should return 401 if no token provided', async () => {
148+
req.header.returns(null);
149+
await jwtAuthHandler()(req, res, next);
150+
151+
expect(res.status.calledWith(401)).to.be.true;
152+
expect(res.send.calledWith('No token provided\n')).to.be.true;
153+
});
154+
155+
it('should return 500 if authorityURL not configured', async () => {
156+
req.header.returns('Bearer fake-token');
157+
jwtConfig.authorityURL = null;
158+
sinon.stub(jwt, 'verify').returns(validVerifyResponse);
159+
160+
await jwtAuthHandler(jwtConfig)(req, res, next);
161+
162+
expect(res.status.calledWith(500)).to.be.true;
163+
expect(res.send.calledWith('OIDC authority URL is not configured\n')).to.be.true;
164+
});
165+
166+
it('should return 500 if clientID not configured', async () => {
167+
req.header.returns('Bearer fake-token');
168+
jwtConfig.clientID = null;
169+
sinon.stub(jwt, 'verify').returns(validVerifyResponse);
170+
171+
await jwtAuthHandler(jwtConfig)(req, res, next);
172+
173+
expect(res.status.calledWith(500)).to.be.true;
174+
expect(res.send.calledWith('OIDC client ID is not configured\n')).to.be.true;
175+
});
176+
177+
it('should return 401 if JWT validation fails', async () => {
178+
req.header.returns('Bearer fake-token');
179+
sinon.stub(jwt, 'verify').throws(new Error('Invalid token'));
180+
181+
await jwtAuthHandler(jwtConfig)(req, res, next);
182+
183+
expect(res.status.calledWith(401)).to.be.true;
184+
expect(res.send.calledWithMatch(/JWT validation failed:/)).to.be.true;
185+
});
186+
});

0 commit comments

Comments
 (0)