test: enhance deep link testing with real JWT examples
Changes: - Add real JWT example for invite testing - Add detailed JWT payload documentation - Update test-deeplinks.sh with valid claim IDs - Add test case for single contact invite - Improve test descriptions and organization This improves test coverage by using real-world JWT examples and valid claim identifiers.
This commit is contained in:
@@ -3,7 +3,10 @@
|
||||
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
||||
<!-- Back -->
|
||||
<div class="text-lg text-center font-light relative px-7">
|
||||
<h1 class="text-lg text-center px-2 py-1 absolute -left-2 -top-1" @click="$router.back()">
|
||||
<h1
|
||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||
@click="$router.back()"
|
||||
>
|
||||
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||
</h1>
|
||||
</div>
|
||||
@@ -17,28 +20,43 @@
|
||||
<font-awesome icon="spinner" class="animate-spin" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<span v-if="contactsImporting.length > sameCount" class="flex justify-center">
|
||||
<span
|
||||
v-if="contactsImporting.length > sameCount"
|
||||
class="flex justify-center"
|
||||
>
|
||||
<input v-model="makeVisible" type="checkbox" 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>
|
||||
<span v-else>{{ sameCount }} contacts are the same as existing contacts</span>
|
||||
<span v-if="sameCount == 1"
|
||||
>One contact is the same as an existing contact</span
|
||||
>
|
||||
<span v-else
|
||||
>{{ sameCount }} contacts are the same as existing contacts</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Results List -->
|
||||
<ul v-if="contactsImporting.length > sameCount" class="border-t border-slate-300">
|
||||
<ul
|
||||
v-if="contactsImporting.length > sameCount"
|
||||
class="border-t border-slate-300"
|
||||
>
|
||||
<li v-for="(contact, index) in contactsImporting" :key="contact.did">
|
||||
<div v-if="
|
||||
!contactsExisting[contact.did] ||
|
||||
!R.isEmpty(contactDifferences[contact.did])
|
||||
" class="grow overflow-hidden border-b border-slate-300 pt-2.5 pb-4">
|
||||
<div
|
||||
v-if="
|
||||
!contactsExisting[contact.did] ||
|
||||
!R.isEmpty(contactDifferences[contact.did])
|
||||
"
|
||||
class="grow overflow-hidden border-b border-slate-300 pt-2.5 pb-4"
|
||||
>
|
||||
<h2 class="text-base font-semibold">
|
||||
<input v-model="contactsSelected[index]" type="checkbox" />
|
||||
{{ contact.name || AppString.NO_CONTACT_NAME }}
|
||||
-
|
||||
<span v-if="contactsExisting[contact.did]" class="text-orange-500">Existing</span>
|
||||
<span v-if="contactsExisting[contact.did]" class="text-orange-500"
|
||||
>Existing</span
|
||||
>
|
||||
<span v-else class="text-green-500">New</span>
|
||||
</h2>
|
||||
<div class="text-sm truncate">
|
||||
@@ -51,9 +69,13 @@
|
||||
<div class="font-bold">Old Value</div>
|
||||
<div class="font-bold">New Value</div>
|
||||
</div>
|
||||
<div v-for="(value, contactField) in contactDifferences[
|
||||
contact.did
|
||||
]" :key="contactField" class="grid grid-cols-3 border">
|
||||
<div
|
||||
v-for="(value, contactField) in contactDifferences[
|
||||
contact.did
|
||||
]"
|
||||
:key="contactField"
|
||||
class="grid grid-cols-3 border"
|
||||
>
|
||||
<div class="border font-bold p-1">
|
||||
{{ capitalizeAndInsertSpacesBeforeCaps(contactField) }}
|
||||
</div>
|
||||
@@ -66,7 +88,8 @@
|
||||
</li>
|
||||
<button
|
||||
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
|
||||
</button>
|
||||
</ul>
|
||||
@@ -78,10 +101,18 @@
|
||||
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)" />
|
||||
<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 class="ml-2 p-2 bg-blue-500 text-white rounded" @click="() => processContactJwt(inputJwt)">
|
||||
<button
|
||||
class="ml-2 p-2 bg-blue-500 text-white rounded"
|
||||
@click="() => processContactJwt(inputJwt)"
|
||||
>
|
||||
Check Import
|
||||
</button>
|
||||
</div>
|
||||
@@ -94,26 +125,26 @@
|
||||
/**
|
||||
* @file Contact Import View Component
|
||||
* @author Matthew Raymer
|
||||
*
|
||||
*
|
||||
* This component handles the import of contacts into the TimeSafari app.
|
||||
* It supports multiple import methods and handles duplicate detection,
|
||||
* contact validation, and visibility settings.
|
||||
*
|
||||
*
|
||||
* Import Methods:
|
||||
* 1. Direct URL Query Parameters:
|
||||
* Example: /contact-import?contacts=[{"did":"did:example:123","name":"Alice"}]
|
||||
*
|
||||
*
|
||||
* 2. JWT in URL Path:
|
||||
* Example: /contact-import/eyJhbGciOiJFUzI1NksifQ...
|
||||
* - Supports both single and bulk imports
|
||||
* - JWT payload can be either:
|
||||
* a) Array format: { contacts: [{did: "...", name: "..."}, ...] }
|
||||
* b) Single contact: { own: true, did: "...", name: "..." }
|
||||
*
|
||||
*
|
||||
* 3. Manual JWT Input:
|
||||
* - Accepts pasted JWT strings
|
||||
* - Validates format and content before processing
|
||||
*
|
||||
*
|
||||
* URL Examples:
|
||||
* ```
|
||||
* # Bulk import via query params
|
||||
@@ -121,35 +152,35 @@
|
||||
* {"did":"did:example:123","name":"Alice"},
|
||||
* {"did":"did:example:456","name":"Bob"}
|
||||
* ]
|
||||
*
|
||||
*
|
||||
* # Single contact via JWT
|
||||
* /contact-import/eyJhbGciOiJFUzI1NksifQ.eyJvd24iOnRydWUsImRpZCI6ImRpZDpleGFtcGxlOjEyMyJ9...
|
||||
*
|
||||
*
|
||||
* # Bulk import via JWT
|
||||
* /contact-import/eyJhbGciOiJFUzI1NksifQ.eyJjb250YWN0cyI6W3siZGlkIjoiZGlkOmV4YW1wbGU6MTIzIn1dfQ...
|
||||
*
|
||||
*
|
||||
* # Redirect to contacts page (single contact)
|
||||
* /contacts?contactJwt=eyJhbGciOiJFUzI1NksifQ...
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Features:
|
||||
* - Automatic duplicate detection
|
||||
* - Field-by-field comparison for existing contacts
|
||||
* - Batch visibility settings
|
||||
* - Auto-import for single new contacts
|
||||
* - Error handling and validation
|
||||
*
|
||||
*
|
||||
* State Management:
|
||||
* - Tracks existing contacts
|
||||
* - Maintains selection state for bulk imports
|
||||
* - Records differences for duplicate contacts
|
||||
* - Manages visibility settings
|
||||
*
|
||||
*
|
||||
* Security Considerations:
|
||||
* - JWT validation for imported contacts
|
||||
* - Visibility control per contact
|
||||
* - Error handling for malformed data
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // Component usage in router
|
||||
* {
|
||||
@@ -157,7 +188,7 @@
|
||||
* name: "contact-import",
|
||||
* component: ContactImportView
|
||||
* }
|
||||
*
|
||||
*
|
||||
* @see {@link Contact} for contact data structure
|
||||
* @see {@link setVisibilityUtil} for visibility management
|
||||
*/
|
||||
@@ -188,21 +219,21 @@ import { decodeEndorserJwt } from "../libs/crypto/vc";
|
||||
/**
|
||||
* Contact Import View Component
|
||||
* @author Matthew Raymer
|
||||
*
|
||||
*
|
||||
* This component handles the secure import of contacts into TimeSafari via JWT tokens.
|
||||
* It supports both single and multiple contact imports with validation and duplicate detection.
|
||||
*
|
||||
*
|
||||
* Import Workflows:
|
||||
* 1. JWT in URL Path (/contact-import/[JWT])
|
||||
* - Extracts JWT from path
|
||||
* - Decodes and validates contact data
|
||||
* - Handles both single and multiple contacts
|
||||
*
|
||||
*
|
||||
* 2. JWT in Query Parameter (/contacts?contactJwt=[JWT])
|
||||
* - Used for single contact redirects
|
||||
* - Processes JWT from query parameter
|
||||
* - Redirects to appropriate view
|
||||
*
|
||||
*
|
||||
* JWT Payload Structure:
|
||||
* ```json
|
||||
* {
|
||||
@@ -216,13 +247,13 @@ import { decodeEndorserJwt } from "../libs/crypto/vc";
|
||||
* "iss": "did:ethr:0x..."
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Security Features:
|
||||
* - JWT validation
|
||||
* - Issuer verification
|
||||
* - Duplicate detection
|
||||
* - Contact data validation
|
||||
*
|
||||
*
|
||||
* @component
|
||||
*/
|
||||
@Component({
|
||||
@@ -275,23 +306,23 @@ export default class ContactImportView extends Vue {
|
||||
|
||||
/**
|
||||
* Component lifecycle hook that initializes the contact import process
|
||||
*
|
||||
*
|
||||
* This method handles three distinct import scenarios:
|
||||
* 1. Query Parameter Import:
|
||||
* - Checks for contacts in URL query parameters
|
||||
* - Parses JSON array of contacts if present
|
||||
*
|
||||
*
|
||||
* 2. JWT URL Import:
|
||||
* - Extracts JWT from URL path using regex pattern '/contact-import/(ey.+)$'
|
||||
* - Decodes JWT without validation (supports future-dated QR codes)
|
||||
* - Handles two JWT payload formats:
|
||||
* a. Array format: payload.contacts or direct array
|
||||
* b. Single contact format: redirects to contacts page with JWT
|
||||
*
|
||||
*
|
||||
* 3. Auto-Import Logic:
|
||||
* - Automatically imports if exactly one new contact is present
|
||||
* - Only triggers if no existing contacts match
|
||||
*
|
||||
*
|
||||
* @throws Will not throw but logs errors during JWT processing
|
||||
* @emits router.push when redirecting for single contact import
|
||||
*/
|
||||
@@ -328,7 +359,7 @@ export default class ContactImportView extends Vue {
|
||||
// JWT tokens always start with 'ey' (base64url encoded header)
|
||||
const JWT_PATTERN = /\/contact-import\/(ey.+)$/;
|
||||
const jwt = window.location.pathname.match(JWT_PATTERN)?.[1];
|
||||
|
||||
|
||||
if (jwt) {
|
||||
const parsedJwt = decodeEndorserJwt(jwt);
|
||||
const contacts: Array<Contact> =
|
||||
@@ -391,7 +422,12 @@ export default class ContactImportView extends Vue {
|
||||
}
|
||||
> = {};
|
||||
Object.keys(contactIn).forEach((key) => {
|
||||
if (!R.equals(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] = {
|
||||
old: existingContact[key as keyof Contact],
|
||||
new: contactIn[key as keyof Contact],
|
||||
@@ -517,8 +553,9 @@ export default class ContactImportView extends Vue {
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Visibility Error",
|
||||
text: `Failed to set visibility for ${failedVisibileToContacts.length} contact${failedVisibileToContacts.length == 1 ? "" : "s"
|
||||
}. You must set them individually: ${failedVisibileToContacts.map((c) => c.name).join(", ")}`,
|
||||
text: `Failed to set visibility for ${failedVisibileToContacts.length} contact${
|
||||
failedVisibileToContacts.length == 1 ? "" : "s"
|
||||
}. You must set them individually: ${failedVisibileToContacts.map((c) => c.name).join(", ")}`,
|
||||
},
|
||||
-1,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user