forked from tsai/budibase
12 changed files with 159 additions and 105 deletions
@ -0,0 +1,79 @@ |
|||||
|
const CouchDB = require("../../../db") |
||||
|
const { StaticDatabases, generateUserID, getUserParams } = require("../../../db/utils") |
||||
|
const { hash } = require("./utils") |
||||
|
const { UserStatus } = require("../../../constants") |
||||
|
|
||||
|
const USER_DB = StaticDatabases.USER.name |
||||
|
|
||||
|
exports.userSave = async ctx => { |
||||
|
const db = new CouchDB(USER_DB) |
||||
|
const { email, password, _id } = ctx.request.body |
||||
|
const hashedPassword = password ? await hash(password) : null |
||||
|
let user = { |
||||
|
...ctx.request.body, |
||||
|
_id: generateUserID(email), |
||||
|
password: hashedPassword, |
||||
|
}, dbUser |
||||
|
// in-case user existed already
|
||||
|
if (_id) { |
||||
|
dbUser = await db.get(_id) |
||||
|
} |
||||
|
// 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({ |
||||
|
password: hashedPassword || dbUser.password, |
||||
|
...user, |
||||
|
}) |
||||
|
ctx.body = { |
||||
|
_id: response.id, |
||||
|
_rev: response.rev, |
||||
|
email, |
||||
|
} |
||||
|
} catch (err) { |
||||
|
if (err.status === 409) { |
||||
|
ctx.throw(400, "User exists already") |
||||
|
} else { |
||||
|
ctx.throw(err.status, err) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
exports.userDelete = async ctx => { |
||||
|
const db = new CouchDB(USER_DB) |
||||
|
await db.destroy(generateUserID(ctx.params.email)) |
||||
|
ctx.body = { |
||||
|
message: `User ${ctx.params.email} deleted.`, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// called internally by app server user fetch
|
||||
|
exports.userFetch = async ctx => { |
||||
|
const db = new CouchDB(USER_DB) |
||||
|
const users = ( |
||||
|
await db.allDocs( |
||||
|
getUserParams(null, { |
||||
|
include_docs: true, |
||||
|
}) |
||||
|
) |
||||
|
).rows.map(row => row.doc) |
||||
|
// user hashed password shouldn't ever be returned
|
||||
|
for (let user of users) { |
||||
|
if (user) { |
||||
|
delete user.password |
||||
|
} |
||||
|
} |
||||
|
ctx.body = users |
||||
|
} |
||||
|
|
||||
|
// called internally by app server user find
|
||||
|
exports.userFind = async ctx => { |
||||
|
const db = new CouchDB(USER_DB) |
||||
|
const user = await db.get(generateUserID(ctx.params.email)) |
||||
|
if (user) { |
||||
|
delete user.password |
||||
|
} |
||||
|
ctx.body = user |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
const bcrypt = require("bcryptjs") |
||||
|
const env = require("../environment") |
||||
|
|
||||
|
const SALT_ROUNDS = env.SALT_ROUNDS || 10 |
||||
|
|
||||
|
exports.hash = async data => { |
||||
|
const salt = await bcrypt.genSalt(SALT_ROUNDS) |
||||
|
return bcrypt.hash(data, salt) |
||||
|
} |
||||
|
|
||||
|
exports.compare = async (data, encrypted) => { |
||||
|
return bcrypt.compare(data, encrypted) |
||||
|
} |
||||
@ -1,92 +0,0 @@ |
|||||
const env = require("../../environment") |
|
||||
const got = require("got") |
|
||||
const AWS = require("aws-sdk") |
|
||||
|
|
||||
const APP_BUCKET = "app-assets" |
|
||||
// this doesn't matter in self host
|
|
||||
const REGION = "eu-west-1" |
|
||||
const PUBLIC_READ_POLICY = { |
|
||||
Version: "2012-10-17", |
|
||||
Statement: [ |
|
||||
{ |
|
||||
Effect: "Allow", |
|
||||
Principal: { |
|
||||
AWS: ["*"], |
|
||||
}, |
|
||||
Action: "s3:GetObject", |
|
||||
Resource: [`arn:aws:s3:::${APP_BUCKET}/*`], |
|
||||
}, |
|
||||
], |
|
||||
} |
|
||||
|
|
||||
async function getCouchSession() { |
|
||||
// fetch session token for the api user
|
|
||||
const session = await got.post(`${env.COUCH_DB_URL}/_session`, { |
|
||||
responseType: "json", |
|
||||
credentials: "include", |
|
||||
json: { |
|
||||
username: env.COUCH_DB_USERNAME, |
|
||||
password: env.COUCH_DB_PASSWORD, |
|
||||
}, |
|
||||
}) |
|
||||
|
|
||||
const cookie = session.headers["set-cookie"][0] |
|
||||
// Get the session cookie value only
|
|
||||
return cookie.split(";")[0] |
|
||||
} |
|
||||
|
|
||||
async function getMinioSession() { |
|
||||
AWS.config.update({ |
|
||||
accessKeyId: env.MINIO_ACCESS_KEY, |
|
||||
secretAccessKey: env.MINIO_SECRET_KEY, |
|
||||
}) |
|
||||
|
|
||||
// make sure the bucket exists
|
|
||||
const objClient = new AWS.S3({ |
|
||||
endpoint: env.MINIO_URL, |
|
||||
region: REGION, |
|
||||
s3ForcePathStyle: true, // needed with minio?
|
|
||||
params: { |
|
||||
Bucket: APP_BUCKET, |
|
||||
}, |
|
||||
}) |
|
||||
// make sure the bucket exists
|
|
||||
try { |
|
||||
await objClient |
|
||||
.headBucket({ |
|
||||
Bucket: APP_BUCKET, |
|
||||
}) |
|
||||
.promise() |
|
||||
} catch (err) { |
|
||||
// bucket doesn't exist create it
|
|
||||
if (err.statusCode === 404) { |
|
||||
await objClient |
|
||||
.createBucket({ |
|
||||
Bucket: APP_BUCKET, |
|
||||
}) |
|
||||
.promise() |
|
||||
} else { |
|
||||
throw err |
|
||||
} |
|
||||
} |
|
||||
// always make sure policy is correct
|
|
||||
await objClient |
|
||||
.putBucketPolicy({ |
|
||||
Bucket: APP_BUCKET, |
|
||||
Policy: JSON.stringify(PUBLIC_READ_POLICY), |
|
||||
}) |
|
||||
.promise() |
|
||||
// Ideally want to send back some pre-signed URLs for files that are to be uploaded
|
|
||||
return { |
|
||||
accessKeyId: env.MINIO_ACCESS_KEY, |
|
||||
secretAccessKey: env.MINIO_SECRET_KEY, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
exports.deploy = async ctx => { |
|
||||
ctx.body = { |
|
||||
couchDbSession: await getCouchSession(), |
|
||||
bucket: APP_BUCKET, |
|
||||
objectStoreSession: await getMinioSession(), |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,12 @@ |
|||||
|
const Router = require("@koa/router") |
||||
|
const controller = require("../../controllers/admin") |
||||
|
const authorized = require("../../../middleware/authorized") |
||||
|
|
||||
|
const router = Router() |
||||
|
|
||||
|
router.post("/api/admin/users", authorized, controller.userSave) |
||||
|
.delete("/api/admin/users/:email", authorized, controller.userDelete) |
||||
|
.get("/api/admin/users", authorized, controller.userFetch) |
||||
|
.get("/api/admin/users/:email", authorized, controller.userFind) |
||||
|
|
||||
|
module.exports = router |
||||
@ -1,9 +0,0 @@ |
|||||
const Router = require("@koa/router") |
|
||||
const controller = require("../controllers/deploy") |
|
||||
const checkKey = require("../../middleware/check-key") |
|
||||
|
|
||||
const router = Router() |
|
||||
|
|
||||
router.post("/api/deploy", checkKey, controller.deploy) |
|
||||
|
|
||||
module.exports = router |
|
||||
@ -1,4 +1,4 @@ |
|||||
const deployRoutes = require("./deploy") |
const adminRoutes = require("./admin") |
||||
const appRoutes = require("./app") |
const appRoutes = require("./app") |
||||
|
|
||||
exports.routes = [deployRoutes, appRoutes] |
exports.routes = [adminRoutes, appRoutes] |
||||
|
|||||
@ -0,0 +1,4 @@ |
|||||
|
exports.UserStatus = { |
||||
|
ACTIVE: "active", |
||||
|
INACTIVE: "inactive", |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
exports.StaticDatabases = { |
||||
|
USER: { |
||||
|
name: "user-db", |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const DocumentTypes = { |
||||
|
USER: "us" |
||||
|
} |
||||
|
|
||||
|
const UNICODE_MAX = "\ufff0" |
||||
|
const SEPARATOR = "_" |
||||
|
|
||||
|
/** |
||||
|
* Generates a new user ID based on the passed in email. |
||||
|
* @param {string} email The email which the ID is going to be built up of. |
||||
|
* @returns {string} The new user ID which the user doc can be stored under. |
||||
|
*/ |
||||
|
exports.generateUserID = email => { |
||||
|
return `${DocumentTypes.USER}${SEPARATOR}${email}` |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets parameters for retrieving users, this is a utility function for the getDocParams function. |
||||
|
*/ |
||||
|
exports.getUserParams = (email = "", otherProps = {}) => { |
||||
|
return { |
||||
|
...otherProps, |
||||
|
startkey: `${DocumentTypes.USER}${SEPARATOR}${email}`, |
||||
|
endkey: `${DocumentTypes.USER}${SEPARATOR}${email}${UNICODE_MAX}`, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
/** |
||||
|
* Check the user token, used when creating admin resources, like for example |
||||
|
* a global user record. |
||||
|
*/ |
||||
|
module.exports = async (ctx, next) => { |
||||
|
next() |
||||
|
} |
||||
Loading…
Reference in new issue