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

[IOPID-94] Add first lollipop consumer #233

Merged
merged 10 commits into from
Apr 19, 2023
11 changes: 11 additions & 0 deletions src/persistence/lollipop.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import * as jose from "jose";
import { AssertionRef } from "../../generated/definitions/backend/AssertionRef";
import { DEFAULT_LOLLIPOP_HASH_ALGORITHM } from "../routers/public";

let lollipopAssertionRef: AssertionRef | undefined = undefined;

let lollipopPublicKey: jose.JWK | undefined = undefined;

export function getAssertionRef() {
return lollipopAssertionRef;
}

export function setAssertionRef(key: string | undefined) {
lollipopAssertionRef = `${DEFAULT_LOLLIPOP_HASH_ALGORITHM}-${key}` as AssertionRef;
}

export function getPublicKey() {
return lollipopPublicKey;
}

export function setPublicKey(key: jose.JWK | undefined) {
lollipopPublicKey = key;
}
91 changes: 91 additions & 0 deletions src/routers/features/lollipop/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { pipe } from "fp-ts/lib/function";
import * as O from "fp-ts/lib/Option";
import * as TE from "fp-ts/lib/TaskEither";
import * as T from "fp-ts/lib/Task";
import * as B from "fp-ts/lib/boolean";
import { getProblemJson } from "../../../payloads/error";
import * as jose from "jose";
/**
* this router serves lollipop API
*/
import { Request, Response, Router } from "express";

import { addHandler } from "../../../payloads/response";
import { getAssertionRef, getPublicKey } from "../../../persistence/lollipop";
import { verifySignatureHeader } from "@mattrglobal/http-signatures";
import { signAlgorithmToVerifierMap } from "../../../utils/httpSignature";
import { serverUrl } from "../../../utils/server";

export const lollipopRouter = Router();

export const DEFAULT_LOLLIPOP_HASH_ALGORITHM = "sha256";

const brokenVerifier = (_: jose.JWK) => {
throw new Error("broken verifier");
};

const toRequestOption = (req: Request, publicKey: jose.JWK) => {
const headers = req.headers;
return {
verifier: {
verify:
req.body["message"] === "BROKEN"
? brokenVerifier(publicKey)
: publicKey.kty === "EC"
? signAlgorithmToVerifierMap["ecdsa-p256-sha256"].verify(publicKey)
: signAlgorithmToVerifierMap["rsa-pss-sha256"].verify(publicKey)
},
url: serverUrl,
method: req.method,
httpHeaders:
req.body["message"] === "INVALID"
? { ...headers, "x-pagopa-lollipop-original-method": "xxx" }
: headers,
body: req.body,
verifyExpiry: false
};
};

const toTaskError = (
res: Response,
code: number,
title?: string,
detail?: string
) => T.of(res.status(code).send(getProblemJson(code, title, detail)));

addHandler(lollipopRouter, "post", "/first-lollipop/sign", async (req, res) =>
pipe(
getPublicKey(),
O.fromNullable,
O.foldW(
() => toTaskError(res, 500, "Public key not found"),
publicKey =>
pipe(
TE.tryCatch(
() =>
verifySignatureHeader(toRequestOption(req, publicKey)).unwrapOr({
verified: false
}),
e => e as Error
),
TE.foldW(
e => toTaskError(res, 500, e.message, JSON.stringify(e.stack)),
verificationResult =>
pipe(
verificationResult.verified,
B.fold(
() =>
toTaskError(
res,
400,
"Invalid signature",
JSON.stringify(verificationResult)
),
() => T.of(res.send({ response: getAssertionRef() }))
)
)
)
)
)
)()
);
13 changes: 3 additions & 10 deletions src/routers/public.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { getProblemJson } from "./../payloads/error";
/**
* this router serves all public API (those ones don't need session)
*/
import {
SemverFromFromUserAgentString,
UserAgentSemver,
UserAgentSemverValid
} from "@pagopa/ts-commons/lib/http-user-agent";
import { JwkPublicKey, parseJwkOrError } from "@pagopa/ts-commons/lib/jwk";
import chalk from "chalk";
import { Response, Router } from "express";
Expand All @@ -29,16 +25,12 @@ import { resetBonusVacanze } from "./features/bonus-vacanze";
import { resetCgn } from "./features/cgn";
import { resetProfile } from "./profile";
import { resetWalletV2 } from "./walletsV2";
import { setAssertionRef } from "../persistence/lollipop";
import { setAssertionRef, setPublicKey } from "../persistence/lollipop";

export const publicRouter = Router();

export const DEFAULT_LOLLIPOP_HASH_ALGORITHM = "sha256";
const DEFAULT_HEADER_LOLLIPOP_PUB_KEY = "x-pagopa-lollipop-pub-key";
const ACCEPTED_LOLLIPOP_USER_AGENT = {
clientName: "IO-App",
clientVersion: "2.23.0"
} as UserAgentSemver;

addHandler(publicRouter, "get", "/login", async (req, res) => {
const lollipopPublicKeyHeaderValue = req.get(DEFAULT_HEADER_LOLLIPOP_PUB_KEY);
Expand All @@ -64,6 +56,7 @@ addHandler(publicRouter, "get", "/login", async (req, res) => {
);

setAssertionRef(thumbprint);
setPublicKey(jwkPK.right);

const samlRequest = getSamlRequest(
DEFAULT_LOLLIPOP_HASH_ALGORITHM,
Expand Down
4 changes: 3 additions & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { satispayRouter } from "./routers/walletsV2/methods/satispay";
import { payPalRouter } from "./routers/walletsV3/methods/paypal";
import { delayer } from "./utils/delay_middleware";
import { idpayRouter } from "./routers/features/idpay";
import { lollipopRouter } from "./routers/features/lollipop";
// create express server
const app: Application = express();
// parse body request as json
Expand Down Expand Up @@ -85,7 +86,8 @@ app.use(errorMiddleware);
cdcRouter,
fciRouter,
pnRouter,
idpayRouter
idpayRouter,
lollipopRouter
].forEach(r => app.use(r));

export default app;