feat: cleaned up routes init

asserting auth and JSON body will now be done via route-specific
mini middlewares (authController's requireUser or optionalUser)
This commit is contained in:
Bobby Wibowo 2022-08-04 21:59:06 +07:00
parent 2dccaacf33
commit d7d6a29123
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
7 changed files with 290 additions and 299 deletions

View File

@ -102,18 +102,18 @@ self.unholdAlbumIdentifiers = res => {
}
self.list = async (req, res) => {
const user = await utils.authorize(req)
const all = req.headers.all === '1'
const simple = req.headers.simple
const ismoderator = perms.is(user, 'moderator')
if (all && !ismoderator) return res.status(403).end()
const ismoderator = perms.is(req.locals.user, 'moderator')
if (all && !ismoderator) {
return res.status(403).end()
}
const filter = function () {
if (!all) {
this.where({
enabled: 1,
userid: user.id
userid: req.locals.user.id
})
}
}
@ -227,34 +227,32 @@ self.list = async (req, res) => {
}
self.create = async (req, res) => {
utils.assertRequestType(req, 'application/json')
const user = await utils.authorize(req)
// Parse POST body
req.body = await req.json()
const name = typeof req.body.name === 'string'
? utils.escape(req.body.name.trim().substring(0, self.titleMaxLength))
: ''
if (!name) throw new ClientError('No album name specified.')
if (!name) {
throw new ClientError('No album name specified.')
}
const album = await utils.db.table('albums')
.where({
name,
enabled: 1,
userid: user.id
userid: req.locals.user.id
})
.first()
if (album) throw new ClientError('Album name already in use.', { statusCode: 403 })
if (album) {
throw new ClientError('Album name already in use.', { statusCode: 403 })
}
const identifier = await self.getUniqueAlbumIdentifier(res)
const ids = await utils.db.table('albums').insert({
name,
enabled: 1,
userid: user.id,
userid: req.locals.user.id,
identifier,
timestamp: Math.floor(Date.now() / 1000),
editedAt: 0,
@ -272,39 +270,33 @@ self.create = async (req, res) => {
}
self.delete = async (req, res) => {
utils.assertRequestType(req, 'application/json')
// Parse POST body and re-map for .disable()
req.body = await req.json()
.then(obj => {
obj.del = true
return obj
})
// Re-map Request.body for .disable()
req.body.del = true
return self.disable(req, res)
}
self.disable = async (req, res) => {
utils.assertRequestType(req, 'application/json')
const user = await utils.authorize(req)
const ismoderator = perms.is(user, 'moderator')
// Parse POST body, if required
req.body = req.body || await req.json()
const ismoderator = perms.is(req.locals.user, 'moderator')
const id = parseInt(req.body.id)
if (isNaN(id)) throw new ClientError('No album specified.')
if (isNaN(id)) {
throw new ClientError('No album specified.')
}
const purge = req.body.purge
// Only allow moderators to delete other users' albums
const del = ismoderator ? req.body.del : false
const filter = function () {
this.where('id', id)
// Only allow moderators to disable other users' albums
if (!ismoderator) {
this.andWhere({
enabled: 1,
userid: user.id
userid: req.locals.user.id
})
}
}
@ -326,7 +318,7 @@ self.disable = async (req, res) => {
if (files.length) {
const ids = files.map(file => file.id)
const failed = await utils.bulkDeleteFromDb('id', ids, user)
const failed = await utils.bulkDeleteFromDb('id', ids, req.locals.user)
if (failed.length) {
return res.json({ success: false, failed })
}
@ -352,36 +344,38 @@ self.disable = async (req, res) => {
await paths.unlink(path.join(paths.zips, `${album.identifier}.zip`))
} catch (error) {
// Re-throw non-ENOENT error
if (error.code !== 'ENOENT') throw error
if (error.code !== 'ENOENT') {
throw error
}
}
return res.json({ success: true })
}
self.edit = async (req, res) => {
utils.assertRequestType(req, 'application/json')
const user = await utils.authorize(req)
const ismoderator = perms.is(user, 'moderator')
// Parse POST body, if required
req.body = req.body || await req.json()
const ismoderator = perms.is(req.locals.user, 'moderator')
const id = parseInt(req.body.id)
if (isNaN(id)) throw new ClientError('No album specified.')
if (isNaN(id)) {
throw new ClientError('No album specified.')
}
const name = typeof req.body.name === 'string'
? utils.escape(req.body.name.trim().substring(0, self.titleMaxLength))
: ''
if (!name) throw new ClientError('No album name specified.')
if (!name) {
throw new ClientError('No album name specified.')
}
const filter = function () {
this.where('id', id)
// Only allow moderators to edit other users' albums
if (!ismoderator) {
this.andWhere({
enabled: 1,
userid: user.id
userid: req.locals.user.id
})
}
}
@ -402,14 +396,14 @@ self.edit = async (req, res) => {
.where({
name,
enabled: 1,
userid: user.id
userid: req.locals.user.id
})
.whereNot('id', id)
.first()
if ((album.enabled || (albumNewState === true)) && nameInUse) {
if (req._old) {
// Old rename API (stick with 200 status code for this)
if (req._legacy) {
// Legacy rename API (stick with 200 status code for this)
throw new ClientError('You did not specify a new name.', { statusCode: 200 })
} else {
throw new ClientError('Album name already in use.', { statusCode: 403 })
@ -448,7 +442,9 @@ self.edit = async (req, res) => {
await paths.rename(oldZip, newZip)
} catch (error) {
// Re-throw non-ENOENT error
if (error.code !== 'ENOENT') throw error
if (error.code !== 'ENOENT') {
throw error
}
}
return res.json({
@ -461,16 +457,11 @@ self.edit = async (req, res) => {
}
self.rename = async (req, res) => {
utils.assertRequestType(req, 'application/json')
// Parse POST body and re-map for .edit()
req.body = await req.json()
.then(obj => {
return {
_old: true,
name: obj.name
}
})
// Re-map Request.body for .edit()
req.body = {
_legacy: true,
name: req.body.name
}
return self.edit(req, res)
}
@ -529,6 +520,7 @@ self.getUpstreamCompat = async (req, res) => {
// map to .get() with chibisafe/upstream compatibility
// This API is known to be used in Pitu/Magane
req.locals.upstreamCompat = true
res._json = res.json
res.json = (body = {}) => {
// Rebuild JSON payload to match lolisafe upstream
@ -549,7 +541,10 @@ self.getUpstreamCompat = async (req, res) => {
}
})
if (rebuild.message) rebuild.message = rebuild.message.replace(/\.$/, '')
if (rebuild.message) {
rebuild.message = rebuild.message.replace(/\.$/, '')
}
return res._json(rebuild)
}
@ -593,7 +588,9 @@ self.generateZip = async (req, res) => {
return
} catch (error) {
// Re-throw non-ENOENT error
if (error.code !== 'ENOENT') throw error
if (error.code !== 'ENOENT') {
throw error
}
}
}
@ -673,19 +670,15 @@ self.generateZip = async (req, res) => {
}
self.addFiles = async (req, res) => {
utils.assertRequestType(req, 'application/json')
const user = await utils.authorize(req)
// Parse POST body
req.body = await req.json()
const ids = req.body.ids
if (!Array.isArray(ids) || !ids.length) {
throw new ClientError('No files specified.')
}
let albumid = parseInt(req.body.albumid)
if (isNaN(albumid) || albumid < 0) albumid = null
if (isNaN(albumid) || albumid < 0) {
albumid = null
}
const failed = []
const albumids = []
@ -694,8 +687,10 @@ self.addFiles = async (req, res) => {
const album = await utils.db.table('albums')
.where('id', albumid)
.where(function () {
if (user.username !== 'root') {
this.where('userid', user.id)
// Only allow "root" user to arbitrarily add/remove files to/from any albums
// NOTE: Dashboard does not facilitate this, intended for manual API calls
if (req.locals.user.username !== 'root') {
this.where('userid', req.locals.user.id)
}
})
.first()
@ -709,7 +704,7 @@ self.addFiles = async (req, res) => {
const files = await utils.db.table('files')
.whereIn('id', ids)
.where('userid', user.id)
.where('userid', req.locals.user.id)
failed.push(...ids.filter(id => !files.find(file => file.id === id)))

View File

@ -35,27 +35,79 @@ const usersPerPage = config.dashboard
? Math.max(Math.min(config.dashboard.usersPerPage || 0, 100), 1)
: 25
self.assertUser = async (token, fields) => {
// Default fields/columns to fetch from database
const _fields = ['id', 'username', 'enabled', 'timestamp', 'permission', 'registration']
// Allow fetching additional fields/columns
if (typeof fields === 'string') {
fields = [fields]
}
if (Array.isArray(fields)) {
_fields.push(...fields)
}
const user = await utils.db.table('users')
.where('token', token)
.select(_fields)
.first()
if (user) {
if (user.enabled === false || user.enabled === 0) {
throw new ClientError('This account has been disabled.', { statusCode: 403 })
}
return user
} else {
throw new ClientError('Invalid token.', { statusCode: 403 })
}
}
// _ is next() if this was a synchronous middleware function
self.requireUser = async (req, res, _, fields) => {
// Throws when token is missing, thus use only for users-only routes
const token = req.headers.token
if (token === undefined) {
throw new ClientError('No token provided.', { statusCode: 403 })
}
// Add user data to Request.locals.user
req.locals.user = await self.assertUser(token, fields)
}
// _ is next() if this was a synchronous middleware function
self.optionalUser = async (req, res, _, fields) => {
// Throws when token if missing only when private is set to true in config,
// thus use for routes that can handle no auth requests
const token = req.headers.token
if (token) {
// Add user data to Request.locals.user
req.locals.user = await self.assertUser(token, fields)
} else if (config.private === true) {
throw new ClientError('No token provided.', { statusCode: 403 })
}
}
self.verify = async (req, res) => {
utils.assertRequestType(req, 'application/json')
// Parse POST body
req.body = await req.json()
const username = typeof req.body.username === 'string'
? req.body.username.trim()
: ''
if (!username) throw new ClientError('No username provided.')
if (!username) {
throw new ClientError('No username provided.')
}
const password = typeof req.body.password === 'string'
? req.body.password.trim()
: ''
if (!password) throw new ClientError('No password provided.')
if (!password) {
throw new ClientError('No password provided.')
}
const user = await utils.db.table('users')
.where('username', username)
.first()
if (!user) throw new ClientError('Wrong credentials.', { statusCode: 403 })
if (!user) {
throw new ClientError('Wrong credentials.', { statusCode: 403 })
}
if (user.enabled === false || user.enabled === 0) {
throw new ClientError('This account has been disabled.', { statusCode: 403 })
@ -70,11 +122,6 @@ self.verify = async (req, res) => {
}
self.register = async (req, res) => {
utils.assertRequestType(req, 'application/json')
// Parse POST body
req.body = await req.json()
if (config.enableUserAccounts === false) {
throw new ClientError('Registration is currently disabled.', { statusCode: 403 })
}
@ -119,12 +166,6 @@ self.register = async (req, res) => {
}
self.changePassword = async (req, res) => {
utils.assertRequestType(req, 'application/json')
const user = await utils.authorize(req)
// Parse POST body
req.body = await req.json()
const password = typeof req.body.password === 'string'
? req.body.password.trim()
: ''
@ -135,7 +176,7 @@ self.changePassword = async (req, res) => {
const hash = await bcrypt.hash(password, saltRounds)
await utils.db.table('users')
.where('id', user.id)
.where('id', req.locals.user.id)
.update('password', hash)
return res.json({ success: true })
@ -152,14 +193,10 @@ self.assertPermission = (user, target) => {
}
self.createUser = async (req, res) => {
utils.assertRequestType(req, 'application/json')
const user = await utils.authorize(req)
// Parse POST body
req.body = await req.json()
const isadmin = perms.is(user, 'admin')
if (!isadmin) return res.status(403).end()
const isadmin = perms.is(req.locals.user, 'admin')
if (!isadmin) {
return res.status(403).end()
}
const username = typeof req.body.username === 'string'
? req.body.username.trim()
@ -215,22 +252,22 @@ self.createUser = async (req, res) => {
}
self.editUser = async (req, res) => {
utils.assertRequestType(req, 'application/json')
const user = await utils.authorize(req)
// Parse POST body, if required
req.body = req.body || await req.json()
const isadmin = perms.is(user, 'admin')
if (!isadmin) throw new ClientError('', { statusCode: 403 })
const isadmin = perms.is(req.locals.user, 'admin')
if (!isadmin) {
return res.status(403).end()
}
const id = parseInt(req.body.id)
if (isNaN(id)) throw new ClientError('No user specified.')
if (isNaN(id)) {
throw new ClientError('No user specified.')
}
const target = await utils.db.table('users')
.where('id', id)
.first()
self.assertPermission(user, target)
// Ensure this user has permission to tamper with target user
self.assertPermission(req.locals.user, target)
const update = {}
@ -272,38 +309,33 @@ self.editUser = async (req, res) => {
}
self.disableUser = async (req, res) => {
utils.assertRequestType(req, 'application/json')
// Parse POST body and re-map for .editUser()
req.body = await req.json()
.then(obj => {
return {
id: obj.id,
enabled: false
}
})
// Re-map Request.body for .editUser()
req.body = {
id: req.body.id,
enabled: false
}
return self.editUser(req, res)
}
self.deleteUser = async (req, res) => {
utils.assertRequestType(req, 'application/json')
const user = await utils.authorize(req)
// Parse POST body
req.body = await req.json()
const isadmin = perms.is(user, 'admin')
if (!isadmin) throw new ClientError('', { statusCode: 403 })
const isadmin = perms.is(req.locals.user, 'admin')
if (!isadmin) {
return res.status(403).end()
}
const id = parseInt(req.body.id)
const purge = req.body.purge
if (isNaN(id)) throw new ClientError('No user specified.')
if (isNaN(id)) {
throw new ClientError('No user specified.')
}
const target = await utils.db.table('users')
.where('id', id)
.first()
self.assertPermission(user, target)
// Ensure this user has permission to tamper with target user
self.assertPermission(req.locals.user, target)
const files = await utils.db.table('files')
.where('userid', id)
@ -312,7 +344,7 @@ self.deleteUser = async (req, res) => {
if (files.length) {
const fileids = files.map(file => file.id)
if (purge) {
const failed = await utils.bulkDeleteFromDb('id', fileids, user)
const failed = await utils.bulkDeleteFromDb('id', fileids, req.locals.user)
utils.invalidateStatsCache('uploads')
if (failed.length) {
return res.json({ success: false, failed })
@ -361,10 +393,10 @@ self.bulkDeleteUsers = async (req, res) => {
}
self.listUsers = async (req, res) => {
const user = await utils.authorize(req)
const isadmin = perms.is(user, 'admin')
if (!isadmin) throw new ClientError('', { statusCode: 403 })
const isadmin = perms.is(req.locals.user, 'admin')
if (!isadmin) {
return res.status(403).end()
}
// Base result object
const result = { success: true, users: [], usersPerPage, count: 0 }

View File

@ -63,16 +63,13 @@ self.unholdTokens = res => {
}
self.verify = async (req, res) => {
utils.assertRequestType(req, 'application/json')
// Parse POST body
req.body = await req.json()
const token = typeof req.body.token === 'string'
? req.body.token.trim()
: ''
if (!token) throw new ClientError('No token provided.', { statusCode: 403 })
if (!token) {
throw new ClientError('No token provided.', { statusCode: 403 })
}
const user = await utils.db.table('users')
.where('token', token)
@ -106,17 +103,14 @@ self.verify = async (req, res) => {
}
self.list = async (req, res) => {
const user = await utils.authorize(req)
return res.json({ success: true, token: user.token })
return res.json({ success: true, token: req.locals.user.token })
}
self.change = async (req, res) => {
const user = await utils.authorize(req, 'token')
const newToken = await self.getUniqueToken(res)
await utils.db.table('users')
.where('token', user.token)
.where('token', req.locals.user.token)
.update({
token: newToken,
timestamp: Math.floor(Date.now() / 1000)

View File

@ -259,15 +259,8 @@ self.upload = async (req, res) => {
throw new ClientError('Request Content-Type must be either multipart/form-data or application/json.')
}
let user
if (config.private === true) {
user = await utils.authorize(req)
} else if (req.headers.token) {
user = await utils.assertUser(req.headers.token)
}
if (config.privateUploadGroup) {
if (!user || !perms.is(user, config.privateUploadGroup)) {
if (!req.locals.user || !perms.is(req.locals.user, config.privateUploadGroup)) {
throw new ClientError(config.privateUploadCustomResponse || 'Your usergroup is not permitted to upload new files.', { statusCode: 403 })
}
}
@ -275,18 +268,18 @@ self.upload = async (req, res) => {
let albumid = parseInt(req.headers.albumid || (req.path_parameters && req.path_parameters.albumid))
if (isNaN(albumid)) albumid = null
const age = self.assertRetentionPeriod(user, req.headers.age)
const age = self.assertRetentionPeriod(req.locals.user, req.headers.age)
if (isMultipart) {
return self.actuallyUpload(req, res, user, { albumid, age })
return self.actuallyUpload(req, res, { albumid, age })
} else {
// Parse POST body
req.body = await req.json()
return self.actuallyUploadUrls(req, res, user, { albumid, age })
return self.actuallyUploadUrls(req, res, { albumid, age })
}
}
self.actuallyUpload = async (req, res, user, data = {}) => {
self.actuallyUpload = async (req, res, data = {}) => {
// Init empty Request.body and Request.files
req.body = {}
req.files = []
@ -471,7 +464,7 @@ self.actuallyUpload = async (req, res, user, data = {}) => {
const filesData = req.files
if (utils.scan.instance) {
const scanResult = await self.scanFiles(req, user, filesData)
const scanResult = await self.scanFiles(req, filesData)
if (scanResult) {
throw new ClientError(scanResult)
}
@ -479,13 +472,13 @@ self.actuallyUpload = async (req, res, user, data = {}) => {
await self.stripTags(req, filesData)
const result = await self.storeFilesToDb(req, res, user, filesData)
return self.sendUploadResponse(req, res, user, result)
const result = await self.storeFilesToDb(req, res, filesData)
return self.sendUploadResponse(req, res, result)
}
/** URL uploads */
self.actuallyUploadUrls = async (req, res, user, data = {}) => {
self.actuallyUploadUrls = async (req, res, data = {}) => {
if (!config.uploads.urlMaxSize) {
throw new ClientError('Upload by URLs is disabled at the moment.', { statusCode: 403 })
}
@ -670,36 +663,23 @@ self.actuallyUploadUrls = async (req, res, user, data = {}) => {
})
if (utils.scan.instance) {
const scanResult = await self.scanFiles(req, user, filesData)
const scanResult = await self.scanFiles(req, filesData)
if (scanResult) {
throw new ClientError(scanResult)
}
}
const result = await self.storeFilesToDb(req, res, user, filesData)
return self.sendUploadResponse(req, res, user, result)
const result = await self.storeFilesToDb(req, res, filesData)
return self.sendUploadResponse(req, res, result)
}
/** Chunk uploads */
self.finishChunks = async (req, res) => {
utils.assertRequestType(req, 'application/json')
if (!chunkedUploads) {
throw new ClientError('Chunked upload is disabled.', { statusCode: 403 })
}
let user
if (config.private === true) {
user = await utils.authorize(req)
if (!user) return
} else if (req.headers.token) {
user = await utils.assertUser(req.headers.token)
}
// Parse POST body
req.body = await req.json()
const files = req.body.files
if (!Array.isArray(files) || !files.length) {
throw new ClientError('Bad request.')
@ -710,7 +690,7 @@ self.finishChunks = async (req, res) => {
file.uuid = `${req.ip}_${file.uuid}`
})
return self.actuallyFinishChunks(req, res, user, files)
return self.actuallyFinishChunks(req, res, files)
.catch(error => {
// Unlink temp files (do not wait)
Promise.all(files.map(async file => {
@ -723,7 +703,7 @@ self.finishChunks = async (req, res) => {
})
}
self.actuallyFinishChunks = async (req, res, user, files) => {
self.actuallyFinishChunks = async (req, res, files) => {
const filesData = []
await Promise.all(files.map(async file => {
if (!file.uuid || typeof chunksData[file.uuid] === 'undefined') {
@ -754,7 +734,7 @@ self.actuallyFinishChunks = async (req, res, user, files) => {
throw new ClientError(`${extname ? `${extname.substr(1).toUpperCase()} files` : 'Files with no extension'} are not permitted.`)
}
const age = self.assertRetentionPeriod(user, file.age)
const age = self.assertRetentionPeriod(req.locals.user, file.age)
let size = file.size
if (size === undefined) {
@ -814,7 +794,7 @@ self.actuallyFinishChunks = async (req, res, user, files) => {
}))
if (utils.scan.instance) {
const scanResult = await self.scanFiles(req, user, filesData)
const scanResult = await self.scanFiles(req, filesData)
if (scanResult) {
throw new ClientError(scanResult)
}
@ -822,8 +802,8 @@ self.actuallyFinishChunks = async (req, res, user, files) => {
await self.stripTags(req, filesData)
const result = await self.storeFilesToDb(req, res, user, filesData)
return self.sendUploadResponse(req, res, user, result)
const result = await self.storeFilesToDb(req, res, filesData)
return self.sendUploadResponse(req, res, result)
}
self.cleanUpChunks = async uuid => {
@ -883,9 +863,9 @@ self.assertScanFileBypass = data => {
return false
}
self.scanFiles = async (req, user, filesData) => {
self.scanFiles = async (req, filesData) => {
const filenames = filesData.map(file => file.filename)
if (self.assertScanUserBypass(user, filenames)) {
if (self.assertScanUserBypass(req.locals.user, filenames)) {
return false
}
@ -950,7 +930,7 @@ self.stripTags = async (req, filesData) => {
/** Database functions */
self.storeFilesToDb = async (req, res, user, filesData) => {
self.storeFilesToDb = async (req, res, filesData) => {
const files = []
const exists = []
const albumids = []
@ -960,10 +940,10 @@ self.storeFilesToDb = async (req, res, user, filesData) => {
// Check if the file exists by checking its hash and size
const dbFile = await utils.db.table('files')
.where(function () {
if (user === undefined) {
this.whereNull('userid')
if (req.locals.user) {
this.where('userid', req.locals.user.id)
} else {
this.where('userid', user.id)
this.whereNull('userid')
}
})
.where({
@ -1002,8 +982,8 @@ self.storeFilesToDb = async (req, res, user, filesData) => {
timestamp
}
if (user) {
data.userid = user.id
if (req.locals.user) {
data.userid = req.locals.user.id
data.albumid = file.albumid
if (data.albumid !== null && !albumids.includes(data.albumid)) {
albumids.push(data.albumid)
@ -1023,10 +1003,11 @@ self.storeFilesToDb = async (req, res, user, filesData) => {
}))
if (files.length) {
// albumids should be empty if non-registerd users (no auth requests)
let authorizedIds = []
if (albumids.length) {
authorizedIds = await utils.db.table('albums')
.where({ userid: user.id })
.where({ userid: req.locals.user.id })
.whereIn('id', albumids)
.select('id')
.then(rows => rows.map(row => row.id))
@ -1057,7 +1038,7 @@ self.storeFilesToDb = async (req, res, user, filesData) => {
/** Final response */
self.sendUploadResponse = async (req, res, user, result) => {
self.sendUploadResponse = async (req, res, result) => {
// Send response
return res.json({
success: true,
@ -1079,7 +1060,7 @@ self.sendUploadResponse = async (req, res, user, result) => {
// If uploaded by user, add delete URL (intended for ShareX and its derivatives)
// Homepage uploader will not use this (use dashboard instead)
if (user) {
if (req.locals.user) {
map.deleteUrl = `${utils.conf.homeDomain}/file/${file.name}?delete`
}
@ -1091,30 +1072,20 @@ self.sendUploadResponse = async (req, res, user, result) => {
/** Delete uploads */
self.delete = async (req, res) => {
utils.assertRequestType(req, 'application/json')
// Parse POST body and re-map for .bulkDelete()
// Original API used by lolisafe v3's frontend
// Re-map Request.body for .bulkDelete()
// This is the legacy API used by lolisafe v3's frontend
// Meanwhile this fork's frontend uses .bulkDelete() straight away
req.body = await req.json()
.then(obj => {
const id = parseInt(obj.id)
return {
field: 'id',
values: isNaN(id) ? undefined : [id]
}
})
const id = parseInt(req.body.id)
req.body = {
_legacy: true,
field: 'id',
values: isNaN(id) ? undefined : [id]
}
return self.bulkDelete(req, res)
}
self.bulkDelete = async (req, res) => {
utils.assertRequestType(req, 'application/json')
const user = await utils.authorize(req)
// Parse POST body, if required
req.body = req.body || await req.json()
const field = req.body.field || 'id'
const values = req.body.values
@ -1122,7 +1093,7 @@ self.bulkDelete = async (req, res) => {
throw new ClientError('No array of files specified.')
}
const failed = await utils.bulkDeleteFromDb(field, values, user)
const failed = await utils.bulkDeleteFromDb(field, values, req.locals.user)
return res.json({ success: true, failed })
}
@ -1130,13 +1101,13 @@ self.bulkDelete = async (req, res) => {
/** List uploads */
self.list = async (req, res) => {
const user = await utils.authorize(req)
const all = req.headers.all === '1'
const filters = req.headers.filters
const minoffset = Number(req.headers.minoffset) || 0
const ismoderator = perms.is(user, 'moderator')
if (all && !ismoderator) return res.status(403).end()
const ismoderator = perms.is(req.locals.user, 'moderator')
if (all && !ismoderator) {
return res.status(403).end()
}
const albumid = req.path_parameters && Number(req.path_parameters.albumid)
const basedomain = utils.conf.domain
@ -1522,7 +1493,7 @@ self.list = async (req, res) => {
})
} else {
// If not listing all uploads, list user's uploads
this.where('userid', user.id)
this.where('userid', req.locals.user.id)
}
// Then, refine using any of the supplied 'albumid' keys and/or NULL flag
@ -1746,8 +1717,7 @@ self.list = async (req, res) => {
/** Get file info */
self.get = async (req, res) => {
const user = await utils.authorize(req)
const ismoderator = perms.is(user, 'moderator')
const ismoderator = perms.is(req.locals.user, 'moderator')
const identifier = req.path_parameters && req.path_parameters.identifier
if (identifier === undefined) {
@ -1757,8 +1727,9 @@ self.get = async (req, res) => {
const file = await utils.db.table('files')
.where('name', identifier)
.where(function () {
// Only allow moderators to get any files' information
if (!ismoderator) {
this.where('userid', user.id)
this.where('userid', req.locals.user.id)
}
})
.first()

View File

@ -398,33 +398,11 @@ self.assertRequestType = (req, type) => {
}
}
self.assertUser = async (token, fields) => {
const _fields = ['id', 'username', 'enabled', 'timestamp', 'permission', 'registration']
if (typeof fields === 'string') fields = [fields]
if (Array.isArray(fields)) {
_fields.push(...fields)
}
const user = await self.db.table('users')
.where('token', token)
.select(_fields)
.first()
if (user) {
if (user.enabled === false || user.enabled === 0) {
throw new ClientError('This account has been disabled.', { statusCode: 403 })
}
return user
} else {
throw new ClientError('Invalid token.', { statusCode: 403 })
}
}
self.authorize = async (req, fields) => {
const token = req.headers.token
if (token === undefined) {
throw new ClientError('No token provided.', { statusCode: 403 })
}
return self.assertUser(token, fields)
self.assertJSON = async (req, res) => {
// Assert Request Content-Type
self.assertRequestType(req, 'application/json')
// Parse JSON payload
req.body = await req.json()
}
self.generateThumbs = async (name, extname, force) => {
@ -801,10 +779,10 @@ self.invalidateStatsCache = type => {
}
const generateStats = async (req, res) => {
const user = await self.authorize(req)
const isadmin = perms.is(user, 'admin')
if (!isadmin) throw new ClientError('', { statusCode: 403 })
const isadmin = perms.is(req.locals.user, 'admin')
if (!isadmin) {
return res.status(403).end()
}
const hrstart = process.hrtime()
const stats = {}

View File

@ -1,10 +1,10 @@
const { Router } = require('hyper-express')
const routes = new Router()
const albumsController = require('./../controllers/albumsController')
const authController = require('./../controllers/authController')
const tokenController = require('./../controllers/tokenController')
const uploadController = require('./../controllers/uploadController')
const utilsController = require('./../controllers/utilsController')
const albums = require('./../controllers/albumsController')
const auth = require('./../controllers/authController')
const tokens = require('./../controllers/tokenController')
const upload = require('./../controllers/uploadController')
const utils = require('./../controllers/utilsController')
const config = require('./../config')
routes.get('/check', async (req, res) => {
@ -16,54 +16,75 @@ routes.get('/check', async (req, res) => {
fileIdentifierLength: config.uploads.fileIdentifierLength,
stripTags: config.uploads.stripTags
}
if (utilsController.retentions.enabled && utilsController.retentions.periods._) {
obj.temporaryUploadAges = utilsController.retentions.periods._
obj.defaultTemporaryUploadAge = utilsController.retentions.default._
if (utils.retentions.enabled && utils.retentions.periods._) {
obj.temporaryUploadAges = utils.retentions.periods._
obj.defaultTemporaryUploadAge = utils.retentions.default._
}
if (utilsController.clientVersion) {
obj.version = utilsController.clientVersion
if (utils.clientVersion) {
obj.version = utils.clientVersion
}
return res.json(obj)
})
routes.post('/login', authController.verify)
routes.post('/register', authController.register)
routes.post('/password/change', authController.changePassword)
routes.get('/uploads', uploadController.list)
routes.get('/uploads/:page', uploadController.list)
routes.post('/upload', uploadController.upload, {
// HyperExpress defaults to 250kb
// https://github.com/kartikk221/hyper-express/blob/6.2.4/docs/Server.md#server-constructor-options
max_body_length: parseInt(config.uploads.maxSize) * 1e6
})
routes.post('/upload/delete', uploadController.delete)
routes.post('/upload/bulkdelete', uploadController.bulkDelete)
routes.post('/upload/finishchunks', uploadController.finishChunks)
routes.get('/upload/get/:identifier', uploadController.get)
routes.post('/upload/:albumid', uploadController.upload)
routes.get('/album/get/:identifier', albumsController.get)
routes.get('/album/zip/:identifier', albumsController.generateZip)
routes.get('/album/:identifier', albumsController.getUpstreamCompat)
routes.get('/album/:albumid/:page', uploadController.list)
routes.get('/albums', albumsController.list)
routes.get('/albums/:page', albumsController.list)
routes.post('/albums', albumsController.create)
routes.post('/albums/addfiles', albumsController.addFiles)
routes.post('/albums/delete', albumsController.delete)
routes.post('/albums/disable', albumsController.disable)
routes.post('/albums/edit', albumsController.edit)
routes.post('/albums/rename', albumsController.rename)
routes.get('/albums/test', albumsController.test)
routes.get('/tokens', tokenController.list)
routes.post('/tokens/verify', tokenController.verify)
routes.post('/tokens/change', tokenController.change)
routes.get('/users', authController.listUsers)
routes.get('/users/:page', authController.listUsers)
routes.post('/users/create', authController.createUser)
routes.post('/users/edit', authController.editUser)
routes.post('/users/disable', authController.disableUser)
routes.post('/users/delete', authController.deleteUser)
routes.get('/stats', utilsController.stats)
/** ./controllers/authController.js */
routes.post('/login', utils.assertJSON, auth.verify)
routes.post('/register', utils.assertJSON, auth.register)
routes.post('/password/change', [auth.requireUser, utils.assertJSON], auth.changePassword)
routes.get('/users', auth.requireUser, auth.listUsers)
routes.get('/users/:page', auth.requireUser, auth.listUsers)
routes.post('/users/create', [auth.requireUser, utils.assertJSON], auth.createUser)
routes.post('/users/delete', [auth.requireUser, utils.assertJSON], auth.deleteUser)
routes.post('/users/disable', [auth.requireUser, utils.assertJSON], auth.disableUser)
routes.post('/users/edit', [auth.requireUser, utils.assertJSON], auth.editUser)
/** ./controllers/uploadController.js */
// HyperExpress defaults to 250kb
// https://github.com/kartikk221/hyper-express/blob/6.4.4/docs/Server.md#server-constructor-options
const maxBodyLength = parseInt(config.uploads.maxSize) * 1e6
routes.post('/upload', { max_body_length: maxBodyLength }, auth.optionalUser, upload.upload)
routes.post('/upload/:albumid', { max_body_length: maxBodyLength }, auth.optionalUser, upload.upload)
routes.post('/upload/finishchunks', [auth.optionalUser, utils.assertJSON], upload.finishChunks)
routes.get('/uploads', auth.requireUser, upload.list)
routes.get('/uploads/:page', auth.requireUser, upload.list)
routes.get('/album/:albumid/:page', auth.requireUser, upload.list)
routes.get('/upload/get/:identifier', auth.requireUser, upload.get)
routes.post('/upload/delete', [auth.requireUser, utils.assertJSON], upload.delete)
routes.post('/upload/bulkdelete', [auth.requireUser, utils.assertJSON], upload.bulkDelete)
/** ./controllers/albumsController.js */
routes.get('/albums', auth.requireUser, albums.list)
routes.get('/albums/:page', auth.requireUser, albums.list)
routes.get('/album/get/:identifier', albums.get)
routes.get('/album/zip/:identifier', albums.generateZip)
routes.get('/album/:identifier', albums.getUpstreamCompat)
routes.post('/albums', [auth.requireUser, utils.assertJSON], albums.create)
routes.post('/albums/addfiles', [auth.requireUser, utils.assertJSON], albums.addFiles)
routes.post('/albums/delete', [auth.requireUser, utils.assertJSON], albums.delete)
routes.post('/albums/disable', [auth.requireUser, utils.assertJSON], albums.disable)
routes.post('/albums/edit', [auth.requireUser, utils.assertJSON], albums.edit)
routes.post('/albums/rename', [auth.requireUser, utils.assertJSON], albums.rename)
/** ./controllers/tokenController.js **/
routes.get('/tokens', auth.requireUser, tokens.list)
routes.post('/tokens/change', async (req, res) => {
// Include user's "token" field into database query
return auth.requireUser(req, res, null, 'token')
}, tokens.change)
routes.post('/tokens/verify', utils.assertJSON, tokens.verify)
/** ./controllers/utilsController.js */
routes.get('/stats', [auth.requireUser], utils.stats)
module.exports = routes

View File

@ -1,6 +1,6 @@
const { Router } = require('hyper-express')
const routes = new Router()
const uploadController = require('./../controllers/uploadController')
const upload = require('./../controllers/uploadController')
const utils = require('./../controllers/utilsController')
const config = require('./../config')
@ -24,7 +24,7 @@ routes.post('/nojs', async (req, res) => {
files: result.files || [{}]
})
}
return uploadController.upload(req, res)
return upload.upload(req, res)
}, {
// HyperExpress defaults to 250kb
// https://github.com/kartikk221/hyper-express/blob/6.2.4/docs/Server.md#server-constructor-options