Skip to content

Commit

Permalink
Setup backend Web2 skeleton
Browse files Browse the repository at this point in the history
This PR setups the backend API skeleton and changes the front-end to
interact with that API.
  • Loading branch information
Frederik Rothenberger committed Dec 14, 2023
1 parent 5d79761 commit 394f34f
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 16 deletions.
11 changes: 10 additions & 1 deletion demos/web2_integration/astro.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { defineConfig } from "astro/config";

// https://astro.build/config
export default defineConfig({});
export default defineConfig({
output: "server",
vite: {
server: {
watch: {
ignored: [".idea"], // don't hot reload on IDE file changes
},
},
},
});
49 changes: 49 additions & 0 deletions demos/web2_integration/src/auth_overrides.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { AuthClient, IdbStorage } from "@dfinity/auth-client";
import { AnonymousIdentity, SignIdentity } from "@dfinity/agent";
import type {
DerEncodedPublicKey,
PublicKey,
Signature,
} from "@dfinity/agent/lib/esm/auth";

export class ChallengeAuthClient extends AuthClient {
public constructor(challenge: string) {
super(
new AnonymousIdentity(),
new ChallengeIdentity(challenge),
null,
new IdbStorage(),
undefined,
undefined,
);
}
}

export class ChallengeIdentity extends SignIdentity {
private readonly challenge: ArrayBuffer;
public constructor(challenge: string) {
super();
this.challenge = base64ToBytes(challenge);
}

getPublicKey(): PublicKey {
return new Challenge(this.challenge);
}

sign(_blob: ArrayBuffer): Promise<Signature> {
throw new Error("not implemented");
}
}

export class Challenge {
constructor(public challenge: ArrayBuffer) {}

public toDer(): DerEncodedPublicKey {
return this.challenge;
}
}

function base64ToBytes(base64: string): Uint8Array {
const binString = atob(base64);
return Uint8Array.from(binString, (m) => m.codePointAt(0)!);
}
12 changes: 12 additions & 0 deletions demos/web2_integration/src/pages/api/attribute/verify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { APIRoute } from "astro";

export const POST: APIRoute = async ({ request }) => {
if (request.headers.get("Content-Type") === "application/json") {
const body = await request.json();
console.log(`submitted verifiable presentation: ${JSON.stringify(body)}`);
return new Response(undefined, {
status: 200,
});
}
return new Response(null, { status: 400 });
};
14 changes: 14 additions & 0 deletions demos/web2_integration/src/pages/api/challenge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { APIRoute } from "astro";
import crypto from "crypto";

export const GET: APIRoute = () => {
console.log("challenge requested");
return new Response(
JSON.stringify({
challenge: crypto.randomBytes(20).toString("base64"),
}),
{
status: 200,
},
);
};
12 changes: 12 additions & 0 deletions demos/web2_integration/src/pages/api/challenge/verify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { APIRoute } from "astro";

export const POST: APIRoute = async ({ request }) => {
if (request.headers.get("Content-Type") === "application/json") {
const body = await request.json();
console.log(`submitted delegation chain: ${JSON.stringify(body)}`);
return new Response(undefined, {
status: 200,
});
}
return new Response(null, { status: 400 });
};
56 changes: 41 additions & 15 deletions demos/web2_integration/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,35 @@ import Layout from "../layouts/Layout.astro";

<script>
import { html } from "lit-html";
import { AuthClient } from "@dfinity/auth-client";
import { nonNullish, isNullish } from "@dfinity/utils";
import { asyncReplace } from "lit-html/directives/async-replace.js";
import { createRef, ref } from "lit-html/directives/ref.js";
import { decodeJwt } from "jose";
import { Chan, renderPage, withRef } from "../utils";
import { ChallengeAuthClient } from "../auth_overrides";
import { DelegationIdentity } from "@dfinity/identity";

const latestPres = new Chan<string | undefined>(undefined);
const ii = new Chan<
{ win: Window; origin: string; flowId: number } | undefined
>(undefined);
const evntHandler = (evnt: MessageEvent) => {
const evntHandler = async (evnt: MessageEvent) => {
console.log(
`Got:
${JSON.stringify(evnt.data, (_key, value) =>
typeof value === "bigint" ? value.toString() : value,
)}`,
);
const credentialSpecs = {
employee: {
credentialType: "VerifiedEmployee",
arguments: { employerName: "DFINITY Foundation" },
},
grad: {
credentialType: "UniversityDegreeCredential",
arguments: { institutionName: "DFINITY College of Engineering" },
},
};

if (evnt.data?.method === "vc-flow-ready" && nonNullish(ii.latest)) {
const principal = withRef(principalInput, (principalInput) => {
Expand Down Expand Up @@ -54,17 +65,6 @@ import Layout from "../layouts/Layout.astro";
return;
}

const credentialSpecs = {
employee: {
credentialType: "VerifiedEmployee",
arguments: { employerName: "DFINITY Foundation" },
},
grad: {
credentialType: "UniversityDegreeCredential",
arguments: { institutionName: "DFINITY College of Engineering" },
},
};

let message = {
id: ii.latest.flowId,
jsonrpc: "2.0",
Expand All @@ -84,6 +84,17 @@ import Layout from "../layouts/Layout.astro";

const verifiablePresentation = evnt.data?.result?.verifiablePresentation;
if (nonNullish(verifiablePresentation)) {
await fetch("/api/attribute/verify", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
verifiablePresentation: verifiablePresentation,
credentialSpec: credentialSpecs[credentialType.latest],
}),
});
const ver = decodeJwt(verifiablePresentation) as any;
const creds = ver.vp.verifiableCredential;

Expand Down Expand Up @@ -124,19 +135,34 @@ import Layout from "../layouts/Layout.astro";
class="c-button"
data-action="authenticate"
@click=${async () => {
const authClient = await AuthClient.create();
let challengeResponse = await fetch("/api/challenge");
let { challenge }: { challenge: string } =
await challengeResponse.json();
console.log(`Retrieved challenge: ${challenge}`);
const authClient = new ChallengeAuthClient(challenge);

const iiUrl = withRef(urlInput, (i) => i.value);

const loginOptions: Parameters<typeof authClient.login>[0] = {
identityProvider: iiUrl,
onSuccess: () => {
onSuccess: async () => {
withRef(principalInput, (principalInput) => {
principalInput.innerText = authClient
.getIdentity()
.getPrincipal()
.toString();
});
let delegation = (
authClient.getIdentity() as DelegationIdentity
).getDelegation();
await fetch("/api/challenge/verify", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(delegation.toJSON()),
});
},
onError: (e) => {
alert(e ?? "Unknown error");
Expand Down

0 comments on commit 394f34f

Please sign in to comment.