mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 16:22:16 +01:00
263 lines
9.5 KiB
Vue
263 lines
9.5 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, watch } from 'vue';
|
|
import { router, useForm, usePage } from '@inertiajs/vue3';
|
|
import ActionSection from '@/Components/ActionSection.vue';
|
|
import ConfirmsPassword from '@/Components/ConfirmsPassword.vue';
|
|
import DangerButton from '@/packages/ui/src/Buttons/DangerButton.vue';
|
|
import InputError from '@/packages/ui/src/Input/InputError.vue';
|
|
import InputLabel from '@/packages/ui/src/Input/InputLabel.vue';
|
|
import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue';
|
|
import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue';
|
|
import TextInput from '@/packages/ui/src/Input/TextInput.vue';
|
|
import axios from 'axios';
|
|
import type { JetstreamUser } from '@/types/jetstream';
|
|
|
|
const props = defineProps<{
|
|
requiresConfirmation: boolean;
|
|
}>();
|
|
|
|
const page = usePage<{
|
|
auth: {
|
|
user: JetstreamUser;
|
|
};
|
|
}>();
|
|
const enabling = ref(false);
|
|
const confirming = ref(false);
|
|
const disabling = ref(false);
|
|
const qrCode = ref(null);
|
|
const setupKey = ref(null);
|
|
const recoveryCodes = ref([]);
|
|
|
|
const confirmationForm = useForm({
|
|
code: '',
|
|
});
|
|
|
|
const twoFactorEnabled = computed(
|
|
() => !enabling.value && page.props.auth.user?.two_factor_enabled
|
|
);
|
|
|
|
watch(twoFactorEnabled, () => {
|
|
if (!twoFactorEnabled.value) {
|
|
confirmationForm.reset();
|
|
confirmationForm.clearErrors();
|
|
}
|
|
});
|
|
|
|
const enableTwoFactorAuthentication = () => {
|
|
enabling.value = true;
|
|
|
|
router.post(
|
|
route('two-factor.enable'),
|
|
{},
|
|
{
|
|
preserveScroll: true,
|
|
onSuccess: () => Promise.all([showQrCode(), showSetupKey(), showRecoveryCodes()]),
|
|
onFinish: () => {
|
|
enabling.value = false;
|
|
confirming.value = props.requiresConfirmation;
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
const showQrCode = () => {
|
|
return axios.get(route('two-factor.qr-code')).then((response) => {
|
|
qrCode.value = response.data.svg;
|
|
});
|
|
};
|
|
|
|
const showSetupKey = () => {
|
|
return axios.get(route('two-factor.secret-key')).then((response) => {
|
|
setupKey.value = response.data.secretKey;
|
|
});
|
|
};
|
|
|
|
const showRecoveryCodes = () => {
|
|
return axios.get(route('two-factor.recovery-codes')).then((response) => {
|
|
recoveryCodes.value = response.data;
|
|
});
|
|
};
|
|
|
|
const confirmTwoFactorAuthentication = () => {
|
|
confirmationForm.post(route('two-factor.confirm'), {
|
|
errorBag: 'confirmTwoFactorAuthentication',
|
|
preserveScroll: true,
|
|
preserveState: true,
|
|
onSuccess: () => {
|
|
confirming.value = false;
|
|
qrCode.value = null;
|
|
setupKey.value = null;
|
|
},
|
|
});
|
|
};
|
|
|
|
const regenerateRecoveryCodes = () => {
|
|
axios.post(route('two-factor.recovery-codes')).then(() => showRecoveryCodes());
|
|
};
|
|
|
|
const disableTwoFactorAuthentication = () => {
|
|
disabling.value = true;
|
|
|
|
router.delete(route('two-factor.disable'), {
|
|
preserveScroll: true,
|
|
onSuccess: () => {
|
|
disabling.value = false;
|
|
confirming.value = false;
|
|
},
|
|
});
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<ActionSection>
|
|
<template #title> Two Factor Authentication </template>
|
|
|
|
<template #description>
|
|
Add additional security to your account using two factor authentication.
|
|
</template>
|
|
|
|
<template #content>
|
|
<h3
|
|
v-if="twoFactorEnabled && !confirming"
|
|
class="text-lg font-medium text-text-secondary">
|
|
You have enabled two factor authentication.
|
|
</h3>
|
|
|
|
<h3
|
|
v-else-if="twoFactorEnabled && confirming"
|
|
class="text-lg font-medium text-text-secondary">
|
|
Finish enabling two factor authentication.
|
|
</h3>
|
|
|
|
<h3 v-else class="text-lg font-medium text-text-primary">
|
|
You have not enabled two factor authentication.
|
|
</h3>
|
|
|
|
<div class="mt-3 max-w-xl text-sm text-text-secondary">
|
|
<p>
|
|
When two factor authentication is enabled, you will be prompted for a secure,
|
|
random token during authentication. You may retrieve this token from your
|
|
phone's Google Authenticator application.
|
|
</p>
|
|
</div>
|
|
|
|
<div v-if="twoFactorEnabled">
|
|
<div v-if="qrCode">
|
|
<div class="mt-4 max-w-xl text-sm text-text-secondary">
|
|
<p v-if="confirming" class="font-semibold">
|
|
To finish enabling two factor authentication, scan the following QR code
|
|
using your phone's authenticator application or enter the setup key and
|
|
provide the generated OTP code.
|
|
</p>
|
|
|
|
<p v-else>
|
|
Two factor authentication is now enabled. Scan the following QR code
|
|
using your phone's authenticator application or enter the setup key.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="mt-4 p-2 inline-block bg-white" v-html="qrCode" />
|
|
|
|
<div v-if="setupKey" class="mt-4 max-w-xl text-sm text-text-secondary">
|
|
<p class="font-semibold">Setup Key: <span v-html="setupKey"></span></p>
|
|
</div>
|
|
|
|
<div v-if="confirming" class="mt-4">
|
|
<InputLabel for="code" value="Code" />
|
|
|
|
<TextInput
|
|
id="code"
|
|
v-model="confirmationForm.code"
|
|
type="text"
|
|
name="code"
|
|
class="block mt-1 w-1/2"
|
|
inputmode="numeric"
|
|
autofocus
|
|
autocomplete="one-time-code"
|
|
@keyup.enter="confirmTwoFactorAuthentication" />
|
|
|
|
<InputError :message="confirmationForm.errors.code" class="mt-2" />
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="recoveryCodes.length > 0 && !confirming">
|
|
<div class="mt-4 max-w-xl text-sm text-muted">
|
|
<p class="font-semibold">
|
|
Store these recovery codes in a secure password manager. They can be
|
|
used to recover access to your account if your two factor authentication
|
|
device is lost.
|
|
</p>
|
|
</div>
|
|
|
|
<div
|
|
class="grid gap-1 max-w-xl mt-4 px-4 py-4 font-mono text-sm bg-input-background text-text-secondary rounded-lg">
|
|
<div v-for="code in recoveryCodes" :key="code">
|
|
{{ code }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-5">
|
|
<div v-if="!twoFactorEnabled">
|
|
<ConfirmsPassword @confirmed="enableTwoFactorAuthentication">
|
|
<PrimaryButton
|
|
type="button"
|
|
:class="{ 'opacity-25': enabling }"
|
|
:disabled="enabling">
|
|
Enable
|
|
</PrimaryButton>
|
|
</ConfirmsPassword>
|
|
</div>
|
|
|
|
<div v-else>
|
|
<ConfirmsPassword @confirmed="confirmTwoFactorAuthentication">
|
|
<PrimaryButton
|
|
v-if="confirming"
|
|
type="button"
|
|
class="me-3"
|
|
:class="{ 'opacity-25': enabling }"
|
|
:disabled="enabling">
|
|
Confirm
|
|
</PrimaryButton>
|
|
</ConfirmsPassword>
|
|
|
|
<ConfirmsPassword @confirmed="regenerateRecoveryCodes">
|
|
<SecondaryButton
|
|
v-if="recoveryCodes.length > 0 && !confirming"
|
|
class="me-3">
|
|
Regenerate Recovery Codes
|
|
</SecondaryButton>
|
|
</ConfirmsPassword>
|
|
|
|
<ConfirmsPassword @confirmed="showRecoveryCodes">
|
|
<SecondaryButton
|
|
v-if="recoveryCodes.length === 0 && !confirming"
|
|
class="me-3">
|
|
Show Recovery Codes
|
|
</SecondaryButton>
|
|
</ConfirmsPassword>
|
|
|
|
<ConfirmsPassword @confirmed="disableTwoFactorAuthentication">
|
|
<SecondaryButton
|
|
v-if="confirming"
|
|
:class="disabling ? 'opacity-25' : ''"
|
|
:disabled="disabling">
|
|
Cancel
|
|
</SecondaryButton>
|
|
</ConfirmsPassword>
|
|
|
|
<ConfirmsPassword @confirmed="disableTwoFactorAuthentication">
|
|
<DangerButton
|
|
v-if="!confirming"
|
|
:class="{ 'opacity-25': disabling }"
|
|
:disabled="disabling">
|
|
Disable
|
|
</DangerButton>
|
|
</ConfirmsPassword>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</ActionSection>
|
|
</template>
|