diff --git a/iam/Dockerfile b/iam/Dockerfile index eb982c78f0..a59286a2fd 100644 --- a/iam/Dockerfile +++ b/iam/Dockerfile @@ -9,4 +9,4 @@ RUN lerna bootstrap --ignore app EXPOSE 80 443 -CMD [ "yarn", "run", "start:iam" ] +CMD [ "yarn", "run", "prod:start:iam" ] diff --git a/iam/__tests__/index.test.ts b/iam/__tests__/index.test.ts index ab78b723ae..30701b868f 100644 --- a/iam/__tests__/index.test.ts +++ b/iam/__tests__/index.test.ts @@ -7,6 +7,8 @@ import { app, config } from "../src/index"; // ---- Types import { ErrorResponseBody, ValidResponseBody } from "@dpopp/types"; +import * as identityMock from "@dpopp/identity/dist/commonjs"; + describe("POST /challenge", function () { it("handles valid challenge requests", async () => { // as each signature is unique, each request results in unique output @@ -181,4 +183,38 @@ describe("POST /verify", function () { expect((response.body as ErrorResponseBody).error).toEqual("Unable to verify proofs"); }); + + it("handles exception if verify credential throws", async () => { + jest.spyOn(identityMock, "verifyCredential").mockRejectedValue("Verify Credential Error"); + + // challenge received from the challenge endpoint + const challenge = { + issuer: config.issuer, + credentialSubject: { + id: "did:ethr:0xNotAnEthereumAddress#challenge-Simple", + address: "0xNotAnEthereumAddress", + challenge: "123456789ABDEFGHIJKLMNOPQRSTUVWXYZ", + }, + }; + // payload containing a signature of the challenge in the challenge credential + const payload = { + type: "Simple", + address: "0x0", + proofs: { + valid: "false", + username: "test", + signature: "pass", + }, + }; + + // create a req against the express app + const response = await request(app) + .post("/api/v0.0.0/verify") + .send({ challenge, payload }) + .set("Accept", "application/json") + .expect(400) + .expect("Content-Type", /json/); + + expect((response.body as ErrorResponseBody).error).toEqual("Unable to verify payload"); + }); }); diff --git a/iam/package.json b/iam/package.json index 981aae176f..7d11f8c024 100644 --- a/iam/package.json +++ b/iam/package.json @@ -11,6 +11,7 @@ "build": "tsc", "prestart": "yarn run build", "start": "node .", + "prod:start": "tsc && node .", "test": "jest --verbose", "prettier": "prettier --write .", "lint": "tsc --noEmit && eslint --ext .ts,.js,.tsx ." diff --git a/iam/src/index.ts b/iam/src/index.ts index 57d8592fe9..e5ac7050d9 100644 --- a/iam/src/index.ts +++ b/iam/src/index.ts @@ -132,65 +132,70 @@ app.post("/api/v0.0.0/verify", (req: Request, res: Response): void => { const payload = requestBody.payload; // Check the challenge and the payload is valid before issueing a credential from a registered provider - return void verifyCredential(DIDKit, challenge).then(async (verified) => { - if (verified && issuer === challenge.issuer) { - // pull the address and checksum so that its stored in a predicatable format - const address = utils.getAddress( - utils.verifyMessage(challenge.credentialSubject.challenge, payload.proofs.signature) - ); - // ensure the only address we save is that of the signer - payload.address = address; - // the signer should be the address outlined in the challenge credential - rebuild the id to check for a full match - const isSigner = challenge.credentialSubject.id === `did:ethr:${address}#challenge-${payload.type}`; - // type is required because we need it to select the correct Identity Provider - if (isSigner && payload && payload.type) { - // each provider will apply buisness logic to the payload inorder to set the `valid` bool on the returned VerifiedPayload - return providers - .verify(payload) - .then((verifiedPayload) => { - // check if the request is valid against the selected Identity Provider - if (verifiedPayload && verifiedPayload?.valid === true) { - // recreate the record to ensure the minimun number of leafs are present to produce a valid merkleTree - const record: ProofRecord = { - // type and address will always be known and can be obtained from the resultant credential - type: payload.type, - address: payload.address, - // version is defined by entry point - version: "0.0.0", - // extend/overwrite with record returned from the provider - ...(verifiedPayload?.record || {}), - }; - - // generate a VC for the given payload - return issueMerkleCredential(DIDKit, key, record) - .then(({ credential }) => { - return res.json({ - record, - credential, - } as CredentialResponseBody); - }) - .catch((error) => { - if (error) { - // return error msg indicating a failure producing VC - return errorRes(res, "Unable to produce a verifiable credential"); - } - }); - } else { - // return error message if an error is present - return errorRes( - res, - (verifiedPayload.error && verifiedPayload.error.join(", ")) || "Unable to verify proofs" - ); - } - }) - .catch(() => { - // error response - return void errorRes(res, "Unable to verify with provider"); - }); + return void verifyCredential(DIDKit, challenge) + .then(async (verified) => { + if (verified && issuer === challenge.issuer) { + // pull the address and checksum so that its stored in a predicatable format + const address = utils.getAddress( + utils.verifyMessage(challenge.credentialSubject.challenge, payload.proofs.signature) + ); + // ensure the only address we save is that of the signer + payload.address = address; + // the signer should be the address outlined in the challenge credential - rebuild the id to check for a full match + const isSigner = challenge.credentialSubject.id === `did:ethr:${address}#challenge-${payload.type}`; + // type is required because we need it to select the correct Identity Provider + if (isSigner && payload && payload.type) { + // each provider will apply buisness logic to the payload inorder to set the `valid` bool on the returned VerifiedPayload + return providers + .verify(payload) + .then((verifiedPayload) => { + // check if the request is valid against the selected Identity Provider + if (verifiedPayload && verifiedPayload?.valid === true) { + // recreate the record to ensure the minimun number of leafs are present to produce a valid merkleTree + const record: ProofRecord = { + // type and address will always be known and can be obtained from the resultant credential + type: payload.type, + address: payload.address, + // version is defined by entry point + version: "0.0.0", + // extend/overwrite with record returned from the provider + ...(verifiedPayload?.record || {}), + }; + + // generate a VC for the given payload + return issueMerkleCredential(DIDKit, key, record) + .then(({ credential }) => { + return res.json({ + record, + credential, + } as CredentialResponseBody); + }) + .catch((error) => { + if (error) { + // return error msg indicating a failure producing VC + return errorRes(res, "Unable to produce a verifiable credential"); + } + }); + } else { + // return error message if an error is present + return errorRes( + res, + (verifiedPayload.error && verifiedPayload.error.join(", ")) || "Unable to verify proofs" + ); + } + }) + .catch(() => { + // error response + return void errorRes(res, "Unable to verify with provider"); + }); + } } - } - // error response - return void errorRes(res, "Unable to verify payload"); - }); + // error response + return void errorRes(res, "Unable to verify payload"); + }) + .catch((errorReason) => { + console.error("Error verifying credential: ", errorReason); + return void errorRes(res, "Unable to verify payload"); + }); }); diff --git a/infra/review/index.ts b/infra/review/index.ts index 25083ff2a0..6c268d8548 100644 --- a/infra/review/index.ts +++ b/infra/review/index.ts @@ -207,7 +207,11 @@ const httpListener = alb.createListener("web-listener", { }); // Target group with the port of the Docker image -const target = alb.createTargetGroup("web-target", { vpc, port: 80 }); +const target = alb.createTargetGroup("web-target", { + vpc, + port: 80, + healthCheck: { path: "/health", unhealthyThreshold: 5 }, +}); // Listen to traffic on port 443 & route it through the target group const httpsListener = target.createListener("web-listener", { diff --git a/package.json b/package.json index 00b23a60b9..06c2fec180 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "start": "concurrently --kill-others \"yarn start:iam\" \"yarn start:app\"", "start:iam": "yarn workspace @dpopp/iam start", "start:app": "yarn workspace @dpopp/app start", + "prod:start:iam": "yarn workspace @dpopp/iam prod:start", "app": "yarn workspace @dpopp/app", "schemas": "yarn workspace @dpopp/schemas", "iam": "yarn workspace @dpopp/iam",