mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2025-01-18 17:21:33 +00:00
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:
parent
2dccaacf33
commit
d7d6a29123
@ -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)))
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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 = {}
|
||||
|
117
routes/api.js
117
routes/api.js
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user