Permissions refactor; create sharing API; update dependencies

This commit is contained in:
garrettmills
2020-02-14 00:11:16 -06:00
parent b427a16601
commit 46f60a671a
17 changed files with 609 additions and 75 deletions

View File

@@ -0,0 +1,40 @@
const Middleware = require('libflitter/middleware/Middleware')
/*
* PageRoute Middleware
* -------------------------------------------------------------
* Put some description here!
*/
class PageRoute extends Middleware {
static get services() {
return [...super.services, 'models']
}
/*
* Run the middleware test.
* This method is required by all Flitter middleware.
* It should either call the next function in the stack,
* or it should handle the response accordingly.
*/
async test(req, res, next, args = {}){
const Page = this.models.get('api:Page')
const PageId = req.form.PageId ? req.form.PageId : req.params.PageId
if ( !PageId ) return res.status(400).message(`Missing PageId.`).api({})
const level = args.level ? args.level : 'view'
const page = await Page.findOne({UUID: PageId})
if ( !page ) return res.status(404).message(`Unable to find page with that id.`).api({})
if ( !(await page.is_accessible_by(req.user, level)) ) return req.security.deny()
if ( !req.form ) req.form = {}
req.form.page = page
/*
* Call the next function in the stack.
*/
next()
}
}
module.exports = exports = PageRoute

View File

@@ -0,0 +1,87 @@
const Middleware = require('libflitter/middleware/Middleware')
/*
* RequiredFields Middleware
* -------------------------------------------------------------
* Put some description here!
*/
class RequiredFields extends Middleware {
static get services() {
return [...super.services, 'configs', 'output', 'utility']
}
/*
* Run the middleware test.
* This method is required by all Flitter middleware.
* It should either call the next function in the stack,
* or it should handle the response accordingly.
*/
async test(req, res, next, args){
// Do stuff here
const search_fields = args.search ? (Array.isArray(args.search) ? args.search : [args.search]) : ['params', 'body', 'query']
const form_config = this.configs.get('api:forms:'+args.form)
const values = {}
for ( const field in form_config.fields ) {
if ( !form_config.fields.hasOwnProperty(field) ) continue
const field_config = form_config.fields[field]
let field_value = this.get_field({ request: req, field, search_fields })
if ( !field_value ) {
if ( field_config.required ) {
return this.fail({ response: res, reason: `Missing required field: ${field}`})
}
} else {
if ( field_config.infer !== false ) {
field_value = this.utility.infer(field_value)
}
if ( field_config.coerce ) {
field_value = field_config.coerce(field_value)
if ( field_config.coerce === Number && isNaN(field_value) ) {
return this.fail({ response: res, reason: 'Invalid numerical value for field: '+field })
}
}
if ( field_config.in_set ) {
if ( Array.isArray(field_config.in_set) ) {
if ( !field_config.in_set.includes(field_value) ) {
return this.fail({ response: res, reason: `Invalid value for ${field}. Value must be one of: ${field_config.join(', ')}`})
}
} else {
this.output.warn(`[Middleware RequiredFields] Invalid in_set for ${field} in ${args.form}. Must be array.`)
}
}
values[field] = field_value
}
}
req.form = values
/*
* Call the next function in the stack.
*/
next()
}
get_field({ request, field, search_fields }) {
for ( const search_field of search_fields ) {
if ( Object.keys(request).includes(search_field) ) {
if ( Object.keys(request[search_field]).includes(field) ) {
return request[search_field][field]
}
} else {
this.output.warn(`[Middleware RequiredFields] Requested search of request field that does not exist: ${search_field}`)
this.output.debug(`[Middleware RequiredFields] Available request keys: ${Object.keys(request).join(', ')}`)
}
}
}
fail({ response, reason }) {
return response.status(400).message(reason).api({})
}
}
module.exports = exports = RequiredFields

View File

@@ -0,0 +1,35 @@
const Middleware = require('libflitter/middleware/Middleware')
/*
* UserRoute Middleware
* -------------------------------------------------------------
* Put some description here!
*/
class UserRoute extends Middleware {
static get services() {
return [...super.services, 'models']
}
/*
* Run the middleware test.
* This method is required by all Flitter middleware.
* It should either call the next function in the stack,
* or it should handle the response accordingly.
*/
async test(req, res, next, args = {}){
const User = this.models.get('auth:User')
const user_id = req.form.user_id ? req.form.user_id : req.params.user_id
if ( !user_id ) return res.status(400).message('Midding user_id.').api({})
const user = await User.findById(user_id)
if ( !user ) return res.status(404).message('Unable to find user with that ID.').api({})
if ( !req.form ) req.form = {}
req.form.user = user
next()
}
}
module.exports = exports = UserRoute

View File

@@ -5,73 +5,88 @@
*/
const index = {
/*
* Define the prefix applied to each of these routes.
* For example, if prefix is '/auth':
* '/' becomes '/auth'
* '/login' becomes '/auth/login'
*/
prefix: '/api/v1',
/*
* Define middleware that should be applied to all
* routes defined in this file. Middleware should be
* included using its non-prefixed canonical name.
*
* You can pass arguments along to a middleware by
* specifying it as an array where the first element
* is the canonical name of the middleware and the
* second element is the argument passed to the
* handler's exec() method.
*/
middleware: [
'auth:UserOnly',
],
/*
* Define GET routes.
* These routes are registered as GET methods.
* Handlers for these routes should be specified as
* an array of canonical references to controller methods
* or middleware that are applied in order.
*/
get: {
// Get the file ref node config for the specified file ref
'/files/:PageId/:NodeId/get/:FilesId': ['controller::api:v1:File.get_config'],
// Download the specified file ID from the specified file ref node
'/files/:PageId/:NodeId/get/:FilesId/:FileId': ['controller::api:v1:File.download'],
'/hello_world': ['controller::api:v1:Misc.hello_world'],
// Get the data for the specified page
'/page/:PageId': ['controller::api:v1:Page.get_page'],
// Get the nodes present on the specified page
'/page/:PageId/nodes': ['controller::api:v1:Page.get_nodes'],
// Get the user's menu tree
'/menu/items': ['controller::api:v1:Menu.get_items'],
// Get the database ref node config for the specified database
'/db/:PageId/:NodeId/get/:DatabaseId': ['controller::api:v1:FormDatabase.get_config'],
// Get the column config records for the specified database
'/db/:PageId/:NodeId/get/:DatabaseId/columns': [ 'controller::api:v1:FormDatabase.get_columns' ],
// Get the row records for the specified database
'/db/:PageId/:NodeId/get/:DatabaseId/data': [ 'controller::api:v1:FormDatabase.get_data' ],
// Get the code ref node config for the specified code editor
'/code/:PageId/:NodeId/get/:CodiumId': ['controller::api:v1:FormCode.get_config'],
// Export the entire personal tree as HTML
'/data/export/html': ['controller::Export.html_export'],
},
post: {
// Upload the file in the 'uploaded_file' key to the specified file ref node
'/file/upload/:PageId/:NodeId/:FilesId': ['middleware::upload:UploadFile', 'controller::api:v1:File.save_upload'],
// Create a new file ref node
'/files/:PageId/:NodeId/create': ['controller::api:v1:File.create_config'],
// Delete a file ref node and its files
'/files/:PageId/:NodeId/delete/:FilesId': ['controller::api:v1:File.delete_group'],
// Save the data for the specified page
'/page/:PageId/save': ['controller::api:v1:Page.save_page'],
// Save the node data for the specified page
'/page/:PageId/nodes/save': ['controller::api:v1:Page.save_nodes'],
// Create a new page in the personal root
'/page/create': ['controller::api:v1:Page.create_top_level'],
// Create a new page as a child of the specified page
'/page/create-child': ['controller::api:v1:Page.create_child'],
// Delete the specified page
'/page/delete/:PageId': ['controller::api:v1:Page.delete_page'],
// Create a new database ref config
'/db/:PageId/:NodeId/create': ['controller::api:v1:FormDatabase.create_new'],
// Set the column configs for a database ref
'/db/:PageId/:NodeId/set/:DatabaseId/columns': [ 'controller::api:v1:FormDatabase.set_columns' ],
// Delete the specified database ref
'/db/:PageId/:NodeId/drop/:DatabaseId': [ 'controller::api:v1:FormDatabase.drop_database' ],
// Set the row data for the specified database ref
'/db/:PageId/:NodeId/set/:DatabaseId/data': ['controller::api:v1:FormDatabase.set_data'],
// Create a new code ref config
'/code/:PageId/:NodeId/create': ['controller::api:v1:FormCode.create_new'],
// Set the data for the specified code ref
'/code/:PageId/:NodeId/set/:CodiumId': ['controller::api:v1:FormCode.set_values'],
// delete the specified code ref
'/code/:PageId/:NodeId/delete/:CodiumId': ['controller::api:v1:FormCode.drop_code'],
},
}

View File

@@ -0,0 +1,46 @@
/*
* API v1 Routes
* -------------------------------------------------------------
* Description here
*/
const index = {
prefix: '/api/v1/share',
middleware: [
'auth:UserOnly',
],
get: {
'/page/:PageId/info': [
['middleware::api:RequiredFields', { form: 'sharing.page' }],
['middleware::api:PageRoute', {level: 'manage'}],
'controller::api:v1:Sharing.page_info',
],
'/page/:PageId/link/:level': [
['middleware::api:RequiredFields', { form: 'sharing.page_link'}],
['middleware::api:PageRoute', {level: 'manage'}],
'controller::api:v1:Sharing.get_link',
],
},
post: {
// Share a page with the specified user.
'/page/:PageId/share': [
['middleware::api:RequiredFields', { form: 'sharing.page_level' }],
['middleware::api:PageRoute', {level: 'manage'}],
'middleware::api:UserRoute',
'controller::api:v1:Sharing.share_page',
],
// Unshare a page with the specified user.
'/page/:PageId/revoke': [
['middleware::api:RequiredFields', { form: 'sharing.page_user' }],
['middleware::api:PageRoute', {level: 'manage'}],
'middleware::api:UserRoute',
'controller::api:v1:Sharing.revoke_page',
],
},
}
module.exports = exports = index

View File

@@ -6,11 +6,21 @@ module.exports = exports = {
'middleware::auth:KeyAction',
'controller::auth:KeyAction.handle',
],
'/login/:key': [
'middleware::auth:UserOnly',
'middleware::auth:KeyAction',
'controller::auth:KeyAction.handle',
],
},
post: {
'/:key': [
'middleware::auth:KeyAction',
'controller::auth:KeyAction.handle',
],
'/login/:key': [
'middleware::auth:UserOnly',
'middleware::auth:KeyAction',
'controller::auth:KeyAction.handle',
],
},
}