Fixing some bugs with previous commit and updating to add the functionality of the api/routing/client.
This commit is contained in:
parent
63f7641c9e
commit
6a50b1057d
|
@ -50,7 +50,7 @@ export const getFrontendStore = () => {
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
const screens = await api.get("/api/screens").then(r => r.json())
|
const screens = await api.get("/api/screens").then(r => r.json())
|
||||||
const routing = await api.get("/api/routing").then(r => r.json())
|
const routing = await api.get("/api/routing/client").then(r => r.json())
|
||||||
|
|
||||||
const mainScreens = screens.filter(screen =>
|
const mainScreens = screens.filter(screen =>
|
||||||
screen._id.includes(pkg.pages.main._id)
|
screen._id.includes(pkg.pages.main._id)
|
||||||
|
|
|
@ -2,6 +2,7 @@ const CouchDB = require("../../db")
|
||||||
const {
|
const {
|
||||||
BUILTIN_LEVELS,
|
BUILTIN_LEVELS,
|
||||||
AccessLevel,
|
AccessLevel,
|
||||||
|
getAccessLevel,
|
||||||
} = require("../../utilities/security/accessLevels")
|
} = require("../../utilities/security/accessLevels")
|
||||||
const {
|
const {
|
||||||
generateAccessLevelID,
|
generateAccessLevelID,
|
||||||
|
@ -22,8 +23,7 @@ exports.fetch = async function(ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.find = async function(ctx) {
|
exports.find = async function(ctx) {
|
||||||
const db = new CouchDB(ctx.user.appId)
|
ctx.body = await getAccessLevel(ctx.user.appId, ctx.params.levelId)
|
||||||
ctx.body = await db.get(ctx.params.levelId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async function(ctx) {
|
exports.save = async function(ctx) {
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
const { getRoutingInfo } = require("../../utilities/routing")
|
const { getRoutingInfo } = require("../../utilities/routing")
|
||||||
const { AccessController } = require("../../utilities/security/accessLevels")
|
const {
|
||||||
|
getUserAccessLevelHierarchy,
|
||||||
|
BUILTIN_LEVEL_IDS,
|
||||||
|
} = require("../../utilities/security/accessLevels")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the full routing structure by querying the routing view and processing the result into the tree.
|
||||||
|
* @param {string} appId The application to produce the routing structure for.
|
||||||
|
* @returns {Promise<object>} The routing structure, this is the full structure designed for use in the builder,
|
||||||
|
* if the client routing is required then the updateRoutingStructureForUserLevel should be used.
|
||||||
|
*/
|
||||||
async function getRoutingStructure(appId) {
|
async function getRoutingStructure(appId) {
|
||||||
const screenRoutes = await getRoutingInfo(appId)
|
const screenRoutes = await getRoutingInfo(appId)
|
||||||
const routing = {}
|
const routing = {}
|
||||||
|
@ -36,13 +45,68 @@ async function getRoutingStructure(appId) {
|
||||||
return { routes: routing }
|
return { routes: routing }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function for recursing through the routing structure and adjusting it to match the user's access level
|
||||||
|
* @param {object} path The routing path, retrieved from the getRoutingStructure function, when this recurses it will
|
||||||
|
* call with this parameter updated to the various subpaths.
|
||||||
|
* @param {string[]} accessLevelIds The full list of access level IDs, this has to be passed in as otherwise we would
|
||||||
|
* need to make this an async function purely for the first call, adds confusion to the recursion.
|
||||||
|
* @returns {object} The routing structure after it has been updated.
|
||||||
|
*/
|
||||||
|
function updateRoutingStructureForUserLevel(path, accessLevelIds) {
|
||||||
|
for (let routeKey of Object.keys(path)) {
|
||||||
|
const pathStructure = path[routeKey]
|
||||||
|
if (pathStructure.subpaths) {
|
||||||
|
pathStructure.subpaths = updateRoutingStructureForUserLevel(
|
||||||
|
pathStructure.subpaths,
|
||||||
|
accessLevelIds
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (pathStructure.screens) {
|
||||||
|
const accessLevelOptions = Object.keys(pathStructure.screens)
|
||||||
|
// starts with highest level and works down through inheritance
|
||||||
|
let found = false
|
||||||
|
// special case for when the screen has no access control
|
||||||
|
if (accessLevelOptions.length === 1 && !accessLevelOptions[0]) {
|
||||||
|
pathStructure.screenId = pathStructure.screens[accessLevelOptions[0]]
|
||||||
|
pathStructure.accessLevelId = BUILTIN_LEVEL_IDS.BASIC
|
||||||
|
found = true
|
||||||
|
} else {
|
||||||
|
for (let levelId of accessLevelIds) {
|
||||||
|
if (accessLevelOptions.indexOf(levelId) !== -1) {
|
||||||
|
pathStructure.screenId = pathStructure.screens[levelId]
|
||||||
|
pathStructure.accessLevelId = levelId
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// remove the screen options now that we've processed it
|
||||||
|
delete pathStructure.screens
|
||||||
|
// if no option was found then remove the route, user can't access it
|
||||||
|
if (!found) {
|
||||||
|
delete path[routeKey]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
exports.fetch = async ctx => {
|
exports.fetch = async ctx => {
|
||||||
ctx.body = await getRoutingStructure(ctx.appId)
|
ctx.body = await getRoutingStructure(ctx.appId)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.clientFetch = async ctx => {
|
exports.clientFetch = async ctx => {
|
||||||
const routing = getRoutingStructure(ctx.appId)
|
const routing = await getRoutingStructure(ctx.appId)
|
||||||
// use the access controller to pick which access level is applicable to this user
|
const accessLevelId = ctx.user.accessLevel._id
|
||||||
const accessController = new AccessController(ctx.appId)
|
// builder is a special case, always return the full routing structure
|
||||||
// TODO: iterate through the routes and pick which the user can access
|
if (accessLevelId === BUILTIN_LEVEL_IDS.BUILDER) {
|
||||||
|
ctx.body = routing
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const accessLevelIds = await getUserAccessLevelHierarchy(
|
||||||
|
ctx.appId,
|
||||||
|
accessLevelId
|
||||||
|
)
|
||||||
|
ctx.body = updateRoutingStructureForUserLevel(routing.routes, accessLevelIds)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ const router = Router()
|
||||||
|
|
||||||
// gets the full structure, not just the correct screen ID for your access level
|
// gets the full structure, not just the correct screen ID for your access level
|
||||||
router
|
router
|
||||||
.get("/api/routing", authorized(BUILDER), controller.fetch)
|
|
||||||
.get("/api/routing/client", controller.clientFetch)
|
.get("/api/routing/client", controller.clientFetch)
|
||||||
|
.get("/api/routing", authorized(BUILDER), controller.fetch)
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
|
@ -188,7 +188,7 @@ const createUserWithPermissions = async (
|
||||||
|
|
||||||
const anonUser = {
|
const anonUser = {
|
||||||
userId: "ANON",
|
userId: "ANON",
|
||||||
accessLevelId: BUILTIN_LEVEL_IDS.ANON,
|
accessLevelId: BUILTIN_LEVEL_IDS.PUBLIC,
|
||||||
appId: appId,
|
appId: appId,
|
||||||
version: packageJson.version,
|
version: packageJson.version,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
const jwt = require("jsonwebtoken")
|
const jwt = require("jsonwebtoken")
|
||||||
const STATUS_CODES = require("../utilities/statusCodes")
|
const STATUS_CODES = require("../utilities/statusCodes")
|
||||||
const accessLevelController = require("../api/controllers/accesslevel")
|
const { getAccessLevel } = require("../utilities/security/accessLevels")
|
||||||
const { BUILTIN_LEVEL_ID_ARRAY } = require("../utilities/security/accessLevels")
|
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
const { AuthTypes } = require("../constants")
|
const { AuthTypes } = require("../constants")
|
||||||
const { getAppId, getCookieName, setCookie } = require("../utilities")
|
const { getAppId, getCookieName, setCookie } = require("../utilities")
|
||||||
|
@ -60,31 +59,3 @@ module.exports = async (ctx, next) => {
|
||||||
|
|
||||||
await next()
|
await next()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the full access level object either from constants
|
|
||||||
* or the database based on the access level ID passed.
|
|
||||||
*
|
|
||||||
* @param {*} appId - appId of the user
|
|
||||||
* @param {*} accessLevelId - the id of the users access level
|
|
||||||
*/
|
|
||||||
const getAccessLevel = async (appId, accessLevelId) => {
|
|
||||||
if (BUILTIN_LEVEL_ID_ARRAY.indexOf(accessLevelId) !== -1) {
|
|
||||||
return {
|
|
||||||
_id: accessLevelId,
|
|
||||||
name: accessLevelId,
|
|
||||||
permissions: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const findAccessContext = {
|
|
||||||
params: {
|
|
||||||
levelId: accessLevelId,
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
appId,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await accessLevelController.find(findAccessContext)
|
|
||||||
return findAccessContext.body
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
const CouchDB = require("../../db")
|
const CouchDB = require("../../db")
|
||||||
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
|
||||||
const BUILTIN_IDS = {
|
const BUILTIN_IDS = {
|
||||||
ADMIN: "ADMIN",
|
ADMIN: "ADMIN",
|
||||||
POWER: "POWER_USER",
|
POWER: "POWER_USER",
|
||||||
BASIC: "BASIC",
|
BASIC: "BASIC",
|
||||||
ANON: "ANON",
|
PUBLIC: "PUBLIC",
|
||||||
BUILDER: "BUILDER",
|
BUILDER: "BUILDER",
|
||||||
}
|
}
|
||||||
|
|
||||||
function AccessLevel(id, name, inherits = null) {
|
function AccessLevel(id, name, inherits) {
|
||||||
this._id = id
|
this._id = id
|
||||||
this.name = name
|
this.name = name
|
||||||
if (inherits) {
|
if (inherits) {
|
||||||
|
@ -19,8 +20,8 @@ function AccessLevel(id, name, inherits = null) {
|
||||||
exports.BUILTIN_LEVELS = {
|
exports.BUILTIN_LEVELS = {
|
||||||
ADMIN: new AccessLevel(BUILTIN_IDS.ADMIN, "Admin", BUILTIN_IDS.POWER),
|
ADMIN: new AccessLevel(BUILTIN_IDS.ADMIN, "Admin", BUILTIN_IDS.POWER),
|
||||||
POWER: new AccessLevel(BUILTIN_IDS.POWER, "Power", BUILTIN_IDS.BASIC),
|
POWER: new AccessLevel(BUILTIN_IDS.POWER, "Power", BUILTIN_IDS.BASIC),
|
||||||
BASIC: new AccessLevel(BUILTIN_IDS.BASIC, "Basic", BUILTIN_IDS.ANON),
|
BASIC: new AccessLevel(BUILTIN_IDS.BASIC, "Basic", BUILTIN_IDS.PUBLIC),
|
||||||
ANON: new AccessLevel(BUILTIN_IDS.ANON, "Anonymous"),
|
ANON: new AccessLevel(BUILTIN_IDS.PUBLIC, "Public"),
|
||||||
BUILDER: new AccessLevel(BUILTIN_IDS.BUILDER, "Builder"),
|
BUILDER: new AccessLevel(BUILTIN_IDS.BUILDER, "Builder"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,27 +37,64 @@ function isBuiltin(accessLevel) {
|
||||||
return exports.BUILTIN_LEVEL_ID_ARRAY.indexOf(accessLevel) !== -1
|
return exports.BUILTIN_LEVEL_ID_ARRAY.indexOf(accessLevel) !== -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the access level object, this is mainly useful for two purposes, to check if the level exists and
|
||||||
|
* to check if the access level inherits any others.
|
||||||
|
* @param {string} appId The app in which to look for the access level.
|
||||||
|
* @param {string|null} accessLevelId The level ID to lookup.
|
||||||
|
* @returns {Promise<AccessLevel|object|null>} The access level object, which may contain an "inherits" property.
|
||||||
|
*/
|
||||||
|
exports.getAccessLevel = async (appId, accessLevelId) => {
|
||||||
|
if (!accessLevelId) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
let accessLevel
|
||||||
|
if (isBuiltin(accessLevelId)) {
|
||||||
|
accessLevel = cloneDeep(
|
||||||
|
Object.values(exports.BUILTIN_LEVELS).find(
|
||||||
|
level => level._id === accessLevelId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
const db = new CouchDB(appId)
|
||||||
|
accessLevel = await db.get(accessLevelId)
|
||||||
|
}
|
||||||
|
return accessLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an ordered array of the user's inherited access level IDs, this can be used
|
||||||
|
* to determine if a user can access something that requires a specific access level.
|
||||||
|
* @param {string} appId The ID of the application from which access levels should be obtained.
|
||||||
|
* @param {string} userAccessLevelId The user's access level, this can be found in their access token.
|
||||||
|
* @returns {Promise<string[]>} returns an ordered array of the access levels, with the first being their
|
||||||
|
* highest level of access and the last being the lowest level.
|
||||||
|
*/
|
||||||
|
exports.getUserAccessLevelHierarchy = async (appId, userAccessLevelId) => {
|
||||||
|
// special case, if they don't have a level then they are a public user
|
||||||
|
if (!userAccessLevelId) {
|
||||||
|
return [BUILTIN_IDS.PUBLIC]
|
||||||
|
}
|
||||||
|
let accessLevelIds = [userAccessLevelId]
|
||||||
|
let userAccess = await exports.getAccessLevel(appId, userAccessLevelId)
|
||||||
|
// check if inherited makes it possible
|
||||||
|
while (
|
||||||
|
userAccess &&
|
||||||
|
userAccess.inherits &&
|
||||||
|
accessLevelIds.indexOf(userAccess.inherits) === -1
|
||||||
|
) {
|
||||||
|
accessLevelIds.push(userAccess.inherits)
|
||||||
|
// go to get the inherited incase it inherits anything
|
||||||
|
userAccess = await exports.getAccessLevel(appId, userAccess.inherits)
|
||||||
|
}
|
||||||
|
// add the user's actual level at the end (not at start as that stops iteration
|
||||||
|
return accessLevelIds
|
||||||
|
}
|
||||||
|
|
||||||
class AccessController {
|
class AccessController {
|
||||||
constructor(appId) {
|
constructor(appId) {
|
||||||
this.appId = appId
|
this.appId = appId
|
||||||
this.accessLevels = {}
|
this.userHierarchies = {}
|
||||||
}
|
|
||||||
|
|
||||||
async getAccessLevel(accessLevelId) {
|
|
||||||
if (this.accessLevels[accessLevelId]) {
|
|
||||||
return this.accessLevels[accessLevelId]
|
|
||||||
}
|
|
||||||
let accessLevel
|
|
||||||
if (isBuiltin(accessLevelId)) {
|
|
||||||
accessLevel = Object.values(exports.BUILTIN_LEVELS).find(
|
|
||||||
level => level._id === accessLevelId
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
const db = new CouchDB(this.appId)
|
|
||||||
accessLevel = await db.get(accessLevelId)
|
|
||||||
}
|
|
||||||
this.accessLevels[accessLevelId] = accessLevel
|
|
||||||
return accessLevel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async hasAccess(tryingAccessLevelId, userAccessLevelId) {
|
async hasAccess(tryingAccessLevelId, userAccessLevelId) {
|
||||||
|
@ -70,16 +108,16 @@ class AccessController {
|
||||||
) {
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
let userAccess = await this.getAccessLevel(userAccessLevelId)
|
let accessLevelIds = this.userHierarchies[userAccessLevelId]
|
||||||
// check if inherited makes it possible
|
if (!accessLevelIds) {
|
||||||
while (userAccess.inherits) {
|
accessLevelIds = await exports.getUserAccessLevelHierarchy(
|
||||||
if (tryingAccessLevelId === userAccess.inherits) {
|
this.appId,
|
||||||
return true
|
userAccessLevelId
|
||||||
}
|
)
|
||||||
// go to get the inherited incase it inherits anything
|
this.userHierarchies[userAccessLevelId] = userAccessLevelId
|
||||||
userAccess = await this.getAccessLevel(userAccess.inherits)
|
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
return accessLevelIds.indexOf(tryingAccessLevelId) !== -1
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkScreensAccess(screens, userAccessLevelId) {
|
async checkScreensAccess(screens, userAccessLevelId) {
|
||||||
|
|
Loading…
Reference in New Issue