mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 23:16:21 +00:00
🆕 Token login added
Backend (AuthHttpHandler): - New users_remember_tokens table stores sha256 hex of the raw token so the DB never holds a usable credential. Seed file adds the table and a login.remember.duration.days setting (default 30). - /api/auth/login accepts "remember": true. On success, issues a fresh 32-byte base64url token, stores the hash, returns the raw token. - New POST /api/auth/remember: accepts the raw token, looks up by hash, on a valid hit mints a fresh SSO ticket, rotates the token (deletes the consumed one and issues a new one), returns both to the client. No Turnstile - it's an automated trusted-device flow. - /api/auth/logout also accepts rememberToken and deletes that single row so other devices keep their tokens. Frontend: - LoginView: "Remember me" checkbox (key login.remember_me already in ExternalTexts). Enabling it persists the returned rememberToken in localStorage.nitro.remember.token. - App.tsx: before deciding to show the login screen, try a silent POST to /api/auth/remember with the stored token. On 200, inject the returned ssoTicket into window.NitroConfig and proceed to the authenticated flow; on 401, forget the token and show login. - PurseView logout: sends the stored rememberToken in the body so the server can delete it, and clears localStorage before reload.
This commit is contained in:
+55
-5
@@ -56,22 +56,72 @@ export const App: FC<{}> = props =>
|
||||
{
|
||||
if(!window.NitroConfig) throw new Error('NitroConfig is not defined!');
|
||||
|
||||
const ssoTicket = window.NitroConfig['sso.ticket'];
|
||||
let ssoTicket = window.NitroConfig['sso.ticket'];
|
||||
let configInitError: unknown = null;
|
||||
|
||||
if(!ssoTicket || ssoTicket === '')
|
||||
{
|
||||
let configInitError: unknown = null;
|
||||
try { await GetConfiguration().init(); }
|
||||
catch(e) { configInitError = e; }
|
||||
|
||||
const rawLoginEnabled = GetConfiguration().getValue<unknown>('login.screen.enabled', false);
|
||||
const loginScreenEnabled = rawLoginEnabled === true || rawLoginEnabled === 'true' || rawLoginEnabled === 1;
|
||||
|
||||
if(configInitError)
|
||||
{
|
||||
NitroLogger.error('[LoginScreen] Failed to load renderer-config.json — cannot resolve login.screen.enabled', configInitError);
|
||||
}
|
||||
|
||||
if(!configInitError)
|
||||
{
|
||||
let storedRemember: string | null = null;
|
||||
try { storedRemember = window.localStorage.getItem('nitro.remember.token'); }
|
||||
catch {}
|
||||
|
||||
if(storedRemember)
|
||||
{
|
||||
const rememberUrlTemplate = GetConfiguration().getValue<string>('login.remember.endpoint', '/api/auth/remember');
|
||||
const rememberUrl = GetConfiguration().interpolate(rememberUrlTemplate);
|
||||
try
|
||||
{
|
||||
const response = await fetch(rememberUrl, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'NitroRememberMe'
|
||||
},
|
||||
body: JSON.stringify({ rememberToken: storedRemember })
|
||||
});
|
||||
if(response.ok)
|
||||
{
|
||||
const payload = await response.json();
|
||||
const ticket = typeof payload.ssoTicket === 'string' ? payload.ssoTicket : '';
|
||||
if(ticket)
|
||||
{
|
||||
window.NitroConfig['sso.ticket'] = ticket;
|
||||
ssoTicket = ticket;
|
||||
try
|
||||
{
|
||||
if(typeof payload.rememberToken === 'string' && payload.rememberToken.length)
|
||||
window.localStorage.setItem('nitro.remember.token', payload.rememberToken);
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
}
|
||||
else if(response.status === 401)
|
||||
{
|
||||
try { window.localStorage.removeItem('nitro.remember.token'); } catch {}
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!ssoTicket || ssoTicket === '')
|
||||
{
|
||||
const rawLoginEnabled = GetConfiguration().getValue<unknown>('login.screen.enabled', false);
|
||||
const loginScreenEnabled = rawLoginEnabled === true || rawLoginEnabled === 'true' || rawLoginEnabled === 1;
|
||||
|
||||
if(loginScreenEnabled)
|
||||
{
|
||||
try { await GetLocalizationManager().init(); }
|
||||
|
||||
Reference in New Issue
Block a user