diff options
Diffstat (limited to 'front-end/src/views/Login/components')
-rw-r--r-- | front-end/src/views/Login/components/Social.vue | 55 | ||||
-rw-r--r-- | front-end/src/views/Login/components/Totp.vue | 29 | ||||
-rw-r--r-- | front-end/src/views/Login/components/UserPass.vue | 98 |
3 files changed, 121 insertions, 61 deletions
diff --git a/front-end/src/views/Login/components/Social.vue b/front-end/src/views/Login/components/Social.vue index 5824226..2cea930 100644 --- a/front-end/src/views/Login/components/Social.vue +++ b/front-end/src/views/Login/components/Social.vue @@ -1,47 +1,30 @@ <template> - - <form class="w-full" @submit.prevent="SocalLogin('/login/social/github')"> - <button type="submit" class="btn social-button" :disabled="waiting"> - <fa-icon :icon="['fab','github']" size="xl" /> - Login with Github - </button> - </form> - <form class="mt-4" @submit.prevent="SocalLogin('/login/social/discord')"> - <button type="submit" class="btn social-button" :disabled="waiting"> - <fa-icon :icon="['fab','discord']" size="xl" /> - Login with Discord - </button> - </form> + <div class="flex flex-col gap-3"> + <div v-for="method in store.socialOauth.methods" :key="method.Id" class=""> + <button + type="submit" + class="btn social-button" + :disabled="waiting" + @click.prevent="submitLogin(method)" + > + <fa-icon :icon="['fab', method.Id]" size="xl" /> + Login with {{ capitalize(method.Id) }} + </button> + </div> + </div> </template> <script setup lang="ts"> -import { apiCall, useWait, useSessionUtils, WebMessage, useUser } from '@vnuge/vnlib.browser' +import { apiCall, useWait, type OAuthMethod } from '@vnuge/vnlib.browser' +import { capitalize } from 'lodash-es'; +import { useStore } from '../../../store'; const { waiting } = useWait() -const { KeyStore } = useSessionUtils() -const { prepareLogin } = useUser() +const store = useStore() -const SocalLogin = async (url:string) => { - await apiCall(async ({ axios }) => { - - //Prepare the login claim - const claim = await prepareLogin() - const { data } = await axios.put<WebMessage<string>>(url, claim) - - const encDat = data.getResultOrThrow() - // Decrypt the result which should be a redirect url - const result = await KeyStore.decryptDataAsync(encDat) - // get utf8 text - const text = new TextDecoder('utf-8').decode(result) - // Recover url - const redirect = new URL(text) - // Force https - redirect.protocol = 'https:' - // redirect to the url - window.location.href = redirect.href - }) -} +//Invoke login wrapped in api call +const submitLogin = (method: OAuthMethod) => apiCall(() => store.socialOauth.beginLoginFlow(method)) </script>
\ No newline at end of file diff --git a/front-end/src/views/Login/components/Totp.vue b/front-end/src/views/Login/components/Totp.vue index 43c05d8..2ba1314 100644 --- a/front-end/src/views/Login/components/Totp.vue +++ b/front-end/src/views/Login/components/Totp.vue @@ -23,25 +23,40 @@ </template> <script setup lang="ts"> -import { useMessage, useWait } from '@vnuge/vnlib.browser'; +import { toRefs, defineAsyncComponent } from 'vue'; +import { IMfaFlowContinuiation, apiCall, useMessage, useWait } from '@vnuge/vnlib.browser'; import { toSafeInteger } from 'lodash-es'; -import VOtpInput from "vue3-otp-input"; +const VOtpInput = defineAsyncComponent(() => import('vue3-otp-input')) -const emit = defineEmits(['submit']) +const emit = defineEmits(['clear']) +const props = defineProps<{ + upgrade: IMfaFlowContinuiation +}>() + +const { upgrade } = toRefs(props) const { waiting } = useWait(); const { onInput } = useMessage(); -const SubimitTotp = async (code : string) => { +const SubimitTotp = (code : string) => { //If a request is still pending, do nothing if (waiting.value) { return } - //Submit a mfa upgrade result - emit('submit', { - code: toSafeInteger(code) + apiCall(async ({ toaster }) => { + //Submit totp code + const res = await upgrade.value.submit({ code: toSafeInteger(code) }) + res.getResultOrThrow() + + emit('clear') + + // Push a new toast message + toaster.general.success({ + title: 'Success', + text: 'You have been logged in', + }) }) } diff --git a/front-end/src/views/Login/components/UserPass.vue b/front-end/src/views/Login/components/UserPass.vue index e218cb8..442abb1 100644 --- a/front-end/src/views/Login/components/UserPass.vue +++ b/front-end/src/views/Login/components/UserPass.vue @@ -1,7 +1,12 @@ <template> <div class=""> <h3>Login</h3> - <form id="user-pass-submit-form" method="post" action="/login" @submit.prevent="SubmitLogin"> + + <div v-if="mfaUpgrade?.type === MfaMethod.TOTP"> + <Totp @clear="totpClear" :upgrade="mfaUpgrade" /> + </div> + + <form v-else id="user-pass-submit-form" method="post" action="/login" @submit.prevent="SubmitLogin"> <fieldset class="" :disabled="waiting" > <div> <div class="float-label"> @@ -17,7 +22,7 @@ <label for="username">Email</label> </div> </div> - <div class="py-3"> + <div class="py-3"> <div class="mb-2 float-label"> <input id="password" @@ -37,29 +42,45 @@ <fa-icon :class="{'animate-spin':waiting}" :icon="waiting ? 'spinner' : 'sign-in-alt'"/> Log-in </button> + <div class="flex flex-row justify-between gap-3 pt-3 pb-2 form-links"> + <router-link to="/pwreset"> + Forgot password + </router-link> + <router-link to="/register"> + Register a new account + </router-link> + </div> </form> - <div class="flex flex-row justify-between gap-3 pt-3 pb-2 form-links"> - <router-link to="/pwreset"> - Forgot password - </router-link> - <router-link to="/register"> - Register a new account - </router-link> - </div> </div> </template> <script setup lang="ts"> -import { reactive } from 'vue' -import useVuelidate from '@vuelidate/core' +import { ref, shallowRef, reactive, defineAsyncComponent, type Ref } from 'vue' +import { useTimeoutFn, set } from '@vueuse/core' +import { useVuelidate } from '@vuelidate/core' +import { isEqual } from 'lodash-es' import { required, maxLength, minLength, email, helpers } from '@vuelidate/validators' -import { useMessage, useVuelidateWrapper, useWait } from '@vnuge/vnlib.browser' - -const emit = defineEmits(['login']) +import { + useVuelidateWrapper, useMfaLogin, totpMfaProcessor, IMfaFlowContinuiation, MfaMethod, + apiCall, useMessage, useWait, debugLog, WebMessage +} from '@vnuge/vnlib.browser' +const Totp = defineAsyncComponent(() => import('./Totp.vue')) -const { onInput } = useMessage(); +const { onInput, setMessage } = useMessage(); const { waiting } = useWait(); +//Setup mfa login with TOTP support +const { login } = useMfaLogin([ totpMfaProcessor() ]) + +const mfaUpgrade = shallowRef<IMfaFlowContinuiation>(); + +const mfaTimeout = ref<number>(600 * 1000); +const mfaTimer = useTimeoutFn(() => { + //Clear upgrade message + mfaUpgrade.value = undefined; + setMessage('Your TOTP request has expired') +}, mfaTimeout, { immediate: false }) + const vState = reactive({ username: '', password: '' }) const rules = { @@ -84,9 +105,50 @@ const SubmitLogin = async () => { if (!await validate()) { return } + + // Run login in an apicall wrapper + await apiCall(async ({ toaster }) => { + + //Attempt to login + const response = await login( + v$.value.username.$model, + v$.value.password.$model + ); + + debugLog('Mfa-login', response); + + //See if the response is a web message + if(response.getResultOrThrow){ + (response as WebMessage).getResultOrThrow(); + } + + //Try to get response as a flow continuation + const mfa = response as IMfaFlowContinuiation + + // Response is a totp upgrade request + if (isEqual(mfa.type, MfaMethod.TOTP)) { + //Store the upgrade message + set(mfaUpgrade, mfa); + //Setup timeout timer + set(mfaTimeout, mfa.expires! * 1000); + mfaTimer.start(); + } + //If login without mfa was successful + else if (response.success) { + // Push a new toast message + toaster.general.success({ + title: 'Success', + text: 'You have been logged in', + }) + } + }) +} - //Emit login and pass the username and password - emit('login', { username: v$.value.username.$model, password: v$.value.password.$model }); +const totpClear = () => { + //Clear timer + mfaTimer.stop(); + //Clear upgrade message + set(mfaUpgrade, undefined); } </script>
\ No newline at end of file |