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") |
|||
|
|||
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