first commit

This commit is contained in:
2026-02-04 23:23:42 +07:00
commit ee28ea1a2d
202 changed files with 34797 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
import { useCallback, useMemo, useState } from 'react';
import { qrCode, recoveryCodes, secretKey } from '@/routes/two-factor';
import type { TwoFactorSecretKey, TwoFactorSetupData } from '@/types';
export type UseTwoFactorAuthReturn = {
qrCodeSvg: string | null;
manualSetupKey: string | null;
recoveryCodesList: string[];
hasSetupData: boolean;
errors: string[];
clearErrors: () => void;
clearSetupData: () => void;
fetchQrCode: () => Promise<void>;
fetchSetupKey: () => Promise<void>;
fetchSetupData: () => Promise<void>;
fetchRecoveryCodes: () => Promise<void>;
};
export const OTP_MAX_LENGTH = 6;
const fetchJson = async <T>(url: string): Promise<T> => {
const response = await fetch(url, {
headers: { Accept: 'application/json' },
});
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.status}`);
}
return response.json();
};
export const useTwoFactorAuth = (): UseTwoFactorAuthReturn => {
const [qrCodeSvg, setQrCodeSvg] = useState<string | null>(null);
const [manualSetupKey, setManualSetupKey] = useState<string | null>(null);
const [recoveryCodesList, setRecoveryCodesList] = useState<string[]>([]);
const [errors, setErrors] = useState<string[]>([]);
const hasSetupData = useMemo<boolean>(
() => qrCodeSvg !== null && manualSetupKey !== null,
[qrCodeSvg, manualSetupKey],
);
const fetchQrCode = useCallback(async (): Promise<void> => {
try {
const { svg } = await fetchJson<TwoFactorSetupData>(qrCode.url());
setQrCodeSvg(svg);
} catch {
setErrors((prev) => [...prev, 'Failed to fetch QR code']);
setQrCodeSvg(null);
}
}, []);
const fetchSetupKey = useCallback(async (): Promise<void> => {
try {
const { secretKey: key } = await fetchJson<TwoFactorSecretKey>(
secretKey.url(),
);
setManualSetupKey(key);
} catch {
setErrors((prev) => [...prev, 'Failed to fetch a setup key']);
setManualSetupKey(null);
}
}, []);
const clearErrors = useCallback((): void => {
setErrors([]);
}, []);
const clearSetupData = useCallback((): void => {
setManualSetupKey(null);
setQrCodeSvg(null);
clearErrors();
}, [clearErrors]);
const fetchRecoveryCodes = useCallback(async (): Promise<void> => {
try {
clearErrors();
const codes = await fetchJson<string[]>(recoveryCodes.url());
setRecoveryCodesList(codes);
} catch {
setErrors((prev) => [...prev, 'Failed to fetch recovery codes']);
setRecoveryCodesList([]);
}
}, [clearErrors]);
const fetchSetupData = useCallback(async (): Promise<void> => {
try {
clearErrors();
await Promise.all([fetchQrCode(), fetchSetupKey()]);
} catch {
setQrCodeSvg(null);
setManualSetupKey(null);
}
}, [clearErrors, fetchQrCode, fetchSetupKey]);
return {
qrCodeSvg,
manualSetupKey,
recoveryCodesList,
hasSetupData,
errors,
clearErrors,
clearSetupData,
fetchQrCode,
fetchSetupKey,
fetchSetupData,
fetchRecoveryCodes,
};
};