diff --git a/desktop/server.js b/desktop/server.js index 63c4309..3209b7f 100644 --- a/desktop/server.js +++ b/desktop/server.js @@ -35,13 +35,17 @@ const configRoutes = require("./electron/api/config/config.routes"); const roomRoutes = require("./electron/api/rooms/rooms.routes"); const { setupRoomWebSocket } = require("./electron/api/rooms/rooms.websocket"); -fastify.addHook("preHandler", async (request) => { +const { getConfig } = require('./electron/shared/config'); +const { values } = getConfig(); +const jwtSecret = values.server?.jwt_secret; + +fastify.addHook("preHandler", async (request, reply) => { const auth = request.headers.authorization; if (!auth) return; try { const token = auth.replace("Bearer ", ""); - request.user = jwt.verify(token, process.env.JWT_SECRET); + request.user = jwt.verify(token, jwtSecret); } catch (e) { return reply.code(401).send({ error: "Invalid token" }); } diff --git a/desktop/src/api/anime/anime.controller.ts b/desktop/src/api/anime/anime.controller.ts index 3d1ae49..0b09689 100644 --- a/desktop/src/api/anime/anime.controller.ts +++ b/desktop/src/api/anime/anime.controller.ts @@ -118,7 +118,7 @@ export async function getWatchStream(req: WatchStreamRequest, reply: FastifyRepl export async function openInMPV(req: any, reply: any) { try { - const { title, video, subtitles = [], chapters = [], animeId, episode, entrySource, token } = req.body; + const { title, video, subtitles = [], chapters = [], animeId, episode, entrySource } = req.body; if (!video?.url) return { error: 'Missing video url' }; @@ -250,10 +250,9 @@ export async function openInMPV(req: any, reply: any) { }; const updateProgress = async () => { - if (!token || progressUpdated) return; + if (!req.user || progressUpdated) return; try { - const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any; - const userId = decoded.id; + const userId = req.user.id; await upsertListEntry({ user_id: userId, entry_id: animeId, @@ -263,6 +262,7 @@ export async function openInMPV(req: any, reply: any) { progress: episode }); progressUpdated = true; + progressUpdated = true; } catch (e) { console.error("[MPV] Progress update failed", e); } }; diff --git a/desktop/src/api/config/config.controller.ts b/desktop/src/api/config/config.controller.ts index 7bdaf37..a95ee7b 100644 --- a/desktop/src/api/config/config.controller.ts +++ b/desktop/src/api/config/config.controller.ts @@ -1,10 +1,16 @@ import { FastifyReply, FastifyRequest } from 'fastify'; import { getConfig, setConfig } from '../../shared/config'; +function hideSecrets(values: any) { + const copy = structuredClone(values); + if (copy.server?.jwt_secret) delete copy.server.jwt_secret; + return copy; +} + export async function getFullConfig(req: FastifyRequest, reply: FastifyReply) { try { const { values, schema } = getConfig(); - return { values, schema }; + return { values: hideSecrets(values), schema }; } catch { return { error: "Error loading config" }; } @@ -22,7 +28,7 @@ export async function getConfigSection( return { error: "Section not found" }; } - return { [section]: values[section] }; + return { [section]: hideSecrets(values)[section] }; } catch { return { error: "Error loading config section" }; } diff --git a/desktop/src/api/user/user.controller.ts b/desktop/src/api/user/user.controller.ts index 0ff40cd..b11ca1e 100644 --- a/desktop/src/api/user/user.controller.ts +++ b/desktop/src/api/user/user.controller.ts @@ -2,6 +2,10 @@ import { FastifyReply, FastifyRequest } from 'fastify'; import * as userService from './user.service'; import {queryOne} from '../../shared/database'; import jwt from "jsonwebtoken"; +import { getConfig } from '../../shared/config'; +const { values } = getConfig(); +const jwtSecret = values.server?.jwt_secret; + interface UserIdParams { id: string; } interface CreateUserBody { @@ -75,7 +79,7 @@ export async function login(req: FastifyRequest, reply: FastifyReply) { const token = jwt.sign( { id: userId }, - process.env.JWT_SECRET!, + jwtSecret, { expiresIn: "7d" } ); diff --git a/desktop/src/scripts/anime/player.js b/desktop/src/scripts/anime/player.js index 163b419..2b189f1 100644 --- a/desktop/src/scripts/anime/player.js +++ b/desktop/src/scripts/anime/player.js @@ -1903,15 +1903,17 @@ const AnimePlayer = (function() { chapters: _skipIntervals, animeId: _animeId, episode: _currentEpisode, - entrySource: _entrySource, - token: token - }; + entrySource: _entrySource + }; try { const res = await fetch('/api/watch/mpv', { method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body) + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify(body), }); if (res.ok) { diff --git a/desktop/src/shared/config.js b/desktop/src/shared/config.js index aa388a9..32dd77c 100644 --- a/desktop/src/shared/config.js +++ b/desktop/src/shared/config.js @@ -2,6 +2,7 @@ import fs from 'fs'; import path from 'path'; import os from 'os'; import yaml from 'js-yaml'; +import crypto from 'crypto'; const BASE_DIR = path.join(os.homedir(), 'WaifuBoards'); const CONFIG_PATH = path.join(BASE_DIR, 'config.yaml'); @@ -17,6 +18,9 @@ const DEFAULT_CONFIG = { ffmpeg: null, ffprobe: null, cloudflared: null, + }, + server: { + jwt_secret: null } }; @@ -39,13 +43,31 @@ function ensureConfigFile() { fs.mkdirSync(BASE_DIR, { recursive: true }); } - if (!fs.existsSync(CONFIG_PATH)) { + let configExists = fs.existsSync(CONFIG_PATH); + + if (!configExists) { fs.writeFileSync( CONFIG_PATH, yaml.dump(DEFAULT_CONFIG), 'utf8' ); } + + const raw = fs.readFileSync(CONFIG_PATH, 'utf8'); + const loaded = yaml.load(raw) || {}; + + if (!loaded.server) loaded.server = {}; + if (!loaded.server.jwt_secret) { + loaded.server.jwt_secret = crypto.randomBytes(32).toString('hex'); + fs.writeFileSync(CONFIG_PATH, yaml.dump(deepMerge(structuredClone(DEFAULT_CONFIG), loaded)), 'utf8'); + } +} + +export function getPublicConfig() { + const { values } = getConfig(); + const publicConfig = structuredClone(values); + if (publicConfig.server) delete publicConfig.server.jwt_secret; + return publicConfig; } export function getConfig() { diff --git a/docker/server.js b/docker/server.js index e2d82fe..ab1207e 100644 --- a/docker/server.js +++ b/docker/server.js @@ -29,13 +29,17 @@ const configRoutes = require("./dist/api/config/config.routes"); const roomRoutes = require("./dist/api/rooms/rooms.routes"); const { setupRoomWebSocket } = require("./dist/api/rooms/rooms.websocket"); +const { getConfig } = require('./dist/shared/config'); +const { values } = getConfig(); +const jwtSecret = values.server?.jwt_secret; + fastify.addHook("preHandler", async (request, reply) => { const auth = request.headers.authorization; if (!auth) return; try { const token = auth.replace("Bearer ", ""); - request.user = jwt.verify(token, process.env.JWT_SECRET); + request.user = jwt.verify(token, jwtSecret); } catch (e) { return reply.code(401).send({ error: "Invalid token" }); } diff --git a/docker/src/api/config/config.controller.ts b/docker/src/api/config/config.controller.ts index 7bdaf37..a95ee7b 100644 --- a/docker/src/api/config/config.controller.ts +++ b/docker/src/api/config/config.controller.ts @@ -1,10 +1,16 @@ import { FastifyReply, FastifyRequest } from 'fastify'; import { getConfig, setConfig } from '../../shared/config'; +function hideSecrets(values: any) { + const copy = structuredClone(values); + if (copy.server?.jwt_secret) delete copy.server.jwt_secret; + return copy; +} + export async function getFullConfig(req: FastifyRequest, reply: FastifyReply) { try { const { values, schema } = getConfig(); - return { values, schema }; + return { values: hideSecrets(values), schema }; } catch { return { error: "Error loading config" }; } @@ -22,7 +28,7 @@ export async function getConfigSection( return { error: "Section not found" }; } - return { [section]: values[section] }; + return { [section]: hideSecrets(values)[section] }; } catch { return { error: "Error loading config section" }; } diff --git a/docker/src/api/user/user.controller.ts b/docker/src/api/user/user.controller.ts index 0ff40cd..b11ca1e 100644 --- a/docker/src/api/user/user.controller.ts +++ b/docker/src/api/user/user.controller.ts @@ -2,6 +2,10 @@ import { FastifyReply, FastifyRequest } from 'fastify'; import * as userService from './user.service'; import {queryOne} from '../../shared/database'; import jwt from "jsonwebtoken"; +import { getConfig } from '../../shared/config'; +const { values } = getConfig(); +const jwtSecret = values.server?.jwt_secret; + interface UserIdParams { id: string; } interface CreateUserBody { @@ -75,7 +79,7 @@ export async function login(req: FastifyRequest, reply: FastifyReply) { const token = jwt.sign( { id: userId }, - process.env.JWT_SECRET!, + jwtSecret, { expiresIn: "7d" } ); diff --git a/docker/src/scripts/anime/player.js b/docker/src/scripts/anime/player.js index 163b419..2b189f1 100644 --- a/docker/src/scripts/anime/player.js +++ b/docker/src/scripts/anime/player.js @@ -1903,15 +1903,17 @@ const AnimePlayer = (function() { chapters: _skipIntervals, animeId: _animeId, episode: _currentEpisode, - entrySource: _entrySource, - token: token - }; + entrySource: _entrySource + }; try { const res = await fetch('/api/watch/mpv', { method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body) + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify(body), }); if (res.ok) { diff --git a/docker/src/shared/config.js b/docker/src/shared/config.js index aa388a9..32dd77c 100644 --- a/docker/src/shared/config.js +++ b/docker/src/shared/config.js @@ -2,6 +2,7 @@ import fs from 'fs'; import path from 'path'; import os from 'os'; import yaml from 'js-yaml'; +import crypto from 'crypto'; const BASE_DIR = path.join(os.homedir(), 'WaifuBoards'); const CONFIG_PATH = path.join(BASE_DIR, 'config.yaml'); @@ -17,6 +18,9 @@ const DEFAULT_CONFIG = { ffmpeg: null, ffprobe: null, cloudflared: null, + }, + server: { + jwt_secret: null } }; @@ -39,13 +43,31 @@ function ensureConfigFile() { fs.mkdirSync(BASE_DIR, { recursive: true }); } - if (!fs.existsSync(CONFIG_PATH)) { + let configExists = fs.existsSync(CONFIG_PATH); + + if (!configExists) { fs.writeFileSync( CONFIG_PATH, yaml.dump(DEFAULT_CONFIG), 'utf8' ); } + + const raw = fs.readFileSync(CONFIG_PATH, 'utf8'); + const loaded = yaml.load(raw) || {}; + + if (!loaded.server) loaded.server = {}; + if (!loaded.server.jwt_secret) { + loaded.server.jwt_secret = crypto.randomBytes(32).toString('hex'); + fs.writeFileSync(CONFIG_PATH, yaml.dump(deepMerge(structuredClone(DEFAULT_CONFIG), loaded)), 'utf8'); + } +} + +export function getPublicConfig() { + const { values } = getConfig(); + const publicConfig = structuredClone(values); + if (publicConfig.server) delete publicConfig.server.jwt_secret; + return publicConfig; } export function getConfig() {