refactor server http errors

- error.coffee centralize all http errors response
- unauthorized = 401 and forbiden = 403
This commit is contained in:
Sébastien Moratinos 2014-01-14 23:13:47 +01:00
parent 00780d630c
commit fa218f7167
9 changed files with 67 additions and 116 deletions

View file

@ -5,6 +5,7 @@ User = require('./models/User')
UserHandler = require('./handlers/user')
config = require '../server_config'
nodemailer = require 'nodemailer'
errors = require './errors'
module.exports.setupRoutes = (app) ->
passport.serializeUser((user, done) -> done(null, user._id))
@ -32,9 +33,7 @@ module.exports.setupRoutes = (app) ->
passport.authenticate('local', (err, user, info) ->
return next(err) if err
if not user
res.status(401)
res.send([{message:info.message, property:info.property}])
return res.end()
return errors.unauthorized(res, [{message:info.message, property:info.property}])
req.logIn(user, (err) ->
return next(err) if (err)
@ -54,28 +53,25 @@ module.exports.setupRoutes = (app) ->
req.logout()
res.end()
)
app.post('/auth/reset', (req, res) ->
unless req.body.email
res.status(422)
res.send([{message:'Need an email specified.', property:email}])
return res.end()
return errors.badInput(res, [{message:'Need an email specified.', property:email}])
User.findOne({emailLower:req.body.email.toLowerCase()}).exec((err, user) ->
if not user
res.status(404)
res.send([{message:'not found.', property:'email'}])
return res.end()
return errors.notFound(res, [{message:'not found.', property:'email'}])
user.set('passwordReset', Math.random().toString(36).slice(2,7).toUpperCase())
user.save (err) =>
return returnServerError(res) if err
return errors.serverError(res) if err
if config.isProduction
transport = createSMTPTransport()
options = createMailOptions req.body.email, user.get('passwordReset')
transport.sendMail options, (error, response) ->
if error
console.error "Error sending mail: #{error.message or error}"
return returnServerError(res) if err
return errors.serverError(res) if err
else
return res.end()
else
@ -102,9 +98,3 @@ createSMTPTransport = ->
pass: config.mail.password
authMethod: "LOGIN"
smtpTransport
returnServerError = (res) ->
res.status(500)
res.send('Server error.')
res.end()

View file

@ -3,6 +3,7 @@ winston = require 'winston'
mongoose = require 'mongoose'
Grid = require 'gridfs-stream'
async = require 'async'
errors = require './errors'
testing = '--unittest' in process.argv
@ -42,9 +43,7 @@ module.exports.setupRoutes = (app) ->
catch error
winston.error("Error trying db method #{req.route.method} route #{parts} from #{name}: #{error}")
winston.error(error)
res.status(404)
res.write("Route #{req.path} not found.")
res.end()
errors.notFound(res, "Route #{req.path} not found.")
getSchema = (req, res, moduleName) ->
try
@ -55,6 +54,4 @@ getSchema = (req, res, moduleName) ->
catch error
winston.error("Error trying to grab schema from #{name}: #{error}")
res.status(404)
res.write("Schema #{moduleName} not found.")
res.end()
errors.notFound(res, "Schema #{moduleName} not found.")

View file

@ -1,31 +1,35 @@
module.exports.notFound = (req, res, message) ->
res.status(404)
message = "Route #{req.path} not found." unless message
res.write(message)
res.end()
module.exports.badMethod = (res) ->
res.status(405)
res.send('Method not allowed.')
res.end()
module.exports.custom = (res, code=500, message='Internal Server Error') ->
res.send code, message
res.end
module.exports.badInput = (res) ->
res.status(422)
res.send('Bad post input.')
res.end()
module.exports.unauthorized = (res, message='Unauthorized') ->
# TODO: The response MUST include a WWW-Authenticate header field
res.send 401, message
res.end
module.exports.conflict = (res) ->
res.status(409)
res.send('File exists.')
res.end()
module.exports.forbidden = (res, message='Forbidden') ->
res.send 403, message
res.end
module.exports.serverError = (res) ->
res.status(500)
res.send('Server error.')
res.end()
module.exports.notFound = (res, message='Not found.') ->
res.send 404, message
res.end
module.exports.unauthorized = (res) ->
res.status(403)
res.send('Unauthorized.')
res.end()
module.exports.badMethod = (res, message='Method Not Allowed') ->
# TODO: The response MUST include an Allow header containing a list of valid methods for the requested resource
res.send 405, message
res.end
module.exports.conflict = (res, message='Conflict. File exists') ->
res.send 409, message
res.end
module.exports.badInput = (res, message='Unprocessable Entity. Bad Input.') ->
res.send 422, message
res.end
module.exports.serverError = (res, message='Internal Server Error') ->
res.send 500, message
res.end

View file

@ -3,12 +3,13 @@ Grid = require 'gridfs-stream'
fs = require 'fs'
request = require 'request'
mongoose = require('mongoose')
errors = require './errors'
module.exports.setupRoutes = (app) ->
app.all '/file*', (req, res) ->
return fileGet(req, res) if req.route.method is 'get'
return filePost(req, res) if req.route.method is 'post'
return returnBadMethod(res)
return errors.badMethod(res)
fileGet = (req, res) ->
@ -27,16 +28,16 @@ fileGet = (req, res) ->
if isFolder
Grid.gfs.collection('media').find query, (err, cursor) ->
return returnServerError(res) if err
return errors.serverError(res) if err
results = cursor.toArray (err, results) ->
return returnServerError(res) if err
return errors.serverError(res) if err
res.setHeader('Content-Type', 'text/json')
res.send(results)
res.end()
else
Grid.gfs.collection('media').findOne query, (err, filedata) =>
return returnNotFound(req, res) if not filedata
return errors.notFound(res) if not filedata
readstream = Grid.gfs.createReadStream({_id: filedata._id, root:'media'})
if req.headers['if-modified-since'] is filedata.uploadDate
res.status(304)
@ -69,12 +70,13 @@ postFileSchema =
required: ['filename', 'mimetype', 'path']
filePost = (req, res) ->
return returnNotAllowed(req, res) unless req.user.isAdmin()
return errors.forbidden(res) unless req.user.isAdmin()
options = req.body
tv4 = require('tv4').tv4
valid = tv4.validate(options, postFileSchema)
hasSource = options.url or options.postName or options.b64png
return returnBadInput(res) if (not valid) or (not hasSource)
# TODO : give tv4.error to badInput
return errors.badInput(res) if (not valid) or (not hasSource)
return saveURL(req, res) if options.url
return saveFile(req, res) if options.postName
return savePNG(req, res) if options.b64png
@ -82,7 +84,7 @@ filePost = (req, res) ->
saveURL = (req, res) ->
options = createPostOptions(req)
checkExistence options, res, req.body.force, (err) ->
return returnServerError(res) if err
return errors.serverError(res) if err
writestream = Grid.gfs.createWriteStream(options)
request(req.body.url).pipe(writestream)
handleStreamEnd(res, writestream)
@ -100,7 +102,7 @@ saveFile = (req, res) ->
savePNG = (req, res) ->
options = createPostOptions(req)
checkExistence options, res, req.body.force, (err) ->
return returnServerError(res) if err
return errors.serverError(res) if err
writestream = Grid.gfs.createWriteStream(options)
img = new Buffer(req.body.b64png, 'base64')
streamBuffers = require 'stream-buffers'
@ -116,13 +118,13 @@ checkExistence = (options, res, force, done) ->
}
Grid.gfs.collection('media').find(q).toArray (err, files) ->
if files.length and not force
returnConflict(res)
errors.conflict(res)
done(true)
else if files.length
q = { _id: files[0]._id }
q.root = 'media'
Grid.gfs.remove q, (err) ->
return returnServerError(res) if err
return errors.serverError(res) if err
done()
else
done()
@ -133,7 +135,7 @@ handleStreamEnd = (res, stream) ->
res.end()
stream.on 'error', ->
return returnServerError(res)
return errors.serverError(res)
CHUNK_SIZE = 1024*256
@ -159,35 +161,3 @@ createPostOptions = (req) ->
options.metadata.description = req.body.description if req.body.description?
options
returnNotAllowed = (req, res, message) ->
res.status(403)
message = "Can't do that, Dave." unless message
res.write(message)
res.end()
returnNotFound = (req, res, message) ->
res.status(404)
message = "Route #{req.path} not found." unless message
res.write(message)
res.end()
returnBadMethod = (res) ->
res.status(405)
res.send('Method not allowed.')
res.end()
returnBadInput = (res) ->
res.status(422)
res.send('Bad post input.')
res.end()
returnConflict = (res) ->
res.status(409)
res.send('File exists.')
res.end()
returnServerError = (res) ->
res.status(500)
res.send('Server error.')
res.end()

View file

@ -12,7 +12,7 @@ folderGet = (req, res) ->
folder = req.path[7..]
userfolder = "/user-#{req.user.id}/"
folder = userfolder if folder is '/me/'
return errors.unauthorized(res) unless (folder is userfolder) or (req.user.isAdmin())
return errors.forbidden(res) unless (folder is userfolder) or (req.user.isAdmin())
mongoose.connection.db.collection 'media.files', (errors, collection) ->
collection.find({'metadata.path': folder}).toArray (err, results) ->

View file

@ -1,6 +1,7 @@
async = require 'async'
mongoose = require('mongoose')
Grid = require 'gridfs-stream'
errors = require '../errors'
module.exports = class Handler
# subclasses should override these properties
@ -38,17 +39,14 @@ module.exports = class Handler
props
# sending functions
sendUnauthorizedError: (res) -> @sendError(res, 403, "Unauthorized.")
sendNotFoundError: (res) -> @sendError(res, 404, 'Resource not found.')
sendMethodNotAllowed: (res) -> @sendError(res, 405, 'Method not allowed.')
sendBadInputError: (res, message) -> @sendError(res, 422, message)
sendDatabaseError: (res, err) -> @sendError(res, 500, 'Database error.')
sendUnauthorizedError: (res) -> errors.forbidden(res) #TODO: rename sendUnauthorizedError to sendForbiddenError
sendNotFoundError: (res) -> errors.notFound(res)
sendMethodNotAllowed: (res) -> errors.badMethod(res)
sendBadInputError: (res, message) -> errors.badInput(res, message)
sendDatabaseError: (res, err) -> errors.serverError(res, 'Database error, ' + err)
sendError: (res, code, message) ->
console.warn "Sending an error code", code, message
res.status(code)
res.send(message)
res.end()
errors.custom(res, code, message)
sendSuccess: (res, message) ->
res.send(message)

View file

@ -41,8 +41,7 @@ LevelHandler = class LevelHandler extends Handler
Session.findOne(sessionQuery).exec (err, doc) =>
return @sendDatabaseError(res, err) if err
if doc
res.send(doc)
res.end()
@sendSuccess(res, doc)
return
initVals = sessionQuery
@ -71,8 +70,7 @@ LevelHandler = class LevelHandler extends Handler
Feedback.findOne(feedbackQuery).exec (err, doc) =>
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res) unless doc?
res.send(doc)
res.end()
@sendSuccess(res, doc)
return
postEditableProperties: ['name']

View file

@ -139,8 +139,7 @@ UserHandler = class UserHandler extends Handler
req.user.set('signedCLA', doc.created)
req.user.save (err) ->
return @sendDatabaseError(res, err) if err
res.send({result:'success'})
res.end()
@sendSuccess(res, {result:'success'})
module.exports = new UserHandler()
@ -157,17 +156,14 @@ module.exports.setupMiddleware = (app) ->
loginUser = (req, res, user, send=true, next=null) ->
user.save((err) ->
if err
res.status(500)
return res.end()
return @sendDatabaseError(res, err)
req.logIn(user, (err) ->
if err
res.status(500)
return res.end()
return @sendDatabaseError(res, err)
if send
res.send(user)
return res.end()
return @sendSuccess(res, user)
next() if next
)
)

View file

@ -92,8 +92,6 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl
req = request.put getURL('/db/user'),
(err, res) ->
expect(res.statusCode).toBe(404)
expect(res.body).toBe('Resource not found.')
done()
done()
form = req.form()
form.append('_id', '513108d4cb8b610000000004')