|
|
|
<template>
|
|
|
|
<QuickNav />
|
|
|
|
|
|
|
|
<!-- CONTENT -->
|
|
|
|
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
|
|
|
<!-- Breadcrumb -->
|
|
|
|
<div class="mb-8">
|
|
|
|
<!-- 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()"
|
|
|
|
>
|
|
|
|
<fa icon="chevron-left" class="fa-fw"></fa>
|
|
|
|
</h1>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Heading -->
|
|
|
|
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
|
|
|
Test
|
|
|
|
</h1>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<h2 class="text-xl font-bold mb-4">Notiwind Alerts</h2>
|
|
|
|
|
|
|
|
<button
|
|
|
|
@click="
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: 'alert',
|
|
|
|
type: 'toast',
|
|
|
|
text: 'I\'m a toast. Without a timeout, I\'m stuck.',
|
|
|
|
},
|
|
|
|
5000,
|
|
|
|
)
|
|
|
|
"
|
|
|
|
class="font-bold uppercase bg-slate-900 text-white px-3 py-2 rounded-md mr-2"
|
|
|
|
>
|
|
|
|
Toast
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<button
|
|
|
|
@click="
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: 'alert',
|
|
|
|
type: 'info',
|
|
|
|
title: 'Information Alert',
|
|
|
|
text: 'Just wanted you to know.',
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
)
|
|
|
|
"
|
|
|
|
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
|
|
|
|
>
|
|
|
|
Info
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<button
|
|
|
|
@click="
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: 'alert',
|
|
|
|
type: 'success',
|
|
|
|
title: 'Success Alert',
|
|
|
|
text: 'Congratulations!',
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
)
|
|
|
|
"
|
|
|
|
class="font-bold uppercase bg-emerald-600 text-white px-3 py-2 rounded-md mr-2"
|
|
|
|
>
|
|
|
|
Success
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<button
|
|
|
|
@click="
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: 'alert',
|
|
|
|
type: 'warning',
|
|
|
|
title: 'Warning Alert',
|
|
|
|
text: 'You might wanna look at this.',
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
)
|
|
|
|
"
|
|
|
|
class="font-bold uppercase bg-amber-600 text-white px-3 py-2 rounded-md mr-2"
|
|
|
|
>
|
|
|
|
Warning
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<button
|
|
|
|
@click="
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: 'alert',
|
|
|
|
type: 'danger',
|
|
|
|
title: 'Danger Alert',
|
|
|
|
text: 'Something terrible has happened!',
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
)
|
|
|
|
"
|
|
|
|
class="font-bold uppercase bg-rose-600 text-white px-3 py-2 rounded-md mr-2"
|
|
|
|
>
|
|
|
|
Danger
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<button
|
|
|
|
@click="
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: 'modal',
|
|
|
|
type: 'notification-permission',
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
)
|
|
|
|
"
|
|
|
|
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
|
|
|
|
>
|
|
|
|
Notif ON
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<button
|
|
|
|
@click="
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: 'modal',
|
|
|
|
type: 'notification-mute',
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
)
|
|
|
|
"
|
|
|
|
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
|
|
|
|
>
|
|
|
|
Notif MUTE
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<button
|
|
|
|
@click="
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: 'modal',
|
|
|
|
type: 'notification-off',
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
)
|
|
|
|
"
|
|
|
|
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
|
|
|
|
>
|
|
|
|
Notif OFF
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="mt-8">
|
|
|
|
<h2 class="text-xl font-bold mb-4">Image Sharing</h2>
|
|
|
|
Populates the "shared-photo" view as if they used "share_target".
|
|
|
|
<input type="file" @change="uploadFile" />
|
|
|
|
<router-link
|
|
|
|
v-if="showFileNextStep()"
|
|
|
|
:to="{
|
|
|
|
name: 'shared-photo',
|
|
|
|
query: { fileName },
|
|
|
|
}"
|
|
|
|
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2 mt-2"
|
|
|
|
>
|
|
|
|
Go to Shared Page
|
|
|
|
</router-link>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="mt-8">
|
|
|
|
<h2 class="text-xl font-bold mb-4">Passkeys</h2>
|
|
|
|
See console for results.
|
|
|
|
<br />
|
|
|
|
See existing passkeys in Chrome at: chrome://settings/passkeys
|
|
|
|
<br />
|
|
|
|
Active DID: {{ activeDid || "nothing, which" }}
|
|
|
|
{{ credIdHex ? "has a passkey ID" : "has no passkey ID" }}
|
|
|
|
|
|
|
|
<div>
|
|
|
|
Register Passkey
|
|
|
|
<button
|
|
|
|
@click="register()"
|
|
|
|
class="font-bold uppercase bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
|
|
|
>
|
|
|
|
Simplewebauthn
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
Create JWT
|
|
|
|
<button
|
|
|
|
@click="createJwtSimplewebauthn()"
|
|
|
|
class="font-bold uppercase bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
|
|
|
>
|
|
|
|
Simplewebauthn
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
@click="createJwtNavigator()"
|
|
|
|
class="font-bold uppercase bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
|
|
|
>
|
|
|
|
Navigator
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div v-if="jwt">
|
|
|
|
Verify New JWT
|
|
|
|
<button
|
|
|
|
@click="verifySimplewebauthn()"
|
|
|
|
class="font-bold uppercase bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
|
|
|
>
|
|
|
|
Simplewebauthn
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
@click="verifyWebCrypto()"
|
|
|
|
class="font-bold uppercase bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
|
|
|
>
|
|
|
|
WebCrypto
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
@click="verifyP256()"
|
|
|
|
class="font-bold uppercase bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
|
|
|
>
|
|
|
|
p256 - broken
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<div v-else>Verify New JWT -- requires creation first</div>
|
|
|
|
<button
|
|
|
|
@click="verifyMyJwt()"
|
|
|
|
class="font-bold uppercase bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
|
|
|
>
|
|
|
|
Verify Hard-Coded JWT
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</section>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
import { Buffer } from "buffer/";
|
|
|
|
import { Base64URLString } from "@simplewebauthn/types";
|
|
|
|
import { ref } from "vue";
|
|
|
|
import { Component, Vue } from "vue-facing-decorator";
|
|
|
|
|
|
|
|
import QuickNav from "@/components/QuickNav.vue";
|
|
|
|
import { NotificationIface } from "@/constants/app";
|
|
|
|
import { accountsDB, db } from "@/db/index";
|
|
|
|
import {
|
|
|
|
createPeerDid,
|
|
|
|
PeerSetup,
|
|
|
|
registerCredential,
|
|
|
|
verifyJwtP256,
|
|
|
|
verifyJwtSimplewebauthn,
|
|
|
|
verifyJwtWebCrypto,
|
|
|
|
} from "@/libs/didPeer";
|
|
|
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
|
|
|
|
|
|
|
const inputFileNameRef = ref<Blob>();
|
|
|
|
|
|
|
|
const TEST_PAYLOAD = {
|
|
|
|
vc: {
|
|
|
|
credentialSubject: {
|
|
|
|
"@context": "https://schema.org",
|
|
|
|
"@type": "GiveAction",
|
|
|
|
description: "pizza",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
@Component({ components: { QuickNav } })
|
|
|
|
export default class Help extends Vue {
|
|
|
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
|
|
|
|
|
|
|
// for file import
|
|
|
|
fileName?: string;
|
|
|
|
|
|
|
|
// for passkeys
|
|
|
|
credIdHex?: string;
|
|
|
|
activeDid?: string;
|
|
|
|
jwt?: string;
|
|
|
|
peerSetup?: PeerSetup;
|
|
|
|
userName?: string;
|
|
|
|
|
|
|
|
async mounted() {
|
|
|
|
await db.open();
|
|
|
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
|
|
|
this.activeDid = (settings?.activeDid as string) || "";
|
|
|
|
this.userName = settings?.firstName as string;
|
|
|
|
|
|
|
|
await accountsDB.open();
|
|
|
|
const account: { identity?: string } | undefined = await accountsDB.accounts
|
|
|
|
.where("did")
|
|
|
|
.equals(this.activeDid)
|
|
|
|
.first();
|
|
|
|
if (this.activeDid) {
|
|
|
|
if (account) {
|
|
|
|
this.credIdHex = account.passkeyCredIdHex as string;
|
|
|
|
} else {
|
|
|
|
alert("No account found for DID " + this.activeDid);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async uploadFile(event: Event) {
|
|
|
|
inputFileNameRef.value = event.target?.["files"][0];
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/File
|
|
|
|
// ... plus it has a `type` property from my testing
|
|
|
|
const file = inputFileNameRef.value;
|
|
|
|
if (file != null) {
|
|
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = async (e) => {
|
|
|
|
const data = e.target?.result as ArrayBuffer;
|
|
|
|
if (data) {
|
|
|
|
const blob = new Blob([new Uint8Array(data)], {
|
|
|
|
type: file.type,
|
|
|
|
});
|
|
|
|
this.fileName = file.name as string;
|
|
|
|
const temp = await db.temp.get("shared-photo");
|
|
|
|
if (temp) {
|
|
|
|
await db.temp.update("shared-photo", { blob });
|
|
|
|
} else {
|
|
|
|
await db.temp.add({ id: "shared-photo", blob });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
reader.readAsArrayBuffer(file as Blob);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
showFileNextStep() {
|
|
|
|
return !!inputFileNameRef.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
public async register() {
|
|
|
|
const DEFAULT_USERNAME = "Time Safari Tester";
|
|
|
|
if (!this.userName) {
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "modal",
|
|
|
|
type: "confirm",
|
|
|
|
title: "No Name",
|
|
|
|
text: "You must have a name to attach to this passkey. Would you like to enter your own name first?",
|
|
|
|
onNo: async () => {
|
|
|
|
this.userName = DEFAULT_USERNAME;
|
|
|
|
},
|
|
|
|
onYes: async () => {
|
|
|
|
this.$router.push({ name: "new-edit-account" });
|
|
|
|
},
|
|
|
|
noText: "try again and use " + DEFAULT_USERNAME,
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const cred = await registerCredential("Time Safari - " + this.userName);
|
|
|
|
const publicKeyBytes = cred.publicKeyBytes;
|
|
|
|
this.activeDid = createPeerDid(publicKeyBytes as Uint8Array);
|
|
|
|
this.credIdHex = cred.credIdHex as string;
|
|
|
|
|
|
|
|
await accountsDB.open();
|
|
|
|
await accountsDB.accounts.add({
|
|
|
|
dateCreated: new Date().toISOString(),
|
|
|
|
did: this.activeDid,
|
|
|
|
passkeyCredIdHex: this.credIdHex,
|
|
|
|
publicKeyHex: Buffer.from(publicKeyBytes).toString("hex"),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public async createJwtSimplewebauthn() {
|
|
|
|
this.peerSetup = new PeerSetup();
|
|
|
|
this.jwt = await this.peerSetup.createJwtSimplewebauthn(
|
|
|
|
this.activeDid as string,
|
|
|
|
TEST_PAYLOAD,
|
|
|
|
this.credIdHex as string,
|
|
|
|
);
|
|
|
|
console.log("simple jwt4url", this.jwt);
|
|
|
|
}
|
|
|
|
|
|
|
|
public async createJwtNavigator() {
|
|
|
|
this.peerSetup = new PeerSetup();
|
|
|
|
this.jwt = await this.peerSetup.createJwtNavigator(
|
|
|
|
this.activeDid as string,
|
|
|
|
TEST_PAYLOAD,
|
|
|
|
this.credIdHex as string,
|
|
|
|
);
|
|
|
|
console.log("lower jwt4url", this.jwt);
|
|
|
|
}
|
|
|
|
|
|
|
|
public async verifyP256() {
|
|
|
|
const decoded = await verifyJwtP256(
|
|
|
|
this.credIdHex as string,
|
|
|
|
this.activeDid as string,
|
|
|
|
this.peerSetup?.authenticatorData as ArrayBuffer,
|
|
|
|
this.peerSetup?.challenge as Uint8Array,
|
|
|
|
this.peerSetup?.clientDataJsonBase64Url as Base64URLString,
|
|
|
|
this.peerSetup?.signature as Base64URLString,
|
|
|
|
);
|
|
|
|
console.log("decoded", decoded);
|
|
|
|
}
|
|
|
|
|
|
|
|
public async verifySimplewebauthn() {
|
|
|
|
const decoded = await verifyJwtSimplewebauthn(
|
|
|
|
this.credIdHex as string,
|
|
|
|
this.activeDid as string,
|
|
|
|
this.peerSetup?.authenticatorData as ArrayBuffer,
|
|
|
|
this.peerSetup?.challenge as Uint8Array,
|
|
|
|
this.peerSetup?.clientDataJsonBase64Url as Base64URLString,
|
|
|
|
this.peerSetup?.signature as Base64URLString,
|
|
|
|
);
|
|
|
|
console.log("decoded", decoded);
|
|
|
|
}
|
|
|
|
|
|
|
|
public async verifyWebCrypto() {
|
|
|
|
const decoded = await verifyJwtWebCrypto(
|
|
|
|
this.credIdHex as string,
|
|
|
|
this.activeDid as string,
|
|
|
|
this.peerSetup?.authenticatorData as ArrayBuffer,
|
|
|
|
this.peerSetup?.challenge as Uint8Array,
|
|
|
|
this.peerSetup?.clientDataJsonBase64Url as Base64URLString,
|
|
|
|
this.peerSetup?.signature as Base64URLString,
|
|
|
|
);
|
|
|
|
console.log("decoded", decoded);
|
|
|
|
}
|
|
|
|
|
|
|
|
public async verifyMyJwt() {
|
|
|
|
const did =
|
|
|
|
"did:peer:0zKMFjvUgYrM1hXwDciYHiA9MxXtJPXnRLJvqoMNAKoDLX9pKMWLb3VDsgua1p2zW1xXRsjZSTNsfvMnNyMS7dB4k7NAhFwL3pXBrBXgyYJ9ri";
|
|
|
|
const jwt =
|
|
|
|
"eyJ0eXAiOiJKV0FOVCIsImFsZyI6IkVTMjU2In0.eyJBdXRoZW50aWNhdGlvbkRhdGFCNjRVUkwiOiJTWllONVlnT2pHaDBOQmNQWkhaZ1c0X2tycm1paGpMSG1Wenp1b01kbDJNRkFBQUFBQSIsIkNsaWVudERhdGFKU09OQjY0VVJMIjoiZXlKMGVYQmxJam9pZDJWaVlYVjBhRzR1WjJWMElpd2lZMmhoYkd4bGJtZGxJam9pWlhsS01sbDVTVFpsZVVwcVkyMVdhMXBYTlRCaFYwWnpWVE5XYVdGdFZtcGtRMGsyWlhsS1FWa3lPWFZrUjFZMFpFTkpOa2x0YURCa1NFSjZUMms0ZG1NeVRtOWFWekZvVEcwNWVWcDVTWE5KYTBJd1pWaENiRWxxYjJsU01td3lXbFZHYW1SSGJIWmlhVWx6U1cxU2JHTXlUbmxoV0VJd1lWYzVkVWxxYjJsalIydzJaVzFGYVdaWU1ITkpiV3hvWkVOSk5rMVVZM2hQUkZVMFRtcHJOVTFEZDJsaFdFNTZTV3B2YVZwSGJHdFBia0pzV2xoSk5rMUljRXhVVlZweFpHeFdibGRZU2s1TlYyaFpaREJTYW1GV2JFbGhWVVUxVkZob1dXUkZjRkZYUnpWVFZFVndNbU5YT1U1VWEwWk1ZakJTVFZkRWJIZFRNREZZVkVkSmVsWnJVbnBhTTFab1RWaEJlV1ZzWTNobFJtaFRZekp3WVZVeFVrOWpNbG95VkZjMVQyVlZNVlJPTWxKRFRrZHpNMVJyUm05U2JtUk5UVE5DV1ZGdVNrTlhSMlExVjFWdk5XTnRhMmxtVVNJc0ltOXlhV2RwYmlJNkltaDBkSEE2THk5c2IyTmhiR2h2YzNRNk9EQTRNQ0lzSW1OeWIzTnpUM0pwWjJsdUlqcG1ZV3h6WlgwIiwiaWF0IjoxNzE4NTg2OTkyLCJpc3MiOiJkaWQ6cGVlcjowektNRmp2VWdZck0xaFh3RGNpWUhpQTlNeFh0SlBYblJMSnZxb01OQUtvRExYOXBLTVdMYjNWRHNndWExcDJ6VzF4WFJzalpTVE5zZnZNbk55TVM3ZEI0azdOQWhGd0wzcFhCckJYZ3lZSjlyaSJ9.MEUCIQDJyCTbMPIFnuBoW3FYnlgtDEIHZ2OrkCEvqVnHU7kJDQIgVxjBjfW1TwQfcSOYwK8Z7AdCWGJlyxtLEsrnPif7caE";
|
|
|
|
const pieces = jwt.split(".");
|
|
|
|
const payload = JSON.parse(Buffer.from(pieces[1], "base64").toString());
|
|
|
|
const authData = Buffer.from(payload["AuthenticationDataB64URL"], "base64");
|
|
|
|
const clientJSON = Buffer.from(
|
|
|
|
payload["ClientDataJSONB64URL"],
|
|
|
|
"base64",
|
|
|
|
).toString();
|
|
|
|
const clientData = JSON.parse(clientJSON);
|
|
|
|
const challenge = clientData.challenge;
|
|
|
|
const signatureB64URL = pieces[2];
|
|
|
|
const decoded = await verifyJwtWebCrypto(
|
|
|
|
this.credIdHex as string,
|
|
|
|
did,
|
|
|
|
authData,
|
|
|
|
challenge,
|
|
|
|
payload["ClientDataJSONB64URL"],
|
|
|
|
signatureB64URL,
|
|
|
|
);
|
|
|
|
console.log("decoded", decoded);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|