Replace password storage with token-based authentication for server switching

Co-authored-by: retardgerman <78982850+retardgerman@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-09-05 20:03:33 +00:00
parent 85929c2854
commit a377317710
3 changed files with 60 additions and 30 deletions

View File

@@ -11,10 +11,7 @@ interface Server {
serverName?: string; serverName?: string;
serverId?: string; serverId?: string;
lastUsername?: string; lastUsername?: string;
savedCredentials?: { savedToken?: string;
username: string;
password: string;
};
} }
interface PreviousServersListProps { interface PreviousServersListProps {
@@ -58,7 +55,7 @@ export const PreviousServersList: React.FC<PreviousServersListProps> = ({
onPress={() => onServerSelect(s)} onPress={() => onServerSelect(s)}
title={getServerDisplayName(s)} title={getServerDisplayName(s)}
subtitle={getServerSubtitle(s)} subtitle={getServerSubtitle(s)}
icon={s.savedCredentials ? "key" : "server"} icon={s.savedToken ? "key" : "server"}
showArrow showArrow
/> />
))} ))}

View File

@@ -13,10 +13,7 @@ interface Server {
serverName?: string; serverName?: string;
serverId?: string; serverId?: string;
lastUsername?: string; lastUsername?: string;
savedCredentials?: { savedToken?: string;
username: string;
password: string;
};
} }
interface Props extends ViewProps {} interface Props extends ViewProps {}
@@ -54,8 +51,8 @@ export const ServerSwitcher: React.FC<Props> = ({ ...props }) => {
const getServerSubtitle = (server: Server) => { const getServerSubtitle = (server: Server) => {
if (server.lastUsername) { if (server.lastUsername) {
const hasCredentials = !!server.savedCredentials; const hasToken = !!server.savedToken;
return hasCredentials return hasToken
? `${server.lastUsername} • Auto-login available` ? `${server.lastUsername} • Auto-login available`
: `Last user: ${server.lastUsername}`; : `Last user: ${server.lastUsername}`;
} }
@@ -81,7 +78,7 @@ export const ServerSwitcher: React.FC<Props> = ({ ...props }) => {
onPress={() => handleServerSwitch(server)} onPress={() => handleServerSwitch(server)}
title={getServerDisplayName(server)} title={getServerDisplayName(server)}
subtitle={getServerSubtitle(server)} subtitle={getServerSubtitle(server)}
icon={server.savedCredentials ? "key" : "server"} icon={server.savedToken ? "key" : "server"}
showArrow showArrow
disabled={switchingServer === server.address} disabled={switchingServer === server.address}
/> />

View File

@@ -33,10 +33,7 @@ interface Server {
serverName?: string; serverName?: string;
serverId?: string; serverId?: string;
lastUsername?: string; lastUsername?: string;
savedCredentials?: { savedToken?: string;
username: string;
password: string;
};
} }
export const apiAtom = atom<Api | null>(null); export const apiAtom = atom<Api | null>(null);
@@ -256,7 +253,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
setApi(jellyfin.createApi(api?.basePath, auth.data?.AccessToken)); setApi(jellyfin.createApi(api?.basePath, auth.data?.AccessToken));
storage.set("token", auth.data?.AccessToken); storage.set("token", auth.data?.AccessToken);
// Save credentials to the current server if requested // Save token to the current server if requested
if (saveCredentials && api.basePath) { if (saveCredentials && api.basePath) {
const previousServers = JSON.parse( const previousServers = JSON.parse(
storage.getString("previousServers") || "[]", storage.getString("previousServers") || "[]",
@@ -267,7 +264,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
return { return {
...server, ...server,
lastUsername: username, lastUsername: username,
savedCredentials: { username, password } savedToken: auth.data.AccessToken
}; };
} }
return server; return server;
@@ -348,20 +345,42 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
// Get current server info for comparison // Get current server info for comparison
const currentServerId = await getCurrentServerId(); const currentServerId = await getCurrentServerId();
// If switching to same server (different URL), try auto-login with saved credentials // If switching to same server (different URL), try auto-login with saved token
if (server.serverId && server.serverId === currentServerId && server.savedCredentials) { if (server.serverId && server.serverId === currentServerId && server.savedToken) {
try { try {
// Just set the new server without logging out // Create API instance with saved token
await setServerMutation.mutateAsync(server); const apiInstance = jellyfin?.createApi(server.address, server.savedToken);
// Auto-login with saved credentials if (!apiInstance) throw new Error("Failed to create API instance");
await loginMutation.mutateAsync({
username: server.savedCredentials.username, // Validate the token by making an authenticated request
password: server.savedCredentials.password, const userApi = getUserApi(apiInstance);
saveCredentials: false, // Don't save again const userResponse = await userApi.getCurrentUser();
});
return; if (userResponse.data) {
// Token is valid, update the API and user
setApi(apiInstance);
setUser(userResponse.data);
storage.set("serverUrl", server.address);
storage.set("token", server.savedToken);
storage.set("user", JSON.stringify(userResponse.data));
return;
}
} catch (error) { } catch (error) {
console.warn("Auto-login failed, falling back to manual login:", error); console.warn("Saved token is invalid, falling back to manual login:", error);
// Remove invalid token from server
const previousServers = JSON.parse(
storage.getString("previousServers") || "[]",
) as Server[];
const updatedServers = previousServers.map((s) => {
if (s.address === server.address) {
const { savedToken, ...serverWithoutToken } = s;
return serverWithoutToken;
}
return s;
});
storage.set("previousServers", JSON.stringify(updatedServers));
} }
} }
@@ -441,6 +460,23 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
if (!jellyfin) return; if (!jellyfin) return;
try { try {
// Migrate any existing savedCredentials to remove them
const previousServers = JSON.parse(
storage.getString("previousServers") || "[]",
) as any[];
if (previousServers.length > 0) {
const migratedServers = previousServers.map((server) => {
if (server.savedCredentials) {
// Remove savedCredentials field for security
const { savedCredentials, ...serverWithoutCredentials } = server;
return serverWithoutCredentials;
}
return server;
});
storage.set("previousServers", JSON.stringify(migratedServers));
}
const token = getTokenFromStorage(); const token = getTokenFromStorage();
const serverUrl = getServerUrlFromStorage(); const serverUrl = getServerUrlFromStorage();
const storedUser = getUserFromStorage(); const storedUser = getUserFromStorage();