feat(auth): capture remember-token from URL and persist for reconnect

The CMS Inertia /client page now passes `&token=<uuid>&token_exp=<unix>`
on the iframe src so Nitro can persist the token to localStorage on first
boot. `App.tsx::prepare()` reads them from `window.location.search` and
calls `SetRememberLogin({ token, expiresAt })` when no remember-login is
already stored.

This wires up the existing reconnect flow: when the WS drops, the loop
in `tryRememberLogin()` (already in this file) POSTs the saved token to
`login.remember.endpoint` (defaults to `${api.url}/api/auth/remember`)
and uses the returned fresh SSO ticket to reconnect. Without this step
the localStorage stayed empty and the reconnect always fell through to
"Session expired" after a few retries because Arcturus clears
`auth_ticket` on first consume.

Server side: the CMS counterpart is in medievalshell/InertiaCMS
commit on djoohotel — adds the /api/auth/remember endpoint backed by
`users_remember_families` (UUID family + 30-day expiry + revoked flag).
This commit is contained in:
medievalshell
2026-05-21 01:01:06 +02:00
parent d762f00c44
commit 9e38de6160
+26 -1
View File
@@ -1,6 +1,6 @@
import { GetAssetManager, GetAvatarRenderManager, GetCommunication, GetConfiguration, GetLocalizationManager, GetRoomEngine, GetRoomSessionManager, GetSessionDataManager, GetSoundManager, GetStage, GetTexturePool, GetTicker, HabboWebTools, LegacyExternalInterface, LoadGameUrlEvent, NitroEventType, NitroLogger, NitroVersion, PrepareRenderer } from '@nitrots/nitro-renderer'; import { GetAssetManager, GetAvatarRenderManager, GetCommunication, GetConfiguration, GetLocalizationManager, GetRoomEngine, GetRoomSessionManager, GetSessionDataManager, GetSoundManager, GetStage, GetTexturePool, GetTicker, HabboWebTools, LegacyExternalInterface, LoadGameUrlEvent, NitroEventType, NitroLogger, NitroVersion, PrepareRenderer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useEffectEvent, useRef, useState } from 'react'; import { FC, useCallback, useEffect, useEffectEvent, useRef, useState } from 'react';
import { ClearRememberLogin, GetRememberLogin, GetUIVersion, StoreRememberLoginFromPayload, persistAccessTokenFromPayload } from './api'; import { ClearRememberLogin, GetRememberLogin, GetUIVersion, SetRememberLogin, StoreRememberLoginFromPayload, persistAccessTokenFromPayload } from './api';
import { Base } from './common'; import { Base } from './common';
import { LoadingView } from './components/loading/LoadingView'; import { LoadingView } from './components/loading/LoadingView';
import { LoginView } from './components/login/LoginView'; import { LoginView } from './components/login/LoginView';
@@ -495,6 +495,31 @@ export const App: FC<{}> = props =>
let ssoTicket = window.NitroConfig['sso.ticket']; let ssoTicket = window.NitroConfig['sso.ticket'];
if(ssoTicket) GetConfiguration().setValue('sso.ticket', ssoTicket); if(ssoTicket) GetConfiguration().setValue('sso.ticket', ssoTicket);
// Cattura il remember-token passato via URL (?token=&token_exp=)
// dal CMS Inertia /client e salvalo in localStorage. Serve a
// tryRememberLogin() in reconnect: chiama POST /api/auth/remember
// col token UUID, riceve un nuovo SSO ticket fresco invece di
// riusare quello cleared da Arcturus dopo il primo consume.
try
{
const urlParams = new URLSearchParams(window.location.search);
const tokenParam = urlParams.get('token');
const tokenExpParam = urlParams.get('token_exp');
if(tokenParam && !GetRememberLogin())
{
const parsedExpiry = Number(tokenExpParam || 0);
const expiresAt = (Number.isFinite(parsedExpiry) && parsedExpiry > 0)
? parsedExpiry
: Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60);
SetRememberLogin({ token: tokenParam, expiresAt });
}
}
catch(e)
{
console.warn('[App] failed to persist remember token from URL', e);
}
bumpProgress(10, taskLabel('loading.task.session', 'Verifica sessione')); bumpProgress(10, taskLabel('loading.task.session', 'Verifica sessione'));
if(!ssoTicket || ssoTicket === '') if(!ssoTicket || ssoTicket === '')