Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DCMAW-10829: Get biometric access token from ReadID #337

Merged
merged 46 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
b8abc07
Adds POST to HttpMethod type
jmooney-dd Jan 15, 2025
8a1369f
Add test describe blocks for getBiometricToken
jmooney-dd Jan 15, 2025
dd8ab84
Updates happy path describe block
jmooney-dd Jan 15, 2025
20a34ac
formatting
jmooney-dd Jan 15, 2025
fcbecbe
Write tests for network error and valid response
jmooney-dd Jan 15, 2025
125f8f9
Adds http request logic and return access token
jmooney-dd Jan 15, 2025
95dc7aa
Use shorthand for headers value
jmooney-dd Jan 15, 2025
63188c3
Updates invalid JSON test
jmooney-dd Jan 15, 2025
c842ea1
Updates response body undefined test
jmooney-dd Jan 15, 2025
836cbc9
update mockData in happy path test
jmooney-dd Jan 15, 2025
127153e
Update envVar for read id mock base url in dev and build to include v…
jmooney-dd Jan 15, 2025
da230c2
Add debug log re attempting network request
jmooney-dd Jan 15, 2025
dc27fe2
Register success and failure attemp logs for network call - not used yet
jmooney-dd Jan 15, 2025
524945b
Use failure and success logs
jmooney-dd Jan 15, 2025
cd72dd8
Remove duplicate data in log
jmooney-dd Jan 15, 2025
72b85a3
Add tests for error logs
jmooney-dd Jan 15, 2025
74fff91
Add success debug log test
jmooney-dd Jan 15, 2025
77b3187
Add attempt debug log test
jmooney-dd Jan 15, 2025
4acf20b
Update mock value of url to be a url
jmooney-dd Jan 15, 2025
55df611
formatting
jmooney-dd Jan 15, 2025
9c111b9
Use optional chaining to resolve code smell and remove impossible test
jmooney-dd Jan 15, 2025
ec3a4ca
Cast http method as const rather than HttpMethod for extra type safety
jmooney-dd Jan 15, 2025
cba28ff
Add optional overide for getBiometricToken func to allow for mocking …
jmooney-dd Jan 15, 2025
9364159
Use mock functions rather than jest mocks where possible
jmooney-dd Jan 15, 2025
16a705d
Omit submitter key in log as it is a secret
jmooney-dd Jan 15, 2025
d70142b
Update test wording
jmooney-dd Jan 15, 2025
f771fdc
Update type sendHttpRequestOverride?: ISendHttpRequest
jmooney-dd Jan 15, 2025
be74576
formatting
jmooney-dd Jan 15, 2025
4cd8d1e
Update function name and test wording
jmooney-dd Jan 15, 2025
bfe58ee
Update test wording and remove last jest mock
jmooney-dd Jan 15, 2025
79ac67b
Set default value for sendHttpRequest method and naming tweeks
jmooney-dd Jan 15, 2025
cced684
Remove un neccessary consting
jmooney-dd Jan 15, 2025
59bea2b
Update log message data in error scenarios
jmooney-dd Jan 15, 2025
f781923
Add content type header to log data and add comment
jmooney-dd Jan 15, 2025
ffffa71
get headers for logging from the httpRequest obj
jmooney-dd Jan 15, 2025
c4aa276
Fix headers in log
jmooney-dd Jan 15, 2025
448cee3
remove console.log
jmooney-dd Jan 15, 2025
4bea75a
minor change to how request log data is contructed
jmooney-dd Jan 15, 2025
6c4282b
Remove consts for things that aren't reused
jmooney-dd Jan 15, 2025
cb52a34
Update key name in logs re http request
jmooney-dd Jan 15, 2025
67a4f0e
naming tweeks
jmooney-dd Jan 15, 2025
9c3d510
naming tweeks in test
jmooney-dd Jan 15, 2025
99b4ee9
naming tweeks in test
jmooney-dd Jan 15, 2025
77eba62
Update test wording
jmooney-dd Jan 15, 2025
46414ee
Use jest mocks rather than custom mocks
jmooney-dd Jan 16, 2025
e9ad5f9
const expected arguments
jmooney-dd Jan 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,170 @@
import { successResult } from "../../utils/result";
import { emptyFailure, Result, successResult } from "../../utils/result";
import { getBiometricToken } from "./getBiometricToken";
import { expect } from "@jest/globals";
import "../../testUtils/matchers";
import { ISendHttpRequest } from "../../services/http/sendHttpRequest";

describe("getBiometricToken", () => {
it("Returns successResult containing a string", async () => {
const result = await getBiometricToken("mockUrl", "mockSubmitterKey");
let result: Result<string, void>;
let consoleDebugSpy: jest.SpyInstance;
let consoleErrorSpy: jest.SpyInstance;
let mockSendHttpRequest: ISendHttpRequest;
const expectedArguments = {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"X-Innovalor-Authorization": "mockSubmitterKey",
},
method: "POST",
url: "https://mockUrl.com/oauth/token?grant_type=client_credentials",
};

expect(result).toEqual(successResult("mockBiometricToken"));
beforeEach(() => {
consoleDebugSpy = jest.spyOn(console, "debug");
consoleErrorSpy = jest.spyOn(console, "error");
});

describe("On every call", () => {
beforeEach(async () => {
mockSendHttpRequest = jest.fn().mockResolvedValue({
statusCode: 200,
body: "mockBody",
headers: {
mockHeaderKey: "mockHeaderValue",
},
});

result = await getBiometricToken(
"https://mockUrl.com",
"mockSubmitterKey",
mockSendHttpRequest,
);
});

it("Logs network call attempt at debug level", () => {
expect(consoleDebugSpy).toHaveBeenCalledWithLogFields({
messageCode:
"MOBILE_ASYNC_BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_ATTEMPT",
});
});
});

describe("Given an error is caught when requesting token", () => {
beforeEach(async () => {
mockSendHttpRequest = jest.fn().mockRejectedValue(new Error("mockError"));

result = await getBiometricToken(
"https://mockUrl.com",
"mockSubmitterKey",
mockSendHttpRequest,
);
});

it("Logs error", () => {
expect(consoleErrorSpy).toHaveBeenCalledWithLogFields({
messageCode:
"MOBILE_ASYNC_BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_FAILURE",
});
});

it("Returns an empty failure", () => {
expect(result).toEqual(emptyFailure());
expect(mockSendHttpRequest).toBeCalledWith(expectedArguments);
});
});

describe("Given the response is invalid", () => {
describe("Given response body is undefined", () => {
beforeEach(async () => {
mockSendHttpRequest = jest.fn().mockResolvedValue({
statusCode: 200,
body: undefined,
headers: {
mockHeaderKey: "mockHeaderValue",
},
});

result = await getBiometricToken(
"https://mockUrl.com",
"mockSubmitterKey",
mockSendHttpRequest,
);
});

it("Logs error", () => {
expect(consoleErrorSpy).toHaveBeenCalledWithLogFields({
messageCode:
"MOBILE_ASYNC_BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_FAILURE",
});
});

it("Returns an empty failure", () => {
expect(result).toEqual(emptyFailure());
expect(mockSendHttpRequest).toBeCalledWith(expectedArguments);
});
});

describe("Given response body cannot be parsed", () => {
beforeEach(async () => {
mockSendHttpRequest = jest.fn().mockResolvedValue({
statusCode: 200,
body: "Invalid JSON",
headers: {
mockHeaderKey: "mockHeaderValue",
},
});

result = await getBiometricToken(
"https://mockUrl.com",
"mockSubmitterKey",
mockSendHttpRequest,
);
});

it("Logs error", () => {
expect(consoleErrorSpy).toHaveBeenCalledWithLogFields({
messageCode:
"MOBILE_ASYNC_BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_FAILURE",
});
});

it("Returns an empty failure", () => {
expect(result).toEqual(emptyFailure());
expect(mockSendHttpRequest).toBeCalledWith(expectedArguments);
});
});
});

describe("Given valid request is made", () => {
beforeEach(async () => {
mockSendHttpRequest = jest.fn().mockResolvedValue({
statusCode: 200,
body: JSON.stringify({
access_token: "mockBiometricToken",
expires_in: 3600,
token_type: "Bearer",
}),
headers: {
mockHeaderKey: "mockHeaderValue",
},
});

result = await getBiometricToken(
"https://mockUrl.com",
"mockSubmitterKey",
mockSendHttpRequest,
);
});

it("Logs success at debug level", () => {
expect(consoleDebugSpy).toHaveBeenCalledWithLogFields({
messageCode:
"MOBILE_ASYNC_BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_SUCCESS",
});
});

it("Returns successResult containing biometric token", () => {
expect(result).toEqual(successResult("mockBiometricToken"));
expect(mockSendHttpRequest).toBeCalledWith(expectedArguments);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,10 +1,93 @@
import { Result, successResult } from "../../utils/result";
import { logger } from "../../common/logging/logger";
import { LogMessage } from "../../common/logging/LogMessage";
import {
ISendHttpRequest,
sendHttpRequest as sendHttpRequestDefault,
} from "../../services/http/sendHttpRequest";
import { emptyFailure, Result, successResult } from "../../utils/result";

export type GetBiometricToken = (
url: string,
submitterKey: string,
sendHttpRequest?: ISendHttpRequest,
) => Promise<Result<string, void>>;

export const getBiometricToken: GetBiometricToken = async () => {
return successResult("mockBiometricToken");
export const getBiometricToken: GetBiometricToken = async (
url: string,
submitterKey: string,
sendHttpRequest: ISendHttpRequest = sendHttpRequestDefault,
) => {
const httpRequest = {
url: `${url}/oauth/token?grant_type=client_credentials`,
method: "POST" as const,
headers: {
"X-Innovalor-Authorization": submitterKey,
"Content-Type": "application/x-www-form-urlencoded",
},
};
const httpRequestLogData = {
...httpRequest,
headers: {
...httpRequest.headers,
"X-Innovalor-Authorization": "Secret value and cannot be logged",
},
};

let response;
try {
logger.debug(
LogMessage.BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_ATTEMPT,
{
data: {
httpRequest: httpRequestLogData,
},
},
);
response = await sendHttpRequest(httpRequest);
} catch (error) {
logger.error(
LogMessage.BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_FAILURE,
{
data: {
error,
httpRequest: httpRequestLogData,
},
},
);
return emptyFailure();
}

if (response?.body == null) {
logger.error(
LogMessage.BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_FAILURE,
{
data: {
response,
httpRequest: httpRequestLogData,
},
},
);
return emptyFailure();
}

let parsedBody;
try {
parsedBody = JSON.parse(response.body);
} catch (error) {
logger.error(
LogMessage.BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_FAILURE,
{
data: {
error,
httpRequest: httpRequestLogData,
},
},
);
return emptyFailure();
}

logger.debug(
LogMessage.BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_SUCCESS,
);
return successResult(parsedBody.access_token);
};
15 changes: 15 additions & 0 deletions backend-api/src/functions/common/logging/LogMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ export class LogMessage implements LogAttributes {
"MOBILE_ASYNC_BIOMETRIC_TOKEN_REQUEST_BODY_INVALID",
"The incoming request body was missing or invalid.",
);
static readonly BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_ATTEMPT =
new LogMessage(
"MOBILE_ASYNC_BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_ATTEMPT",
"Attempting to retrieve biometric access token from ReadID",
);
static readonly BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_FAILURE =
new LogMessage(
"MOBILE_ASYNC_BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_FAILURE",
"Failed to retrieve biometric access token from ReadID",
);
static readonly BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_SUCCESS =
new LogMessage(
"MOBILE_ASYNC_BIOMETRIC_TOKEN_GET_BIOMETRIC_TOKEN_FROM_READID_SUCCESS",
"Successfully retrieved biometric access token from ReadID",
);

private constructor(
public readonly messageCode: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export type RetryConfig = {
delayInMillis?: number;
};

export type HttpMethod = "GET";
export type HttpMethod = "GET" | "POST";

export type HttpHeaders = {
[key: string]: string;
Expand Down
4 changes: 2 additions & 2 deletions backend-api/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ Mappings:
EnvironmentVariables:
dev:
STSBASEURL: 'https://mob-sts-mock.review-b-async.dev.account.gov.uk'
ReadIdBaseUrl: 'https://api-mob-readid-mock.review-b-async.dev.account.gov.uk'
ReadIdBaseUrl: 'https://api-mob-readid-mock.review-b-async.dev.account.gov.uk/v2'
jmooney-dd marked this conversation as resolved.
Show resolved Hide resolved
ClientRegistrySecretPath: 'dev/clientRegistry'
BiometricSubmitterKeySecretPathPassport: '/dev/BIOMETRIC_SUBMITTER_ACCESS_KEY_NFC_PASSPORT'
BiometricSubmitterKeySecretPathBrp: '/dev/BIOMETRIC_SUBMITTER_ACCESS_KEY_NFC_BRP'
Expand All @@ -169,7 +169,7 @@ Mappings:

build:
STSBASEURL: 'https://mob-sts-mock.review-b-async.build.account.gov.uk'
ReadIdBaseUrl: 'https://api-mob-readid-mock.review-b-async.build.account.gov.uk'
ReadIdBaseUrl: 'https://api-mob-readid-mock.review-b-async.build.account.gov.uk/v2'
ClientRegistrySecretPath: 'build/clientRegistry'
BiometricSubmitterKeySecretPathPassport: '/build/BIOMETRIC_SUBMITTER_ACCESS_KEY_NFC_PASSPORT'
BiometricSubmitterKeySecretPathBrp: '/build/BIOMETRIC_SUBMITTER_ACCESS_KEY_NFC_BRP'
Expand Down