<template> <div class="grow flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8"> <div class="max-w-md w-full space-y-8 pb-20"> <div class="flex flex-col items-center gap-4"> <AppLogo /> <h2 class="text-center text-4xl font-extrabold text-gray-900">Zugang zurücksetzen</h2> </div> <div v-if="verification == 'loading'" class="flex flex-col gap-2 items-center"> <p class="w-fit">Rücksetz-Link wird verifiziert</p> <Spinner class="my-auto" /> </div> <div v-else-if="verification == 'failed'" class="flex flex-col gap-2 items-center"> <p class="w-fit">Rücksetz-Link nicht gültig</p> <RouterLink to="/reset" class="text-primary">Zum zurücksetzen Start</RouterLink> </div> <form v-else class="flex flex-col gap-2" @submit.prevent="reset"> <div class="w-full flex flex-row gap-2 justify-center"> <p class="w-1/2 p-0.5 pl-0 rounded-lg py-2.5 text-sm text-center font-medium leading-5 outline-hidden cursor-pointer" :class=" tab == 'totp' ? 'bg-red-200 shadow-sm border-b-2 border-primary rounded-b-none' : ' hover:bg-red-200' " @click="tab = 'totp'" > TOTP </p> <p class="w-1/2 p-0.5 rounded-lg py-2.5 text-sm text-center font-medium leading-5 outline-hidden cursor-pointer" :class=" tab == 'password' ? 'bg-red-200 shadow-sm border-b-2 border-primary rounded-b-none' : 'hover:bg-red-200' " @click="tab = 'password'" > Passwort </p> </div> <div v-if="tab == 'totp'" class="flex flex-col gap-2"> <img :src="image" alt="totp" class="w-56 h-56 self-center" /> <TextCopy :copyText="otp" /> <div class="-space-y-px"> <div> <input id="totp" name="totp" type="text" required placeholder="TOTP" /> </div> </div> </div> <div v-else> <input id="password" name="password" type="password" required placeholder="Passwort" class="rounded-b-none!" autocomplete="new-password" :class="notMatching ? 'border-red-600!' : ''" /> <input id="password_rep" name="password_rep" type="password" required placeholder="Passwort wiederholen" class="rounded-t-none!" autocomplete="new-password" :class="notMatching ? 'border-red-600!' : ''" /> <p v-if="notMatching">Passwörter stimmen nicht überein</p> </div> <div class="flex flex-row gap-2"> <button type="submit" primary :disabled="resetStatus == 'loading' || resetStatus == 'success'"> TOTP zurücksetzen </button> <Spinner v-if="resetStatus == 'loading'" class="my-auto" /> <SuccessCheckmark v-else-if="resetStatus == 'success'" /> <FailureXMark v-else-if="resetStatus == 'failed'" /> </div> <p v-if="resetError" class="text-center">{{ resetError }}</p> </form> <FormBottomBar /> </div> </div> </template> <script setup lang="ts"> import { defineComponent } from "vue"; import Spinner from "@/components/Spinner.vue"; import SuccessCheckmark from "@/components/SuccessCheckmark.vue"; import FailureXMark from "@/components/FailureXMark.vue"; import { RouterLink } from "vue-router"; import FormBottomBar from "@/components/FormBottomBar.vue"; import TextCopy from "@/components/TextCopy.vue"; import AppLogo from "@/components/AppLogo.vue"; import { hashString } from "@/helpers/crypto"; </script> <script lang="ts"> export default defineComponent({ props: { token: String, mail: String, }, data() { return { tab: "totp", verification: "loading" as "success" | "loading" | "failed", image: undefined as undefined | string, otp: undefined as undefined | string, resetStatus: undefined as undefined | "loading" | "success" | "failed", resetError: "" as string, notMatching: false as boolean, }; }, mounted() { this.$http .post(`/reset/verify`, { token: this.token, mail: this.mail, }) .then((result) => { setTimeout(() => { this.verification = "success"; this.image = result.data.dataUrl; this.otp = result.data.otp; }, 1000); }) .catch((err) => { setTimeout(() => { this.verification = "failed"; }, 1000); }); }, methods: { async reset(e: any) { let secret = ""; if (this.tab == "totp") secret = this.totp(e); else secret = await this.password(e); if (secret == "") return; this.resetStatus = "loading"; this.resetError = ""; this.$http .put(`/reset`, { token: this.token, mail: this.mail, secret: secret, routine: this.tab, }) .then((result) => { this.resetStatus = "success"; localStorage.setItem("accessToken", result.data.accessToken); localStorage.setItem("refreshToken", result.data.refreshToken); setTimeout(() => { this.$router.push(`/admin`); }, 1000); }) .catch((err) => { this.resetStatus = "failed"; this.resetError = err.response.data; }); }, totp(e: any) { let formData = e.target.elements; return formData.totp.value; }, async password(e: any) { let formData = e.target.elements; let new_pw = await hashString(formData.password.value); let new_rep = await hashString(formData.password_rep.value); if (new_pw != new_rep) { this.notMatching = true; return ""; } this.notMatching = false; return await hashString(formData.password.value); }, }, }); </script>