Browse Source

change the contact-sharing data into a JWT for the contact-import page

split_build_process
Trent Larson 4 weeks ago
parent
commit
a99a0fb5cc
  1. 13
      src/libs/crypto/vc/index.ts
  2. 2
      src/router/index.ts
  3. 35
      src/views/ContactImportView.vue
  4. 21
      src/views/ContactsView.vue
  5. 2
      src/views/GiftedDetailsView.vue
  6. 2
      src/views/IdentitySwitcherView.vue

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

@ -9,7 +9,6 @@
import { Buffer } from "buffer/"; import { Buffer } from "buffer/";
import * as didJwt from "did-jwt"; import * as didJwt from "did-jwt";
import { JWTVerified } from "did-jwt"; import { JWTVerified } from "did-jwt";
import { JWTDecoded } from "did-jwt/lib/JWT";
import { Resolver } from "did-resolver"; import { Resolver } from "did-resolver";
import { IIdentifier } from "@veramo/core"; import { IIdentifier } from "@veramo/core";
import * as u8a from "uint8arrays"; import * as u8a from "uint8arrays";
@ -41,7 +40,7 @@ export interface KeyMeta {
passkeyCredIdHex?: string; passkeyCredIdHex?: string;
} }
const resolver = new Resolver({ ethr: didEthLocalResolver }); const ethLocalResolver = new Resolver({ ethr: didEthLocalResolver });
/** /**
* Tell whether a key is from a passkey * Tell whether a key is from a passkey
@ -62,6 +61,7 @@ export async function createEndorserJwtForKey(
const privateKeyHex = identity.keys[0].privateKeyHex; const privateKeyHex = identity.keys[0].privateKeyHex;
const signer = await SimpleSigner(privateKeyHex as string); const signer = await SimpleSigner(privateKeyHex as string);
const options = { const options = {
// alg: "ES256K", // "K" is the default, "K-R" is used by the server in tests
issuer: account.did, issuer: account.did,
signer: signer, signer: signer,
expiresIn: undefined as number | undefined, expiresIn: undefined as number | undefined,
@ -124,7 +124,8 @@ function bytesToHex(b: Uint8Array): string {
} }
// We should be calling 'verify' in more places, showing warnings if it fails. // We should be calling 'verify' in more places, showing warnings if it fails.
export function decodeEndorserJwt(jwt: string): JWTDecoded { // @returns JWTDecoded with { header: JWTHeader, payload: string, signature: string, data: string } (but doesn't verify the signature)
export function decodeEndorserJwt(jwt: string) {
return didJwt.decodeJWT(jwt); return didJwt.decodeJWT(jwt);
} }
@ -134,10 +135,8 @@ export async function decodeAndVerifyJwt(
jwt: string, jwt: string,
): Promise<Omit<JWTVerified, "didResolutionResult" | "signer" | "jwt">> { ): Promise<Omit<JWTVerified, "didResolutionResult" | "signer" | "jwt">> {
const pieces = jwt.split("."); const pieces = jwt.split(".");
console.log("WTF decodeAndVerifyJwt", typeof jwt, jwt, pieces);
const header = JSON.parse(base64urlDecodeString(pieces[0])); const header = JSON.parse(base64urlDecodeString(pieces[0]));
const payload = JSON.parse(base64urlDecodeString(pieces[1])); const payload = JSON.parse(base64urlDecodeString(pieces[1]));
console.log("WTF decodeAndVerifyJwt after", header, payload);
const issuerDid = payload.iss; const issuerDid = payload.iss;
if (!issuerDid) { if (!issuerDid) {
return Promise.reject({ return Promise.reject({
@ -149,7 +148,9 @@ export async function decodeAndVerifyJwt(
if (issuerDid.startsWith(ETHR_DID_PREFIX)) { if (issuerDid.startsWith(ETHR_DID_PREFIX)) {
try { try {
const verified = await didJwt.verifyJWT(jwt, { resolver }); const verified = await didJwt.verifyJWT(jwt, {
resolver: ethLocalResolver,
});
return verified; return verified;
} catch (e: unknown) { } catch (e: unknown) {
return Promise.reject({ return Promise.reject({

2
src/router/index.ts

@ -70,7 +70,7 @@ const routes: Array<RouteRecordRaw> = [
component: () => import("../views/ContactGiftingView.vue"), component: () => import("../views/ContactGiftingView.vue"),
}, },
{ {
path: "/contact-import", path: "/contact-import/:jwt?",
name: "contact-import", name: "contact-import",
component: () => import("../views/ContactImportView.vue"), component: () => import("../views/ContactImportView.vue"),
}, },

35
src/views/ContactImportView.vue

@ -16,10 +16,11 @@
Contact Import Contact Import
</h1> </h1>
<span class="flex justify-center"> <span v-if="contactsImporting.length > 0" class="flex justify-center">
<input type="checkbox" v-model="makeVisible" class="mr-2" /> <input type="checkbox" v-model="makeVisible" class="mr-2" />
Make my activity visible to these contacts. Make my activity visible to these contacts.
</span> </span>
<div v-if="sameCount > 0"> <div v-if="sameCount > 0">
<span v-if="sameCount == 1" <span v-if="sameCount == 1"
>One contact is the same as an existing contact</span >One contact is the same as an existing contact</span
@ -85,17 +86,19 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { JWTVerified } from "did-jwt";
import * as R from "ramda"; import * as R from "ramda";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router"; import { Router } from "vue-router";
import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue";
import OfferDialog from "@/components/OfferDialog.vue";
import { AppString, NotificationIface } from "@/constants/app"; import { AppString, NotificationIface } from "@/constants/app";
import { db, retrieveSettingsForActiveAccount } from "@/db/index"; import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import QuickNav from "@/components/QuickNav.vue"; import { decodeAndVerifyJwt } from "@/libs/crypto/vc/index";
import EntityIcon from "@/components/EntityIcon.vue";
import OfferDialog from "@/components/OfferDialog.vue";
import { setVisibilityUtil } from "@/libs/endorserServer"; import { setVisibilityUtil } from "@/libs/endorserServer";
@Component({ @Component({
@ -127,9 +130,27 @@ export default class ContactImportView extends Vue {
this.apiServer = settings.apiServer || ""; this.apiServer = settings.apiServer || "";
// Retrieve the imported contacts from the query parameter // Retrieve the imported contacts from the query parameter
const importedContacts = const importedContacts = (this.$route as Router).query[
((this.$route as Router).query["contacts"] as string) || "[]"; "contacts"
this.contactsImporting = JSON.parse(importedContacts); ] as string;
if (importedContacts) {
await this.setContactsSelected(JSON.parse(importedContacts));
}
// match everything after /contact-import/ in the window.location.pathname
const jwt = window.location.pathname.match(
/\/contact-import\/(ey.+)$/,
)?.[1];
if (jwt) {
// decode the JWT
// eslint-disable-next-line prettier/prettier
const parsedJwt: Omit<JWTVerified, "didResolutionResult" | "signer" | "jwt"> = await decodeAndVerifyJwt(jwt);
await this.setContactsSelected(parsedJwt.payload.contacts as Contact[]);
}
}
async setContactsSelected(contacts: Array<Contact>) {
this.contactsImporting = contacts;
this.contactsSelected = new Array(this.contactsImporting.length).fill(true); this.contactsSelected = new Array(this.contactsImporting.length).fill(true);
await db.open(); await db.open();

21
src/views/ContactsView.vue

@ -323,7 +323,7 @@ import GiftedDialog from "@/components/GiftedDialog.vue";
import OfferDialog from "@/components/OfferDialog.vue"; import OfferDialog from "@/components/OfferDialog.vue";
import ContactNameDialog from "@/components/ContactNameDialog.vue"; import ContactNameDialog from "@/components/ContactNameDialog.vue";
import TopMessage from "@/components/TopMessage.vue"; import TopMessage from "@/components/TopMessage.vue";
import { AppString, NotificationIface } from "@/constants/app"; import { APP_SERVER, AppString, NotificationIface } from "@/constants/app";
import { import {
db, db,
retrieveSettingsForActiveAccount, retrieveSettingsForActiveAccount,
@ -336,6 +336,7 @@ import { decodeEndorserJwt } from "@/libs/crypto/vc";
import { import {
CONTACT_CSV_HEADER, CONTACT_CSV_HEADER,
CONTACT_URL_PREFIX, CONTACT_URL_PREFIX,
createEndorserJwtForDid,
GiveSummaryRecord, GiveSummaryRecord,
getHeaders, getHeaders,
isDid, isDid,
@ -415,6 +416,7 @@ export default class ContactsView extends Vue {
); );
// handle a contact sent via URL // handle a contact sent via URL
// @deprecated: use /contact-import/:jwt with a JWT that has an array of contacts
const importedContactJwt = (this.$route as RouteLocationNormalizedLoaded) const importedContactJwt = (this.$route as RouteLocationNormalizedLoaded)
.query["contactJwt"] as string; .query["contactJwt"] as string;
if (importedContactJwt) { if (importedContactJwt) {
@ -1247,7 +1249,7 @@ export default class ContactsView extends Vue {
}; };
} }
private copySelectedContacts() { private async copySelectedContacts() {
if (this.contactsSelected.length === 0) { if (this.contactsSelected.length === 0) {
this.danger("You must select contacts to copy."); this.danger("You must select contacts to copy.");
return; return;
@ -1255,18 +1257,23 @@ export default class ContactsView extends Vue {
const selectedContacts = this.contacts.filter((c) => const selectedContacts = this.contacts.filter((c) =>
this.contactsSelected.includes(c.did), this.contactsSelected.includes(c.did),
); );
const message = console.log(
"To add contacts, paste this into the box on the 'Contacts' screen.\n\n" + "Array of selected contacts:",
JSON.stringify(selectedContacts); JSON.stringify(selectedContacts),
);
const contactsJwt = await createEndorserJwtForDid(this.activeDid, {
contacts: selectedContacts,
});
const contactsJwtUrl = APP_SERVER + "/contact-import/" + contactsJwt;
useClipboard() useClipboard()
.copy(message) .copy(contactsJwtUrl)
.then(() => { .then(() => {
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
type: "info", type: "info",
title: "Copied", title: "Copied",
text: "Those contacts were copied to the clipboard. Have them paste it in the box on their 'Contacts' screen.", text: "The link for those contacts is now in the clipboard.",
}, },
5000, 5000,
); );

2
src/views/GiftedDetailsView.vue

@ -114,7 +114,7 @@
{{ {{
giverDid giverDid
? "This was provided by " + giverName + "." ? "This was provided by " + giverName + "."
: "No individual gave." : "No named individual gave."
}} }}
</label> </label>
<fa <fa

2
src/views/IdentitySwitcherView.vue

@ -188,7 +188,7 @@ export default class IdentitySwitcherView extends Vue {
group: "alert", group: "alert",
type: "warning", type: "warning",
title: "Cannot Delete", title: "Cannot Delete",
text: "You cannot delete the active identity.", text: "You cannot delete the active identity. Set to another identity or 'no identity' first.",
}, },
3000, 3000,
); );

Loading…
Cancel
Save