You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
412 lines
13 KiB
412 lines
13 KiB
12 months ago
|
<template>
|
||
|
<QuickNav />
|
||
|
<!-- CONTENT -->
|
||
|
<section id="Content" class="p-6 pb-24">
|
||
|
<!-- Breadcrumb -->
|
||
|
<div id="ViewBreadcrumb" class="mb-8">
|
||
|
<h1 class="text-lg text-center font-light relative px-7">
|
||
|
<!-- Back -->
|
||
|
<button
|
||
|
@click="$router.go(-1)"
|
||
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||
|
>
|
||
|
<fa icon="chevron-left" class="fa-fw"></fa>
|
||
|
</button>
|
||
|
Verifiable Claim Details
|
||
|
</h1>
|
||
|
</div>
|
||
|
|
||
|
<!-- Details -->
|
||
|
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
||
|
<div>
|
||
|
<div class="block pb-4 flex gap-4 overflow-hidden">
|
||
|
<div class="overflow-hidden">
|
||
|
<h2 class="text-xl">{{ veriClaim.id }}</h2>
|
||
|
<div class="text-sm">
|
||
|
<div>
|
||
|
{{ veriClaim.claimType }}
|
||
|
</div>
|
||
|
<div>
|
||
|
<fa icon="message" class="fa-fw text-slate-400"></fa>
|
||
|
{{ veriClaim.claim?.description }}
|
||
|
</div>
|
||
|
<div>
|
||
|
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
||
|
{{ veriClaim.issuer }}
|
||
|
</div>
|
||
|
<div>
|
||
|
<fa icon="calendar" class="fa-fw text-slate-400"></fa>
|
||
|
{{ veriClaim.issuedAt?.replace(/T/, " ").replace(/Z/, " UTC") }}
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div>
|
||
|
<h2 class="font-bold text-2xl">Confirmations</h2>
|
||
|
|
||
|
<span v-if="totalConfirmers() === 0">Nobody has confirmed this.</span>
|
||
|
<span v-else-if="totalConfirmers() === 1">
|
||
|
One person has confirmed this.
|
||
|
</span>
|
||
|
<span v-else> {{ totalConfirmers() }} people have confirmed this. </span>
|
||
|
|
||
|
<div v-if="totalConfirmers() > 0">
|
||
|
<div
|
||
|
v-if="
|
||
|
confirmerIdList.length === 0 && confsVisibleToIdList.length === 0
|
||
|
"
|
||
|
>
|
||
|
Nobody that you know confirmed this claim, nor do they have any
|
||
|
confirmers in their network.
|
||
|
</div>
|
||
|
|
||
|
<div
|
||
|
v-if="confirmerIdList.length === 0 && confsVisibleToIdList.length > 0"
|
||
|
>
|
||
|
<!-- Only show if this person has links to confirmers (below). -->
|
||
|
Nobody that you know has issued or confirmed this claim.
|
||
|
</div>
|
||
|
<div v-if="confirmerIdList.length > 0">
|
||
|
The following people have issued or confirmed this claim.
|
||
|
<ul>
|
||
|
<li
|
||
|
v-for="confirmerId in confirmerIdList"
|
||
|
:key="confirmerId"
|
||
|
class="list-disc"
|
||
|
>
|
||
|
<div class="flex gap-4">
|
||
|
<div class="grow overflow-hidden">
|
||
|
<div class="text-sm">
|
||
|
{{ confirmerId }}
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</li>
|
||
|
</ul>
|
||
|
</div>
|
||
|
|
||
|
<!--
|
||
|
Never need to show the following message.
|
||
|
If there is nobody in the confirmerIdList then we'll show the combined "nobody" message.
|
||
|
If there is somebody in the confirmerIdList then that's all they need to show.
|
||
|
-->
|
||
|
<!-- Nobody that you know can see someone who has confirmed this claim. -->
|
||
|
|
||
|
<div v-if="confsVisibleToIdList.length > 0">
|
||
|
The following people can connect you with people who have issued or
|
||
|
confirmed this claim.
|
||
|
<ul>
|
||
|
<li
|
||
|
v-for="confsVisibleTo in confsVisibleToIdList"
|
||
|
:key="confsVisibleTo"
|
||
|
class="list-disc"
|
||
|
>
|
||
|
<div class="flex gap-4">
|
||
|
<div class="grow overflow-hidden">
|
||
|
<div class="text-sm">
|
||
|
{{ confsVisibleTo }}
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</li>
|
||
|
</ul>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div>
|
||
|
<h2 class="font-bold text-2xl mt-8">Claim</h2>
|
||
|
<pre>{{ util.inspect(veriClaim, false, null) }}</pre>
|
||
|
</div>
|
||
|
|
||
|
<h2 class="font-bold text-2xl mt-8">Full Claim</h2>
|
||
|
<p>
|
||
|
The full claim includes the claim as it was originally issued, including
|
||
|
the signature (ie. the proof of issuance by that person).
|
||
|
</p>
|
||
|
<div v-if="!fullClaim">
|
||
|
<div v-if="fullClaimMessage">
|
||
|
{{ fullClaimMessage }}
|
||
|
</div>
|
||
|
<button
|
||
|
v-else
|
||
|
class="bg-blue-600 text-white mt-4 px-4 py-2 rounded-md mb-4"
|
||
|
@click="showFullClaim(veriClaim.id)"
|
||
|
>
|
||
|
Load Full Claim Details
|
||
|
</button>
|
||
|
</div>
|
||
|
<div v-else>
|
||
|
<pre>{{ util.inspect(fullClaim, false, null) }}</pre>
|
||
|
</div>
|
||
|
|
||
|
<a :href="apiServer + '/api/claim/' + veriClaim.id" target="_blank">
|
||
|
<button class="bg-blue-600 text-white mt-4 px-4 py-2 rounded-md mb-4">
|
||
|
View on the Public Server
|
||
|
</button>
|
||
|
</a>
|
||
|
</section>
|
||
|
</template>
|
||
|
|
||
|
<script lang="ts">
|
||
|
import { AxiosError, RawAxiosRequestHeaders } from "axios";
|
||
|
import * as R from "ramda";
|
||
|
import { IIdentifier } from "@veramo/core";
|
||
|
import * as util from "util";
|
||
|
import { Component, Vue } from "vue-facing-decorator";
|
||
|
|
||
|
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||
|
import OfferDialog from "@/components/OfferDialog.vue";
|
||
|
import { accountsDB, db } from "@/db/index";
|
||
|
import { Contact } from "@/db/tables/contacts";
|
||
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||
|
import { accessToken } from "@/libs/crypto";
|
||
|
import {
|
||
|
BLANK_GENERIC_CLAIM,
|
||
|
didInfo,
|
||
|
isHiddenDid,
|
||
|
stripEndorserPrefix,
|
||
|
} from "@/libs/endorserServer";
|
||
|
import QuickNav from "@/components/QuickNav.vue";
|
||
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||
|
import { Account } from "@/db/tables/accounts";
|
||
|
|
||
|
interface Notification {
|
||
|
group: string;
|
||
|
type: string;
|
||
|
title: string;
|
||
|
text: string;
|
||
|
}
|
||
|
|
||
|
@Component({
|
||
|
components: { EntityIcon, GiftedDialog, OfferDialog, QuickNav },
|
||
|
})
|
||
|
export default class ClaimView extends Vue {
|
||
|
$notify!: (notification: Notification, timeout?: number) => void;
|
||
|
|
||
|
activeDid = "";
|
||
|
allMyDids: Array<string> = [];
|
||
|
allContacts: Array<Contact> = [];
|
||
|
apiServer = "";
|
||
|
confirmerIdList = []; // list of DIDs that have confirmed this claim excluding the issuer
|
||
|
confsVisibleErrorMessage = "";
|
||
|
confsVisibleToIdList = []; // list of DIDs that can see any confirmer
|
||
|
fullClaim = null;
|
||
|
fullClaimMessage = "";
|
||
|
numConfsNotVisible = 0; // number of hidden DIDs in the confirmerIdList, minus the issuer if they aren't visible
|
||
|
veriClaim = BLANK_GENERIC_CLAIM;
|
||
|
|
||
|
util = util;
|
||
|
|
||
|
async created() {
|
||
|
await db.open();
|
||
|
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
||
|
this.activeDid = settings?.activeDid || "";
|
||
|
this.apiServer = settings?.apiServer || "";
|
||
|
this.allContacts = await db.contacts.toArray();
|
||
|
|
||
|
await accountsDB.open();
|
||
|
const accounts = accountsDB.accounts;
|
||
|
const accountsArr = await accounts?.toArray();
|
||
|
this.allMyDids = accountsArr.map((acc) => acc.did);
|
||
|
const account = accountsArr.find((acc) => acc.did === this.activeDid);
|
||
|
const identity = JSON.parse(account?.identity || "null");
|
||
|
|
||
|
const pathParam = window.location.pathname.substring("/claim/".length);
|
||
|
let claimId;
|
||
|
if (pathParam) {
|
||
|
claimId = decodeURIComponent(pathParam);
|
||
|
this.loadClaim(claimId, identity);
|
||
|
} else {
|
||
|
this.$notify(
|
||
|
{
|
||
|
group: "alert",
|
||
|
type: "danger",
|
||
|
title: "Error",
|
||
|
text: "No claim ID was provided.",
|
||
|
},
|
||
|
-1,
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
totalConfirmers() {
|
||
|
return (
|
||
|
this.numConfsNotVisible +
|
||
|
this.confirmerIdList.length +
|
||
|
this.confsVisibleToIdList.length
|
||
|
);
|
||
|
}
|
||
|
|
||
|
public async getIdentity(activeDid: string) {
|
||
|
await accountsDB.open();
|
||
|
const account = (await accountsDB.accounts
|
||
|
.where("did")
|
||
|
.equals(activeDid)
|
||
|
.first()) as Account;
|
||
|
const identity = JSON.parse(account?.identity || "null");
|
||
|
|
||
|
if (!identity) {
|
||
|
throw new Error(
|
||
|
"Attempted to load project records with no identity available.",
|
||
|
);
|
||
|
}
|
||
|
return identity;
|
||
|
}
|
||
|
|
||
|
public async getHeaders(identity: IIdentifier) {
|
||
|
const headers: RawAxiosRequestHeaders = {
|
||
|
"Content-Type": "application/json",
|
||
|
};
|
||
|
if (identity) {
|
||
|
const token = await accessToken(identity);
|
||
|
headers["Authorization"] = "Bearer " + token;
|
||
|
}
|
||
|
return headers;
|
||
|
}
|
||
|
|
||
|
// Isn't there a better way to make this available to the template?
|
||
|
didInfo(
|
||
|
did: string,
|
||
|
activeDid: string,
|
||
|
dids: Array<string>,
|
||
|
contacts: Array<Contact>,
|
||
|
) {
|
||
|
return didInfo(did, activeDid, dids, contacts);
|
||
|
}
|
||
|
|
||
|
async loadClaim(claimId: string, identity: IIdentifier) {
|
||
|
const url =
|
||
|
this.apiServer + "/api/claim/byHandle/" + encodeURIComponent(claimId);
|
||
|
const headers = await this.getHeaders(identity);
|
||
|
|
||
|
try {
|
||
|
const resp = await this.axios.get(url, { headers });
|
||
|
if (resp.status === 200) {
|
||
|
this.veriClaim = resp.data;
|
||
|
} else {
|
||
|
// actually, axios typically throws an error so we never get here
|
||
|
console.log("Error getting claim:", resp);
|
||
|
this.$notify(
|
||
|
{
|
||
|
group: "alert",
|
||
|
type: "danger",
|
||
|
title: "Error",
|
||
|
text: "There was a problem getting that claim. See logs for more info.",
|
||
|
},
|
||
|
-1,
|
||
|
);
|
||
|
}
|
||
|
} catch (error: unknown) {
|
||
|
const serverError = error as AxiosError;
|
||
|
console.error("Error retrieving claim:", serverError);
|
||
|
this.$notify(
|
||
|
{
|
||
|
group: "alert",
|
||
|
type: "danger",
|
||
|
title: "Error",
|
||
|
text: "Something went wrong retrieving that claim. See logs for more info.",
|
||
|
},
|
||
|
-1,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
const confirmUrl =
|
||
|
this.apiServer +
|
||
|
"/api/report/issuersWhoClaimedOrConfirmed?claimId=" +
|
||
|
encodeURIComponent(stripEndorserPrefix(claimId));
|
||
|
const confirmHeaders = await this.getHeaders(identity);
|
||
|
try {
|
||
|
const response = await this.axios.get(confirmUrl, {
|
||
|
headers: confirmHeaders,
|
||
|
});
|
||
|
if (response.status === 200) {
|
||
|
console.log("response:", response);
|
||
|
const resultList1 = response.data.result || [];
|
||
|
const resultList2 = R.reject(isHiddenDid, resultList1);
|
||
|
const resultList3 = R.reject(
|
||
|
(did: string) => did === this.veriClaim.issuer,
|
||
|
resultList2,
|
||
|
);
|
||
|
console.log("all result lists:", resultList1, resultList2, resultList3);
|
||
|
this.confirmerIdList = resultList3;
|
||
|
this.numConfsNotVisible = resultList1.length - resultList2.length;
|
||
|
if (resultList3.length === resultList2.length) {
|
||
|
// the issuer was not in the "visible" list so they must be hidden
|
||
|
// so subtract them from the non-visible confirmers count
|
||
|
this.numConfsNotVisible = this.numConfsNotVisible - 1;
|
||
|
}
|
||
|
this.confsVisibleToIdList =
|
||
|
response.data.result.resultVisibleToDids || [];
|
||
|
} else {
|
||
|
this.confsVisibleErrorMessage =
|
||
|
"Had problems retrieving confirmations.";
|
||
|
}
|
||
|
} catch (error: unknown) {
|
||
|
const serverError = error as AxiosError;
|
||
|
console.error("Error retrieving confirmations:", serverError);
|
||
|
this.confsVisibleErrorMessage =
|
||
|
"Had problems retrieving confirmations. See logs for more info.";
|
||
|
}
|
||
|
console.log("confirmerIdList:", this.confirmerIdList);
|
||
|
console.log("confsVisibleToIdList:", this.confsVisibleToIdList);
|
||
|
}
|
||
|
|
||
|
async showFullClaim(claimId: string) {
|
||
|
await accountsDB.open();
|
||
|
const accounts = accountsDB.accounts;
|
||
|
const accountsArr = await accounts?.toArray();
|
||
|
const account = accountsArr.find((acc) => acc.did === this.activeDid);
|
||
|
const identity = JSON.parse(account?.identity || "null");
|
||
|
|
||
|
const url =
|
||
|
this.apiServer + "/api/claim/full/" + encodeURIComponent(claimId);
|
||
|
const headers = await this.getHeaders(identity);
|
||
|
|
||
|
try {
|
||
|
const resp = await this.axios.get(url, { headers });
|
||
|
if (resp.status === 200) {
|
||
|
this.fullClaim = resp.data;
|
||
|
} else {
|
||
|
// actually, axios typically throws an error so we never get here
|
||
|
console.log("Error getting full claim:", resp);
|
||
|
this.$notify(
|
||
|
{
|
||
|
group: "alert",
|
||
|
type: "danger",
|
||
|
title: "Error",
|
||
|
text: "There was a problem getting that claim. See logs for more info.",
|
||
|
},
|
||
|
-1,
|
||
|
);
|
||
|
}
|
||
|
} catch (error: unknown) {
|
||
|
console.error("Error retrieving full claim:", error);
|
||
|
const serverError = error as AxiosError;
|
||
|
if (serverError.response?.status === 403) {
|
||
|
this.fullClaimMessage =
|
||
|
"You are not authorized to view the full contents of this claim." +
|
||
|
" To see all the details, ask the issuer to allow you to see their claims." +
|
||
|
" If you cannot see the issuer's DID, ask someone in the Confirmations section above." +
|
||
|
" If there are no connections, you will have to ask people in your" +
|
||
|
" network for their help, some other way; send them to this page and" +
|
||
|
" see if they can make a connection for you.";
|
||
|
} else {
|
||
|
this.$notify(
|
||
|
{
|
||
|
group: "alert",
|
||
|
type: "danger",
|
||
|
title: "Error",
|
||
|
text: "Something went wrong retrieving that claim. See logs for more info.",
|
||
|
},
|
||
|
-1,
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
</script>
|