forked from trent_larson/crowd-funder-for-time-pwa
fix problem after minimizing use of account private data
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
<h1 class="text-xl font-bold text-center mb-4 relative">
|
<h1 class="text-xl font-bold text-center mb-4 relative">
|
||||||
Welcome to Time Safari
|
Welcome to Time Safari
|
||||||
<br />
|
<br />
|
||||||
- Showcasing Gratitude & Magnifing Time
|
- Showcasing Gratitude & Magnifying Time
|
||||||
<div
|
<div
|
||||||
class="text-lg text-center leading-none absolute right-0 -top-1"
|
class="text-lg text-center leading-none absolute right-0 -top-1"
|
||||||
@click="onClickClose(true)"
|
@click="onClickClose(true)"
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export default class ClaimCertificateView extends Vue {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (claimData.claimType === "GiveAction" && claimData.claim.agent) {
|
if (claimData.claimType === "GiveAction" && claimData.claim.agent) {
|
||||||
const presentedText = "Thanks To ";
|
const presentedText = "Thanks To";
|
||||||
ctx.font = "14px Arial";
|
ctx.font = "14px Arial";
|
||||||
const presentedWidth = ctx.measureText(presentedText).width;
|
const presentedWidth = ctx.measureText(presentedText).width;
|
||||||
ctx.fillText(
|
ctx.fillText(
|
||||||
@@ -148,8 +148,36 @@ export default class ClaimCertificateView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// alternatively, show some offer details
|
||||||
|
if (claimData.claimType === "Offer") {
|
||||||
|
const presentedText = "To";
|
||||||
|
ctx.font = "14px Arial";
|
||||||
|
const presentedWidth = ctx.measureText(presentedText).width;
|
||||||
|
ctx.fillText(
|
||||||
|
presentedText,
|
||||||
|
(CANVAS_WIDTH - presentedWidth) / 2, // Center horizontally
|
||||||
|
CANVAS_HEIGHT * 0.37,
|
||||||
|
);
|
||||||
|
// fulfills
|
||||||
|
const agentDid =
|
||||||
|
claimData.claim.agent.identifier || claimData.claim.agent;
|
||||||
|
const agentText = serverUtil.didInfoForCertificate(
|
||||||
|
agentDid,
|
||||||
|
allContacts,
|
||||||
|
);
|
||||||
|
ctx.font = "bold 20px Arial";
|
||||||
|
const agentWidth = ctx.measureText(agentText).width;
|
||||||
|
ctx.fillText(
|
||||||
|
agentText,
|
||||||
|
(CANVAS_WIDTH - agentWidth) / 2, // Center horizontally
|
||||||
|
CANVAS_HEIGHT * 0.41,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const descriptionText =
|
const descriptionText =
|
||||||
claimData.claim.name || claimData.claim.description;
|
claimData.claim.name ||
|
||||||
|
claimData.claim.description ||
|
||||||
|
claimData.claim.itemOffered?.description; // for Offers
|
||||||
if (descriptionText) {
|
if (descriptionText) {
|
||||||
const descriptionLine =
|
const descriptionLine =
|
||||||
descriptionText.length > 50
|
descriptionText.length > 50
|
||||||
@@ -164,12 +192,12 @@ export default class ClaimCertificateView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
const possibleObject =
|
||||||
claimData.claim.object?.amountOfThisGood &&
|
claimData.claim.object || // for GiveActions
|
||||||
claimData.claim.object?.unitCode
|
claimData.claim.includesObject; // for Offers
|
||||||
) {
|
if (possibleObject?.amountOfThisGood && possibleObject?.unitCode) {
|
||||||
const amount = claimData.claim.object.amountOfThisGood;
|
const amount = possibleObject.amountOfThisGood;
|
||||||
const unit = claimData.claim.object.unitCode;
|
const unit = possibleObject.unitCode;
|
||||||
const amountText = serverUtil.displayAmount(unit, amount);
|
const amountText = serverUtil.displayAmount(unit, amount);
|
||||||
const amountWidth = ctx.measureText(amountText).width;
|
const amountWidth = ctx.measureText(amountText).width;
|
||||||
// if there was no description then put this in that spot, otherwise put it below the description
|
// if there was no description then put this in that spot, otherwise put it below the description
|
||||||
|
|||||||
@@ -203,8 +203,12 @@ import "leaflet/dist/leaflet.css";
|
|||||||
import { AxiosError, AxiosRequestHeaders } from "axios";
|
import { AxiosError, AxiosRequestHeaders } from "axios";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import { hexToBytes } from "@noble/hashes/utils";
|
import { hexToBytes } from "@noble/hashes/utils";
|
||||||
import type { EventTemplate, VerifiedEvent } from "nostr-tools/lib/types/core";
|
// these core imports could also be included as "import type ..."
|
||||||
import { accountFromSeedWords } from "nostr-tools/nip06";
|
import { EventTemplate, UnsignedEvent, VerifiedEvent } from "nostr-tools/core";
|
||||||
|
import {
|
||||||
|
accountFromExtendedKey,
|
||||||
|
extendedKeysFromSeedWords,
|
||||||
|
} from "nostr-tools/nip06";
|
||||||
import { finalizeEvent, serializeEvent } from "nostr-tools/pure";
|
import { finalizeEvent, serializeEvent } from "nostr-tools/pure";
|
||||||
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";
|
||||||
@@ -225,7 +229,6 @@ import {
|
|||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import {
|
import {
|
||||||
retrieveAccountCount,
|
retrieveAccountCount,
|
||||||
retrieveAccountMetadata,
|
|
||||||
retrieveFullyDecryptedAccount,
|
retrieveFullyDecryptedAccount,
|
||||||
} from "@/libs/util";
|
} from "@/libs/util";
|
||||||
|
|
||||||
@@ -472,31 +475,45 @@ export default class NewEditProjectView extends Vue {
|
|||||||
try {
|
try {
|
||||||
const resp = await this.axios.post(url, payload, { headers });
|
const resp = await this.axios.post(url, payload, { headers });
|
||||||
if (resp.data?.success?.handleId) {
|
if (resp.data?.success?.handleId) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Saved",
|
||||||
|
text: "The project was saved successfully.",
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
|
||||||
this.errorMessage = "";
|
this.errorMessage = "";
|
||||||
|
|
||||||
const projectPath = encodeURIComponent(resp.data.success.handleId);
|
const projectPath = encodeURIComponent(resp.data.success.handleId);
|
||||||
|
|
||||||
if (this.sendToTrustroots || this.sendToTripHopping) {
|
if (this.sendToTrustroots || this.sendToTripHopping) {
|
||||||
if (this.latitude && this.longitude) {
|
if (this.latitude && this.longitude) {
|
||||||
let signedPayload: VerifiedEvent | undefined; // sign something to prove ownership of pubkey
|
let payloadAndKey; // sign something to prove ownership of pubkey
|
||||||
if (this.sendToTrustroots) {
|
if (this.sendToTrustroots) {
|
||||||
signedPayload = await this.signPayload();
|
payloadAndKey = await this.signSomePayload();
|
||||||
|
// not going to await... the save was successful, so we'll continue to the next page
|
||||||
this.sendToNostrPartner(
|
this.sendToNostrPartner(
|
||||||
"NOSTR-EVENT-TRUSTROOTS",
|
"NOSTR-EVENT-TRUSTROOTS",
|
||||||
"Trustroots",
|
"Trustroots",
|
||||||
resp.data.success.claimId,
|
resp.data.success.claimId,
|
||||||
signedPayload,
|
payloadAndKey.signedEvent,
|
||||||
|
payloadAndKey.publicExtendedKey,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.sendToTripHopping) {
|
if (this.sendToTripHopping) {
|
||||||
if (!signedPayload) {
|
if (!payloadAndKey) {
|
||||||
signedPayload = await this.signPayload();
|
payloadAndKey = await this.signSomePayload();
|
||||||
}
|
}
|
||||||
|
// not going to await... the save was successful, so we'll continue to the next page
|
||||||
this.sendToNostrPartner(
|
this.sendToNostrPartner(
|
||||||
"NOSTR-EVENT-TRIPHOPPING",
|
"NOSTR-EVENT-TRIPHOPPING",
|
||||||
"TripHopping",
|
"TripHopping",
|
||||||
resp.data.success.claimId,
|
resp.data.success.claimId,
|
||||||
signedPayload,
|
payloadAndKey.signedEvent,
|
||||||
|
payloadAndKey.publicExtendedKey,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -576,19 +593,28 @@ export default class NewEditProjectView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async signPayload(): Promise<VerifiedEvent> {
|
/**
|
||||||
|
* @return a signed payload and an extended public key for later transmission
|
||||||
|
*/
|
||||||
|
private async signSomePayload(): Promise<{
|
||||||
|
signedEvent: VerifiedEvent;
|
||||||
|
publicExtendedKey: string;
|
||||||
|
}> {
|
||||||
const account = await retrieveFullyDecryptedAccount(this.activeDid);
|
const account = await retrieveFullyDecryptedAccount(this.activeDid);
|
||||||
// get the last number of the derivationPath
|
// get the last number of the derivationPath
|
||||||
const finalDerNum = account?.derivationPath?.split?.("/")?.reverse()[0];
|
const finalDerNum = account?.derivationPath?.split?.("/")?.reverse()[0];
|
||||||
// remove any trailing '
|
// remove any trailing '
|
||||||
const finalDerNumNoApostrophe = finalDerNum?.replace(/'/g, "");
|
const finalDerNumNoApostrophe = finalDerNum?.replace(/'/g, "");
|
||||||
const accountNum = Number(finalDerNumNoApostrophe || 0);
|
const accountNum = Number(finalDerNumNoApostrophe || 0);
|
||||||
const pubPri = accountFromSeedWords(
|
const extPubPri = extendedKeysFromSeedWords(
|
||||||
account?.mnemonic as string,
|
account?.mnemonic as string,
|
||||||
"",
|
"",
|
||||||
accountNum,
|
accountNum,
|
||||||
);
|
);
|
||||||
const privateBytes = hexToBytes(pubPri?.privateKey);
|
const publicExtendedKey: string = extPubPri?.publicExtendedKey;
|
||||||
|
const privateExtendedKey = extPubPri?.privateExtendedKey;
|
||||||
|
const privateKey = accountFromExtendedKey(privateExtendedKey).privateKey;
|
||||||
|
const privateBytes = hexToBytes(privateKey);
|
||||||
// No real content is necessary, we just want something signed,
|
// No real content is necessary, we just want something signed,
|
||||||
// so we might as well use nostr libs for nostr functions.
|
// so we might as well use nostr libs for nostr functions.
|
||||||
// Besides: someday we may create real content that we can relay.
|
// Besides: someday we may create real content that we can relay.
|
||||||
@@ -598,9 +624,12 @@ export default class NewEditProjectView extends Vue {
|
|||||||
content: "",
|
content: "",
|
||||||
created_at: 0,
|
created_at: 0,
|
||||||
};
|
};
|
||||||
// Why does IntelliJ not see matching types?
|
const signedEvent: VerifiedEvent = finalizeEvent(
|
||||||
const signedEvent = finalizeEvent(event, privateBytes);
|
// Why does IntelliJ not see matching types?
|
||||||
return signedEvent;
|
event as EventTemplate,
|
||||||
|
privateBytes,
|
||||||
|
) as VerifiedEvent;
|
||||||
|
return { signedEvent, publicExtendedKey };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async sendToNostrPartner(
|
private async sendToNostrPartner(
|
||||||
@@ -608,41 +637,37 @@ export default class NewEditProjectView extends Vue {
|
|||||||
serviceName: string,
|
serviceName: string,
|
||||||
jwtId: string,
|
jwtId: string,
|
||||||
signedPayload: VerifiedEvent,
|
signedPayload: VerifiedEvent,
|
||||||
|
publicExtendedKey: string,
|
||||||
) {
|
) {
|
||||||
// first, get the public key for nostr
|
|
||||||
const account = await retrieveAccountMetadata(this.activeDid);
|
|
||||||
// get the last number of the derivationPath
|
|
||||||
const finalDerNum = account?.derivationPath?.split?.("/")?.reverse()[0];
|
|
||||||
// remove any trailing '
|
|
||||||
const finalDerNumNoApostrophe = finalDerNum?.replace(/'/g, "");
|
|
||||||
const accountNum = Number(finalDerNumNoApostrophe || 0);
|
|
||||||
const pubPri = accountFromSeedWords(
|
|
||||||
account?.mnemonic as string,
|
|
||||||
"",
|
|
||||||
accountNum,
|
|
||||||
);
|
|
||||||
const nostrPubKey = pubPri?.publicKey;
|
|
||||||
|
|
||||||
let partnerServer = DEFAULT_PARTNER_API_SERVER;
|
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
|
||||||
if (settings.partnerApiServer) {
|
|
||||||
partnerServer = settings.partnerApiServer;
|
|
||||||
}
|
|
||||||
const endorserPartnerUrl = partnerServer + "/api/partner/link";
|
|
||||||
const timeSafariUrl = window.location.origin + "/claim/" + jwtId;
|
|
||||||
const content = this.fullClaim.name + " - see " + timeSafariUrl;
|
|
||||||
// Why does IntelliJ not see matching types?
|
|
||||||
const payload = serializeEvent(signedPayload);
|
|
||||||
const partnerParams = {
|
|
||||||
jwtId: jwtId,
|
|
||||||
linkCode: linkCode,
|
|
||||||
inputJson: JSON.stringify(content),
|
|
||||||
pubKeyHex: nostrPubKey,
|
|
||||||
pubKeyImage: payload,
|
|
||||||
pubKeySigHex: signedPayload.sig,
|
|
||||||
};
|
|
||||||
const headers = await getHeaders(this.activeDid);
|
|
||||||
try {
|
try {
|
||||||
|
let partnerServer = DEFAULT_PARTNER_API_SERVER;
|
||||||
|
const settings = await retrieveSettingsForActiveAccount();
|
||||||
|
if (settings.partnerApiServer) {
|
||||||
|
partnerServer = settings.partnerApiServer;
|
||||||
|
}
|
||||||
|
const endorserPartnerUrl = partnerServer + "/api/partner/link";
|
||||||
|
const timeSafariUrl = window.location.origin + "/claim/" + jwtId;
|
||||||
|
const content = this.fullClaim.name + " - see " + timeSafariUrl;
|
||||||
|
const publicKeyHex = accountFromExtendedKey(publicExtendedKey).publicKey;
|
||||||
|
const unsignedPayload: UnsignedEvent = {
|
||||||
|
// why doesn't "...signedPayload" work?
|
||||||
|
kind: signedPayload.kind,
|
||||||
|
tags: signedPayload.tags,
|
||||||
|
content: signedPayload.content,
|
||||||
|
created_at: signedPayload.created_at,
|
||||||
|
pubkey: publicKeyHex,
|
||||||
|
};
|
||||||
|
// Why does IntelliJ not see matching types?
|
||||||
|
const payload = serializeEvent(unsignedPayload as UnsignedEvent);
|
||||||
|
const partnerParams = {
|
||||||
|
jwtId: jwtId,
|
||||||
|
linkCode: linkCode,
|
||||||
|
inputJson: JSON.stringify(content),
|
||||||
|
pubKeyHex: publicKeyHex,
|
||||||
|
pubKeyImage: payload,
|
||||||
|
pubKeySigHex: signedPayload.sig,
|
||||||
|
};
|
||||||
|
const headers = await getHeaders(this.activeDid);
|
||||||
const linkResp = await this.axios.post(
|
const linkResp = await this.axios.post(
|
||||||
endorserPartnerUrl,
|
endorserPartnerUrl,
|
||||||
partnerParams,
|
partnerParams,
|
||||||
@@ -731,7 +756,7 @@ export default class NewEditProjectView extends Vue {
|
|||||||
group: "alert",
|
group: "alert",
|
||||||
type: "info",
|
type: "info",
|
||||||
title: "About Nostr Events",
|
title: "About Nostr Events",
|
||||||
text: "This will cause a submission to a partner on the nostr network. It will contain your public key data which may allow correlation, so don't allow this if you're not comfortable with that.",
|
text: "This will submit this project to a partner on the nostr network. It will contain your public key data which may allow correlation, so don't allow this if you're not comfortable with that.",
|
||||||
},
|
},
|
||||||
7000,
|
7000,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user