diff options
Diffstat (limited to 'front-end/src/views/Login/components')
-rw-r--r-- | front-end/src/views/Login/components/Social.vue | 52 | ||||
-rw-r--r-- | front-end/src/views/Login/components/UserPass.vue | 123 |
2 files changed, 88 insertions, 87 deletions
diff --git a/front-end/src/views/Login/components/Social.vue b/front-end/src/views/Login/components/Social.vue index 3c93d0e..2087524 100644 --- a/front-end/src/views/Login/components/Social.vue +++ b/front-end/src/views/Login/components/Social.vue @@ -1,13 +1,29 @@ <script setup lang="ts"> -import { shallowRef } from 'vue' import { apiCall, useWait, type OAuthMethod } from '@vnuge/vnlib.browser' -import { capitalize } from 'lodash-es'; +import { capitalize, map } from 'lodash-es'; import { useStore } from '../../../store'; +import { useAsyncState } from '@vueuse/core'; +import { shallowRef } from 'vue'; +import { Mutable } from '@vueuse/core'; const { waiting } = useWait() const store = useStore() +const buttonCont = shallowRef<HTMLDivElement | null>(null) + +const filterSvgIcon = (oauth: OAuthMethod[]) => { + return map(oauth, (method: Mutable<OAuthMethod>) => { + //parse the base64 icon as an svg + if (method.icon) { + return{ + ...method, + icon: atob(method.icon).replace(/(width|height)="[^"]*"/g, '') + } + } + return method; + }) +} -const methods = shallowRef<OAuthMethod[]>([]) +const { state: methods, isReady } = useAsyncState(store.socialOauth().then(p => filterSvgIcon(p.methods)), []); //Invoke login wrapped in api call const submitLogin = (method: OAuthMethod) => apiCall(async () => { @@ -15,34 +31,24 @@ const submitLogin = (method: OAuthMethod) => apiCall(async () => { await beginLoginFlow(method) }) -const getIcon = (method: OAuthMethod): string[] => { - switch (method.id) { - case 'auth0': - return ['fa', 'certificate'] - default: - return ['fab', method.id] - } -} - -//Load methods once the fetch completes -store.socialOauth().then(m => methods.value = m.methods); - </script> <template> - <div class="flex flex-col gap-3"> + <div ref="buttonCont" v-if="isReady" class="flex flex-col gap-3"> <div v-for="method in methods" :key="method.id" class=""> - <button - type="submit" - class="btn social-button" - :disabled="waiting" - @click.prevent="submitLogin(method)" - > - <fa-icon :icon="getIcon(method)" size="xl" /> + <button type="submit" class="btn social-button" :disabled="waiting" @click.prevent="submitLogin(method)"> + + <div v-html="method.icon" class="w-6 h-6" > + </div> + Login with {{ capitalize(method.id) }} </button> </div> </div> + <div v-else class="my-8"> + <fa-icon icon="spinner" size="2xl" spin /> + </div> + </template> diff --git a/front-end/src/views/Login/components/UserPass.vue b/front-end/src/views/Login/components/UserPass.vue index bc9d8d1..16c8aab 100644 --- a/front-end/src/views/Login/components/UserPass.vue +++ b/front-end/src/views/Login/components/UserPass.vue @@ -1,67 +1,13 @@ -<template> - <div class=""> - <h3>Login</h3> - <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"> - <input - id="username" - v-model="v$.username.$model" - type="email" - class="w-full primary input" - placeholder="Email" - :class="{ 'data-invalid': v$.username.$invalid }" - @input="onInput" - > - <label for="username">Email</label> - </div> - </div> - <div class="py-3"> - <div class="mb-2 float-label"> - <input - id="password" - v-model="v$.password.$model" - type="password" - class="w-full primary input" - placeholder="Password" - :class="{ 'data-invalid': v$.password.$invalid }" - @input="onInput" - > - <label for="password">Password</label> - </div> - </div> - </fieldset> - <button type="submit" form="user-pass-submit-form" class="btn primary" :disabled="waiting"> - <!-- Display spinner if waiting, otherwise the sign-in icon --> - <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> -</template> <script setup lang="ts"> import { ref, shallowRef, reactive, defineAsyncComponent, Ref } from 'vue' import { useTimeoutFn, set } from '@vueuse/core' import { useVuelidate } from '@vuelidate/core' -import { isEqual } from 'lodash-es' +import { isEmpty } from 'lodash-es' import { required, maxLength, minLength, email, helpers } from '@vuelidate/validators' import { - useVuelidateWrapper, useMfaLogin, totpMfaProcessor, IMfaFlowContinuiation, MfaMethod, + useVuelidateWrapper, useMfaLogin, totpMfaProcessor, IMfaFlowContinuiation, apiCall, useMessage, useWait, debugLog, WebMessage, type VuelidateInstance } from '@vnuge/vnlib.browser' @@ -126,12 +72,21 @@ const SubmitLogin = async () => { //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 + // Response is an mfa upgrade + if (!isEmpty(mfa.type)) { + + /** + * If mfa has a type assicated, then we should have a handler matched + * with it to continue the flow + * + * All mfa upgrades will have a token expiration, and an assoicated + * type string name (string) + */ + set(mfaUpgrade, mfa); - //Setup timeout timer + set(mfaTimeout, mfa.expires! * 1000); + mfaTimer.start(); } //If login without mfa was successful @@ -145,11 +100,51 @@ const SubmitLogin = async () => { }) } -const totpClear = () => { - //Clear timer +const mfaClear = () => { mfaTimer.stop(); - //Clear upgrade message set(mfaUpgrade, undefined); } -</script>
\ No newline at end of file +</script> + +<template> + <div class=""> + <h3>Login</h3> + + <div v-if="mfaUpgrade?.type === 'totp'"> + <Totp @clear="mfaClear()" :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"> + <input id="username" v-model="v$.username.$model" type="email" class="w-full primary input" + placeholder="Email" :class="{ 'data-invalid': v$.username.$invalid }" @input="onInput"> + <label for="username">Email</label> + </div> + </div> + <div class="py-3"> + <div class="mb-2 float-label"> + <input id="password" v-model="v$.password.$model" type="password" class="w-full primary input" + placeholder="Password" :class="{ 'data-invalid': v$.password.$invalid }" @input="onInput"> + <label for="password">Password</label> + </div> + </div> + </fieldset> + <button type="submit" form="user-pass-submit-form" class="btn primary" :disabled="waiting"> + <!-- Display spinner if waiting, otherwise the sign-in icon --> + <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> +</template>
\ No newline at end of file |