fix(auth): persist ToS acceptance in Rust/SQLite backend
ToS acceptance was stored in localStorage which gets cleared on app updates, causing users to re-login frequently. Now stores ToS state in SQLite (~/.local/share/qbz/legal_settings.db) which survives updates. - Add legal_settings.rs with SQLite storage for ToS acceptance - Update qobuzLegalStore.ts to use Rust invoke commands - LoginView now loads ToS from Rust before auto-login check - Migration from localStorage for existing users Closes #31
This commit is contained in:
1 parent
fe7816f6c6
commit
3926209b0d
5 files changed
+212
-8
No files matched your search
@@ -0,0 +1,117 @@
|
||||
//! Legal settings storage
|
||||
//!
|
||||
//! Persists user acceptance of legal terms:
|
||||
//! - qobuz_tos_accepted: User has accepted Qobuz Terms of Service
|
||||
|
||||
use rusqlite::{Connection, params};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use log::info;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LegalSettings {
|
||||
/// User has accepted Qobuz Terms of Service
|
||||
pub qobuz_tos_accepted: bool,
|
||||
}
|
||||
|
||||
impl Default for LegalSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
qobuz_tos_accepted: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LegalSettingsStore {
|
||||
conn: Connection,
|
||||
}
|
||||
|
||||
impl LegalSettingsStore {
|
||||
pub fn new() -> Result<Self, String> {
|
||||
let data_dir = dirs::data_dir()
|
||||
.ok_or("Could not determine data directory")?
|
||||
.join("qbz");
|
||||
|
||||
std::fs::create_dir_all(&data_dir)
|
||||
.map_err(|e| format!("Failed to create data directory: {}", e))?;
|
||||
|
||||
let db_path = data_dir.join("legal_settings.db");
|
||||
let conn = Connection::open(&db_path)
|
||||
.map_err(|e| format!("Failed to open legal settings database: {}", e))?;
|
||||
|
||||
// Create table
|
||||
conn.execute_batch(
|
||||
"CREATE TABLE IF NOT EXISTS legal_settings (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
qobuz_tos_accepted INTEGER NOT NULL DEFAULT 0
|
||||
);"
|
||||
).map_err(|e| format!("Failed to create legal settings table: {}", e))?;
|
||||
|
||||
// Insert default row if it doesn't exist
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO legal_settings (id, qobuz_tos_accepted) VALUES (1, 0)",
|
||||
[]
|
||||
).map_err(|e| format!("Failed to insert default legal settings: {}", e))?;
|
||||
|
||||
info!("[LegalSettings] Database initialized");
|
||||
|
||||
Ok(Self { conn })
|
||||
}
|
||||
|
||||
pub fn get_settings(&self) -> Result<LegalSettings, String> {
|
||||
self.conn
|
||||
.query_row(
|
||||
"SELECT qobuz_tos_accepted FROM legal_settings WHERE id = 1",
|
||||
[],
|
||||
|row| {
|
||||
let qobuz_tos_accepted: i32 = row.get(0)?;
|
||||
Ok(LegalSettings {
|
||||
qobuz_tos_accepted: qobuz_tos_accepted != 0,
|
||||
})
|
||||
},
|
||||
)
|
||||
.map_err(|e| format!("Failed to get legal settings: {}", e))
|
||||
}
|
||||
|
||||
pub fn set_qobuz_tos_accepted(&self, accepted: bool) -> Result<(), String> {
|
||||
self.conn
|
||||
.execute(
|
||||
"UPDATE legal_settings SET qobuz_tos_accepted = ? WHERE id = 1",
|
||||
params![accepted as i32],
|
||||
)
|
||||
.map_err(|e| format!("Failed to update ToS acceptance: {}", e))?;
|
||||
|
||||
info!("[LegalSettings] Qobuz ToS accepted: {}", accepted);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub type LegalSettingsState = Arc<Mutex<LegalSettingsStore>>;
|
||||
|
||||
// Tauri commands
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_legal_settings(
|
||||
state: tauri::State<'_, LegalSettingsState>,
|
||||
) -> Result<LegalSettings, String> {
|
||||
let store = state.lock().map_err(|e| format!("Lock error: {}", e))?;
|
||||
store.get_settings()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_qobuz_tos_accepted(
|
||||
state: tauri::State<'_, LegalSettingsState>,
|
||||
) -> Result<bool, String> {
|
||||
let store = state.lock().map_err(|e| format!("Lock error: {}", e))?;
|
||||
let settings = store.get_settings()?;
|
||||
Ok(settings.qobuz_tos_accepted)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_qobuz_tos_accepted(
|
||||
state: tauri::State<'_, LegalSettingsState>,
|
||||
accepted: bool,
|
||||
) -> Result<(), String> {
|
||||
let store = state.lock().map_err(|e| format!("Lock error: {}", e))?;
|
||||
store.set_qobuz_tos_accepted(accepted)
|
||||
}
|
||||
@@ -16,6 +16,7 @@ pub mod favorites_preferences;
|
||||
pub mod favorites_cache;
|
||||
pub mod subscription_state;
|
||||
pub mod tray_settings;
|
||||
pub mod legal_settings;
|
||||
|
||||
pub use audio_settings::{
|
||||
AudioSettings,
|
||||
@@ -83,3 +84,12 @@ pub use favorites_cache::{
|
||||
sync_cached_favorite_artists,
|
||||
clear_favorites_cache,
|
||||
};
|
||||
|
||||
pub use legal_settings::{
|
||||
LegalSettings,
|
||||
LegalSettingsState,
|
||||
LegalSettingsStore,
|
||||
get_legal_settings,
|
||||
get_qobuz_tos_accepted,
|
||||
set_qobuz_tos_accepted,
|
||||
};
|
||||
@@ -196,6 +196,13 @@ pub fn run() {
|
||||
// Initialize tray settings state
|
||||
let tray_settings_state = config::tray_settings::TraySettingsState::new()
|
||||
.expect("Failed to initialize tray settings");
|
||||
// Initialize legal settings state (ToS acceptance persistence)
|
||||
let legal_settings_state = config::legal_settings::LegalSettingsState::new(
|
||||
std::sync::Mutex::new(
|
||||
config::legal_settings::LegalSettingsStore::new()
|
||||
.expect("Failed to initialize legal settings")
|
||||
)
|
||||
);
|
||||
// Initialize updates state
|
||||
let updates_state = updates::UpdatesState::new()
|
||||
.expect("Failed to initialize updates state");
|
||||
@@ -442,6 +449,7 @@ pub fn run() {
|
||||
.manage(favorites_prefs_state)
|
||||
.manage(favorites_cache_state)
|
||||
.manage(tray_settings_state)
|
||||
.manage(legal_settings_state)
|
||||
.manage(updates_state)
|
||||
.manage(musicbrainz_state)
|
||||
.manage(listenbrainz_state)
|
||||
@@ -841,6 +849,10 @@ pub fn run() {
|
||||
config::tray_settings::set_enable_tray,
|
||||
config::tray_settings::set_minimize_to_tray,
|
||||
config::tray_settings::set_close_to_tray,
|
||||
// Legal settings commands
|
||||
config::legal_settings::get_legal_settings,
|
||||
config::legal_settings::get_qobuz_tos_accepted,
|
||||
config::legal_settings::set_qobuz_tos_accepted,
|
||||
// MusicBrainz integration commands
|
||||
commands::musicbrainz_resolve_track,
|
||||
commands::musicbrainz_resolve_artist,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import TitleBar from '../TitleBar.svelte';
|
||||
import { t } from '$lib/i18n';
|
||||
import { setManualOffline } from '$lib/stores/offlineStore';
|
||||
import { qobuzTosAccepted } from '$lib/stores/qobuzLegalStore';
|
||||
import { qobuzTosAccepted, loadTosAcceptance } from '$lib/stores/qobuzLegalStore';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
interface UserInfo {
|
||||
@@ -83,6 +83,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Load ToS acceptance from Rust (persisted, survives app updates)
|
||||
initStatus = 'Loading preferences...';
|
||||
await loadTosAcceptance();
|
||||
|
||||
// Check for saved credentials and auto-login
|
||||
initStatus = 'Checking saved credentials...';
|
||||
const hasSavedCreds = await invoke<boolean>('has_saved_credentials');
|
||||
|
||||
@@ -1,18 +1,79 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { writable } from 'svelte/store';
|
||||
import { writable, get } from 'svelte/store';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
const STORAGE_KEY = 'qbz-qobuz-tos-accepted';
|
||||
// Store for ToS acceptance - persisted in Rust (survives app updates)
|
||||
export const qobuzTosAccepted = writable<boolean>(false);
|
||||
|
||||
function readInitial(): boolean {
|
||||
// Flag to track if we've loaded from Rust
|
||||
let initialized = false;
|
||||
|
||||
/**
|
||||
* Load ToS acceptance state from Rust backend.
|
||||
* Should be called early in app initialization.
|
||||
*/
|
||||
export async function loadTosAcceptance(): Promise<boolean> {
|
||||
if (!browser) return false;
|
||||
return localStorage.getItem(STORAGE_KEY) === 'true';
|
||||
|
||||
try {
|
||||
const accepted = await invoke<boolean>('get_qobuz_tos_accepted');
|
||||
qobuzTosAccepted.set(accepted);
|
||||
initialized = true;
|
||||
return accepted;
|
||||
} catch (err) {
|
||||
console.error('Failed to load ToS acceptance from backend:', err);
|
||||
// Fallback to localStorage for migration from old versions
|
||||
const localValue = localStorage.getItem('qbz-qobuz-tos-accepted') === 'true';
|
||||
if (localValue) {
|
||||
// Migrate to Rust storage
|
||||
try {
|
||||
await invoke('set_qobuz_tos_accepted', { accepted: true });
|
||||
qobuzTosAccepted.set(true);
|
||||
localStorage.removeItem('qbz-qobuz-tos-accepted');
|
||||
console.log('Migrated ToS acceptance to Rust storage');
|
||||
return true;
|
||||
} catch (migrateErr) {
|
||||
console.error('Failed to migrate ToS acceptance:', migrateErr);
|
||||
}
|
||||
}
|
||||
initialized = true;
|
||||
return localValue;
|
||||
}
|
||||
}
|
||||
|
||||
export const qobuzTosAccepted = writable<boolean>(readInitial());
|
||||
/**
|
||||
* Set ToS acceptance state in Rust backend.
|
||||
*/
|
||||
export async function setTosAcceptance(accepted: boolean): Promise<void> {
|
||||
if (!browser) return;
|
||||
|
||||
try {
|
||||
await invoke('set_qobuz_tos_accepted', { accepted });
|
||||
qobuzTosAccepted.set(accepted);
|
||||
} catch (err) {
|
||||
console.error('Failed to save ToS acceptance:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to changes and persist to Rust.
|
||||
* Only persists if value actually changed and we're initialized.
|
||||
*/
|
||||
if (browser) {
|
||||
let lastValue: boolean | null = null;
|
||||
|
||||
qobuzTosAccepted.subscribe((value) => {
|
||||
localStorage.setItem(STORAGE_KEY, String(value));
|
||||
// Skip if not initialized or value hasn't changed
|
||||
if (!initialized || lastValue === value) {
|
||||
lastValue = value;
|
||||
return;
|
||||
}
|
||||
lastValue = value;
|
||||
|
||||
// Persist to Rust (fire and forget, errors logged in setTosAcceptance)
|
||||
invoke('set_qobuz_tos_accepted', { accepted: value }).catch((err) => {
|
||||
console.error('Failed to persist ToS acceptance:', err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user