Browse Source

remove remaining getIdentity calls & fix QR code for did:peer

master
Trent Larson 4 months ago
parent
commit
9b65fb7ef9
  1. 12
      src/libs/crypto/vc/didPeer.ts
  2. 2
      src/libs/crypto/vc/index.ts
  3. 5
      src/libs/endorserServer.ts
  4. 12
      src/libs/util.ts
  5. 6
      src/views/ContactAmountsView.vue
  6. 78
      src/views/ContactQRScanShowView.vue
  7. 3
      src/views/GiftedDetails.vue
  8. 13
      src/views/NewEditProjectView.vue
  9. 26
      src/views/ProjectsView.vue

12
src/libs/crypto/vc/didPeer.ts

@ -1,10 +1,10 @@
import {Buffer} from "buffer/"; import { Buffer } from "buffer/";
import {decode as cborDecode} from "cbor-x"; import { decode as cborDecode } from "cbor-x";
import {bytesToMultibase, multibaseToBytes} from "did-jwt"; import { bytesToMultibase, multibaseToBytes } from "did-jwt";
import {getWebCrypto} from "@/libs/crypto/vc/passkeyHelpers"; import { getWebCrypto } from "@/libs/crypto/vc/passkeyHelpers";
const PEER_DID_PREFIX = "did:peer:"; export const PEER_DID_PREFIX = "did:peer:";
const PEER_DID_MULTIBASE_PREFIX = PEER_DID_PREFIX + "0"; const PEER_DID_MULTIBASE_PREFIX = PEER_DID_PREFIX + "0";
/** /**
@ -93,4 +93,4 @@ export function createPeerDid(publicKeyBytes: Uint8Array) {
"p256-pub", "p256-pub",
); );
return PEER_DID_MULTIBASE_PREFIX + methodSpecificId; return PEER_DID_MULTIBASE_PREFIX + methodSpecificId;
} }

2
src/libs/crypto/vc/index.ts

@ -13,6 +13,8 @@ import * as u8a from "uint8arrays";
import { createDidPeerJwt } from "@/libs/crypto/vc/passkeyDidPeer"; import { createDidPeerJwt } from "@/libs/crypto/vc/passkeyDidPeer";
export const ETHR_DID_PREFIX = "did:ethr:";
/** /**
* Meta info about a key * Meta info about a key
*/ */

5
src/libs/endorserServer.ts

@ -6,7 +6,7 @@ import { DEFAULT_IMAGE_API_SERVER } from "@/constants/app";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { accessToken } from "@/libs/crypto"; import { accessToken } from "@/libs/crypto";
import { NonsensitiveDexie } from "@/db/index"; import { NonsensitiveDexie } from "@/db/index";
import { getAccount, getIdentity } from "@/libs/util"; import { getAccount } from "@/libs/util";
import { createEndorserJwtForKey, KeyMeta } from "@/libs/crypto/vc"; import { createEndorserJwtForKey, KeyMeta } from "@/libs/crypto/vc";
export const SCHEMA_ORG_CONTEXT = "https://schema.org"; export const SCHEMA_ORG_CONTEXT = "https://schema.org";
@ -1001,8 +1001,7 @@ export async function setVisibilityUtil(
} }
const url = const url =
apiServer + "/api/report/" + (visibility ? "canSeeMe" : "cannotSeeMe"); apiServer + "/api/report/" + (visibility ? "canSeeMe" : "cannotSeeMe");
const identity = await getIdentity(activeDid); const headers = await getHeaders(activeDid);
const headers = await getHeaders(identity.did);
const payload = JSON.stringify({ did: contact.did }); const payload = JSON.stringify({ did: contact.did });
try { try {

12
src/libs/util.ts

@ -211,18 +211,6 @@ export const getAccount = async (
return account; return account;
}; };
export const getIdentity = async (activeDid: string): Promise<IIdentifier> => {
const account = await getAccount(activeDid);
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error(
`Attempted to load identity ${activeDid} but no identifier was found`,
);
}
return identity;
};
/** /**
* Generates a new identity, saves it to the database, and sets it as the active identity. * Generates a new identity, saves it to the database, and sets it as the active identity.
* @return {Promise<string>} with the DID of the new identity * @return {Promise<string>} with the DID of the new identity

6
src/views/ContactAmountsView.vue

@ -124,7 +124,6 @@ import {
GiveVerifiableCredential, GiveVerifiableCredential,
SCHEMA_ORG_CONTEXT, SCHEMA_ORG_CONTEXT,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
@Component({ components: { QuickNav } }) @Component({ components: { QuickNav } })
export default class ContactAmountssView extends Vue { export default class ContactAmountssView extends Vue {
@ -175,12 +174,11 @@ export default class ContactAmountssView extends Vue {
async loadGives(activeDid: string, contact: Contact) { async loadGives(activeDid: string, contact: Contact) {
try { try {
const identity = await libsUtil.getIdentity(this.activeDid);
let result: Array<GiveSummaryRecord> = []; let result: Array<GiveSummaryRecord> = [];
const url = const url =
this.apiServer + this.apiServer +
"/api/v2/report/gives?agentDid=" + "/api/v2/report/gives?agentDid=" +
encodeURIComponent(identity.did) + encodeURIComponent(this.activeDid) +
"&recipientDid=" + "&recipientDid=" +
encodeURIComponent(contact.did); encodeURIComponent(contact.did);
const headers = await getHeaders(activeDid); const headers = await getHeaders(activeDid);
@ -209,7 +207,7 @@ export default class ContactAmountssView extends Vue {
"/api/v2/report/gives?agentDid=" + "/api/v2/report/gives?agentDid=" +
encodeURIComponent(contact.did) + encodeURIComponent(contact.did) +
"&recipientDid=" + "&recipientDid=" +
encodeURIComponent(identity.did); encodeURIComponent(this.activeDid);
const headers2 = await getHeaders(activeDid); const headers2 = await getHeaders(activeDid);
const resp2 = await this.axios.get(url2, { headers: headers2 }); const resp2 = await this.axios.get(url2, { headers: headers2 });
if (resp2.status === 200) { if (resp2.status === 200) {

78
src/views/ContactQRScanShowView.vue

@ -34,7 +34,11 @@
</p> </p>
</div> </div>
<div @click="onCopyToClipboard()" v-if="activeDid" class="text-center"> <div
@click="onCopyUrlToClipboard()"
v-if="activeDid && activeDid.startsWith(ETHR_DID_PREFIX)"
class="text-center"
>
<!-- <!--
Play with display options: https://qr-code-styling.com/ Play with display options: https://qr-code-styling.com/
See docs: https://www.npmjs.com/package/qr-code-generator-vue3 See docs: https://www.npmjs.com/package/qr-code-generator-vue3
@ -45,8 +49,18 @@
:dotsOptions="{ type: 'square' }" :dotsOptions="{ type: 'square' }"
class="flex justify-center" class="flex justify-center"
/> />
<span> Click that QR to copy your contact URL to your clipboard. </span> <span>
<div>Not scanning? Show it in pieces.</div> Click this or QR code to copy your contact URL to your clipboard.
</span>
</div>
<div v-else-if="activeDid" class="text-center">
<!-- Not an ETHR DID so force them to paste it. (Passkey Peer DIDs are too big.) -->
<span @click="onCopyDidToClipboard()" class="text-blue-500">
Click here to copy your DID to your clipboard.
</span>
<span>
Then give it to them so they can paste it in their list of People.
</span>
</div> </div>
<div class="text-center" v-else> <div class="text-center" v-else>
You have no identitifiers yet, so You have no identitifiers yet, so
@ -92,13 +106,14 @@ import {
nextDerivationPath, nextDerivationPath,
} from "@/libs/crypto"; } from "@/libs/crypto";
import { import {
CONTACT_URL_PREFIX, createEndorserJwtForDid, CONTACT_URL_PREFIX,
createEndorserJwtForDid,
ENDORSER_JWT_URL_LOCATION, ENDORSER_JWT_URL_LOCATION,
isDid, isDid,
register, register,
setVisibilityUtil, setVisibilityUtil,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util"; import { ETHR_DID_PREFIX } from "@/libs/crypto/vc";
@Component({ @Component({
components: { components: {
@ -117,6 +132,8 @@ export default class ContactQRScanShow extends Vue {
isRegistered = false; isRegistered = false;
qrValue = ""; qrValue = "";
ETHR_DID_PREFIX = ETHR_DID_PREFIX;
async created() { async created() {
await db.open(); await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); const settings = await db.settings.get(MASTER_SETTINGS_KEY);
@ -131,20 +148,9 @@ export default class ContactQRScanShow extends Vue {
const accounts = await accountsDB.accounts.toArray(); const accounts = await accountsDB.accounts.toArray();
const account = R.find((acc) => acc.did === this.activeDid, accounts); const account = R.find((acc) => acc.did === this.activeDid, accounts);
if (account) { if (account) {
const identity = await libsUtil.getIdentity(this.activeDid); const publicKeyHex = account.publicKeyHex;
const publicKeyHex = identity.keys[0].publicKeyHex;
const publicEncKey = Buffer.from(publicKeyHex, "hex").toString("base64"); const publicEncKey = Buffer.from(publicKeyHex, "hex").toString("base64");
const newDerivPath = nextDerivationPath(account.derivationPath as string);
const nextPublicHex = deriveAddress(
account.mnemonic as string,
newDerivPath,
)[2];
const nextPublicEncKey = Buffer.from(nextPublicHex, "hex");
const nextPublicEncKeyHash = sha256(nextPublicEncKey);
const nextPublicEncKeyHashBase64 =
Buffer.from(nextPublicEncKeyHash).toString("base64");
const contactInfo = { const contactInfo = {
iat: Date.now(), iat: Date.now(),
iss: this.activeDid, iss: this.activeDid,
@ -153,13 +159,28 @@ export default class ContactQRScanShow extends Vue {
(settings?.firstName || "") + (settings?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""), // deprecated, pre v 0.1.3 (settings?.lastName ? ` ${settings.lastName}` : ""), // deprecated, pre v 0.1.3
publicEncKey, publicEncKey,
nextPublicEncKeyHash: nextPublicEncKeyHashBase64,
profileImageUrl: settings?.profileImageUrl, profileImageUrl: settings?.profileImageUrl,
registered: settings?.isRegistered, registered: settings?.isRegistered,
}, },
}; };
const vcJwt: string = await createEndorserJwtForDid(identity.did, contactInfo); if (account?.mnemonic && account?.derivationPath) {
const newDerivPath = nextDerivationPath(
account.derivationPath as string,
);
const nextPublicHex = deriveAddress(
account.mnemonic as string,
newDerivPath,
)[2];
const nextPublicEncKey = Buffer.from(nextPublicHex, "hex");
const nextPublicEncKeyHash = sha256(nextPublicEncKey);
const nextPublicEncKeyHashBase64 =
Buffer.from(nextPublicEncKeyHash).toString("base64");
contactInfo.own.nextPublicEncKeyHash = nextPublicEncKeyHashBase64;
}
const vcJwt = await createEndorserJwtForDid(this.activeDid, contactInfo);
const viewPrefix = CONTACT_URL_PREFIX + ENDORSER_JWT_URL_LOCATION; const viewPrefix = CONTACT_URL_PREFIX + ENDORSER_JWT_URL_LOCATION;
this.qrValue = viewPrefix + vcJwt; this.qrValue = viewPrefix + vcJwt;
} }
@ -409,7 +430,7 @@ export default class ContactQRScanShow extends Vue {
); );
} }
onCopyToClipboard() { onCopyUrlToClipboard() {
//this.onScanDetect([{ rawValue: this.qrValue }]); // good for testing //this.onScanDetect([{ rawValue: this.qrValue }]); // good for testing
useClipboard() useClipboard()
.copy(this.qrValue) .copy(this.qrValue)
@ -426,5 +447,22 @@ export default class ContactQRScanShow extends Vue {
); );
}); });
} }
onCopyDidToClipboard() {
//this.onScanDetect([{ rawValue: this.qrValue }]); // good for testing
useClipboard()
.copy(this.activeDid)
.then(() => {
this.$notify(
{
group: "alert",
type: "info",
title: "Copied",
text: "Your DID was copied to the clipboard. Have them paste it on their 'People' screen to add you.",
},
10000,
);
});
}
} }
</script> </script>

3
src/views/GiftedDetails.vue

@ -313,12 +313,11 @@ export default class GiftedDetails extends Vue {
if (this.projectId) { if (this.projectId) {
// console.log("Getting project name from cache", this.projectId); // console.log("Getting project name from cache", this.projectId);
const identity = await libsUtil.getIdentity(this.activeDid);
const project = await getPlanFromCache( const project = await getPlanFromCache(
this.projectId, this.projectId,
this.axios, this.axios,
this.apiServer, this.apiServer,
identity.did, this.activeDid,
); );
this.projectName = project?.name this.projectName = project?.name
? "the project: " + project.name ? "the project: " + project.name

13
src/views/NewEditProjectView.vue

@ -175,7 +175,6 @@
import "leaflet/dist/leaflet.css"; import "leaflet/dist/leaflet.css";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import { IIdentifier } from "@veramo/core";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet"; import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet";
@ -189,7 +188,6 @@ import {
createEndorserJwtVcFromClaim, createEndorserJwtVcFromClaim,
PlanVerifiableCredential, PlanVerifiableCredential,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
import { useAppStore } from "@/store/app"; import { useAppStore } from "@/store/app";
@Component({ @Component({
@ -229,8 +227,6 @@ export default class NewEditProjectView extends Vue {
zoneName = DateTime.local().zoneName; zoneName = DateTime.local().zoneName;
zoom = 2; zoom = 2;
libsUtil = libsUtil;
async mounted() { async mounted() {
await accountsDB.open(); await accountsDB.open();
this.numAccounts = await accountsDB.accounts.count(); this.numAccounts = await accountsDB.accounts.count();
@ -365,7 +361,7 @@ export default class NewEditProjectView extends Vue {
} }
} }
private async saveProject(identity: IIdentifier) { private async saveProject(issuerDid: string) {
// Make a claim // Make a claim
const vcClaim: PlanVerifiableCredential = this.fullClaim; const vcClaim: PlanVerifiableCredential = this.fullClaim;
if (this.projectId) { if (this.projectId) {
@ -416,13 +412,13 @@ export default class NewEditProjectView extends Vue {
} else { } else {
delete vcClaim.startTime; delete vcClaim.startTime;
} }
const vcJwt = await createEndorserJwtVcFromClaim(identity.did, vcClaim); const vcJwt = await createEndorserJwtVcFromClaim(issuerDid, vcClaim);
// Make the xhr request payload // Make the xhr request payload
const payload = JSON.stringify({ jwtEncoded: vcJwt }); const payload = JSON.stringify({ jwtEncoded: vcJwt });
const url = this.apiServer + "/api/v2/claim"; const url = this.apiServer + "/api/v2/claim";
const token = await accessToken(identity.did); const token = await accessToken(issuerDid);
const headers = { const headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: "Bearer " + token, Authorization: "Bearer " + token,
@ -508,8 +504,7 @@ export default class NewEditProjectView extends Vue {
if (this.numAccounts === 0) { if (this.numAccounts === 0) {
console.error("Error: there is no account."); console.error("Error: there is no account.");
} else { } else {
const identity = await libsUtil.getIdentity(this.activeDid); this.saveProject(this.activeDid);
this.saveProject(identity);
} }
} }

26
src/views/ProjectsView.vue

@ -235,7 +235,6 @@ import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto"; import { accessToken } from "@/libs/crypto";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import { IIdentifier } from "@veramo/core";
import InfiniteScroll from "@/components/InfiniteScroll.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import ProjectIcon from "@/components/ProjectIcon.vue"; import ProjectIcon from "@/components/ProjectIcon.vue";
@ -255,9 +254,9 @@ export default class ProjectsView extends Vue {
); );
} }
activeDid = "";
apiServer = ""; apiServer = "";
projects: PlanData[] = []; projects: PlanData[] = [];
currentIid: IIdentifier;
isLoading = false; isLoading = false;
isRegistered = false; isRegistered = false;
numAccounts = 0; numAccounts = 0;
@ -271,7 +270,7 @@ export default class ProjectsView extends Vue {
try { try {
await db.open(); await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); const settings = await db.settings.get(MASTER_SETTINGS_KEY);
const activeDid: string = (settings?.activeDid as string) || ""; this.activeDid = (settings?.activeDid as string) || "";
this.apiServer = (settings?.apiServer as string) || ""; this.apiServer = (settings?.apiServer as string) || "";
this.isRegistered = !!settings?.isRegistered; this.isRegistered = !!settings?.isRegistered;
@ -281,7 +280,6 @@ export default class ProjectsView extends Vue {
console.error("No accounts found."); console.error("No accounts found.");
this.errNote("You need an identifier to load your projects."); this.errNote("You need an identifier to load your projects.");
} else { } else {
this.currentIid = await libsUtil.getIdentity(activeDid);
await this.loadOffers(); await this.loadOffers();
} }
} catch (err) { } catch (err) {
@ -342,7 +340,7 @@ export default class ProjectsView extends Vue {
if (this.projects.length > 0 && payload) { if (this.projects.length > 0 && payload) {
const latestProject = this.projects[this.projects.length - 1]; const latestProject = this.projects[this.projects.length - 1];
await this.loadProjects( await this.loadProjects(
this.currentIid, this.activeDid,
`beforeId=${latestProject.rowid}`, `beforeId=${latestProject.rowid}`,
); );
} }
@ -350,13 +348,12 @@ export default class ProjectsView extends Vue {
/** /**
* Load projects initially * Load projects initially
* @param identifier of the user * @param issuerDid of the user
* @param urlExtra additional url parameters in a string * @param urlExtra additional url parameters in a string
**/ **/
async loadProjects(identifier?: IIdentifier, urlExtra: string = "") { async loadProjects(activeDid?: string, urlExtra: string = "") {
const identity = identifier || this.currentIid;
const url = `${this.apiServer}/api/v2/report/plansByIssuer?${urlExtra}`; const url = `${this.apiServer}/api/v2/report/plansByIssuer?${urlExtra}`;
const token: string = await accessToken(identity.did); const token: string = await accessToken(activeDid);
await this.projectDataLoader(url, token); await this.projectDataLoader(url, token);
} }
@ -446,19 +443,18 @@ export default class ProjectsView extends Vue {
async loadMoreOfferData(payload: boolean) { async loadMoreOfferData(payload: boolean) {
if (this.offers.length > 0 && payload) { if (this.offers.length > 0 && payload) {
const latestOffer = this.offers[this.offers.length - 1]; const latestOffer = this.offers[this.offers.length - 1];
await this.loadOffers(this.currentIid, `&beforeId=${latestOffer.jwtId}`); await this.loadOffers(this.activeDid, `&beforeId=${latestOffer.jwtId}`);
} }
} }
/** /**
* Load offers initially * Load offers initially
* @param identifier of the user * @param issuerDid of the user
* @param urlExtra additional url parameters in a string * @param urlExtra additional url parameters in a string
**/ **/
async loadOffers(identifier?: IIdentifier, urlExtra: string = "") { async loadOffers(issuerDid?: string, urlExtra: string = "") {
const identity = identifier || this.currentIid; const url = `${this.apiServer}/api/v2/report/offers?offeredByDid=${issuerDid}${urlExtra}`;
const url = `${this.apiServer}/api/v2/report/offers?offeredByDid=${identity.did}${urlExtra}`; const token: string = await accessToken(issuerDid);
const token: string = await accessToken(identity.did);
await this.offerDataLoader(url, token); await this.offerDataLoader(url, token);
} }

Loading…
Cancel
Save