aboutsummaryrefslogtreecommitdiff
path: root/front-end/src/views/Login/components
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-12-13 17:58:51 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-12-13 17:58:51 -0500
commit4b8ae76132d2342f40cec703b3d5145ea075c451 (patch)
tree62b942b6181261566cd3245ee35cd15a138aabf2 /front-end/src/views/Login/components
parentb564708f29cf8a709c3e3d981477b2ec8440673e (diff)
log time coming ui and lib updates
Diffstat (limited to 'front-end/src/views/Login/components')
-rw-r--r--front-end/src/views/Login/components/Social.vue55
-rw-r--r--front-end/src/views/Login/components/Totp.vue29
-rw-r--r--front-end/src/views/Login/components/UserPass.vue98
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