From d3c80127ecdc83cffb9ba9a05f47452fec60287c Mon Sep 17 00:00:00 2001 From: Pitu Date: Tue, 15 Jun 2021 00:12:26 +0900 Subject: [PATCH] chore: update process.env usage --- nuxt.config.js | 4 - src/api/routes/albums/albumZipGET.js | 8 +- src/api/routes/auth/loginPOST.js | 3 +- src/api/routes/auth/registerPOST.js | 2 +- src/api/routes/service/configGET.js | 17 ++--- src/api/routes/uploads/uploadPOST.js | 8 +- src/api/structures/Route.js | 3 +- src/api/structures/Server.js | 19 +++-- src/api/utils/ThumbUtil.js | 11 +-- src/api/utils/Util.js | 51 +++++-------- src/api/utils/generateThumbs.js | 2 +- src/setup.js | 110 ++++----------------------- 12 files changed, 76 insertions(+), 162 deletions(-) diff --git a/nuxt.config.js b/nuxt.config.js index 09e5a73..561be15 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -10,16 +10,12 @@ const clientConfig = { serviceName: process.env.SERVICE_NAME, maxFileSize: parseInt(process.env.MAX_SIZE, 10), chunkSize: parseInt(process.env.CHUNK_SIZE, 10), - maxLinksPerAlbum: parseInt(process.env.MAX_LINKS_PER_ALBUM, 10), publicMode: process.env.PUBLIC_MODE === 'true', userAccounts: process.env.USER_ACCOUNTS === 'true' }; export default { ssr: true, - server: { - port: process.env.WEBSITE_PORT - }, srcDir: 'src/site/', head: { title: process.env.SERVICE_NAME, diff --git a/src/api/routes/albums/albumZipGET.js b/src/api/routes/albums/albumZipGET.js index 22b0b6f..8def099 100644 --- a/src/api/routes/albums/albumZipGET.js +++ b/src/api/routes/albums/albumZipGET.js @@ -38,13 +38,13 @@ class albumGET extends Route { If the date when the album was zipped is greater than the album's last edit, we just send the zip to the user */ if (album.zippedAt > album.editedAt) { - const filePath = path.join(__dirname, '../../../../', process.env.UPLOAD_FOLDER, 'zips', `${album.userId}-${album.id}.zip`); + const filePath = path.join(__dirname, '../../../../uploads', 'zips', `${album.userId}-${album.id}.zip`); const exists = await jetpack.existsAsync(filePath); /* Make sure the file exists just in case, and if not, continue to it's generation. */ if (exists) { - const fileName = `${process.env.SERVICE_NAME}-${identifier}.zip`; + const fileName = `${Util.config.serviceName}-${identifier}.zip`; return res.download(filePath, fileName); } } @@ -77,8 +77,8 @@ class albumGET extends Route { .update('zippedAt', db.fn.now()) .wasMutated(); - const filePath = path.join(__dirname, '../../../../', process.env.UPLOAD_FOLDER, 'zips', `${album.userId}-${album.id}.zip`); - const fileName = `${process.env.SERVICE_NAME}-${identifier}.zip`; + const filePath = path.join(__dirname, '../../../../uploads', 'zips', `${album.userId}-${album.id}.zip`); + const fileName = `${Util.config.serviceName}-${identifier}.zip`; return res.download(filePath, fileName); } catch (error) { log.error(error); diff --git a/src/api/routes/auth/loginPOST.js b/src/api/routes/auth/loginPOST.js index 373252b..cc72145 100644 --- a/src/api/routes/auth/loginPOST.js +++ b/src/api/routes/auth/loginPOST.js @@ -2,6 +2,7 @@ const bcrypt = require('bcrypt'); const moment = require('moment'); const JWT = require('jsonwebtoken'); const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); class loginPOST extends Route { constructor() { @@ -37,7 +38,7 @@ class loginPOST extends Route { iss: 'chibisafe', sub: user.id, iat: moment.utc().valueOf() - }, process.env.SECRET, { expiresIn: '30d' }); + }, Util.config.secret, { expiresIn: '30d' }); return res.json({ message: 'Successfully logged in.', diff --git a/src/api/routes/auth/registerPOST.js b/src/api/routes/auth/registerPOST.js index 7b9eb3c..e740c83 100644 --- a/src/api/routes/auth/registerPOST.js +++ b/src/api/routes/auth/registerPOST.js @@ -12,7 +12,7 @@ class registerPOST extends Route { async run(req, res, db) { // Only allow admins to create new accounts if the sign up is deactivated const user = await Util.isAuthorized(req); - if ((!user || !user.isAdmin) && process.env.USER_ACCOUNTS === 'false') return res.status(401).json({ message: 'Creation of new accounts is currently disabled' }); + if ((!user || !user.isAdmin) && !Util.config.userAccounts) return res.status(401).json({ message: 'Creation of new accounts is currently disabled' }); if (!req.body) return res.status(400).json({ message: 'No body provided' }); const { username, password } = req.body; diff --git a/src/api/routes/service/configGET.js b/src/api/routes/service/configGET.js index 291f0a4..b0a9c16 100644 --- a/src/api/routes/service/configGET.js +++ b/src/api/routes/service/configGET.js @@ -1,4 +1,5 @@ const Route = require('../../structures/Route'); +const Util = require('../../utils/Util'); class configGET extends Route { constructor() { @@ -9,15 +10,13 @@ class configGET extends Route { return res.json({ message: 'Successfully retrieved config', config: { - serviceName: process.env.SERVICE_NAME, - uploadFolder: process.env.UPLOAD_FOLDER, - maxUploadSize: parseInt(process.env.MAX_SIZE, 10), - filenameLength: parseInt(process.env.GENERATED_FILENAME_LENGTH, 10), - albumLinkLength: parseInt(process.env.GENERATED_ALBUM_LENGTH, 10), - generateThumbnails: process.env.GENERATE_THUMBNAILS === 'true', - generateZips: process.env.GENERATE_ZIPS === 'true', - publicMode: process.env.PUBLIC_MODE === 'true', - enableAccounts: process.env.USER_ACCOUNTS === 'true' + serviceName: Util.config.serviceName, + maxUploadSize: Util.config.maxSize, + filenameLength: Util.config.generatedFilenameLength, + albumLinkLength: Util.config.generatedAlbumLength, + generateZips: Util.config.generateZips, + publicMode: Util.config.publicMode, + enableAccounts: Util.config.userAccounts } }); } diff --git a/src/api/routes/uploads/uploadPOST.js b/src/api/routes/uploads/uploadPOST.js index 7386490..4e96c80 100644 --- a/src/api/routes/uploads/uploadPOST.js +++ b/src/api/routes/uploads/uploadPOST.js @@ -8,8 +8,8 @@ const multerStorage = require('../../utils/multerStorage'); const chunksData = {}; const chunkedUploadsTimeout = 1800000; -const chunksDir = path.join(__dirname, '../../../../', process.env.UPLOAD_FOLDER, 'chunks'); -const uploadDir = path.join(__dirname, '../../../../', process.env.UPLOAD_FOLDER); +const chunksDir = path.join(__dirname, '../../../../uploads/chunks'); +const uploadDir = path.join(__dirname, '../../../../uploads'); const cleanUpChunks = async (uuid, onTimeout) => { @@ -72,7 +72,7 @@ const initChunks = async uuid => { const executeMulter = multer({ // Guide: https://github.com/expressjs/multer#limits limits: { - fileSize: parseInt(process.env.MAX_SIZE, 10) * (1000 * 1000), + fileSize: Util.config.maxSize * (1000 * 1000), // Maximum number of non-file fields. // Dropzone.js will add 6 extra fields for chunked uploads. // We don't use them for anything else. @@ -257,7 +257,7 @@ class uploadPOST extends Route { async run(req, res, db) { const user = await Util.isAuthorized(req); - if (!user && process.env.PUBLIC_MODE === 'false') return res.status(401).json({ message: 'Not authorized to use this resource' }); + if (!user && !Util.config.publicMode) return res.status(401).json({ message: 'Not authorized to use this resource' }); const { finishedchunks } = req.headers; const albumId = req.headers.albumid ? req.headers.albumid === 'null' ? null : req.headers.albumid : null; if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' }); diff --git a/src/api/structures/Route.js b/src/api/structures/Route.js index 24d45b2..9496d0f 100644 --- a/src/api/structures/Route.js +++ b/src/api/structures/Route.js @@ -2,6 +2,7 @@ const JWT = require('jsonwebtoken'); const db = require('./Database'); const moment = require('moment'); const log = require('../utils/Log'); +const Util = require('../utils/Util'); class Route { constructor(path, method, options) { @@ -30,7 +31,7 @@ class Route { const token = req.headers.authorization.split(' ')[1]; if (!token) return res.status(401).json({ message: 'No authorization header provided' }); - return JWT.verify(token, process.env.SECRET, async (error, decoded) => { + return JWT.verify(token, Util.config.secret, async (error, decoded) => { if (error) { log.error(error); return res.status(401).json({ message: 'Invalid token' }); diff --git a/src/api/structures/Server.js b/src/api/structures/Server.js index 268ba68..53be9fb 100644 --- a/src/api/structures/Server.js +++ b/src/api/structures/Server.js @@ -19,11 +19,10 @@ const CronJob = require('cron').CronJob; const log = require('../utils/Log'); const Util = require('../utils/Util'); - // 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), + windowMs: parseInt(Util.config.rateLimitWindow, 10), + max: parseInt(Util.config.rateLimitMax, 10), delayMs: 0 }); @@ -65,6 +64,7 @@ class Server { } registerAllTheRoutes() { + console.log(Util.config); jetpack.find(this.routesFolder, { matching: '*.js' }).forEach(routeFile => { const RouteClass = require(path.join('../../../', routeFile)); let routes = [RouteClass]; @@ -72,8 +72,8 @@ class Server { for (const File of routes) { 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}`); + this.server[route.method](Util.config.routePrefix + route.path, route.authorize.bind(route)); + log.info(`Found route ${route.method.toUpperCase()} ${Util.config.routePrefix}${route.path}`); } catch (e) { log.error(`Failed loading route from file ${routeFile} with error: ${e.message}`); } @@ -110,4 +110,11 @@ class Server { } } -new Server().start(); +const start = async () => { + const conf = await Util.config; + console.log(conf); + new Server().start(); +}; + +start(); + diff --git a/src/api/utils/ThumbUtil.js b/src/api/utils/ThumbUtil.js index d08ecab..fb6e47f 100644 --- a/src/api/utils/ThumbUtil.js +++ b/src/api/utils/ThumbUtil.js @@ -10,11 +10,12 @@ class ThumbUtil { static imageExtensions = ['.jpg', '.jpeg', '.gif', '.png', '.webp']; static videoExtensions = ['.webm', '.mp4', '.wmv', '.avi', '.mov']; - static thumbPath = path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER, 'thumbs'); - static squareThumbPath = path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER, 'thumbs', 'square'); - static videoPreviewPath = path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER, 'thumbs', 'preview'); + static thumbPath = path.join(__dirname, '../../../', 'uploads', 'thumbs'); + static squareThumbPath = path.join(__dirname, '../../../', 'uploads', 'thumbs', 'square'); + static videoPreviewPath = path.join(__dirname, '../../../', 'uploads', 'thumbs', 'preview'); static generateThumbnails(filename) { + if (!filename) return; const ext = path.extname(filename).toLowerCase(); const output = `${filename.slice(0, -ext.length)}.webp`; const previewOutput = `${filename.slice(0, -ext.length)}.webm`; @@ -27,7 +28,7 @@ class ThumbUtil { } static async generateThumbnailForImage(filename, output) { - const filePath = path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER, filename); + const filePath = path.join(__dirname, '../../../', 'uploads', filename); const file = await jetpack.readAsync(filePath, 'buffer'); await sharp(file) @@ -41,7 +42,7 @@ class ThumbUtil { } static async generateThumbnailForVideo(filename, output) { - const filePath = path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER, filename); + const filePath = path.join(__dirname, '../../../', 'uploads', filename); ffmpeg(filePath) .thumbnail({ diff --git a/src/api/utils/Util.js b/src/api/utils/Util.js index 727851e..3780460 100644 --- a/src/api/utils/Util.js +++ b/src/api/utils/Util.js @@ -12,25 +12,22 @@ const log = require('./Log'); const ThumbUtil = require('./ThumbUtil'); const StatsGenerator = require('./StatsGenerator'); -const blockedExtensions = process.env.BLOCKED_EXTENSIONS.split(','); const preserveExtensions = ['.tar.gz', '.tar.z', '.tar.bz2', '.tar.lzma', '.tar.lzo', '.tar.xz']; class Util { - static uploadPath = path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER); + static uploadPath = path.join(__dirname, '../../../', 'uploads'); static statsLastSavedTime = null; static _config = null; static get config() { + if (this._config) return this._config; return (async () => { if (this._config === null) { - const conf = await db('config').select('key', 'value'); - this._config = conf.reduce((acc, { key, value }) => { - if (typeof value === 'string' || value instanceof String) { - acc[key] = JSON.parse(value); - } else { - acc[key] = value; - } - }, {}); + const conf = await db('settings').select('key', 'value'); + this._config = conf.reduce((obj, item) => ( + // eslint-disable-next-line no-sequences + obj[item.key] = typeof item.value === 'string' || item.value instanceof String ? JSON.parse(item.value) : item.value, obj + ), {}); } return this._config; })(); @@ -49,15 +46,15 @@ class Util { serviceName: process.env.SERVICE_NAME || 'change-me', chunkSize: process.env.CHUNK_SIZE || 90, maxSize: process.env.MAX_SIZE || 5000, + // eslint-disable-next-line eqeqeq generateZips: process.env.GENERATE_ZIPS == undefined ? true : false, generatedFilenameLength: process.env.GENERATED_FILENAME_LENGTH || 12, generatedAlbumLength: process.env.GENERATED_ALBUM_LENGTH || 6, - uploadFolder: process.env.UPLOAD_FOLDER || 'uploads', blockedExtensions: process.env.BLOCKED_EXTENSIONS || ['.jar', '.exe', '.msi', '.com', '.bat', '.cmd', '.scr', '.ps1', '.sh'], + // eslint-disable-next-line eqeqeq publicMode: process.env.PUBLIC_MODE == undefined ? true : false, + // eslint-disable-next-line eqeqeq userAccounts: process.env.USER_ACCOUNTS == undefined ? true : false, - adminAccount: process.env.ADMIN_ACCOUNT || 'admin', - adminPassword: process.env.ADMIN_PASSWORD || 'admin', metaThemeColor: process.env.META_THEME_COLOR || '#20222b', metaDescription: process.env.META_DESCRIPTION || 'Blazing fast file uploader and bunker written in node! 🚀', metaKeywords: process.env.META_KEYWORDS || 'chibisafe,lolisafe,upload,uploader,file,vue,images,ssr,file uploader,free', @@ -65,16 +62,16 @@ class Util { }; } - static async writeConfigToDb(config, overwrite = true) { + static async writeConfigToDb(config) { // TODO: Check that the config passes the joi schema validation + if (!config || !config.key || !config.key) return; try { - if (overwrite) { - await db.table('settings').first().update(config); - } else { - await db.table('settings').insert(config); - } + config.value = JSON.stringify(config.value); + await db.table('settings').insert(config); } catch (error) { console.error(error); + } finally { + this.invalidateConfigCache(); } } @@ -83,7 +80,7 @@ class Util { } static isExtensionBlocked(extension) { - return blockedExtensions.includes(extension); + return this.config.blockedExtensions.includes(extension); } static getMimeFromType(fileTypeMimeObj) { @@ -109,7 +106,7 @@ class Util { static getUniqueFilename(extension) { const retry = (i = 0) => { const filename = randomstring.generate({ - length: parseInt(process.env.GENERATED_FILENAME_LENGTH, 10), + length: this.config.generatedFilenameLength, capitalization: 'lowercase' }) + extension; @@ -126,7 +123,7 @@ class Util { static getUniqueAlbumIdentifier() { const retry = async (i = 0) => { const identifier = randomstring.generate({ - length: parseInt(process.env.GENERATED_ALBUM_LENGTH, 10), + length: this.config.generatedAlbumLength, capitalization: 'lowercase' }); const exists = await db @@ -223,7 +220,7 @@ class Util { const token = req.headers.authorization.split(' ')[1]; if (!token) return false; - return JWT.verify(token, process.env.SECRET, async (error, decoded) => { + return JWT.verify(token, this.config.secret, async (error, decoded) => { if (error) { log.error(error); return false; @@ -249,13 +246,7 @@ class Util { zip.addLocalFile(path.join(Util.uploadPath, file)); } zip.writeZip( - path.join( - __dirname, - '../../../', - process.env.UPLOAD_FOLDER, - 'zips', - `${album.userId}-${album.id}.zip` - ) + path.join(__dirname, '../../../', 'uploads', 'zips', `${album.userId}-${album.id}.zip`) ); } catch (error) { log.error(error); diff --git a/src/api/utils/generateThumbs.js b/src/api/utils/generateThumbs.js index d2cd91b..a22fcb6 100644 --- a/src/api/utils/generateThumbs.js +++ b/src/api/utils/generateThumbs.js @@ -6,7 +6,7 @@ const path = require('path'); const ThumbUtil = require('./ThumbUtil'); const start = async () => { - const files = fs.readdirSync(path.join(__dirname, '../../../', process.env.UPLOAD_FOLDER)); + const files = fs.readdirSync(path.join(__dirname, '../../../uploads')); for (const fileName of files) { console.log(`Generating thumb for '${fileName}`); // eslint-disable-next-line no-await-in-loop diff --git a/src/setup.js b/src/setup.js index 7171c19..de684f9 100644 --- a/src/setup.js +++ b/src/setup.js @@ -1,5 +1,4 @@ /* eslint-disable no-console */ -const randomstring = require('randomstring'); const jetpack = require('fs-jetpack'); const qoa = require('qoa'); @@ -16,53 +15,12 @@ async function start() { const wizard = [ { type: 'input', - query: 'Port to run chibisafe in: (5000)', + query: 'Port to run chibisafe in? (default: 5000)', handle: 'SERVER_PORT' }, - { - type: 'input', - query: 'Full domain this instance is gonna be running on (Ex: https://chibisafe.moe):', - handle: 'DOMAIN' - }, - { - type: 'input', - query: 'Name of the service? (Ex: chibisafe):', - handle: 'SERVICE_NAME' - }, - { - type: 'input', - query: 'Maximum allowed upload file size in MB (Ex: 100):', - handle: 'MAX_SIZE' - }, - { - type: 'confirm', - query: 'Allow users to download entire albums in ZIP format? (true)', - handle: 'GENERATE_ZIPS', - accept: 'y', - deny: 'n' - }, - { - type: 'confirm', - query: 'Allow people to upload files without an account? (true)', - handle: 'PUBLIC_MODE', - accept: 'y', - deny: 'n' - }, - { - type: 'confirm', - query: 'Allow people to create new accounts? (true)', - handle: 'USER_ACCOUNTS', - accept: 'y', - deny: 'n' - }, - { - type: 'input', - query: 'Name of the admin account? (admin)', - handle: 'ADMIN_ACCOUNT' - }, { type: 'interactive', - query: 'Which predefined database do you want to use?', + query: 'Which database do you want to use? (select sqlite3 if not sure)', handle: 'DB_CLIENT', symbol: '>', menu: [ @@ -73,22 +31,22 @@ async function start() { }, { type: 'input', - query: 'Database host (Ignore if you selected sqlite3):', + query: 'Database host (Leave blank if you selected sqlite3):', handle: 'DB_HOST' }, { type: 'input', - query: 'Database user (Ignore if you selected sqlite3):', + query: 'Database user (Leave blank if you selected sqlite3):', handle: 'DB_USER' }, { type: 'input', - query: 'Database password (Ignore if you selected sqlite3):', + query: 'Database password (Leave blank if you selected sqlite3):', handle: 'DB_PASSWORD' }, { type: 'input', - query: 'Database name (Ignore if you selected sqlite3):', + query: 'Database name (Leave blank if you selected sqlite3):', handle: 'DB_DATABASE' } ]; @@ -97,69 +55,29 @@ async function start() { let envfile = ''; const defaultSettings = { - _1: '# Server settings', SERVER_PORT: response.SERVER_PORT || 5000, - WEBSITE_PORT: 5001, - ROUTE_PREFIX: '/api', - RATE_LIMIT_WINDOW: 2, - RATE_LIMIT_MAX: 5, - SECRET: randomstring.generate(64), - - _2: '# Service settings', - SERVICE_NAME: response.SERVICE_NAME || 'change-me', - DOMAIN: response.DOMAIN || `http://localhost:${response.SERVER_PORT}`, - - _3: '# File related settings', - CHUNK_SIZE: 90, - MAX_SIZE: response.MAX_SIZE || 5000, - GENERATE_ZIPS: response.GENERATE_ZIPS == undefined ? true : false, - GENERATED_FILENAME_LENGTH: 12, - GENERATED_ALBUM_LENGTH: 6, - MAX_LINKS_PER_ALBUM: 5, - UPLOAD_FOLDER: 'uploads', - BLOCKED_EXTENSIONS: ['.jar', '.exe', '.msi', '.com', '.bat', '.cmd', '.scr', '.ps1', '.sh'], - - _4: '# User settings', - PUBLIC_MODE: response.PUBLIC_MODE == undefined ? true : false, - USER_ACCOUNTS: response.USER_ACCOUNTS == undefined ? true : false, - ADMIN_ACCOUNT: response.ADMIN_ACCOUNT || 'admin', - ADMIN_PASSWORD: randomstring.generate(16), - - _5: '# Database connection settings', DB_CLIENT: response.DB_CLIENT, DB_HOST: response.DB_HOST || null, DB_USER: response.DB_USER || null, DB_PASSWORD: response.DB_PASSWORD || null, - DB_DATABASE: response.DB_DATABASE || null, - - _6: '# Social and sharing settings', - META_THEME_COLOR: '#20222b', - META_DESCRIPTION: 'Blazing fast file uploader and bunker written in node! 🚀', - META_KEYWORDS: 'chibisafe,lolisafe,upload,uploader,file,vue,images,ssr,file uploader,free', - META_TWITTER_HANDLE: '@its_pitu' + DB_DATABASE: response.DB_DATABASE || null }; const keys = Object.keys(defaultSettings); for (const item of keys) { - let prefix = `${item}=`; - if (item.startsWith('_1')) { - prefix = ''; - } else if (item.startsWith('_')) { - prefix = '\n'; - } - envfile += `${prefix}${defaultSettings[item]}\n`; + envfile += `${item}=${defaultSettings[item]}\n`; } jetpack.write('.env', envfile); jetpack.dir('database'); console.log(); - console.log('===================================================='); - console.log('== .env file generated successfully. =='); - console.log('===================================================='); - console.log(`== Your admin password is: ${defaultSettings.ADMIN_PASSWORD} ==`); - console.log('== MAKE SURE TO CHANGE IT AFTER YOUR FIRST LOGIN! =='); - console.log('===================================================='); + console.log('====================================================='); + console.log('== .env file generated successfully. =='); + console.log('====================================================='); + console.log(`== Both your initial user and password are 'admin' ==`); + console.log('== MAKE SURE TO CHANGE IT AFTER YOUR FIRST LOGIN =='); + console.log('====================================================='); console.log(); setTimeout(() => {}, 1000); }