diff --git a/index.js b/index.js index 7379322e8..315bd3bf5 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,5 @@ +const Path = require('path') +module.exports = {} const optionDefinitions = [ { name: 'config', alias: 'c', type: String }, { name: 'metadata', alias: 'm', type: String }, @@ -12,7 +14,6 @@ const optionDefinitions = [ const commandLineArgs = require('./server/libs/commandLineArgs') const options = commandLineArgs(optionDefinitions) -const Path = require('path') process.env.NODE_ENV = options.dev ? 'development' : process.env.NODE_ENV || 'production' const server = require('./server/Server') @@ -31,6 +32,8 @@ if (isDev || options['prod-with-dev-env']) { 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 + if (devEnv.RouterBasePath !== undefined) process.env.ROUTER_BASE_PATH = devEnv.RouterBasePath + if (devEnv.RecommendationsEnabled) process.env.RECOMMENDATIONS_ENABLED = 'true' process.env.SOURCE = 'local' process.env.ROUTER_BASE_PATH = devEnv.RouterBasePath ?? '/audiobookshelf' } diff --git a/server/Server.js b/server/Server.js index 457aa61ab..28ea52a94 100644 --- a/server/Server.js +++ b/server/Server.js @@ -38,7 +38,7 @@ const ApiCacheManager = require('./managers/ApiCacheManager') const BinaryManager = require('./managers/BinaryManager') const ShareManager = require('./managers/ShareManager') const LibraryScanner = require('./scanner/LibraryScanner') - +const buildRecommendationsRouter = require('./routers/recommendations') //Import the main Passport and Express-Session library const passport = require('passport') const expressSession = require('express-session') @@ -229,10 +229,6 @@ class Server { res.setHeader('Content-Security-Policy', "frame-ancestors 'self'") } - // Security: Prevent referrer leakage to protect against token exposure - // Using 'no-referrer' to completely prevent token leakage in referer headers - res.setHeader('Referrer-Policy', 'no-referrer') - /** * @temporary * This is necessary for the ebook & cover API endpoint in the mobile apps @@ -244,8 +240,8 @@ class Server { * Running in development allows cors to allow testing the mobile apps in the browser * or env variable ALLOW_CORS = '1' */ - if (global.AllowCors || Logger.isDev || req.path.match(/\/api\/items\/([a-z0-9-]{36})\/(ebook|cover)(\/[0-9]+)?/) || global.ServerSettings.allowedOrigins?.length) { - const allowedOrigins = ['capacitor://localhost', 'http://localhost', ...(global.ServerSettings.allowedOrigins ? global.ServerSettings.allowedOrigins : [])] + if (global.AllowCors || Logger.isDev || req.path.match(/\/api\/items\/([a-z0-9-]{36})\/(ebook|cover)(\/[0-9]+)?/)) { + const allowedOrigins = ['capacitor://localhost', 'http://localhost'] if (global.AllowCors || Logger.isDev || allowedOrigins.some((o) => o === req.get('origin'))) { res.header('Access-Control-Allow-Origin', req.get('origin')) res.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE, OPTIONS') @@ -321,6 +317,8 @@ class Server { // Static folder router.use(express.static(Path.join(global.appRoot, 'static'))) + router.use('/api/recommendations', this.auth.ifAuthNeeded(this.authMiddleware.bind(this)), buildRecommendationsRouter(this.auth)) + // RSS Feed temp route router.get('/feed/:slug', (req, res) => { Logger.info(`[Server] Requesting rss feed ${req.params.slug}`) diff --git a/server/migrations/2024092401-create-book-recommendations.js b/server/migrations/2024092401-create-book-recommendations.js new file mode 100644 index 000000000..a37471713 --- /dev/null +++ b/server/migrations/2024092401-create-book-recommendations.js @@ -0,0 +1,22 @@ +'use strict' +module.exports = { + up: async (qi, Sequelize) => { + await qi.createTable('book_recommendations', { + id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, + bookId: { type: Sequelize.STRING, allowNull: false }, + recommenderUserId: { type: Sequelize.STRING, allowNull: false }, // string user id + recipientUserId: { type: Sequelize.STRING, allowNull: true }, // string user id + tagId: { type: Sequelize.INTEGER, allowNull: false }, + note: { type: Sequelize.TEXT, allowNull: true }, + visibility: { type: Sequelize.ENUM('public', 'recipient-only'), allowNull: false, defaultValue: 'public' }, + createdAt: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.fn('NOW') }, + updatedAt: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.fn('NOW') } + }) + await qi.addIndex('book_recommendations', ['bookId', 'tagId']) + await qi.addIndex('book_recommendations', ['recipientUserId']) + await qi.addIndex('book_recommendations', ['recommenderUserId', 'createdAt']) + }, + down: async (qi) => { + await qi.dropTable('book_recommendations') + } +}