aboutsummaryrefslogtreecommitdiff
path: root/front-end/src/components/Login
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-01-20 23:49:29 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2024-01-20 23:49:29 -0500
commit6cb7da37824d02a1898d08d0f9495c77fde4dd1d (patch)
tree95e37ea3c20f416d6a205ee4ab050c307b18eafe /front-end/src/components/Login
inital commit
Diffstat (limited to 'front-end/src/components/Login')
-rw-r--r--front-end/src/components/Login/PkiLogin.vue50
-rw-r--r--front-end/src/components/Login/UserPass.vue118
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