aboutsummaryrefslogtreecommitdiff
path: root/front-end/src/components/Login/UserPass.vue
blob: 9fd64f44db532c2b247d3694a962bcd4d8471d1f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
<script setup lang="ts">
import { ref, shallowRef, reactive } 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'

import OptInput from '../global/OtpInput.vue'//So small it does not need to be async

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">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="flex justify-center btn">
            <span v-if="waiting" class="mx-auto animate-spin">
                <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 15 15">
                    <path fill="currentColor" fill-rule="evenodd"
                        d="M8 .5V5H7V.5h1ZM5.146 5.854l-3-3l.708-.708l3 3l-.708.708Zm4-.708l3-3l.708.708l-3 3l-.708-.708Zm.855 1.849L14.5 7l-.002 1l-4.5-.006l.002-1Zm-9.501 0H5v1H.5v-1Zm5.354 2.859l-3 3l-.708-.708l3-3l.708.708Zm6.292 3l-3-3l.708-.708l3 3l-.708.708ZM8 10v4.5H7V10h1Z"
                        clip-rule="evenodd" />
                </svg>
            </span>
            <span v-else>
                Sign in
            </span>
        </button>
    </form>
</template>

<style scoped lang="scss">

</style>