Skip to content

Commit

Permalink
fix(iam): add error handling to verify endpoint when verification fails
Browse files Browse the repository at this point in the history
- also add prod:start:iam script to start iam server without preliminary
  lint or test checks
- update iam Dockerfile to run prod:start:iam script instead of normal
  start script
- configure health check path and thresholds for target group

[passportxyz#13]
  • Loading branch information
shavinac committed May 2, 2022
1 parent 6b57200 commit 462e8fa
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 61 deletions.
2 changes: 1 addition & 1 deletion iam/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ RUN lerna bootstrap --ignore app

EXPOSE 80 443

CMD [ "yarn", "run", "start:iam" ]
CMD [ "yarn", "run", "prod:start:iam" ]
36 changes: 36 additions & 0 deletions iam/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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");
});
});
1 change: 1 addition & 0 deletions iam/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 ."
Expand Down
123 changes: 64 additions & 59 deletions iam/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});
6 changes: 5 additions & 1 deletion infra/review/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 462e8fa

Please sign in to comment.