forked from trent_larson/crowd-funder-for-time-pwa
change the contact-sharing data into a JWT for the contact-import page
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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"),
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
{{
|
||||
giverDid
|
||||
? "This was provided by " + giverName + "."
|
||||
: "No individual gave."
|
||||
: "No named individual gave."
|
||||
}}
|
||||
</label>
|
||||
<fa
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user