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 da32d45 commit 82d9ea1
Showing 1 changed file with 123 additions and 117 deletions.
240 changes: 123 additions & 117 deletions src/frontend/src/flows/verifiableCredentials/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,140 +46,146 @@ export const vcFlow = async ({
},

/* How the credentials are actually verified */
verifyCredentials: async ({
request: {
credentialSubject: givenP_RP,
issuer: { origin: issuerOrigin, canisterId: issuerCanisterId_ },
credentialSpec,
},
rpOrigin,
}) => {
// 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.
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;
}
verifyCredentials: (args) => verifyCredentials({ connection, ...args }),
});

const vcIssuer = new VcIssuer(issuerCanisterId);
// XXX: We don't check that the language matches the user's language. We need
// to figure what to do UX-wise first.
const consentInfo = await vcIssuer.getConsentMessage({ credentialSpec });
if (result === "orphan") {
await showMessage({
message: "No credentials data provided! Wrong URL?",
});
}

if (consentInfo === "error") {
return abortedCredentials({ reason: "auth_failed_issuer" });
}
return await new Promise((_) => {
/* halt forever */
});
};

// Ask user to confirm the verification of credentials
const allowed = await allowCredentials({
relyingOrigin: rpOrigin,
providerOrigin: issuerOrigin,
consentMessage: consentInfo.consent_message,
userNumber: undefined,
});
if (allowed.tag === "canceled") {
return "aborted";
}
allowed.tag satisfies "allowed";
type VerifyCredentials = Parameters<typeof vcProtocol>[0]["verifyCredentials"];
type VerifyCredentialsArgs = Parameters<VerifyCredentials>[0];

const userNumber = allowed.userNumber;
const verifyCredentials = async ({
connection,
request: {
credentialSubject: givenP_RP,
issuer: { origin: issuerOrigin, canisterId: issuerCanisterId_ },
credentialSpec,
},
rpOrigin,
}: { connection: Connection } & VerifyCredentialsArgs) => {
// 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.
let issuerCanisterId = issuerCanisterId_?.toText();

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

// For the rest of the flow we need to be authenticated, so authenticate
// XXX: this simply breaks for PIN identities
const authResult = await withLoader(() =>
handleLogin({
login: () => connection.login(userNumber),
})
);
issuerCanisterId = lookedUp.ok;
}

if (authResult.tag !== "ok") {
return abortedCredentials({ reason: "auth_failed_ii" });
}
const authenticatedConnection = authResult.connection;

// Compute the user's principal on the RP and ensure it matches what the RP sent us
const computedP_RP = await withLoader(() =>
authenticatedConnection.getPrincipal({
origin: rpOrigin,
})
);
if (computedP_RP.compareTo(givenP_RP) !== "eq") {
console.error(
[
"bad principals for user number/origin",
userNumber,
rpOrigin,
computedP_RP.toString(),
givenP_RP.toString(),
].join(", ")
);
return abortedCredentials({ reason: "bad_principal_rp" });
}
const vcIssuer = new VcIssuer(issuerCanisterId);
// XXX: We don't check that the language matches the user's language. We need
// to figure what to do UX-wise first.
const consentInfo = await vcIssuer.getConsentMessage({ credentialSpec });

// Ask II to generate the aliases
if (consentInfo === "error") {
return abortedCredentials({ reason: "auth_failed_issuer" });
}

const pAliasPending = getAliasCredentials({
rpOrigin,
issuerOrigin,
authenticatedConnection,
});

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

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

const pAlias = pAliasRes.ok;

const issuedCredential = await issueCredential({
vcIssuer,
issuerOrigin,
issuerAliasCredential: pAlias.issuerAliasCredential,
credentialSpec,
authenticatedConnection,
});
// Ask user to confirm the verification of credentials
const allowed = await allowCredentials({
relyingOrigin: rpOrigin,
providerOrigin: issuerOrigin,
consentMessage: consentInfo.consent_message,
userNumber: undefined,
});
if (allowed.tag === "canceled") {
return "aborted";
}
allowed.tag satisfies "allowed";

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

if ("err" in credentials) {
return abortedCredentials({ reason: credentials.err });
}
// For the rest of the flow we need to be authenticated, so authenticate
// XXX: this simply breaks for PIN identities
const authResult = await withLoader(() =>
handleLogin({
login: () => connection.login(userNumber),
})
);

const [issuedCredential, pAlias] = credentials;
if (authResult.tag !== "ok") {
return abortedCredentials({ reason: "auth_failed_ii" });
}
const authenticatedConnection = authResult.connection;

// Create the presentation and return it to the RP
return createPresentation({
issuerCanisterId,
rpAliasCredential: pAlias.rpAliasCredential,
issuedCredential,
});
},
// Compute the user's principal on the RP and ensure it matches what the RP sent us
const computedP_RP = await withLoader(() =>
authenticatedConnection.getPrincipal({
origin: rpOrigin,
})
);
if (computedP_RP.compareTo(givenP_RP) !== "eq") {
console.error(
[
"bad principals for user number/origin",
userNumber,
rpOrigin,
computedP_RP.toString(),
givenP_RP.toString(),
].join(", ")
);
return abortedCredentials({ reason: "bad_principal_rp" });
}

// Ask II to generate the aliases

const pAliasPending = getAliasCredentials({
rpOrigin,
issuerOrigin,
authenticatedConnection,
});

if (result === "orphan") {
await showMessage({
message: "No credentials data provided! Wrong URL?",
// Grab the credentials from the issuer
const credentials = await withLoader(async () => {
const pAliasRes = await pAliasPending;

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

const pAlias = pAliasRes.ok;

const issuedCredential = await issueCredential({
vcIssuer,
issuerOrigin,
issuerAliasCredential: pAlias.issuerAliasCredential,
credentialSpec,
authenticatedConnection,
});

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

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

return await new Promise((_) => {
/* halt forever */
const [issuedCredential, pAlias] = credentials;

// Create the presentation and return it to the RP
return createPresentation({
issuerCanisterId,
rpAliasCredential: pAlias.rpAliasCredential,
issuedCredential,
});
};

Expand Down

0 comments on commit 82d9ea1

Please sign in to comment.