feat: Implement new authentication and account management system, refactoring password gates and settings components.

This commit is contained in:
kuekhaoyang
2026-02-17 00:09:56 +08:00
parent ba615e8965
commit 0753492dfd
22 changed files with 420 additions and 820 deletions
+59
View File
@@ -0,0 +1,59 @@
/**
* Auth Store - Simple module-level session management
* NOT Zustand — needs to be synchronous at import time for store key generation
*/
export interface AuthSession {
profileId: string;
name: string;
role: 'admin' | 'viewer';
}
const SESSION_KEY = 'kvideo-session';
export function getSession(): AuthSession | null {
if (typeof window === 'undefined') return null;
// Check sessionStorage first, then localStorage (for persisted sessions)
const raw = sessionStorage.getItem(SESSION_KEY) || localStorage.getItem(SESSION_KEY);
if (!raw) return null;
try {
const parsed = JSON.parse(raw);
if (parsed && parsed.profileId && parsed.name && parsed.role) {
return parsed as AuthSession;
}
} catch {
// Invalid session data
}
return null;
}
export function setSession(session: AuthSession, persist: boolean): void {
if (typeof window === 'undefined') return;
const data = JSON.stringify(session);
sessionStorage.setItem(SESSION_KEY, data);
if (persist) {
localStorage.setItem(SESSION_KEY, data);
}
}
export function clearSession(): void {
if (typeof window === 'undefined') return;
sessionStorage.removeItem(SESSION_KEY);
localStorage.removeItem(SESSION_KEY);
// Also clear old unlock keys for backward compat cleanup
sessionStorage.removeItem('kvideo-unlocked');
localStorage.removeItem('kvideo-unlocked');
}
export function isAdmin(): boolean {
const session = getSession();
if (!session) return true; // No auth configured = full access
return session.role === 'admin';
}
export function getProfileId(): string {
const session = getSession();
return session?.profileId || '';
}
+3 -2
View File
@@ -6,6 +6,7 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { FavoriteItem } from '@/lib/types';
import { profiledKey } from '@/lib/utils/profile-storage';
const MAX_FAVORITES = 100;
@@ -117,8 +118,8 @@ const createFavoritesStore = (name: string) =>
)
);
export const useFavoritesStore = createFavoritesStore('kvideo-favorites-store');
export const usePremiumFavoritesStore = createFavoritesStore('kvideo-premium-favorites-store');
export const useFavoritesStore = createFavoritesStore(profiledKey('kvideo-favorites-store'));
export const usePremiumFavoritesStore = createFavoritesStore(profiledKey('kvideo-premium-favorites-store'));
/**
* Helper hook to get the appropriate favorites store
+3 -2
View File
@@ -7,6 +7,7 @@ import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { VideoHistoryItem, Episode } from '@/lib/types';
import { clearSegmentsForUrl, clearAllCache } from '@/lib/utils/cacheManager';
import { profiledKey } from '@/lib/utils/profile-storage';
const MAX_HISTORY_ITEMS = 50;
@@ -151,8 +152,8 @@ const createHistoryStore = (name: string) =>
)
);
export const useHistoryStore = createHistoryStore('kvideo-history-store');
export const usePremiumHistoryStore = createHistoryStore('kvideo-premium-history-store');
export const useHistoryStore = createHistoryStore(profiledKey('kvideo-history-store'));
export const usePremiumHistoryStore = createHistoryStore(profiledKey('kvideo-premium-history-store'));
/**
* Helper hook to get the appropriate history store
+3 -2
View File
@@ -6,6 +6,7 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { profiledKey } from '@/lib/utils/profile-storage';
const MAX_HISTORY_ITEMS = 20;
@@ -113,8 +114,8 @@ const createSearchHistoryStore = (name: string) =>
)
);
export const useSearchHistoryStore = createSearchHistoryStore('kvideo-search-history');
export const usePremiumSearchHistoryStore = createSearchHistoryStore('kvideo-premium-search-history');
export const useSearchHistoryStore = createSearchHistoryStore(profiledKey('kvideo-search-history'));
export const usePremiumSearchHistoryStore = createSearchHistoryStore(profiledKey('kvideo-premium-search-history'));
/**
* Helper hook to get the appropriate search history store
+5 -4
View File
@@ -1,4 +1,5 @@
import type { AppSettings } from './settings-store';
import { profiledKey } from '@/lib/utils/profile-storage';
export const SEARCH_HISTORY_KEY = 'kvideo-search-history';
export const WATCH_HISTORY_KEY = 'kvideo-watch-history';
@@ -20,8 +21,8 @@ export function exportSettings(settings: AppSettings, includeHistory: boolean =
};
if (includeHistory && typeof window !== 'undefined') {
const searchHistory = localStorage.getItem(SEARCH_HISTORY_KEY);
const watchHistory = localStorage.getItem(WATCH_HISTORY_KEY);
const searchHistory = localStorage.getItem(profiledKey(SEARCH_HISTORY_KEY));
const watchHistory = localStorage.getItem(profiledKey(WATCH_HISTORY_KEY));
if (searchHistory) exportData.searchHistory = JSON.parse(searchHistory);
if (watchHistory) exportData.watchHistory = JSON.parse(watchHistory);
@@ -49,11 +50,11 @@ export function importSettings(
// Case 2: History only (can be independent)
if (data.searchHistory && typeof window !== 'undefined') {
localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(data.searchHistory));
localStorage.setItem(profiledKey(SEARCH_HISTORY_KEY), JSON.stringify(data.searchHistory));
imported = true;
}
if (data.watchHistory && typeof window !== 'undefined') {
localStorage.setItem(WATCH_HISTORY_KEY, JSON.stringify(data.watchHistory));
localStorage.setItem(profiledKey(WATCH_HISTORY_KEY), JSON.stringify(data.watchHistory));
imported = true;
}
-12
View File
@@ -28,10 +28,6 @@ export interface AppSettings {
sortBy: SortOption;
searchHistory: boolean;
watchHistory: boolean;
passwordAccess: boolean;
accessPasswords: string[];
settingsPasswordEnabled: boolean;
settingsPasswords: string[];
// Player settings
autoNextEpisode: boolean;
autoSkipIntro: boolean;
@@ -106,10 +102,6 @@ function getDefaultAppSettings(): AppSettings {
sortBy: 'default',
searchHistory: true,
watchHistory: true,
passwordAccess: false,
accessPasswords: [],
settingsPasswordEnabled: false,
settingsPasswords: [],
autoNextEpisode: true,
autoSkipIntro: false,
skipIntroSeconds: 0,
@@ -186,10 +178,6 @@ export const settingsStore = {
sortBy: parsed.sortBy || 'default',
searchHistory: parsed.searchHistory !== undefined ? parsed.searchHistory : true,
watchHistory: parsed.watchHistory !== undefined ? parsed.watchHistory : true,
passwordAccess: parsed.passwordAccess !== undefined ? parsed.passwordAccess : false,
accessPasswords: Array.isArray(parsed.accessPasswords) ? parsed.accessPasswords : [],
settingsPasswordEnabled: parsed.settingsPasswordEnabled !== undefined ? parsed.settingsPasswordEnabled : false,
settingsPasswords: Array.isArray(parsed.settingsPasswords) ? parsed.settingsPasswords : [],
autoNextEpisode: parsed.autoNextEpisode !== undefined ? parsed.autoNextEpisode : true,
autoSkipIntro: parsed.autoSkipIntro !== undefined ? parsed.autoSkipIntro : false,
skipIntroSeconds: typeof parsed.skipIntroSeconds === 'number' ? parsed.skipIntroSeconds : 0,
+10
View File
@@ -0,0 +1,10 @@
import { getProfileId } from '@/lib/store/auth-store';
/**
* Returns a profiled localStorage key based on the current user's profileId.
* If no session exists, returns the base key unchanged (backward compatible).
*/
export function profiledKey(baseKey: string): string {
const id = getProfileId();
return id ? `${baseKey}-p-${id}` : baseKey;
}