mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
🆙 Small fix Login
This commit is contained in:
+10
-10
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"socket.url": "ws://192.168.1.52:2096",
|
||||
"asset.url": "https://client.slogga.it/nitro/bundled",
|
||||
"image.library.url": "https://client.slogga.it/c_images/",
|
||||
"hof.furni.url": "https://client.slogga.it/c_images/dcr/hof_furni",
|
||||
"images.url": "https://client.slogga.it/nitro/images",
|
||||
"gamedata.url": "https://client.slogga.it/nitro/gamedata",
|
||||
"socket.url": "ws://localhost:2096",
|
||||
"asset.url": "https://localhost/nitro/bundled",
|
||||
"image.library.url": "https://localhost/c_images/",
|
||||
"hof.furni.url": "https://localhost/c_images/dcr/hof_furni",
|
||||
"images.url": "https://localhost/nitro/images",
|
||||
"gamedata.url": "https://localhost/nitro/gamedata",
|
||||
"sounds.url": "${asset.url}/sounds/%sample%.mp3",
|
||||
"external.texts.url": [
|
||||
"${gamedata.url}/ExternalTexts.json",
|
||||
@@ -40,10 +40,10 @@
|
||||
"room.landscapes.enabled": true,
|
||||
"room.zoom.enabled": true,
|
||||
"login.screen.enabled": false,
|
||||
"login.endpoint": "https://websocket.yourdomain.com/api/auth/login",
|
||||
"login.register.endpoint": "https://websocket.yourdomain.com/api/auth/register",
|
||||
"login.forgot.endpoint": "https://websocket.yourdomain.com/api/auth/forgot-password",
|
||||
"login.logout.endpoint": "https://websocket.yourdomain.com/api/auth/logout",
|
||||
"login.endpoint": "${socket.url}/api/auth/login",
|
||||
"login.register.endpoint": "${socket.url}/api/auth/register",
|
||||
"login.forgot.endpoint": "${socket.url}/api/auth/forgot-password",
|
||||
"login.logout.endpoint": "${socket.url}/api/auth/logout",
|
||||
"login.turnstile.enabled": false,
|
||||
"login.turnstile.sitekey": "",
|
||||
"avatar.mandatory.libraries": [
|
||||
|
||||
@@ -14,8 +14,8 @@ const interpolate = (value: string | null | undefined): string =>
|
||||
|
||||
const LOCK_KEY = 'nitro.login.lock';
|
||||
const MAX_ATTEMPTS = 5;
|
||||
const LOCK_WINDOW_MS = 60_000;
|
||||
const LOCK_DURATION_MS = 2 * 60_000;
|
||||
const LOCK_WINDOW_MS = 60_000; // rolling 60s window
|
||||
const LOCK_DURATION_MS = 2 * 60_000; // 2 minute lockout
|
||||
|
||||
type AttemptState = { attempts: number; firstAt: number; lockedUntil: number };
|
||||
|
||||
@@ -33,7 +33,7 @@ const readLock = (): AttemptState =>
|
||||
const writeLock = (state: AttemptState) =>
|
||||
{
|
||||
try { sessionStorage.setItem(LOCK_KEY, JSON.stringify(state)); }
|
||||
catch { }
|
||||
catch { /* ignore */ }
|
||||
};
|
||||
|
||||
export interface LoginViewProps
|
||||
@@ -63,6 +63,21 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated }) =>
|
||||
const rightRepeat = interpolate(loginImages['right.repeat'] || GetConfigurationValue<string>('login_right.repeat', ''));
|
||||
const right = interpolate(loginImages['right'] || GetConfigurationValue<string>('login_right', ''));
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('[LoginView] resolved background assets', {
|
||||
'asset.url': GetConfigurationValue<string>('asset.url', ''),
|
||||
login_background: background,
|
||||
'login_background.colour': backgroundColor,
|
||||
login_sun: sun,
|
||||
login_drape: drape,
|
||||
login_left: left,
|
||||
login_right: right,
|
||||
'login_right.repeat': rightRepeat
|
||||
});
|
||||
}, [ background, backgroundColor, sun, drape, left, right, rightRepeat ]);
|
||||
|
||||
const loginUrl = GetConfigurationValue<string>('login.endpoint', '/api/auth/login');
|
||||
const registerUrl = GetConfigurationValue<string>('login.register.endpoint', '/api/auth/register');
|
||||
const forgotUrl = GetConfigurationValue<string>('login.forgot.endpoint', '/api/auth/forgot-password');
|
||||
@@ -73,19 +88,40 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated }) =>
|
||||
|| rawTurnstileEnabled === 1
|
||||
|| rawTurnstileEnabled === '1') && !!turnstileSiteKey;
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('[LoginView] turnstile config', {
|
||||
rawTurnstileEnabled,
|
||||
turnstileEnabled,
|
||||
turnstileSiteKey: turnstileSiteKey ? (turnstileSiteKey.slice(0, 6) + '…') : '(empty)'
|
||||
});
|
||||
}, [ rawTurnstileEnabled, turnstileEnabled, turnstileSiteKey ]);
|
||||
|
||||
const resetLoginTurnstile = useCallback(() =>
|
||||
{
|
||||
setLoginTurnstileToken('');
|
||||
setLoginTurnstileResetSignal(prev => prev + 1);
|
||||
}, []);
|
||||
|
||||
// Clear error on mode change but keep the success notification so users
|
||||
// returning to the login form can read it (e.g. "Account created").
|
||||
// Reset the login captcha only when we're actually on the login form.
|
||||
useEffect(() =>
|
||||
{
|
||||
setError(null);
|
||||
setInfo(null);
|
||||
if(mode === 'login') resetLoginTurnstile();
|
||||
}, [ mode, resetLoginTurnstile ]);
|
||||
|
||||
// Auto-dismiss the info notification after a few seconds so it doesn't
|
||||
// hang around forever once the user has seen it.
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!info) return;
|
||||
const timeout = window.setTimeout(() => setInfo(null), 8000);
|
||||
return () => window.clearTimeout(timeout);
|
||||
}, [ info ]);
|
||||
|
||||
const lockState = useMemo(() => readLock(), [ submitting ]);
|
||||
const now = Date.now();
|
||||
const isLocked = lockState.lockedUntil > now;
|
||||
@@ -126,7 +162,7 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated }) =>
|
||||
|
||||
let payload: Record<string, unknown> = {};
|
||||
try { payload = await response.json(); }
|
||||
catch { }
|
||||
catch { /* ignore non-json responses */ }
|
||||
|
||||
return { ok: response.ok, status: response.status, payload };
|
||||
}, []);
|
||||
@@ -198,6 +234,11 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated }) =>
|
||||
}
|
||||
}, [ submitting, username, password, turnstileEnabled, loginTurnstileToken, loginUrl, postJson, clearLock, recordFailure, onAuthenticated, resetLoginTurnstile ]);
|
||||
|
||||
// Register + forgot-password submit handlers receive the Turnstile token
|
||||
// from the dialog (the dialog owns its own widget lifecycle), so the
|
||||
// login widget underneath can't reset or overwrite it while the user
|
||||
// is working on the modal.
|
||||
|
||||
const handleRegisterSubmit = useCallback(async (body: { username: string; email: string; password: string; turnstileToken: string; }, onDialogReset: () => void) =>
|
||||
{
|
||||
if(turnstileEnabled && !body.turnstileToken)
|
||||
@@ -221,7 +262,8 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated }) =>
|
||||
|
||||
if(ok)
|
||||
{
|
||||
setInfo(typeof payload.message === 'string' ? payload.message : 'Account created. You can now log in.');
|
||||
const friendly = `Welcome aboard, ${ body.username }! Your account is ready — log in below with the password you just chose.`;
|
||||
setInfo(typeof payload.message === 'string' ? payload.message : friendly);
|
||||
setMode('login');
|
||||
setUsername(body.username);
|
||||
setPassword('');
|
||||
@@ -263,7 +305,8 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated }) =>
|
||||
|
||||
if(ok)
|
||||
{
|
||||
setInfo(typeof payload.message === 'string' ? payload.message : 'If an account exists we just sent a reset link to your email.');
|
||||
const friendly = 'Email sent! If an account matches that address you\'ll find a reset link in your inbox shortly (check spam if it doesn\'t show up within a minute).';
|
||||
setInfo(typeof payload.message === 'string' ? payload.message : friendly);
|
||||
setMode('login');
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user