From f33dee9b7a7ccb9f38c07615442e90b706b585c3 Mon Sep 17 00:00:00 2001
From: Trent Larson
Date: Fri, 25 Apr 2025 20:14:30 -0600
Subject: [PATCH 1/4] do not share the invite JWT with the server; always keep
it on the client, generated at will
---
src/views/InviteOneView.vue | 39 ++++++++++++++++---------------
test-playwright/05-invite.spec.ts | 5 ++++
2 files changed, 25 insertions(+), 19 deletions(-)
diff --git a/src/views/InviteOneView.vue b/src/views/InviteOneView.vue
index 563bec4b..f1cbfa36 100644
--- a/src/views/InviteOneView.vue
+++ b/src/views/InviteOneView.vue
@@ -73,17 +73,15 @@
invite.expiresAt > new Date().toISOString()
"
class="text-center text-blue-500 cursor-pointer"
- :title="inviteLink(invite.jwt)"
- @click="
- copyInviteAndNotify(invite.inviteIdentifier, invite.jwt)
- "
+ title="Click to copy invite link"
+ @click="copyInviteAndNotify(invite)"
>
{{ getTruncatedInviteId(invite.inviteIdentifier) }}
+
+
@@ -146,7 +146,7 @@ import { logger } from "../utils/logger";
interface Invite {
inviteIdentifier: string;
expiresAt: string;
- jwt: string;
+ jwt?: string; // only used to store a JWT for testing, after the link is clicked
notes: string;
redeemedAt: string | null;
redeemedBy: string | null;
@@ -220,18 +220,27 @@ export default class InviteOneView extends Vue {
return `${redeemedBy.slice(0, 13)}...${redeemedBy.slice(-3)}`;
}
- inviteLink(jwt: string): string {
+ inviteLink(jwt: string | undefined): string {
+ if (!jwt) return "Click to set JWT link";
return APP_SERVER + "/invite-one-accept/" + jwt;
}
- copyInviteAndNotify(inviteId: string, jwt: string) {
- useClipboard().copy(this.inviteLink(jwt));
+ async copyInviteAndNotify(invite: Invite) {
+ const expiresIn = (new Date(invite.expiresAt).getTime() - Date.now()) / 1000;
+ const inviteJwt = await createInviteJwt(
+ this.activeDid,
+ undefined,
+ invite.inviteIdentifier,
+ expiresIn,
+ );
+ invite.jwt = inviteJwt; // set for testing
+ useClipboard().copy(this.inviteLink(inviteJwt));
this.$notify(
{
group: "alert",
type: "success",
title: "Copied",
- text: "Your clipboard now contains the link for invite " + inviteId,
+ text: "Your clipboard now contains the link for invite " + invite.inviteIdentifier,
},
5000,
);
@@ -295,22 +304,14 @@ export default class InviteOneView extends Vue {
},
};
}
- const expiresIn = (new Date(expiresAt).getTime() - Date.now()) / 1000;
- const inviteJwt = await createInviteJwt(
- this.activeDid,
- undefined,
- inviteIdentifier,
- expiresIn,
- );
await axios.post(
this.apiServer + "/api/userUtil/invite",
- { inviteJwt: inviteJwt, notes: notes },
+ { inviteIdentifier: inviteIdentifier, notes: notes, expiresAt: expiresAt },
{ headers },
);
const newInvite = {
inviteIdentifier: inviteIdentifier,
expiresAt: expiresAt,
- jwt: inviteJwt,
notes: notes,
redeemedAt: null,
redeemedBy: null,
diff --git a/test-playwright/05-invite.spec.ts b/test-playwright/05-invite.spec.ts
index 821f7c39..c0c34668 100644
--- a/test-playwright/05-invite.spec.ts
+++ b/test-playwright/05-invite.spec.ts
@@ -44,6 +44,11 @@ test('Check User 0 can invite someone', async ({ page }) => {
await expect(page.locator('div[role="alert"] button:has-text("Yes")')).toBeHidden();
// check that the invite is in the list
+ const newInviteRow = page.locator(`tr:has(td:has-text("Neighbor ${neighborNum}"))`);
+ await expect(newInviteRow).toBeVisible();
+ // click on the link in the first column, which generates the JWT in the other column
+ await newInviteRow.locator('td:first-child').click();
+
const newInviteLine = page.locator(`td:has-text("Neighbor ${neighborNum}")`);
await expect(newInviteLine).toBeVisible();
// retrieve the link from the title
--
2.30.2
From 4905334ed4e58efca8c785e16d40368502358bc6 Mon Sep 17 00:00:00 2001
From: Trent Larson
Date: Mon, 28 Apr 2025 20:16:30 -0600
Subject: [PATCH 2/4] avoid an HTML complaint about bad formatting
---
src/views/HelpView.vue | 40 ++++++++++++++++++++--------------------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/src/views/HelpView.vue b/src/views/HelpView.vue
index fe4d9555..c7027a86 100644
--- a/src/views/HelpView.vue
+++ b/src/views/HelpView.vue
@@ -502,27 +502,27 @@
then don't use it.
As for data & privacy:
-
-
- If using notifications, a server stores push token data. That can be revoked at any time
- by disabling notifications on the Profile page.
-
-
- If sending images, a server stores them, too. They can be removed by editing the claim
- and deleting them.
-
-
- If sending other partner system data (eg. to Trustroots) a public key and message
- data are stored on a server. Those can be removed via direct personal request.
-
+ If using notifications, a server stores push token data. That can be revoked at any time
+ by disabling notifications on the Profile page.
+
+
+ If sending images, a server stores them, too. They can be removed by editing the claim
+ and deleting them.
+
+
+ If sending other partner system data (eg. to Trustroots) a public key and message
+ data are stored on a server. Those can be removed via direct personal request.
+