OWASP/SSO_Project

View on GitHub
vue-ui/src/components/AuthenticatorManage.vue

Summary

Maintainability
Test Coverage
<template>
    <div id="authenticatorManage">
        <svg
            aria-hidden="true"
            version="1.1"
            xmlns="http://www.w3.org/2000/svg"
            xmlns:xlink="http://www.w3.org/1999/xlink"
        >
            <defs>
                <symbol
                    id="icon-stopwatch"
                    viewBox="0 0 32 32"
                >
                    <credit>"stopwatch" by Keyamoon (IcoMoon - icomoon.io)</credit>
                    <path d="M16 6.038v-2.038h4v-2c0-1.105-0.895-2-2-2h-6c-1.105 0-2 0.895-2 2v2h4v2.038c-6.712 0.511-12 6.119-12 12.962 0 7.18 5.82 13 13 13s13-5.82 13-13c0-6.843-5.288-12.451-12-12.962zM22.071 26.071c-1.889 1.889-4.4 2.929-7.071 2.929s-5.182-1.040-7.071-2.929c-1.889-1.889-2.929-4.4-2.929-7.071s1.040-5.182 2.929-7.071c1.814-1.814 4.201-2.844 6.754-2.923l-0.677 9.813c-0.058 0.822 0.389 1.181 0.995 1.181s1.053-0.36 0.995-1.181l-0.677-9.813c2.552 0.079 4.94 1.11 6.754 2.923 1.889 1.889 2.929 4.4 2.929 7.071s-1.040 5.182-2.929 7.071z" />
                </symbol>
                <symbol
                    id="icon-bin2"
                    viewBox="0 0 32 32"
                >
                    <credit>"bin2" by Keyamoon (IcoMoon - icomoon.io)</credit>
                    <path d="M6 32h20l2-22h-24zM20 4v-4h-8v4h-10v6l2-2h24l2 2v-6h-10zM18 4h-4v-2h4v2z" />
                </symbol>
                <symbol
                    id="icon-certificate"
                    viewBox="0 0 32 32"
                >
                    <credit>"certificate" by Yannick Lung (Hawcons - hawcons.com)</credit>
                    <path
                        d="M13 19.95v0 0c0.619-0.631 1-1.496 1-2.45 0-1.933-1.567-3.5-3.5-3.5s-3.5 1.567-3.5 3.5c0 0.954 0.381 1.818 1 2.45v3.050h-1.995c-1.108 0-2.005-0.895-2.005-2v-13c0-1.11 0.898-2 2.005-2h19.99c1.108 0 2.005 0.895 2.005 2v13c0 1.11-0.898 2-2.005 2h-12.995v-3.050zM9 20.663v4.937l1.5-1.5 1.5 1.5v-4.937c-0.455 0.216-0.963 0.337-1.5 0.337s-1.045-0.121-1.5-0.337v0 0zM7 11v1h18v-1h-18zM16 14v1h9v-1h-9zM19 17v1h6v-1h-6zM10.5 20c1.381 0 2.5-1.119 2.5-2.5s-1.119-2.5-2.5-2.5c-1.381 0-2.5 1.119-2.5 2.5s1.119 2.5 2.5 2.5v0z"
                    />
                </symbol>
                <symbol
                    id="icon-plus"
                    viewBox="0 0 32 32"
                >
                    <credit>"plus" by Keyamoon (IcoMoon - icomoon.io)</credit>
                    <path
                        d="M31 12h-11v-11c0-0.552-0.448-1-1-1h-6c-0.552 0-1 0.448-1 1v11h-11c-0.552 0-1 0.448-1 1v6c0 0.552 0.448 1 1 1h11v11c0 0.552 0.448 1 1 1h6c0.552 0 1-0.448 1-1v-11h11c0.552 0 1-0.448 1-1v-6c0-0.552-0.448-1-1-1z"
                    />
                </symbol>
            </defs>
        </svg>
    
        <h4 class="card-title mb-2">
            {{ $root.user.authenticators.length ? $t("authenticator.review") : $t("authenticator.add") }}
        </h4>
        <div
            v-if="!$root.user.authenticators.length"
            id="help-setup"
            class="alert alert-info mb-0"
        >
            <svg>
                <use xlink:href="#icon-stopwatch" />
            </svg>
            <span>{{ $t("authenticator.hint") }}</span>
        </div>
        <div class="list-group">
            <div
                v-for="authenticator in $root.user.authenticators"
                :key="authenticator.handle"
                class="list-group-item d-flex justify-content-between align-items-center"
            >
                <svg
                    :title="
                        authenticator.type == 'fido2'
                            ? $t('authenticator.fido2-title')
                            : $t('authenticator.cert-title')
                    "
                >
                    <use
                        :xlink:href="
                            '#icon-' +
                                (authenticator.type == 'fido2'
                                    ? 'fidoalliance'
                                    : 'certificate')
                        "
                    />
                </svg>
                {{ authenticator.label }}
                <a
                    href="#"
                    class="text-danger float-right remove-authenticator"
                    :title="$t('authenticator.remove')"
                    @click="
                        removeAuthenticator(authenticator.type, authenticator.userHandle)
                    "
                >
                    <svg><use xlink:href="#icon-bin2" /></svg>
                </a>
            </div>

            <ValidationObserver
                ref="authCreator"
                v-slot="{ handleSubmit, reset }"
            >
                <form @submit.prevent="handleSubmit(createAuthenticator)">
                    <div
                        id="add-authenticator-group"
                        class="input-group"
                    >
                        <div class="input-group-prepend col-sm-4 p-0">
                            <ValidationProvider
                                v-slot="{ errors }"
                                rules="required"
                                name="type"
                            >
                                <select
                                    v-model="createType"
                                    class="custom-select"
                                    :title="$t('authenticator.choose-type-title')"
                                >
                                    <option
                                        selected
                                        disabled
                                    >
                                        {{ $t("authenticator.choose") }}
                                    </option>
                                    <option
                                        value="cert"
                                    >
                                        {{ $t("authenticator.choose-cert") }}
                                    </option>
                                    <option
                                        value="fido"
                                        :disabled="!fidoAvailable"
                                    >
                                        {{ $t("confirm.fido2") }}
                                    </option>
                                </select>
                                <span
                                    v-if="errors.length"
                                    class="badge badge-danger"
                                >
                                    {{ $t("authenticator.fill-choose") }}
                                </span>
                            </ValidationProvider>
                        </div>
                        <ValidationProvider
                            v-slot="{ errors }"
                            name="label"
                            class="form-control col-sm-6"
                        >
                            <input
                                v-model="createLabel"
                                type="text"
                                :placeholder="$t('authenticator.label')"
                                pattern="^[\w \-]{1,25}$"
                                class="col-sm-12"
                                :title="$t('authenticator.label-title')"
                                required
                            >
                            <span
                                v-if="errors.length"
                                class="badge badge-danger"
                            >
                                {{ $t("authenticator.fill-label") }}
                            </span>
                        </ValidationProvider>
                        <div
                            class="input-group-append col-sm-2"
                            :title="$t('authenticator.submit-title')"
                        >
                            <button
                                type="submit"
                                class="btn btn-outline-secondary"
                            >
                                <svg><use xlink:href="#icon-plus" /></svg>
                            </button>
                        </div>
                    </div>
                </form>
            </ValidationObserver>
        </div>
    </div>
</template>
<script>
const base64buffer = require("base64-arraybuffer");

export default {
    name: "AuthenticatorManage",
    data() {
        return {
            createType: window.PublicKeyCredential ? "fido" : "cert",
            createLabel: "",
            fidoError: 0,
            tempToken: "",
            fidoAvailable: window.PublicKeyCredential,
        };
    },
    methods: {
        removeAuthenticator(type, handle) {
            if (!confirm("Are you sure that you want to delete this authenticator?"))
                return;
            this.$root.apiPost("/authenticator/delete", {
                session: this.$root.user.session,
                handle,
                type,
            })
                .then(() => {
                    this.$emit("reload");
                })
                .catch(() => {
                    // todo
                });
        },
        createAuthenticator() {
            const that = this;
            switch (this.createType) {
                case "cert":
                    this.axios({
                        url: this.$root.backend + "/cert/register",
                        method: "POST",
                        responseType: "blob",
                        data: {
                            label: this.createLabel,
                        },
                    }).then(response => {
                        const url = window.URL.createObjectURL(new Blob([response.data]));
                        const link = document.createElement("a");
                        link.href = url;
                        link.target = "_blank";
                        link.id = "download-cert-tmp";
                        link.setAttribute("download", this.$root.user.username + ".p12");
                        document.body.appendChild(link);
                        
                        if(!window.Cypress) {
                            link.click();
                            link.remove();
                        }
                        
                        this.$emit("reload");
                    });
                    break;
                case "fido":
                    this.$root.apiGet("/fido2/register")
                        .then(dataRaw => {
                            const thisData = dataRaw.data;
                            
                            this.tempToken = thisData.token;
                            const registrationOptions = thisData.options;

                            registrationOptions.challenge = base64buffer.decode(
                                registrationOptions.challenge
                            );
                            registrationOptions.user.name = this.$root.user.username;
                            registrationOptions.user.displayName = this.$root.user.username;
                            registrationOptions.user.id = base64buffer.decode(
                                registrationOptions.user.id
                            );

                            return navigator.credentials.create({
                                publicKey: registrationOptions,
                            });
                        })
                        .then(credential => {
                            const passableCredential = {
                                id: credential.id,
                                rawId: base64buffer.encode(credential.rawId),
                                response: {
                                    clientDataJSON: base64buffer.encode(
                                        credential.response.clientDataJSON
                                    ),
                                    attestationObject: base64buffer.encode(
                                        credential.response.attestationObject
                                    ),
                                },
                                type: credential.type,
                            };

                            this.$root.apiPost("/fido2/register", {
                                token: that.tempToken,
                                response: passableCredential,
                                label: this.createLabel,
                            })
                                .then(() => {
                                    this.$emit("reload");
                                })
                                .catch(err => {
                                    this.fidoError = err.response.status;
                                })
                                .finally(() => {
                                    this.createType = null;
                                    this.createLabel = "";
                                    this.$refs.authCreator.reset();
                                });
                        })
                        .then(() => {})
                        .catch(err => {
                            console.error(err);
                            this.fidoError = 1;
                        });
                    break;
            }
        },
    },
};
</script>
<style scoped>
.list-group-item > svg {
    font-size: 2em;
}

a.text-danger > svg {
    font-size: 1.5em;
}

#add-authenticator-group input,
#add-authenticator-group .form-control,
#add-authenticator-group button,
#add-authenticator-group select {
    border: 0;
    border-bottom: 1px;
}

#add-authenticator-group {
    border-bottom-left-radius: 0.25rem;
    border-bottom-right-radius: 0.25rem;
    border: 1px solid rgba(0, 0, 0, 0.125);
    border-top: 0;
}

#add-authenticator-group button {
    margin-left: -0.5em;
    border-radius: 0.25rem;
}

#help-setup {
    display: flex;
    flex-direction: row;
}

#help-setup svg {
    margin-right: 10px;
    align-self: center;
    font-size: 5em;
}
</style>