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"