From 7268d24143dca10b75b64a6800cec9fdfa4e1d72 Mon Sep 17 00:00:00 2001 From: Pitu <7425261+Pitu@users.noreply.github.com> Date: Sun, 16 Sep 2018 00:55:30 -0300 Subject: Base structures --- src/api/structures/Database.js | 110 +++++++++++++++++++++++++++++++++++++++++ src/api/structures/Route.js | 44 +++++++++++++++++ src/api/structures/Server.js | 78 +++++++++++++++++++++++++++++ 3 files changed, 232 insertions(+) create mode 100644 src/api/structures/Database.js create mode 100644 src/api/structures/Route.js create mode 100644 src/api/structures/Server.js (limited to 'src/api/structures') diff --git a/src/api/structures/Database.js b/src/api/structures/Database.js new file mode 100644 index 0000000..dc26afe --- /dev/null +++ b/src/api/structures/Database.js @@ -0,0 +1,110 @@ +const log = require('../utils/Log'); +const { server } = require('../../../config'); +const db = require('knex')(server.database); +const bcrypt = require('bcrypt'); +const moment = require('moment'); +const randomstring = require('randomstring'); + +class Database { + constructor() { + this.createTables(); + } + + async createTables() { + if (!await db.schema.hasTable('users')) { + await db.schema.createTable('users', table => { + table.increments(); + table.string('username'); + table.string('password'); + table.boolean('enabled').defaultTo(true); + table.boolean('isAdmin').defaultTo(false); + table.string('apiKey'); + table.timestamp('passwordEditedAt'); + table.timestamp('apiKeyEditedAt'); + table.timestamp('createdAt'); + table.timestamp('editedAt'); + }); + } + + if (!await db.schema.hasTable('albums')) { + await db.schema.createTable('albums', table => { + table.increments(); + table.integer('userId'); + table.string('name'); + // table.string('identifier'); + // table.boolean('enabled'); + // table.boolean('enableDownload').defaultTo(true); + table.timestamp('createdAt'); + table.timestamp('editedAt'); + }); + } + + if (!await db.schema.hasTable('files')) { + await db.schema.createTable('files', table => { + table.increments(); + table.integer('userId'); + table.string('name'); + table.string('original'); + table.string('type'); + table.integer('size'); + table.string('hash'); + table.string('ip'); + table.timestamp('createdAt'); + table.timestamp('editedAt'); + }); + } + + if (!await db.schema.hasTable('links')) { + await db.schema.createTable('links', table => { + table.increments(); + table.integer('albumId'); + table.string('identifier'); + table.integer('views').defaultTo(0); + table.boolean('enabled').defaultTo(true); + table.boolean('enableDownload').defaultTo(true); + table.timestamp('expiresAt'); + table.timestamp('createdAt'); + table.timestamp('editedAt'); + }); + } + + if (!await db.schema.hasTable('albumsFiles')) { + await db.schema.createTable('albumsFiles', table => { + table.increments(); + table.integer('albumId'); + table.integer('fileId'); + }); + } + + if (!await db.schema.hasTable('albumsLinks')) { + await db.schema.createTable('albumsLinks', table => { + table.increments(); + table.integer('albumId'); + table.integer('linkId'); + }); + } + + const now = moment.utc().toDate(); + const user = await db.table('users').where({ username: 'root' }).first(); + if (user) return; + try { + const hash = await bcrypt.hash('root', 10); + await db.table('users').insert({ + username: 'root', + password: hash, + apiKey: randomstring.generate(64), + passwordEditedAt: now, + apiKeyEditedAt: now, + createdAt: now, + editedAt: now, + isAdmin: true + }); + log.success('Successfully created the root user with password "root". Make sure to log in and change it!'); + } catch (error) { + log.error(error); + if (error) log.error('Error generating password hash for root'); + } + } +} + +module.exports = Database; diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js new file mode 100644 index 0000000..77ebd32 --- /dev/null +++ b/src/api/structures/Route.js @@ -0,0 +1,44 @@ +const JWT = require('jsonwebtoken'); +const { server } = require('../../../config'); +const db = require('knex')(server.database); +const moment = require('moment'); + +class Route { + constructor(path, method, options) { + if (!path) throw new Error('Every route needs a URL associated with it.'); + if (!method) throw new Error('Every route needs its method specified.'); + + this.path = path; + this.method = method; + this.options = options || {}; + } + + authorize(req, res) { + if (this.options.bypassAuth) return this.run(req, res); + if (!req.headers.authorization) return res.status(401).json({ message: 'No authorization header provided' }); + const token = req.headers.authorization.split(' ')[1]; + if (!token) return res.status(401).json({ message: 'No authorization header provided' }); + + return JWT.verify(token, server.secret, async (error, decoded) => { + if (error) { + console.log(error); + return res.status(401).json({ message: 'Your token appears to be invalid' }); + } + const id = decoded ? decoded.sub : ''; + const iat = decoded ? decoded.iat : ''; + + const user = await db.table('users').where({ id }).first(); + if (!user) return res.status(401).json({ message: 'Invalid authorization' }); + if (iat && iat < moment(user.passwordEditedAt).format('x')) return res.status(401).json({ message: 'Token expired' }); + if (!user.enabled) return res.status(401).json({ message: 'This account has been disabled' }); + + return this.run(req, res, user); + }); + } + + run(req, res, user) { // eslint-disable-line no-unused-vars + return; + } +} + +module.exports = Route; diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js new file mode 100644 index 0000000..ae4b678 --- /dev/null +++ b/src/api/structures/Server.js @@ -0,0 +1,78 @@ +const config = require('../../../config'); +const log = require('../utils/Log'); +const express = require('express'); +const helmet = require('helmet'); +const cors = require('cors'); +const RateLimit = require('express-rate-limit'); +const bodyParser = require('body-parser'); +const jetpack = require('fs-jetpack'); +const path = require('path'); +const Database = require('./Database'); +const oneliner = require('one-liner'); + +const rateLimiter = new RateLimit({ + windowMs: config.server.rateLimits.window, + max: config.server.rateLimits.max, + delayMs: 0 +}); + +class Server { + constructor() { + this.port = config.server.ports.backend; + this.server = express(); + this.server.set('trust proxy', 1); + this.server.use(helmet()); + this.server.use(cors({ allowedHeaders: ['Accept', 'Authorization', 'Cache-Control', 'X-Requested-With', 'Content-Type', 'albumId'] })); + this.server.use((req, res, next) => { + if (req.headers.accept === 'application/vnd.lolisafe.json') return next(); + return res.status(405).json({ message: 'Incorrect `Accept` header provided' }); + }); + this.server.use(bodyParser.urlencoded({ extended: true })); + this.server.use(bodyParser.json()); + // this.server.use(rateLimiter); + this.routesFolder = path.join(__dirname, '..', 'routes'); + this.database = new Database(); + this.server.get('/config', (req, res) => res.json({ + baseURL: config.backendLocation, + serviceName: config.serviceName, + maxFileSize: config.uploads.uploadMaxSize, + chunkSize: config.uploads.chunkSize + })); + } + + registerAllTheRoutes() { + jetpack.find(this.routesFolder, { matching: '*.js' }).forEach(routeFile => { + const RouteClass = require(path.join('..', '..', '..', routeFile)); + let routes = [RouteClass]; + if (Array.isArray(RouteClass)) routes = RouteClass; + for (const File of routes) { + const route = new File(); + this.server[route.method](config.server.routePrefix + route.path, route.authorize.bind(route)); + log.info(`Found route ${route.method.toUpperCase()} ${config.server.routePrefix}${route.path}`); + } + }); + } + + writeFrontendConfig() { + const template = oneliner` + module.exports = { + baseURL: '${config.backendLocation}', + serviceName: '${config.serviceName}', + maxFileSize: '${config.uploads.uploadMaxSize}', + chunkSize: '${config.uploads.chunkSize}' + }`; + jetpack.write(path.join(__dirname, '..', '..', 'frontend', 'config.js'), template); + log.success('Frontend config file generated successfully'); + } + + start() { + jetpack.dir('uploads/chunks'); + jetpack.dir('uploads/thumbs/square'); + this.registerAllTheRoutes(); + this.server.listen(this.port, () => { + log.success(`Backend ready and listening on port ${this.port}`); + }); + } +} + +module.exports = Server; -- cgit v1.2.3 From f2c885b718528d42df412e612520fb471c46d0bd Mon Sep 17 00:00:00 2001 From: Pitu <7425261+Pitu@users.noreply.github.com> Date: Mon, 17 Sep 2018 04:55:42 -0300 Subject: Commented all the code --- src/api/structures/Route.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 77ebd32..9ff65f0 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -2,6 +2,7 @@ const JWT = require('jsonwebtoken'); const { server } = require('../../../config'); const db = require('knex')(server.database); const moment = require('moment'); +const log = require('../utils/Log'); class Route { constructor(path, method, options) { @@ -21,7 +22,7 @@ class Route { return JWT.verify(token, server.secret, async (error, decoded) => { if (error) { - console.log(error); + log.error(error); return res.status(401).json({ message: 'Your token appears to be invalid' }); } const id = decoded ? decoded.sub : ''; -- cgit v1.2.3 From 4b2b02110b457d8ebeee78e1bdf99eb0660d0626 Mon Sep 17 00:00:00 2001 From: Pitu <7425261+Pitu@users.noreply.github.com> Date: Tue, 18 Sep 2018 03:34:00 -0300 Subject: We can now download albums yayyyy --- src/api/structures/Database.js | 2 ++ src/api/structures/Server.js | 4 ++++ 2 files changed, 6 insertions(+) (limited to 'src/api/structures') diff --git a/src/api/structures/Database.js b/src/api/structures/Database.js index dc26afe..76ea006 100644 --- a/src/api/structures/Database.js +++ b/src/api/structures/Database.js @@ -34,6 +34,7 @@ class Database { // table.string('identifier'); // table.boolean('enabled'); // table.boolean('enableDownload').defaultTo(true); + table.timestamp('zippedAt'); table.timestamp('createdAt'); table.timestamp('editedAt'); }); @@ -57,6 +58,7 @@ class Database { if (!await db.schema.hasTable('links')) { await db.schema.createTable('links', table => { table.increments(); + table.integer('userId'); table.integer('albumId'); table.string('identifier'); table.integer('views').defaultTo(0); diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index ae4b678..0b05570 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -24,6 +24,10 @@ class Server { this.server.use(helmet()); this.server.use(cors({ allowedHeaders: ['Accept', 'Authorization', 'Cache-Control', 'X-Requested-With', 'Content-Type', 'albumId'] })); this.server.use((req, res, next) => { + /* + This bypasses the headers.accept for album download, since it's accesed directly through the browser. + */ + if (req.url.includes('/api/album/') && req.url.includes('/zip') && req.method === 'GET') return next(); if (req.headers.accept === 'application/vnd.lolisafe.json') return next(); return res.status(405).json({ message: 'Incorrect `Accept` header provided' }); }); -- cgit v1.2.3 From e33cf304495d327b152a01cc6906643ccd8dd62a Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 19 Feb 2019 00:06:38 +0900 Subject: Changes --- src/api/structures/Server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index 0b05570..5ead078 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -79,4 +79,4 @@ class Server { } } -module.exports = Server; +new Server().start(); -- cgit v1.2.3 From 89a271818ed25b0a17a17dd1d6804e34d1f2ec0f Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 19 Feb 2019 23:52:24 +0900 Subject: Switch config to .env --- src/api/structures/Route.js | 24 ++++++++++++++++++------ src/api/structures/Server.js | 32 +++++++------------------------- 2 files changed, 25 insertions(+), 31 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 9ff65f0..32d576f 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -1,6 +1,13 @@ const JWT = require('jsonwebtoken'); -const { server } = require('../../../config'); -const db = require('knex')(server.database); +const db = require('knex')({ + client: process.env.DB_CLIENT, + connection: { + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_DATABASE + } +}); const moment = require('moment'); const log = require('../utils/Log'); @@ -15,12 +22,12 @@ class Route { } authorize(req, res) { - if (this.options.bypassAuth) return this.run(req, res); + if (this.options.bypassAuth) return this.run(req, res, db); if (!req.headers.authorization) return res.status(401).json({ message: 'No authorization header provided' }); const token = req.headers.authorization.split(' ')[1]; if (!token) return res.status(401).json({ message: 'No authorization header provided' }); - return JWT.verify(token, server.secret, async (error, decoded) => { + return JWT.verify(token, process.env.SECRET, async (error, decoded) => { if (error) { log.error(error); return res.status(401).json({ message: 'Your token appears to be invalid' }); @@ -33,13 +40,18 @@ class Route { if (iat && iat < moment(user.passwordEditedAt).format('x')) return res.status(401).json({ message: 'Token expired' }); if (!user.enabled) return res.status(401).json({ message: 'This account has been disabled' }); - return this.run(req, res, user); + return this.run(req, res, db, user); }); } - run(req, res, user) { // eslint-disable-line no-unused-vars + run(req, res, db) { // eslint-disable-line no-unused-vars return; } + + error(res, error) { + log.error(error); + return res.status(500).json({ message: 'There was a problem parsing the request' }); + } } module.exports = Route; diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index 5ead078..dc72558 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -1,4 +1,5 @@ -const config = require('../../../config'); +require('dotenv').config(); + const log = require('../utils/Log'); const express = require('express'); const helmet = require('helmet'); @@ -8,17 +9,16 @@ const bodyParser = require('body-parser'); const jetpack = require('fs-jetpack'); const path = require('path'); const Database = require('./Database'); -const oneliner = require('one-liner'); const rateLimiter = new RateLimit({ - windowMs: config.server.rateLimits.window, - max: config.server.rateLimits.max, + windowMs: process.env.RATE_LIMIT_WINDOW, + max: process.env.RATE_LIMIT_MAX, delayMs: 0 }); class Server { constructor() { - this.port = config.server.ports.backend; + this.port = process.env.SERVER_PORT; this.server = express(); this.server.set('trust proxy', 1); this.server.use(helmet()); @@ -36,12 +36,6 @@ class Server { // this.server.use(rateLimiter); this.routesFolder = path.join(__dirname, '..', 'routes'); this.database = new Database(); - this.server.get('/config', (req, res) => res.json({ - baseURL: config.backendLocation, - serviceName: config.serviceName, - maxFileSize: config.uploads.uploadMaxSize, - chunkSize: config.uploads.chunkSize - })); } registerAllTheRoutes() { @@ -51,24 +45,12 @@ class Server { if (Array.isArray(RouteClass)) routes = RouteClass; for (const File of routes) { const route = new File(); - this.server[route.method](config.server.routePrefix + route.path, route.authorize.bind(route)); - log.info(`Found route ${route.method.toUpperCase()} ${config.server.routePrefix}${route.path}`); + this.server[route.method](process.env.ROUTE_PREFIX + route.path, route.authorize.bind(route)); + log.info(`Found route ${route.method.toUpperCase()} ${process.env.ROUTE_PREFIX}${route.path}`); } }); } - writeFrontendConfig() { - const template = oneliner` - module.exports = { - baseURL: '${config.backendLocation}', - serviceName: '${config.serviceName}', - maxFileSize: '${config.uploads.uploadMaxSize}', - chunkSize: '${config.uploads.chunkSize}' - }`; - jetpack.write(path.join(__dirname, '..', '..', 'frontend', 'config.js'), template); - log.success('Frontend config file generated successfully'); - } - start() { jetpack.dir('uploads/chunks'); jetpack.dir('uploads/thumbs/square'); -- cgit v1.2.3 From 25c5a06ec3e363f5b98607949b76b8b395e4c962 Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 21 Feb 2019 23:05:56 +0900 Subject: derp --- src/api/structures/Route.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 32d576f..480763e 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -4,7 +4,7 @@ const db = require('knex')({ connection: { host: process.env.DB_HOST, user: process.env.DB_USER, - password: process.env.DB_PASS, + password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE } }); -- cgit v1.2.3 From 44e6fd31d2fa7761c90ff1d6932cf69d163b22e8 Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 21 Feb 2019 23:49:29 +0900 Subject: Database migration and seeding --- src/api/structures/Database.js | 112 ----------------------------------------- src/api/structures/Server.js | 2 - 2 files changed, 114 deletions(-) delete mode 100644 src/api/structures/Database.js (limited to 'src/api/structures') diff --git a/src/api/structures/Database.js b/src/api/structures/Database.js deleted file mode 100644 index 76ea006..0000000 --- a/src/api/structures/Database.js +++ /dev/null @@ -1,112 +0,0 @@ -const log = require('../utils/Log'); -const { server } = require('../../../config'); -const db = require('knex')(server.database); -const bcrypt = require('bcrypt'); -const moment = require('moment'); -const randomstring = require('randomstring'); - -class Database { - constructor() { - this.createTables(); - } - - async createTables() { - if (!await db.schema.hasTable('users')) { - await db.schema.createTable('users', table => { - table.increments(); - table.string('username'); - table.string('password'); - table.boolean('enabled').defaultTo(true); - table.boolean('isAdmin').defaultTo(false); - table.string('apiKey'); - table.timestamp('passwordEditedAt'); - table.timestamp('apiKeyEditedAt'); - table.timestamp('createdAt'); - table.timestamp('editedAt'); - }); - } - - if (!await db.schema.hasTable('albums')) { - await db.schema.createTable('albums', table => { - table.increments(); - table.integer('userId'); - table.string('name'); - // table.string('identifier'); - // table.boolean('enabled'); - // table.boolean('enableDownload').defaultTo(true); - table.timestamp('zippedAt'); - table.timestamp('createdAt'); - table.timestamp('editedAt'); - }); - } - - if (!await db.schema.hasTable('files')) { - await db.schema.createTable('files', table => { - table.increments(); - table.integer('userId'); - table.string('name'); - table.string('original'); - table.string('type'); - table.integer('size'); - table.string('hash'); - table.string('ip'); - table.timestamp('createdAt'); - table.timestamp('editedAt'); - }); - } - - if (!await db.schema.hasTable('links')) { - await db.schema.createTable('links', table => { - table.increments(); - table.integer('userId'); - table.integer('albumId'); - table.string('identifier'); - table.integer('views').defaultTo(0); - table.boolean('enabled').defaultTo(true); - table.boolean('enableDownload').defaultTo(true); - table.timestamp('expiresAt'); - table.timestamp('createdAt'); - table.timestamp('editedAt'); - }); - } - - if (!await db.schema.hasTable('albumsFiles')) { - await db.schema.createTable('albumsFiles', table => { - table.increments(); - table.integer('albumId'); - table.integer('fileId'); - }); - } - - if (!await db.schema.hasTable('albumsLinks')) { - await db.schema.createTable('albumsLinks', table => { - table.increments(); - table.integer('albumId'); - table.integer('linkId'); - }); - } - - const now = moment.utc().toDate(); - const user = await db.table('users').where({ username: 'root' }).first(); - if (user) return; - try { - const hash = await bcrypt.hash('root', 10); - await db.table('users').insert({ - username: 'root', - password: hash, - apiKey: randomstring.generate(64), - passwordEditedAt: now, - apiKeyEditedAt: now, - createdAt: now, - editedAt: now, - isAdmin: true - }); - log.success('Successfully created the root user with password "root". Make sure to log in and change it!'); - } catch (error) { - log.error(error); - if (error) log.error('Error generating password hash for root'); - } - } -} - -module.exports = Database; diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index dc72558..d2cc2f1 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -8,7 +8,6 @@ const RateLimit = require('express-rate-limit'); const bodyParser = require('body-parser'); const jetpack = require('fs-jetpack'); const path = require('path'); -const Database = require('./Database'); const rateLimiter = new RateLimit({ windowMs: process.env.RATE_LIMIT_WINDOW, @@ -35,7 +34,6 @@ class Server { this.server.use(bodyParser.json()); // this.server.use(rateLimiter); this.routesFolder = path.join(__dirname, '..', 'routes'); - this.database = new Database(); } registerAllTheRoutes() { -- cgit v1.2.3 From c7a4a39de4e6113e88f07fefb3668e9fd3b1372a Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 22 Feb 2019 00:00:07 +0900 Subject: Add support for sqlite --- src/api/structures/Route.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 480763e..4c89724 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -5,7 +5,8 @@ const db = require('knex')({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, - database: process.env.DB_DATABASE + database: process.env.DB_DATABASE, + filename: '../../../database.sqlite' } }); const moment = require('moment'); -- cgit v1.2.3 From 80a76d868fccc4639052a1cf605a495aa157dd72 Mon Sep 17 00:00:00 2001 From: Kana Date: Fri, 22 Feb 2019 15:06:29 +0900 Subject: Update Route.js --- src/api/structures/Route.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 4c89724..5466147 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -1,3 +1,4 @@ +const nodePath = require('path'); const JWT = require('jsonwebtoken'); const db = require('knex')({ client: process.env.DB_CLIENT, @@ -6,7 +7,7 @@ const db = require('knex')({ user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, - filename: '../../../database.sqlite' + filename: nodePath.join(__dirname, '..', '..', '..', 'database.sqlite') } }); const moment = require('moment'); -- cgit v1.2.3 From fc95cb7b0f047806937c25f0fc1104c72b0a32cb Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 23 Feb 2019 00:45:45 +0900 Subject: Better DB handling and stuff --- src/api/structures/Route.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 5466147..60c8b06 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -8,7 +8,8 @@ const db = require('knex')({ password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, filename: nodePath.join(__dirname, '..', '..', '..', 'database.sqlite') - } + }, + useNullAsDefault: process.env.DB_CLIENT === 'sqlite' ? true : false }); const moment = require('moment'); const log = require('../utils/Log'); -- cgit v1.2.3 From ab66e095a8255f38dba4661951cc0359f309c403 Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 26 Feb 2019 22:26:35 +0900 Subject: Added adminOnly routes --- src/api/structures/Route.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 60c8b06..a359488 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -33,7 +33,7 @@ class Route { return JWT.verify(token, process.env.SECRET, async (error, decoded) => { if (error) { log.error(error); - return res.status(401).json({ message: 'Your token appears to be invalid' }); + return res.status(401).json({ message: 'Invalid token' }); } const id = decoded ? decoded.sub : ''; const iat = decoded ? decoded.iat : ''; @@ -42,6 +42,7 @@ class Route { if (!user) return res.status(401).json({ message: 'Invalid authorization' }); if (iat && iat < moment(user.passwordEditedAt).format('x')) return res.status(401).json({ message: 'Token expired' }); if (!user.enabled) return res.status(401).json({ message: 'This account has been disabled' }); + if (this.options.adminOnly && !user.isAdmin) return res.status(401).json({ message: 'Invalid authorization' }); return this.run(req, res, db, user); }); -- cgit v1.2.3 From 73d85e8c7938e1db30da3cc4354b143d4a078473 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 2 Mar 2019 02:08:11 +0900 Subject: Enviroment variables parsing fix --- src/api/structures/Server.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index d2cc2f1..0dd22d7 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -10,14 +10,14 @@ const jetpack = require('fs-jetpack'); const path = require('path'); const rateLimiter = new RateLimit({ - windowMs: process.env.RATE_LIMIT_WINDOW, - max: process.env.RATE_LIMIT_MAX, + windowMs: parseInt(process.env.RATE_LIMIT_WINDOW, 10), + max: parseInt(process.env.RATE_LIMIT_MAX, 10), delayMs: 0 }); class Server { constructor() { - this.port = process.env.SERVER_PORT; + this.port = parseInt(process.env.SERVER_PORT, 10); this.server = express(); this.server.set('trust proxy', 1); this.server.use(helmet()); -- cgit v1.2.3 From 107d1f4750e8f82a628b528c4ec200e918be271d Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 19 Mar 2019 07:58:36 +0000 Subject: API key WIP --- src/api/structures/Route.js | 1 + 1 file changed, 1 insertion(+) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index a359488..19d33f9 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -26,6 +26,7 @@ class Route { authorize(req, res) { if (this.options.bypassAuth) return this.run(req, res, db); + console.log(req.headers); if (!req.headers.authorization) return res.status(401).json({ message: 'No authorization header provided' }); const token = req.headers.authorization.split(' ')[1]; if (!token) return res.status(401).json({ message: 'No authorization header provided' }); -- cgit v1.2.3 From 9aba5cd2216b7daf48850f430919db69f4925713 Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 29 Mar 2019 00:36:39 +0900 Subject: Fix --- src/api/structures/Server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index 0dd22d7..d10abc9 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -27,7 +27,7 @@ class Server { This bypasses the headers.accept for album download, since it's accesed directly through the browser. */ if (req.url.includes('/api/album/') && req.url.includes('/zip') && req.method === 'GET') return next(); - if (req.headers.accept === 'application/vnd.lolisafe.json') return next(); + if (req.headers.accept.includes('application/vnd.lolisafe.json')) return next(); return res.status(405).json({ message: 'Incorrect `Accept` header provided' }); }); this.server.use(bodyParser.urlencoded({ extended: true })); -- cgit v1.2.3 From b12cc4c28953ddef972193fd3986d6898bc4dba5 Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 29 Mar 2019 00:36:50 +0900 Subject: WIP apiKey validation --- src/api/structures/Route.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index a359488..ecb2be0 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -13,6 +13,7 @@ const db = require('knex')({ }); const moment = require('moment'); const log = require('../utils/Log'); +const bcrypt = require('bcrypt'); class Route { constructor(path, method, options) { @@ -26,6 +27,8 @@ class Route { authorize(req, res) { if (this.options.bypassAuth) return this.run(req, res, db); + if (req.headers.apiKey) return this.authorizeApiKey(req, res, req.headers.apiKey); + if (!req.headers.authorization) return res.status(401).json({ message: 'No authorization header provided' }); const token = req.headers.authorization.split(' ')[1]; if (!token) return res.status(401).json({ message: 'No authorization header provided' }); @@ -48,6 +51,17 @@ class Route { }); } + authorizeApiKey(req, res, apiKey) { + if (this.options.noApiKey) return res.status(401).json({ message: 'Api Key not allowed for this resource' }); + + /* + Need to read more into how api keys work before proceeding any further + + const comparePassword = await bcrypt.compare(password, user.password); + if (!comparePassword) return res.status(401).json({ message: 'Invalid authorization.' }); + */ + } + run(req, res, db) { // eslint-disable-line no-unused-vars return; } -- cgit v1.2.3 From 4b0966f857388ce5bfbd1ff04d51284647df593e Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 5 Apr 2019 06:05:21 +0000 Subject: Ditched sqlite. Use postgres or mysql/mariadb --- src/api/structures/Route.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 8a73454..960dc4b 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -1,4 +1,3 @@ -const nodePath = require('path'); const JWT = require('jsonwebtoken'); const db = require('knex')({ client: process.env.DB_CLIENT, @@ -6,10 +5,8 @@ const db = require('knex')({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, - database: process.env.DB_DATABASE, - filename: nodePath.join(__dirname, '..', '..', '..', 'database.sqlite') - }, - useNullAsDefault: process.env.DB_CLIENT === 'sqlite' ? true : false + database: process.env.DB_DATABASE + } }); const moment = require('moment'); const log = require('../utils/Log'); -- cgit v1.2.3 From 5dc7eda038d673767fbb0f872632695b42a21304 Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 16 Apr 2019 02:56:49 +0000 Subject: Check if accept header is passed --- src/api/structures/Server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index d10abc9..f8c6ad1 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -27,7 +27,7 @@ class Server { This bypasses the headers.accept for album download, since it's accesed directly through the browser. */ if (req.url.includes('/api/album/') && req.url.includes('/zip') && req.method === 'GET') return next(); - if (req.headers.accept.includes('application/vnd.lolisafe.json')) return next(); + if (req.headers.accept && req.headers.accept.includes('application/vnd.lolisafe.json')) return next(); return res.status(405).json({ message: 'Incorrect `Accept` header provided' }); }); this.server.use(bodyParser.urlencoded({ extended: true })); -- cgit v1.2.3 From ac36cdc143f2210a746b22391b2a9160ddb57dcb Mon Sep 17 00:00:00 2001 From: Pitu Date: Wed, 24 Apr 2019 08:38:53 +0000 Subject: Standarize database calls to support sqlite as well as mysql/postgres --- src/api/structures/Route.js | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 960dc4b..17f210e 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -1,3 +1,4 @@ +const nodePath = require('path'); const JWT = require('jsonwebtoken'); const db = require('knex')({ client: process.env.DB_CLIENT, @@ -5,8 +6,38 @@ const db = require('knex')({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, - database: process.env.DB_DATABASE - } + database: process.env.DB_DATABASE, + filename: nodePath.join(__dirname, '..', '..', '..', 'database.sqlite') + }, + postProcessResponse: result => { + /* + Fun fact: Depending on the database used by the user and given that I don't want + to force a specific database for everyone because of the nature of this project, + some things like different data types for booleans need to be considered like in + the implementation below where sqlite returns 1 and 0 instead of true and false. + */ + const booleanFields = [ + 'enabled', + 'enableDownload', + 'isAdmin' + ]; + + const processResponse = row => { + Object.keys(row).forEach(key => { + if (booleanFields.includes(key)) { + row[key] = row[key] === 1 ? true : false; + } + }); + return row; + }; + + if (Array.isArray(result)) { + return result.map(row => processResponse(row)); + } + + return processResponse(result); + }, + useNullAsDefault: process.env.DB_CLIENT === 'sqlite3' ? true : false }); const moment = require('moment'); const log = require('../utils/Log'); -- cgit v1.2.3 From fec273b23b2d5792d0900151bb17a33ad3c8d9dc Mon Sep 17 00:00:00 2001 From: Pitu Date: Wed, 24 Apr 2019 08:41:49 +0000 Subject: Fix when response is not an object --- src/api/structures/Route.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 17f210e..cb2878b 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -31,11 +31,9 @@ const db = require('knex')({ return row; }; - if (Array.isArray(result)) { - return result.map(row => processResponse(row)); - } - - return processResponse(result); + if (Array.isArray(result)) return result.map(row => processResponse(row)); + if (typeof result === 'object') return processResponse(result); + return result; }, useNullAsDefault: process.env.DB_CLIENT === 'sqlite3' ? true : false }); -- cgit v1.2.3 From c074b5e1971c26a69c3f4801f92e8a1c1ad072cd Mon Sep 17 00:00:00 2001 From: Pitu Date: Wed, 24 Apr 2019 09:28:30 +0000 Subject: Fix database value conversion --- src/api/structures/Route.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index cb2878b..c04c585 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -25,7 +25,8 @@ const db = require('knex')({ const processResponse = row => { Object.keys(row).forEach(key => { if (booleanFields.includes(key)) { - row[key] = row[key] === 1 ? true : false; + if (row[key] === 0) row[key] = false; + else if (row[key] === 1) row[key] = true; } }); return row; -- cgit v1.2.3 From 8e4f1b7838e3c43320f2e25e691c1808ae3c4089 Mon Sep 17 00:00:00 2001 From: Pitu Date: Mon, 30 Sep 2019 07:06:22 +0000 Subject: feature: album links --- src/api/structures/Server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index f8c6ad1..50f6754 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -26,7 +26,7 @@ class Server { /* This bypasses the headers.accept for album download, since it's accesed directly through the browser. */ - if (req.url.includes('/api/album/') && req.url.includes('/zip') && req.method === 'GET') return next(); + if ((req.url.includes('/api/album/') || req.url.includes('/zip')) && req.method === 'GET') return next(); if (req.headers.accept && req.headers.accept.includes('application/vnd.lolisafe.json')) return next(); return res.status(405).json({ message: 'Incorrect `Accept` header provided' }); }); -- cgit v1.2.3 From cba7bf8586f59a049f79aba586db201ac6f3530b Mon Sep 17 00:00:00 2001 From: Pitu Date: Sun, 13 Oct 2019 02:53:45 +0900 Subject: This commit adds a bunch of features for admins: * banning IP * see files from other users if you are admin * be able to see details of an uploaded file and it's user * improved display of thumbnails for non-image files --- src/api/structures/Route.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index c04c585..2db9bc6 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -52,7 +52,10 @@ class Route { this.options = options || {}; } - authorize(req, res) { + async authorize(req, res) { + const banned = await db.table('bans').where({ ip: req.ip }).first(); + if (banned) return res.status(401).json({ message: 'This IP has been banned from using the service.' }); + if (this.options.bypassAuth) return this.run(req, res, db); if (req.headers.apiKey) return this.authorizeApiKey(req, res, req.headers.apiKey); if (!req.headers.authorization) return res.status(401).json({ message: 'No authorization header provided' }); -- cgit v1.2.3 From c114e59be329fa9ceb8f1f8e79356a0e3afbd1ae Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 9 May 2020 19:21:20 +0900 Subject: Feature: * Frontend is now served by the API process * Only 1 process spawns for lolisafe to work * Switched frontend from server-side render to static site, now saved in `/dist` --- src/api/structures/Server.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index 50f6754..c80c44f 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -23,16 +23,23 @@ class Server { this.server.use(helmet()); this.server.use(cors({ allowedHeaders: ['Accept', 'Authorization', 'Cache-Control', 'X-Requested-With', 'Content-Type', 'albumId'] })); this.server.use((req, res, next) => { - /* - This bypasses the headers.accept for album download, since it's accesed directly through the browser. - */ + // This bypasses the headers.accept for album download, since it's accesed directly through the browser. if ((req.url.includes('/api/album/') || req.url.includes('/zip')) && req.method === 'GET') return next(); + // This bypasses the headers.accept if we are accessing the frontend + if (!req.url.includes('/api/') && req.method === 'GET') return next(); if (req.headers.accept && req.headers.accept.includes('application/vnd.lolisafe.json')) return next(); return res.status(405).json({ message: 'Incorrect `Accept` header provided' }); }); this.server.use(bodyParser.urlencoded({ extended: true })); this.server.use(bodyParser.json()); // this.server.use(rateLimiter); + + // Serve the frontend if we are in production mode + if (process.env.NODE_ENV === 'production') { + this.server.use(express.static(path.join(__dirname, '..', '..', '..', 'dist'))); + this.server.use(express.static(path.join(__dirname, '..', '..', '..', 'uploads'))); + } + this.routesFolder = path.join(__dirname, '..', 'routes'); } -- cgit v1.2.3 From 4c52932426a3e91a205940a6ab08bfee3e23fadf Mon Sep 17 00:00:00 2001 From: Pitu Date: Sun, 10 May 2020 20:02:48 +0900 Subject: Features: * Serve files during development * Own endpoint for fetching the albums of a file --- src/api/structures/Server.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index c80c44f..44d4e44 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -37,8 +37,9 @@ class Server { // Serve the frontend if we are in production mode if (process.env.NODE_ENV === 'production') { this.server.use(express.static(path.join(__dirname, '..', '..', '..', 'dist'))); - this.server.use(express.static(path.join(__dirname, '..', '..', '..', 'uploads'))); } + // Serve the uploads + this.server.use(express.static(path.join(__dirname, '..', '..', '..', 'uploads'))); this.routesFolder = path.join(__dirname, '..', 'routes'); } -- cgit v1.2.3 From 496477ebda3f6c347a9944e22daae447d15ebc31 Mon Sep 17 00:00:00 2001 From: Pitu Date: Mon, 11 May 2020 00:57:56 +0900 Subject: Feature: enable apiKey access to uploads and album fetching for the uploader/sharex/3rd party --- src/api/structures/Route.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 2db9bc6..23a3522 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -57,7 +57,9 @@ class Route { if (banned) return res.status(401).json({ message: 'This IP has been banned from using the service.' }); if (this.options.bypassAuth) return this.run(req, res, db); - if (req.headers.apiKey) return this.authorizeApiKey(req, res, req.headers.apiKey); + // The only reason I call it token here and not Api Key is to be backwards compatible with the uploader and sharex + // Small price to pay. + if (req.headers.token) return this.authorizeApiKey(req, res, req.headers.token); if (!req.headers.authorization) return res.status(401).json({ message: 'No authorization header provided' }); const token = req.headers.authorization.split(' ')[1]; @@ -81,15 +83,13 @@ class Route { }); } - authorizeApiKey(req, res, apiKey) { - if (this.options.noApiKey) return res.status(401).json({ message: 'Api Key not allowed for this resource' }); + async authorizeApiKey(req, res, apiKey) { + if (!this.options.canApiKey) return res.status(401).json({ message: 'Api Key not allowed for this resource' }); + const user = await db.table('users').where({ apiKey }).first(); + if (!user) return res.status(401).json({ message: 'Invalid authorization' }); + if (!user.enabled) return res.status(401).json({ message: 'This account has been disabled' }); - /* - Need to read more into how api keys work before proceeding any further - - const comparePassword = await bcrypt.compare(password, user.password); - if (!comparePassword) return res.status(401).json({ message: 'Invalid authorization.' }); - */ + return this.run(req, res, db, user); } run(req, res, db) { // eslint-disable-line no-unused-vars -- cgit v1.2.3 From f189ddf9e6e1dda0c8e56afd367bc378ee19b8fd Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 25 Jun 2020 02:05:48 +0900 Subject: Cleanup --- src/api/structures/Route.js | 1 - 1 file changed, 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 23a3522..8956c24 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -40,7 +40,6 @@ const db = require('knex')({ }); const moment = require('moment'); const log = require('../utils/Log'); -const bcrypt = require('bcrypt'); class Route { constructor(path, method, options) { -- cgit v1.2.3 From 207fc916d960b04190f7a971d672000fbd934baf Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 25 Jun 2020 02:06:11 +0900 Subject: Handle nuxt routes on page load --- src/api/structures/Server.js | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index 44d4e44..a8eccd9 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -34,13 +34,8 @@ class Server { this.server.use(bodyParser.json()); // this.server.use(rateLimiter); - // Serve the frontend if we are in production mode - if (process.env.NODE_ENV === 'production') { - this.server.use(express.static(path.join(__dirname, '..', '..', '..', 'dist'))); - } // Serve the uploads this.server.use(express.static(path.join(__dirname, '..', '..', '..', 'uploads'))); - this.routesFolder = path.join(__dirname, '..', 'routes'); } @@ -57,10 +52,32 @@ class Server { }); } + serveNuxt() { + // Serve the frontend if we are in production mode + if (process.env.NODE_ENV === 'production') { + this.server.use(express.static(path.join(__dirname, '..', '..', '..', 'dist'))); + } + + /* + For vue router to work with express we need this fallback. + After all the routes are loaded and the static files handled and if the + user is trying to access a non-mapped route we serve the website instead + since it has routes of it's own that don't work if accessed directly + */ + this.server.all('*', (_req, res) => { + try { + res.sendFile(path.join(__dirname, '..', '..', '..', 'dist', 'index.html')); + } catch (error) { + res.json({ success: false, message: 'Something went wrong' }); + } + }); + } + start() { jetpack.dir('uploads/chunks'); jetpack.dir('uploads/thumbs/square'); this.registerAllTheRoutes(); + this.serveNuxt(); this.server.listen(this.port, () => { log.success(`Backend ready and listening on port ${this.port}`); }); -- cgit v1.2.3 From dd46f79550d8e7a2f7a0364cc0fb8e7a38ed4aba Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Thu, 2 Jul 2020 23:40:35 +0300 Subject: feat: return APIKey when fetching user --- src/api/structures/Route.js | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 8956c24..2402481 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -7,7 +7,7 @@ const db = require('knex')({ user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, - filename: nodePath.join(__dirname, '..', '..', '..', 'database.sqlite') + filename: nodePath.join(__dirname, '../../../database.sqlite') }, postProcessResponse: result => { /* @@ -16,11 +16,7 @@ const db = require('knex')({ some things like different data types for booleans need to be considered like in the implementation below where sqlite returns 1 and 0 instead of true and false. */ - const booleanFields = [ - 'enabled', - 'enableDownload', - 'isAdmin' - ]; + const booleanFields = ['enabled', 'enableDownload', 'isAdmin']; const processResponse = row => { Object.keys(row).forEach(key => { @@ -52,7 +48,10 @@ class Route { } async authorize(req, res) { - const banned = await db.table('bans').where({ ip: req.ip }).first(); + const banned = await db + .table('bans') + .where({ ip: req.ip }) + .first(); if (banned) return res.status(401).json({ message: 'This IP has been banned from using the service.' }); if (this.options.bypassAuth) return this.run(req, res, db); @@ -72,11 +71,16 @@ class Route { const id = decoded ? decoded.sub : ''; const iat = decoded ? decoded.iat : ''; - const user = await db.table('users').where({ id }).first(); + const user = await db + .table('users') + .where({ id }) + .first(); if (!user) return res.status(401).json({ message: 'Invalid authorization' }); - if (iat && iat < moment(user.passwordEditedAt).format('x')) return res.status(401).json({ message: 'Token expired' }); + if (iat && iat < moment(user.passwordEditedAt).format('x')) + return res.status(401).json({ message: 'Token expired' }); if (!user.enabled) return res.status(401).json({ message: 'This account has been disabled' }); - if (this.options.adminOnly && !user.isAdmin) return res.status(401).json({ message: 'Invalid authorization' }); + if (this.options.adminOnly && !user.isAdmin) + return res.status(401).json({ message: 'Invalid authorization' }); return this.run(req, res, db, user); }); @@ -84,14 +88,18 @@ class Route { async authorizeApiKey(req, res, apiKey) { if (!this.options.canApiKey) return res.status(401).json({ message: 'Api Key not allowed for this resource' }); - const user = await db.table('users').where({ apiKey }).first(); + const user = await db + .table('users') + .where({ apiKey }) + .first(); if (!user) return res.status(401).json({ message: 'Invalid authorization' }); if (!user.enabled) return res.status(401).json({ message: 'This account has been disabled' }); return this.run(req, res, db, user); } - run(req, res, db) { // eslint-disable-line no-unused-vars + run(req, res, db) { + // eslint-disable-line no-unused-vars return; } -- cgit v1.2.3 From 49d3e3b203ee287a53beb2a04faa8bf38ace6834 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Wed, 8 Jul 2020 03:15:27 +0300 Subject: feat: add morgan for logging requests if env is not production --- src/api/structures/Route.js | 20 +++++++++----------- src/api/structures/Server.js | 40 ++++++++++++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 19 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 2402481..c2ad32e 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -7,9 +7,9 @@ const db = require('knex')({ user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, - filename: nodePath.join(__dirname, '../../../database.sqlite') + filename: nodePath.join(__dirname, '../../../database.sqlite'), }, - postProcessResponse: result => { + postProcessResponse: (result) => { /* Fun fact: Depending on the database used by the user and given that I don't want to force a specific database for everyone because of the nature of this project, @@ -18,8 +18,8 @@ const db = require('knex')({ */ const booleanFields = ['enabled', 'enableDownload', 'isAdmin']; - const processResponse = row => { - Object.keys(row).forEach(key => { + const processResponse = (row) => { + Object.keys(row).forEach((key) => { if (booleanFields.includes(key)) { if (row[key] === 0) row[key] = false; else if (row[key] === 1) row[key] = true; @@ -28,11 +28,11 @@ const db = require('knex')({ return row; }; - if (Array.isArray(result)) return result.map(row => processResponse(row)); + if (Array.isArray(result)) return result.map((row) => processResponse(row)); if (typeof result === 'object') return processResponse(result); return result; }, - useNullAsDefault: process.env.DB_CLIENT === 'sqlite3' ? true : false + useNullAsDefault: process.env.DB_CLIENT === 'sqlite3', }); const moment = require('moment'); const log = require('../utils/Log'); @@ -76,11 +76,9 @@ class Route { .where({ id }) .first(); if (!user) return res.status(401).json({ message: 'Invalid authorization' }); - if (iat && iat < moment(user.passwordEditedAt).format('x')) - return res.status(401).json({ message: 'Token expired' }); + if (iat && iat < moment(user.passwordEditedAt).format('x')) { return res.status(401).json({ message: 'Token expired' }); } if (!user.enabled) return res.status(401).json({ message: 'This account has been disabled' }); - if (this.options.adminOnly && !user.isAdmin) - return res.status(401).json({ message: 'Invalid authorization' }); + if (this.options.adminOnly && !user.isAdmin) { return res.status(401).json({ message: 'Invalid authorization' }); } return this.run(req, res, db, user); }); @@ -100,7 +98,7 @@ class Route { run(req, res, db) { // eslint-disable-line no-unused-vars - return; + } error(res, error) { diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index a8eccd9..5d2290b 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -1,6 +1,5 @@ require('dotenv').config(); -const log = require('../utils/Log'); const express = require('express'); const helmet = require('helmet'); const cors = require('cors'); @@ -8,11 +7,14 @@ const RateLimit = require('express-rate-limit'); const bodyParser = require('body-parser'); const jetpack = require('fs-jetpack'); const path = require('path'); +const morgan = require('morgan'); +const log = require('../utils/Log'); +const ThumbUtil = require('../utils/ThumbUtil'); const rateLimiter = new RateLimit({ windowMs: parseInt(process.env.RATE_LIMIT_WINDOW, 10), max: parseInt(process.env.RATE_LIMIT_MAX, 10), - delayMs: 0 + delayMs: 0, }); class Server { @@ -32,16 +34,38 @@ class Server { }); this.server.use(bodyParser.urlencoded({ extended: true })); this.server.use(bodyParser.json()); + if (process.env.NODE_ENV !== 'production') { + this.server.use(morgan('combined', { + skip(req) { + let ext = req.path.split('.').pop(); + if (ext) { ext = `.${ext.toLowerCase()}`; } + + if ( + ThumbUtil.imageExtensions.indexOf(ext) > -1 + || ThumbUtil.videoExtensions.indexOf(ext) > -1 + || req.path.indexOf('_nuxt') > -1 + || req.path.indexOf('favicon.ico') > -1 + ) { + return true; + } + return false; + }, + 'stream': { + write(str) { log.debug(str); }, + }, + })); + } // this.server.use(rateLimiter); // Serve the uploads - this.server.use(express.static(path.join(__dirname, '..', '..', '..', 'uploads'))); - this.routesFolder = path.join(__dirname, '..', 'routes'); + this.server.use(express.static(path.join(__dirname, '../../../uploads'))); + this.routesFolder = path.join(__dirname, '../routes'); } registerAllTheRoutes() { - jetpack.find(this.routesFolder, { matching: '*.js' }).forEach(routeFile => { - const RouteClass = require(path.join('..', '..', '..', routeFile)); + jetpack.find(this.routesFolder, { matching: '*.js' }).forEach((routeFile) => { + // eslint-disable-next-line import/no-dynamic-require, global-require + const RouteClass = require(path.join('../../../', routeFile)); let routes = [RouteClass]; if (Array.isArray(RouteClass)) routes = RouteClass; for (const File of routes) { @@ -55,7 +79,7 @@ class Server { serveNuxt() { // Serve the frontend if we are in production mode if (process.env.NODE_ENV === 'production') { - this.server.use(express.static(path.join(__dirname, '..', '..', '..', 'dist'))); + this.server.use(express.static(path.join(__dirname, '../../../dist'))); } /* @@ -66,7 +90,7 @@ class Server { */ this.server.all('*', (_req, res) => { try { - res.sendFile(path.join(__dirname, '..', '..', '..', 'dist', 'index.html')); + res.sendFile(path.join(__dirname, '../../../dist/index.html')); } catch (error) { res.json({ success: false, message: 'Something went wrong' }); } -- cgit v1.2.3 From ad852de51a0d2dd5d29c08838d5a430c58849e74 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Wed, 8 Jul 2020 04:00:12 +0300 Subject: chore: linter the entire project using the new rules --- src/api/structures/Route.js | 8 +++----- src/api/structures/Server.js | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index c2ad32e..400ae3d 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -55,7 +55,8 @@ class Route { if (banned) return res.status(401).json({ message: 'This IP has been banned from using the service.' }); if (this.options.bypassAuth) return this.run(req, res, db); - // The only reason I call it token here and not Api Key is to be backwards compatible with the uploader and sharex + // The only reason I call it token here and not Api Key is to be backwards compatible + // with the uploader and sharex // Small price to pay. if (req.headers.token) return this.authorizeApiKey(req, res, req.headers.token); if (!req.headers.authorization) return res.status(401).json({ message: 'No authorization header provided' }); @@ -96,10 +97,7 @@ class Route { return this.run(req, res, db, user); } - run(req, res, db) { - // eslint-disable-line no-unused-vars - - } + run() {} error(res, error) { log.error(error); diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index 5d2290b..c8537fb 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -11,6 +11,7 @@ const morgan = require('morgan'); const log = require('../utils/Log'); const ThumbUtil = require('../utils/ThumbUtil'); +// eslint-disable-next-line no-unused-vars const rateLimiter = new RateLimit({ windowMs: parseInt(process.env.RATE_LIMIT_WINDOW, 10), max: parseInt(process.env.RATE_LIMIT_MAX, 10), -- cgit v1.2.3 From 746a4546122be2ed79ad5858de6ce2c686f78ef0 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Thu, 9 Jul 2020 02:22:08 +0300 Subject: fix: stop leaking user's password and their apikey to admins --- src/api/structures/Route.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 400ae3d..6be0dc7 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -77,7 +77,9 @@ class Route { .where({ id }) .first(); if (!user) return res.status(401).json({ message: 'Invalid authorization' }); - if (iat && iat < moment(user.passwordEditedAt).format('x')) { return res.status(401).json({ message: 'Token expired' }); } + if (iat && iat < moment(user.passwordEditedAt).format('x')) { + return res.status(401).json({ message: 'Token expired' }); + } if (!user.enabled) return res.status(401).json({ message: 'This account has been disabled' }); if (this.options.adminOnly && !user.isAdmin) { return res.status(401).json({ message: 'Invalid authorization' }); } -- cgit v1.2.3 From 2d06d918a154c15196ca92fb8f7873ca3c797f00 Mon Sep 17 00:00:00 2001 From: Pitu Date: Sat, 18 Jul 2020 02:21:31 +0900 Subject: Timeout, package and docs cleanup --- src/api/structures/Server.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index a8eccd9..2039ed5 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -78,9 +78,10 @@ class Server { jetpack.dir('uploads/thumbs/square'); this.registerAllTheRoutes(); this.serveNuxt(); - this.server.listen(this.port, () => { + const server = this.server.listen(this.port, () => { log.success(`Backend ready and listening on port ${this.port}`); }); + server.setTimeout(600000); } } -- cgit v1.2.3 From 6fee07d9e15fb785721d9c6870231f1d0c95f10c Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Mon, 20 Jul 2020 21:29:06 +0300 Subject: fix: don't crash the server if a route fails to load --- src/api/structures/Server.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index 7f1b4d5..e25e089 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -70,9 +70,13 @@ class Server { let routes = [RouteClass]; if (Array.isArray(RouteClass)) routes = RouteClass; for (const File of routes) { - const route = new File(); - this.server[route.method](process.env.ROUTE_PREFIX + route.path, route.authorize.bind(route)); - log.info(`Found route ${route.method.toUpperCase()} ${process.env.ROUTE_PREFIX}${route.path}`); + try { + const route = new File(); + this.server[route.method](process.env.ROUTE_PREFIX + route.path, route.authorize.bind(route)); + log.info(`Found route ${route.method.toUpperCase()} ${process.env.ROUTE_PREFIX}${route.path}`); + } catch (e) { + log.error(`Failed loading route from file ${routeFile} with error: ${e.message}`); + } } }); } -- cgit v1.2.3 From 90001c2df56d58e69fd199a518ae7f3e4ed327fc Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Thu, 24 Dec 2020 10:40:50 +0200 Subject: chore: remove trailing commas --- src/api/structures/Route.js | 4 ++-- src/api/structures/Server.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 6be0dc7..74589c5 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -7,7 +7,7 @@ const db = require('knex')({ user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, - filename: nodePath.join(__dirname, '../../../database.sqlite'), + filename: nodePath.join(__dirname, '../../../database.sqlite') }, postProcessResponse: (result) => { /* @@ -32,7 +32,7 @@ const db = require('knex')({ if (typeof result === 'object') return processResponse(result); return result; }, - useNullAsDefault: process.env.DB_CLIENT === 'sqlite3', + useNullAsDefault: process.env.DB_CLIENT === 'sqlite3' }); const moment = require('moment'); const log = require('../utils/Log'); diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index e25e089..83b2880 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -15,7 +15,7 @@ const ThumbUtil = require('../utils/ThumbUtil'); const rateLimiter = new RateLimit({ windowMs: parseInt(process.env.RATE_LIMIT_WINDOW, 10), max: parseInt(process.env.RATE_LIMIT_MAX, 10), - delayMs: 0, + delayMs: 0 }); class Server { @@ -52,8 +52,8 @@ class Server { return false; }, 'stream': { - write(str) { log.debug(str); }, - }, + write(str) { log.debug(str); } + } })); } // this.server.use(rateLimiter); -- cgit v1.2.3 From fb2c27086f570fec60f4d52dcc9ca80e53186293 Mon Sep 17 00:00:00 2001 From: Pitu Date: Thu, 24 Dec 2020 23:45:16 +0900 Subject: Fix ESLint rules once and for all --- src/api/structures/Route.js | 8 ++++---- src/api/structures/Server.js | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 74589c5..3806325 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -9,7 +9,7 @@ const db = require('knex')({ database: process.env.DB_DATABASE, filename: nodePath.join(__dirname, '../../../database.sqlite') }, - postProcessResponse: (result) => { + postProcessResponse: result => { /* Fun fact: Depending on the database used by the user and given that I don't want to force a specific database for everyone because of the nature of this project, @@ -18,8 +18,8 @@ const db = require('knex')({ */ const booleanFields = ['enabled', 'enableDownload', 'isAdmin']; - const processResponse = (row) => { - Object.keys(row).forEach((key) => { + const processResponse = row => { + Object.keys(row).forEach(key => { if (booleanFields.includes(key)) { if (row[key] === 0) row[key] = false; else if (row[key] === 1) row[key] = true; @@ -28,7 +28,7 @@ const db = require('knex')({ return row; }; - if (Array.isArray(result)) return result.map((row) => processResponse(row)); + if (Array.isArray(result)) return result.map(row => processResponse(row)); if (typeof result === 'object') return processResponse(result); return result; }, diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index 83b2880..0ef91fd 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -42,16 +42,16 @@ class Server { if (ext) { ext = `.${ext.toLowerCase()}`; } if ( - ThumbUtil.imageExtensions.indexOf(ext) > -1 - || ThumbUtil.videoExtensions.indexOf(ext) > -1 - || req.path.indexOf('_nuxt') > -1 - || req.path.indexOf('favicon.ico') > -1 + ThumbUtil.imageExtensions.indexOf(ext) > -1 || + ThumbUtil.videoExtensions.indexOf(ext) > -1 || + req.path.indexOf('_nuxt') > -1 || + req.path.indexOf('favicon.ico') > -1 ) { return true; } return false; }, - 'stream': { + stream: { write(str) { log.debug(str); } } })); @@ -64,7 +64,7 @@ class Server { } registerAllTheRoutes() { - jetpack.find(this.routesFolder, { matching: '*.js' }).forEach((routeFile) => { + jetpack.find(this.routesFolder, { matching: '*.js' }).forEach(routeFile => { // eslint-disable-next-line import/no-dynamic-require, global-require const RouteClass = require(path.join('../../../', routeFile)); let routes = [RouteClass]; -- cgit v1.2.3 From 09d8d02e6c11bb4aea9cd129bf195868bab0738f Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 25 Dec 2020 02:08:54 +0900 Subject: Cleanup --- src/api/structures/Server.js | 23 ----------------------- 1 file changed, 23 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index 0ef91fd..6a4abaa 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -7,9 +7,7 @@ const RateLimit = require('express-rate-limit'); const bodyParser = require('body-parser'); const jetpack = require('fs-jetpack'); const path = require('path'); -const morgan = require('morgan'); const log = require('../utils/Log'); -const ThumbUtil = require('../utils/ThumbUtil'); // eslint-disable-next-line no-unused-vars const rateLimiter = new RateLimit({ @@ -35,27 +33,6 @@ class Server { }); this.server.use(bodyParser.urlencoded({ extended: true })); this.server.use(bodyParser.json()); - if (process.env.NODE_ENV !== 'production') { - this.server.use(morgan('combined', { - skip(req) { - let ext = req.path.split('.').pop(); - if (ext) { ext = `.${ext.toLowerCase()}`; } - - if ( - ThumbUtil.imageExtensions.indexOf(ext) > -1 || - ThumbUtil.videoExtensions.indexOf(ext) > -1 || - req.path.indexOf('_nuxt') > -1 || - req.path.indexOf('favicon.ico') > -1 - ) { - return true; - } - return false; - }, - stream: { - write(str) { log.debug(str); } - } - })); - } // this.server.use(rateLimiter); // Serve the uploads -- cgit v1.2.3 From 3051fbe9480f367be93bdcca45104be7b7d69bd8 Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 25 Dec 2020 02:54:05 +0900 Subject: Feat: add rotating logs when running in production env --- src/api/structures/Server.js | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index 6a4abaa..cf2a781 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -7,6 +7,8 @@ const RateLimit = require('express-rate-limit'); const bodyParser = require('body-parser'); const jetpack = require('fs-jetpack'); const path = require('path'); +const morgan = require('morgan'); +const rfs = require('rotating-file-stream'); const log = require('../utils/Log'); // eslint-disable-next-line no-unused-vars @@ -33,6 +35,14 @@ class Server { }); this.server.use(bodyParser.urlencoded({ extended: true })); this.server.use(bodyParser.json()); + + if (process.env.NODE_ENV === 'production') { + const accessLogStream = rfs.createStream('access.log', { + interval: '1d', // rotate daily + path: path.join(__dirname, '../../../logs', 'log') + }); + this.server.use(morgan('combined', { stream: accessLogStream })); + } // this.server.use(rateLimiter); // Serve the uploads -- cgit v1.2.3 From 493e05df27ba3b2c6fbd36547f0c7aa1699e038c Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 25 Dec 2020 03:08:53 +0900 Subject: Fix: thumbnail creation --- src/api/structures/Server.js | 1 + 1 file changed, 1 insertion(+) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index cf2a781..865419d 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -92,6 +92,7 @@ class Server { start() { jetpack.dir('uploads/chunks'); jetpack.dir('uploads/thumbs/square'); + jetpack.dir('uploads/thumbs/preview'); this.registerAllTheRoutes(); this.serveNuxt(); const server = this.server.listen(this.port, () => { -- cgit v1.2.3 From f73cde6bb501d72e46f0aadf95e6809e9d265e5b Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 25 Dec 2020 03:58:19 +0900 Subject: Chore: Move database to a subfolder for docker purposes --- src/api/structures/Route.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 3806325..ff69e77 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -7,7 +7,7 @@ const db = require('knex')({ user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, - filename: nodePath.join(__dirname, '../../../database.sqlite') + filename: nodePath.join(__dirname, '../../../database/database.sqlite') }, postProcessResponse: result => { /* -- cgit v1.2.3 From 5c2f6782ddeafec5503e5b7187420afc503cd865 Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 25 Dec 2020 20:09:17 +0900 Subject: Chore: prevent server from starting if .env config missing --- src/api/structures/Server.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index 865419d..cc1064f 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -1,5 +1,10 @@ require('dotenv').config(); +if (!process.env.SERVER_PORT) { + console.log('Run the setup script first or fill the .env file manually before starting'); + process.exit(0); +} + const express = require('express'); const helmet = require('helmet'); const cors = require('cors'); @@ -43,7 +48,9 @@ class Server { }); this.server.use(morgan('combined', { stream: accessLogStream })); } - // this.server.use(rateLimiter); + + // Apply rate limiting to the api only + this.server.use('/api/', rateLimiter); // Serve the uploads this.server.use(express.static(path.join(__dirname, '../../../uploads'))); @@ -52,7 +59,6 @@ class Server { registerAllTheRoutes() { jetpack.find(this.routesFolder, { matching: '*.js' }).forEach(routeFile => { - // eslint-disable-next-line import/no-dynamic-require, global-require const RouteClass = require(path.join('../../../', routeFile)); let routes = [RouteClass]; if (Array.isArray(RouteClass)) routes = RouteClass; -- cgit v1.2.3 From ec2f9e0d989792c1760b48e063467cf6e59c580a Mon Sep 17 00:00:00 2001 From: Pitu Date: Fri, 25 Dec 2020 20:45:22 +0900 Subject: Rebrand --- src/api/structures/Server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index cc1064f..b8952a9 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -35,7 +35,7 @@ class Server { if ((req.url.includes('/api/album/') || req.url.includes('/zip')) && req.method === 'GET') return next(); // This bypasses the headers.accept if we are accessing the frontend if (!req.url.includes('/api/') && req.method === 'GET') return next(); - if (req.headers.accept && req.headers.accept.includes('application/vnd.lolisafe.json')) return next(); + if (req.headers.accept && req.headers.accept.includes('application/vnd.chibisafe.json')) return next(); return res.status(405).json({ message: 'Incorrect `Accept` header provided' }); }); this.server.use(bodyParser.urlencoded({ extended: true })); -- cgit v1.2.3 From 13058d99d658c0920ce75b79d6b24df18a873ea9 Mon Sep 17 00:00:00 2001 From: Zephyrrus Date: Sun, 27 Dec 2020 18:18:06 +0200 Subject: fix: nsfw album toggle doesn't propagate the changes properly fix: add nsfw flag to the booleanFields in knex postProcessResponse --- src/api/structures/Route.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api/structures') diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index ff69e77..bb7ba87 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -16,7 +16,7 @@ const db = require('knex')({ some things like different data types for booleans need to be considered like in the implementation below where sqlite returns 1 and 0 instead of true and false. */ - const booleanFields = ['enabled', 'enableDownload', 'isAdmin']; + const booleanFields = ['enabled', 'enableDownload', 'isAdmin', 'nsfw']; const processResponse = row => { Object.keys(row).forEach(key => { -- cgit v1.2.3