- added 2fa authentication during login. see resources/js/Pages/Auth/login.vue
All checks were successful
CI Pipeline / japa-tests (push) Successful in 1m2s

- added validate() method inside app/Srvices/TwoFactorProvider.ts
- added twoFactorChallenge() method inside app/Controllers/Http/Auth/AuthController.ts for logging in via 2fa-code
This commit is contained in:
Kaimbacher 2024-02-16 15:32:47 +01:00
parent b2dce0259a
commit f828ca4491
7 changed files with 233 additions and 84 deletions

View file

@ -1,37 +1,7 @@
<!-- <template>
<div>
<Link href="/app">Home</Link>
<br />
<h1>Login</h1>
<Head>
<title>About - My app</title>
<meta
head-key="description"
name="description"
content="This is a page specific description"
/>
</Head>
</div>
</template>
<script>
import AuthLayout from '@/Layouts/Auth.vue';
import LayoutGuest from '@/Layouts/LayoutGuest.vue';
export default {
layout: AuthLayout,
};
</script>
<script setup>
// import { Head, Link } from '@inertiajs/vue3';
import { Head, Link } from '@inertiajs/inertia-vue3'
import FormField from '@/Components/FormField.vue';
import FormControl from '@/Components/FormControl.vue';
</script> -->
<template>
<LayoutGuest>
<Head title="Login" />
<!-- <SectionFullScreen v-slot="{ cardClass }" :bg="'greenBlue'"> -->
@ -40,8 +10,9 @@ import FormControl from '@/Components/FormControl.vue';
<img src="/logo.svg" class="h-10 mr-4" alt="Windster Logo" />
<!-- <span class="self-center text-2xl font-bold whitespace-nowrap">Tethys</span> -->
</a>
<!-- Card -->
<CardBox :class="cardClass" form @submit.prevent="submit">
<CardBox v-if="isTwoFactorAuthNeeded == false" :class="cardClass" form
@submit.prevent="submit">
<FormValidationErrors v-bind:errors="errors" />
<NotificationBarInCard v-if="status" color="info">
@ -49,18 +20,13 @@ import FormControl from '@/Components/FormControl.vue';
</NotificationBarInCard>
<FormField label="Email" label-for="email" help="Please enter your email">
<FormControl v-model="form.email" :icon="mdiAccount" id="email" autocomplete="email" type="email" required />
<FormControl v-model="form.email" :icon="mdiAccount" id="email" autocomplete="email" type="email"
required />
</FormField>
<FormField label="Password" label-for="password" help="Please enter your password">
<FormControl
v-model="form.password"
:icon="mdiAsterisk"
type="password"
id="password"
autocomplete="current-password"
required
/>
<FormControl v-model="form.password" :icon="mdiAsterisk" type="password" id="password"
autocomplete="current-password" required />
</FormField>
<FormCheckRadioGroup v-model="form.remember" name="remember" :options="{ remember: 'Remember' }" />
@ -79,36 +45,62 @@ import FormControl from '@/Components/FormControl.vue';
<BaseDivider />
<!-- buttons -->
<BaseLevel>
<BaseButtons>
<!-- <BaseButton type="submit" color="info" label="Login" :class="{ 'opacity-25': form.processing }"
v-bind:disabled="form.processing" /> -->
<button
type="submit"
v-bind:disabled="form.processing"
:class="{ 'opacity-25': form.processing }"
class="text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-base px-5 py-3 w-full sm:w-auto text-center"
>
Login to your account
</button>
<!-- <BaseButton v-if="canResetPassword" :route-name="route('password.request')" color="info" outline
<BaseButtons>
<BaseButton type="submit" color="info" label=" Login to your account" :class="{ 'opacity-25': form.processing }"
v-bind:disabled="form.processing" />
<!-- <button type="submit" v-bind:disabled="form.processing" :class="{ 'opacity-25': form.processing }"
class="text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-base px-5 py-3 w-full sm:w-auto text-center">
Login to your account
</button> -->
<!-- <BaseButton v-if="canResetPassword" :route-name="route('password.request')" color="info" outline
label="Remind" /> -->
</BaseButtons>
<!-- <Link :href="stardust.route('app.register.show')"> Register </Link> -->
</BaseLevel>
<div class="text-sm font-medium text-gray-500">
</BaseButtons>
<!-- <Link :href="stardust.route('app.register.show')"> Register </Link> -->
<!-- <div class="text-sm font-medium text-gray-500">
Not registered? <a href="" class="text-teal-500 hover:underline">Create account</a>
</div>
</div> -->
</CardBox>
<CardBox v-else-if="isTwoFactorAuthNeeded" :icon="mdiTwoFactorAuthentication" :class="cardClass" form
@submit.prevent="submitFa2Form">
<FormField label="2FA Code" label-for="code" help="Please enter 2factor code">
<FormControl v-model="fa2Form.code" :icon="mdiAccount" id="code" type="tel" required />
</FormField>
<div v-if="flash && flash.message" class="flex flex-col mt-6 animate-fade-in">
<div class="bg-yellow-500 border-l-4 border-orange-400 text-white p-4" role="alert">
<p class="font-bold">Be Warned</p>
<p>{{ flash.message }}</p>
</div>
</div>
<BaseDivider />
<BaseButtons>
<!-- <button type="submit" :icon="mdiContentSaveCheck" :disabled="fa2Form.processing"
:class="{ 'opacity-25': fa2Form.processing }"
class="text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-base px-5 py-3 w-full sm:w-auto text-center">
Verify
</button> -->
<BaseButton type="submit" :icon="mdiContentSaveCheck" color="info" label=" Login to your account" :class="{ 'opacity-25': fa2Form.processing }"
v-bind:disabled="fa2Form.processing" />
</BaseButtons>
<!-- <Link :href="stardust.route('app.register.show')"> Register </Link> -->
</CardBox>
</SectionFullScreen>
</LayoutGuest>
</template>
<script setup lang="ts">
import { useForm, Head } from '@inertiajs/vue3';
import { Ref } from 'vue';
import { Ref, ref } from 'vue';
// import { Head, Link, useForm } from '@inertiajs/inertia-vue3';
import { mdiAccount, mdiAsterisk } from '@mdi/js';
import { mdiAccount, mdiAsterisk, mdiTwoFactorAuthentication, mdiContentSaveCheck, mdiLock } from '@mdi/js';
import LayoutGuest from '@/Layouts/LayoutGuest.vue';
import SectionFullScreen from '@/Components/SectionFullScreen.vue';
import CardBox from '@/Components/CardBox.vue';
@ -116,22 +108,55 @@ import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue';
import FormField from '@/Components/FormField.vue';
import FormControl from '@/Components/FormControl.vue';
import BaseDivider from '@/Components/BaseDivider.vue';
// import BaseButton from '@/Components/BaseButton.vue';
import BaseButton from '@/Components/BaseButton.vue';
import BaseButtons from '@/Components/BaseButtons.vue';
import FormValidationErrors from '@/Components/FormValidationErrors.vue';
import NotificationBarInCard from '@/Components/NotificationBarInCard.vue';
import BaseLevel from '@/Components/BaseLevel.vue';
import { stardust } from '@eidellev/adonis-stardust/client';
// import NotificationBar from '@/Components/NotificationBar.vue';
import { computed } from 'vue';
import { computed, watch } from 'vue';
import { usePage } from '@inertiajs/vue3';
// import axios from 'axios';
// import { LoginState } from '@/Dataset';
// interface IErrorMessage {
// [key: string]: Array<string>;
// }
const flash: Ref<any> = computed(() => {
return usePage().props.flash;
let test = usePage().props;
return test.flash;
});
const user_id: Ref<any> = computed(() => {
let test = usePage().props;
return test.user_id;
});
const isTwoFactorAuthNeeded = ref(false);
// const user_id: ComputedRef<number> = computed(() => {
// return usePage().props.flash.user_id as number;
// });
// const isTwoFactorAuthNeeded = computed(() => {
// if (flash.new_user_id) {
// return true;
// } else {
// return false;
// }
// });
watch(user_id, () => {
if (user_id.value) {
isTwoFactorAuthNeeded.value = true;
} else {
isTwoFactorAuthNeeded.value = false;
}
});
defineProps({
@ -146,11 +171,57 @@ defineProps({
},
});
const form = useForm({
// const { version } = usePage();
const form = useForm(() => ({
email: '',
password: '',
remember: [],
});
}));
// const login = async (data): Promise<any | null> => {
// try {
// let resp = await axios.post(stardust.route('login.store'), data, {
// headers: {
// 'X-Inertia': true,
// 'X-Inertia-Partial-Component': 'Users',
// 'X-Inertia-Partial-Data': 'users',
// 'X-Inertia-Version': version,
// }
// });
// // Check if the response contains a redirect status
// if (resp.data.component && resp.data.url) {
// // Use Inertia.js to visit the specified component
// router.visit(resp.data.url, resp.data.props);
// } else {
// // If it's not a redirect, reset the password field in the form
// form.reset('password');
// return resp.data;
// }
// } catch (error) {
// // Handle errors if any
// console.error('Error during login:', error);
// throw error;
// }
// };
// const submit = async () => {
// // Transform the formData object
// const formData = form.data();
// const transformedData = {
// ...formData,
// remember: formData.remember && formData.remember.length ? 'on' : '',
// };
// const resp = await login(transformedData);
// if (resp) {
// if (resp.state == LoginState.STATE_VALIDATED) {
// isTwoFactorAuthNeeded.value = true
// }
// fa2Form.login_id = resp.new_user_id;
// }
// };
const submit = async () => {
await form
@ -164,4 +235,26 @@ const submit = async () => {
},
});
};
const fa2Form = useForm(() => ({
code: '',
remember: [],
login_id: ''
}));
const submitFa2Form = async () => {
await fa2Form
.transform((data) => ({
...data,
remember: fa2Form.remember && fa2Form.remember.length ? 'on' : '',
login_id: user_id.value
}))
.post(stardust.route('login.twoFactorChallenge'), {
onFinish: () => {
fa2Form.reset('code');
fa2Form.reset('login_id');
// form.reset('password');
},
});
};
</script>