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 * as didJwt from "did-jwt";
import { JWTVerified } from "did-jwt";
import { JWTDecoded } from "did-jwt/lib/JWT";
import { Resolver } from "did-resolver";
import { IIdentifier } from "@veramo/core";
import * as u8a from "uint8arrays";
@ -41,7 +40,7 @@ export interface KeyMeta {
passkeyCredIdHex?: string;
}
const resolver = new Resolver({ ethr: didEthLocalResolver });
const ethLocalResolver = new Resolver({ ethr: didEthLocalResolver });
/**
* Tell whether a key is from a passkey
@ -62,6 +61,7 @@ export async function createEndorserJwtForKey(
const privateKeyHex = identity.keys[0].privateKeyHex;
const signer = await SimpleSigner(privateKeyHex as string);
const options = {
// alg: "ES256K", // "K" is the default, "K-R" is used by the server in tests
issuer: account.did,
signer: signer,
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.
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);
}
@ -134,10 +135,8 @@ export async function decodeAndVerifyJwt(
jwt: string,
): Promise<Omit<JWTVerified, "didResolutionResult" | "signer" | "jwt">> {
const pieces = jwt.split(".");
console.log("WTF decodeAndVerifyJwt", typeof jwt, jwt, pieces);
const header = JSON.parse(base64urlDecodeString(pieces[0]));
const payload = JSON.parse(base64urlDecodeString(pieces[1]));
console.log("WTF decodeAndVerifyJwt after", header, payload);
const issuerDid = payload.iss;
if (!issuerDid) {
return Promise.reject({
@ -149,7 +148,9 @@ export async function decodeAndVerifyJwt(
if (issuerDid.startsWith(ETHR_DID_PREFIX)) {
try {
const verified = await didJwt.verifyJWT(jwt, { resolver });
const verified = await didJwt.verifyJWT(jwt, {
resolver: ethLocalResolver,
});
return verified;
} catch (e: unknown) {
return Promise.reject({

2
src/router/index.ts

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

35
src/views/ContactImportView.vue

@ -16,10 +16,11 @@
Contact Import
</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" />
Make my activity visible to these contacts.
</span>
<div v-if="sameCount > 0">
<span v-if="sameCount == 1"
>One contact is the same as an existing contact</span
@ -85,17 +86,19 @@
</template>
<script lang="ts">
import { JWTVerified } from "did-jwt";
import * as R from "ramda";
import { Component, Vue } from "vue-facing-decorator";
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 { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import * as libsUtil from "@/libs/util";
import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue";
import OfferDialog from "@/components/OfferDialog.vue";
import { decodeAndVerifyJwt } from "@/libs/crypto/vc/index";
import { setVisibilityUtil } from "@/libs/endorserServer";
@Component({
@ -127,9 +130,27 @@ export default class ContactImportView extends Vue {
this.apiServer = settings.apiServer || "";
// Retrieve the imported contacts from the query parameter
const importedContacts =
((this.$route as Router).query["contacts"] as string) || "[]";
this.contactsImporting = JSON.parse(importedContacts);
const importedContacts = (this.$route as Router).query[
"contacts"
] 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);
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 ContactNameDialog from "@/components/ContactNameDialog.vue";
import TopMessage from "@/components/TopMessage.vue";
import { AppString, NotificationIface } from "@/constants/app";
import { APP_SERVER, AppString, NotificationIface } from "@/constants/app";
import {
db,
retrieveSettingsForActiveAccount,
@ -336,6 +336,7 @@ import { decodeEndorserJwt } from "@/libs/crypto/vc";
import {
CONTACT_CSV_HEADER,
CONTACT_URL_PREFIX,
createEndorserJwtForDid,
GiveSummaryRecord,
getHeaders,
isDid,
@ -415,6 +416,7 @@ export default class ContactsView extends Vue {
);
// 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)
.query["contactJwt"] as string;
if (importedContactJwt) {
@ -1247,7 +1249,7 @@ export default class ContactsView extends Vue {
};
}
private copySelectedContacts() {
private async copySelectedContacts() {
if (this.contactsSelected.length === 0) {
this.danger("You must select contacts to copy.");
return;
@ -1255,18 +1257,23 @@ export default class ContactsView extends Vue {
const selectedContacts = this.contacts.filter((c) =>
this.contactsSelected.includes(c.did),
);
const message =
"To add contacts, paste this into the box on the 'Contacts' screen.\n\n" +
JSON.stringify(selectedContacts);
console.log(
"Array of selected contacts:",
JSON.stringify(selectedContacts),
);
const contactsJwt = await createEndorserJwtForDid(this.activeDid, {
contacts: selectedContacts,
});
const contactsJwtUrl = APP_SERVER + "/contact-import/" + contactsJwt;
useClipboard()
.copy(message)
.copy(contactsJwtUrl)
.then(() => {
this.$notify(
{
group: "alert",
type: "info",
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,
);

2
src/views/GiftedDetailsView.vue

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

2
src/views/IdentitySwitcherView.vue

@ -188,7 +188,7 @@ export default class IdentitySwitcherView extends Vue {
group: "alert",
type: "warning",
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,
);

Loading…
Cancel
Save