mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-07-16 09:23:07 +01:00
fix(review): share logout/expiry cleanup, never save a PIN credential without its hash
This commit is contained in:
@@ -186,20 +186,30 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
// session on the first 401 so the app drops cleanly to the login screen.
|
// session on the first 401 so the app drops cleanly to the login screen.
|
||||||
const sessionExpiredRef = useRef(false);
|
const sessionExpiredRef = useRef(false);
|
||||||
|
|
||||||
|
// Shared teardown for manual logout AND forced session expiry — keeping it
|
||||||
|
// in one place prevents the two paths from drifting (a 401 expiry must wipe
|
||||||
|
// plugin settings / Jellyseerr state too, or the next login on the same
|
||||||
|
// device inherits the previous user's data).
|
||||||
|
// Saved credentials are kept so the user can quick-login again.
|
||||||
|
const clearSessionState = useCallback(async () => {
|
||||||
|
storage.remove("token");
|
||||||
|
storage.remove("user");
|
||||||
|
clearTVDiscoverySafely();
|
||||||
|
setUser(null);
|
||||||
|
setApi(null);
|
||||||
|
setPluginSettings(undefined);
|
||||||
|
await clearAllJellyseerData();
|
||||||
|
queryClient.clear();
|
||||||
|
storage.remove("REACT_QUERY_OFFLINE_CACHE");
|
||||||
|
}, [setUser, setApi, setPluginSettings, clearAllJellyseerData, queryClient]);
|
||||||
|
|
||||||
const handleSessionExpired = useCallback(() => {
|
const handleSessionExpired = useCallback(() => {
|
||||||
if (sessionExpiredRef.current) return; // run once per session
|
if (sessionExpiredRef.current) return; // run once per session
|
||||||
sessionExpiredRef.current = true;
|
sessionExpiredRef.current = true;
|
||||||
storage.remove("token");
|
clearSessionState().catch((e) =>
|
||||||
storage.remove("user");
|
writeErrorLog(`Session-expiry cleanup failed: ${e?.message ?? e}`),
|
||||||
setUser(null);
|
);
|
||||||
setApi(null);
|
}, [clearSessionState]);
|
||||||
queryClient.clear();
|
|
||||||
storage.remove("REACT_QUERY_OFFLINE_CACHE");
|
|
||||||
// Same TV-discovery cleanup as a manual logout, so a forced expiry
|
|
||||||
// doesn't leave stale discovery state behind.
|
|
||||||
clearTVDiscoverySafely();
|
|
||||||
// Saved credentials are kept so the user can quick-login again.
|
|
||||||
}, [setUser, setApi, queryClient]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only guard an authenticated session. A pre-auth api (login screen) keeps
|
// Only guard an authenticated session. A pre-auth api (login screen) keeps
|
||||||
@@ -375,7 +385,10 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
if (!api?.basePath || !user?.Id || !user.Name || !token) return;
|
if (!api?.basePath || !user?.Id || !user.Name || !token) return;
|
||||||
const securityType = options?.securityType || "none";
|
const securityType = options?.securityType || "none";
|
||||||
let pinHash: string | undefined;
|
let pinHash: string | undefined;
|
||||||
if (securityType === "pin" && options?.pinCode) {
|
if (securityType === "pin") {
|
||||||
|
// Never persist a "pin" credential without its hash — it would be
|
||||||
|
// impossible to unlock.
|
||||||
|
if (!options?.pinCode) throw new Error("PIN code is required");
|
||||||
pinHash = await hashPIN(options.pinCode);
|
pinHash = await hashPIN(options.pinCode);
|
||||||
}
|
}
|
||||||
await saveAccountCredential({
|
await saveAccountCredential({
|
||||||
@@ -424,18 +437,24 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
if (securityType === "pin" && options.pinCode) {
|
if (securityType === "pin" && options.pinCode) {
|
||||||
pinHash = await hashPIN(options.pinCode);
|
pinHash = await hashPIN(options.pinCode);
|
||||||
}
|
}
|
||||||
|
if (securityType === "pin" && !pinHash) {
|
||||||
await saveAccountCredential({
|
// Never persist a "pin" credential without its hash — it would be
|
||||||
serverUrl: api.basePath,
|
// impossible to unlock. Skip the save rather than failing a login
|
||||||
serverName: serverName || "",
|
// that already succeeded.
|
||||||
token: auth.data.AccessToken,
|
writeErrorLog("Account save skipped: PIN required but missing");
|
||||||
userId: auth.data.User.Id || "",
|
} else {
|
||||||
username,
|
await saveAccountCredential({
|
||||||
savedAt: Date.now(),
|
serverUrl: api.basePath,
|
||||||
securityType,
|
serverName: serverName || "",
|
||||||
pinHash,
|
token: auth.data.AccessToken,
|
||||||
primaryImageTag: auth.data.User.PrimaryImageTag ?? undefined,
|
userId: auth.data.User.Id || "",
|
||||||
});
|
username,
|
||||||
|
savedAt: Date.now(),
|
||||||
|
securityType,
|
||||||
|
pinHash,
|
||||||
|
primaryImageTag: auth.data.User.PrimaryImageTag ?? undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const recentPluginSettings = await refreshStreamyfinPluginSettings();
|
const recentPluginSettings = await refreshStreamyfinPluginSettings();
|
||||||
@@ -495,19 +514,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
writeErrorLog("Failed to delete expo push token for device"),
|
writeErrorLog("Failed to delete expo push token for device"),
|
||||||
);
|
);
|
||||||
|
|
||||||
storage.remove("token");
|
await clearSessionState();
|
||||||
storage.remove("user");
|
|
||||||
clearTVDiscoverySafely();
|
|
||||||
setUser(null);
|
|
||||||
setApi(null);
|
|
||||||
setPluginSettings(undefined);
|
|
||||||
await clearAllJellyseerData();
|
|
||||||
|
|
||||||
// Clear React Query cache to prevent data from previous account lingering
|
|
||||||
queryClient.clear();
|
|
||||||
storage.remove("REACT_QUERY_OFFLINE_CACHE");
|
|
||||||
|
|
||||||
// Note: We keep saved credentials for quick switching back
|
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.error("Logout failed:", error);
|
console.error("Logout failed:", error);
|
||||||
|
|||||||
Reference in New Issue
Block a user