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

Releases/0.7.0 #757

Merged
merged 9 commits into from
Sep 18, 2024
2 changes: 1 addition & 1 deletion docker-compose/.env
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ MSSQL_DB_PORT=1433

#sqlite | postgres | mssql
DATABASE_ENGINE=postgres
VERSION_TAG=0.6.0
VERSION_TAG=0.7.0
COMPOSE_PROFILES=$DATABASE_ENGINE
8 changes: 4 additions & 4 deletions docker-compose/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ services:
depends_on:
- caddy
environment:
NEXT_PUBLIC_VC_REPO: "http://localhost:$VC_REPO_PORT"
NEXT_PUBLIC_ISSUER: "http://localhost:$ISSUER_API_PORT"
NEXT_PUBLIC_VERIFIER: "http://localhost:$VERIFIER_API_PORT"
NEXT_PUBLIC_WALLET: "http://localhost:$DEMO_WALLET_FRONTEND_PORT"
NEXT_PUBLIC_VC_REPO: "http://host.docker.internal:$VC_REPO_PORT"
NEXT_PUBLIC_ISSUER: "http://host.docker.internal:$ISSUER_API_PORT"
NEXT_PUBLIC_VERIFIER: "http://host.docker.internal:$VERIFIER_API_PORT"
NEXT_PUBLIC_WALLET: "http://host.docker.internal:$DEMO_WALLET_FRONTEND_PORT"
PORT: $WEB_PORTAL_PORT

vc-repo:
Expand Down
298 changes: 176 additions & 122 deletions waltid-applications/waltid-web-wallet/libs/composables/presentation.ts
Original file line number Diff line number Diff line change
@@ -1,137 +1,191 @@
import { encodeDisclosure } from "./disclosures.ts";
import { useCurrentWallet } from "./accountWallet.ts";
import { ref, type Ref, computed, watch } from "vue";
import { computed, type Ref, ref, watch } from "vue";
import { decodeRequest } from "./siop-requests.ts";
import { navigateTo } from "nuxt/app";

export async function usePresentation(query: any) {
const index = ref(0);
const failed = ref(false);
const failMessage = ref("Unknown error occurred.");

const currentWallet = useCurrentWallet();
async function resolvePresentationRequest(request: string) {
try {
const response = await $fetch(`/wallet-api/wallet/${currentWallet.value}/exchange/resolvePresentationRequest`, {
method: "POST",
body: request
});
return response;
} catch (e) {
failed.value = true;
throw e;
}
const index = ref(0);
const failed = ref(false);
const failMessage = ref("Unknown error occurred.");

const currentWallet = useCurrentWallet();

async function resolvePresentationRequest(request: string) {
try {
const response = await $fetch(
`/wallet-api/wallet/${currentWallet.value}/exchange/resolvePresentationRequest`,
{
method: "POST",
body: request,
},
);
return response;
} catch (e) {
failed.value = true;
throw e;
}

const request = await resolvePresentationRequest(decodeRequest(query.request as string));
const presentationUrl = new URL(request as string);
const presentationParams = presentationUrl.searchParams;

const verifierHost = new URL(presentationParams.get("response_uri") ?? presentationParams.get("redirect_uri") ?? "").host;
const presentationDefinition = presentationParams.get("presentation_definition") as string;
const matchedCredentials = await $fetch<Array<{ id: string, document: string, parsedDocument?: string, disclosures?: string }>>(`/wallet-api/wallet/${currentWallet.value}/exchange/matchCredentialsForPresentationDefinition`, {
method: "POST",
body: presentationDefinition
});

const selection = ref<{ [key: string]: boolean; }>({});
const selectedCredentialIds = computed(() => Object.entries(selection.value).filter((it) => it[1]).map((it) => it[0]))
for (let credential of matchedCredentials) {
selection.value[credential.id] = true
}

const request = await resolvePresentationRequest(
decodeRequest(query.request as string),
);
const presentationUrl = new URL(request as string);
const presentationParams = presentationUrl.searchParams;

const verifierHost = new URL(
presentationParams.get("response_uri") ??
presentationParams.get("redirect_uri") ??
"",
).host;
const presentationDefinition = presentationParams.get(
"presentation_definition",
) as string;
const matchedCredentials = await $fetch<
Array<{
id: string;
document: string;
parsedDocument?: string;
disclosures?: string;
}>
>(
`/wallet-api/wallet/${currentWallet.value}/exchange/matchCredentialsForPresentationDefinition`,
{
method: "POST",
body: presentationDefinition,
},
);

const selection = ref<{ [key: string]: boolean }>({});
const selectedCredentialIds = computed(() =>
Object.entries(selection.value)
.filter((it) => it[1])
.map((it) => it[0]),
);
for (let credential of matchedCredentials) {
selection.value[credential.id] = true;
}

const disclosures: Ref<{ [key: string]: any[] }> = ref({});
const encodedDisclosures = computed(() => {
if (JSON.stringify(disclosures.value) === "{}") return null;

const m: { [key: string]: any[] } = {};
for (let credId in disclosures.value) {
if (m[credId] === undefined) {
m[credId] = [];
}

for (let disclosure of disclosures.value[credId]) {
m[credId].push(encodeDisclosure(disclosure));
}
}

const disclosures: Ref<{ [key: string]: any[] }> = ref({});
const encodedDisclosures = computed(() => {
if (JSON.stringify(disclosures.value) === "{}") return null

const m: { [key: string]: any[] } = {}
for (let credId in disclosures.value) {
if (m[credId] === undefined) {
m[credId] = []
}

for (let disclosure of disclosures.value[credId]) {
m[credId].push(encodeDisclosure(disclosure))
}
}

return m
})

function addDisclosure(credentialId: string, disclosure: string) {
if (disclosures.value[credentialId] === undefined) {
disclosures.value[credentialId] = []
}
disclosures.value[credentialId].push(disclosure)
}
return m;
});

function removeDisclosure(credentialId: string, disclosure: string) {
disclosures.value[credentialId] = disclosures.value[credentialId].filter((elem) => elem[0] != disclosure[0])
function addDisclosure(credentialId: string, disclosure: string) {
if (disclosures.value[credentialId] === undefined) {
disclosures.value[credentialId] = [];
}

const disclosureModalState: Ref<{ [key: string]: boolean }> = ref({});
disclosures.value[credentialId].push(disclosure);
}

function removeDisclosure(credentialId: string, disclosure: string) {
disclosures.value[credentialId] = disclosures.value[credentialId].filter(
(elem) => elem[0] != disclosure[0],
);
}

const disclosureModalState: Ref<{ [key: string]: boolean }> = ref({});

for (let credential of matchedCredentials) {
disclosureModalState.value[credential.id] = false;
}
if (matchedCredentials[index.value]) {
disclosureModalState.value[matchedCredentials[index.value].id] = true;
}

function toggleDisclosure(credentialId: string) {
disclosureModalState.value[credentialId] =
!disclosureModalState.value[credentialId];
}

// Disable all disclosure modals when switching between credentials and set the current one to active
watch(index, () => {
for (let credential of matchedCredentials) {
disclosureModalState.value[credential.id] = false
disclosureModalState.value[credential.id] = false;
}
disclosureModalState.value[matchedCredentials[index.value].id] = true

function toggleDisclosure(credentialId: string) {
disclosureModalState.value[credentialId] = !disclosureModalState.value[credentialId]
}
// Disable all disclosure modals when switching between credentials and set the current one to active
watch(index, () => {
for (let credential of matchedCredentials) {
disclosureModalState.value[credential.id] = false
}
disclosureModalState.value[matchedCredentials[index.value].id] = true
})

async function acceptPresentation() {
const req = {
//did: String, // todo: choose DID of shared credential // for now wallet-api chooses the default wallet did
presentationRequest: request,
selectedCredentials: selectedCredentialIds.value,
disclosures: encodedDisclosures.value
};

const response = await fetch(`/wallet-api/wallet/${currentWallet.value}/exchange/usePresentationRequest`, {
method: "POST",
body: JSON.stringify(req),
redirect: "manual",
headers: {
"Content-Type": "application/json"
}
disclosureModalState.value[matchedCredentials[index.value].id] = true;
});

async function acceptPresentation() {
const req = {
//did: String, // todo: choose DID of shared credential // for now wallet-api chooses the default wallet did
presentationRequest: request,
selectedCredentials: selectedCredentialIds.value,
disclosures: encodedDisclosures.value,
};

const response = await fetch(
`/wallet-api/wallet/${currentWallet.value}/exchange/usePresentationRequest`,
{
method: "POST",
body: JSON.stringify(req),
redirect: "manual",
headers: {
"Content-Type": "application/json",
},
},
);

if (response.ok) {
const parsedResponse: { redirectUri: string } = await response.json();
if (parsedResponse.redirectUri) {
navigateTo(parsedResponse.redirectUri, {
external: true,
});

if (response.ok) {
const parsedResponse: { redirectUri: string } = await response.json();
if (parsedResponse.redirectUri) {
navigateTo(parsedResponse.redirectUri, {
external: true
});
} else {
window.alert("Presentation successful, no redirect URL supplied.");
navigateTo(`/wallet/${currentWallet.value}`, {
external: true
});
}
} else {
failed.value = true;
const error: { message: string; redirectUri: string | null | undefined, errorMessage: string } = await response.json();
failMessage.value = error.message;

console.log("Error response: " + JSON.stringify(error));
window.alert(error.errorMessage)

if (error.redirectUri != null) {
navigateTo(error.redirectUri as string, {
external: true
});
}
}
}

return {
currentWallet, verifierHost, presentationDefinition, matchedCredentials, selectedCredentialIds, disclosures, selection, index, disclosureModalState, toggleDisclosure, addDisclosure, removeDisclosure, acceptPresentation, failed, failMessage
} else {
window.alert("Presentation successful, no redirect URL supplied.");
navigateTo(`/wallet/${currentWallet.value}`, {
external: true,
});
}
} else {
failed.value = true;
const error: {
message: string;
redirectUri: string | null | undefined;
errorMessage: string;
} = await response.json();
failMessage.value = error.message;

console.log("Error response: " + JSON.stringify(error));
window.alert(error.errorMessage);

if (error.redirectUri != null) {
navigateTo(error.redirectUri as string, {
external: true,
});
}
}
}
}

return {
currentWallet,
verifierHost,
presentationDefinition,
matchedCredentials,
selectedCredentialIds,
disclosures,
selection,
index,
disclosureModalState,
toggleDisclosure,
addDisclosure,
removeDisclosure,
acceptPresentation,
failed,
failMessage,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import id.walt.credentials.verification.models.PresentationResultEntry
import id.walt.credentials.verification.models.PresentationVerificationResponse
import id.walt.credentials.verification.policies.JwtSignaturePolicy
import id.walt.crypto.utils.JwsUtils.decodeJws
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
Expand All @@ -22,6 +23,8 @@ import kotlin.time.measureTime
@JsExport
object Verifier {

private val log = KotlinLogging.logger { }

private fun JsonObject.getW3CType() = (this["type"] ?: this["vc"]?.jsonObject?.get("type") ?: this["vp"]?.jsonObject?.get("type")
?: throw IllegalArgumentException("No `type` supplied: $this")).let {
when (it) {
Expand Down
Loading
Loading