From b2ba7768c8b37f87dc5db4c2323efd36a7140b52 Mon Sep 17 00:00:00 2001 From: CyferShepard Date: Sun, 7 Apr 2024 15:59:08 +0200 Subject: [PATCH] auth setup fixes --- backend/classes/jellyfin-api.js | 3 - backend/routes/api.js | 22 -------- backend/routes/auth.js | 29 ++++++++++ backend/routes/logging.js | 99 +++++++++++++-------------------- backend/routes/proxy.js | 44 +++++++++++++++ backend/server.js | 12 ++-- backend/swagautogen.js | 95 ++++++++++++++++--------------- backend/swagger.json | 86 ++++++++++++++++++++++++++-- src/pages/setup.jsx | 26 +++------ 9 files changed, 257 insertions(+), 159 deletions(-) diff --git a/backend/classes/jellyfin-api.js b/backend/classes/jellyfin-api.js index 3260938..467599b 100644 --- a/backend/classes/jellyfin-api.js +++ b/backend/classes/jellyfin-api.js @@ -412,9 +412,6 @@ class JellyfinAPI { } async validateSettings(url, apikey) { - if (!this.configReady) { - return []; - } try { const response = await axios.get(url, { headers: { diff --git a/backend/routes/api.js b/backend/routes/api.js index 0cda6db..909c0f0 100644 --- a/backend/routes/api.js +++ b/backend/routes/api.js @@ -897,26 +897,4 @@ router.post("/deletePlaybackActivity", async (req, res) => { } }); -//Jellyfin related functions - -router.post("/validateSettings", async (req, res) => { - const { url, apikey } = req.body; - - if (url === undefined || apikey === undefined) { - res.status(400); - res.send("URL or API Key not provided"); - return; - } - - var _url = url; - _url = _url.replace(/\/web\/index\.html#!\/home\.html$/, ""); - if (!/^https?:\/\//i.test(url)) { - _url = "http://" + url; - } - _url = _url.replace(/\/$/, "") + "/system/configuration"; - - const validation = await Jellyfin.validateSettings(_url, apikey); - res.send(validation); -}); - module.exports = router; diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 26f96c6..b1f979d 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -83,4 +83,33 @@ router.post("/createuser", async (req, res) => { } }); +router.post("/configSetup", async (req, res) => { + try { + const { JF_HOST, JF_API_KEY } = req.body; + const config = await new configClass().getConfig(); + + if (JF_HOST === undefined && JF_API_KEY === undefined) { + res.status(400); + res.send("JF_HOST and JF_API_KEY are required for configuration"); + return; + } + + const { rows: getConfig } = await db.query('SELECT * FROM app_config where "ID"=1'); + + if (config.state != null && config.state < 2) { + let query = 'UPDATE app_config SET "JF_HOST"=$1, "JF_API_KEY"=$2 where "ID"=1'; + if (getConfig.length === 0) { + query = 'INSERT INTO app_config ("ID","JF_HOST","JF_API_KEY","APP_USER","APP_PASSWORD") VALUES (1,$1,$2,null,null)'; + } + + const { rows } = await db.query(query, [JF_HOST, JF_API_KEY]); + res.send(rows); + } else { + res.sendStatus(500); + } + } catch (error) { + console.log(error); + } +}); + module.exports = router; diff --git a/backend/routes/logging.js b/backend/routes/logging.js index 2d2d41f..19d5ef4 100644 --- a/backend/routes/logging.js +++ b/backend/routes/logging.js @@ -1,14 +1,11 @@ - const db = require("../db"); -const moment = require('moment'); -const taskstate = require('../logging/taskstate'); +const moment = require("moment"); +const taskstate = require("../logging/taskstate"); - -const {jf_logging_columns,jf_logging_mapping,} = require("../models/jf_logging"); +const { jf_logging_columns, jf_logging_mapping } = require("../models/jf_logging"); const express = require("express"); const router = express.Router(); - - +// #swagger.tags = ['Logs'] router.get("/getLogs", async (req, res) => { try { const { rows } = await db.query(`SELECT * FROM jf_logging order by "TimeRun" desc LIMIT 50 `); @@ -18,72 +15,54 @@ router.get("/getLogs", async (req, res) => { } }); - -async function insertLog(uuid,triggertype,taskType) -{ +async function insertLog(uuid, triggertype, taskType) { try { let startTime = moment(); - const log= - { - "Id":uuid, - "Name":taskType, - "Type":"Task", - "ExecutionType":triggertype, - "Duration":0, - "TimeRun":startTime, - "Log":JSON.stringify([{}]), - "Result":taskstate.RUNNING - + const log = { + Id: uuid, + Name: taskType, + Type: "Task", + ExecutionType: triggertype, + Duration: 0, + TimeRun: startTime, + Log: JSON.stringify([{}]), + Result: taskstate.RUNNING, }; - - await db.insertBulk("jf_logging",log,jf_logging_columns); - } catch (error) { - console.log(error); - return []; - } - + await db.insertBulk("jf_logging", log, jf_logging_columns); + } catch (error) { + console.log(error); + return []; + } } -async function updateLog(uuid,data,taskstate) -{ +async function updateLog(uuid, data, taskstate) { try { - - const { rows:task } = await db.query( - `SELECT "TimeRun" FROM jf_logging WHERE "Id" = '${uuid}';` - ); + const { rows: task } = await db.query(`SELECT "TimeRun" FROM jf_logging WHERE "Id" = '${uuid}';`); if (task.length === 0) { console.log("Unable to find task to update"); - }else{ - + } else { let endtime = moment(); let startTime = moment(task[0].TimeRun); - let duration = endtime.diff(startTime, 'seconds'); - const log= - { - "Id":uuid, - "Name":"NULL Placeholder", - "Type":"Task", - "ExecutionType":"NULL Placeholder", - "Duration":duration, - "TimeRun":startTime, - "Log":JSON.stringify(data), - "Result":taskstate - + let duration = endtime.diff(startTime, "seconds"); + const log = { + Id: uuid, + Name: "NULL Placeholder", + Type: "Task", + ExecutionType: "NULL Placeholder", + Duration: duration, + TimeRun: startTime, + Log: JSON.stringify(data), + Result: taskstate, }; - - await db.insertBulk("jf_logging",log,jf_logging_columns); - } - - } catch (error) { - console.log(error); - return []; - } - + await db.insertBulk("jf_logging", log, jf_logging_columns); + } + } catch (error) { + console.log(error); + return []; + } } - -module.exports = -{router,insertLog,updateLog}; +module.exports = { router, insertLog, updateLog }; diff --git a/backend/routes/proxy.js b/backend/routes/proxy.js index 41c2dd8..1dc3e6b 100644 --- a/backend/routes/proxy.js +++ b/backend/routes/proxy.js @@ -163,4 +163,48 @@ router.get("/getRecentlyAdded", async (req, res) => { } }); +//Jellyfin related functions + +router.post("/validateSettings", async (req, res) => { + const { url, apikey } = req.body; + + if (url === undefined || apikey === undefined) { + res.status(400); + res.send("URL or API Key not provided"); + return; + } + + const urlRegex = new RegExp( + "^((http|https):\\/\\/)?" + // optional protocol + "((([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\\.)+" + // subdomain + "([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])" + // domain name + "|([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}))" + // OR ip (v4) address + "(\\:[0-9]+)?$", // port + "i" // case-insensitive + ); + + const isValidUrl = (string) => urlRegex.test(string); + console.log(url, isValidUrl(url)); + if (!isValidUrl(url)) { + res.status(400); + + res.send({ + isValid: false, + errorMessage: "Invalid URL", + }); + return; + } + + var _url = url; + _url = _url.replace(/\/web\/index\.html#!\/home\.html$/, ""); + if (!/^https?:\/\//i.test(url)) { + _url = "http://" + url; + } + _url = _url.replace(/\/$/, "") + "/system/configuration"; + + const validation = await Jellyfin.validateSettings(_url, apikey); + + res.send(validation); +}); + module.exports = router; diff --git a/backend/server.js b/backend/server.js index 2b6ab23..8ade570 100644 --- a/backend/server.js +++ b/backend/server.js @@ -19,10 +19,10 @@ const knexConfig = require("./migrations"); const authRouter = require("./routes/auth"); const apiRouter = require("./routes/api"); const proxyRouter = require("./routes/proxy"); -const syncRouter = require("./routes/sync"); +const { router: syncRouter } = require("./routes/sync"); const statsRouter = require("./routes/stats"); -const backupRouter = require("./routes/backup"); -const logRouter = require("./routes/logging"); +const { router: backupRouter } = require("./routes/backup"); +const { router: logRouter } = require("./routes/logging"); const utilsRouter = require("./routes/utils"); // tasks @@ -61,16 +61,16 @@ app.use("/proxy", proxyRouter, () => { app.use("/api", authenticate, apiRouter, () => { /* #swagger.tags = ['API']*/ }); // mount the API router at /api, with JWT middleware -app.use("/sync", authenticate, syncRouter.router, () => { +app.use("/sync", authenticate, syncRouter, () => { /* #swagger.tags = ['Sync']*/ }); // mount the API router at /sync, with JWT middleware app.use("/stats", authenticate, statsRouter, () => { /* #swagger.tags = ['Stats']*/ }); // mount the API router at /stats, with JWT middleware -app.use("/backup", authenticate, backupRouter.router, () => { +app.use("/backup", authenticate, backupRouter, () => { /* #swagger.tags = ['Backup']*/ }); // mount the API router at /backup, with JWT middleware -app.use("/logs", authenticate, logRouter.router, () => { +app.use("/logs", authenticate, logRouter, () => { /* #swagger.tags = ['Logs']*/ }); // mount the API router at /logs, with JWT middleware app.use("/utils", authenticate, utilsRouter, () => { diff --git a/backend/swagautogen.js b/backend/swagautogen.js index c3c24e9..3520a8e 100644 --- a/backend/swagautogen.js +++ b/backend/swagautogen.js @@ -1,55 +1,58 @@ -const swaggerAutogen = require('swagger-autogen')(); +const swaggerAutogen = require("swagger-autogen")(); -const outputFile = './swagger.json'; -const endpointsFiles = ['./server.js']; +const outputFile = "./swagger.json"; +const endpointsFiles = ["./server.js"]; const config = { - info: { - title: 'Jellystat API Documentation', - description: '', + info: { + title: "Jellystat API Documentation", + description: "", + }, + tags: [ + { + name: "API", + description: "Jellystat API Endpoints", }, - tags: [ - { - name: 'API', - description: 'Jellystat API Endpoints', - }, - { - name: 'Auth', - description: 'Jellystat Auth Endpoints', - }, - { - name: 'Proxy', - description: 'Jellyfin Proxied Endpoints', - }, - { - name: 'Stats', - description: 'Jellystat Statisitc Endpoints', - }, - { - name: 'Backup', - description: 'Jellystat Backup/Restore Endpoints', - }, - { - name: 'Logs', - description: 'Jellystat Log Endpoints', - }, - ], - host: '', - schemes: ['http', 'https'], - securityDefinitions: { - apiKey: { - type: 'apiKey', - name: 'x-api-token', - in: 'header', - }, + { + name: "Auth", + description: "Jellystat Auth Endpoints", }, - security: [ - { - apiKey: [], - }, - ], + { + name: "Proxy", + description: "Jellyfin Proxied Endpoints", + }, + { + name: "Stats", + description: "Jellystat Statisitc Endpoints", + }, + { + name: "Sync", + description: "Jellystat Sync Endpoints", + }, + { + name: "Backup", + description: "Jellystat Backup/Restore Endpoints", + }, + { + name: "Logs", + description: "Jellystat Log Endpoints", + }, + ], + host: "", + schemes: ["http", "https"], + securityDefinitions: { + apiKey: { + type: "apiKey", + name: "x-api-token", + in: "header", + }, + }, + security: [ + { + apiKey: [], + }, + ], }; module.exports = config; - swaggerAutogen(outputFile, endpointsFiles, config); diff --git a/backend/swagger.json b/backend/swagger.json index 7ac9b19..83b8200 100644 --- a/backend/swagger.json +++ b/backend/swagger.json @@ -24,6 +24,10 @@ "name": "Stats", "description": "Jellystat Statisitc Endpoints" }, + { + "name": "Sync", + "description": "Jellystat Sync Endpoints" + }, { "name": "Backup", "description": "Jellystat Backup/Restore Endpoints" @@ -90,6 +94,9 @@ "responses": { "200": { "description": "OK" + }, + "500": { + "description": "Internal Server Error" } } } @@ -130,6 +137,42 @@ } } }, + "/auth/configSetup": { + "post": { + "tags": [ + "Auth" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "schema": { + "type": "object", + "properties": { + "JF_HOST": { + "example": "any" + }, + "JF_API_KEY": { + "example": "any" + } + } + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, "/proxy/web/assets/img/devices/": { "get": { "tags": [ @@ -312,6 +355,39 @@ } } }, + "/proxy/validateSettings": { + "post": { + "tags": [ + "Proxy" + ], + "description": "", + "parameters": [ + { + "name": "body", + "in": "body", + "schema": { + "type": "object", + "properties": { + "url": { + "example": "any" + }, + "apikey": { + "example": "any" + } + } + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + }, "/api/getconfig": { "get": { "tags": [ @@ -1874,7 +1950,7 @@ } } }, - "/api/validateSettings": { + "/api/deletePlaybackActivity": { "post": { "tags": [ "API" @@ -1902,10 +1978,7 @@ "schema": { "type": "object", "properties": { - "url": { - "example": "any" - }, - "apikey": { + "ids": { "example": "any" } } @@ -1927,6 +2000,9 @@ }, "404": { "description": "Not Found" + }, + "503": { + "description": "Service Unavailable" } } } diff --git a/src/pages/setup.jsx b/src/pages/setup.jsx index 14a1c29..61b5c52 100644 --- a/src/pages/setup.jsx +++ b/src/pages/setup.jsx @@ -38,21 +38,18 @@ function Setup() { } async function validateSettings(_url, _apikey) { - const result = await axios.post( - "/api/validateSettings", - { + const result = await axios + .post("/proxy/validateSettings", { url: _url, apikey: _apikey, - }, - { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - } - ); + }) + .catch((error) => { + return error.response; + }); let data = result.data; + console.log(result); + console.log(data); return { isValid: data.isValid, errorMessage: data.errorMessage }; } @@ -70,12 +67,7 @@ function Setup() { // Send a POST request to /api/setconfig/ with the updated configuration axios - .post("/api/setconfig/", formValues, { - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", - }, - }) + .post("/auth/configSetup/", formValues) .then(async () => { setsubmitButtonText(i18next.t("SETTINGS_SAVED")); setProcessing(false);