forked from jsnbuchanan/crowd-funder-for-time-pwa
add sanity checks for importing bulk contacts, eg. when there is a truncated link
This commit is contained in:
@@ -103,10 +103,9 @@ export const accessToken = async (did?: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@return payload of JWT pulled out of the URL and decoded:
|
@return payload of JWT pulled out of any recognized URL path (if any) and decoded:
|
||||||
{ iat: number, iss: string (DID), own: { name, publicEncKey (base64-encoded key) } }
|
{ iat: number, iss: string (DID), own: { name, publicEncKey (base64-encoded key) } }
|
||||||
|
... or an array of such as { contacts: [ contact, ... ] }
|
||||||
Result may be a single contact or it may be { contacts: [ contact, ... ] }
|
|
||||||
*/
|
*/
|
||||||
export const getContactPayloadFromJwtUrl = (jwtUrlText: string) => {
|
export const getContactPayloadFromJwtUrl = (jwtUrlText: string) => {
|
||||||
let jwtText = jwtUrlText;
|
let jwtText = jwtUrlText;
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ function bytesToHex(b: Uint8Array): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We should be calling 'verify' in more places, showing warnings if it fails.
|
// We should be calling 'verify' in more places, showing warnings if it fails.
|
||||||
// @returns JWTDecoded with { header: JWTHeader, payload: string, signature: string, data: string } (but doesn't verify the signature)
|
// @returns JWTDecoded with { header: JWTHeader, payload: any, signature: string, data: string } (but doesn't verify the signature)
|
||||||
export function decodeEndorserJwt(jwt: string) {
|
export function decodeEndorserJwt(jwt: string) {
|
||||||
return didJwt.decodeJWT(jwt);
|
return didJwt.decodeJWT(jwt);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,10 @@
|
|||||||
Contact Import
|
Contact Import
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
<div v-if="checkingImports" class="text-center">
|
||||||
|
<fa icon="spinner" class="animate-spin" />
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
<span
|
<span
|
||||||
v-if="contactsImporting.length > sameCount"
|
v-if="contactsImporting.length > sameCount"
|
||||||
class="flex justify-center"
|
class="flex justify-center"
|
||||||
@@ -61,16 +65,20 @@
|
|||||||
<div v-if="contactDifferences[contact.did]">
|
<div v-if="contactDifferences[contact.did]">
|
||||||
<div>
|
<div>
|
||||||
<div class="grid grid-cols-3 gap-2">
|
<div class="grid grid-cols-3 gap-2">
|
||||||
<div class="font-bold">Field</div>
|
<div></div>
|
||||||
<div class="font-bold">Old Value</div>
|
<div class="font-bold">Old Value</div>
|
||||||
<div class="font-bold">New Value</div>
|
<div class="font-bold">New Value</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="(value, contactField) in contactDifferences[contact.did]"
|
v-for="(value, contactField) in contactDifferences[
|
||||||
|
contact.did
|
||||||
|
]"
|
||||||
:key="contactField"
|
:key="contactField"
|
||||||
class="grid grid-cols-3 border"
|
class="grid grid-cols-3 border"
|
||||||
>
|
>
|
||||||
<div class="border p-1">{{ contactField }}</div>
|
<div class="border font-bold p-1">
|
||||||
|
{{ capitalizeAndInsertSpacesBeforeCaps(contactField) }}
|
||||||
|
</div>
|
||||||
<div class="border p-1">{{ value.old }}</div>
|
<div class="border p-1">{{ value.old }}</div>
|
||||||
<div class="border p-1">{{ value.new }}</div>
|
<div class="border p-1">{{ value.new }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,34 +86,65 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<fa icon="spinner" v-if="importing" class="animate-spin" />
|
|
||||||
<button
|
<button
|
||||||
v-else
|
|
||||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-sm text-white mt-2 px-2 py-1.5 rounded"
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-sm text-white mt-2 px-2 py-1.5 rounded"
|
||||||
@click="importContacts"
|
@click="importContacts"
|
||||||
>
|
>
|
||||||
Import Selected Contacts
|
Import Selected Contacts
|
||||||
</button>
|
</button>
|
||||||
</ul>
|
</ul>
|
||||||
<p v-else>There are no contacts to import.</p>
|
<p v-else-if="contactsImporting.length > 0">
|
||||||
|
All those contacts are already in your list with the same information.
|
||||||
|
</p>
|
||||||
|
<div v-else>
|
||||||
|
There are no contacts in that import. If some were sent, try again to
|
||||||
|
get the full text and paste it. (Note that iOS cuts off data in text
|
||||||
|
messages.) Ask the person to send the data a different way, eg. email.
|
||||||
|
<div class="mt-4 text-center">
|
||||||
|
<textarea
|
||||||
|
v-model="inputJwt"
|
||||||
|
placeholder="Contact-import data"
|
||||||
|
class="mt-4 border-2 border-gray-300 p-2 rounded"
|
||||||
|
cols="30"
|
||||||
|
@input="() => checkContactJwt(inputJwt)"
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<button
|
||||||
|
@click="() => processContactJwt(inputJwt)"
|
||||||
|
class="ml-2 p-2 bg-blue-500 text-white rounded"
|
||||||
|
>
|
||||||
|
Check Import
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { JWTVerified } from "did-jwt";
|
import { JWTPayload, JWTVerified } from "did-jwt";
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { Router } from "vue-router";
|
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||||
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
import OfferDialog from "@/components/OfferDialog.vue";
|
import OfferDialog from "@/components/OfferDialog.vue";
|
||||||
import { AppString, NotificationIface } from "@/constants/app";
|
import { APP_SERVER, AppString, NotificationIface } from "@/constants/app";
|
||||||
import { db, retrieveSettingsForActiveAccount } from "@/db/index";
|
import {
|
||||||
import { Contact } from "@/db/tables/contacts";
|
db,
|
||||||
|
logConsoleAndDb,
|
||||||
|
retrieveSettingsForActiveAccount,
|
||||||
|
} from "@/db/index";
|
||||||
|
import { Contact, ContactMethod } from "@/db/tables/contacts";
|
||||||
import * as libsUtil from "@/libs/util";
|
import * as libsUtil from "@/libs/util";
|
||||||
import { decodeAndVerifyJwt } from "@/libs/crypto/vc/index";
|
import { decodeAndVerifyJwt } from "@/libs/crypto/vc";
|
||||||
import { setVisibilityUtil } from "@/libs/endorserServer";
|
import {
|
||||||
|
capitalizeAndInsertSpacesBeforeCaps,
|
||||||
|
errorStringForLog,
|
||||||
|
setVisibilityUtil,
|
||||||
|
} from "@/libs/endorserServer";
|
||||||
|
import { getContactPayloadFromJwtUrl } from "@/libs/crypto";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { EntityIcon, OfferDialog, QuickNav },
|
components: { EntityIcon, OfferDialog, QuickNav },
|
||||||
@@ -114,6 +153,7 @@ export default class ContactImportView extends Vue {
|
|||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
AppString = AppString;
|
AppString = AppString;
|
||||||
|
capitalizeAndInsertSpacesBeforeCaps = capitalizeAndInsertSpacesBeforeCaps;
|
||||||
libsUtil = libsUtil;
|
libsUtil = libsUtil;
|
||||||
R = R;
|
R = R;
|
||||||
|
|
||||||
@@ -126,10 +166,14 @@ export default class ContactImportView extends Vue {
|
|||||||
string,
|
string,
|
||||||
Record<
|
Record<
|
||||||
string,
|
string,
|
||||||
{ new: string | boolean | undefined; old: string | boolean | undefined }
|
{
|
||||||
|
new: string | boolean | Array<ContactMethod> | undefined;
|
||||||
|
old: string | boolean | Array<ContactMethod> | undefined;
|
||||||
|
}
|
||||||
>
|
>
|
||||||
> = {}; // for existing contacts, it shows the difference between imported and existing contacts for each key
|
> = {}; // for existing contacts, it shows the difference between imported and existing contacts for each key
|
||||||
importing = false;
|
checkingImports = false;
|
||||||
|
inputJwt: string = "";
|
||||||
makeVisible = true;
|
makeVisible = true;
|
||||||
sameCount = 0;
|
sameCount = 0;
|
||||||
|
|
||||||
@@ -139,9 +183,8 @@ export default class ContactImportView extends Vue {
|
|||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
|
|
||||||
// look for any imported contacts from the query parameter
|
// look for any imported contacts from the query parameter
|
||||||
const importedContacts = (this.$route as Router).query[
|
const importedContacts = (this.$route as RouteLocationNormalizedLoaded)
|
||||||
"contacts"
|
.query["contacts"] as string;
|
||||||
] as string;
|
|
||||||
if (importedContacts) {
|
if (importedContacts) {
|
||||||
await this.setContactsSelected(JSON.parse(importedContacts));
|
await this.setContactsSelected(JSON.parse(importedContacts));
|
||||||
}
|
}
|
||||||
@@ -176,13 +219,13 @@ export default class ContactImportView extends Vue {
|
|||||||
const differences: Record<
|
const differences: Record<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
new: string | boolean | undefined;
|
new: string | boolean | Array<ContactMethod> | undefined;
|
||||||
old: string | boolean | undefined;
|
old: string | boolean | Array<ContactMethod> | undefined;
|
||||||
}
|
}
|
||||||
> = {};
|
> = {};
|
||||||
Object.keys(contactIn).forEach((key) => {
|
Object.keys(contactIn).forEach((key) => {
|
||||||
// eslint-disable-next-line prettier/prettier
|
// eslint-disable-next-line prettier/prettier
|
||||||
if (contactIn[key as keyof Contact] !== existingContact[key as keyof Contact]) {
|
if (!R.equals(contactIn[key as keyof Contact], existingContact[key as keyof Contact])) {
|
||||||
differences[key] = {
|
differences[key] = {
|
||||||
old: existingContact[key as keyof Contact],
|
old: existingContact[key as keyof Contact],
|
||||||
new: contactIn[key as keyof Contact],
|
new: contactIn[key as keyof Contact],
|
||||||
@@ -200,8 +243,56 @@ export default class ContactImportView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check the contact-import JWT
|
||||||
|
async checkContactJwt(jwtInput: string) {
|
||||||
|
if (
|
||||||
|
jwtInput.endsWith(APP_SERVER) ||
|
||||||
|
jwtInput.endsWith(APP_SERVER + "/") ||
|
||||||
|
jwtInput.endsWith("contact-import") ||
|
||||||
|
jwtInput.endsWith("contact-import/")
|
||||||
|
) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: "That is only part of the contact-import data; it's missing data at the end. Try another way to get the full data.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// process the invite JWT and/or text message containing the URL with the JWT
|
||||||
|
async processContactJwt(jwtInput: string) {
|
||||||
|
this.checkingImports = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// (For another approach used with invites, see InviteOneAcceptView.processInvite)
|
||||||
|
const payload: JWTPayload = getContactPayloadFromJwtUrl(jwtInput);
|
||||||
|
if (Array.isArray(payload.contacts)) {
|
||||||
|
await this.setContactsSelected(payload.contacts);
|
||||||
|
} else {
|
||||||
|
throw new Error("Invalid contact-import JWT or URL: " + jwtInput);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const fullError = "Error importing contacts: " + errorStringForLog(error);
|
||||||
|
logConsoleAndDb(fullError, true);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: "There was an error processing the contact-import data.",
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.checkingImports = false;
|
||||||
|
}
|
||||||
|
|
||||||
async importContacts() {
|
async importContacts() {
|
||||||
this.importing = true;
|
this.checkingImports = true;
|
||||||
let importedCount = 0,
|
let importedCount = 0,
|
||||||
updatedCount = 0;
|
updatedCount = 0;
|
||||||
for (let i = 0; i < this.contactsImporting.length; i++) {
|
for (let i = 0; i < this.contactsImporting.length; i++) {
|
||||||
@@ -253,7 +344,7 @@ export default class ContactImportView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.importing = false;
|
this.checkingImports = false;
|
||||||
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
if (Array.isArray(payload.contacts)) {
|
if (Array.isArray(payload.contacts)) {
|
||||||
// reroute to the ContactsImport
|
// reroute to the ContactsImport
|
||||||
(this.$router as Router).push({
|
(this.$router as Router).push({
|
||||||
path: "/contacts-import/" + url.substring(url.lastIndexOf("/") + 1),
|
path: "/contact-import/" + url.substring(url.lastIndexOf("/") + 1),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,9 +69,9 @@
|
|||||||
|
|
||||||
<div class="flex justify-between" v-if="contacts.length > 0">
|
<div class="flex justify-between" v-if="contacts.length > 0">
|
||||||
<div class="w-full text-left">
|
<div class="w-full text-left">
|
||||||
|
<div v-if="!showGiveNumbers">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
v-if="!showGiveNumbers"
|
|
||||||
:checked="contactsSelected.length === contacts.length"
|
:checked="contactsSelected.length === contacts.length"
|
||||||
@click="
|
@click="
|
||||||
contactsSelected.length === contacts.length
|
contactsSelected.length === contacts.length
|
||||||
@@ -95,6 +95,10 @@
|
|||||||
>
|
>
|
||||||
Copy Selections
|
Copy Selections
|
||||||
</button>
|
</button>
|
||||||
|
<button @click="showCopySelectionsInfo()">
|
||||||
|
<fa icon="circle-info" class="text-xl text-blue-500 ml-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full text-right">
|
<div class="w-full text-right">
|
||||||
@@ -883,7 +887,7 @@ export default class ContactsView extends Vue {
|
|||||||
if (Array.isArray(payload.contacts)) {
|
if (Array.isArray(payload.contacts)) {
|
||||||
// reroute to the ContactsImport
|
// reroute to the ContactsImport
|
||||||
(this.$router as Router).push({
|
(this.$router as Router).push({
|
||||||
path: "/contacts-import/" + url.substring(url.lastIndexOf("/") + 1),
|
path: "/contact-import/" + url.substring(url.lastIndexOf("/") + 1),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1349,5 +1353,17 @@ export default class ContactsView extends Vue {
|
|||||||
return did.substring(0, did.indexOf(":", 4) + 7) + "...";
|
return did.substring(0, did.indexOf(":", 4) + 7) + "...";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private showCopySelectionsInfo() {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "info",
|
||||||
|
title: "Copying Contacts",
|
||||||
|
text: "Contact info will include name, ID, profile image, and public key.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<QuickNav selected="Invite" />
|
<QuickNav selected="Invite" />
|
||||||
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
||||||
<div v-if="acceptInput" class="text-center mt-4">
|
<div
|
||||||
|
v-if="checkingInvite"
|
||||||
|
class="text-lg text-center font-light relative px-7"
|
||||||
|
>
|
||||||
|
<fa icon="spinner" class="fa-spin-pulse" />
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-center mt-4">
|
||||||
<p>That invitation did not work.</p>
|
<p>That invitation did not work.</p>
|
||||||
<p class="mt-2">
|
<p class="mt-2">
|
||||||
Go back to your invite message and copy the entire text, then paste it
|
Go back to your invite message and copy the entire text, then paste it
|
||||||
here.
|
here.
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-2">
|
<p class="mt-2">
|
||||||
If the link looks correct, try Chrome. (For example, iOS may have cut
|
If the data looks correct, try Chrome. (For example, iOS may have cut
|
||||||
off the invite data, or it may have shown a preview that stole your
|
off the invite data, or it may have shown a preview that stole your
|
||||||
invite.) If it still complains, you may need the person who invited you
|
invite.) If it still complains, you may need the person who invited you
|
||||||
to send a new one.
|
to send a new one.
|
||||||
@@ -25,16 +31,9 @@
|
|||||||
@click="() => processInvite(inputJwt, true)"
|
@click="() => processInvite(inputJwt, true)"
|
||||||
class="ml-2 p-2 bg-blue-500 text-white rounded"
|
class="ml-2 p-2 bg-blue-500 text-white rounded"
|
||||||
>
|
>
|
||||||
Submit
|
Accept
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-if="checkingInvite"
|
|
||||||
class="text-lg text-center font-light relative px-7"
|
|
||||||
>
|
|
||||||
<fa icon="spinner" class="fa-spin-pulse" />
|
|
||||||
Loading…
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -43,7 +42,7 @@ import { Component, Vue } from "vue-facing-decorator";
|
|||||||
import { Router } from "vue-router";
|
import { Router } from "vue-router";
|
||||||
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import { NotificationIface } from "@/constants/app";
|
import { APP_SERVER, NotificationIface } from "@/constants/app";
|
||||||
import {
|
import {
|
||||||
db,
|
db,
|
||||||
logConsoleAndDb,
|
logConsoleAndDb,
|
||||||
@@ -57,7 +56,6 @@ import { generateSaveAndActivateIdentity } from "@/libs/util";
|
|||||||
export default class InviteOneAcceptView extends Vue {
|
export default class InviteOneAcceptView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
acceptInput: boolean = false;
|
|
||||||
activeDid: string = "";
|
activeDid: string = "";
|
||||||
apiServer: string = "";
|
apiServer: string = "";
|
||||||
checkingInvite: boolean = true;
|
checkingInvite: boolean = true;
|
||||||
@@ -91,6 +89,7 @@ export default class InviteOneAcceptView extends Vue {
|
|||||||
|
|
||||||
// parse the string: extract the URL or JWT if surrounded by spaces
|
// parse the string: extract the URL or JWT if surrounded by spaces
|
||||||
// and then extract the JWT from the URL
|
// and then extract the JWT from the URL
|
||||||
|
// (For another approach used with contacts, see getContactPayloadFromJwtUrl)
|
||||||
const urlMatch = jwtInput.match(/(https?:\/\/[^\s]+)/);
|
const urlMatch = jwtInput.match(/(https?:\/\/[^\s]+)/);
|
||||||
if (urlMatch && urlMatch[1]) {
|
if (urlMatch && urlMatch[1]) {
|
||||||
// extract the JWT from the URL, meaning any character except "?"
|
// extract the JWT from the URL, meaning any character except "?"
|
||||||
@@ -112,13 +111,12 @@ export default class InviteOneAcceptView extends Vue {
|
|||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Missing invite",
|
title: "Missing Invite",
|
||||||
text: "There was no invite. Paste the entire text that has the link.",
|
text: "There was no invite. Paste the entire text that has the data.",
|
||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.acceptInput = true;
|
|
||||||
} else {
|
} else {
|
||||||
//const payload: JWTPayload =
|
//const payload: JWTPayload =
|
||||||
decodeEndorserJwt(jwt);
|
decodeEndorserJwt(jwt);
|
||||||
@@ -144,7 +142,6 @@ export default class InviteOneAcceptView extends Vue {
|
|||||||
3000,
|
3000,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.acceptInput = true;
|
|
||||||
}
|
}
|
||||||
this.checkingInvite = false;
|
this.checkingInvite = false;
|
||||||
}
|
}
|
||||||
@@ -152,6 +149,8 @@ export default class InviteOneAcceptView extends Vue {
|
|||||||
// check the invite JWT
|
// check the invite JWT
|
||||||
async checkInvite(jwtInput: string) {
|
async checkInvite(jwtInput: string) {
|
||||||
if (
|
if (
|
||||||
|
jwtInput.endsWith(APP_SERVER) ||
|
||||||
|
jwtInput.endsWith(APP_SERVER + "/") ||
|
||||||
jwtInput.endsWith("invite-one-accept") ||
|
jwtInput.endsWith("invite-one-accept") ||
|
||||||
jwtInput.endsWith("invite-one-accept/")
|
jwtInput.endsWith("invite-one-accept/")
|
||||||
) {
|
) {
|
||||||
@@ -160,7 +159,7 @@ export default class InviteOneAcceptView extends Vue {
|
|||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: "That is only part of the invite link; it's missing data at the end. Try another way to get the full link.",
|
text: "That is only part of the invite data; it's missing some at the end. Try another way to get the full data.",
|
||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user