-
-
-
-
-
diff --git a/resources/js/Pages/Auth/usePasskeyLogin.ts b/resources/js/Pages/Auth/usePasskeyLogin.ts
new file mode 100644
index 000000000..af3713d2b
--- /dev/null
+++ b/resources/js/Pages/Auth/usePasskeyLogin.ts
@@ -0,0 +1,53 @@
+import { useForm } from "@inertiajs/vue3";
+import { api } from "@/api/api";
+import { route } from "@/utils/url";
+import { browserSupportsWebAuthnAutofill, startAuthentication } from "@simplewebauthn/browser";
+import { onMounted } from "vue";
+
+type PasskeyLoginOptions = {
+ autofill?: boolean;
+}
+
+export function usePasskeyLogin(options: PasskeyLoginOptions) {
+ return useSpatiePasskeyLogin(options);
+}
+
+function useSpatiePasskeyLogin(options: PasskeyLoginOptions) {
+ const passkeyForm = useForm({
+ remember: false,
+ start_authentication_response: '',
+ });
+
+ async function loginWithPasskey({ autofill = false, remember = false } = {}) {
+ try {
+ const response = await api.get(route('passkeys.authentication_options'), {
+ ignoreContentType: true,
+ });
+
+ const authenticationOptions = response.data;
+ const authenticationResponse = await startAuthentication({
+ optionsJSON: authenticationOptions,
+ useBrowserAutofill: autofill,
+ });
+
+ passkeyForm.remember = remember;
+ passkeyForm.start_authentication_response = JSON.stringify(authenticationResponse);
+
+ passkeyForm.post(route('passkeys.login'), {
+ headers: {
+ 'X-Sharp': '1',
+ },
+ });
+ } catch (error) {
+ console.error(error);
+ }
+ }
+
+ if(options.autofill && browserSupportsWebAuthnAutofill()) {
+ onMounted(() => {
+ loginWithPasskey({ autofill: true });
+ })
+ }
+
+ return { loginWithPasskey }
+}
diff --git a/resources/js/Pages/Auth/usePasskeyRegister.ts b/resources/js/Pages/Auth/usePasskeyRegister.ts
new file mode 100644
index 000000000..13074bb15
--- /dev/null
+++ b/resources/js/Pages/Auth/usePasskeyRegister.ts
@@ -0,0 +1,41 @@
+import { useForm } from "@inertiajs/vue3";
+import { api } from "@/api/api";
+import { route } from "@/utils/url";
+import { startRegistration } from "@simplewebauthn/browser";
+
+export function usePasskeyRegister() {
+ return useSpatiePasskeyRegister();
+}
+
+function useSpatiePasskeyRegister() {
+ const form = useForm({
+ name: '',
+ passkey: '',
+ });
+
+ async function registerPasskey() {
+ form.clearErrors();
+
+ try {
+ const optionsResponse = await api.post(route('code16.sharp.passkeys.spatie.validate'), {
+ name: form.name,
+ });
+
+ const registrationResponse = await startRegistration({
+ optionsJSON: optionsResponse.data.passkeyOptions,
+ });
+
+ form.passkey = JSON.stringify(registrationResponse);
+
+ form.post(route('code16.sharp.passkeys.spatie.store'));
+ } catch (error: any) {
+ if (error.response?.status === 422) {
+ form.setError({ name: error.response.data.errors?.name?.[0] });
+ } else {
+ console.error(error);
+ }
+ }
+ }
+
+ return { form, registerPasskey }
+}
diff --git a/resources/js/components/ui/alert/AlertTitle.vue b/resources/js/components/ui/alert/AlertTitle.vue
index 7b2f5f1b5..f228dd8e8 100644
--- a/resources/js/components/ui/alert/AlertTitle.vue
+++ b/resources/js/components/ui/alert/AlertTitle.vue
@@ -8,7 +8,7 @@ const props = defineProps<{