Skip to content

Commit

Permalink
feat(Pollux): Implementing Pollux.parseVerifiableCredential with tests (
Browse files Browse the repository at this point in the history
  • Loading branch information
curtis-h authored Mar 7, 2023
1 parent f6eabef commit da05e65
Show file tree
Hide file tree
Showing 3 changed files with 342 additions and 5 deletions.
4 changes: 2 additions & 2 deletions domain/models/VerifiableCredential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ export class VerifiableCredentialTypeContainer {
constructor(
public readonly id: string,
public readonly type: string
) {}
) { }
}

export interface VerifiableCredential {
readonly id?: string
readonly credentialType: CredentialType
readonly context: Array<string>
readonly type: Array<string>
readonly credentialSceham?: VerifiableCredentialTypeContainer
readonly credentialSchema?: VerifiableCredentialTypeContainer
readonly credentialSubject: string
readonly credentialStatus?: VerifiableCredentialTypeContainer
readonly refreshService: VerifiableCredentialTypeContainer
Expand Down
121 changes: 118 additions & 3 deletions pollux/Pollux.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import Castor from "../domain/buildingBlocks/Castor";
import { default as PolluxInterface } from "../domain/buildingBlocks/Pollux";
import { VerifiableCredential } from "../domain/models/VerifiableCredential";
import { DID } from '../domain'
import { InvalidCredentialError, InvalidJWTString } from '../domain/models/errors/Pollux'
import { CredentialType, VerifiableCredential, VerifiableCredentialTypeContainer } from '../domain/models/VerifiableCredential'

export default class Pollux implements PolluxInterface {
private castor: Castor;
Expand All @@ -11,6 +12,120 @@ export default class Pollux implements PolluxInterface {
}

parseVerifiableCredential(jwtString: string): VerifiableCredential {
throw new Error("Not implemented");
const parts = jwtString.split(".");

if (parts.length != 3) throw new InvalidJWTString();

const decoded = Buffer.from(parts[1], "base64").toString();
const verifiableCredential = this.parseCredential(decoded);
const type = this.parseCredentialType(verifiableCredential);

switch (type) {
case CredentialType.JWT:
return this.parseJWTCredential(verifiableCredential, jwtString);

case CredentialType.W3C:
return this.parseW3CCredential(verifiableCredential);
}

throw new InvalidCredentialError();
}

private parseCredential(value: string): object {
try {
const verifiableCredential = JSON.parse(value);

if (typeof verifiableCredential === "object" && verifiableCredential !== null) {
return verifiableCredential;
}
}
catch (e) {
throw new InvalidCredentialError();
}

throw new InvalidCredentialError();
}

private parseCredentialType(credential: object): CredentialType {
if ("type" in credential && Array.isArray(credential.type)) {
const type = credential.type[0];

if (type === CredentialType.JWT)
return CredentialType.JWT;

if (type === CredentialType.W3C)
return CredentialType.W3C;
}

return CredentialType.Unknown;
}

private parseJWTCredential(val: any, jwtString: string): VerifiableCredential {
return {
id: jwtString,
aud: val.aud,
context: val.context,
credentialSubject: val.credentialSubject,
credentialType: CredentialType.JWT,
evidence: this.parseVerifiableCredentialTypeContainer(val.evidence),
expirationDate: val.expirationDate,
issuanceDate: val.issuanceDate,
issuer: this.parseDID(val.issuer),
refreshService: this.parseVerifiableCredentialTypeContainer(val.refreshService),
termsOfUse: this.parseVerifiableCredentialTypeContainer(val.termsOfUse),
type: val.type,
validFrom: this.parseVerifiableCredentialTypeContainer(val.validFrom),
validUntil: this.parseVerifiableCredentialTypeContainer(val.validUntil),
credentialSchema: this.parseVerifiableCredentialTypeContainer(val.credentialSchema),
credentialStatus: this.parseVerifiableCredentialTypeContainer(val.credentialStatus),
proof: val.proof
};
}

private parseW3CCredential(val: any) {
return {
id: val.id,
aud: val.aud,
context: val.context,
credentialSubject: val.credentialSubject,
credentialType: CredentialType.W3C,
evidence: this.parseVerifiableCredentialTypeContainer(val.evidence),
expirationDate: val.expirationDate,
issuanceDate: val.issuanceDate,
issuer: this.parseDID(val.issuer),
refreshService: this.parseVerifiableCredentialTypeContainer(val.refreshService),
termsOfUse: this.parseVerifiableCredentialTypeContainer(val.termsOfUse),
type: val.type,
validFrom: this.parseVerifiableCredentialTypeContainer(val.validFrom),
validUntil: this.parseVerifiableCredentialTypeContainer(val.validUntil),
credentialSchema: this.parseVerifiableCredentialTypeContainer(val.credentialSchema),
credentialStatus: this.parseVerifiableCredentialTypeContainer(val.credentialStatus),
proof: val.proof
};
}

private parseDID(value: unknown) {
if (
typeof value === "object" && value !== null
&& "schema" in value && typeof value.schema === "string"
&& "method" in value && typeof value.method === "string"
&& "methodId" in value && typeof value.methodId === "string"
) {
return new DID(value.schema, value.method, value.methodId);
}

throw new InvalidCredentialError();
}

private parseVerifiableCredentialTypeContainer(value: unknown) {
if (
typeof value === "object" && value !== null
&& "id" in value && typeof value.id === "string"
&& "type" in value && typeof value.type === "string"
) {
return new VerifiableCredentialTypeContainer(value.id, value.type);
}

throw new InvalidCredentialError();
}
}
222 changes: 222 additions & 0 deletions tests/pollux/Pollux.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import { expect } from "chai";
import { DID } from "../../domain";
import Castor from "../../castor/Castor";
import Apollo from "../../domain/buildingBlocks/Apollo";
import { InvalidCredentialError, InvalidJWTString } from "../../domain/models/errors/Pollux";
import { CredentialType, VerifiableCredential, VerifiableCredentialTypeContainer } from "../../domain/models/VerifiableCredential";
import Pollux from "../../pollux/Pollux";

const jwtParts = [
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwidHlwZSI6Imp3dCJ9",
"18bn-r7uRWAG4FCFBjxemKvFYPCAoJTOHaHthuXh5nM"
];
const jwtString = jwtParts.join(".");

describe("Pollux", () => {
let pollux: Pollux;

beforeEach(() => {
const apollo = {} as Apollo;
const castor = new Castor(apollo);
pollux = new Pollux(castor);
});

describe("parseVerifiableCredential", () => {
describe("Invalid JWT string", () => {
[
"",
`${jwtParts[0]}`,
`${jwtParts[0]}.${jwtParts[1]}`,
].forEach(value => {
it(`should error when too few parts [${value.split(".").length}]`, () => {
expect(() => pollux.parseVerifiableCredential(value)).throws(InvalidJWTString);
})
});

[
`${jwtString}.${jwtParts[0]}`,
`${jwtString}.${jwtParts[0]}.${jwtParts[1]}`,
].forEach(value => {
it(`should error when too many parts [${value.split(".").length}]`, () => {
expect(() => pollux.parseVerifiableCredential(value)).throws(InvalidJWTString);
})
});

it("should error when not encoded JSON", () => {
const encoded = Buffer.from("a").toString("base64");
const value = `${jwtParts[0]}.${encoded}.${jwtParts[2]}`;

expect(() => pollux.parseVerifiableCredential(value)).throws();
})
});

const encodeCredential = (cred: object): string => {
const json = JSON.stringify(cred);
const encoded = Buffer.from(json).toString("base64");
const value = `${jwtParts[0]}.${encoded}.${jwtParts[2]}`;
return value;
}

const makeCredentialForType = (type: string[]): string => {
const cred: Pick<VerifiableCredential, "type"> = { type };
return encodeCredential(cred);
}

describe("Invalid Credential", () => {
[
[], // undefined
[""], // empty string
["abc"], // not CredentialType
].forEach((type: string[]) => {
it(`should error with incorrect Credential.type [${type}]`, () => {
const value = makeCredentialForType(type);
expect(() => pollux.parseVerifiableCredential(value)).throws(InvalidCredentialError);
});
});

it(`should error with incorrect case Credential.type [JWT]`, () => {
const value = makeCredentialForType(["JWT"]);
expect(() => pollux.parseVerifiableCredential(value)).throws(InvalidCredentialError);
});

it(`should error with incorrect case Credential.type [W3C]`, () => {
const value = makeCredentialForType(["W3C"]);
expect(() => pollux.parseVerifiableCredential(value)).throws(InvalidCredentialError);
});
});

describe("Valid Credential", () => {
it(`should return JWTVerifiableCredential`, () => {
const cred: VerifiableCredential = {
id: "jwtid",
credentialType: CredentialType.JWT,
type: [CredentialType.JWT],
aud: ["aud"],
context: ["context"],
credentialSubject: "credSubject",
evidence: new VerifiableCredentialTypeContainer("evidenceId", "evidenceType"),
expirationDate: "expDate",
issuanceDate: "issDate",
issuer: new DID(
"did",
"peer",
"2.Ez6LSms555YhFthn1WV8ciDBpZm86hK9tp83WojJUmxPGk1hZ.Vz6MkmdBjMyB4TS5UbbQw54szm8yvMMf1ftGV2sQVYAxaeWhE.SeyJpZCI6Im5ldy1pZCIsInQiOiJkbSIsInMiOiJodHRwczovL21lZGlhdG9yLnJvb3RzaWQuY2xvdWQiLCJhIjpbImRpZGNvbW0vdjIiXX0"
),
refreshService: new VerifiableCredentialTypeContainer("refreshServiceId", "refreshServiceType"),
termsOfUse: new VerifiableCredentialTypeContainer("termsOfUseId", "termsOfUseType"),
validFrom: new VerifiableCredentialTypeContainer("validFromId", "validFromType"),
validUntil: new VerifiableCredentialTypeContainer("validUntilId", "validUntilType"),
credentialSchema: new VerifiableCredentialTypeContainer("credentialSchemaId", "credentialSchemaType"),
credentialStatus: new VerifiableCredentialTypeContainer("credentialStatusId", "credentialStatusType"),
proof: "proof",
};

const encoded = encodeCredential(cred);
const result = pollux.parseVerifiableCredential(encoded);

expect(result).to.not.be.undefined;
expect(result.id).to.equal(encoded);
expect(result.aud).to.eql(cred.aud);
expect(result.context).to.eql(cred.context);
expect(result.credentialSubject).to.equal(cred.credentialSubject);
expect(result.credentialType).to.equal(cred.credentialType);
expect(result.expirationDate).to.equal(cred.expirationDate);
expect(result.issuanceDate).to.equal(cred.issuanceDate);
expect(result.type).to.eql(cred.type);
expect(result.proof).to.equal(cred.proof);

expect(result.issuer).to.be.an.instanceOf(DID);
expect(result.issuer).to.eql(cred.issuer);

expect(result.evidence).to.be.an.instanceOf(VerifiableCredentialTypeContainer);
expect(result.evidence).to.eql(cred.evidence);

expect(result.refreshService).to.be.an.instanceOf(VerifiableCredentialTypeContainer);
expect(result.refreshService).to.eql(cred.refreshService);

expect(result.termsOfUse).to.be.an.instanceOf(VerifiableCredentialTypeContainer);
expect(result.termsOfUse).to.eql(cred.termsOfUse);

expect(result.validFrom).to.be.an.instanceOf(VerifiableCredentialTypeContainer);
expect(result.validFrom).to.eql(cred.validFrom);

expect(result.validUntil).to.be.an.instanceOf(VerifiableCredentialTypeContainer);
expect(result.validUntil).to.eql(cred.validUntil);

expect(result.credentialSchema).to.be.an.instanceOf(VerifiableCredentialTypeContainer);
expect(result.credentialSchema).to.eql(cred.credentialSchema);

expect(result.credentialStatus).to.be.an.instanceOf(VerifiableCredentialTypeContainer);
expect(result.credentialStatus).to.eql(cred.credentialStatus);

});

it(`should return W3CVerifiableCredential`, () => {
const cred: VerifiableCredential = {
id: "w3cid",
credentialType: CredentialType.W3C,
type: [CredentialType.W3C],
aud: ["aud"],
context: ["context"],
credentialSubject: "credSubject",
evidence: new VerifiableCredentialTypeContainer("evidenceId", "evidenceType"),
expirationDate: "expDate",
issuanceDate: "issDate",
issuer: new DID(
"did",
"peer",
"2.Ez6LSms555YhFthn1WV8ciDBpZm86hK9tp83WojJUmxPGk1hZ.Vz6MkmdBjMyB4TS5UbbQw54szm8yvMMf1ftGV2sQVYAxaeWhE.SeyJpZCI6Im5ldy1pZCIsInQiOiJkbSIsInMiOiJodHRwczovL21lZGlhdG9yLnJvb3RzaWQuY2xvdWQiLCJhIjpbImRpZGNvbW0vdjIiXX0"
),
refreshService: new VerifiableCredentialTypeContainer("refreshServiceId", "refreshServiceType"),
termsOfUse: new VerifiableCredentialTypeContainer("termsOfUseId", "termsOfUseType"),
validFrom: new VerifiableCredentialTypeContainer("validFromId", "validFromType"),
validUntil: new VerifiableCredentialTypeContainer("validUntilId", "validUntilType"),
credentialSchema: new VerifiableCredentialTypeContainer("credentialSchemaId", "credentialSchemaType"),
credentialStatus: new VerifiableCredentialTypeContainer("credentialStatusId", "credentialStatusType"),
proof: "proofW3c",
};

const encoded = encodeCredential(cred);
const result = pollux.parseVerifiableCredential(encoded);

expect(result).to.not.be.undefined;
expect(result.id).to.equal(cred.id);
expect(result.aud).to.eql(cred.aud);
expect(result.context).to.eql(cred.context);
expect(result.credentialSubject).to.equal(cred.credentialSubject);
expect(result.credentialType).to.equal(cred.credentialType);
expect(result.expirationDate).to.equal(cred.expirationDate);
expect(result.issuanceDate).to.equal(cred.issuanceDate);
expect(result.type).to.eql(cred.type);
expect(result.proof).to.equal(cred.proof);

expect(result.issuer).to.be.an.instanceOf(DID);
expect(result.issuer).to.eql(cred.issuer);

expect(result.evidence).to.be.an.instanceOf(VerifiableCredentialTypeContainer);
expect(result.evidence).to.eql(cred.evidence);

expect(result.refreshService).to.be.an.instanceOf(VerifiableCredentialTypeContainer);
expect(result.refreshService).to.eql(cred.refreshService);

expect(result.termsOfUse).to.be.an.instanceOf(VerifiableCredentialTypeContainer);
expect(result.termsOfUse).to.eql(cred.termsOfUse);

expect(result.validFrom).to.be.an.instanceOf(VerifiableCredentialTypeContainer);
expect(result.validFrom).to.eql(cred.validFrom);

expect(result.validUntil).to.be.an.instanceOf(VerifiableCredentialTypeContainer);
expect(result.validUntil).to.eql(cred.validUntil);

expect(result.credentialSchema).to.be.an.instanceOf(VerifiableCredentialTypeContainer);
expect(result.credentialSchema).to.eql(cred.credentialSchema);

expect(result.credentialStatus).to.be.an.instanceOf(VerifiableCredentialTypeContainer);
expect(result.credentialStatus).to.eql(cred.credentialStatus);

});
});
});

});

0 comments on commit da05e65

Please sign in to comment.