Files
drop/server/pages/mfa/setup/totp.vue
T
DecDuck 13c97cfcfc In-app store, torrential backend, locales (#332)
* feat: add store nav and fixes

* fix: reduce password requirement & new task error ui

* fix: client webtoken fix

* fix: delta versions and dockerfile

* fix: use setup platforms for filter & display

* fix: setup not accounted when returning valid options

* feat: tighter delta version support

* feat: dl/disk size

* feat: offload manifest generation to torrential

* fix: bump torrential

* feat: remove droplet

* feat: bump torrential

* feat: convert locales
2026-02-06 00:12:24 +11:00

91 lines
2.5 KiB
Vue

<template>
<main
class="mx-auto grid lg:grid-cols-2 max-w-md lg:max-w-none min-h-full place-items-center w-full gap-4 px-6 py-12 sm:py-32 lg:px-8"
>
<div>
<div class="text-left max-w-md">
<h1
class="mt-4 text-3xl font-bold font-display tracking-tight text-zinc-100 sm:text-5xl"
>
{{ $t("auth.2fa.totp.createTitle") }}
</h1>
<p class="mt-6 text-base leading-7 text-zinc-400">
{{ $t("auth.2fa.totp.createDescription") }}
</p>
<div class="mt-8">
<p class="text-xs leading-7 text-zinc-200">
{{ $t("auth.2fa.totp.createHint") }}
</p>
<div class="mt-2 flex flex-row gap-2">
<CodeInput
:length="6"
placeholder="123456"
size="w-10 h-10 text-sm"
@complete="(code) => complete(code)"
/>
</div>
<div
v-if="error"
class="mt-4 rounded-md bg-red-600/10 p-4 max-w-sm mx-auto"
>
<div class="flex">
<div class="flex-shrink-0">
<XCircleIcon class="h-5 w-5 text-red-600" aria-hidden="true" />
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-red-600">
{{ error }}
</h3>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div class="max-w-2xl flex flex-col items-center gap-2">
<div id="qrcode" />
<p
class="font-bold font-display text-zinc-500 uppercase font-sm tracking-tight"
>
{{ totpSecrets?.secret }}
</p>
</div>
</div>
</main>
</template>
<script setup lang="ts">
import type { FetchError } from "ofetch";
useHead({
title: "Set up TOTP",
});
const totpSecrets = await $dropFetch("/api/v1/user/mfa/totp/start", {
method: "POST",
});
const error = ref<string | undefined>();
const router = useRouter();
onMounted(async () => {
const kjua = await import("kjua");
const el = kjua.default({ text: totpSecrets.url, render: "svg", size: 400 });
document.querySelector("#qrcode")?.appendChild(el);
});
async function complete(code: string) {
try {
await $dropFetch("/api/v1/user/mfa/totp/finish", {
method: "POST",
body: { code },
});
router.push("/mfa/setup/successful");
} catch (e) {
error.value =
(e as FetchError).data?.message ?? (e as FetchError).statusMessage;
}
}
</script>