diff --git a/.github/README.md b/.github/README.md index 25bafc7..11810e1 100644 --- a/.github/README.md +++ b/.github/README.md @@ -295,12 +295,12 @@ If you want to customize the font or colours of the viewer page, create a file i By default, ass directs the index route `/` to this README. Follow these steps to use a custom index: -1. Run `npm run setup` to re-run the setup script. - - The defaults are set by your existing config, so you can press `Enter` to accept the defaults on most prompts. - - The one setting you want to change is `Filename for your custom index`. Enter a name for your index, including `.js` (custom index's must be `.js` files). -2. Make a new file in the `share/` directory matching the name you entered (this directory can be found in the `ass/` directory. It is created automatically after setup is run). -3. Your index file needs to export a single function taking three arguments: `(req, res, next)`. Some code samples for common use cases are provided below. -4. Restart ass. The startup info logs should say **`Custom index:`**` enabled`. +1. Create a file in the `share/` directory called `index.html` or `index.js`. + - ass will treat `index.html` as an HTML file and will send it to the client. + - ass will treat `index.js` as a Node.js file that exports a function representing [Express middleware](https://expressjs.com/en/guide/using-middleware.html). ass will pass all handling of the index to this function. The function should take three arguments: `(req, res, next)`. Some code samples for common use cases are provided below. + - If both `index.html` and `index.js` are present, the `index.html` file will be served first. +2. Add whatever you want to the file. +3. Restart ass. The startup info logs should mention which file is being used as the index. ### Custom index code samples @@ -310,13 +310,6 @@ By default, ass directs the index route `/` to this README. Follow these steps t module.exports = (req, res, next) => res.redirect('/register'); ``` -**Send an HTML file** - -```js -const path = require('path'); -module.exports = (req, res, next) => res.sendFile(path.join(__dirname, 'index.html')); -``` - ## File storage ass supports three methods of file storage: local, S3, or [Skynet]. diff --git a/package-lock.json b/package-lock.json index 0ffb423..ce0343c 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ass", - "version": "0.11.0-rc.1", + "version": "0.12.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ass", - "version": "0.11.0-rc.1", + "version": "0.12.0", "license": "ISC", "dependencies": { "@skynetlabs/skynet-nodejs": "^2.3.0", @@ -25,6 +25,7 @@ "discord-webhook-node": "^1.1.8", "escape-html": "^1.0.3", "express": "^4.17.3", + "express-brute": "^1.0.1", "express-busboy": "^8.0.2", "ffmpeg-static": "^4.4.0", "fs-extra": "^10.0.1", @@ -46,6 +47,7 @@ "devDependencies": { "@types/escape-html": "^1.0.1", "@types/express": "^4.17.13", + "@types/express-brute": "^1.0.1", "@types/express-busboy": "^8.0.0", "@types/ffmpeg-static": "^3.0.0", "@types/fs-extra": "^9.0.12", @@ -512,6 +514,15 @@ "@types/serve-static": "*" } }, + "node_modules/@types/express-brute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/express-brute/-/express-brute-1.0.1.tgz", + "integrity": "sha512-rG4YWh+tIDvwupiPwuLaz0fEpFE7ShLOX59ZdejMQ4jYlshmxtN5KjB7VFGsggk4TmeVnoHF7QrwLwan2wj8cg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/express-busboy": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@types/express-busboy/-/express-busboy-8.0.0.tgz", @@ -2131,6 +2142,18 @@ "node": ">= 0.10.0" } }, + "node_modules/express-brute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/express-brute/-/express-brute-1.0.1.tgz", + "integrity": "sha512-ieZmwox3oIZdQCVjvvnwQvrKQumWdb/JjmC9mWplF42AuHCBXr6Yk/I+nLTRQx+9F+2aapOW9kYLwA6xIlwA9g==", + "dependencies": { + "long-timeout": "~0.1.1", + "underscore": "~1.8.3" + }, + "peerDependencies": { + "express": "4.x" + } + }, "node_modules/express-busboy": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/express-busboy/-/express-busboy-8.0.2.tgz", @@ -2968,6 +2991,11 @@ "lodash._baseuniq": "~4.6.0" } }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -5204,6 +5232,11 @@ "node": ">=4.2.0" } }, + "node_modules/underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha512-5WsVTFcH1ut/kkhAaHf4PVgI8c7++GiVcpCGxPouI6ZVjsqPnSDf8h/8HtVqc0t4fzRXwnMK70EcZeAs3PIddg==" + }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -5859,6 +5892,15 @@ "@types/serve-static": "*" } }, + "@types/express-brute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/express-brute/-/express-brute-1.0.1.tgz", + "integrity": "sha512-rG4YWh+tIDvwupiPwuLaz0fEpFE7ShLOX59ZdejMQ4jYlshmxtN5KjB7VFGsggk4TmeVnoHF7QrwLwan2wj8cg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/express-busboy": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@types/express-busboy/-/express-busboy-8.0.0.tgz", @@ -7128,6 +7170,15 @@ "vary": "~1.1.2" } }, + "express-brute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/express-brute/-/express-brute-1.0.1.tgz", + "integrity": "sha512-ieZmwox3oIZdQCVjvvnwQvrKQumWdb/JjmC9mWplF42AuHCBXr6Yk/I+nLTRQx+9F+2aapOW9kYLwA6xIlwA9g==", + "requires": { + "long-timeout": "~0.1.1", + "underscore": "~1.8.3" + } + }, "express-busboy": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/express-busboy/-/express-busboy-8.0.2.tgz", @@ -7787,6 +7838,11 @@ "lodash._baseuniq": "~4.6.0" } }, + "long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -9373,6 +9429,11 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==" }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha512-5WsVTFcH1ut/kkhAaHf4PVgI8c7++GiVcpCGxPouI6ZVjsqPnSDf8h/8HtVqc0t4fzRXwnMK70EcZeAs3PIddg==" + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", diff --git a/package.json b/package.json index 59a2640..c7d6833 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ass", - "version": "0.11.0-rc.1", + "version": "0.12.0", "description": "The superior self-hosted ShareX server", "main": "ass.js", "engines": { @@ -54,6 +54,7 @@ "discord-webhook-node": "^1.1.8", "escape-html": "^1.0.3", "express": "^4.17.3", + "express-brute": "^1.0.1", "express-busboy": "^8.0.2", "ffmpeg-static": "^4.4.0", "fs-extra": "^10.0.1", @@ -75,6 +76,7 @@ "devDependencies": { "@types/escape-html": "^1.0.1", "@types/express": "^4.17.13", + "@types/express-brute": "^1.0.1", "@types/express-busboy": "^8.0.0", "@types/ffmpeg-static": "^3.0.0", "@types/fs-extra": "^9.0.12", diff --git a/src/ass.ts b/src/ass.ts index c6d357a..5b41b6b 100644 --- a/src/ass.ts +++ b/src/ass.ts @@ -23,7 +23,7 @@ if (!fs.existsSync(configPath) || fs.readFileSync(configPath).toString().length //#endregion // Load the JSON -const { host, port, useSsl, isProxied, s3enabled, frontendName, indexFile, useSia, diskFilePath }: Config = fs.readJsonSync(path('config.json')); +const { host, port, useSsl, isProxied, s3enabled, frontendName, useSia, diskFilePath }: Config = fs.readJsonSync(path('config.json')); const { CODE_INTERNAL_SERVER_ERROR }: MagicNumbers = fs.readJsonSync(path('MagicNumbers.json')); const { name, version, homepage }: Package = fs.readJsonSync(path('package.json')); @@ -58,6 +58,24 @@ app.disable('x-powered-by'); app.set('trust proxy', isProxied); app.set('view engine', 'pug'); +// Rate limiting using express-brute +// ! Notice ! +// The rate limiting used here is very trivial and should be used with caution. +// I plan to improve this in the future somehow (possibly with redis, who knows). +// - tycrek, 2022-08-18 +// todo: fix this eventually +import ExpressBrute from 'express-brute'; +const bruteforce = new ExpressBrute(new ExpressBrute.MemoryStore(), { + freeRetries: 50, + minWait: 50, // 50ms + maxWait: 500, // 500ms + lifetime: 5, // 5 seconds + failCallback: (_req, res, _next, _nextValidRequestDate) => res.sendStatus(429), +}); + +// Routes to protect +app.get(['/'], bruteforce.prevent, (_req, _res, next) => next()); + // Express logger middleware app.use(log.middleware()); @@ -73,11 +91,12 @@ useSsl && app.use(helmet.hsts({ preload: true })); // skipcq: JS-0093 app.use(nofavicon); // Use custom index, otherwise render README.md -const ASS_INDEX = indexFile !== '' && fs.existsSync(path('share', indexFile)) && require(`../share/${indexFile}`); -const ASS_INDEX_ENABLED = typeof ASS_INDEX === typeof Function; -app.get('/', (req, res, next) => ASS_INDEX_ENABLED // skipcq: JS-0229 - ? ASS_INDEX(req, res, next) - : res.redirect(homepage)); +type ASS_INDEX_TYPE = 'html' | 'js' | undefined; +const ASS_INDEX: ASS_INDEX_TYPE = fs.existsSync(path('share', 'index.html')) ? 'html' : fs.existsSync(path('share', 'index.js')) ? 'js' : undefined; +app.get('/', (req, res, next) => + ASS_INDEX === 'html' ? res.sendFile(path('share', 'index.html')) : + ASS_INDEX === 'js' ? require(path('share', 'index.js'))(req, res, next) : // skipcq: JS-0359 + res.redirect(homepage)) // Set up custom frontend const ASS_FRONTEND = fs.existsSync(path(`./${frontendName}/package.json`)) ? (require('submodule'), require(`../${frontendName}`)) : { enabled: false }; @@ -111,7 +130,7 @@ app.use((err: ErrWrap, _req: Request, res: Response) => log.error(err.message).e .info('Files', `${data().size}`) .info('Data engine', data().name, data().type) .info('Frontend', ASS_FRONTEND.enabled ? ASS_FRONTEND.brand : 'disabled', `${ASS_FRONTEND.enabled ? `${getTrueHttp()}${getTrueDomain()}${ASS_FRONTEND.endpoint}` : ''}`) - .info('Custom index', ASS_INDEX_ENABLED ? `enabled` : 'disabled') + .info('Custom index', ASS_INDEX ?? 'disabled') .blank() .express()!.Host(app, port, host, () => log.success('Ready for uploads', `Storing resources ${s3enabled ? 'in S3' : useSia ? 'on Sia blockchain' : 'on disk'}`)); })(); diff --git a/src/setup.js b/src/setup.js index 34ed82b..19990d4 100644 --- a/src/setup.js +++ b/src/setup.js @@ -14,7 +14,6 @@ const config = { viewDirect: false, dataEngine: '@tycrek/papito', frontendName: 'ass-x', - indexFile: '', useSia: false, s3enabled: false, }; @@ -173,12 +172,6 @@ function doSetup() { default: config.frontendName, required: false }, - indexFile: { - description: 'Filename for your custom index, if using one (must be a JS file)', - type: 'string', - default: config.indexFile, - required: false - }, useSia: { description: 'Use Sia Skynet for decentralized file storage?', type: 'boolean',