Updated key view, add webauthn-json

This commit is contained in:
Spitfireap
2024-06-22 19:45:25 +02:00
parent 01231c4a59
commit 72a3076d04
4 changed files with 45 additions and 162 deletions
+2
View File
@@ -1,6 +1,7 @@
{
"name": "frontend",
"version": "3.0.0",
"type": "module",
"private": true,
"scripts": {
"dev": "vite",
@@ -14,6 +15,7 @@
"gettext:compile": "vue-gettext-compile"
},
"dependencies": {
"@github/webauthn-json": "^2.1.1",
"@mdi/font": "^7.3.67",
"apexcharts": "^3.45.2",
"axios": "^1.6.7",
+26 -161
View File
@@ -73,9 +73,9 @@
</v-card>
</div>
<ConfirmDialog ref="confirmDeletion"/>
<ConfirmDialog ref="confirmEdition">
<ConfirmDialog :callback_agree="checkForm" ref="confirmEdition">
<v-form ref="editForm">
<v-text-field v-model="editName" :label="$gettext('name')"/>
<v-text-field v-model="editName" :label="$gettext('name')" :rules="[rules.required]"/>
<v-switch v-model="editEnabled" :label="$gettext('enabled')" color="principal"/>
</v-form>
</ConfirmDialog>
@@ -88,6 +88,10 @@ import { useGettext } from 'vue3-gettext'
import rules from '@/plugins/rules.js'
import { useAuthStore } from '@/stores'
import ConfirmDialog from '@/components/tools/ConfirmDialog.vue'
import {
create,
parseCreationOptionsFromJSON,
} from "@github/webauthn-json/browser-ponyfill";
const authStore = useAuthStore()
@@ -95,7 +99,6 @@ const { $gettext } = useGettext()
const name = ref()
const registrationLoading = ref(false)
const creationOption = ref()
const browserCapable = !!(navigator.credentials && navigator.credentials.create && navigator.credentials.get && window.PublicKeyCredential)
const fidoForm = ref()
const fidoCreds = computed(() => authStore.fidoCreds)
@@ -115,17 +118,21 @@ const editName = ref('')
const editEnabled = ref(false)
async function startFidoRegistration() {
registrationLoading.value = true
const { valid } = await fidoForm.value.validate()
if (!valid) {
registrationLoading.value = false
return
}
if (creationOption.value) {
navigator.credentials.create({...creationOption.value})
.then(function (attestation) {
const result = createResponseToJSON(attestation)
result.name = name.value
authStore.addFidoCred(result)
})
const creationOption = await authApi.beginFidoRegistration()
console.log(creationOption)
if (creationOption) {
const options = parseCreationOptionsFromJSON(creationOption.data)
const result = await create(options)
const response = result.toJSON()
response.name = name.value
console.log(response)
authStore.addFidoCred(response)
}
}
@@ -145,11 +152,16 @@ async function deleteCred(cred) {
authStore.deleteFidoCreds(cred.id)
}
async function checkForm() {
return (await editForm.value.validate()).valid
}
async function editCred(cred) {
editName.value = cred.name
editEnabled.value = cred.enabled
const result = await confirmEdition.value.open(
$gettext('Edition'),
$gettext('Edtion'),
$gettext('Edit your device'),
{
color: 'warning',
@@ -160,159 +172,12 @@ async function editCred(cred) {
if (!result) {
return
}
console.log(editForm.value.items)
data = {name: editName.value, enabled: editEnabled.value}
//TODO : send it !
}
onMounted(() => {
registrationLoading.value = true
authStore.getFidoCreds()
authApi.beginFidoRegistration().then((resp) => {
creationOption.value = createRequestFromJSON(resp.data)
}).finally(() => registrationLoading.value = false)
})
// Taken from the example of python-fido2 repo
// src/webauthn-json/base64url.ts
function base64urlToBuffer(baseurl64String) {
const padding = "==".slice(0, (4 - baseurl64String.length % 4) % 4);
const base64String = baseurl64String.replace(/-/g, "+").replace(/_/g, "/") + padding;
const str = atob(base64String);
const buffer = new ArrayBuffer(str.length);
const byteView = new Uint8Array(buffer);
for (let i = 0; i < str.length; i++) {
byteView[i] = str.charCodeAt(i);
}
return buffer;
}
function bufferToBase64url(buffer) {
const byteView = new Uint8Array(buffer);
let str = "";
for (const charCode of byteView) {
str += String.fromCharCode(charCode);
}
const base64String = btoa(str);
const base64urlString = base64String.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
return base64urlString;
}
// src/webauthn-json/convert.ts
const copyValue = "copy";
const convertValue = "convert";
function convert(conversionFn, schema, input) {
if (schema === copyValue) {
return input;
}
if (schema === convertValue) {
return conversionFn(input);
}
if (schema instanceof Array) {
return input.map((v) => convert(conversionFn, schema[0], v));
}
if (schema instanceof Object) {
const output = {};
for (const [key, schemaField] of Object.entries(schema)) {
if (schemaField.derive) {
const v = schemaField.derive(input);
if (v !== void 0) {
input[key] = v;
}
}
if (!(key in input)) {
if (schemaField.required) {
throw new Error(`Missing key: ${key}`);
}
continue;
}
if (input[key] == null) {
output[key] = null;
continue;
}
output[key] = convert(conversionFn, schemaField.schema, input[key]);
}
return output;
}
}
function derived(schema, derive) {
return {
required: true,
schema,
derive
};
}
function required(schema) {
return {
required: true,
schema
};
}
function optional(schema) {
return {
required: false,
schema
};
}
const publicKeyCredentialDescriptorSchema = {
type: required(copyValue),
id: required(convertValue),
transports: optional(copyValue)
}
const simplifiedExtensionsSchema = {
appid: optional(copyValue),
appidExclude: optional(copyValue),
credProps: optional(copyValue)
}
const simplifiedClientExtensionResultsSchema = {
appid: optional(copyValue),
appidExclude: optional(copyValue),
credProps: optional(copyValue)
}
const credentialCreationOptions = {
publicKey: required({
rp: required(copyValue),
user: required({
id: required(convertValue),
name: required(copyValue),
displayName: required(copyValue)
}),
challenge: required(convertValue),
pubKeyCredParams: required(copyValue),
timeout: optional(copyValue),
excludeCredentials: optional([publicKeyCredentialDescriptorSchema]),
authenticatorSelection: optional(copyValue),
attestation: optional(copyValue),
extensions: optional(simplifiedExtensionsSchema)
}),
signal: optional(copyValue)
}
const publicKeyCredentialWithAttestation = {
type: required(copyValue),
id: required(copyValue),
rawId: required(convertValue),
authenticatorAttachment: optional(copyValue),
response: required({
clientDataJSON: required(convertValue),
attestationObject: required(convertValue),
transports: derived(copyValue, (response) => {
var _a;
return ((_a = response.getTransports) == null ? void 0 : _a.call(response)) || [];
})
}),
clientExtensionResults: derived(simplifiedClientExtensionResultsSchema, (pkc) => pkc.getClientExtensionResults())
}
function createRequestFromJSON(requestJSON) {
return convert(base64urlToBuffer, credentialCreationOptions, requestJSON);
}
function createResponseToJSON(credential) {
return convert(bufferToBase64url, publicKeyCredentialWithAttestation, credential);
}
</script>
@@ -34,6 +34,14 @@ import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const props = defineProps({
// the callback should return true or false, returning false inhibits the close event
callback_agree : {
type: Function,
default: null
}
})
const dialog = ref(false)
const message = ref('')
const title = ref('')
@@ -61,7 +69,10 @@ defineExpose({
open,
})
function agree() {
async function agree() {
if (props.callback_agree !== null && ! (await props.callback_agree())) {
return
}
storedResolve(true)
dialog.value = false
}
+5
View File
@@ -181,6 +181,11 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b"
integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==
"@github/webauthn-json@^2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@github/webauthn-json/-/webauthn-json-2.1.1.tgz#648e63fc28050917d2882cc2b27817a88cb420fc"
integrity sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ==
"@humanwhocodes/config-array@^0.11.13":
version "0.11.14"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"