feat: use sessionStorage by default for UUID/tokens, add opt-in remember-me with warning
This commit is contained in:
@@ -110,6 +110,14 @@ class AuthModal extends HTMLElement {
|
|||||||
</div>
|
</div>
|
||||||
` : ''}
|
` : ''}
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="remember-me" ${auth.getRememberMe() ? 'checked' : ''}>
|
||||||
|
<span>${t('auth.rememberMe')}</span>
|
||||||
|
</label>
|
||||||
|
<p class="field-hint">${t('auth.rememberMeHint')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary btn-lg btn-block" ${this.loading ? 'disabled' : ''}>
|
<button type="submit" class="btn btn-primary btn-lg btn-block" ${this.loading ? 'disabled' : ''}>
|
||||||
${this.loading ? t('auth.loggingIn') : t('auth.login')}
|
${this.loading ? t('auth.loggingIn') : t('auth.login')}
|
||||||
</button>
|
</button>
|
||||||
@@ -247,6 +255,9 @@ class AuthModal extends HTMLElement {
|
|||||||
this.error = null
|
this.error = null
|
||||||
this.render()
|
this.render()
|
||||||
|
|
||||||
|
const rememberMe = this.querySelector('#remember-me')?.checked || false
|
||||||
|
auth.setRememberMe(rememberMe)
|
||||||
|
|
||||||
const result = await auth.login(uuid)
|
const result = await auth.login(uuid)
|
||||||
|
|
||||||
this.loading = false
|
this.loading = false
|
||||||
@@ -469,6 +480,26 @@ style.textContent = /* css */`
|
|||||||
auth-modal .link-btn:hover {
|
auth-modal .link-btn:hover {
|
||||||
color: var(--color-accent-hover);
|
color: var(--color-accent-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auth-modal .checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
auth-modal .checkbox-label input {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
accent-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
auth-modal .field-hint {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
margin-top: var(--space-xs);
|
||||||
|
}
|
||||||
`
|
`
|
||||||
document.head.appendChild(style)
|
document.head.appendChild(style)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { directus } from './directus.js'
|
import { directus } from './directus.js'
|
||||||
|
import { setPersist, getPersist } from './directus/client.js'
|
||||||
import { i18n } from '../i18n.js'
|
import { i18n } from '../i18n.js'
|
||||||
|
|
||||||
const AUTH_DOMAIN = 'dgray.io'
|
const AUTH_DOMAIN = 'dgray.io'
|
||||||
@@ -16,6 +17,9 @@ class AuthService {
|
|||||||
this.currentUser = null
|
this.currentUser = null
|
||||||
this.listeners = new Set()
|
this.listeners = new Set()
|
||||||
this.hashCache = new Map()
|
this.hashCache = new Map()
|
||||||
|
if (localStorage.getItem('dgray_remember') === '1') {
|
||||||
|
setPersist(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -136,6 +140,8 @@ class AuthService {
|
|||||||
|
|
||||||
this.currentUser = null
|
this.currentUser = null
|
||||||
this.clearStoredUuid()
|
this.clearStoredUuid()
|
||||||
|
localStorage.removeItem('dgray_remember')
|
||||||
|
setPersist(false)
|
||||||
this.resetPreferencesToDefaults()
|
this.resetPreferencesToDefaults()
|
||||||
this.notifyListeners()
|
this.notifyListeners()
|
||||||
}
|
}
|
||||||
@@ -227,7 +233,8 @@ class AuthService {
|
|||||||
* @param {string} uuid
|
* @param {string} uuid
|
||||||
*/
|
*/
|
||||||
storeUuid(uuid) {
|
storeUuid(uuid) {
|
||||||
localStorage.setItem('dgray_uuid', uuid)
|
const storage = getPersist() ? localStorage : sessionStorage
|
||||||
|
storage.setItem('dgray_uuid', uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -235,16 +242,30 @@ class AuthService {
|
|||||||
* @returns {string|null}
|
* @returns {string|null}
|
||||||
*/
|
*/
|
||||||
getStoredUuid() {
|
getStoredUuid() {
|
||||||
return localStorage.getItem('dgray_uuid')
|
return sessionStorage.getItem('dgray_uuid') || localStorage.getItem('dgray_uuid')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears stored UUID
|
* Clears stored UUID
|
||||||
*/
|
*/
|
||||||
clearStoredUuid() {
|
clearStoredUuid() {
|
||||||
|
sessionStorage.removeItem('dgray_uuid')
|
||||||
localStorage.removeItem('dgray_uuid')
|
localStorage.removeItem('dgray_uuid')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setRememberMe(value) {
|
||||||
|
setPersist(value)
|
||||||
|
if (value) {
|
||||||
|
localStorage.setItem('dgray_remember', '1')
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('dgray_remember')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRememberMe() {
|
||||||
|
return localStorage.getItem('dgray_remember') === '1'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe to auth state changes
|
* Subscribe to auth state changes
|
||||||
* @param {Function} callback
|
* @param {Function} callback
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
const DIRECTUS_URL = 'https://api.dgray.io'
|
const DIRECTUS_URL = 'https://api.dgray.io'
|
||||||
|
|
||||||
|
let _persist = false
|
||||||
|
|
||||||
|
export function setPersist(value) {
|
||||||
|
_persist = value
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPersist() {
|
||||||
|
return _persist
|
||||||
|
}
|
||||||
|
|
||||||
|
function _storage() {
|
||||||
|
return _persist ? localStorage : sessionStorage
|
||||||
|
}
|
||||||
|
|
||||||
class DirectusError extends Error {
|
class DirectusError extends Error {
|
||||||
constructor(status, message, data = {}) {
|
constructor(status, message, data = {}) {
|
||||||
super(message)
|
super(message)
|
||||||
@@ -37,13 +51,16 @@ class DirectusClient {
|
|||||||
// ── Token Management ──
|
// ── Token Management ──
|
||||||
|
|
||||||
loadTokens() {
|
loadTokens() {
|
||||||
const stored = localStorage.getItem('dgray_auth')
|
const stored = sessionStorage.getItem('dgray_auth') || localStorage.getItem('dgray_auth')
|
||||||
if (stored) {
|
if (stored) {
|
||||||
try {
|
try {
|
||||||
const { accessToken, refreshToken, expiry } = JSON.parse(stored)
|
const { accessToken, refreshToken, expiry } = JSON.parse(stored)
|
||||||
this.accessToken = accessToken
|
this.accessToken = accessToken
|
||||||
this.refreshToken = refreshToken
|
this.refreshToken = refreshToken
|
||||||
this.tokenExpiry = expiry
|
this.tokenExpiry = expiry
|
||||||
|
if (localStorage.getItem('dgray_auth')) {
|
||||||
|
_persist = true
|
||||||
|
}
|
||||||
this.scheduleTokenRefresh()
|
this.scheduleTokenRefresh()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.clearTokens()
|
this.clearTokens()
|
||||||
@@ -56,7 +73,7 @@ class DirectusClient {
|
|||||||
this.refreshToken = refreshToken
|
this.refreshToken = refreshToken
|
||||||
this.tokenExpiry = Date.now() + (expiresIn * 1000)
|
this.tokenExpiry = Date.now() + (expiresIn * 1000)
|
||||||
|
|
||||||
localStorage.setItem('dgray_auth', JSON.stringify({
|
_storage().setItem('dgray_auth', JSON.stringify({
|
||||||
accessToken: this.accessToken,
|
accessToken: this.accessToken,
|
||||||
refreshToken: this.refreshToken,
|
refreshToken: this.refreshToken,
|
||||||
expiry: this.tokenExpiry
|
expiry: this.tokenExpiry
|
||||||
@@ -69,6 +86,7 @@ class DirectusClient {
|
|||||||
this.accessToken = null
|
this.accessToken = null
|
||||||
this.refreshToken = null
|
this.refreshToken = null
|
||||||
this.tokenExpiry = null
|
this.tokenExpiry = null
|
||||||
|
sessionStorage.removeItem('dgray_auth')
|
||||||
localStorage.removeItem('dgray_auth')
|
localStorage.removeItem('dgray_auth')
|
||||||
|
|
||||||
if (this.refreshTimeout) {
|
if (this.refreshTimeout) {
|
||||||
|
|||||||
@@ -198,7 +198,9 @@
|
|||||||
"downloadBackup": "Backup herunterladen",
|
"downloadBackup": "Backup herunterladen",
|
||||||
"confirmSaved": "Ich habe meine UUID gespeichert",
|
"confirmSaved": "Ich habe meine UUID gespeichert",
|
||||||
"registrationFailed": "Registrierung fehlgeschlagen",
|
"registrationFailed": "Registrierung fehlgeschlagen",
|
||||||
"loginRequired": "Bitte melde dich an, um fortzufahren"
|
"loginRequired": "Bitte melde dich an, um fortzufahren",
|
||||||
|
"rememberMe": "Auf diesem Gerät merken",
|
||||||
|
"rememberMeHint": "Deine UUID wird lokal gespeichert. Nur aktivieren auf vertrauenswürdigen Geräten."
|
||||||
},
|
},
|
||||||
"favorites": {
|
"favorites": {
|
||||||
"title": "Favoriten",
|
"title": "Favoriten",
|
||||||
|
|||||||
@@ -198,7 +198,9 @@
|
|||||||
"downloadBackup": "Download Backup",
|
"downloadBackup": "Download Backup",
|
||||||
"confirmSaved": "I have saved my UUID",
|
"confirmSaved": "I have saved my UUID",
|
||||||
"registrationFailed": "Registration failed",
|
"registrationFailed": "Registration failed",
|
||||||
"loginRequired": "Please log in to continue"
|
"loginRequired": "Please log in to continue",
|
||||||
|
"rememberMe": "Remember me on this device",
|
||||||
|
"rememberMeHint": "Your UUID will be stored locally. Only enable on trusted devices."
|
||||||
},
|
},
|
||||||
"favorites": {
|
"favorites": {
|
||||||
"title": "Favorites",
|
"title": "Favorites",
|
||||||
|
|||||||
@@ -198,7 +198,9 @@
|
|||||||
"downloadBackup": "Descargar copia de seguridad",
|
"downloadBackup": "Descargar copia de seguridad",
|
||||||
"confirmSaved": "He guardado mi UUID",
|
"confirmSaved": "He guardado mi UUID",
|
||||||
"registrationFailed": "Error en el registro",
|
"registrationFailed": "Error en el registro",
|
||||||
"loginRequired": "Inicia sesión para continuar"
|
"loginRequired": "Inicia sesión para continuar",
|
||||||
|
"rememberMe": "Recordarme en este dispositivo",
|
||||||
|
"rememberMeHint": "Tu UUID se guardará localmente. Actívalo solo en dispositivos de confianza."
|
||||||
},
|
},
|
||||||
"favorites": {
|
"favorites": {
|
||||||
"title": "Favoritos",
|
"title": "Favoritos",
|
||||||
|
|||||||
@@ -198,7 +198,9 @@
|
|||||||
"downloadBackup": "Télécharger la sauvegarde",
|
"downloadBackup": "Télécharger la sauvegarde",
|
||||||
"confirmSaved": "J'ai sauvegardé mon UUID",
|
"confirmSaved": "J'ai sauvegardé mon UUID",
|
||||||
"registrationFailed": "Échec de l'inscription",
|
"registrationFailed": "Échec de l'inscription",
|
||||||
"loginRequired": "Veuillez vous connecter pour continuer"
|
"loginRequired": "Veuillez vous connecter pour continuer",
|
||||||
|
"rememberMe": "Se souvenir de moi",
|
||||||
|
"rememberMeHint": "Votre UUID sera stocké localement. N'activez que sur des appareils de confiance."
|
||||||
},
|
},
|
||||||
"favorites": {
|
"favorites": {
|
||||||
"title": "Favoris",
|
"title": "Favoris",
|
||||||
|
|||||||
@@ -198,7 +198,9 @@
|
|||||||
"downloadBackup": "Scarica backup",
|
"downloadBackup": "Scarica backup",
|
||||||
"confirmSaved": "Ho salvato il mio UUID",
|
"confirmSaved": "Ho salvato il mio UUID",
|
||||||
"registrationFailed": "Registrazione fallita",
|
"registrationFailed": "Registrazione fallita",
|
||||||
"loginRequired": "Accedi per continuare"
|
"loginRequired": "Accedi per continuare",
|
||||||
|
"rememberMe": "Ricordami su questo dispositivo",
|
||||||
|
"rememberMeHint": "Il tuo UUID verrà salvato localmente. Attiva solo su dispositivi affidabili."
|
||||||
},
|
},
|
||||||
"favorites": {
|
"favorites": {
|
||||||
"title": "Preferiti",
|
"title": "Preferiti",
|
||||||
|
|||||||
@@ -198,7 +198,9 @@
|
|||||||
"downloadBackup": "Baixar Backup",
|
"downloadBackup": "Baixar Backup",
|
||||||
"confirmSaved": "Eu salvei meu UUID",
|
"confirmSaved": "Eu salvei meu UUID",
|
||||||
"registrationFailed": "Falha no cadastro",
|
"registrationFailed": "Falha no cadastro",
|
||||||
"loginRequired": "Por favor, faça login para continuar"
|
"loginRequired": "Por favor, faça login para continuar",
|
||||||
|
"rememberMe": "Lembrar neste dispositivo",
|
||||||
|
"rememberMeHint": "Seu UUID será armazenado localmente. Ative apenas em dispositivos confiáveis."
|
||||||
},
|
},
|
||||||
"favorites": {
|
"favorites": {
|
||||||
"title": "Favoritos",
|
"title": "Favoritos",
|
||||||
|
|||||||
@@ -198,7 +198,9 @@
|
|||||||
"downloadBackup": "Скачать резервную копию",
|
"downloadBackup": "Скачать резервную копию",
|
||||||
"confirmSaved": "Я сохранил свой UUID",
|
"confirmSaved": "Я сохранил свой UUID",
|
||||||
"registrationFailed": "Регистрация не удалась",
|
"registrationFailed": "Регистрация не удалась",
|
||||||
"loginRequired": "Войдите, чтобы продолжить"
|
"loginRequired": "Войдите, чтобы продолжить",
|
||||||
|
"rememberMe": "Запомнить на этом устройстве",
|
||||||
|
"rememberMeHint": "Ваш UUID будет сохранён локально. Включайте только на доверенных устройствах."
|
||||||
},
|
},
|
||||||
"favorites": {
|
"favorites": {
|
||||||
"title": "Избранное",
|
"title": "Избранное",
|
||||||
|
|||||||
Reference in New Issue
Block a user