From 97ef6a2bd6377fe26b07cd773c0fe3d3a0172f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Wed, 14 Aug 2024 23:09:18 +0200 Subject: [PATCH 01/13] "Added cloning feature to metadata editor and share page, with corresponding backend model update" --- .../editor/metadataEditor/metadataEditor.jsx | 29 ++++++++++++- .../editor/metadataEditor/metadataEditor.less | 14 +++++++ client/homebrew/pages/sharePage/sharePage.jsx | 41 +++++++++++-------- server/homebrew.model.js | 4 +- 4 files changed, 68 insertions(+), 20 deletions(-) diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 0f1f6ad549..41eee53cbc 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -38,7 +38,8 @@ const MetadataEditor = createClass({ systems : [], renderer : 'legacy', theme : '5ePHB', - lang : 'en' + lang : 'en', + cloning : true, }, onChange : ()=>{}, reportError : ()=>{} @@ -102,6 +103,12 @@ const MetadataEditor = createClass({ } this.props.onChange(this.props.metadata, 'renderer'); }, + + handleCloning: function(e) { + const newMetadata = { ...this.props.metadata, cloning: e.target.checked }; + this.props.onChange(newMetadata, 'cloning'); + }, + handlePublish : function(val){ this.props.onChange({ ...this.props.metadata, @@ -312,6 +319,24 @@ const MetadataEditor = createClass({ ; }, + renderCloning : function(){ + return
+ +
+ +
+ +
+ + }, + render : function(){ return

Brew

@@ -379,6 +404,8 @@ const MetadataEditor = createClass({

Privacy

+ {this.renderCloning()} +
diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.less b/client/homebrew/editor/metadataEditor/metadataEditor.less index 27ebd88c26..e8e76eda14 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.less +++ b/client/homebrew/editor/metadataEditor/metadataEditor.less @@ -131,6 +131,20 @@ cursor : pointer; } } + + .cloning.field .value{ + label { + display : inline-flex; + align-items : center; + margin-right : 15px; + font-size : 0.7em; + font-weight : 800; + white-space : nowrap; + vertical-align : middle; + cursor : pointer; + user-select : none; + } + } .publish.field .value { position : relative; margin-bottom : 15px; diff --git a/client/homebrew/pages/sharePage/sharePage.jsx b/client/homebrew/pages/sharePage/sharePage.jsx index 9b4f9b73d6..dbcfcb0690 100644 --- a/client/homebrew/pages/sharePage/sharePage.jsx +++ b/client/homebrew/pages/sharePage/sharePage.jsx @@ -87,24 +87,29 @@ const SharePage = createClass({ - {this.props.brew.shareId && <> - - - - source - - - view - - {this.renderEditLink()} - - download - - - clone to new - - - } + {this.props.brew.shareId && ( + <> + + {this.props.brew.cloning !== false && ( + + + source + + + view + + {this.renderEditLink()} + + download + + + clone to new + + + )} + + )} + diff --git a/server/homebrew.model.js b/server/homebrew.model.js index c8db8fdccb..34423a3228 100644 --- a/server/homebrew.model.js +++ b/server/homebrew.model.js @@ -26,7 +26,9 @@ const HomebrewSchema = mongoose.Schema({ updatedAt : { type: Date, default: Date.now }, lastViewed : { type: Date, default: Date.now }, views : { type: Number, default: 0 }, - version : { type: Number, default: 1 } + version : { type: Number, default: 1 }, + + cloning : { type: Boolean, default:true} }, { versionKey: false }); HomebrewSchema.statics.increaseView = async function(query) { From a65a47aba098d0cb2bb73ea02ba1181079108ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Wed, 14 Aug 2024 23:24:49 +0200 Subject: [PATCH 02/13] "Added cloning disabled check to /source/:id, /download/:id, and /new/:id routes" --- server/app.js | 92 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 30 deletions(-) diff --git a/server/app.js b/server/app.js index b419c5cead..60aca982f7 100644 --- a/server/app.js +++ b/server/app.js @@ -165,41 +165,65 @@ app.get('/faq', async (req, res, next)=>{ }); //Source page -app.get('/source/:id', asyncHandler(getBrew('share')), (req, res)=>{ - const { brew } = req; - - const replaceStrings = { '&': '&', '<': '<', '>': '>' }; - let text = brew.text; - for (const replaceStr in replaceStrings) { - text = text.replaceAll(replaceStr, replaceStrings[replaceStr]); - } - text = `
${text}
`; - res.status(200).send(text); +app.get('/source/:id', asyncHandler(getBrew('share')), (req, res) => { + const { brew } = req; + + // Check if cloning is disabled + if (brew.cloning === false) { + const errorText = `This brew's author has disabled cloning, so its source is not publicly available.`; + return res.status(403).send(errorText); + } + + // HTML encode the text + const replaceStrings = { '&': '&', '<': '<', '>': '>' }; + let text = brew.text; + for (const replaceStr in replaceStrings) { + text = text.replaceAll(replaceStr, replaceStrings[replaceStr]); + } + text = `
${text}
`; + + // Send the response + res.status(200).send(text); }); //Download brew source page -app.get('/download/:id', asyncHandler(getBrew('share')), (req, res)=>{ - const { brew } = req; - sanitizeBrew(brew, 'share'); - const prefix = 'HB - '; - - const encodeRFC3986ValueChars = (str)=>{ - return ( - encodeURIComponent(str) - .replace(/[!'()*]/g, (char)=>{`%${char.charCodeAt(0).toString(16).toUpperCase()}`;}) - ); - }; - - let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', ''); - if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; }; - res.set({ - 'Cache-Control' : 'no-cache', - 'Content-Type' : 'text/plain', - 'Content-Disposition' : `attachment; filename*=UTF-8''${encodeRFC3986ValueChars(fileName)}.txt` - }); - res.status(200).send(brew.text); +app.get('/download/:id', asyncHandler(getBrew('share')), (req, res) => { + const { brew } = req; + sanitizeBrew(brew, 'share'); + + // Check if cloning is disabled + if (brew.cloning === false) { + const errorText = `This brew's author has disabled cloning, so its source is not available for download.`; + return res.status(403).send(errorText); + } + + const prefix = 'HB - '; + + // Function to encode characters according to RFC 3986 + const encodeRFC3986ValueChars = (str) => { + return encodeURIComponent(str) + .replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`); + }; + + // Sanitize and format filename + let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', ''); + if (!fileName || fileName.length === 0) { + fileName = `${prefix}-Untitled-Brew`; + } + + // Set headers for file download + res.set({ + 'Cache-Control': 'no-cache', + 'Content-Type': 'text/plain', + 'Content-Disposition': `attachment; filename*=UTF-8''${encodeRFC3986ValueChars(fileName)}.txt` + }); + + // Handle empty or undefined brew.text + const textToSend = brew.text || ''; + res.status(200).send(textToSend); }); + //User Page app.get('/user/:username', async (req, res, next)=>{ const ownAccount = req.account && (req.account.username == req.params.username); @@ -291,6 +315,14 @@ app.get('/edit/:id', asyncHandler(getBrew('edit')), asyncHandler(async(req, res, //New Page from ID app.get('/new/:id', asyncHandler(getBrew('share')), asyncHandler(async(req, res, next)=>{ sanitizeBrew(req.brew, 'share'); + + // Check if cloning is disabled + if (req.brew.cloning === false) { + console.log('error 403'); + const errorText = `This brew's author has disabled cloning, so its source is not available for download.`; + return res.status(403).send(errorText); + } + splitTextStyleAndMetadata(req.brew); const brew = { shareId : req.brew.shareId, From 89a891bd2417671906d5621795226ec3c379b57e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 15 Aug 2024 12:03:12 +0200 Subject: [PATCH 03/13] "Added SourcePage and download route, updated error handling for cloning disabled brews, and refactored source and download page logic" --- client/homebrew/homebrew.jsx | 3 + .../pages/errorPage/errors/errorIndex.js | 15 ++++ .../homebrew/pages/sourcePage/sourcePage.jsx | 69 ++++++++++++++++++ .../homebrew/pages/sourcePage/sourcePage.less | 35 +++++++++ server/app.js | 71 +++++++------------ 5 files changed, 146 insertions(+), 47 deletions(-) create mode 100644 client/homebrew/pages/sourcePage/sourcePage.jsx create mode 100644 client/homebrew/pages/sourcePage/sourcePage.less diff --git a/client/homebrew/homebrew.jsx b/client/homebrew/homebrew.jsx index 2226c4f3f9..310680b327 100644 --- a/client/homebrew/homebrew.jsx +++ b/client/homebrew/homebrew.jsx @@ -11,6 +11,7 @@ const SharePage = require('./pages/sharePage/sharePage.jsx'); const NewPage = require('./pages/newPage/newPage.jsx'); const ErrorPage = require('./pages/errorPage/errorPage.jsx'); const AccountPage = require('./pages/accountPage/accountPage.jsx'); +const SourcePage = require('./pages/sourcePage/sourcePage.jsx'); const WithRoute = (props)=>{ const params = useParams(); @@ -68,6 +69,8 @@ const Homebrew = createClass({ } /> } /> + } /> + } /> } /> } /> } /> diff --git a/client/homebrew/pages/errorPage/errors/errorIndex.js b/client/homebrew/pages/errorPage/errors/errorIndex.js index 7bf2caae1f..0a422602c9 100644 --- a/client/homebrew/pages/errorPage/errors/errorIndex.js +++ b/client/homebrew/pages/errorPage/errors/errorIndex.js @@ -148,6 +148,21 @@ const errorIndex = (props)=>{ **Requested access:** ${props.brew.accessType} **Brew ID:** ${props.brew.brewId}`, + + //Brew's cloning blocked + '23' : dedent` + ## This brew's cloning features have been disabled + + The author of this brew does not want other people using its contents, so viewing the source, + cloning the brew and downloading the text has been disabled. + + If you think this is a mistake, you may contact the author. + + : + + **Brew ID:** ${props.brew.brewId} + + **Brew Title:** ${props.brew.brewTitle}`, // Brew locked by Administrators error '100' : dedent` diff --git a/client/homebrew/pages/sourcePage/sourcePage.jsx b/client/homebrew/pages/sourcePage/sourcePage.jsx new file mode 100644 index 0000000000..eca98414d2 --- /dev/null +++ b/client/homebrew/pages/sourcePage/sourcePage.jsx @@ -0,0 +1,69 @@ +import React from 'react'; +import './sourcePage.less'; +const UIPage = require('../basePages/uiPage/uiPage.jsx'); + +const SourcePage = (props) => { + const { brew } = props; + + const sanitizeFilename = (filename) => { + return filename.replace(/[\/\\?%*:|"<>]/g, '_'); + }; + + const prefix = 'HB - '; + let fileName = sanitizeFilename(`${prefix}${brew.title}`).replace(/ /g, ''); + if (!fileName || fileName.length === 0) { + fileName = `${prefix}-Untitled-Brew`; + } + + // Function to encode text for HTML display + const encodeText = () => { + const replaceStrings = { '&': '&', '<': '<', '>': '>' }; + let text = brew.text || ''; + for (const replaceStr in replaceStrings) { + text = text.replace( + new RegExp(replaceStr, 'g'), + replaceStrings[replaceStr] + ); + } + return text; + }; + + // Function to handle downloading the text + const downloadText = () => { + const encodedText = encodeText(); + const blob = new Blob([encodedText], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${fileName}.txt`; // Set the filename for download + a.click(); // Trigger the download + URL.revokeObjectURL(url); // Clean up the URL object + }; + + const renderSourcePage = () => ( +
+
+ + +
+ + +
{encodeText()}
+
+
+ ); + + // Render the page + return {renderSourcePage()}; +}; + +module.exports = SourcePage; diff --git a/client/homebrew/pages/sourcePage/sourcePage.less b/client/homebrew/pages/sourcePage/sourcePage.less new file mode 100644 index 0000000000..ab430c9c3a --- /dev/null +++ b/client/homebrew/pages/sourcePage/sourcePage.less @@ -0,0 +1,35 @@ + +.source { + position : relative; + width : 100%; + height : fit-content; + padding : 20px 100px 20px 20px; + background : #E8DECD; + border : 1px solid #A27A3E; + border-radius : 5px; + + .buttons { + display:flex; + flex-direction: column; + position : absolute; + top : 0; + right : 0; + background : #00000024; + border-bottom : 1px solid #A27A3E; + border-left : 1px solid #A27A3E; + + border-top-right-radius : 5px; + border-bottom-left-radius : 5px; + + button { + color : black; + background : none; + + &:has(+button) { border-bottom : 1px solid #A27A3E; } + + &:hover { background : #00000024; } + + &:active { background : #2722194C; } + } + } +} \ No newline at end of file diff --git a/server/app.js b/server/app.js index 60aca982f7..542b4f7ae7 100644 --- a/server/app.js +++ b/server/app.js @@ -165,62 +165,38 @@ app.get('/faq', async (req, res, next)=>{ }); //Source page -app.get('/source/:id', asyncHandler(getBrew('share')), (req, res) => { - const { brew } = req; +app.get('/source/:id', asyncHandler(getBrew('share')), (req, res, next) => { + const ownBrew = req.account && req.brew.authors.includes(req.account.username); // Check if cloning is disabled - if (brew.cloning === false) { - const errorText = `This brew's author has disabled cloning, so its source is not publicly available.`; - return res.status(403).send(errorText); + if (req.brew.cloning === false && !ownBrew) { + const error = new Error(`Cloning blocked`); + error.status = 423; // HTTP status code for "Locked" + error.HBErrorCode = '23'; + error.brewId = req.brew.shareId; + error.brewTitle = req.brew.title; + return next(error); } - // HTML encode the text - const replaceStrings = { '&': '&', '<': '<', '>': '>' }; - let text = brew.text; - for (const replaceStr in replaceStrings) { - text = text.replaceAll(replaceStr, replaceStrings[replaceStr]); - } - text = `
${text}
`; - - // Send the response - res.status(200).send(text); + return next(); }); //Download brew source page -app.get('/download/:id', asyncHandler(getBrew('share')), (req, res) => { - const { brew } = req; - sanitizeBrew(brew, 'share'); - +app.get('/download/:id', asyncHandler(getBrew('share')), (req, res, next) => { + const ownBrew = req.account && req.brew.authors.includes(req.account.username); + // Check if cloning is disabled - if (brew.cloning === false) { - const errorText = `This brew's author has disabled cloning, so its source is not available for download.`; - return res.status(403).send(errorText); + if (req.brew.cloning === false && !ownBrew) { + const error = new Error(`Cloning blocked`); + error.status = 423; // HTTP status code for "Locked" + error.HBErrorCode = '23'; + error.brewId = req.brew.shareId; + error.brewTitle = req.brew.title; + return next(error); // Pass the error to the error-handling middleware } - const prefix = 'HB - '; - - // Function to encode characters according to RFC 3986 - const encodeRFC3986ValueChars = (str) => { - return encodeURIComponent(str) - .replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`); - }; - - // Sanitize and format filename - let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', ''); - if (!fileName || fileName.length === 0) { - fileName = `${prefix}-Untitled-Brew`; - } - - // Set headers for file download - res.set({ - 'Cache-Control': 'no-cache', - 'Content-Type': 'text/plain', - 'Content-Disposition': `attachment; filename*=UTF-8''${encodeRFC3986ValueChars(fileName)}.txt` - }); - - // Handle empty or undefined brew.text - const textToSend = brew.text || ''; - res.status(200).send(textToSend); + // If cloning is allowed, proceed to the next middleware or route handler + return next(); }); @@ -314,10 +290,11 @@ app.get('/edit/:id', asyncHandler(getBrew('edit')), asyncHandler(async(req, res, //New Page from ID app.get('/new/:id', asyncHandler(getBrew('share')), asyncHandler(async(req, res, next)=>{ + const ownAccount = req.account && (req.account.username == req.params.username); sanitizeBrew(req.brew, 'share'); // Check if cloning is disabled - if (req.brew.cloning === false) { + if (req.brew.cloning === false && !ownAccount) { console.log('error 403'); const errorText = `This brew's author has disabled cloning, so its source is not available for download.`; return res.status(403).send(errorText); From 44e2e3826131d6193d0c050edcb32575f2e6729c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 15 Aug 2024 13:55:03 +0200 Subject: [PATCH 04/13] change 423 to 401, internal error code from 23 to 10 --- client/homebrew/pages/errorPage/errors/errorIndex.js | 2 +- server/app.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/homebrew/pages/errorPage/errors/errorIndex.js b/client/homebrew/pages/errorPage/errors/errorIndex.js index 0a422602c9..6e9da2605b 100644 --- a/client/homebrew/pages/errorPage/errors/errorIndex.js +++ b/client/homebrew/pages/errorPage/errors/errorIndex.js @@ -150,7 +150,7 @@ const errorIndex = (props)=>{ **Brew ID:** ${props.brew.brewId}`, //Brew's cloning blocked - '23' : dedent` + '10' : dedent` ## This brew's cloning features have been disabled The author of this brew does not want other people using its contents, so viewing the source, diff --git a/server/app.js b/server/app.js index 542b4f7ae7..17f5a28a66 100644 --- a/server/app.js +++ b/server/app.js @@ -171,8 +171,8 @@ app.get('/source/:id', asyncHandler(getBrew('share')), (req, res, next) => { // Check if cloning is disabled if (req.brew.cloning === false && !ownBrew) { const error = new Error(`Cloning blocked`); - error.status = 423; // HTTP status code for "Locked" - error.HBErrorCode = '23'; + error.status = 401; // HTTP status code for "Locked" + error.HBErrorCode = '10'; error.brewId = req.brew.shareId; error.brewTitle = req.brew.title; return next(error); @@ -188,8 +188,8 @@ app.get('/download/:id', asyncHandler(getBrew('share')), (req, res, next) => { // Check if cloning is disabled if (req.brew.cloning === false && !ownBrew) { const error = new Error(`Cloning blocked`); - error.status = 423; // HTTP status code for "Locked" - error.HBErrorCode = '23'; + error.status = 401; // HTTP status code for "Locked" + error.HBErrorCode = '10'; error.brewId = req.brew.shareId; error.brewTitle = req.brew.title; return next(error); // Pass the error to the error-handling middleware From a6a9bd4dce3d1b9040adf51077658c69f2549803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 15 Aug 2024 13:59:11 +0200 Subject: [PATCH 05/13] add author list to error page --- client/homebrew/pages/errorPage/errors/errorIndex.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/homebrew/pages/errorPage/errors/errorIndex.js b/client/homebrew/pages/errorPage/errors/errorIndex.js index 6e9da2605b..027d09e23c 100644 --- a/client/homebrew/pages/errorPage/errors/errorIndex.js +++ b/client/homebrew/pages/errorPage/errors/errorIndex.js @@ -158,11 +158,15 @@ const errorIndex = (props)=>{ If you think this is a mistake, you may contact the author. + If you are the author, please login to the account that has authorship of this brew. + : **Brew ID:** ${props.brew.brewId} - **Brew Title:** ${props.brew.brewTitle}`, + **Brew Title:** ${props.brew.brewTitle} + + **Brew Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${author})`;}).join(', ') || 'Unable to list authors'}`, // Brew locked by Administrators error '100' : dedent` From e393a98f79b58299ffd0321e8b163b9504f777cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 15 Aug 2024 17:30:33 +0200 Subject: [PATCH 06/13] add www authenticate to the response --- server/app.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/server/app.js b/server/app.js index 17f5a28a66..2a3b9bde82 100644 --- a/server/app.js +++ b/server/app.js @@ -168,16 +168,16 @@ app.get('/faq', async (req, res, next)=>{ app.get('/source/:id', asyncHandler(getBrew('share')), (req, res, next) => { const ownBrew = req.account && req.brew.authors.includes(req.account.username); - // Check if cloning is disabled if (req.brew.cloning === false && !ownBrew) { - const error = new Error(`Cloning blocked`); - error.status = 401; // HTTP status code for "Locked" - error.HBErrorCode = '10'; - error.brewId = req.brew.shareId; - error.brewTitle = req.brew.title; + res.set('WWW-Authenticate', 'Bearer realm="Authorization Required"'); + const error = new Error('Cloning blocked'); + error.status = 401; + error.HBErrorCode = '10'; + error.brewId = req.brew.shareId; + error.brewTitle = req.brew.title; + error.authors = req.brew.authors; return next(error); } - return next(); }); @@ -185,21 +185,18 @@ app.get('/source/:id', asyncHandler(getBrew('share')), (req, res, next) => { app.get('/download/:id', asyncHandler(getBrew('share')), (req, res, next) => { const ownBrew = req.account && req.brew.authors.includes(req.account.username); - // Check if cloning is disabled if (req.brew.cloning === false && !ownBrew) { - const error = new Error(`Cloning blocked`); - error.status = 401; // HTTP status code for "Locked" + res.set('WWW-Authenticate', 'Bearer realm="Authorization Required"'); + const error = new Error('Cloning blocked'); + error.status = 401; error.HBErrorCode = '10'; error.brewId = req.brew.shareId; error.brewTitle = req.brew.title; - return next(error); // Pass the error to the error-handling middleware + return next(error); } - - // If cloning is allowed, proceed to the next middleware or route handler return next(); }); - //User Page app.get('/user/:username', async (req, res, next)=>{ const ownAccount = req.account && (req.account.username == req.params.username); From ee7ee9853bd57f83c890cd1c500356ae0ff7666a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 16 Aug 2024 10:59:38 +0200 Subject: [PATCH 07/13] revert download page --- client/homebrew/homebrew.jsx | 1 - server/app.js | 42 +++++++++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/client/homebrew/homebrew.jsx b/client/homebrew/homebrew.jsx index 310680b327..402b8274d3 100644 --- a/client/homebrew/homebrew.jsx +++ b/client/homebrew/homebrew.jsx @@ -70,7 +70,6 @@ const Homebrew = createClass({ } /> } /> } /> - } /> } /> } /> } /> diff --git a/server/app.js b/server/app.js index 2a3b9bde82..3adebfa704 100644 --- a/server/app.js +++ b/server/app.js @@ -166,16 +166,17 @@ app.get('/faq', async (req, res, next)=>{ //Source page app.get('/source/:id', asyncHandler(getBrew('share')), (req, res, next) => { - const ownBrew = req.account && req.brew.authors.includes(req.account.username); + const { brew, account } = req; + const ownBrew = account && brew.authors.includes(account.username); - if (req.brew.cloning === false && !ownBrew) { + if (brew.cloning === false && !ownBrew) { res.set('WWW-Authenticate', 'Bearer realm="Authorization Required"'); const error = new Error('Cloning blocked'); error.status = 401; error.HBErrorCode = '10'; - error.brewId = req.brew.shareId; - error.brewTitle = req.brew.title; - error.authors = req.brew.authors; + error.brewId = brew.shareId; + error.brewTitle = brew.title; + error.authors = brew.authors; return next(error); } return next(); @@ -183,18 +184,39 @@ app.get('/source/:id', asyncHandler(getBrew('share')), (req, res, next) => { //Download brew source page app.get('/download/:id', asyncHandler(getBrew('share')), (req, res, next) => { - const ownBrew = req.account && req.brew.authors.includes(req.account.username); + const { brew, account } = req; + const ownBrew = account && brew.authors.includes(account.username); - if (req.brew.cloning === false && !ownBrew) { + if (brew.cloning === false && !ownBrew) { res.set('WWW-Authenticate', 'Bearer realm="Authorization Required"'); const error = new Error('Cloning blocked'); error.status = 401; error.HBErrorCode = '10'; - error.brewId = req.brew.shareId; - error.brewTitle = req.brew.title; + error.brewId = brew.shareId; + error.brewTitle = brew.title; + error.authors = brew.authors; return next(error); } - return next(); + + + sanitizeBrew(brew, 'share'); + const prefix = 'HB - '; + + const encodeRFC3986ValueChars = (str)=>{ + return ( + encodeURIComponent(str) + .replace(/[!'()*]/g, (char)=>{`%${char.charCodeAt(0).toString(16).toUpperCase()}`;}) + ); + }; + + let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', ''); + if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; }; + res.set({ + 'Cache-Control' : 'no-cache', + 'Content-Type' : 'text/plain', + 'Content-Disposition' : `attachment; filename*=UTF-8''${encodeRFC3986ValueChars(fileName)}.txt` + }); + res.status(200).send(brew.text); }); //User Page From 4595322e631ab51e17277ab974f84fe1c171ecb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Fri, 16 Aug 2024 11:03:54 +0200 Subject: [PATCH 08/13] block actual cloning in newpage --- server/app.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/server/app.js b/server/app.js index 3adebfa704..75af56b389 100644 --- a/server/app.js +++ b/server/app.js @@ -309,14 +309,18 @@ app.get('/edit/:id', asyncHandler(getBrew('edit')), asyncHandler(async(req, res, //New Page from ID app.get('/new/:id', asyncHandler(getBrew('share')), asyncHandler(async(req, res, next)=>{ - const ownAccount = req.account && (req.account.username == req.params.username); + const ownBrew = req.account && req.brew.authors.includes(req.account.username); sanitizeBrew(req.brew, 'share'); - // Check if cloning is disabled - if (req.brew.cloning === false && !ownAccount) { - console.log('error 403'); - const errorText = `This brew's author has disabled cloning, so its source is not available for download.`; - return res.status(403).send(errorText); + if (req.brew.cloning === false && !ownBrew) { + res.set('WWW-Authenticate', 'Bearer realm="Authorization Required"'); + const error = new Error('Cloning blocked'); + error.status = 401; + error.HBErrorCode = '10'; + error.brewId = req.brew.shareId; + error.brewTitle = req.brew.title; + error.authors = req.brew.authors; + return next(error); } splitTextStyleAndMetadata(req.brew); From 7f8afecc5c47fd3772e4a726dba07b97c626366a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 29 Aug 2024 10:48:19 +0200 Subject: [PATCH 09/13] log config vars --- server/app.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/app.js b/server/app.js index 75af56b389..a66f42c125 100644 --- a/server/app.js +++ b/server/app.js @@ -439,6 +439,12 @@ app.get('/account', asyncHandler(async (req, res, next)=>{ })); const nodeEnv = config.get('node_env'); + +console.group(); +console.log('Config vars') +console.table(config); +console.groupEnd(); + const isLocalEnvironment = config.get('local_environments').includes(nodeEnv); // Local only if(isLocalEnvironment){ From f00c9633edb7ebc41d690b28cda63804744a0370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 29 Aug 2024 10:51:34 +0200 Subject: [PATCH 10/13] simplify console log --- server/app.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/app.js b/server/app.js index a66f42c125..b6c20f5ac5 100644 --- a/server/app.js +++ b/server/app.js @@ -440,10 +440,7 @@ app.get('/account', asyncHandler(async (req, res, next)=>{ const nodeEnv = config.get('node_env'); -console.group(); -console.log('Config vars') -console.table(config); -console.groupEnd(); +console.log('Config vars: ',config); const isLocalEnvironment = config.get('local_environments').includes(nodeEnv); // Local only From 29187e731246f9ba02256eb2c122d052f858c5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 29 Aug 2024 10:56:06 +0200 Subject: [PATCH 11/13] use getter --- server/app.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/app.js b/server/app.js index b6c20f5ac5..a6f97ea520 100644 --- a/server/app.js +++ b/server/app.js @@ -440,7 +440,9 @@ app.get('/account', asyncHandler(async (req, res, next)=>{ const nodeEnv = config.get('node_env'); -console.log('Config vars: ',config); +console.log(config.get('HEROKU_PR_NUMBER')); +console.log(config.get('HEROKU_APP_NAME')); +console.log(config.get('HEROKU_BRANCH')); const isLocalEnvironment = config.get('local_environments').includes(nodeEnv); // Local only From 9d360400c6f58872923c11fe8ebf67520f09d386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 29 Aug 2024 11:09:16 +0200 Subject: [PATCH 12/13] lets try again --- server/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/app.js b/server/app.js index a6f97ea520..021018ce04 100644 --- a/server/app.js +++ b/server/app.js @@ -440,9 +440,9 @@ app.get('/account', asyncHandler(async (req, res, next)=>{ const nodeEnv = config.get('node_env'); -console.log(config.get('HEROKU_PR_NUMBER')); -console.log(config.get('HEROKU_APP_NAME')); -console.log(config.get('HEROKU_BRANCH')); +console.log(config.get('heroku_pr_number')); +console.log(config.get('heroku_app_name')); +console.log(config.get('heroku_branch')); const isLocalEnvironment = config.get('local_environments').includes(nodeEnv); // Local only From 5bde0f4a0cca1377df23ca58a21afc21d45514f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Thu, 29 Aug 2024 23:20:59 +0200 Subject: [PATCH 13/13] revert last console logs --- server/app.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/server/app.js b/server/app.js index 021018ce04..601557f9db 100644 --- a/server/app.js +++ b/server/app.js @@ -440,10 +440,6 @@ app.get('/account', asyncHandler(async (req, res, next)=>{ const nodeEnv = config.get('node_env'); -console.log(config.get('heroku_pr_number')); -console.log(config.get('heroku_app_name')); -console.log(config.get('heroku_branch')); - const isLocalEnvironment = config.get('local_environments').includes(nodeEnv); // Local only if(isLocalEnvironment){