diff options
Diffstat (limited to 'front-end/src/components/Login')
-rw-r--r-- | front-end/src/components/Login/PkiLogin.vue | 50 | ||||
-rw-r--r-- | front-end/src/components/Login/UserPass.vue | 118 |
2 files changed, 168 insertions, 0 deletions
diff --git a/front-end/src/components/Login/PkiLogin.vue b/front-end/src/components/Login/PkiLogin.vue new file mode 100644 index 0000000..26528c4 --- /dev/null +++ b/front-end/src/components/Login/PkiLogin.vue @@ -0,0 +1,50 @@ +<script setup lang="ts"> +import { isEmpty } from 'lodash-es'; +import { apiCall, debugLog, useMessage } from '@vnuge/vnlib.browser'; +import { ref } from 'vue' +import { decodeJwt } from 'jose' +import { useStore } from '../../store'; + +const { setMessage } = useMessage() +const { pkiAuth } = useStore() + +const otp = ref('') + +const onSubmit = () => { + + apiCall(async () => { + if (isEmpty(otp.value)) { + setMessage('Please enter your OTP') + return + } + try{ + //try to decode the jwt to confirm its form is valid + const jwt = decodeJwt(otp.value) + debugLog(jwt) + } + catch{ + setMessage('Your OTP is not valid') + return + } + await pkiAuth.login(otp.value) + }) +} +</script> + +<template> + <form id="pki-login-form" class="max-w-sm mx-auto" @submit.prevent="onSubmit"> + <label for="message" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"> + Paste your one time password (OTP) + </label> + <textarea + id="message" rows="5" + v-model="otp" + class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" + placeholder="Enter your OTP" + > + </textarea> + + <button type="submit" for="pki-login-form" class="mt-4 btn">Submit</button> + + </form> +</template>
\ No newline at end of file diff --git a/front-end/src/components/Login/UserPass.vue b/front-end/src/components/Login/UserPass.vue new file mode 100644 index 0000000..b25a335 --- /dev/null +++ b/front-end/src/components/Login/UserPass.vue @@ -0,0 +1,118 @@ +<script setup lang="ts"> +import { ref, shallowRef, reactive, defineAsyncComponent } 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 { + useVuelidateWrapper, useMfaLogin, totpMfaProcessor, IMfaFlowContinuiation, MfaMethod, + apiCall, useMessage, useWait, debugLog, WebMessage +} from '@vnuge/vnlib.browser' +const OptInput = defineAsyncComponent(() => import('../global/OtpInput.vue')) + +const { setMessage } = useMessage(); +const { waiting } = useWait(); + +//Setup mfa login with TOTP support +const { login } = useMfaLogin([ totpMfaProcessor() ]) + +const mfaUpgrade = shallowRef<IMfaFlowContinuiation | undefined>(); + +const mfaTimeout = ref<number>(600 * 1000); +const mfaTimer = useTimeoutFn(() => { + //Clear upgrade message + set(mfaUpgrade, undefined); + setMessage('Your TOTP request has expired') +}, mfaTimeout, { immediate: false }) + +const vState = reactive({ username: '', password: '' }) + +const rules = { + username: { + required: helpers.withMessage('Email cannot be empty', required), + email: helpers.withMessage('Your email address is not valid', email), + maxLength: helpers.withMessage('Email address must be less than 50 characters', maxLength(50)) + }, + password: { + required: helpers.withMessage('Password cannot be empty', required), + minLength: helpers.withMessage('Password must be at least 8 characters', minLength(8)), + maxLength: helpers.withMessage('Password must have less than 128 characters', maxLength(128)) + } +} + +const v$ = useVuelidate(rules, vState) +const { validate } = useVuelidateWrapper(v$ as any); + +const onSubmit = async () => { + + // If the form is not valid set the error message + 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', + }) + } + }) +} +</script> + +<template> + <form v-if="mfaUpgrade" @submit.prevent="" class="max-w-sm mx-auto"> + <OptInput :upgrade="mfaUpgrade" /> + <p id="helper-text-explanation" class="mt-2 text-sm text-gray-500 dark:text-gray-400"> + Please enter your 6 digit TOTP code from your authenticator app. + </p> + </form> + <form v-else class="space-y-4 md:space-y-6" action="#" @submit.prevent="onSubmit" :disabled="waiting"> + <fieldset> + <label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label> + <input type="email" name="email" id="email" class="input" placeholder="name@company.com" required + v-model="v$.username.$model" + > + </fieldset> + <fieldset> + <label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Password</label> + <input type="password" name="password" id="password" class="input" placeholder="••••••••" required + v-model="v$.password.$model" + > + </fieldset> + <button type="submit" class="btn">Sign in</button> + </form> +</template> + +<style scoped lang="scss"> + +</style>
\ No newline at end of file |