diff --git a/client/admin/brewUtils/brewLookup/brewLookup.jsx b/client/admin/brewUtils/brewLookup/brewLookup.jsx index 50a2f2015..e5b585ced 100644 --- a/client/admin/brewUtils/brewLookup/brewLookup.jsx +++ b/client/admin/brewUtils/brewLookup/brewLookup.jsx @@ -1,3 +1,5 @@ +require('./brewLookup.less'); + const React = require('react'); const createClass = require('create-react-class'); const cx = require('classnames'); @@ -12,22 +14,43 @@ const BrewLookup = createClass({ }, getInitialState() { return { - query : '', - foundBrew : null, - searching : false, - error : null + query : '', + foundBrew : null, + searching : false, + error : null, + scriptCount : 0 }; }, handleChange(e){ this.setState({ query: e.target.value }); }, lookup(){ - this.setState({ searching: true, error: null }); + this.setState({ searching: true, error: null, scriptCount: 0 }); request.get(`/admin/lookup/${this.state.query}`) - .then((res)=>this.setState({ foundBrew: res.body })) + .then((res)=>{ + const foundBrew = res.body; + const scriptCheck = foundBrew?.text.match(/(<\/?s)cript/g); + this.setState({ + foundBrew : foundBrew, + scriptCount : scriptCheck?.length || 0, + }); + }) .catch((err)=>this.setState({ error: err })) - .finally(()=>this.setState({ searching: false })); + .finally(()=>{ + this.setState({ + searching : false + }); + }); + }, + + async cleanScript(){ + if(!this.state.foundBrew?.shareId) return; + + await request.put(`/admin/clean/script/${this.state.foundBrew.shareId}`) + .catch((err)=>{ this.setState({ error: err }); return; }); + + this.lookup(); }, renderFoundBrew(){ @@ -46,12 +69,23 @@ const BrewLookup = createClass({
Share Link
/share/{brew.shareId}
+
Created Time
+
{brew.createdAt ? Moment(brew.createdAt).toLocaleString() : 'No creation date'}
+
Last Updated
{Moment(brew.updatedAt).fromNow()}
Num of Views
{brew.views}
+ +
SCRIPT tags detected
+
{this.state.scriptCount}
+ {this.state.scriptCount > 0 && +
+ +
+ } ; }, diff --git a/client/admin/brewUtils/brewLookup/brewLookup.less b/client/admin/brewUtils/brewLookup/brewLookup.less new file mode 100644 index 000000000..da15e3a64 --- /dev/null +++ b/client/admin/brewUtils/brewLookup/brewLookup.less @@ -0,0 +1,6 @@ +.brewLookup { + .cleanButton { + display : inline-block; + width : 100%; + } +} \ No newline at end of file diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx index 1c45269cf..4685775b9 100644 --- a/client/homebrew/brewRenderer/brewRenderer.jsx +++ b/client/homebrew/brewRenderer/brewRenderer.jsx @@ -161,7 +161,8 @@ const BrewRenderer = (props)=>{ renderedPages.length = 0; // Render currently-edited page first so cross-page effects (variables, links) can propagate out first - renderedPages[props.currentEditorCursorPageNum - 1] = renderPage(rawPages[props.currentEditorCursorPageNum - 1], props.currentEditorCursorPageNum - 1); + if(rawPages.length > props.currentEditorCursorPageNum -1) + renderedPages[props.currentEditorCursorPageNum - 1] = renderPage(rawPages[props.currentEditorCursorPageNum - 1], props.currentEditorCursorPageNum - 1); _.forEach(rawPages, (page, index)=>{ if((isInView(index) || !renderedPages[index]) && typeof window !== 'undefined'){ diff --git a/package-lock.json b/package-lock.json index ae99df672..a9b64c1d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,11 +21,11 @@ "cookie-parser": "^1.4.7", "create-react-class": "^15.7.0", "dedent-tabs": "^0.10.3", - "dompurify": "^3.1.7", + "dompurify": "^3.2.0", "expr-eval": "^2.0.2", "express": "^4.21.1", "express-async-handler": "^1.2.0", - "express-static-gzip": "2.1.8", + "express-static-gzip": "2.2.0", "fs-extra": "11.2.0", "idb-keyval": "^6.2.1", "js-yaml": "^4.1.0", @@ -33,13 +33,13 @@ "less": "^3.13.1", "lodash": "^4.17.21", "marked": "11.2.0", - "marked-emoji": "^1.4.2", + "marked-emoji": "^1.4.3", "marked-extended-tables": "^1.0.10", "marked-gfm-heading-id": "^3.2.0", "marked-smartypants-lite": "^1.0.2", "markedLegacy": "npm:marked@^0.3.19", "moment": "^2.30.1", - "mongoose": "^8.7.3", + "mongoose": "^8.8.1", "nanoid": "3.3.4", "nconf": "^0.12.1", "react": "^18.3.1", @@ -4327,9 +4327,9 @@ } }, "node_modules/bson": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz", - "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.9.0.tgz", + "integrity": "sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==", "engines": { "node": ">=16.20.1" } @@ -5455,9 +5455,9 @@ } }, "node_modules/dompurify": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz", - "integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.0.tgz", + "integrity": "sha512-AMdOzK44oFWqHEi0wpOqix/fUNY707OmoeFDnbi3Q5I8uOpy21ufUA5cDJPr0bosxrflOVD/H2DMSvuGKJGfmQ==" }, "node_modules/duplexer2": { "version": "0.1.4", @@ -6290,10 +6290,11 @@ "license": "MIT" }, "node_modules/express-static-gzip": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/express-static-gzip/-/express-static-gzip-2.1.8.tgz", - "integrity": "sha512-g8tiJuI9Y9Ffy59ehVXvqb0hhP83JwZiLxzanobPaMbkB5qBWA8nuVgd+rcd5qzH3GkgogTALlc0BaADYwnMbQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/express-static-gzip/-/express-static-gzip-2.2.0.tgz", + "integrity": "sha512-4ZQ0pHX0CAauxmzry2/8XFLM6aZA4NBvg9QezSlsEO1zLnl7vMFa48/WIcjzdfOiEUS4S1npPPKP2NHHYAp6qg==", "dependencies": { + "parseurl": "^1.3.3", "serve-static": "^1.16.2" } }, @@ -10487,11 +10488,11 @@ } }, "node_modules/marked-emoji": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/marked-emoji/-/marked-emoji-1.4.2.tgz", - "integrity": "sha512-2sP+bp2z76dwbILzQ7ijy2PyjjAJR3iAZCzaNGThD2UijFUBeidkn6MoCdX/j47tPIcWt9nwnjqRQPd01ZrfdA==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/marked-emoji/-/marked-emoji-1.4.3.tgz", + "integrity": "sha512-HDZx1VOmzu7XT2QNKWfrHGbNRMTWKj9XD78yrcH1madD30HpGLMODPOmKr/e7CA7NKKXkpXXNdndQn++ysXmHg==", "peerDependencies": { - "marked": ">=4 <15" + "marked": ">=4 <16" } }, "node_modules/marked-extended-tables": { @@ -10865,13 +10866,13 @@ } }, "node_modules/mongoose": { - "version": "8.7.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.7.3.tgz", - "integrity": "sha512-Xl6+dzU5ZpEcDoJ8/AyrIdAwTY099QwpolvV73PIytpK13XqwllLq/9XeVzzLEQgmyvwBVGVgjmMrKbuezxrIA==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.8.1.tgz", + "integrity": "sha512-l7DgeY1szT98+EKU8GYnga5WnyatAu+kOQ2VlVX1Mxif6A0Umt0YkSiksCiyGxzx8SPhGe9a53ND1GD4yVDrPA==", "dependencies": { "bson": "^6.7.0", "kareem": "2.6.3", - "mongodb": "6.9.0", + "mongodb": "~6.10.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -10943,9 +10944,9 @@ } }, "node_modules/mongoose/node_modules/mongodb": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.9.0.tgz", - "integrity": "sha512-UMopBVx1LmEUbW/QE0Hw18u583PEDVQmUmVzzBRH0o/xtE9DBRA5ZYLOjpLIa03i8FXjzvQECJcqoMvCXftTUA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.10.0.tgz", + "integrity": "sha512-gP9vduuYWb9ZkDM546M+MP2qKVk5ZG2wPF63OvSRuUbqCR+11ZCAE1mOfllhlAG0wcoJY5yDL/rV3OmYEwXIzg==", "dependencies": { "@mongodb-js/saslprep": "^1.1.5", "bson": "^6.7.0", diff --git a/package.json b/package.json index a48423f50..08473ca05 100644 --- a/package.json +++ b/package.json @@ -98,11 +98,11 @@ "cookie-parser": "^1.4.7", "create-react-class": "^15.7.0", "dedent-tabs": "^0.10.3", - "dompurify": "^3.1.7", + "dompurify": "^3.2.0", "expr-eval": "^2.0.2", "express": "^4.21.1", "express-async-handler": "^1.2.0", - "express-static-gzip": "2.1.8", + "express-static-gzip": "2.2.0", "fs-extra": "11.2.0", "idb-keyval": "^6.2.1", "js-yaml": "^4.1.0", @@ -110,13 +110,13 @@ "less": "^3.13.1", "lodash": "^4.17.21", "marked": "11.2.0", - "marked-emoji": "^1.4.2", + "marked-emoji": "^1.4.3", "marked-extended-tables": "^1.0.10", "marked-gfm-heading-id": "^3.2.0", "marked-smartypants-lite": "^1.0.2", "markedLegacy": "npm:marked@^0.3.19", "moment": "^2.30.1", - "mongoose": "^8.7.3", + "mongoose": "^8.8.1", "nanoid": "3.3.4", "nconf": "^0.12.1", "react": "^18.3.1", diff --git a/server/admin.api.js b/server/admin.api.js index fd724b9f1..bc179ff7b 100644 --- a/server/admin.api.js +++ b/server/admin.api.js @@ -5,6 +5,10 @@ const Moment = require('moment'); const templateFn = require('../client/template.js'); const zlib = require('zlib'); +const HomebrewAPI = require('./homebrew.api.js'); +const asyncHandler = require('express-async-handler'); +const { splitTextStyleAndMetadata } = require('../shared/helpers.js'); + process.env.ADMIN_USER = process.env.ADMIN_USER || 'admin'; process.env.ADMIN_PASS = process.env.ADMIN_PASS || 'password3'; @@ -66,23 +70,8 @@ router.post('/admin/cleanup', mw.adminOnly, (req, res)=>{ }); /* Searches for matching edit or share id, also attempts to partial match */ -router.get('/admin/lookup/:id', mw.adminOnly, async (req, res, next)=>{ - HomebrewModel.findOne({ - $or : [ - { editId: { $regex: req.params.id, $options: 'i' } }, - { shareId: { $regex: req.params.id, $options: 'i' } }, - ] - }).exec() - .then((brew)=>{ - if(!brew) // No document found - return res.status(404).json({ error: 'Document not found' }); - else - return res.json(brew); - }) - .catch((err)=>{ - console.error(err); - return res.status(500).json({ error: 'Internal Server Error' }); - }); +router.get('/admin/lookup/:id', mw.adminOnly, asyncHandler(HomebrewAPI.getBrew('admin', false)), async (req, res, next)=>{ + return res.json(req.brew); }); /* Find 50 brews that aren't compressed yet */ @@ -100,6 +89,25 @@ router.get('/admin/finduncompressed', mw.adminOnly, (req, res)=>{ }); }); +/* Cleans `` from the "text" field of a brew */ +router.put('/admin/clean/script/:id', asyncHandler(HomebrewAPI.getBrew('admin', false)), async (req, res)=>{ + console.log(`[ADMIN] Cleaning script tags from ShareID ${req.params.id}`); + + function cleanText(text){return text.replaceAll(/(<\/?s)cript/gi, '');}; + + const brew = req.brew; + + const properties = ['text', 'description', 'title']; + properties.forEach((property)=>{ + brew[property] = cleanText(brew[property]); + }); + + splitTextStyleAndMetadata(brew); + + req.body = brew; + + return await HomebrewAPI.updateBrew(req, res); +}); /* Compresses the "text" field of a brew to binary */ router.put('/admin/compress/:id', (req, res)=>{ @@ -144,7 +152,7 @@ router.get('/admin/notification/all', async (req, res, next)=>{ try { const notifications = await NotificationModel.getAll(); return res.json(notifications); - + } catch (error) { console.log('Error getting all notifications: ', error.message); return res.status(500).json({ message: error.message }); diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 213b341ca..e156f3c85 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -87,8 +87,18 @@ const api = { // Get relevant IDs for the brew const { id, googleId } = api.getId(req); + const accessMap = { + edit : { editId: id }, + share : { shareId: id }, + admin : { + $or : [ + { editId: id }, + { shareId: id }, + ] } + }; + // Try to find the document in the Homebrewery database -- if it doesn't exist, that's fine. - let stub = await HomebrewModel.get(accessType === 'edit' ? { editId: id } : { shareId: id }) + let stub = await HomebrewModel.get(accessMap[accessType]) .catch((err)=>{ if(googleId) { console.warn(`Unable to find document stub for ${accessType}Id ${id}`); @@ -295,9 +305,8 @@ const api = { req.params.id = currentTheme.theme; req.params.renderer = currentTheme.renderer; - } + } else { //=== Static Themes ===// - else { const localSnippets = `${req.params.renderer}_${req.params.id}`; // Just log the name for loading on client const localStyle = `@import url(\"/themes/${req.params.renderer}/${req.params.id}/style.css\");`; completeSnippets.push(localSnippets);