Browse Source

do not share the invite JWT with the server; always keep it on the client, generated at will

pull/132/head
Trent Larson 5 days ago
parent
commit
f33dee9b7a
  1. 39
      src/views/InviteOneView.vue
  2. 5
      test-playwright/05-invite.spec.ts

39
src/views/InviteOneView.vue

@ -73,17 +73,15 @@
invite.expiresAt > new Date().toISOString() invite.expiresAt > new Date().toISOString()
" "
class="text-center text-blue-500 cursor-pointer" class="text-center text-blue-500 cursor-pointer"
:title="inviteLink(invite.jwt)" title="Click to copy invite link"
@click=" @click="copyInviteAndNotify(invite)"
copyInviteAndNotify(invite.inviteIdentifier, invite.jwt)
"
> >
{{ getTruncatedInviteId(invite.inviteIdentifier) }} {{ getTruncatedInviteId(invite.inviteIdentifier) }}
</span> </span>
<span <span
v-else v-else
class="text-center text-slate-500 cursor-pointer" class="text-center text-slate-500 cursor-pointer"
:title="inviteLink(invite.jwt)" title="Click to see invite details"
@click=" @click="
showInvite( showInvite(
invite.inviteIdentifier, invite.inviteIdentifier,
@ -120,6 +118,8 @@
@click="deleteInvite(invite.inviteIdentifier, invite.notes)" @click="deleteInvite(invite.inviteIdentifier, invite.notes)"
/> />
</td> </td>
<td class="hidden" :data-testId="invite.jwt" aria-hidden="true">
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -146,7 +146,7 @@ import { logger } from "../utils/logger";
interface Invite { interface Invite {
inviteIdentifier: string; inviteIdentifier: string;
expiresAt: string; expiresAt: string;
jwt: string; jwt?: string; // only used to store a JWT for testing, after the link is clicked
notes: string; notes: string;
redeemedAt: string | null; redeemedAt: string | null;
redeemedBy: string | null; redeemedBy: string | null;
@ -220,18 +220,27 @@ export default class InviteOneView extends Vue {
return `${redeemedBy.slice(0, 13)}...${redeemedBy.slice(-3)}`; 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; return APP_SERVER + "/invite-one-accept/" + jwt;
} }
copyInviteAndNotify(inviteId: string, jwt: string) { async copyInviteAndNotify(invite: Invite) {
useClipboard().copy(this.inviteLink(jwt)); 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( this.$notify(
{ {
group: "alert", group: "alert",
type: "success", type: "success",
title: "Copied", title: "Copied",
text: "Your clipboard now contains the link for invite " + inviteId, text: "Your clipboard now contains the link for invite " + invite.inviteIdentifier,
}, },
5000, 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( await axios.post(
this.apiServer + "/api/userUtil/invite", this.apiServer + "/api/userUtil/invite",
{ inviteJwt: inviteJwt, notes: notes }, { inviteIdentifier: inviteIdentifier, notes: notes, expiresAt: expiresAt },
{ headers }, { headers },
); );
const newInvite = { const newInvite = {
inviteIdentifier: inviteIdentifier, inviteIdentifier: inviteIdentifier,
expiresAt: expiresAt, expiresAt: expiresAt,
jwt: inviteJwt,
notes: notes, notes: notes,
redeemedAt: null, redeemedAt: null,
redeemedBy: null, redeemedBy: null,

5
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(); await expect(page.locator('div[role="alert"] button:has-text("Yes")')).toBeHidden();
// check that the invite is in the list // 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}")`); const newInviteLine = page.locator(`td:has-text("Neighbor ${neighborNum}")`);
await expect(newInviteLine).toBeVisible(); await expect(newInviteLine).toBeVisible();
// retrieve the link from the title // retrieve the link from the title

Loading…
Cancel
Save