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
5 changes: 4 additions & 1 deletion scripts/generate-api-models.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ IO_BACKEND_VERSION=v17.5.2
IO_SERVICES_METADATA_VERSION=1.0.93
# Session manager version
IO_SESSION_MANAGER_VERSION=1.8.0
# Send Functions
IO_SEND_FUNC=1.5.5

declare -a noParams=(
"./generated/definitions/backend https://raw.githubusercontent.com/pagopa/io-backend/$IO_BACKEND_VERSION/api_public.yaml"
Expand All @@ -31,7 +33,8 @@ declare -a noStrict=(
)

declare -a noStrictRequestTypesRespondeDecoders=(
"./generated/definitions/pn/aar https://raw.githubusercontent.com/pagopa/io-messages/send-func@1.4.1/apps/send-func/openapi/aar-notification.yaml"
"./generated/definitions/pn/aar https://raw.githubusercontent.com/pagopa/io-messages/send-func@$IO_SEND_FUNC/apps/send-func/openapi/aar-notification.yaml"
"./generated/definitions/pn/lollipopLambda https://raw.githubusercontent.com/pagopa/io-messages/send-func@$IO_SEND_FUNC/apps/send-func/openapi/lollipop-integration-check.yaml"
"./generated/definitions/pn https://raw.githubusercontent.com/pagopa/io-backend/$IO_BACKEND_VERSION/api_pn.yaml"
"./generated/definitions/trial_system https://raw.githubusercontent.com/pagopa/io-backend/$IO_BACKEND_VERSION/api_trial_system.yaml"
"./generated/definitions/fims_history https://raw.githubusercontent.com/pagopa/io-backend/$IO_BACKEND_VERSION/api_io_fims.yaml"
Expand Down
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ const defaultConfig: IoDevServerConfig = {
send: {
isServiceUpsertRateLimited: false,
aarQRCodeUrl: "https://cittadini.notifichedigitali.it/io",
lollipopLambdaResponseCode: 200,
mandateTimeToLiveSeconds: 120,
paymentDocumentExpirationTimeSeconds: 10,
paymentDocumentGenerationTimeSeconds: 3,
Expand Down
56 changes: 52 additions & 4 deletions src/features/messages/routers/ioSendRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from "../services/ioSendService";
import MessagesService from "../services/messagesService";
import { bodyToString } from "../utils";
import { generateLollipopLambdaGetPath } from "../../pn/routers/lollipopLambda";

export const ioSendRouter = Router();

Expand Down Expand Up @@ -163,21 +164,68 @@ addHandler(
() => Math.random() * 500
);

addHandler(
ioSendRouter,
"get",
"/api/com/v1/send/lollipop-check/test",
lollipopMiddleware(async (req, res) => {
const sendLollipopLambdaGetUrl = `${serverUrl}${generateLollipopLambdaGetPath()}`;
commonHandleIsTestQueryParam(req);
const sendLollipopLambdaGetFetch = () =>
fetch(sendLollipopLambdaGetUrl, {
method: "get",
headers: generateRequestHeaders(req.headers, "application/json", true)
});
await fetchSENDDataAndForwardResponse(
sendLollipopLambdaGetFetch,
"lollipop-test",
res
);
}),
() => Math.random() * 500
);

addHandler(
ioSendRouter,
"post",
"/api/com/v1/send/lollipop-check/test",
lollipopMiddleware(async (req, res) => {
const sendLollipopLambdaPostUrl = `${serverUrl}${generateLollipopLambdaGetPath()}`;
const sendLollipopLambdaPostBodyEither = bodyToString(req.body);
if (handleLeftEitherIfNeeded(sendLollipopLambdaPostBodyEither, res)) {
return;
}
commonHandleIsTestQueryParam(req);
const sendLambdaLollipopPostFetch = () =>
fetch(sendLollipopLambdaPostUrl, {
method: "post",
headers: generateRequestHeaders(req.headers, "application/json", true),
body: sendLollipopLambdaPostBodyEither.right
});
await fetchSENDDataAndForwardResponse(
sendLambdaLollipopPostFetch,
"lollipop-test",
res
);
}),
() => Math.random() * 500
);

const fetchSENDDataAndForwardResponse = async (
fetchFunction: () => Promise<globalThis.Response>,
endpointName: string,
res: Response
) => {
try {
const sendQResponse = await fetchFunction();
const sendResponse = await fetchFunction();

const contentType = sendQResponse.headers.get("content-type");
const responseBodyBuffer = await sendQResponse.arrayBuffer();
const contentType = sendResponse.headers.get("content-type");
const responseBodyBuffer = await sendResponse.arrayBuffer();
const body = Buffer.from(responseBodyBuffer);
if (contentType) {
res.setHeader("Content-Type", contentType);
}
res.status(sendQResponse.status).send(body);
res.status(sendResponse.status).send(body);
} catch (e) {
const errorMessage = unknownToString(e);
res
Expand Down
11 changes: 7 additions & 4 deletions src/features/messages/services/ioSendService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,19 @@ export const isTestOrUndefinedFromQuery = (

export const generateRequestHeaders = (
headers: IncomingHttpHeaders,
contentType: string = "application/json"
contentType: string = "application/json",
excludeTaxId: boolean = false
): Record<string, string> => ({
...MessagesService.lollipopClientHeadersFromHeaders(headers),
...MessagesService.generateFakeLollipopServerHeaders(
ioDevServerConfig.profile.attrs.fiscal_code
),
...MessagesService.sendAPIKeyHeader(),
...MessagesService.sendTaxIdHeader(
ioDevServerConfig.profile.attrs.fiscal_code
),
...(excludeTaxId
? {}
: MessagesService.sendTaxIdHeader(
ioDevServerConfig.profile.attrs.fiscal_code
)),
"Content-Type": contentTypeHeaderFromHeaders(headers, contentType),
...ioSourceHeaderFromRequestHeaders(headers)
});
Expand Down
69 changes: 69 additions & 0 deletions src/features/pn/routers/lollipopLambda.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Request, Response, Router } from "express";
import { addHandler } from "../../../payloads/response";
import { initializationMiddleware } from "../middlewares/initializationMiddleware";
import { authenticationMiddleware } from "../middlewares/authenticationMiddleware";
import { ioDevServerConfig } from "../../../config";
import { handleLeftEitherIfNeeded } from "../../../utils/error";
import { checkAndValidateLollipopHeaders } from "../services/lollipopService";
import { lollipopLambdaResponseFromBodyAndConfig } from "../services/lollipopLambdaService";

const lollipopLambdaPath = "/aws/send/lollipop-test";

export const generateLollipopLambdaGetPath = () => lollipopLambdaPath;
export const generateLollipopLambdaPostPath = () => lollipopLambdaPath;

export const sendLollipopLambdaRouter = Router();

addHandler(
sendLollipopLambdaRouter,
"get",
lollipopLambdaPath,
// Middleware have to be used like this (instead of directly giving the middleware to the router via use)
// because supertest (when testing) calls every middleware upon test initialization, even if it not in a
// router directly called by the test, thus making every test fail due to the authentication middleware
authenticationMiddleware(
initializationMiddleware((req, res) =>
commonLollipopLambdaHandling("GET", req, res)
)
)
);

addHandler(
sendLollipopLambdaRouter,
"post",
lollipopLambdaPath,
// Middleware have to be used like this (instead of directly giving the middleware to the router via use)
// because supertest (when testing) calls every middleware upon test initialization, even if it not in a
// router directly called by the test, thus making every test fail due to the authentication middleware
authenticationMiddleware(
initializationMiddleware((req, res) =>
commonLollipopLambdaHandling("POST", req, res)
)
)
);

const commonLollipopLambdaHandling = (
requestVerb: "GET" | "POST",
req: Request,
res: Response
) => {
const sendConfig = ioDevServerConfig.send;
if (!sendConfig.skipLollipopVerification) {
const lollipopHeadersEither = checkAndValidateLollipopHeaders(req.headers);
if (handleLeftEitherIfNeeded(lollipopHeadersEither, res)) {
return;
}
}

const requestBody = requestVerb === "POST" ? req.body : undefined;
const lollipopLambdaGetResponseEither =
lollipopLambdaResponseFromBodyAndConfig(
requestVerb,
requestBody,
sendConfig
);
if (handleLeftEitherIfNeeded(lollipopLambdaGetResponseEither, res)) {
return;
}
res.status(200).json(lollipopLambdaGetResponseEither.right);
};
102 changes: 102 additions & 0 deletions src/features/pn/services/lollipopLambdaService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Either, isLeft, left } from "fp-ts/lib/Either";
import { readableReportSimplified } from "@pagopa/ts-commons/lib/reporters";
import { SendConfig } from "../types/sendConfig";
import { ExpressFailure } from "../../../utils/expressDTO";
import { SuccessResponse } from "../../../../generated/definitions/pn/lollipopLambda/SuccessResponse";
import { getProblemJson } from "../../../payloads/error";
import { ErrorResponse } from "../../../../generated/definitions/pn/lollipopLambda/ErrorResponse";

export const lollipopLambdaResponseFromBodyAndConfig = (
requestVerb: "GET" | "POST",
requestBody: unknown,
sendConfig: SendConfig
): Either<ExpressFailure, SuccessResponse> => {
const timestamp = new Date();
const statusCode = sendConfig.lollipopLambdaResponseCode ?? 200;
if (statusCode === 401) {
return left({
httpStatusCode: 401,
reason: {}
});
} else if (statusCode === 400 || statusCode === 403 || statusCode === 500) {
const errorResponseEither = ErrorResponse.decode({
success: false,
timestamp,
error: {
message: `Server was configured to send a ${statusCode} status code`,
statusCode
}
});
if (isLeft(errorResponseEither)) {
return left({
httpStatusCode: 500,
reason: getProblemJson(
500,
"Conversion to ErrorResponse failed",
`Unable to convert input data to the ErrorResponse data structure (${readableReportSimplified(
errorResponseEither.left
)})`
)
});
}
return left({
httpStatusCode: statusCode,
reason: errorResponseEither.right
});
}
const bodyStringOrUndefined = unknownBodyToStringOrUndefined(requestBody);
const bodyLengthOrUndefined = stringOrUndefinedToByteLengthOrUndefined(
bodyStringOrUndefined
);
const successResponseEither = SuccessResponse.decode({
success: true,
timestamp,
data: {
message: "Server was configured to send a 200 status code",
timestamp,
request: {
method: requestVerb,
path: "lollipop-test",
hasBody: !!bodyLengthOrUndefined,
bodyLength: bodyLengthOrUndefined ?? 0
}
}
});
if (isLeft(successResponseEither)) {
return left({
httpStatusCode: 500,
reason: getProblemJson(
500,
"Conversion to SuccessResponse failed",
`Unable to convert input data to the SuccessResponse data structure (${readableReportSimplified(
successResponseEither.left
)})`
)
});
}
return successResponseEither;
};

const stringOrUndefinedToByteLengthOrUndefined = (
input: string | undefined
): number | undefined => (input ? Buffer.byteLength(input, "utf8") : undefined);

const unknownBodyToStringOrUndefined = (body: unknown): string | undefined => {
if (typeof body === "string") {
return body;
} else if (typeof body === "object") {
try {
return JSON.stringify(body);
} catch {
return undefined;
}
} else if (
typeof body === "number" ||
(typeof BigInt !== "undefined" && typeof body === "bigint") ||
typeof body === "boolean" ||
typeof body === "symbol"
) {
return String(body);
}
return undefined;
};
7 changes: 7 additions & 0 deletions src/features/pn/types/sendConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ export const SendConfig = t.intersection([
}),
t.partial({
aarQRCodeUrl: t.string,
lollipopLambdaResponseCode: t.union([
t.literal(200),
t.literal(400),
t.literal(401),
t.literal(403),
t.literal(500)
]),
mandateTimeToLiveSeconds: t.number,
paymentDocumentExpirationTimeSeconds: t.number,
paymentDocumentGenerationTimeSeconds: t.number,
Expand Down
2 changes: 2 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { sendServiceRouter } from "./features/pn/routers/serviceRouter";
import { sendMandatesRouter } from "./features/pn/routers/mandatesRouter";
import { ioSendRouter } from "./features/messages/routers/ioSendRouter";
import { cdcRouter } from "./features/cdc/routers";
import { sendLollipopLambdaRouter } from "./features/pn/routers/lollipopLambda";

// create express server
const app: Application = express();
Expand Down Expand Up @@ -87,6 +88,7 @@ app.use(fastLoginMiddleware);
fciRouter,
sendAARRouter,
sendDocumentsRouter,
sendLollipopLambdaRouter,
sendMandatesRouter,
sendNotificationsRouter,
sendPrevalidatedUrisRouter,
Expand Down
Loading