refactor: remove two-factor authentication management features from security settings
This commit is contained in:
parent
098c2a9cf1
commit
2fe3173817
@ -4,44 +4,18 @@
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\Settings\PasswordUpdateRequest;
|
use App\Http\Requests\Settings\PasswordUpdateRequest;
|
||||||
use App\Http\Requests\Settings\TwoFactorAuthenticationRequest;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Routing\Controllers\HasMiddleware;
|
|
||||||
use Illuminate\Routing\Controllers\Middleware;
|
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
use Inertia\Response;
|
use Inertia\Response;
|
||||||
use Laravel\Fortify\Features;
|
|
||||||
|
|
||||||
class SecurityController extends Controller implements HasMiddleware
|
class SecurityController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Get the middleware that should be assigned to the controller.
|
|
||||||
*/
|
|
||||||
public static function middleware(): array
|
|
||||||
{
|
|
||||||
return Features::canManageTwoFactorAuthentication()
|
|
||||||
&& Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword')
|
|
||||||
? [new Middleware('password.confirm', only: ['edit'])]
|
|
||||||
: [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the user's security settings page.
|
* Show the user's security settings page.
|
||||||
*/
|
*/
|
||||||
public function edit(TwoFactorAuthenticationRequest $request): Response
|
public function edit(): Response
|
||||||
{
|
{
|
||||||
$props = [
|
return Inertia::render('settings/security');
|
||||||
'canManageTwoFactor' => Features::canManageTwoFactorAuthentication(),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (Features::canManageTwoFactorAuthentication()) {
|
|
||||||
$request->ensureStateIsValid();
|
|
||||||
|
|
||||||
$props['twoFactorEnabled'] = $request->user()->hasEnabledTwoFactorAuthentication();
|
|
||||||
$props['requiresConfirmation'] = Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm');
|
|
||||||
}
|
|
||||||
|
|
||||||
return Inertia::render('settings/security', $props);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,64 +1,25 @@
|
|||||||
import SecurityController from '@/actions/App/Http/Controllers/Settings/SecurityController';
|
import SecurityController from '@/actions/App/Http/Controllers/Settings/SecurityController';
|
||||||
import PasswordInput from '@/components/password-input';
|
import PasswordInput from '@/components/password-input';
|
||||||
import TwoFactorRecoveryCodes from '@/components/two-factor-recovery-codes';
|
|
||||||
import TwoFactorSetupModal from '@/components/two-factor-setup-modal';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Field, FieldError } from "@/components/ui/field";
|
import { Field, FieldError } from "@/components/ui/field";
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { useTwoFactorAuth } from '@/hooks/use-two-factor-auth';
|
|
||||||
import { edit } from '@/routes/security';
|
import { edit } from '@/routes/security';
|
||||||
import { disable, enable } from '@/routes/two-factor';
|
|
||||||
import { Head, useForm } from '@inertiajs/react';
|
import { Head, useForm } from '@inertiajs/react';
|
||||||
import { Loader, Save, ShieldCheck } from 'lucide-react';
|
import { Loader, Save } from 'lucide-react';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useRef } from 'react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
type Props = {
|
export default function Security() {
|
||||||
canManageTwoFactor?: boolean;
|
|
||||||
requiresConfirmation?: boolean;
|
|
||||||
twoFactorEnabled?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Security({
|
|
||||||
canManageTwoFactor = false,
|
|
||||||
requiresConfirmation = false,
|
|
||||||
twoFactorEnabled = false,
|
|
||||||
}: Props) {
|
|
||||||
const passwordInput = useRef<HTMLInputElement>(null);
|
const passwordInput = useRef<HTMLInputElement>(null);
|
||||||
const currentPasswordInput = useRef<HTMLInputElement>(null);
|
const currentPasswordInput = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const {
|
|
||||||
qrCodeSvg,
|
|
||||||
hasSetupData,
|
|
||||||
manualSetupKey,
|
|
||||||
clearSetupData,
|
|
||||||
clearTwoFactorAuthData,
|
|
||||||
fetchSetupData,
|
|
||||||
recoveryCodesList,
|
|
||||||
fetchRecoveryCodes,
|
|
||||||
errors: tfaErrors,
|
|
||||||
} = useTwoFactorAuth();
|
|
||||||
const [showSetupModal, setShowSetupModal] = useState<boolean>(false);
|
|
||||||
const prevTwoFactorEnabled = useRef(twoFactorEnabled);
|
|
||||||
|
|
||||||
const { data, setData, put, processing, errors, reset } = useForm({
|
const { data, setData, put, processing, errors, reset } = useForm({
|
||||||
current_password: '',
|
current_password: '',
|
||||||
password: '',
|
password: '',
|
||||||
password_confirmation: '',
|
password_confirmation: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { post: postEnable, processing: processingEnable } = useForm({});
|
|
||||||
const { post: postDisable, processing: processingDisable } = useForm({});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (prevTwoFactorEnabled.current && !twoFactorEnabled) {
|
|
||||||
clearTwoFactorAuthData();
|
|
||||||
}
|
|
||||||
|
|
||||||
prevTwoFactorEnabled.current = twoFactorEnabled;
|
|
||||||
}, [twoFactorEnabled, clearTwoFactorAuthData]);
|
|
||||||
|
|
||||||
const onSubmit = (e: React.FormEvent) => {
|
const onSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
put(SecurityController.update().url, {
|
put(SecurityController.update().url, {
|
||||||
@ -78,18 +39,6 @@ export default function Security({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEnable2FA = (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
postEnable(enable().url, {
|
|
||||||
onSuccess: () => setShowSetupModal(true),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDisable2FA = (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
postDisable(disable().url);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<Head title="Keamanan Akun" />
|
<Head title="Keamanan Akun" />
|
||||||
@ -97,131 +46,59 @@ export default function Security({
|
|||||||
<h1 className="sr-only">Keamanan Akun</h1>
|
<h1 className="sr-only">Keamanan Akun</h1>
|
||||||
|
|
||||||
<form onSubmit={onSubmit} className="space-y-6">
|
<form onSubmit={onSubmit} className="space-y-6">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
|
||||||
<div className="lg:col-span-2 space-y-6">
|
<CardHeader className="border-b">
|
||||||
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
|
<CardTitle>Perbarui Kata Sandi</CardTitle>
|
||||||
<CardHeader className="border-b">
|
</CardHeader>
|
||||||
<CardTitle>Perbarui Kata Sandi</CardTitle>
|
<CardContent className="space-y-6 pt-6">
|
||||||
</CardHeader>
|
<Field>
|
||||||
<CardContent className="space-y-6 pt-6">
|
<Label htmlFor="current_password" required>Kata Sandi Saat Ini</Label>
|
||||||
<Field>
|
<PasswordInput
|
||||||
<Label htmlFor="current_password" required>Kata Sandi Saat Ini</Label>
|
id="current_password"
|
||||||
<PasswordInput
|
ref={currentPasswordInput}
|
||||||
id="current_password"
|
value={data.current_password}
|
||||||
ref={currentPasswordInput}
|
onChange={(e) => setData('current_password', e.target.value)}
|
||||||
value={data.current_password}
|
className="w-full"
|
||||||
onChange={(e) => setData('current_password', e.target.value)}
|
autoComplete="current-password"
|
||||||
className="w-full"
|
placeholder="Masukkan kata sandi saat ini"
|
||||||
autoComplete="current-password"
|
/>
|
||||||
placeholder="Masukkan kata sandi saat ini"
|
<FieldError error={errors.current_password} label="Kata sandi saat ini" className="text-xs" />
|
||||||
/>
|
</Field>
|
||||||
<FieldError error={errors.current_password} label="Kata sandi saat ini" className="text-xs" />
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<Field>
|
<Field>
|
||||||
<Label htmlFor="password" required>Kata Sandi Baru</Label>
|
<Label htmlFor="password" required>Kata Sandi Baru</Label>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
id="password"
|
id="password"
|
||||||
ref={passwordInput}
|
ref={passwordInput}
|
||||||
value={data.password}
|
value={data.password}
|
||||||
onChange={(e) => setData('password', e.target.value)}
|
onChange={(e) => setData('password', e.target.value)}
|
||||||
placeholder="Masukkan kata sandi baru"
|
placeholder="Masukkan kata sandi baru"
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
/>
|
/>
|
||||||
<FieldError error={errors.password} label="Kata sandi baru" className="text-xs" />
|
<FieldError error={errors.password} label="Kata sandi baru" className="text-xs" />
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field>
|
<Field>
|
||||||
<Label htmlFor="password_confirmation" required>Konfirmasi Kata Sandi Baru</Label>
|
<Label htmlFor="password_confirmation" required>Konfirmasi Kata Sandi Baru</Label>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
id="password_confirmation"
|
id="password_confirmation"
|
||||||
value={data.password_confirmation}
|
value={data.password_confirmation}
|
||||||
onChange={(e) => setData('password_confirmation', e.target.value)}
|
onChange={(e) => setData('password_confirmation', e.target.value)}
|
||||||
placeholder="Ulangi kata sandi baru"
|
placeholder="Ulangi kata sandi baru"
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
/>
|
/>
|
||||||
<FieldError error={errors.password_confirmation} label="Konfirmasi kata sandi baru" className="text-xs" />
|
<FieldError error={errors.password_confirmation} label="Konfirmasi kata sandi baru" className="text-xs" />
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Button type="submit" disabled={processing}>
|
<Button type="submit" disabled={processing}>
|
||||||
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
|
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
|
||||||
{processing ? 'Menyimpan...' : 'Simpan'}
|
{processing ? 'Menyimpan...' : 'Simpan'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{canManageTwoFactor && (
|
|
||||||
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
|
|
||||||
<CardHeader className="border-b">
|
|
||||||
<CardTitle>Autentikasi Dua Faktor (2FA)</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{twoFactorEnabled
|
|
||||||
? "Anda akan diminta memasukkan PIN acak yang aman saat masuk, yang dapat Anda ambil dari aplikasi pendukung TOTP di ponsel Anda."
|
|
||||||
: "Saat Anda mengaktifkan autentikasi dua faktor, Anda akan diminta PIN aman saat masuk. PIN ini dapat diambil dari aplikasi pendukung TOTP di ponsel Anda."
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{twoFactorEnabled ? (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="destructive"
|
|
||||||
onClick={handleDisable2FA}
|
|
||||||
disabled={processingDisable}
|
|
||||||
>
|
|
||||||
{processingDisable ? <Loader className="size-4 animate-spin" /> : null}
|
|
||||||
Nonaktifkan 2FA
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<TwoFactorRecoveryCodes
|
|
||||||
recoveryCodesList={recoveryCodesList}
|
|
||||||
fetchRecoveryCodes={fetchRecoveryCodes}
|
|
||||||
errors={tfaErrors}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
{hasSetupData ? (
|
|
||||||
<Button type="button" onClick={() => setShowSetupModal(true)}>
|
|
||||||
<ShieldCheck className="mr-2 h-4 w-4" />
|
|
||||||
Lanjutkan Penyiapan
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={handleEnable2FA}
|
|
||||||
disabled={processingEnable}
|
|
||||||
>
|
|
||||||
{processingEnable ? <Loader className="size-4 animate-spin" /> : null}
|
|
||||||
Aktifkan 2FA
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{canManageTwoFactor && (
|
|
||||||
<TwoFactorSetupModal
|
|
||||||
isOpen={showSetupModal}
|
|
||||||
onClose={() => setShowSetupModal(false)}
|
|
||||||
requiresConfirmation={requiresConfirmation}
|
|
||||||
twoFactorEnabled={twoFactorEnabled}
|
|
||||||
qrCodeSvg={qrCodeSvg}
|
|
||||||
manualSetupKey={manualSetupKey}
|
|
||||||
clearSetupData={clearSetupData}
|
|
||||||
fetchSetupData={fetchSetupData}
|
|
||||||
errors={tfaErrors}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user