From fc3d7122a78ed3599bdfe6817a8bf6b4db0596e9 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Tue, 13 Jul 2021 10:02:08 +0100 Subject: [PATCH] Add jest to auth package + test oidc strategy --- packages/auth/package.json | 4 + packages/auth/src/middleware/passport/oidc.js | 9 +- .../middleware/passport/tests/oidc.spec.js | 150 ++++++++++++++++++ 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 packages/auth/src/middleware/passport/tests/oidc.spec.js diff --git a/packages/auth/package.json b/packages/auth/package.json index 690d232459..0f6cf6e6bc 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -5,6 +5,10 @@ "main": "src/index.js", "author": "Budibase", "license": "AGPL-3.0", + "scripts": { + "test": "jest", + "test:watch": "jest --watchAll" + }, "dependencies": { "aws-sdk": "^2.901.0", "bcryptjs": "^2.4.3", diff --git a/packages/auth/src/middleware/passport/oidc.js b/packages/auth/src/middleware/passport/oidc.js index b636abbf09..3a75dfcf8e 100644 --- a/packages/auth/src/middleware/passport/oidc.js +++ b/packages/auth/src/middleware/passport/oidc.js @@ -65,7 +65,11 @@ function getEmail(profile, jwtClaims) { return username } - return null + throw new Error( + `Could not determine user email from profile ${JSON.stringify( + profile + )} and claims ${JSON.stringify(jwtClaims)}` + ) } function validEmail(value) { @@ -119,3 +123,6 @@ exports.strategyFactory = async function (config, callbackUrl) { throw new Error("Error constructing OIDC authentication strategy", err) } } + +// expose for testing +exports.authenticate = authenticate diff --git a/packages/auth/src/middleware/passport/tests/oidc.spec.js b/packages/auth/src/middleware/passport/tests/oidc.spec.js new file mode 100644 index 0000000000..487bd71407 --- /dev/null +++ b/packages/auth/src/middleware/passport/tests/oidc.spec.js @@ -0,0 +1,150 @@ +describe("oidc", () => { + describe("strategyFactory", () => { + // mock passport strategy factory + jest.mock("@techpass/passport-openidconnect") + const mockStrategy = require("@techpass/passport-openidconnect").Strategy + + // mock the response from .well-known/openid-configuration + const configUrlResponse = { + issuer: "mockIssuer", + authorization_endpoint: "mockAuthorizationEndpoint", + token_endpoint: "mockTokenEndpoint", + userinfo_endpoint: "mockUserInfoEndpoint" + } + + // mock the request to retrieve the oidc configuration + jest.mock("node-fetch", () => jest.fn(() => ( + { + ok: true, + json: async () => configUrlResponse + } + ))) + const mockFetch = require("node-fetch") + + it("should create successfully create an oidc strategy", async () => { + const oidc = require("../oidc") + + // mock the config supplied to the strategy factory + config = { + configUrl: "http://someconfigurl", + clientID: "clientId", + clientSecret: "clientSecret", + } + callbackUrl = "http://somecallbackurl" + + await oidc.strategyFactory(config, callbackUrl) + + expect(mockFetch).toHaveBeenCalledWith("http://someconfigurl") + expect(mockStrategy).toHaveBeenCalledWith( + { + issuer: configUrlResponse.issuer, + authorizationURL: configUrlResponse.authorization_endpoint, + tokenURL: configUrlResponse.token_endpoint, + userInfoURL: configUrlResponse.userinfo_endpoint, + clientID: config.clientID, + clientSecret: config.clientSecret, + callbackURL: callbackUrl, + }, + expect.anything() + ) + }) + }) + + describe("authenticate", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + // mock third party common authentication + jest.mock("../third-party-common") + const authenticateThirdParty = require("../third-party-common").authenticateThirdParty + + // parameters + const issuer = "mockIssuer" + const sub = "mockSub" + const profile = { + id: "mockId", + _json: { + email : "mock@budibase.com" + } + } + let jwtClaims = {} + const accessToken = "mockAccessToken" + const refreshToken = "mockRefreshToken" + const idToken = "mockIdToken" + const params = {} + // mock the passport callback + const mockDone = jest.fn() + + const thirdPartyUser = { + provider: issuer, + providerType: "oidc", + userId: profile.id, + profile: profile, + email: "mock@budibase.com", + oauth2: { + accessToken: accessToken, + refreshToken: refreshToken, + }, + } + + async function doAuthenticate() { + const oidc = require("../oidc") + + await oidc.authenticate( + issuer, + sub, + profile, + jwtClaims, + accessToken, + refreshToken, + idToken, + params, + mockDone + ) + } + + async function doTest() { + await doAuthenticate() + + expect(authenticateThirdParty).toHaveBeenCalledWith( + thirdPartyUser, + false, + mockDone) + } + + it("delegates authentication to third party common", async () => { + doTest() + }) + + it("uses JWT email to get email", async () => { + delete profile._json.email + jwtClaims = { + email : "mock@budibase.com" + } + + doTest() + }) + + it("uses JWT username to get email", async () => { + delete profile._json.email + jwtClaims = { + preferred_username : "mock@budibase.com" + } + + doTest() + }) + + it("uses JWT invalid username to get email", async () => { + delete profile._json.email + + jwtClaims = { + preferred_username : "invalidUsername" + } + + await expect(doAuthenticate()).rejects.toThrow("Could not determine user email from profile"); + }) + + }) +}) +