Browse Source

Add setting to use legacy authentication (plain)

- Supports additional servers such as supysonic
master
jeffvli 3 years ago
committed by Jeff
parent
commit
882e3cee4b
  1. 126
      src/api/api.ts
  2. 76
      src/components/settings/Login.tsx
  3. 4
      src/components/shared/setDefaultSettings.ts
  4. 1
      src/shared/mockSettings.ts

126
src/api/api.ts

@ -1,21 +1,34 @@
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
import axios from 'axios'; import axios from 'axios';
import _ from 'lodash'; import _ from 'lodash';
import settings from 'electron-settings';
import { nanoid } from 'nanoid/non-secure'; import { nanoid } from 'nanoid/non-secure';
import axiosRetry from 'axios-retry'; import axiosRetry from 'axios-retry';
import { mockSettings } from '../shared/mockSettings';
const legacyAuth =
process.env.NODE_ENV === 'test'
? mockSettings.legacyAuth
: Boolean(settings.getSync('legacyAuth'));
const getAuth = (useLegacyAuth: boolean) => {
if (useLegacyAuth) {
return {
username: localStorage.getItem('username') || '',
password: localStorage.getItem('password') || '',
server: localStorage.getItem('server') || '',
};
}
const getAuth = () => { return {
const serverConfig = {
username: localStorage.getItem('username') || '', username: localStorage.getItem('username') || '',
salt: localStorage.getItem('salt') || '', salt: localStorage.getItem('salt') || '',
hash: localStorage.getItem('hash') || '', hash: localStorage.getItem('hash') || '',
server: localStorage.getItem('server') || '', server: localStorage.getItem('server') || '',
}; };
return serverConfig;
}; };
const auth = getAuth(); const auth = getAuth(legacyAuth);
const API_BASE_URL = `${auth.server}/rest`; const API_BASE_URL = `${auth.server}/rest`;
export const api = axios.create({ export const api = axios.create({
@ -25,8 +38,9 @@ export const api = axios.create({
api.interceptors.request.use((config) => { api.interceptors.request.use((config) => {
config.params = config.params || {}; config.params = config.params || {};
config.params.u = auth.username; config.params.u = auth.username;
config.params.s = auth.salt; config.params.s = legacyAuth ? null : auth.salt;
config.params.t = auth.hash; config.params.t = legacyAuth ? null : auth.hash;
config.params.p = legacyAuth ? auth.password : null;
config.params.v = '1.15.0'; config.params.v = '1.15.0';
config.params.c = 'sonixd'; config.params.c = 'sonixd';
config.params.f = 'json'; config.params.f = 'json';
@ -99,11 +113,23 @@ const authParams = {
f: 'json', f: 'json',
}; };
const getCoverArtUrl = (item: any, size = 150) => { const getCoverArtUrl = (item: any, useLegacyAuth: boolean, size = 150) => {
if (!item.coverArt) { if (!item.coverArt) {
return 'img/placeholder.jpg'; return 'img/placeholder.jpg';
} }
if (useLegacyAuth) {
return (
`${API_BASE_URL}/getCoverArt` +
`?id=${item.coverArt}` +
`&u=${auth.username}` +
`&p=${auth.password}` +
`&v=1.15.0` +
`&c=sonixd` +
`&size=${size}`
);
}
return ( return (
`${API_BASE_URL}/getCoverArt` + `${API_BASE_URL}/getCoverArt` +
`?id=${item.coverArt}` + `?id=${item.coverArt}` +
@ -116,7 +142,18 @@ const getCoverArtUrl = (item: any, size = 150) => {
); );
}; };
const getStreamUrl = (id: string) => { const getStreamUrl = (id: string, useLegacyAuth: boolean) => {
if (useLegacyAuth) {
return (
`${API_BASE_URL}/stream` +
`?id=${id}` +
`&u=${auth.username}` +
`&p=${auth.password}` +
`&v=1.15.0` +
`&c=sonixd`
);
}
return ( return (
`${API_BASE_URL}/stream` + `${API_BASE_URL}/stream` +
`?id=${id}` + `?id=${id}` +
@ -147,7 +184,7 @@ export const getPlaylists = async (sortBy: string) => {
return (newData || []).map((playlist: any) => ({ return (newData || []).map((playlist: any) => ({
...playlist, ...playlist,
name: playlist.name, name: playlist.name,
image: playlist.songCount > 0 ? getCoverArtUrl(playlist) : 'img/placeholder.jpg', image: playlist.songCount > 0 ? getCoverArtUrl(playlist, legacyAuth) : 'img/placeholder.jpg',
type: 'playlist', type: 'playlist',
uniqueId: nanoid(), uniqueId: nanoid(),
})); }));
@ -160,13 +197,16 @@ export const getPlaylist = async (id: string) => {
entry: null, // Normalize to 'song' instead of 'entry' entry: null, // Normalize to 'song' instead of 'entry'
song: (data.playlist.entry || []).map((entry: any, index: any) => ({ song: (data.playlist.entry || []).map((entry: any, index: any) => ({
...entry, ...entry,
streamUrl: getStreamUrl(entry.id), streamUrl: getStreamUrl(entry.id, legacyAuth),
image: getCoverArtUrl(entry), image: getCoverArtUrl(entry, legacyAuth),
type: 'music', type: 'music',
index, index,
uniqueId: nanoid(), uniqueId: nanoid(),
})), })),
image: data.playlist.songCount > 0 ? getCoverArtUrl(data.playlist) : 'img/placeholder.jpg', image:
data.playlist.songCount > 0
? getCoverArtUrl(data.playlist, legacyAuth)
: 'img/placeholder.jpg',
}; };
}; };
@ -192,7 +232,7 @@ export const getPlayQueue = async () => {
...data.playQueue, ...data.playQueue,
entry: (data.playQueue.entry || []).map((entry: any, index: any) => ({ entry: (data.playQueue.entry || []).map((entry: any, index: any) => ({
...entry, ...entry,
streamUrl: getStreamUrl(entry.id), streamUrl: getStreamUrl(entry.id, legacyAuth),
index, index,
})), })),
}; };
@ -207,7 +247,7 @@ export const getStarred = async (options: { musicFolderId?: string | number }) =
...entry, ...entry,
title: entry.name, title: entry.name,
albumId: entry.id, albumId: entry.id,
image: getCoverArtUrl(entry), image: getCoverArtUrl(entry, legacyAuth),
starred: entry.starred || undefined, starred: entry.starred || undefined,
type: 'album', type: 'album',
isDir: false, isDir: false,
@ -216,8 +256,8 @@ export const getStarred = async (options: { musicFolderId?: string | number }) =
})), })),
song: (data.starred2.song || []).map((entry: any, index: any) => ({ song: (data.starred2.song || []).map((entry: any, index: any) => ({
...entry, ...entry,
streamUrl: getStreamUrl(entry.id), streamUrl: getStreamUrl(entry.id, legacyAuth),
image: getCoverArtUrl(entry), image: getCoverArtUrl(entry, legacyAuth),
starred: entry.starred || undefined, starred: entry.starred || undefined,
type: 'music', type: 'music',
index, index,
@ -226,8 +266,8 @@ export const getStarred = async (options: { musicFolderId?: string | number }) =
artist: (data.starred2.artist || []).map((entry: any, index: any) => ({ artist: (data.starred2.artist || []).map((entry: any, index: any) => ({
...entry, ...entry,
albumCount: entry.albumCount || undefined, albumCount: entry.albumCount || undefined,
coverArt: getCoverArtUrl(entry), coverArt: getCoverArtUrl(entry, legacyAuth),
image: getCoverArtUrl(entry), image: getCoverArtUrl(entry, legacyAuth),
starred: entry.starred || Date.now(), // Airsonic does not return the starred date starred: entry.starred || Date.now(), // Airsonic does not return the starred date
type: 'artist', type: 'artist',
index, index,
@ -268,7 +308,7 @@ export const getAlbums = async (
...entry, ...entry,
title: entry.name, title: entry.name,
albumId: entry.id, albumId: entry.id,
image: getCoverArtUrl(entry, coverArtSize), image: getCoverArtUrl(entry, legacyAuth, coverArtSize),
starred: entry.starred || undefined, starred: entry.starred || undefined,
type: 'album', type: 'album',
isDir: false, isDir: false,
@ -308,7 +348,7 @@ export const getAlbumsDirect = async (
...entry, ...entry,
title: entry.name, title: entry.name,
albumId: entry.id, albumId: entry.id,
image: getCoverArtUrl(entry, coverArtSize), image: getCoverArtUrl(entry, legacyAuth, coverArtSize),
starred: entry.starred || undefined, starred: entry.starred || undefined,
type: 'album', type: 'album',
isDir: false, isDir: false,
@ -365,7 +405,7 @@ export const getAllAlbums = (
...entry, ...entry,
title: entry.name, title: entry.name,
albumId: entry.id, albumId: entry.id,
image: getCoverArtUrl(entry, coverArtSize), image: getCoverArtUrl(entry, legacyAuth, coverArtSize),
starred: entry.starred || undefined, starred: entry.starred || undefined,
type: 'album', type: 'album',
isDir: false, isDir: false,
@ -400,13 +440,13 @@ export const getAlbum = async (id: string, coverArtSize = 150) => {
return { return {
...data.album, ...data.album,
image: getCoverArtUrl(data.album, coverArtSize), image: getCoverArtUrl(data.album, legacyAuth, coverArtSize),
type: 'album', type: 'album',
isDir: false, isDir: false,
song: (data.album.song || []).map((entry: any, index: any) => ({ song: (data.album.song || []).map((entry: any, index: any) => ({
...entry, ...entry,
streamUrl: getStreamUrl(entry.id), streamUrl: getStreamUrl(entry.id, legacyAuth),
image: getCoverArtUrl(entry, coverArtSize), image: getCoverArtUrl(entry, legacyAuth, coverArtSize),
type: 'music', type: 'music',
starred: entry.starred || undefined, starred: entry.starred || undefined,
index, index,
@ -433,8 +473,8 @@ export const getRandomSongs = async (
...data.randomSongs, ...data.randomSongs,
song: (data.randomSongs.song || []).map((entry: any, index: any) => ({ song: (data.randomSongs.song || []).map((entry: any, index: any) => ({
...entry, ...entry,
streamUrl: getStreamUrl(entry.id), streamUrl: getStreamUrl(entry.id, legacyAuth),
image: getCoverArtUrl(entry, coverArtSize), image: getCoverArtUrl(entry, legacyAuth, coverArtSize),
starred: entry.starred || undefined, starred: entry.starred || undefined,
index, index,
uniqueId: nanoid(), uniqueId: nanoid(),
@ -453,7 +493,7 @@ export const getArtists = async (options: { musicFolderId?: string | number }) =
artists.map((artist: any) => artists.map((artist: any) =>
artistList.push({ artistList.push({
...artist, ...artist,
image: getCoverArtUrl(artist, 150), image: getCoverArtUrl(artist, legacyAuth, 150),
type: 'artist', type: 'artist',
uniqueId: nanoid(), uniqueId: nanoid(),
}) })
@ -471,14 +511,14 @@ export const getArtist = async (id: string, coverArtSize = 150) => {
return { return {
...data.artist, ...data.artist,
image: getCoverArtUrl(data.artist, coverArtSize), image: getCoverArtUrl(data.artist, legacyAuth, coverArtSize),
type: 'artist', type: 'artist',
album: (data.artist.album || []).map((entry: any, index: any) => ({ album: (data.artist.album || []).map((entry: any, index: any) => ({
...entry, ...entry,
albumId: entry.id, albumId: entry.id,
type: 'album', type: 'album',
isDir: false, isDir: false,
image: getCoverArtUrl(entry, coverArtSize), image: getCoverArtUrl(entry, legacyAuth, coverArtSize),
starred: entry.starred || undefined, starred: entry.starred || undefined,
index, index,
uniqueId: nanoid(), uniqueId: nanoid(),
@ -646,7 +686,7 @@ export const getSimilarSongs = async (id: string, count: number, coverArtSize =
return { return {
song: (data.similarSongs2.song || []).map((entry: any, index: any) => ({ song: (data.similarSongs2.song || []).map((entry: any, index: any) => ({
...entry, ...entry,
image: getCoverArtUrl(entry, coverArtSize), image: getCoverArtUrl(entry, legacyAuth, coverArtSize),
index, index,
uniqueId: nanoid(), uniqueId: nanoid(),
})), })),
@ -779,7 +819,7 @@ export const search3 = async (options: {
return { return {
artist: (results.artist || []).map((entry: any, index: any) => ({ artist: (results.artist || []).map((entry: any, index: any) => ({
...entry, ...entry,
image: getCoverArtUrl(entry), image: getCoverArtUrl(entry, legacyAuth),
starred: entry.starred || undefined, starred: entry.starred || undefined,
type: 'artist', type: 'artist',
index, index,
@ -788,7 +828,7 @@ export const search3 = async (options: {
album: (results.album || []).map((entry: any, index: any) => ({ album: (results.album || []).map((entry: any, index: any) => ({
...entry, ...entry,
albumId: entry.id, albumId: entry.id,
image: getCoverArtUrl(entry), image: getCoverArtUrl(entry, legacyAuth),
starred: entry.starred || undefined, starred: entry.starred || undefined,
type: 'album', type: 'album',
isDir: false, isDir: false,
@ -797,8 +837,8 @@ export const search3 = async (options: {
})), })),
song: (results.song || []).map((entry: any, index: any) => ({ song: (results.song || []).map((entry: any, index: any) => ({
...entry, ...entry,
streamUrl: getStreamUrl(entry.id), streamUrl: getStreamUrl(entry.id, legacyAuth),
image: getCoverArtUrl(entry), image: getCoverArtUrl(entry, legacyAuth),
type: 'music', type: 'music',
starred: entry.starred || undefined, starred: entry.starred || undefined,
index, index,
@ -830,7 +870,7 @@ export const getIndexes = async (options: {
...folder, ...folder,
title: folder.name, title: folder.name,
isDir: true, isDir: true,
image: getCoverArtUrl(folder), image: getCoverArtUrl(folder, legacyAuth),
uniqueId: nanoid(), uniqueId: nanoid(),
type: 'folder', type: 'folder',
}); });
@ -845,8 +885,8 @@ export const getIndexes = async (options: {
...song, ...song,
index, index,
type: 'music', type: 'music',
streamUrl: getStreamUrl(song.id), streamUrl: getStreamUrl(song.id, legacyAuth),
image: getCoverArtUrl(song), image: getCoverArtUrl(song, legacyAuth),
uniqueId: nanoid(), uniqueId: nanoid(),
}) })
); );
@ -872,7 +912,7 @@ export const getMusicDirectory = async (options: { id: string }) => {
(folders || []).forEach((folder: any) => (folders || []).forEach((folder: any) =>
child.push({ child.push({
...folder, ...folder,
image: getCoverArtUrl(folder), image: getCoverArtUrl(folder, legacyAuth),
uniqueId: nanoid(), uniqueId: nanoid(),
type: 'folder', type: 'folder',
}) })
@ -883,8 +923,8 @@ export const getMusicDirectory = async (options: { id: string }) => {
...song, ...song,
index, index,
type: 'music', type: 'music',
streamUrl: getStreamUrl(song.id), streamUrl: getStreamUrl(song.id, legacyAuth),
image: getCoverArtUrl(song), image: getCoverArtUrl(song, legacyAuth),
uniqueId: nanoid(), uniqueId: nanoid(),
}) })
); );
@ -906,8 +946,8 @@ export const getAllDirectorySongs = async (options: { id: string }, data: any[]
...entry, ...entry,
index, index,
type: 'music', type: 'music',
streamUrl: getStreamUrl(entry.id), streamUrl: getStreamUrl(entry.id, legacyAuth),
image: getCoverArtUrl(entry), image: getCoverArtUrl(entry, legacyAuth),
uniqueId: nanoid(), uniqueId: nanoid(),
}); });
} }

76
src/components/settings/Login.tsx

@ -5,15 +5,17 @@ import settings from 'electron-settings';
import { Button, Form, ControlLabel, Message } from 'rsuite'; import { Button, Form, ControlLabel, Message } from 'rsuite';
import axios from 'axios'; import axios from 'axios';
import setDefaultSettings from '../shared/setDefaultSettings'; import setDefaultSettings from '../shared/setDefaultSettings';
import { StyledInput, StyledInputGroup } from '../shared/styled'; import { StyledCheckbox, StyledInput } from '../shared/styled';
import { LoginPanel } from './styled'; import { LoginPanel } from './styled';
import GenericPage from '../layout/GenericPage'; import GenericPage from '../layout/GenericPage';
import logo from '../../../assets/icon.png'; import logo from '../../../assets/icon.png';
import { mockSettings } from '../../shared/mockSettings';
const Login = () => { const Login = () => {
const [serverName, setServerName] = useState(''); const [serverName, setServerName] = useState('');
const [userName, setUserName] = useState(''); const [userName, setUserName] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [legacyAuth, setLegacyAuth] = useState(false);
const [message, setMessage] = useState(''); const [message, setMessage] = useState('');
const handleConnect = async () => { const handleConnect = async () => {
@ -23,9 +25,13 @@ const Login = () => {
const hash = md5(password + salt); const hash = md5(password + salt);
try { try {
const testConnection = await axios.get( const testConnection = legacyAuth
`${cleanServerName}/rest/getScanStatus?v=1.15.0&c=sonixd&f=json&u=${userName}&s=${salt}&t=${hash}` ? await axios.get(
); `${cleanServerName}/rest/getScanStatus?v=1.15.0&c=sonixd&f=json&u=${userName}&p=${password}`
)
: await axios.get(
`${cleanServerName}/rest/getScanStatus?v=1.15.0&c=sonixd&f=json&u=${userName}&s=${salt}&t=${hash}`
);
// Since a valid request will return a 200 response, we need to check that there // Since a valid request will return a 200 response, we need to check that there
// are no additional failures reported by the server // are no additional failures reported by the server
@ -45,12 +51,14 @@ const Login = () => {
localStorage.setItem('server', cleanServerName); localStorage.setItem('server', cleanServerName);
localStorage.setItem('serverBase64', btoa(cleanServerName)); localStorage.setItem('serverBase64', btoa(cleanServerName));
localStorage.setItem('username', userName); localStorage.setItem('username', userName);
localStorage.setItem('password', password);
localStorage.setItem('salt', salt); localStorage.setItem('salt', salt);
localStorage.setItem('hash', hash); localStorage.setItem('hash', hash);
settings.setSync('server', cleanServerName); settings.setSync('server', cleanServerName);
settings.setSync('serverBase64', btoa(cleanServerName)); settings.setSync('serverBase64', btoa(cleanServerName));
settings.setSync('username', userName); settings.setSync('username', userName);
settings.setSync('password', password);
settings.setSync('salt', salt); settings.setSync('salt', salt);
settings.setSync('hash', hash); settings.setSync('hash', hash);
@ -67,38 +75,48 @@ const Login = () => {
<h1>Sign in</h1> <h1>Sign in</h1>
<img src={logo} height="80px" width="80px" alt="" /> <img src={logo} height="80px" width="80px" alt="" />
</span> </span>
<br />
{message !== '' && <Message type="error" description={message} />} {message !== '' && <Message type="error" description={message} />}
<Form id="login-form" fluid style={{ paddingTop: '20px' }}> <Form id="login-form" fluid style={{ paddingTop: '20px' }}>
<ControlLabel>Server</ControlLabel> <ControlLabel>Server</ControlLabel>
<StyledInputGroup> <StyledInput
<StyledInput id="login-servername"
id="login-servername" name="servername"
name="servername" value={serverName}
value={serverName} onChange={(e: string) => setServerName(e)}
onChange={(e: string) => setServerName(e)} />
/>
</StyledInputGroup>
<br /> <br />
<ControlLabel>Username</ControlLabel> <ControlLabel>Username</ControlLabel>
<StyledInputGroup> <StyledInput
<StyledInput id="login-username"
id="login-username" name="name"
name="name" value={userName}
value={userName} onChange={(e: string) => setUserName(e)}
onChange={(e: string) => setUserName(e)} />
/>
</StyledInputGroup>
<br /> <br />
<ControlLabel>Password</ControlLabel> <ControlLabel>Password</ControlLabel>
<StyledInputGroup> <StyledInput
<StyledInput id="login-password"
id="login-password" name="password"
name="password" type="password"
type="password" value={password}
value={password} onChange={(e: string) => setPassword(e)}
onChange={(e: string) => setPassword(e)} />
/> <br />
</StyledInputGroup> <StyledCheckbox
defaultChecked={
process.env.NODE_ENV === 'test'
? mockSettings
: Boolean(settings.getSync('legacyAuth'))
}
checked={legacyAuth}
onChange={(_v: any, e: boolean) => {
settings.setSync('legacyAuth', e);
setLegacyAuth(e);
}}
>
Legacy auth (plaintext)
</StyledCheckbox>
<br /> <br />
<Button <Button
id="login-button" id="login-button"

4
src/components/shared/setDefaultSettings.ts

@ -2,6 +2,10 @@ import settings from 'electron-settings';
import path from 'path'; import path from 'path';
const setDefaultSettings = (force: boolean) => { const setDefaultSettings = (force: boolean) => {
if (force || !settings.hasSync('legacyAuth')) {
settings.setSync('legacyAuth', false);
}
if (force || !settings.hasSync('theme')) { if (force || !settings.hasSync('theme')) {
settings.setSync('theme', 'defaultDark'); settings.setSync('theme', 'defaultDark');
} }

1
src/shared/mockSettings.ts

@ -3,6 +3,7 @@ export const mockSettings = {
showDebugWindow: false, showDebugWindow: false,
globalMediaHotkeys: true, globalMediaHotkeys: true,
cachePath: 'C:\\Users\\jli\\AppData\\Roaming\\Electron', cachePath: 'C:\\Users\\jli\\AppData\\Roaming\\Electron',
legacyAuth: false,
volume: 0.93, volume: 0.93,
seekForwardInterval: 5, seekForwardInterval: 5,
seekBackwardInterval: 5, seekBackwardInterval: 5,

Loading…
Cancel
Save