jwt secret is now autogenerated

This commit is contained in:
2026-01-07 19:07:47 +01:00
parent 82ddc6d5e9
commit c225a9f48d
11 changed files with 101 additions and 25 deletions

View File

@@ -35,13 +35,17 @@ const configRoutes = require("./electron/api/config/config.routes");
const roomRoutes = require("./electron/api/rooms/rooms.routes"); const roomRoutes = require("./electron/api/rooms/rooms.routes");
const { setupRoomWebSocket } = require("./electron/api/rooms/rooms.websocket"); 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; const auth = request.headers.authorization;
if (!auth) return; if (!auth) return;
try { try {
const token = auth.replace("Bearer ", ""); const token = auth.replace("Bearer ", "");
request.user = jwt.verify(token, process.env.JWT_SECRET); request.user = jwt.verify(token, jwtSecret);
} catch (e) { } catch (e) {
return reply.code(401).send({ error: "Invalid token" }); return reply.code(401).send({ error: "Invalid token" });
} }

View File

@@ -118,7 +118,7 @@ export async function getWatchStream(req: WatchStreamRequest, reply: FastifyRepl
export async function openInMPV(req: any, reply: any) { export async function openInMPV(req: any, reply: any) {
try { 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' }; if (!video?.url) return { error: 'Missing video url' };
@@ -250,10 +250,9 @@ export async function openInMPV(req: any, reply: any) {
}; };
const updateProgress = async () => { const updateProgress = async () => {
if (!token || progressUpdated) return; if (!req.user || progressUpdated) return;
try { try {
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any; const userId = req.user.id;
const userId = decoded.id;
await upsertListEntry({ await upsertListEntry({
user_id: userId, user_id: userId,
entry_id: animeId, entry_id: animeId,
@@ -263,6 +262,7 @@ export async function openInMPV(req: any, reply: any) {
progress: episode progress: episode
}); });
progressUpdated = true; progressUpdated = true;
progressUpdated = true;
} catch (e) { console.error("[MPV] Progress update failed", e); } } catch (e) { console.error("[MPV] Progress update failed", e); }
}; };

View File

@@ -1,10 +1,16 @@
import { FastifyReply, FastifyRequest } from 'fastify'; import { FastifyReply, FastifyRequest } from 'fastify';
import { getConfig, setConfig } from '../../shared/config'; 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) { export async function getFullConfig(req: FastifyRequest, reply: FastifyReply) {
try { try {
const { values, schema } = getConfig(); const { values, schema } = getConfig();
return { values, schema }; return { values: hideSecrets(values), schema };
} catch { } catch {
return { error: "Error loading config" }; return { error: "Error loading config" };
} }
@@ -22,7 +28,7 @@ export async function getConfigSection(
return { error: "Section not found" }; return { error: "Section not found" };
} }
return { [section]: values[section] }; return { [section]: hideSecrets(values)[section] };
} catch { } catch {
return { error: "Error loading config section" }; return { error: "Error loading config section" };
} }

View File

@@ -2,6 +2,10 @@ import { FastifyReply, FastifyRequest } from 'fastify';
import * as userService from './user.service'; import * as userService from './user.service';
import {queryOne} from '../../shared/database'; import {queryOne} from '../../shared/database';
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { getConfig } from '../../shared/config';
const { values } = getConfig();
const jwtSecret = values.server?.jwt_secret;
interface UserIdParams { id: string; } interface UserIdParams { id: string; }
interface CreateUserBody { interface CreateUserBody {
@@ -75,7 +79,7 @@ export async function login(req: FastifyRequest, reply: FastifyReply) {
const token = jwt.sign( const token = jwt.sign(
{ id: userId }, { id: userId },
process.env.JWT_SECRET!, jwtSecret,
{ expiresIn: "7d" } { expiresIn: "7d" }
); );

View File

@@ -1903,15 +1903,17 @@ const AnimePlayer = (function() {
chapters: _skipIntervals, chapters: _skipIntervals,
animeId: _animeId, animeId: _animeId,
episode: _currentEpisode, episode: _currentEpisode,
entrySource: _entrySource, entrySource: _entrySource
token: token };
};
try { try {
const res = await fetch('/api/watch/mpv', { const res = await fetch('/api/watch/mpv', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {
body: JSON.stringify(body) 'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(body),
}); });
if (res.ok) { if (res.ok) {

View File

@@ -2,6 +2,7 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import os from 'os'; import os from 'os';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import crypto from 'crypto';
const BASE_DIR = path.join(os.homedir(), 'WaifuBoards'); const BASE_DIR = path.join(os.homedir(), 'WaifuBoards');
const CONFIG_PATH = path.join(BASE_DIR, 'config.yaml'); const CONFIG_PATH = path.join(BASE_DIR, 'config.yaml');
@@ -17,6 +18,9 @@ const DEFAULT_CONFIG = {
ffmpeg: null, ffmpeg: null,
ffprobe: null, ffprobe: null,
cloudflared: null, cloudflared: null,
},
server: {
jwt_secret: null
} }
}; };
@@ -39,13 +43,31 @@ function ensureConfigFile() {
fs.mkdirSync(BASE_DIR, { recursive: true }); fs.mkdirSync(BASE_DIR, { recursive: true });
} }
if (!fs.existsSync(CONFIG_PATH)) { let configExists = fs.existsSync(CONFIG_PATH);
if (!configExists) {
fs.writeFileSync( fs.writeFileSync(
CONFIG_PATH, CONFIG_PATH,
yaml.dump(DEFAULT_CONFIG), yaml.dump(DEFAULT_CONFIG),
'utf8' '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() { export function getConfig() {

View File

@@ -29,13 +29,17 @@ const configRoutes = require("./dist/api/config/config.routes");
const roomRoutes = require("./dist/api/rooms/rooms.routes"); const roomRoutes = require("./dist/api/rooms/rooms.routes");
const { setupRoomWebSocket } = require("./dist/api/rooms/rooms.websocket"); 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) => { fastify.addHook("preHandler", async (request, reply) => {
const auth = request.headers.authorization; const auth = request.headers.authorization;
if (!auth) return; if (!auth) return;
try { try {
const token = auth.replace("Bearer ", ""); const token = auth.replace("Bearer ", "");
request.user = jwt.verify(token, process.env.JWT_SECRET); request.user = jwt.verify(token, jwtSecret);
} catch (e) { } catch (e) {
return reply.code(401).send({ error: "Invalid token" }); return reply.code(401).send({ error: "Invalid token" });
} }

View File

@@ -1,10 +1,16 @@
import { FastifyReply, FastifyRequest } from 'fastify'; import { FastifyReply, FastifyRequest } from 'fastify';
import { getConfig, setConfig } from '../../shared/config'; 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) { export async function getFullConfig(req: FastifyRequest, reply: FastifyReply) {
try { try {
const { values, schema } = getConfig(); const { values, schema } = getConfig();
return { values, schema }; return { values: hideSecrets(values), schema };
} catch { } catch {
return { error: "Error loading config" }; return { error: "Error loading config" };
} }
@@ -22,7 +28,7 @@ export async function getConfigSection(
return { error: "Section not found" }; return { error: "Section not found" };
} }
return { [section]: values[section] }; return { [section]: hideSecrets(values)[section] };
} catch { } catch {
return { error: "Error loading config section" }; return { error: "Error loading config section" };
} }

View File

@@ -2,6 +2,10 @@ import { FastifyReply, FastifyRequest } from 'fastify';
import * as userService from './user.service'; import * as userService from './user.service';
import {queryOne} from '../../shared/database'; import {queryOne} from '../../shared/database';
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { getConfig } from '../../shared/config';
const { values } = getConfig();
const jwtSecret = values.server?.jwt_secret;
interface UserIdParams { id: string; } interface UserIdParams { id: string; }
interface CreateUserBody { interface CreateUserBody {
@@ -75,7 +79,7 @@ export async function login(req: FastifyRequest, reply: FastifyReply) {
const token = jwt.sign( const token = jwt.sign(
{ id: userId }, { id: userId },
process.env.JWT_SECRET!, jwtSecret,
{ expiresIn: "7d" } { expiresIn: "7d" }
); );

View File

@@ -1903,15 +1903,17 @@ const AnimePlayer = (function() {
chapters: _skipIntervals, chapters: _skipIntervals,
animeId: _animeId, animeId: _animeId,
episode: _currentEpisode, episode: _currentEpisode,
entrySource: _entrySource, entrySource: _entrySource
token: token };
};
try { try {
const res = await fetch('/api/watch/mpv', { const res = await fetch('/api/watch/mpv', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {
body: JSON.stringify(body) 'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(body),
}); });
if (res.ok) { if (res.ok) {

View File

@@ -2,6 +2,7 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import os from 'os'; import os from 'os';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import crypto from 'crypto';
const BASE_DIR = path.join(os.homedir(), 'WaifuBoards'); const BASE_DIR = path.join(os.homedir(), 'WaifuBoards');
const CONFIG_PATH = path.join(BASE_DIR, 'config.yaml'); const CONFIG_PATH = path.join(BASE_DIR, 'config.yaml');
@@ -17,6 +18,9 @@ const DEFAULT_CONFIG = {
ffmpeg: null, ffmpeg: null,
ffprobe: null, ffprobe: null,
cloudflared: null, cloudflared: null,
},
server: {
jwt_secret: null
} }
}; };
@@ -39,13 +43,31 @@ function ensureConfigFile() {
fs.mkdirSync(BASE_DIR, { recursive: true }); fs.mkdirSync(BASE_DIR, { recursive: true });
} }
if (!fs.existsSync(CONFIG_PATH)) { let configExists = fs.existsSync(CONFIG_PATH);
if (!configExists) {
fs.writeFileSync( fs.writeFileSync(
CONFIG_PATH, CONFIG_PATH,
yaml.dump(DEFAULT_CONFIG), yaml.dump(DEFAULT_CONFIG),
'utf8' '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() { export function getConfig() {