Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Expand Up @@ -7,9 +7,9 @@ import { NodeHttpHandler } from "@smithy/node-http-handler";
import { LogMessage } from "../../../common/logging/LogMessage";
import { logger } from "../../../common/logging/logger";
import { emptyFailure, Result, successResult } from "../../../utils/result";
import { SQSMessageBody } from "./types";
import { ISendMessageToSqs, SQSMessageBody } from "./types";

export const sendMessageToSqs = async (
export const sendMessageToSqs: ISendMessageToSqs = async (
sqsArn: string,
messageBody: SQSMessageBody,
): Promise<Result<string | undefined, void>> => {
Expand Down
14 changes: 14 additions & 0 deletions backend-api/src/functions/adapters/aws/sqs/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
import { VcIssuedTxMAEvent } from "../../../asyncIssueBiometricCredential/getVcIssuedEvent";
import {
AppStartEvent,
ClientCredentialsTokenIssuedEvent,
StartEvent,
} from "../../../common/audit/types";
import { Result } from "../../../utils/result";

export type ISendMessageToSqs = (
sqsArn: string,
messageBody: SQSMessageBody,
) => Promise<Result<string | undefined, void>>;

export interface VendorProcessingMessage {
biometricSessionId: string;
Expand All @@ -22,6 +33,9 @@ export interface VerifiableCredentialMessage {

export type SQSMessageBody =
| VcIssuedTxMAEvent
| AppStartEvent
| ClientCredentialsTokenIssuedEvent
| StartEvent
| VendorProcessingMessage
| OutboundQueueErrorMessage
| VerifiableCredentialMessage;
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { expect } from "@jest/globals";
import { APIGatewayProxyResult, Context } from "aws-lambda";
import "../../../tests/testUtils/matchers";
import { logger } from "../common/logging/logger";
import { MockEventWriterSuccess } from "../services/events/tests/mocks";
import {
MockSessionServiceGetErrorResult,
MockSessionServiceGetNullSuccessResult,
Expand All @@ -13,12 +12,12 @@ import { MockJWTBuilder } from "../testUtils/mockJwtBuilder";
import { buildRequest } from "../testUtils/mockRequest";
import {
mockGovukSigninJourneyId,
mockInertEventService,
mockSendMessageToSqsFailure,
mockSendMessageToSqsSuccess,
mockSessionId,
mockSuccessfulEventService,
mockWriteGenericEventSuccessResult,
NOW_IN_MILLISECONDS,
NOW_IN_SECONDS,
} from "../testUtils/unitTestData";
import { errorResult } from "../utils/result";
import { lambdaHandlerConstructor } from "./asyncActiveSessionHandler";
import { IAsyncActiveSessionDependencies } from "./handlerDependencies";
import {
Expand All @@ -31,6 +30,7 @@ import {
MockTokenServiceServerError,
MockTokenServiceSuccess,
} from "./mocks";
import { AppStartEvent } from "../common/audit/types";

const env = {
ENCRYPTION_KEY_ARN: "mockEncryptionKeyArn",
Expand Down Expand Up @@ -59,11 +59,17 @@ describe("Async Active Session", () => {
jweDecrypter: () => new MockJweDecrypterSuccess(),
tokenService: () => new MockTokenServiceSuccess(),
sessionService: () => new MockSessionServiceGetSuccessResult(),
eventService: () => new MockEventWriterSuccess(),
sendMessageToSqs: mockSendMessageToSqsSuccess,
};
context = buildLambdaContext();
consoleInfoSpy = jest.spyOn(console, "info");
consoleErrorSpy = jest.spyOn(console, "error");
jest.useFakeTimers();
jest.setSystemTime(NOW_IN_MILLISECONDS);
});

afterEach(() => {
jest.useRealTimers();
});

describe("On every invocation", () => {
Expand All @@ -73,7 +79,14 @@ describe("Async Active Session", () => {
beforeEach(async () => {
result = await lambdaHandlerConstructor(
dependencies,
{ ...validRequest, ...{ headers: { "User-Agent": androidUserAgent } } },
{
...validRequest,
...{
headers: {
"User-Agent": androidUserAgent,
},
},
},
context,
);
});
Expand Down Expand Up @@ -480,26 +493,20 @@ describe("Async Active Session", () => {
describe("Given an active session is found", () => {
beforeEach(async () => {
const request = buildRequest({
headers: { Authorization: `Bearer ${jwtBuilder.getEncodedJwt()}` },
headers: {
Authorization: `Bearer ${jwtBuilder.getEncodedJwt()}`,
"Txma-Audit-Encoded": "mockTxmaAuditEncoded",
},
});
dependencies.sessionService = () =>
new MockSessionServiceGetSuccessResult();
dependencies.eventService = () => mockSuccessfulEventService;

result = await lambdaHandlerConstructor(dependencies, request, context);
});

describe("Given DCMAW_ASYNC_CRI_APP_START event fails to write to TxMA", () => {
beforeEach(async () => {
dependencies.eventService = () => ({
...mockInertEventService,
writeGenericEvent: jest.fn().mockResolvedValue(
errorResult({
errorMessage: "mockError",
}),
),
});

dependencies.sendMessageToSqs = mockSendMessageToSqsFailure;
result = await lambdaHandlerConstructor(
dependencies,
validRequest,
Expand Down Expand Up @@ -532,18 +539,29 @@ describe("Async Active Session", () => {
});

it("Writes DCMAW_ASYNC_CRI_APP_START event to TxMA", () => {
expect(mockWriteGenericEventSuccessResult).toHaveBeenCalledWith({
eventName: "DCMAW_ASYNC_CRI_APP_START",
sub: "mockSub",
sessionId: mockSessionId,
govukSigninJourneyId: mockGovukSigninJourneyId,
getNowInMilliseconds: Date.now,
componentId: "https://mockIssuer.com/",
ipAddress: "1.1.1.1",
txmaAuditEncoded: undefined,
redirect_uri: "https://mockUrl.com/redirect",
suspected_fraud_signal: undefined,
});
const appStart: AppStartEvent = {
user: {
user_id: "mockSub",
session_id: mockSessionId,
govuk_signin_journey_id: mockGovukSigninJourneyId,
ip_address: "1.1.1.1",
},
timestamp: NOW_IN_SECONDS,
event_timestamp_ms: NOW_IN_MILLISECONDS,
event_name: "DCMAW_ASYNC_CRI_APP_START",
component_id: "https://mockIssuer.com/",
extensions: { redirect_uri: "https://mockUrl.com/redirect" },
restricted: {
device_information: { encoded: "mockTxmaAuditEncoded" },
},
};
expect(mockSendMessageToSqsSuccess).toHaveBeenCalledNthWithSqsMessage(
1,
{
sqsArn: "mockTxmaSqs",
expectedMessage: appStart,
},
);
});

it("Logs COMPLETED with persistent identifiers", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
getAuditData,
} from "../common/request/getAuditData/getAuditData";
import { Session } from "../services/session/types";
import { IEventService } from "../services/events/types";
import { ISendMessageToSqs } from "../adapters/aws/sqs/types";
import { getAppStartEvent } from "./getAppStartEvent";

export async function lambdaHandlerConstructor(
dependencies: IAsyncActiveSessionDependencies,
Expand All @@ -35,7 +36,6 @@ export async function lambdaHandlerConstructor(
return serverErrorResponse;
}
const config = configResult.value;
const eventService = dependencies.eventService(config.TXMA_SQS);

const authorizationHeaderResult = new RequestService().getAuthorizationHeader(
event.headers["Authorization"] ?? event.headers["authorization"],
Expand Down Expand Up @@ -103,12 +103,16 @@ export async function lambdaHandlerConstructor(
govukSigninJourneyId: session.govukSigninJourneyId,
});

return await handleOkResponse(eventService, {
session,
auditData: getAuditData(event),
sub,
issuer: config.ISSUER,
});
return await handleOkResponse(
config.TXMA_SQS,
dependencies.sendMessageToSqs,
{
session,
auditData: getAuditData(event),
sub,
issuer: config.ISSUER,
},
);
}

export const lambdaHandler = lambdaHandlerConstructor.bind(null, dependencies);
Expand Down Expand Up @@ -161,29 +165,25 @@ interface HandleOkResponseData {
}

async function handleOkResponse(
eventService: IEventService,
sqsArn: string,
sendMessageToSqs: ISendMessageToSqs,
{ session, auditData, sub, issuer }: HandleOkResponseData,
) {
const { ipAddress, txmaAuditEncoded } = auditData;
const { govukSigninJourneyId, redirectUri, sessionId, state } = session;
const eventName = "DCMAW_ASYNC_CRI_APP_START";

const writeEventResult = await eventService.writeGenericEvent({
eventName,
sub,
const appStartEvent = getAppStartEvent({
sessionId,
govukSigninJourneyId,
getNowInMilliseconds: Date.now,
componentId: issuer,
ipAddress,
txmaAuditEncoded,
redirect_uri: redirectUri,
suspected_fraud_signal: undefined,
redirectUri,
userId: sub,
issuer,
...auditData,
});
if (writeEventResult.isError) {
const sendMessageToSqsResult = await sendMessageToSqs(sqsArn, appStartEvent);
if (sendMessageToSqsResult.isError) {
logger.error(LogMessage.ERROR_WRITING_AUDIT_EVENT, {
data: {
auditEventName: eventName,
auditEventName: appStartEvent.event_name,
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { AppStartEvent } from "../common/audit/types";

export const getAppStartEvent = (inputs: {
sessionId: string;
userId: string;
issuer: string;
govukSigninJourneyId: string;
ipAddress: string;
redirectUri?: string;
txmaAuditEncoded?: string;
}): AppStartEvent => {
const timestampInMillis = Date.now();
return {
event_name: "DCMAW_ASYNC_CRI_APP_START",
user: {
user_id: inputs.userId,
session_id: inputs.sessionId,
ip_address: inputs.ipAddress,
govuk_signin_journey_id: inputs.govukSigninJourneyId,
},
event_timestamp_ms: timestampInMillis,
timestamp: Math.floor(timestampInMillis / 1000),
component_id: inputs.issuer,
...(inputs.redirectUri && {
extensions: { redirect_uri: inputs.redirectUri },
}),
...(inputs.txmaAuditEncoded && {
restricted: { device_information: { encoded: inputs.txmaAuditEncoded } },
}),
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ import {
import { TokenService } from "./TokenService/TokenService";
import { IDecryptJwe, JweDecrypter } from "./jwe/jweDecrypter";
import { ITokenService } from "./TokenService/types";
import { IEventService } from "../services/events/types";
import { EventService } from "../services/events/eventService";
import { ISendMessageToSqs } from "../adapters/aws/sqs/types";
import { sendMessageToSqs } from "../adapters/aws/sqs/sendMessageToSqs";

export interface IAsyncActiveSessionDependencies {
env: NodeJS.ProcessEnv;
jweDecrypter: (encryptionKeyId: string) => IDecryptJwe;
tokenService: () => ITokenService;
sessionService: (tableName: string) => ISessionService;
eventService: (sqsQueue: string) => IEventService;
sendMessageToSqs: ISendMessageToSqs;
}

export const dependencies: IAsyncActiveSessionDependencies = {
env: process.env,
jweDecrypter: (encryptionKeyId: string) => new JweDecrypter(encryptionKeyId),
tokenService: () => new TokenService(),
sessionService: (tableName: string) => new SessionService(tableName),
eventService: (sqsQueue) => new EventService(sqsQueue),
sendMessageToSqs,
};
Loading
Loading