access-mate
Version:
Attribute base access control using o-is for the conditions
179 lines (161 loc) • 4.34 kB
JavaScript
'use strict'
const AccessMate = require('../../index')
const express = require('express')
const _ = require('lodash')
const uuid = require('uuid')
const knex = require('knex')(require('./knexfile').development)
const policySet = require('./lib/policy-set')
const cookieParser = require('cookie-parser')
const app = express()
app.use(express.json())
app.use(cookieParser())
const authorize = (options) => {
const fullOptions = Object.assign({
environment: {}
}, options)
return AccessMate.strategies.crud(policySet, fullOptions)
}
// pre-load the subject that way it is always available
app.use((req, res, next) => {
if(req.cookies.sessionUser) {
knex('user')
.where('id', '=', req.cookies.sessionUser)
.then(([user]) => {
req.subject = user || { admin: false, banned: false }
next()
})
.catch(next)
} else {
// not logged in, use a subject which makes the most sense.
req.subject = {
admin: false,
banned: false
}
next()
}
})
// Get a single user by id.
app.get('/user/:id', (req, res, next) => {
knex('user')
.where('id', '=', req.params.id)
.then((users) => {
if(users.length === 0) {
res.status(404).end()
} else {
const [user] = users
const auth = authorize({
target: 'user',
action: 'read',
resource: user,
subject: req.subject
})
if(!auth.authorize) {
res.status(401).end()
} else {
res.status(200).send(_.omit(auth.omit, user))
}
}
})
.catch(next)
})
// Return an error if there are any fields being set which aren't permitted.
const canWriteFields = (req, res, auth) => {
const fields = auth.omit.filter((field) => {
return _.has(req.body, field)
})
if(fields.length > 0) {
res.status(401).send({ fields })
return false
} else {
return true
}
}
// Post here is used for creating new users.
app.post('/user', (req, res, next) => {
const auth = authorize({
target: 'user',
action: 'create',
resource: req.body,
subject: req.subject
})
if(!auth.authorize) {
res.status(401).end()
} else if(canWriteFields(req, res, auth)) {
const user = Object.assign({
banned: false,
admin: false
}, res.body, { id: uuid.v4() })
knex('user')
.insert(user)
.then((user) => {
res.status(200).send(_.omit(auth.omit, user))
})
.catch(next)
}
})
// Return true if allowed to edit the resource. If not allowed to edit the
// resource this will send an appropriate 401 response and return false.
const canEdit = (req, res, user, updated) => {
const auth = authorize({
target: 'user',
action: 'update',
resource: updated,
previousResource: user,
subject: req.subject
})
// Return an error if access to the document as a whole is not permitted.
if(!auth.authorize) {
res.status(401).end()
return false
} else {
return canWriteFields(req, res, auth)
}
}
// Handles sending a properly filtered response after the user has been
// successfully updated.
const updateResponse = (req, res, updated) => {
// Now that the update has occured, we need to return a
// response of the updated resource. We could always return a
// 204 to avoid the read check but from a frontend dev's
// perspective this isn't a very nice thing to do :P
const readAuth = authorize({
target: 'user',
action: 'read',
resource: req.body,
subject: req.subject
})
// If for some bizare reason the subject is allowed to update but not read
// the object will return a 204 with no body (not sure what would truly be
// appropriate here, more of an edge case).
if(!readAuth.authorize) {
res.status(204).end()
} else {
const body = _.omit(readAuth.omit, updated)
res.status(200).send(body)
}
}
// patch is for partial updates - which does what we want for our acl here.
app.patch('/user/:id', (req, res, next) => {
knex('user')
.where('user', '=', req.params.id)
.then(([user]) => {
if(!user) {
res.status(404).end()
} else {
const updated = Object.assign({}, user, req.body)
if(canEdit(req, res, user, updated)) {
// Make sure that no one can edit the id.
delete req.body.id
// Make the update in the database.
return knex('user')
.update(req.body)
.where('id', '=', req.params.id)
.then(() => {
updateResponse(req, res, updated)
})
}
}
})
.catch(next)
})
app.listen(3000)