First pass of global user configuration through existing user API with role mappings.
This commit is contained in:
parent
4abe6192dc
commit
8cde219db9
|
@ -26,6 +26,10 @@ static_resources:
|
||||||
cluster: redis-service
|
cluster: redis-service
|
||||||
prefix_rewrite: "/"
|
prefix_rewrite: "/"
|
||||||
|
|
||||||
|
- match: { prefix: "/api/admin" }
|
||||||
|
route:
|
||||||
|
cluster: worker-dev
|
||||||
|
|
||||||
- match: { prefix: "/api/" }
|
- match: { prefix: "/api/" }
|
||||||
route:
|
route:
|
||||||
cluster: server-dev
|
cluster: server-dev
|
||||||
|
@ -123,3 +127,17 @@ static_resources:
|
||||||
address: {{ address }}
|
address: {{ address }}
|
||||||
port_value: 3000
|
port_value: 3000
|
||||||
|
|
||||||
|
- name: worker-dev
|
||||||
|
connect_timeout: 0.25s
|
||||||
|
type: strict_dns
|
||||||
|
lb_policy: round_robin
|
||||||
|
load_assignment:
|
||||||
|
cluster_name: worker-dev
|
||||||
|
endpoints:
|
||||||
|
- lb_endpoints:
|
||||||
|
- endpoint:
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: {{ address }}
|
||||||
|
port_value: 4002
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,11 @@ static_resources:
|
||||||
route:
|
route:
|
||||||
cluster: app-service
|
cluster: app-service
|
||||||
|
|
||||||
|
# special case for worker admin API
|
||||||
|
- match: { path: "/api/admin" }
|
||||||
|
route:
|
||||||
|
cluster: worker-service
|
||||||
|
|
||||||
# special case for when API requests are made, can just forward, not to minio
|
# special case for when API requests are made, can just forward, not to minio
|
||||||
- match: { prefix: "/api/" }
|
- match: { prefix: "/api/" }
|
||||||
route:
|
route:
|
||||||
|
|
|
@ -32,3 +32,7 @@ exports.getUserParams = (email = "", otherProps = {}) => {
|
||||||
endkey: `${DocumentTypes.USER}${SEPARATOR}${email}${UNICODE_MAX}`,
|
endkey: `${DocumentTypes.USER}${SEPARATOR}${email}${UNICODE_MAX}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.getEmailFromUserID = id => {
|
||||||
|
return id.split(`${DocumentTypes.USER}${SEPARATOR}`)[1]
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ const { jwt, local, google } = require("./middleware")
|
||||||
const { Cookies, UserStatus } = require("./constants")
|
const { Cookies, UserStatus } = require("./constants")
|
||||||
const { hash, compare } = require("./hashing")
|
const { hash, compare } = require("./hashing")
|
||||||
const { getAppId, setCookie } = require("./utils")
|
const { getAppId, setCookie } = require("./utils")
|
||||||
const { generateUserID, getUserParams } = require("./db/utils")
|
const { generateUserID, getUserParams, getEmailFromUserID } = require("./db/utils")
|
||||||
|
|
||||||
// Strategies
|
// Strategies
|
||||||
passport.use(new LocalStrategy(local.options, local.authenticate))
|
passport.use(new LocalStrategy(local.options, local.authenticate))
|
||||||
|
@ -36,6 +36,7 @@ module.exports = {
|
||||||
StaticDatabases,
|
StaticDatabases,
|
||||||
generateUserID,
|
generateUserID,
|
||||||
getUserParams,
|
getUserParams,
|
||||||
|
getEmailFromUserID,
|
||||||
hash,
|
hash,
|
||||||
compare,
|
compare,
|
||||||
getAppId,
|
getAppId,
|
||||||
|
|
|
@ -79,6 +79,7 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@budibase/auth": "^0.0.1",
|
||||||
"@budibase/client": "^0.8.9",
|
"@budibase/client": "^0.8.9",
|
||||||
"@budibase/string-templates": "^0.8.9",
|
"@budibase/string-templates": "^0.8.9",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
|
|
|
@ -7,7 +7,7 @@ const { generateUserID } = require("../../db/utils")
|
||||||
const { setCookie } = require("../../utilities")
|
const { setCookie } = require("../../utilities")
|
||||||
const { outputProcessing } = require("../../utilities/rowProcessor")
|
const { outputProcessing } = require("../../utilities/rowProcessor")
|
||||||
const { ViewNames } = require("../../db/utils")
|
const { ViewNames } = require("../../db/utils")
|
||||||
const { UserStatus } = require("../../constants")
|
const { UserStatus } = require("@budibase/auth")
|
||||||
const setBuilderToken = require("../../utilities/builder/setBuilderToken")
|
const setBuilderToken = require("../../utilities/builder/setBuilderToken")
|
||||||
|
|
||||||
const INVALID_ERR = "Invalid Credentials"
|
const INVALID_ERR = "Invalid Credentials"
|
||||||
|
|
|
@ -48,7 +48,7 @@ async function findRow(db, appId, tableId, rowId) {
|
||||||
appId,
|
appId,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
await usersController.find(ctx)
|
await usersController.findMetadata(ctx)
|
||||||
row = ctx.body
|
row = ctx.body
|
||||||
} else {
|
} else {
|
||||||
row = await db.get(rowId)
|
row = await db.get(rowId)
|
||||||
|
@ -103,7 +103,7 @@ exports.patch = async function(ctx) {
|
||||||
...row,
|
...row,
|
||||||
password: ctx.request.body.password,
|
password: ctx.request.body.password,
|
||||||
}
|
}
|
||||||
await usersController.update(ctx)
|
await usersController.updateMetadata(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ exports.save = async function(ctx) {
|
||||||
if (row.tableId === ViewNames.USERS) {
|
if (row.tableId === ViewNames.USERS) {
|
||||||
// the row has been updated, need to put it into the ctx
|
// the row has been updated, need to put it into the ctx
|
||||||
ctx.request.body = row
|
ctx.request.body = row
|
||||||
await usersController.create(ctx)
|
await usersController.createMetadata(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,7 +310,7 @@ exports.fetchTableRows = async function(ctx) {
|
||||||
let rows,
|
let rows,
|
||||||
table = await db.get(ctx.params.tableId)
|
table = await db.get(ctx.params.tableId)
|
||||||
if (ctx.params.tableId === ViewNames.USERS) {
|
if (ctx.params.tableId === ViewNames.USERS) {
|
||||||
await usersController.fetch(ctx)
|
await usersController.fetchMetadata(ctx)
|
||||||
rows = ctx.body
|
rows = ctx.body
|
||||||
} else {
|
} else {
|
||||||
const response = await db.allDocs(
|
const response = await db.allDocs(
|
||||||
|
|
|
@ -1,109 +1,137 @@
|
||||||
const CouchDB = require("../../db")
|
const CouchDB = require("../../db")
|
||||||
const bcrypt = require("../../utilities/bcrypt")
|
const {
|
||||||
const { generateUserID, getUserParams, ViewNames } = require("../../db/utils")
|
generateUserID,
|
||||||
|
getUserParams,
|
||||||
|
getEmailFromUserID,
|
||||||
|
} = require("@budibase/auth")
|
||||||
|
const { InternalTables } = require("../../db/utils")
|
||||||
const { getRole } = require("../../utilities/security/roles")
|
const { getRole } = require("../../utilities/security/roles")
|
||||||
const { UserStatus } = require("../../constants")
|
const { checkSlashesInUrl } = require("../../utilities")
|
||||||
|
const env = require("../../environment")
|
||||||
|
const fetch = require("node-fetch")
|
||||||
|
|
||||||
exports.fetch = async function(ctx) {
|
async function deleteGlobalUser(email) {
|
||||||
|
const endpoint = `/api/admin/users/${email}`
|
||||||
|
const reqCfg = { method: "DELETE" }
|
||||||
|
const response = await fetch(
|
||||||
|
checkSlashesInUrl(env.WORKER_URL + endpoint),
|
||||||
|
reqCfg
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getGlobalUsers(email = null) {
|
||||||
|
const endpoint = email ? `/api/admin/users/${email}` : `/api/admin/users`
|
||||||
|
const reqCfg = { method: "GET" }
|
||||||
|
const response = await fetch(
|
||||||
|
checkSlashesInUrl(env.WORKER_URL + endpoint),
|
||||||
|
reqCfg
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveGlobalUser(appId, email, body) {
|
||||||
|
const globalUser = await getGlobalUsers(email)
|
||||||
|
const roles = globalUser.roles || {}
|
||||||
|
if (body.roleId) {
|
||||||
|
roles.appId = body.roleId
|
||||||
|
}
|
||||||
|
const endpoint = `/api/admin/users`
|
||||||
|
const reqCfg = {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
...globalUser,
|
||||||
|
email,
|
||||||
|
password: body.password,
|
||||||
|
status: body.status,
|
||||||
|
roles,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
checkSlashesInUrl(env.WORKER_URL + endpoint),
|
||||||
|
reqCfg
|
||||||
|
)
|
||||||
|
await response.json()
|
||||||
|
delete body.email
|
||||||
|
delete body.password
|
||||||
|
delete body.roleId
|
||||||
|
delete body.status
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.fetchMetadata = async function(ctx) {
|
||||||
const database = new CouchDB(ctx.appId)
|
const database = new CouchDB(ctx.appId)
|
||||||
const users = (
|
const global = await getGlobalUsers()
|
||||||
|
const metadata = (
|
||||||
await database.allDocs(
|
await database.allDocs(
|
||||||
getUserParams(null, {
|
getUserParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
).rows.map(row => row.doc)
|
).rows.map(row => row.doc)
|
||||||
// user hashed password shouldn't ever be returned
|
const users = []
|
||||||
for (let user of users) {
|
for (let user of global) {
|
||||||
delete user.password
|
const info = metadata.find(meta => meta._id.includes(user.email))
|
||||||
|
users.push({
|
||||||
|
...user,
|
||||||
|
...info,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
ctx.body = users
|
ctx.body = users
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: need to replace this with something that purely manages metadata
|
exports.createMetadata = async function(ctx) {
|
||||||
exports.create = async function(ctx) {
|
const appId = ctx.appId
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = new CouchDB(appId)
|
||||||
const { email, password, roleId } = ctx.request.body
|
const { email, roleId } = ctx.request.body
|
||||||
|
|
||||||
if (!email || !password) {
|
|
||||||
ctx.throw(400, "email and Password Required.")
|
|
||||||
}
|
|
||||||
|
|
||||||
const role = await getRole(ctx.appId, roleId)
|
|
||||||
|
|
||||||
|
// check role valid
|
||||||
|
const role = await getRole(appId, roleId)
|
||||||
if (!role) ctx.throw(400, "Invalid Role")
|
if (!role) ctx.throw(400, "Invalid Role")
|
||||||
|
|
||||||
const hashedPassword = await bcrypt.hash(password)
|
const metadata = await saveGlobalUser(appId, email, ctx.request.body)
|
||||||
|
|
||||||
const user = {
|
const user = {
|
||||||
...ctx.request.body,
|
...metadata,
|
||||||
// these must all be after the object spread, make sure
|
|
||||||
// any values are overwritten, generateUserID will always
|
|
||||||
// generate the same ID for the user as it is not UUID based
|
|
||||||
_id: generateUserID(email),
|
_id: generateUserID(email),
|
||||||
type: "user",
|
type: "user",
|
||||||
password: hashedPassword,
|
tableId: InternalTables.USER_METADATA,
|
||||||
tableId: ViewNames.USERS,
|
|
||||||
}
|
|
||||||
// add the active status to a user if its not provided
|
|
||||||
if (user.status == null) {
|
|
||||||
user.status = UserStatus.ACTIVE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await db.post(user)
|
const response = await db.post(user)
|
||||||
ctx.status = 200
|
|
||||||
ctx.message = "User created successfully."
|
|
||||||
ctx.userId = response.id
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
_rev: response.rev,
|
_rev: response.rev,
|
||||||
email,
|
email,
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
if (err.status === 409) {
|
|
||||||
ctx.throw(400, "User exists already")
|
|
||||||
} else {
|
|
||||||
ctx.throw(err.status, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.update = async function(ctx) {
|
exports.updateMetadata = async function(ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const appId = ctx.appId
|
||||||
|
const db = new CouchDB(appId)
|
||||||
const user = ctx.request.body
|
const user = ctx.request.body
|
||||||
let dbUser
|
let email = user.email || getEmailFromUserID(user._id)
|
||||||
if (user.email && !user._id) {
|
const metadata = await saveGlobalUser(appId, email, ctx.request.body)
|
||||||
user._id = generateUserID(user.email)
|
|
||||||
}
|
|
||||||
// get user incase password removed
|
|
||||||
if (user._id) {
|
|
||||||
dbUser = await db.get(user._id)
|
|
||||||
}
|
|
||||||
if (user.password) {
|
|
||||||
user.password = await bcrypt.hash(user.password)
|
|
||||||
} else {
|
|
||||||
delete user.password
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await db.put({
|
if (!metadata._id) {
|
||||||
password: dbUser.password,
|
user._id = generateUserID(email)
|
||||||
...user,
|
}
|
||||||
|
ctx.body = await db.put({
|
||||||
|
...metadata,
|
||||||
})
|
})
|
||||||
user._rev = response.rev
|
|
||||||
|
|
||||||
ctx.status = 200
|
|
||||||
ctx.body = response
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async function(ctx) {
|
exports.destroyMetadata = async function(ctx) {
|
||||||
const database = new CouchDB(ctx.appId)
|
const db = new CouchDB(ctx.appId)
|
||||||
await database.destroy(generateUserID(ctx.params.email))
|
const email = ctx.params.email
|
||||||
|
await deleteGlobalUser(email)
|
||||||
|
await db.destroy(generateUserID(email))
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: `User ${ctx.params.email} deleted.`,
|
message: `User ${ctx.params.email} deleted.`,
|
||||||
}
|
}
|
||||||
ctx.status = 200
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.find = async function(ctx) {
|
exports.findMetadata = async function(ctx) {
|
||||||
const database = new CouchDB(ctx.appId)
|
const database = new CouchDB(ctx.appId)
|
||||||
let lookup = ctx.params.email
|
let lookup = ctx.params.email
|
||||||
? generateUserID(ctx.params.email)
|
? generateUserID(ctx.params.email)
|
||||||
|
|
|
@ -11,31 +11,31 @@ const router = Router()
|
||||||
|
|
||||||
router
|
router
|
||||||
.get(
|
.get(
|
||||||
"/api/users",
|
"/api/users/metadata",
|
||||||
authorized(PermissionTypes.USER, PermissionLevels.READ),
|
authorized(PermissionTypes.USER, PermissionLevels.READ),
|
||||||
controller.fetch
|
controller.fetchMetadata
|
||||||
)
|
)
|
||||||
.get(
|
.get(
|
||||||
"/api/users/:email",
|
"/api/users/metadata/:email",
|
||||||
authorized(PermissionTypes.USER, PermissionLevels.READ),
|
authorized(PermissionTypes.USER, PermissionLevels.READ),
|
||||||
controller.find
|
controller.findMetadata
|
||||||
)
|
)
|
||||||
.put(
|
.put(
|
||||||
"/api/users",
|
"/api/users/metadata",
|
||||||
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
|
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
|
||||||
controller.update
|
controller.updateMetadata
|
||||||
)
|
)
|
||||||
.post(
|
.post(
|
||||||
"/api/users",
|
"/api/users/metadata",
|
||||||
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
|
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
|
||||||
usage,
|
usage,
|
||||||
controller.create
|
controller.createMetadata
|
||||||
)
|
)
|
||||||
.delete(
|
.delete(
|
||||||
"/api/users/:email",
|
"/api/users/metadata/:email",
|
||||||
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
|
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
|
||||||
usage,
|
usage,
|
||||||
controller.destroy
|
controller.destroyMetadata
|
||||||
)
|
)
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
|
@ -75,7 +75,7 @@ module.exports.run = async function({ inputs, appId, apiKey, emitter }) {
|
||||||
if (env.isProd()) {
|
if (env.isProd()) {
|
||||||
await usage.update(apiKey, usage.Properties.USER, 1)
|
await usage.update(apiKey, usage.Properties.USER, 1)
|
||||||
}
|
}
|
||||||
await userController.create(ctx)
|
await userController.createMetadata(ctx)
|
||||||
return {
|
return {
|
||||||
response: ctx.body,
|
response: ctx.body,
|
||||||
// internal property not returned through the API
|
// internal property not returned through the API
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const { BUILTIN_ROLE_IDS } = require("../utilities/security/roles")
|
const { BUILTIN_ROLE_IDS } = require("../utilities/security/roles")
|
||||||
|
const { UserStatus } = require("@budibase/auth")
|
||||||
|
|
||||||
exports.LOGO_URL =
|
exports.LOGO_URL =
|
||||||
"https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg"
|
"https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg"
|
||||||
|
@ -27,11 +28,6 @@ exports.AuthTypes = {
|
||||||
EXTERNAL: "external",
|
EXTERNAL: "external",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.UserStatus = {
|
|
||||||
ACTIVE: "active",
|
|
||||||
INACTIVE: "inactive",
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.USERS_TABLE_SCHEMA = {
|
exports.USERS_TABLE_SCHEMA = {
|
||||||
_id: "ta_users",
|
_id: "ta_users",
|
||||||
type: "table",
|
type: "table",
|
||||||
|
@ -68,7 +64,7 @@ exports.USERS_TABLE_SCHEMA = {
|
||||||
constraints: {
|
constraints: {
|
||||||
type: exports.FieldTypes.STRING,
|
type: exports.FieldTypes.STRING,
|
||||||
presence: false,
|
presence: false,
|
||||||
inclusion: Object.values(exports.UserStatus),
|
inclusion: Object.values(UserStatus),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -34,7 +34,10 @@ const DocumentTypes = {
|
||||||
const ViewNames = {
|
const ViewNames = {
|
||||||
LINK: "by_link",
|
LINK: "by_link",
|
||||||
ROUTING: "screen_routes",
|
ROUTING: "screen_routes",
|
||||||
USERS: "ta_users",
|
}
|
||||||
|
|
||||||
|
const InternalTables = {
|
||||||
|
USER_METADATA: "ta_users",
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchIndexes = {
|
const SearchIndexes = {
|
||||||
|
@ -43,6 +46,7 @@ const SearchIndexes = {
|
||||||
|
|
||||||
exports.StaticDatabases = StaticDatabases
|
exports.StaticDatabases = StaticDatabases
|
||||||
exports.ViewNames = ViewNames
|
exports.ViewNames = ViewNames
|
||||||
|
exports.InternalTables = InternalTables
|
||||||
exports.DocumentTypes = DocumentTypes
|
exports.DocumentTypes = DocumentTypes
|
||||||
exports.SEPARATOR = SEPARATOR
|
exports.SEPARATOR = SEPARATOR
|
||||||
exports.UNICODE_MAX = UNICODE_MAX
|
exports.UNICODE_MAX = UNICODE_MAX
|
||||||
|
|
|
@ -279,7 +279,7 @@ class TestConfiguration {
|
||||||
roleId,
|
roleId,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
controllers.user.create
|
controllers.user.createMetadata
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,7 +289,7 @@ class TestConfiguration {
|
||||||
{
|
{
|
||||||
email,
|
email,
|
||||||
},
|
},
|
||||||
controllers.user.find
|
controllers.user.findMetadata
|
||||||
)
|
)
|
||||||
return this._req(
|
return this._req(
|
||||||
{
|
{
|
||||||
|
@ -297,7 +297,7 @@ class TestConfiguration {
|
||||||
status: "inactive",
|
status: "inactive",
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
controllers.user.update
|
controllers.user.updateMetadata
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"got": "^11.8.1",
|
"got": "^11.8.1",
|
||||||
"joi": "^17.2.1",
|
"joi": "^17.4.0",
|
||||||
"koa": "^2.7.0",
|
"koa": "^2.7.0",
|
||||||
"koa-body": "^4.2.0",
|
"koa-body": "^4.2.0",
|
||||||
"koa-compress": "^4.0.1",
|
"koa-compress": "^4.0.1",
|
||||||
|
|
|
@ -3,11 +3,28 @@ const passport = require("@budibase/auth")
|
||||||
const controller = require("../../controllers/admin")
|
const controller = require("../../controllers/admin")
|
||||||
const authController = require("../../controllers/admin/auth")
|
const authController = require("../../controllers/admin/auth")
|
||||||
const authenticated = require("../../../middleware/authenticated")
|
const authenticated = require("../../../middleware/authenticated")
|
||||||
|
const joiValidator = require("../../../middleware/joi-validator")
|
||||||
|
const Joi = require("joi")
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
|
function buildUserSaveValidation() {
|
||||||
|
// prettier-ignore
|
||||||
|
return joiValidator.body(Joi.object({
|
||||||
|
_id: Joi.string(),
|
||||||
|
_rev: Joi.string(),
|
||||||
|
email: Joi.string(),
|
||||||
|
password: Joi.string(),
|
||||||
|
// maps appId -> roleId for the user
|
||||||
|
roles: Joi.object()
|
||||||
|
.pattern(/.*/, Joi.string())
|
||||||
|
.required()
|
||||||
|
.unknown(true)
|
||||||
|
}).required().unknown(true))
|
||||||
|
}
|
||||||
|
|
||||||
router
|
router
|
||||||
.post("/api/admin/users", authenticated, controller.userSave)
|
.post("/api/admin/users", buildUserSaveValidation(), authenticated, controller.userSave)
|
||||||
.post("/api/admin/authenticate", authController.authenticate)
|
.post("/api/admin/authenticate", authController.authenticate)
|
||||||
.delete("/api/admin/users/:email", authenticated, controller.userDelete)
|
.delete("/api/admin/users/:email", authenticated, controller.userDelete)
|
||||||
.get("/api/admin/users", authenticated, controller.userFetch)
|
.get("/api/admin/users", authenticated, controller.userFetch)
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
function validate(schema, property) {
|
||||||
|
// Return a Koa middleware function
|
||||||
|
return (ctx, next) => {
|
||||||
|
if (!schema) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
let params = null
|
||||||
|
if (ctx[property] != null) {
|
||||||
|
params = ctx[property]
|
||||||
|
} else if (ctx.request[property] != null) {
|
||||||
|
params = ctx.request[property]
|
||||||
|
}
|
||||||
|
const { error } = schema.validate(params)
|
||||||
|
if (error) {
|
||||||
|
ctx.throw(400, `Invalid ${property} - ${error.message}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.body = schema => {
|
||||||
|
return validate(schema, "body")
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.params = schema => {
|
||||||
|
return validate(schema, "params")
|
||||||
|
}
|
|
@ -1261,10 +1261,10 @@ jmespath@0.15.0, jmespath@^0.15.0:
|
||||||
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
|
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
|
||||||
integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=
|
integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=
|
||||||
|
|
||||||
joi@^17.2.1:
|
joi@^17.4.0:
|
||||||
version "17.3.0"
|
version "17.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/joi/-/joi-17.3.0.tgz#f1be4a6ce29bc1716665819ac361dfa139fff5d2"
|
resolved "https://registry.yarnpkg.com/joi/-/joi-17.4.0.tgz#b5c2277c8519e016316e49ababd41a1908d9ef20"
|
||||||
integrity sha512-Qh5gdU6niuYbUIUV5ejbsMiiFmBdw8Kcp8Buj2JntszCkCfxJ9Cz76OtHxOZMPXrt5810iDIXs+n1nNVoquHgg==
|
integrity sha512-F4WiW2xaV6wc1jxete70Rw4V/VuMd6IN+a5ilZsxG4uYtUXWu2kq9W5P2dz30e7Gmw8RCbY/u/uk+dMPma9tAg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@hapi/hoek" "^9.0.0"
|
"@hapi/hoek" "^9.0.0"
|
||||||
"@hapi/topo" "^5.0.0"
|
"@hapi/topo" "^5.0.0"
|
||||||
|
|
Loading…
Reference in New Issue