fix and update
This commit is contained in:
@@ -41,6 +41,8 @@ import { useToast } from './toast-provider';
|
||||
import { PinLockModal } from './pin-lock-modal';
|
||||
import { AppBootstrapScreen } from './app-bootstrap-screen';
|
||||
import { EMBEDDED_AUTH_MESSAGE, type EmbeddedAuthPayload } from '@/lib/embedded-auth-bridge';
|
||||
import { usePinIdleLock } from '@/hooks/use-pin-idle-lock';
|
||||
import { clearPinIdleWatch, isPinIdleWatchActive, markPinIdleWatch } from '@/lib/pin-idle-storage';
|
||||
|
||||
interface AuthContextValue {
|
||||
user: PublicUser | null;
|
||||
@@ -126,7 +128,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { showToast } = useToast();
|
||||
const { publicApiUrl } = usePublicSettings();
|
||||
const { publicApiUrl, pinLockTimeoutMinutes } = usePublicSettings();
|
||||
const initialAuth = React.useMemo(() => readInitialAuthState(), []);
|
||||
const [token, setToken] = React.useState<string | null>(initialAuth.token);
|
||||
const [user, setUser] = React.useState<PublicUser | null>(null);
|
||||
@@ -188,6 +190,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const activatePinLock = React.useCallback((sessionId: string) => {
|
||||
const resolvedSessionId = sessionId || window.localStorage.getItem(AUTH_SESSION_KEY);
|
||||
if (!resolvedSessionId) return;
|
||||
markPinIdleWatch(user?.id);
|
||||
setLockedSessionId(resolvedSessionId);
|
||||
setIsPinLocked(true);
|
||||
setPinError(null);
|
||||
@@ -196,7 +199,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
if (cachedUser) {
|
||||
setUser(cachedUser);
|
||||
}
|
||||
}, []);
|
||||
}, [user?.id]);
|
||||
|
||||
const warmUpAndApplySession = React.useCallback(
|
||||
async (accessToken: string) => {
|
||||
@@ -275,6 +278,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
window.localStorage.removeItem(AUTH_REFRESH_KEY);
|
||||
window.localStorage.removeItem(AUTH_SESSION_KEY);
|
||||
window.localStorage.removeItem(AUTH_USER_CACHE_KEY);
|
||||
clearPinIdleWatch();
|
||||
initialBootstrapDoneRef.current = false;
|
||||
setToken(null);
|
||||
setUser(null);
|
||||
@@ -456,22 +460,19 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
void refreshProfile();
|
||||
}, [refreshProfile]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isSessionReady || isPinLocked) return;
|
||||
if (pathname.startsWith('/auth/')) return;
|
||||
const accessToken = token ?? window.localStorage.getItem(AUTH_TOKEN_KEY);
|
||||
if (!accessToken) return;
|
||||
const pinIdleLockEnabled =
|
||||
isSessionReady &&
|
||||
!isPinLocked &&
|
||||
!pathname.startsWith('/auth/') &&
|
||||
Boolean(token ?? (typeof window !== 'undefined' ? window.localStorage.getItem(AUTH_TOKEN_KEY) : null)) &&
|
||||
isPinIdleWatchActive(user?.id);
|
||||
|
||||
void fetchAuthSession(accessToken)
|
||||
.then((session) => {
|
||||
applySessionState(session, { setUser, setToken, activatePinLock, clearPinLock });
|
||||
})
|
||||
.catch((error) => {
|
||||
if (isPinRequiredError(error)) {
|
||||
activatePinLock(error.sessionId ?? window.localStorage.getItem(AUTH_SESSION_KEY) ?? '');
|
||||
}
|
||||
});
|
||||
}, [activatePinLock, clearPinLock, isPinLocked, isSessionReady, pathname, token]);
|
||||
usePinIdleLock({
|
||||
enabled: pinIdleLockEnabled,
|
||||
timeoutMinutes: pinLockTimeoutMinutes,
|
||||
accessToken: token,
|
||||
onLockRequired: activatePinLock,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
function handleVisibilityChange() {
|
||||
@@ -515,6 +516,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
await establishSession(auth);
|
||||
} else {
|
||||
persistPartialAuth(auth);
|
||||
markPinIdleWatch(auth.user.id);
|
||||
setHasStoredSession(true);
|
||||
}
|
||||
return auth;
|
||||
@@ -550,6 +552,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
await establishSession(response.auth);
|
||||
} else if (response.auth) {
|
||||
persistPartialAuth(response.auth);
|
||||
markPinIdleWatch(response.auth.user.id);
|
||||
setHasStoredSession(true);
|
||||
}
|
||||
return response;
|
||||
@@ -571,6 +574,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
await establishSession(auth);
|
||||
} else {
|
||||
persistPartialAuth(auth);
|
||||
markPinIdleWatch(auth.user.id);
|
||||
setHasStoredSession(true);
|
||||
}
|
||||
return auth;
|
||||
@@ -592,6 +596,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
await establishSession(auth);
|
||||
} else {
|
||||
persistPartialAuth(auth);
|
||||
markPinIdleWatch(auth.user.id);
|
||||
setHasStoredSession(true);
|
||||
}
|
||||
return auth;
|
||||
@@ -620,6 +625,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
await establishSession(auth);
|
||||
} else {
|
||||
persistPartialAuth(auth);
|
||||
markPinIdleWatch(auth.user.id);
|
||||
setHasStoredSession(true);
|
||||
}
|
||||
return auth;
|
||||
@@ -650,6 +656,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
window.localStorage.setItem(AUTH_TOKEN_KEY, response.accessToken);
|
||||
window.localStorage.setItem(AUTH_SESSION_KEY, sessionId);
|
||||
setToken(response.accessToken);
|
||||
markPinIdleWatch(user?.id);
|
||||
|
||||
try {
|
||||
await warmUpAndApplySession(response.accessToken);
|
||||
@@ -673,7 +680,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
router.replace('/');
|
||||
}
|
||||
},
|
||||
[clearPinLock, pathname, router, warmUpAndApplySession]
|
||||
[clearPinLock, pathname, router, user?.id, warmUpAndApplySession]
|
||||
);
|
||||
|
||||
const handlePinUnlock = React.useCallback(
|
||||
|
||||
@@ -4,7 +4,8 @@ import * as React from 'react';
|
||||
import { KeyRound } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { PinInput } from '@/components/ui/pin-input';
|
||||
import { isPinInputComplete } from '@/lib/pin-input';
|
||||
|
||||
interface PinLockModalProps {
|
||||
open: boolean;
|
||||
@@ -39,20 +40,18 @@ export function PinLockModal({ open, isSubmitting, error, onSubmit }: PinLockMod
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<Input
|
||||
<PinInput
|
||||
autoFocus
|
||||
className="h-[58px] text-center text-lg tracking-[0.4em]"
|
||||
placeholder="PIN"
|
||||
value={pin}
|
||||
onChange={(event) => setPin(event.target.value)}
|
||||
onChange={setPin}
|
||||
required
|
||||
minLength={4}
|
||||
maxLength={6}
|
||||
inputMode="numeric"
|
||||
autoComplete="one-time-code"
|
||||
/>
|
||||
{error ? <p className="text-center text-sm text-red-600">{error}</p> : null}
|
||||
<Button type="submit" size="lg" className="w-full rounded-[18px]" disabled={isSubmitting || pin.length < 4}>
|
||||
<Button type="submit" size="lg" className="w-full rounded-[18px]" disabled={isSubmitting || !isPinInputComplete(pin)}>
|
||||
{isSubmitting ? 'Проверяем...' : 'Разблокировать'}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
@@ -11,6 +11,7 @@ interface PublicSettingsContextValue {
|
||||
publicFrontendUrl: string;
|
||||
ldapEnabled: boolean;
|
||||
ldapUseLdaps: boolean;
|
||||
pinLockTimeoutMinutes: number;
|
||||
isLoading: boolean;
|
||||
refreshPublicSettings: () => Promise<void>;
|
||||
}
|
||||
@@ -22,6 +23,7 @@ const PublicSettingsContext = createContext<PublicSettingsContextValue>({
|
||||
publicFrontendUrl: DEFAULT_PUBLIC_FRONTEND_URL,
|
||||
ldapEnabled: false,
|
||||
ldapUseLdaps: false,
|
||||
pinLockTimeoutMinutes: 15,
|
||||
isLoading: true,
|
||||
refreshPublicSettings: async () => undefined
|
||||
});
|
||||
@@ -65,6 +67,7 @@ export function PublicSettingsProvider({ children }: { children: React.ReactNode
|
||||
publicFrontendUrl: publicFrontendUrl.replace(/\/+$/, ''),
|
||||
ldapEnabled: ['true', '1', 'yes'].includes((settings.LDAP_ENABLED ?? '').trim().toLowerCase()),
|
||||
ldapUseLdaps: ['true', '1', 'yes'].includes((settings.LDAP_USE_LDAPS ?? '').trim().toLowerCase()),
|
||||
pinLockTimeoutMinutes: Math.max(1, Number.parseInt(settings.PIN_LOCK_TIMEOUT_MINUTES ?? '15', 10) || 15),
|
||||
isLoading,
|
||||
refreshPublicSettings
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user