Skip to content

Commit 2d35652

Browse files
VIA-537 SB Continue replacing usage of eager config object.
1 parent 96ccf85 commit 2d35652

File tree

10 files changed

+133
-97
lines changed

10 files changed

+133
-97
lines changed

src/app/api/auth/[...nextauth]/provider.test.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import NHSLoginAuthProvider from "@src/app/api/auth/[...nextauth]/provider";
2-
import { configProvider } from "@src/utils/config";
2+
import lazyConfig from "@src/utils/lazy-config";
3+
import { AsyncConfigMock, lazyConfigBuilder } from "@test-data/config/builders";
34

45
jest.mock("@src/utils/config");
56
jest.mock("@src/utils/auth/pem-to-crypto-key");
67
jest.mock("sanitize-data", () => ({ sanitize: jest.fn() }));
78

89
describe("provider", () => {
9-
(configProvider as jest.Mock).mockImplementation(() => ({
10-
NHS_LOGIN_URL: "",
11-
NHS_LOGIN_CLIENT_ID: "",
12-
NHS_LOGIN_SCOPE: "",
13-
NHS_LOGIN_PRIVATE_KEY: "",
14-
}));
10+
const mockedConfig = lazyConfig as AsyncConfigMock;
11+
12+
beforeEach(() => {
13+
const defaultConfig = lazyConfigBuilder().build();
14+
Object.assign(mockedConfig, defaultConfig);
15+
});
1516

1617
it("should be configured correctly", async () => {
1718
const provider = await NHSLoginAuthProvider();

src/app/api/auth/[...nextauth]/provider.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,26 @@
11
import { OIDCConfig } from "@auth/core/providers";
22
import pemToCryptoKey from "@src/utils/auth/pem-to-crypto-key";
3-
import { AppConfig, configProvider } from "@src/utils/config";
3+
import lazyConfig from "@src/utils/lazy-config";
44
import { Profile } from "next-auth";
55

66
export const NHS_LOGIN_PROVIDER_ID = "nhs-login";
77

88
const NHSLoginAuthProvider = async (): Promise<OIDCConfig<Profile>> => {
9-
const config: AppConfig = await configProvider();
10-
119
return {
1210
id: NHS_LOGIN_PROVIDER_ID,
1311
name: "NHS Login Auth Provider",
1412
type: "oidc",
15-
issuer: config.NHS_LOGIN_URL,
16-
clientId: config.NHS_LOGIN_CLIENT_ID,
17-
wellKnown: `${config.NHS_LOGIN_URL}/.well-known/openid-configuration`,
13+
issuer: (await lazyConfig.NHS_LOGIN_URL) as string,
14+
clientId: (await lazyConfig.NHS_LOGIN_CLIENT_ID) as string,
15+
wellKnown: `${await lazyConfig.NHS_LOGIN_URL}/.well-known/openid-configuration`,
1816
authorization: {
1917
params: {
20-
scope: `${config.NHS_LOGIN_SCOPE}`,
18+
scope: `${await lazyConfig.NHS_LOGIN_SCOPE}`,
2119
prompt: "none",
2220
},
2321
},
2422
token: {
25-
clientPrivateKey: await pemToCryptoKey(config.NHS_LOGIN_PRIVATE_KEY),
23+
clientPrivateKey: await pemToCryptoKey((await lazyConfig.NHS_LOGIN_PRIVATE_KEY) as string),
2624
},
2725
client: {
2826
token_endpoint_auth_method: "private_key_jwt",
Lines changed: 41 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,66 @@
11
import { VaccineType } from "@src/models/vaccine";
22
import { getSSOUrlToNBSForVaccine } from "@src/services/nbs/nbs-service";
33
import { generateAssertedLoginIdentityJwt } from "@src/utils/auth/generate-auth-payload";
4-
import { AppConfig, configProvider } from "@src/utils/config";
4+
import lazyConfig from "@src/utils/lazy-config";
5+
import { AsyncConfigMock, lazyConfigBuilder } from "@test-data/config/builders";
56

6-
jest.mock("@src/utils/config");
77
jest.mock("@src/utils/auth/generate-auth-payload", () => ({
88
generateAssertedLoginIdentityJwt: jest.fn(),
99
}));
1010
jest.mock("sanitize-data", () => ({ sanitize: jest.fn() }));
1111

12-
const nbsUrlFromConfig = "https://test-nbs-url";
12+
const nbsUrlFromConfig = new URL("https://test-nbs-url");
1313
const nbsBookingPathFromConfig = "/test/path/book";
1414

1515
const mockAssertedLoginIdentityJWT = "mock-jwt";
1616

1717
describe("getSSOUrlToNBSForVaccine", () => {
18-
beforeEach(() => {
19-
(configProvider as jest.Mock).mockImplementation(
20-
(): Partial<AppConfig> => ({
21-
NBS_URL: nbsUrlFromConfig,
22-
NBS_BOOKING_PATH: nbsBookingPathFromConfig,
23-
}),
24-
);
18+
const mockedConfig = lazyConfig as AsyncConfigMock;
2519

26-
(generateAssertedLoginIdentityJwt as jest.Mock).mockReturnValue(mockAssertedLoginIdentityJWT);
27-
});
20+
describe("when NBS config is valid", () => {
21+
beforeEach(() => {
22+
const defaultConfig = lazyConfigBuilder()
23+
.withNbsUrl(nbsUrlFromConfig)
24+
.andNbsBookingPath(nbsBookingPathFromConfig)
25+
.build();
26+
Object.assign(mockedConfig, defaultConfig);
2827

29-
it("returns sso url of NBS configured in config for RSV vaccine", async () => {
30-
const nbsRedirectUrl = new URL(await getSSOUrlToNBSForVaccine(VaccineType.RSV));
31-
expect(nbsRedirectUrl.origin).toEqual(nbsUrlFromConfig);
32-
expect(nbsRedirectUrl.pathname).toEqual(`${nbsBookingPathFromConfig}/rsv`);
33-
});
28+
(generateAssertedLoginIdentityJwt as jest.Mock).mockReturnValue(mockAssertedLoginIdentityJWT);
29+
});
3430

35-
it("should include campaignID query param in NBS URL", async () => {
36-
const nbsRedirectUrl = new URL(await getSSOUrlToNBSForVaccine(VaccineType.RSV));
37-
expect(nbsRedirectUrl.searchParams.get("wt.mc_id")).toEqual(expect.any(String));
38-
});
31+
it("returns sso url of NBS configured in config for RSV vaccine", async () => {
32+
const nbsRedirectUrl = new URL(await getSSOUrlToNBSForVaccine(VaccineType.RSV));
33+
expect(nbsRedirectUrl.origin).toEqual(nbsUrlFromConfig.origin);
34+
expect(nbsRedirectUrl.pathname).toEqual(`${nbsBookingPathFromConfig}/rsv`);
35+
});
3936

40-
it("should include assertedLoginIdentity query param in NBS URL", async () => {
41-
const nbsRedirectUrl = new URL(await getSSOUrlToNBSForVaccine(VaccineType.RSV));
42-
expect(nbsRedirectUrl.searchParams.get("assertedLoginIdentity")).toEqual(mockAssertedLoginIdentityJWT);
43-
});
37+
it("should include campaignID query param in NBS URL", async () => {
38+
const nbsRedirectUrl = new URL(await getSSOUrlToNBSForVaccine(VaccineType.RSV));
39+
expect(nbsRedirectUrl.searchParams.get("wt.mc_id")).toEqual(expect.any(String));
40+
});
41+
42+
it("should include assertedLoginIdentity query param in NBS URL", async () => {
43+
const nbsRedirectUrl = new URL(await getSSOUrlToNBSForVaccine(VaccineType.RSV));
44+
expect(nbsRedirectUrl.searchParams.get("assertedLoginIdentity")).toEqual(mockAssertedLoginIdentityJWT);
45+
});
4446

45-
it("should redirect to SSO failure page if error occurs generating assertedLoginIdentity", async () => {
46-
(generateAssertedLoginIdentityJwt as jest.Mock).mockRejectedValue(
47-
new Error("Error creating SSO assertedLoginIdentity: id_token.jti attribute missing from session"),
48-
);
47+
it("should redirect to SSO failure page if error occurs generating assertedLoginIdentity", async () => {
48+
(generateAssertedLoginIdentityJwt as jest.Mock).mockRejectedValue(
49+
new Error("Error creating SSO assertedLoginIdentity: id_token.jti attribute missing from session"),
50+
);
4951

50-
const nbsRedirectUrl = await getSSOUrlToNBSForVaccine(VaccineType.RSV);
51-
expect(nbsRedirectUrl).toBe("/sso-failure");
52+
const nbsRedirectUrl = await getSSOUrlToNBSForVaccine(VaccineType.RSV);
53+
expect(nbsRedirectUrl).toBe("/sso-failure");
54+
});
5255
});
5356

54-
it("should redirect to SSO failure page if NBS Url config invalid", async () => {
55-
(configProvider as jest.Mock).mockImplementation(
56-
(): Partial<AppConfig> => ({
57-
NBS_URL: "not-a-valid-url",
58-
NBS_BOOKING_PATH: nbsBookingPathFromConfig,
59-
}),
60-
);
57+
describe("Without valid config", () => {
58+
it("should redirect to SSO failure page if NBS Url config invalid", async () => {
59+
const defaultConfig = {};
60+
Object.assign(mockedConfig, defaultConfig);
6161

62-
const nbsRedirectUrl = await getSSOUrlToNBSForVaccine(VaccineType.RSV);
63-
expect(nbsRedirectUrl).toBe("/sso-failure");
62+
const nbsRedirectUrl = await getSSOUrlToNBSForVaccine(VaccineType.RSV);
63+
expect(nbsRedirectUrl).toBe("/sso-failure");
64+
});
6465
});
6566
});

src/services/nbs/nbs-service.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { SSO_FAILURE_ROUTE } from "@src/app/sso-failure/constants";
44
import { VaccineType } from "@src/models/vaccine";
55
import { generateAssertedLoginIdentityJwt } from "@src/utils/auth/generate-auth-payload";
6-
import { AppConfig, configProvider } from "@src/utils/config";
6+
import lazyConfig from "@src/utils/lazy-config";
77
import { logger } from "@src/utils/logger";
88
import { Logger } from "pino";
99

@@ -24,11 +24,12 @@ const nbsVaccinePath: Record<VaccinesWithNBSBookingAvailable, string> = {
2424
};
2525

2626
const getSSOUrlToNBSForVaccine = async (vaccineType: VaccinesWithNBSBookingAvailable) => {
27-
const config: AppConfig = await configProvider();
28-
2927
let redirectUrl;
3028
try {
31-
const nbsURl = new URL(`${config.NBS_URL}${config.NBS_BOOKING_PATH}${nbsVaccinePath[vaccineType]}`);
29+
const nbsURl = new URL(
30+
`${await lazyConfig.NBS_BOOKING_PATH}${nbsVaccinePath[vaccineType]}`,
31+
(await lazyConfig.NBS_URL) as URL,
32+
);
3233
const nbsQueryParams = await getNbsQueryParams();
3334
nbsQueryParams.forEach((param) => {
3435
nbsURl.searchParams.append(param.name, param.value);
@@ -43,8 +44,7 @@ const getSSOUrlToNBSForVaccine = async (vaccineType: VaccinesWithNBSBookingAvail
4344
};
4445

4546
const getNbsQueryParams = async () => {
46-
const config: AppConfig = await configProvider();
47-
const assertedLoginIdentityJWT = await generateAssertedLoginIdentityJwt(config);
47+
const assertedLoginIdentityJWT = await generateAssertedLoginIdentityJwt();
4848

4949
return [
5050
{ name: NBS_QUERY_PARAMS.CAMPAIGN_ID, value: PLACEHOLDER_CAMPAIGN_ID },

src/utils/auth/generate-auth-payload.test.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,21 @@
33
*/
44
import { generateAssertedLoginIdentityJwt } from "@src/utils/auth/generate-auth-payload";
55
import { getJwtToken } from "@src/utils/auth/get-jwt-token";
6-
import { AppConfig } from "@src/utils/config";
7-
import { appConfigBuilder } from "@test-data/config/builders";
6+
import lazyConfig from "@src/utils/lazy-config";
7+
import { AsyncConfigMock, lazyConfigBuilder } from "@test-data/config/builders";
88
import jwt from "jsonwebtoken";
99
import { jwtDecode } from "jwt-decode";
1010

1111
jest.mock("jsonwebtoken", () => ({
1212
sign: jest.fn(),
1313
}));
1414
jest.mock("jwt-decode");
15+
jest.mock("sanitize-data", () => ({ sanitize: jest.fn() }));
1516

1617
jest.mock("@src/utils/auth/get-jwt-token", () => ({
1718
getJwtToken: jest.fn(),
1819
}));
1920

20-
const mockConfig: AppConfig = appConfigBuilder()
21-
.withNHS_LOGIN_URL("https://mock.nhs.login")
22-
.andNHS_LOGIN_CLIENT_ID("mock-client-id")
23-
.andNHS_LOGIN_PRIVATE_KEY("private-key")
24-
.build();
25-
2621
const mockSignedJwt = "mock-signed-jwt";
2722
const mockRandomUUID = "mock-jti";
2823
const mockNowInSeconds = 1749052001;
@@ -35,6 +30,7 @@ const mockJwtToken = {
3530

3631
describe("generate-auth-payload", () => {
3732
let randomUUIDSpy: jest.SpyInstance;
33+
const mockedConfig = lazyConfig as AsyncConfigMock;
3834

3935
beforeAll(() => {
4036
randomUUIDSpy = jest.spyOn(global.crypto, "randomUUID").mockReturnValue(mockRandomUUID);
@@ -47,6 +43,12 @@ describe("generate-auth-payload", () => {
4743
(jwtDecode as jest.Mock).mockReturnValue({
4844
jti: mockJtiFromSessionIdToken,
4945
});
46+
const defaultConfig = lazyConfigBuilder()
47+
.withNhsLoginUrl(new URL("https://mock.nhs.login"))
48+
.andNhsLoginClientId("mock-client-id")
49+
.andNhsLoginPrivateKey("private-key")
50+
.build();
51+
Object.assign(mockedConfig, defaultConfig);
5052
});
5153

5254
afterAll(() => {
@@ -62,13 +64,13 @@ describe("generate-auth-payload", () => {
6264

6365
const expectedAssertedLoginPayloadContent = {
6466
code: mockJtiFromSessionIdToken,
65-
iss: mockConfig.NHS_LOGIN_CLIENT_ID,
67+
iss: "mock-client-id",
6668
jti: mockRandomUUID,
6769
iat: mockNowInSeconds,
6870
exp: mockNowInSeconds + 60,
6971
};
7072

71-
const token = await generateAssertedLoginIdentityJwt(mockConfig);
73+
const token = await generateAssertedLoginIdentityJwt();
7274

7375
expect(jwt.sign).toHaveBeenCalledWith(expectedAssertedLoginPayloadContent, "private-key", { algorithm: "RS512" });
7476
expect(token).toEqual(mockSignedJwt);
@@ -83,7 +85,7 @@ describe("generate-auth-payload", () => {
8385

8486
(getJwtToken as jest.Mock).mockResolvedValue(mockJwtTokenWithMissingJti);
8587

86-
await expect(generateAssertedLoginIdentityJwt(mockConfig)).rejects.toThrow(
88+
await expect(generateAssertedLoginIdentityJwt()).rejects.toThrow(
8789
"Missing information. hasJwtToken=true, hasNHSLogin=true, hasIDToken=false",
8890
);
8991
});
@@ -93,7 +95,7 @@ describe("generate-auth-payload", () => {
9395

9496
(jwt.sign as jest.Mock).mockRejectedValue(new Error("Invalid key"));
9597

96-
await expect(generateAssertedLoginIdentityJwt(mockConfig)).rejects.toThrow("Invalid key");
98+
await expect(generateAssertedLoginIdentityJwt()).rejects.toThrow("Invalid key");
9799
});
98100
});
99101
});

src/utils/auth/generate-auth-payload.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { getJwtToken } from "@src/utils/auth/get-jwt-token";
22
import { DecodedIdToken } from "@src/utils/auth/types";
3-
import { AppConfig } from "@src/utils/config";
3+
import lazyConfig from "@src/utils/lazy-config";
44
import jwt from "jsonwebtoken";
55
import { jwtDecode } from "jwt-decode";
66

77
const ASSERTED_LOGIN_IDENTITY_EXPIRY_SECONDS = 60;
88

9-
const generateAssertedLoginIdentityJwt = async (config: AppConfig): Promise<string> => {
9+
const generateAssertedLoginIdentityJwt = async (): Promise<string> => {
1010
const token = await getJwtToken();
1111

1212
if (!token?.nhs_login?.id_token) {
@@ -19,14 +19,14 @@ const generateAssertedLoginIdentityJwt = async (config: AppConfig): Promise<stri
1919

2020
const nowInSeconds: number = Math.floor(Date.now() / 1000);
2121
const payload = {
22-
iss: config.NHS_LOGIN_CLIENT_ID,
22+
iss: await lazyConfig.NHS_LOGIN_CLIENT_ID,
2323
jti: crypto.randomUUID(),
2424
code: jtiFromIdToken,
2525
exp: nowInSeconds + ASSERTED_LOGIN_IDENTITY_EXPIRY_SECONDS,
2626
iat: nowInSeconds,
2727
};
2828

29-
return jwt.sign(payload, config.NHS_LOGIN_PRIVATE_KEY, { algorithm: "RS512" });
29+
return jwt.sign(payload, (await lazyConfig.NHS_LOGIN_PRIVATE_KEY) as string, { algorithm: "RS512" });
3030
};
3131

3232
export { generateAssertedLoginIdentityJwt };

src/utils/auth/get-jwt-token.test.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { getJwtToken } from "@src/utils/auth/get-jwt-token";
22
import { AccessToken } from "@src/utils/auth/types";
3-
import { configProvider } from "@src/utils/config";
4-
import { appConfigBuilder } from "@test-data/config/builders";
3+
import lazyConfig from "@src/utils/lazy-config";
4+
import { AsyncConfigMock, lazyConfigBuilder } from "@test-data/config/builders";
55
import { getToken } from "next-auth/jwt";
66
import { cookies, headers } from "next/headers";
77

@@ -13,15 +13,14 @@ jest.mock("next/headers", () => ({
1313
headers: jest.fn(),
1414
cookies: jest.fn(),
1515
}));
16-
17-
jest.mock("@src/utils/config", () => ({
18-
configProvider: jest.fn(),
19-
}));
16+
jest.mock("sanitize-data", () => ({ sanitize: jest.fn() }));
2017

2118
describe("getJwtToken", () => {
19+
const mockedConfig = lazyConfig as AsyncConfigMock;
20+
2221
beforeEach(() => {
23-
const mockConfig = appConfigBuilder().withAUTH_SECRET("test-auth-secret");
24-
(configProvider as jest.Mock).mockResolvedValue(mockConfig);
22+
const defaultConfig = lazyConfigBuilder().withAuthSecret("test-auth-secret").build();
23+
Object.assign(mockedConfig, defaultConfig);
2524
});
2625

2726
const mockGetTokenResult = {

src/utils/auth/get-jwt-token.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
import { AppConfig, configProvider } from "@src/utils/config";
1+
import lazyConfig from "@src/utils/lazy-config";
22
import { JWT, getToken } from "next-auth/jwt";
33
import { cookies, headers } from "next/headers";
44

55
const getJwtToken = async (): Promise<JWT | null> => {
6-
const config: AppConfig = await configProvider();
76
const headerEntries = await headers();
87
const cookieEntries = await cookies();
98
const req = {
109
headers: Object.fromEntries(headerEntries),
1110
cookies: Object.fromEntries(cookieEntries.getAll().map((c) => [c.name, c.value])),
1211
};
1312

14-
return await getToken({ req, secret: config.AUTH_SECRET, secureCookie: true });
13+
return await getToken({ req, secret: (await lazyConfig.AUTH_SECRET) as string, secureCookie: true });
1514
};
1615
export { getJwtToken };

0 commit comments

Comments
 (0)