[Remote] Actually fix auth (#260)
* fix favicon, basic auth * actual fix......
This commit is contained in:
parent
b375238baf
commit
9e3e038d42
3 changed files with 125 additions and 103 deletions
|
@ -34,6 +34,7 @@ interface MimeType {
|
||||||
|
|
||||||
interface StatefulWebSocket extends WebSocket {
|
interface StatefulWebSocket extends WebSocket {
|
||||||
alive: boolean;
|
alive: boolean;
|
||||||
|
auth: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let server: Server | undefined;
|
let server: Server | undefined;
|
||||||
|
@ -52,7 +53,7 @@ type SendData = ServerEvent & {
|
||||||
|
|
||||||
function send({ client, event, data }: SendData): void {
|
function send({ client, event, data }: SendData): void {
|
||||||
if (client.readyState === WebSocket.OPEN) {
|
if (client.readyState === WebSocket.OPEN) {
|
||||||
if (client.alive) {
|
if (client.alive && client.auth) {
|
||||||
client.send(JSON.stringify({ data, event }));
|
client.send(JSON.stringify({ data, event }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,12 +321,13 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
||||||
|
|
||||||
wsServer.on('connection', (ws) => {
|
wsServer.on('connection', (ws) => {
|
||||||
let authFail: number | undefined;
|
let authFail: number | undefined;
|
||||||
|
ws.alive = true;
|
||||||
|
|
||||||
if (!settings.username && !settings.password) {
|
if (!settings.username && !settings.password) {
|
||||||
ws.alive = true;
|
ws.auth = true;
|
||||||
} else {
|
} else {
|
||||||
authFail = setTimeout(() => {
|
authFail = setTimeout(() => {
|
||||||
if (!ws.alive) {
|
if (!ws.auth) {
|
||||||
ws.close();
|
ws.close();
|
||||||
}
|
}
|
||||||
}, 10000) as unknown as number;
|
}, 10000) as unknown as number;
|
||||||
|
@ -334,30 +336,29 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
||||||
ws.on('error', console.error);
|
ws.on('error', console.error);
|
||||||
|
|
||||||
ws.on('message', (data) => {
|
ws.on('message', (data) => {
|
||||||
if (!ws.alive) {
|
|
||||||
try {
|
|
||||||
const auth = data.toString().split(' ')[1];
|
|
||||||
const [login, password] = Buffer.from(auth, 'base64')
|
|
||||||
.toString()
|
|
||||||
.split(':');
|
|
||||||
|
|
||||||
if (login === settings.username && password === settings.password) {
|
|
||||||
ws.alive = true;
|
|
||||||
} else {
|
|
||||||
ws.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(authFail);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(data.toString()) as ClientEvent;
|
const json = JSON.parse(data.toString()) as ClientEvent;
|
||||||
const event = json.event;
|
const event = json.event;
|
||||||
|
|
||||||
|
if (!ws.auth) {
|
||||||
|
if (event === 'authenticate') {
|
||||||
|
const auth = json.header.split(' ')[1];
|
||||||
|
const [login, password] = Buffer.from(auth, 'base64')
|
||||||
|
.toString()
|
||||||
|
.split(':');
|
||||||
|
|
||||||
|
if (login === settings.username && password === settings.password) {
|
||||||
|
ws.auth = true;
|
||||||
|
} else {
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(authFail);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case 'pause': {
|
case 'pause': {
|
||||||
getMainWindow()?.webContents.send('renderer-player-pause');
|
getMainWindow()?.webContents.send('renderer-player-pause');
|
||||||
|
|
|
@ -87,7 +87,7 @@ export const useRemoteStore = create<SettingsSlice>()(
|
||||||
devtools(
|
devtools(
|
||||||
immer((set, get) => ({
|
immer((set, get) => ({
|
||||||
actions: {
|
actions: {
|
||||||
reconnect: () => {
|
reconnect: async () => {
|
||||||
const existing = get().socket;
|
const existing = get().socket;
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
|
@ -99,91 +99,102 @@ export const useRemoteStore = create<SettingsSlice>()(
|
||||||
existing.close(4001);
|
existing.close(4001);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set(async (state) => {
|
|
||||||
try {
|
|
||||||
const credentials = await fetch('/credentials');
|
|
||||||
const authHeader = await credentials.text();
|
|
||||||
|
|
||||||
const socket = new WebSocket(
|
let authHeader: string | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const credentials = await fetch('/credentials');
|
||||||
|
authHeader = await credentials.text();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to get credentials');
|
||||||
|
}
|
||||||
|
|
||||||
|
set((state) => {
|
||||||
|
const socket = new WebSocket(
|
||||||
|
// eslint-disable-next-line no-restricted-globals
|
||||||
|
location.href.replace('http', 'ws'),
|
||||||
|
) as StatefulWebSocket;
|
||||||
|
|
||||||
|
socket.natural = false;
|
||||||
|
|
||||||
|
socket.addEventListener('message', (message) => {
|
||||||
|
const { event, data } = JSON.parse(message.data) as ServerEvent;
|
||||||
|
|
||||||
|
switch (event) {
|
||||||
|
case 'error': {
|
||||||
|
toast.error({ message: data, title: 'Socket error' });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'favorite': {
|
||||||
|
set((state) => {
|
||||||
|
if (state.info.song?.id === data.id) {
|
||||||
|
state.info.song.userFavorite = data.favorite;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'proxy': {
|
||||||
|
set((state) => {
|
||||||
|
if (state.info.song) {
|
||||||
|
state.info.song.imageUrl = `data:image/jpeg;base64,${data}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'rating': {
|
||||||
|
set((state) => {
|
||||||
|
if (state.info.song?.id === data.id) {
|
||||||
|
state.info.song.userRating = data.rating;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'song': {
|
||||||
|
set((nested) => {
|
||||||
|
nested.info = { ...nested.info, ...data };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener('open', () => {
|
||||||
|
if (authHeader) {
|
||||||
|
socket.send(
|
||||||
|
JSON.stringify({
|
||||||
|
event: 'authenticate',
|
||||||
|
header: authHeader,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
set({ connected: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener('close', (reason) => {
|
||||||
|
if (reason.code === 4002 || reason.code === 4003) {
|
||||||
// eslint-disable-next-line no-restricted-globals
|
// eslint-disable-next-line no-restricted-globals
|
||||||
location.href.replace('http', 'ws'),
|
location.reload();
|
||||||
) as StatefulWebSocket;
|
} else if (reason.code === 4000) {
|
||||||
|
toast.warn({
|
||||||
|
message: 'Feishin remote server is down',
|
||||||
|
title: 'Connection closed',
|
||||||
|
});
|
||||||
|
} else if (reason.code !== 4001 && !socket.natural) {
|
||||||
|
toast.error({
|
||||||
|
message: 'Socket closed for unexpected reason',
|
||||||
|
title: 'Connection closed',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
socket.natural = false;
|
if (!socket.natural) {
|
||||||
|
set({ connected: false, info: {} });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
socket.addEventListener('message', (message) => {
|
state.socket = socket;
|
||||||
const { event, data } = JSON.parse(message.data) as ServerEvent;
|
|
||||||
|
|
||||||
switch (event) {
|
|
||||||
case 'error': {
|
|
||||||
toast.error({ message: data, title: 'Socket error' });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'favorite': {
|
|
||||||
set((state) => {
|
|
||||||
if (state.info.song?.id === data.id) {
|
|
||||||
state.info.song.userFavorite = data.favorite;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'proxy': {
|
|
||||||
set((state) => {
|
|
||||||
if (state.info.song) {
|
|
||||||
state.info.song.imageUrl = `data:image/jpeg;base64,${data}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'rating': {
|
|
||||||
set((state) => {
|
|
||||||
if (state.info.song?.id === data.id) {
|
|
||||||
state.info.song.userRating = data.rating;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'song': {
|
|
||||||
set((nested) => {
|
|
||||||
nested.info = { ...nested.info, ...data };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.addEventListener('open', () => {
|
|
||||||
socket.send(authHeader);
|
|
||||||
set({ connected: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.addEventListener('close', (reason) => {
|
|
||||||
if (reason.code === 4002 || reason.code === 4003) {
|
|
||||||
// eslint-disable-next-line no-restricted-globals
|
|
||||||
location.reload();
|
|
||||||
} else if (reason.code === 4000) {
|
|
||||||
toast.warn({
|
|
||||||
message: 'Feishin remote server is down',
|
|
||||||
title: 'Connection closed',
|
|
||||||
});
|
|
||||||
} else if (reason.code !== 4001 && !socket.natural) {
|
|
||||||
toast.error({
|
|
||||||
message: 'Socket closed for unexpected reason',
|
|
||||||
title: 'Connection closed',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!socket.natural) {
|
|
||||||
set({ connected: false, info: {} });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
state.socket = socket;
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
send: (data: ClientEvent) => {
|
send: (data: ClientEvent) => {
|
||||||
|
console.log(data, get().socket);
|
||||||
get().socket?.send(JSON.stringify(data));
|
get().socket?.send(JSON.stringify(data));
|
||||||
},
|
},
|
||||||
toggleIsDark: () => {
|
toggleIsDark: () => {
|
||||||
|
|
|
@ -53,4 +53,14 @@ export interface ClientVolume {
|
||||||
volume: number;
|
volume: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClientEvent = ClientSimpleEvent | ClientFavorite | ClientRating | ClientVolume;
|
export interface ClientAuth {
|
||||||
|
event: 'authenticate';
|
||||||
|
header: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClientEvent =
|
||||||
|
| ClientAuth
|
||||||
|
| ClientSimpleEvent
|
||||||
|
| ClientFavorite
|
||||||
|
| ClientRating
|
||||||
|
| ClientVolume;
|
||||||
|
|
Reference in a new issue