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

fix: v4 attachments #290

Merged
merged 1 commit into from
May 13, 2024
Merged
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
74 changes: 41 additions & 33 deletions src/4.0/__tests__/documentBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,15 +254,17 @@ describe(`DocumentBuilder`, () => {

test("given svg rendering method, should be added into the document", async () => {
const signed = await new DocumentBuilder({
credentialSubject: { name: "John Doe" },
credentialSubject: {
name: "John Doe",
attachments: [
{
data: "data",
filename: "file",
mimeType: "application/pdf",
},
],
},
name: "Diploma",
attachments: [
{
data: "data",
fileName: "file",
mimeType: "application/pdf",
},
],
})
.svgRenderer({
type: "EMBEDDED",
Expand All @@ -288,15 +290,17 @@ describe(`DocumentBuilder`, () => {

test("given no rendering method, should reflect in the output document", async () => {
const signed = await new DocumentBuilder({
credentialSubject: { name: "John Doe" },
credentialSubject: {
name: "John Doe",
attachments: [
{
data: "data",
filename: "file",
mimeType: "application/pdf",
},
],
},
name: "Diploma",
attachments: [
{
data: "data",
fileName: "file",
mimeType: "application/pdf",
},
],
})
.noRenderer()
.noRevocation()
Expand All @@ -312,15 +316,17 @@ describe(`DocumentBuilder`, () => {

test("given attachment is added, should be added into the document", async () => {
const signed = await new DocumentBuilder({
credentialSubject: { name: "John Doe" },
credentialSubject: {
name: "John Doe",
attachments: [
{
data: "data",
filename: "file",
mimeType: "application/pdf",
},
],
},
name: "Diploma",
attachments: [
{
data: "data",
fileName: "file",
mimeType: "application/pdf",
},
],
})
.embeddedRenderer({
rendererUrl: "https://example.com",
Expand All @@ -334,11 +340,11 @@ describe(`DocumentBuilder`, () => {
})
.wrapAndSign({ signer: SAMPLE_SIGNING_KEYS });

expect(signed.attachments).toMatchInlineSnapshot(`
expect(signed.credentialSubject.attachments).toMatchInlineSnapshot(`
[
{
"data": "data",
"fileName": "file",
"filename": "file",
"mimeType": "application/pdf",
},
]
Expand Down Expand Up @@ -446,14 +452,16 @@ describe(`DocumentBuilder`, () => {
expect(() => {
try {
new DocumentBuilder({
credentialSubject: { name: "John Doe" },
credentialSubject: {
name: "John Doe",
attachments: [
{
data: "data",
fileName: "file",
} as any,
],
},
name: "Diploma",
attachments: [
{
data: "data",
fileName: "file",
} as any,
],
});
} catch (e) {
error = e;
Expand Down
17 changes: 10 additions & 7 deletions src/4.0/__tests__/guard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import { wrapDocument } from "../wrap";

const RAW_DOCUMENT = {
...RAW_DOCUMENT_DID,
attachments: [
{
mimeType: "image/png",
fileName: "aaa",
data: "abcd",
},
],
credentialSubject: {
...RAW_DOCUMENT_DID.credentialSubject,
attachments: [
{
mimeType: "image/png",
filename: "aaa",
data: "abcd",
},
],
},
} satisfies V4Document;

describe("V4.0 guard", () => {
Expand Down
83 changes: 44 additions & 39 deletions src/4.0/__tests__/obfuscate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { RAW_DOCUMENT_DID, SIGNED_WRAPPED_DOCUMENT_DID_OBFUSCATED, WRAPPED_DOCUM
import { hashLeafNode } from "../digest";
import { getObfuscatedData, isObfuscated } from "../../shared/utils";

const makeV4RawDocument = <T extends Pick<V4Document, "credentialSubject" | "attachments">>(props: T) =>
const makeV4RawDocument = <T extends Pick<V4Document, "credentialSubject">>(props: T) =>
({
...RAW_DOCUMENT_DID,
...(props as T),
Expand Down Expand Up @@ -134,27 +134,27 @@ describe("privacy", () => {
});

test("given an entire array of objects to remove, should remove the array itself, then for every item, add each of its key's hash into privacy.obfuscated", async () => {
const PATH_TO_REMOVE = "attachments";
const PATH_TO_REMOVE = "credentialSubject.attachments";
const wrappedDocument = await wrapDocument(
makeV4RawDocument({
credentialSubject: {
arrayOfObject: [
{ foo: "bar", doo: "foo" },
{ foo: "baz", doo: "faz" },
],
attachments: [
{
mimeType: "image/png",
filename: "aaa",
data: "abcd",
},
{
mimeType: "image/png",
filename: "bbb",
data: "abcd",
},
],
},
attachments: [
{
mimeType: "image/png",
fileName: "aaa",
data: "abcd",
},
{
mimeType: "image/png",
fileName: "bbb",
data: "abcd",
},
],
})
);
const obfuscatedDocument = await obfuscateVerifiableCredential(wrappedDocument, PATH_TO_REMOVE);
Expand All @@ -163,12 +163,12 @@ describe("privacy", () => {
expect(verified).toBe(true);

[
"attachments[0].mimeType",
"attachments[0].fileName",
"attachments[0].data",
"attachments[1].mimeType",
"attachments[1].fileName",
"attachments[1].data",
"credentialSubject.attachments[0].mimeType",
"credentialSubject.attachments[0].filename",
"credentialSubject.attachments[0].data",
"credentialSubject.attachments[1].mimeType",
"credentialSubject.attachments[1].filename",
"credentialSubject.attachments[1].data",
].forEach((expectedRemovedField) => {
const value = get(wrappedDocument, expectedRemovedField);
const salt = findSaltByPath(wrappedDocument.proof.salts, expectedRemovedField);
Expand All @@ -180,7 +180,7 @@ describe("privacy", () => {
);
expect(findSaltByPath(obfuscatedDocument.proof.salts, expectedRemovedField)).toBeUndefined();
});
expect(obfuscatedDocument.attachments).toBeUndefined();
expect(obfuscatedDocument.credentialSubject.attachments).toBeUndefined();
expect(obfuscatedDocument.proof.privacy.obfuscated).toHaveLength(6);
});

Expand Down Expand Up @@ -213,28 +213,33 @@ describe("privacy", () => {
{ foo: "bar", doo: "foo" },
{ foo: "baz", doo: "faz" },
],
attachments: [
{
mimeType: "image/png",
filename: "aaa",
data: "abcd",
},
{
mimeType: "image/png",
filename: "bbb",
data: "abcd",
},
{
mimeType: "image/png",
filename: "ccc",
data: "abcd",
},
],
},
attachments: [
{
mimeType: "image/png",
fileName: "aaa",
data: "abcd",
},
{
mimeType: "image/png",
fileName: "bbb",
data: "abcd",
},
{
mimeType: "image/png",
fileName: "ccc",
data: "abcd",
},
],
})
);

expect(() => obfuscateVerifiableCredential(wrappedDocument, ["attachments[0]", "attachments[2]"])).toThrow();
expect(() =>
obfuscateVerifiableCredential(wrappedDocument, [
"credentialSubject.attachments[0]",
"credentialSubject.attachments[2]",
])
).toThrow();
});

test("given a path to remove all elements in an object, should throw", async () => {
Expand Down
4 changes: 2 additions & 2 deletions src/4.0/digest.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { sortBy } from "lodash";
import { keccak256 } from "js-sha3";
import { V4Document, Salt } from "./types";
import { W3cVerifiableCredential, Salt } from "./types";
import { LeafValue, traverseAndFlatten } from "./traverseAndFlatten";
import { hashToBuffer } from "../shared/utils/hashing";

export const digestCredential = (document: V4Document, salts: Salt[], obfuscatedData: string[]) => {
export const digestCredential = (document: W3cVerifiableCredential, salts: Salt[], obfuscatedData: string[]) => {
// find all leaf nodes in the document and hash them
// proof is not part of the digest
const { proof: _, ...documentWithoutProof } = document;
Expand Down
18 changes: 4 additions & 14 deletions src/4.0/documentBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import { ZodError, z } from "zod";

const SingleDocumentProps = z.object({
name: V4Document.shape.name.unwrap(),
credentialSubject: z.record(z.unknown()),
attachments: V4Document.shape.attachments,
credentialSubject: V4Document.shape.credentialSubject,
});

const DocumentProps = z.union([SingleDocumentProps, z.array(SingleDocumentProps)]);
Expand Down Expand Up @@ -84,14 +83,7 @@ type DocumentProps = {
*
* Maps to "credentialSubject"
*/
credentialSubject: Record<string, unknown>;
/**
* Optional attachments that will be rendered out of the box with OpenAttestation's
* Decentralised Renderer Components
*
* Maps to "attachments"
*/
attachments?: V4Document["attachments"];
credentialSubject: z.infer<typeof V4Document.shape.credentialSubject>;
};

type State = {
Expand Down Expand Up @@ -137,7 +129,7 @@ export class DocumentBuilder<Props extends DocumentProps | DocumentProps[]> {
if (!issuer) throw new Error("Issuer is required");
if (Array.isArray(data)) {
const toWrap = data.map(
({ name, credentialSubject, attachments }) =>
({ name, credentialSubject }) =>
({
"@context": [
"https://www.w3.org/ns/credentials/v2",
Expand All @@ -148,7 +140,6 @@ export class DocumentBuilder<Props extends DocumentProps | DocumentProps[]> {
name,
credentialSubject,
...(renderMethod && { renderMethod }),
...(attachments && { attachments }),
...(credentialStatus && { credentialStatus }),
} satisfies V4Document)
);
Expand All @@ -159,7 +150,7 @@ export class DocumentBuilder<Props extends DocumentProps | DocumentProps[]> {
// this should never happen
if (!data) throw new Error("CredentialSubject is required");

const { name, credentialSubject, attachments } = data;
const { name, credentialSubject } = data;
return wrapDocument({
"@context": [
"https://www.w3.org/ns/credentials/v2",
Expand All @@ -170,7 +161,6 @@ export class DocumentBuilder<Props extends DocumentProps | DocumentProps[]> {
name,
credentialSubject,
...(renderMethod && { renderMethod }),
...(attachments && { attachments }),
...(credentialStatus && { credentialStatus }),
}) as unknown as WrappedReturn<Props>;
};
Expand Down
Loading
Loading