From a992400d6abd9ca96e9b637e987ec46e4fbc5a08 Mon Sep 17 00:00:00 2001 From: advplyr Date: Mon, 23 Jun 2025 16:56:08 -0500 Subject: [PATCH] Add ENV REACT_CLIENT_PATH to target a Nextjs frontend instead of Nuxt --- index.js | 1 + server/Auth.js | 13 ++++++++- server/Server.js | 74 ++++++++++++++++++++++++++++-------------------- 3 files changed, 57 insertions(+), 31 deletions(-) diff --git a/index.js b/index.js index 2839c238..7fcd90fc 100644 --- a/index.js +++ b/index.js @@ -28,6 +28,7 @@ if (isDev) { if (devEnv.SkipBinariesCheck) process.env.SKIP_BINARIES_CHECK = '1' if (devEnv.AllowIframe) process.env.ALLOW_IFRAME = '1' if (devEnv.BackupPath) process.env.BACKUP_PATH = devEnv.BackupPath + if (devEnv.ReactClientPath) process.env.REACT_CLIENT_PATH = devEnv.ReactClientPath process.env.SOURCE = 'local' process.env.ROUTER_BASE_PATH = devEnv.RouterBasePath ?? '/audiobookshelf' } diff --git a/server/Auth.js b/server/Auth.js index ba72947a..4e76ee33 100644 --- a/server/Auth.js +++ b/server/Auth.js @@ -442,7 +442,17 @@ class Auth { // Local strategy login route (takes username and password) router.post('/login', passport.authenticate('local'), async (req, res) => { // return the user login response json if the login was successfull - res.json(await this.getUserLoginResponsePayload(req.user)) + const userResponse = await this.getUserLoginResponsePayload(req.user) + + // Experimental Next.js client uses bearer token in cookies + res.cookie('auth_token', userResponse.user.token, { + httpOnly: true, + secure: req.secure || req.get('x-forwarded-proto') === 'https', + sameSite: 'strict', + maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days + }) + + res.json(userResponse) }) // openid strategy login route (this redirects to the configured openid login provider) @@ -718,6 +728,7 @@ class Auth { const authMethod = req.cookies.auth_method res.clearCookie('auth_method') + res.clearCookie('auth_token') let logoutUrl = null diff --git a/server/Server.js b/server/Server.js index 5c6f3c16..22a53a3a 100644 --- a/server/Server.js +++ b/server/Server.js @@ -220,6 +220,7 @@ class Server { async start() { Logger.info('=== Starting Server ===') + this.initProcessEventListeners() await this.init() @@ -281,6 +282,7 @@ class Server { await this.auth.initPassportJs() const router = express.Router() + // if RouterBasePath is set, modify all requests to include the base path app.use((req, res, next) => { const urlStartsWithRouterBasePath = req.url.startsWith(global.RouterBasePath) @@ -313,10 +315,6 @@ class Server { router.use('/hls', this.hlsRouter.router) router.use('/public', this.publicRouter.router) - // Static path to generated nuxt - const distPath = Path.join(global.appRoot, '/client/dist') - router.use(express.static(distPath)) - // Static folder router.use(express.static(Path.join(global.appRoot, 'static'))) @@ -336,32 +334,6 @@ class Server { // Auth routes await this.auth.initAuthRoutes(router) - // Client dynamic routes - const dynamicRoutes = [ - '/item/:id', - '/author/:id', - '/audiobook/:id/chapters', - '/audiobook/:id/edit', - '/audiobook/:id/manage', - '/library/:library', - '/library/:library/search', - '/library/:library/bookshelf/:id?', - '/library/:library/authors', - '/library/:library/narrators', - '/library/:library/stats', - '/library/:library/series/:id?', - '/library/:library/podcast/search', - '/library/:library/podcast/latest', - '/library/:library/podcast/download-queue', - '/config/users/:id', - '/config/users/:id/sessions', - '/config/item-metadata-utils/:id', - '/collection/:id', - '/playlist/:id', - '/share/:slug' - ] - dynamicRoutes.forEach((route) => router.get(route, (req, res) => res.sendFile(Path.join(distPath, 'index.html')))) - router.post('/init', (req, res) => { if (Database.hasRootUser) { Logger.error(`[Server] attempt to init server when server already has a root user`) @@ -392,6 +364,48 @@ class Server { }) router.get('/healthcheck', (req, res) => res.sendStatus(200)) + const ReactClientPath = process.env.REACT_CLIENT_PATH + if (!ReactClientPath) { + // Static path to generated nuxt + const distPath = Path.join(global.appRoot, '/client/dist') + router.use(express.static(distPath)) + + // Client dynamic routes + const dynamicRoutes = [ + '/item/:id', + '/author/:id', + '/audiobook/:id/chapters', + '/audiobook/:id/edit', + '/audiobook/:id/manage', + '/library/:library', + '/library/:library/search', + '/library/:library/bookshelf/:id?', + '/library/:library/authors', + '/library/:library/narrators', + '/library/:library/stats', + '/library/:library/series/:id?', + '/library/:library/podcast/search', + '/library/:library/podcast/latest', + '/library/:library/podcast/download-queue', + '/config/users/:id', + '/config/users/:id/sessions', + '/config/item-metadata-utils/:id', + '/collection/:id', + '/playlist/:id', + '/share/:slug' + ] + dynamicRoutes.forEach((route) => router.get(route, (req, res) => res.sendFile(Path.join(distPath, 'index.html')))) + } else { + // This is for using the experimental Next.js client + Logger.info(`Using React client at ${ReactClientPath}`) + const nextPath = Path.join(ReactClientPath, 'node_modules/next') + const next = require(nextPath) + const nextApp = next({ dev: Logger.isDev, dir: ReactClientPath }) + const handle = nextApp.getRequestHandler() + await nextApp.prepare() + router.get('*', (req, res) => handle(req, res)) + } + const unixSocketPrefix = 'unix/' if (this.Host?.startsWith(unixSocketPrefix)) { const sockPath = this.Host.slice(unixSocketPrefix.length)