From 72a3076d041131c0dbf799735f98a61660ff224a Mon Sep 17 00:00:00 2001 From: Spitfireap Date: Mon, 17 Jun 2024 22:09:28 +0200 Subject: [PATCH] Updated key view, add webauthn-json --- frontend/package.json | 2 + .../src/components/account/FidoAuthForm.vue | 187 +++--------------- .../src/components/tools/ConfirmDialog.vue | 13 +- frontend/yarn.lock | 5 + 4 files changed, 45 insertions(+), 162 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index ce01e1e7d..e234a26bf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/components/account/FidoAuthForm.vue b/frontend/src/components/account/FidoAuthForm.vue index de1e1cb5f..604052d85 100644 --- a/frontend/src/components/account/FidoAuthForm.vue +++ b/frontend/src/components/account/FidoAuthForm.vue @@ -73,9 +73,9 @@ - + - + @@ -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); -} - diff --git a/frontend/src/components/tools/ConfirmDialog.vue b/frontend/src/components/tools/ConfirmDialog.vue index 5faae125f..b44632272 100644 --- a/frontend/src/components/tools/ConfirmDialog.vue +++ b/frontend/src/components/tools/ConfirmDialog.vue @@ -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 } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 0ff26f5a3..27fb680ed 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -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"