OWASP/SSO_Project

View on GitHub
vue-ui/src/views/login.vue

Summary

Maintainability
Test Coverage
<template>
    <div class="card-body">
        <h4 class="card-title">
        {{ $t("router.login") }}
    </h4>
        <ValidationObserver
            v-if="!loading"
            v-slot="{ invalid }"
        >
            <div
                v-if="availableUsers.length > 0"
                id="resume-session"
                class="mb-4 row"
            >
                <div
                    v-for="(item, index) in availableUsers"
                    :key="index"
                    class="col-sm-3"
                >
                    <a
                        href="#"
                        :title="$t('login.resume', { email: item.email })"
                        @click="resumeSession(item.email)"
                    >
                        <img
                            class="rounded-circle img-fluid"
                            :alt="item.email"
                            :src="'https://www.gravatar.com/avatar/' + item.hash"
                        >
                    </a>
                </div>
            </div>
        
            <form @submit.prevent="submit">
                <div
                    v-if="error == 404"
                    class="alert alert-danger"
                >
                    {{ $t("login.404") }}
                </div>

                <div class="form-group">
                    <label for="email">{{ $t("general.email-address") }}</label>
                    <ValidationProvider
                        v-slot="{ errors }"
                        name="Email"
                    >
                        <input
                            id="email"
                            v-model="email"
                            type="email"
                            class="form-control"
                            autocomplete="username"
                            required
                            autofocus
                        >
                        <span
                            v-if="errors.length"
                            class="badge badge-danger"
                        >
                            {{ $t("login.fill-email") }}
                        </span>
                    </ValidationProvider>
                </div>

                <div class="form-group">
                    <label
                        for="password"
                    >
                        {{ $t("general.password") }}
                        <router-link
                            id="goReset"
                            to="/reset-password"
                            class="float-right"
                        >
                            {{ $t("login.reset-password") }}
                        </router-link>
                    </label>
                    <ValidationProvider
                        v-slot="{ errors }"
                        rules="required|min:8|max:255"
                        name="Password"
                    >
                        <input
                            id="password"
                            v-model="password"
                            type="password"
                            class="form-control"
                            autocomplete="current-password"
                        >
                        <span
                            v-if="errors.length"
                            class="badge badge-danger"
                        >
                            {{ $t("login.fill-password") }}
                        </span>
                    </ValidationProvider>
                </div>

                <div class="form-group m-0">
                    <button
                        type="submit"
                        class="btn btn-primary btn-block"
                        :disabled="invalid"
                    >
                        {{ $t("login.login") }}
                    </button>
                </div>
                <div class="mt-4 text-center">
                    {{ $t("login.no-account") }}
                    <router-link
                        id="goRegister"
                        to="/register"
                    >
                        {{ $t("login.register") }}
                    </router-link>
                </div>
            </form>
        </ValidationObserver>

        <div
            v-if="loading"
            class="col-md-12 text-center"
        >
            <div
                class="spinner-border spinner-border-lg"
                role="status"
            ></div>
        </div>
        
        <iframe
            ref="certFrame"
            name="certFrame"
            class="hidden"
            @load="certFrameLoad"
        ></iframe>
        <form
            ref="certForm"
            :action="$root.backend + '/cert/login'"
            target="certFrame"
            method="POST"
            class="hidden"
        >
            <input
                v-if="$root.authToken && $root.authToken.token"
                type="hidden"
                name="authorizationToken"
                :value="$root.authToken.token"
            >
            <input
                v-if="$root.ssoPage && $root.ssoPage.token"
                type="hidden"
                name="token"
                :value="$root.ssoPage.token"
            >
        </form>
    </div>
</template>

<script>
const md5 = require("md5");

export default {
    name: "Login",
    data() {
        return {
            email: "",
            password: "",
            loading: false,
            error: 0,
            certTimeout: null,
            availableUsers: [],
            certSubmitted: false,
        };
    },
    beforeMount() {
        this.loadAvailableUsers();
    },
    mounted() {
        window.addEventListener("message", event => {
            if(!event.data || event.data.type /*Webpack */) return;
            if(event.data.token) {
                clearTimeout(this.certTimeout);
                this.$root.setLoginToken(event.data.username, event.data);
                
                this.$router.push("/audit");
            }
        }, false);
    },
    methods: {
        loadAvailableUsers() {
            this.availableUsers = [];
            this.$root.listLoginToken().forEach(email => {
                this.availableUsers.push({
                    hash: md5(email),
                    email: email,
                });
            });
            
            this.loading = false;
        },
        submit() {
            this.loading = true;

            this.$root.apiPost("/local/login", {
                username: this.email,
                password: this.password,
            })
                .then(token => {
                    this.$root.setLoginToken(token.data.username, token.data);
                    
                    this.routeUser();
                })
                .catch(err => {
                    console.error(err);
                    this.error = err.response.status;
                    this.loading = false;
                });
        },
        routeUser() {
            if(!this.$root.authToken) {
                this.loading = false;
                return;
            }

            this.$root
                .getMe()
                .then(() => {
                    this.$refs.certForm.submit();
                    this.certSubmitted = true;
                })
                .catch(() => {
                    this.loading = false;
                    this.error = 404;
                    this.$root.changeUser(this.$root.emptyUser);
                    this.loadAvailableUsers();
                });
        },
        certFrameLoad() {
            // If the certificate login fails, there will be no message posted and it can't proceed
            // This is why we here need to detect when the page has finished loading and there was no message
            if(this.$root.user.id && this.certSubmitted) {
                this.certTimeout = setTimeout(() => {
                    this.$router.push(
                        this.$root.user.isAuthenticated ? "/audit" : "/two-factor"
                    );
                }, 1000);
            }
        },
        resumeSession(email) {
            this.loading = true;
            this.$root.useLoginToken(email);
            this.routeUser();
        },
    },
};
</script>