diff --git a/package-lock.json b/package-lock.json index 257045fc7..949829051 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "classnames": "^2.5.1", "codemirror": "^5.65.6", "cookie-parser": "^1.4.7", + "cors": "^2.8.5", "create-react-class": "^15.7.0", "dedent-tabs": "^0.10.3", "dompurify": "^3.2.3", @@ -4805,6 +4806,19 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", diff --git a/package.json b/package.json index 17d1b5533..690fd7312 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "classnames": "^2.5.1", "codemirror": "^5.65.6", "cookie-parser": "^1.4.7", + "cors": "^2.8.5", "create-react-class": "^15.7.0", "dedent-tabs": "^0.10.3", "dompurify": "^3.2.3", diff --git a/server/app.js b/server/app.js index 8a2e17bbd..39086cc21 100644 --- a/server/app.js +++ b/server/app.js @@ -2,7 +2,7 @@ // Set working directory to project root import { dirname } from 'path'; import { fileURLToPath } from 'url'; -import packageJSON from './../package.json' with { type: "json" }; +import packageJSON from './../package.json' with { type: 'json' }; const __dirname = dirname(fileURLToPath(import.meta.url)); process.chdir(`${__dirname}/..`); @@ -26,7 +26,7 @@ import serveCompressedStaticAssets from './static-assets.mv.js'; import sanitizeFilename from 'sanitize-filename'; import asyncHandler from 'express-async-handler'; import templateFn from '../client/template.js'; -import {model as HomebrewModel } from './homebrew.model.js'; +import { model as HomebrewModel } from './homebrew.model.js'; import { DEFAULT_BREW } from './brewDefaults.js'; import { splitTextStyleAndMetadata } from '../shared/helpers.js'; @@ -47,7 +47,7 @@ const sanitizeBrew = (brew, accessType)=>{ return brew; }; -app.set('trust proxy', 1 /* number of proxies between user and server */) +app.set('trust proxy', 1 /* number of proxies between user and server */); app.use('/', serveCompressedStaticAssets(`build`)); app.use(contentNegotiation); @@ -55,14 +55,51 @@ app.use(bodyParser.json({ limit: '25mb' })); app.use(cookieParser()); app.use(forceSSL); +import cors from 'cors'; + +const nodeEnv = config.get('node_env'); +const isLocalEnvironment = config.get('local_environments').includes(nodeEnv); + +const corsOptions = { + origin : (origin, callback)=>{ + + const allowedOrigins = [ + 'https://homebrewery.naturalcrit.com', + 'https://naturalcrit.com', + 'https://naturalcrit-stage.herokuapp.com', + 'https://homebrewery-stage.herokuapp.com', + ]; + + if(isLocalEnvironment) { + allowedOrigins.push('http://localhost:8000', 'http://localhost:8010'); + } + + const herokuRegex = /^https:\/\/(?:homebrewery-pr-\d+\.herokuapp\.com|naturalcrit-pr-\d+\.herokuapp\.com)$/; // Matches any Heroku app + + if(!origin || allowedOrigins.includes(origin) || herokuRegex.test(origin)) { + callback(null, true); + } else { + console.log(origin, 'not allowed'); + callback(new Error('Not allowed by CORS, if you think this is an error, please contact us')); + } + }, + methods : ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + credentials : true, +}; + +app.use(cors(corsOptions)); + //Account Middleware app.use((req, res, next)=>{ + console.log('passing through acc middleware, checking for cookies now: does cookies exist? ', !!req.cookies, ', ok, does the session cookie exist? ', !!req.cookies.nc_session); if(req.cookies && req.cookies.nc_session){ try { req.account = jwt.decode(req.cookies.nc_session, config.get('secret')); //console.log("Just loaded up JWT from cookie:"); //console.log(req.account); - } catch (e){} + } catch (e){ + console.log(e); + } } req.config = { @@ -273,7 +310,7 @@ app.get('/user/:username', async (req, res, next)=>{ console.log(err); }); - brews.forEach(brew => brew.stubbed = true); //All brews from MongoDB are "stubbed" + brews.forEach((brew)=>brew.stubbed = true); //All brews from MongoDB are "stubbed" if(ownAccount && req?.account?.googleId){ const auth = await GoogleActions.authCheck(req.account, res); @@ -312,6 +349,37 @@ app.get('/user/:username', async (req, res, next)=>{ return next(); }); +//Change author name on brews +app.put('/api/user/rename', async (req, res)=>{ + const { username, newUsername } = req.body; + + //this next logs will be removed in a next PR, as i need to get this live to test if req.account is created when passing the request from naturalcrit.com + + console.log(`is user ${req.account.username} equal to ${username}? ${req.account.username === username} ${req.account.username === username && 'then add the damn auth for renaming!'}`); + console.log('renaming'); + + if(!username || !newUsername) { + return res.status(400).json({ error: 'Username and newUsername are required.' }); + } + try { + const brews = await HomebrewModel.getByUser(username, true, ['authors']); + const renamePromises = brews.map(async (brew)=>{ + const updatedAuthors = brew.authors.map((author)=>author === username ? newUsername : author + ); + return HomebrewModel.updateOne( + { _id: brew._id }, + { $set: { authors: updatedAuthors } } + ); + }); + await Promise.all(renamePromises); + + return res.json({ success: true, message: `Brews for ${username} renamed to ${newUsername}.` }); + } catch (error) { + console.error('Error renaming brews:', error); + return res.status(500).json({ error: 'Failed to rename brews.' }); + } +}); + //Edit Page app.get('/edit/:id', asyncHandler(getBrew('edit')), asyncHandler(async(req, res, next)=>{ req.brew = req.brew.toObject ? req.brew.toObject() : req.brew; @@ -399,7 +467,7 @@ app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, r app.get('/account', asyncHandler(async (req, res, next)=>{ const data = {}; data.title = 'Account Information Page'; - + if(!req.account) { res.set('WWW-Authenticate', 'Bearer realm="Authorization Required"'); const error = new Error('No valid account'); @@ -413,7 +481,7 @@ app.get('/account', asyncHandler(async (req, res, next)=>{ let googleCount = []; if(req.account) { if(req.account.googleId) { - auth = await GoogleActions.authCheck(req.account, res, false) + auth = await GoogleActions.authCheck(req.account, res, false); googleCount = await GoogleActions.listGoogleBrews(auth) .catch((err)=>{ @@ -448,8 +516,6 @@ app.get('/account', asyncHandler(async (req, res, next)=>{ return next(); })); -const nodeEnv = config.get('node_env'); -const isLocalEnvironment = config.get('local_environments').includes(nodeEnv); // Local only if(isLocalEnvironment){ // Login @@ -477,8 +543,8 @@ app.get('/vault', asyncHandler(async(req, res, next)=>{ //Send rendered page app.use(asyncHandler(async (req, res, next)=>{ - if (!req.route) return res.redirect('/'); // Catch-all for invalid routes - + if(!req.route) return res.redirect('/'); // Catch-all for invalid routes + const page = await renderPage(req, res); if(!page) return; res.send(page); diff --git a/server/homebrew.api.js b/server/homebrew.api.js index a75887742..159c08b47 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -467,12 +467,11 @@ const api = { } }; -router.use('/api', checkClientVersion); -router.post('/api', asyncHandler(api.newBrew)); -router.put('/api/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew)); -router.put('/api/update/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew)); -router.delete('/api/:id', asyncHandler(api.deleteBrew)); -router.get('/api/remove/:id', asyncHandler(api.deleteBrew)); +router.post('/api', checkClientVersion, asyncHandler(api.newBrew)); +router.put('/api/:id', checkClientVersion, asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew)); +router.put('/api/update/:id', checkClientVersion, asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew)); +router.delete('/api/:id', checkClientVersion, asyncHandler(api.deleteBrew)); +router.get('/api/remove/:id', checkClientVersion, asyncHandler(api.deleteBrew)); router.get('/api/theme/:renderer/:id', asyncHandler(api.getThemeBundle)); export default api; \ No newline at end of file diff --git a/server/middleware/check-client-version.js b/server/middleware/check-client-version.js index 001995f4e..45d868a5a 100644 --- a/server/middleware/check-client-version.js +++ b/server/middleware/check-client-version.js @@ -1,10 +1,10 @@ -import packageJSON from '../../package.json' with { type: "json" }; -const version = packageJSON.version; +import packageJSON from '../../package.json' with { type: 'json' }; export default (req, res, next)=>{ const userVersion = req.get('Homebrewery-Version'); + const version = packageJSON.version; - if(userVersion != version) { + if(userVersion !== version) { return res.status(412).send({ message : `Client version ${userVersion} is out of date. Please save your changes elsewhere and refresh to pick up client version ${version}.` }); @@ -12,3 +12,4 @@ export default (req, res, next)=>{ next(); }; +