Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
nmattia committed Dec 4, 2023
1 parent 283b4c1 commit da32d45
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 38 deletions.
5 changes: 4 additions & 1 deletion demos/test-app/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,9 @@ function handleFlowReady(evnt: MessageEvent) {
const opts = latestOpts;

if (opts === undefined) {
return showError("Didn't work");
return showError(
"Unexpected: received OK from IDP but this test app is not ready"
);
}

const principal = principalEl.innerText;
Expand Down Expand Up @@ -414,6 +416,7 @@ function handleFlowReady(evnt: MessageEvent) {

function handleFlowFinished(evnt: MessageEvent) {
try {
// Make the presentation presentable
const verifiablePresentation = evnt.data?.result?.verifiablePresentation;
if (verifiablePresentation === undefined) {
return showError("No presentation");
Expand Down
93 changes: 56 additions & 37 deletions src/frontend/src/flows/verifiableCredentials/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ import { VcVerifiablePresentation, vcProtocol } from "./postMessageInterface";
import { VcIssuer } from "./vcIssuer";

// The "verifiable credentials" approval flow
// TODO: make sure 'abortedCredentials' error page is never triggered from
// within withLoader
export const vcFlow = async ({
connection,
}: {
Expand Down Expand Up @@ -59,11 +57,18 @@ export const vcFlow = async ({
// Use the issuer canister ID provided by the RP, or look it up
// from the issuer origin if no canister ID was provided.
// NOTE: we do _not_ check the origin's canister ID if a canister ID was provided explicitly.
const issuerCanisterId =
issuerCanisterId_?.toText() ??
(await lookupCanister({
origin: issuerOrigin,
}));
let issuerCanisterId = issuerCanisterId_?.toText();

if (isNullish(issuerCanisterId)) {
const lookedUp = await withLoader(() =>
lookupCanister({ origin: issuerOrigin })
);
if (lookedUp === "not_found") {
return abortedCredentials({ reason: "no_canister_id" });
}

issuerCanisterId = lookedUp.ok;
}

const vcIssuer = new VcIssuer(issuerCanisterId);
// XXX: We don't check that the language matches the user's language. We need
Expand Down Expand Up @@ -130,12 +135,14 @@ export const vcFlow = async ({

// Grab the credentials from the issuer
const credentials = await withLoader(async () => {
const pAlias = await pAliasPending;
const pAliasRes = await pAliasPending;

if (pAlias === "aborted") {
return "aborted";
if ("err" in pAliasRes) {
return { err: pAliasRes.err };
}

const pAlias = pAliasRes.ok;

const issuedCredential = await issueCredential({
vcIssuer,
issuerOrigin,
Expand All @@ -144,14 +151,14 @@ export const vcFlow = async ({
authenticatedConnection,
});

if (issuedCredential === "aborted") {
return "aborted";
if ("err" in issuedCredential) {
return { err: issuedCredential.err };
}
return [issuedCredential, pAlias] as const;
return [issuedCredential.ok, pAlias] as const;
});

if (credentials === "aborted") {
return "aborted";
if ("err" in credentials) {
return abortedCredentials({ reason: credentials.err });
}

const [issuedCredential, pAlias] = credentials;
Expand Down Expand Up @@ -182,7 +189,7 @@ const lookupCanister = async ({
origin,
}: {
origin: string;
}): Promise<string> => {
}): Promise<{ ok: string } | "not_found"> => {
const response = await fetch(
origin,
// fail on redirects
Expand All @@ -196,7 +203,7 @@ const lookupCanister = async ({

if (response.status !== 200) {
console.error("Bad response when looking for canister ID", response.status);
return abortedCredentials({ reason: "no_canister_id" });
return "not_found";
}

const HEADER_NAME = "x-ic-canister-id";
Expand All @@ -207,10 +214,10 @@ const lookupCanister = async ({
`Canister ID header '${HEADER_NAME}' was not set on origin ${origin}`
);

return abortedCredentials({ reason: "no_canister_id" });
return "not_found";
}

return canisterId;
return { ok: canisterId };
};

// Prepare & get aliases
Expand All @@ -224,10 +231,12 @@ const getAliasCredentials = async ({
authenticatedConnection: AuthenticatedConnection;
}): Promise<
| {
rpAliasCredential: SignedIdAlias;
issuerAliasCredential: SignedIdAlias;
ok: {
rpAliasCredential: SignedIdAlias;
issuerAliasCredential: SignedIdAlias;
};
}
| "aborted"
| { err: "internal_error" | "auth_failed_ii" }
> => {
const preparedIdAlias = await authenticatedConnection.prepareIdAlias({
issuerOrigin,
Expand All @@ -236,11 +245,13 @@ const getAliasCredentials = async ({

if ("error" in preparedIdAlias) {
if (preparedIdAlias.error === "internal_error") {
return abortedCredentials({ reason: "internal_error" });
console.error("Could not prepare ID alias");
return { err: "internal_error" };
}

preparedIdAlias.error satisfies "authentication_failed";
return abortedCredentials({ reason: "auth_failed_ii" });
console.error("Could not prepare ID alias: authentication failed");
return { err: "auth_failed_ii" };
}

const result = await authenticatedConnection.getIdAlias({
Expand All @@ -251,19 +262,21 @@ const getAliasCredentials = async ({

if ("error" in result) {
if (result.error === "internal_error") {
return abortedCredentials({ reason: "internal_error" });
console.error("Could not get ID alias");
return { err: "internal_error" };
}

result.error satisfies "authentication_failed";
return abortedCredentials({ reason: "auth_failed_ii" });
console.error("Could not get ID alias: authentication failed");
return { err: "auth_failed_ii" };
}

const {
rp_id_alias_credential: rpAliasCredential,
issuer_id_alias_credential: issuerAliasCredential,
} = result;

return { rpAliasCredential, issuerAliasCredential };
return { ok: { rpAliasCredential, issuerAliasCredential } };
};

// Contact the issuer to issue the credentials
Expand All @@ -279,16 +292,21 @@ const issueCredential = async ({
issuerAliasCredential: SignedIdAlias;
credentialSpec: CredentialSpec;
authenticatedConnection: AuthenticatedConnection;
}): Promise<IssuedCredentialData | "aborted"> => {
const issuerIdentity = await authenticateForIssuer({
}): Promise<
| { ok: IssuedCredentialData }
| { err: "auth_failed_issuer" | "issuer_api_error" }
> => {
const issuerIdentityRes = await authenticateForIssuer({
authenticatedConnection,
issuerOrigin,
});

if (issuerIdentity === "aborted") {
return abortedCredentials({ reason: "auth_failed_issuer" });
if ("err" in issuerIdentityRes) {
return { err: issuerIdentityRes.err };
}

const issuerIdentity = issuerIdentityRes.ok;

const args = {
signedIdAlias: issuerAliasCredential,
credentialSpec,
Expand All @@ -298,7 +316,7 @@ const issueCredential = async ({
const preparedCredential = await vcIssuer.prepareCredential(args);

if (preparedCredential === "error") {
return abortedCredentials({ reason: "issuer_api_error" });
return { err: "issuer_api_error" };
}

const issuedCredential = await vcIssuer.getCredential({
Expand All @@ -308,10 +326,10 @@ const issueCredential = async ({
});

if (issuedCredential === "error") {
return abortedCredentials({ reason: "issuer_api_error" });
return { err: "issuer_api_error" };
}

return issuedCredential;
return { ok: issuedCredential };
};

// Perform an authentication (delegation, etc) of the user to the issuer
Expand All @@ -322,7 +340,7 @@ const authenticateForIssuer = async ({
}: {
authenticatedConnection: AuthenticatedConnection;
issuerOrigin: string;
}): Promise<DelegationIdentity | "aborted"> => {
}): Promise<{ ok: DelegationIdentity } | { err: "auth_failed_issuer" }> => {
// This is basically a copy-paste of what we have in the authentication flow

const tempIdentity: ECDSAKeyIdentity = await ECDSAKeyIdentity.generate({
Expand All @@ -336,7 +354,8 @@ const authenticateForIssuer = async ({
});

if ("error" in delegation) {
return abortedCredentials({ reason: "internal_error" });
console.error("Could not fetch delegation");
return { err: "auth_failed_issuer" };
}

const [userKey, parsed_signed_delegation] = delegation;
Expand All @@ -352,7 +371,7 @@ const authenticateForIssuer = async ({
[degs],
Uint8Array.from(userKey)
);
return DelegationIdentity.fromDelegation(tempIdentity, delegations);
return { ok: DelegationIdentity.fromDelegation(tempIdentity, delegations) };
};

// Create the final presentation (to be then returned to the RP)
Expand Down

0 comments on commit da32d45

Please sign in to comment.