1
0
Fork 0
mirror of synced 2024-07-09 00:06:05 +12:00

Merge pull request #1383 from Budibase/feature/global-user-management

Feature/global user management
This commit is contained in:
Martin McKeaveney 2021-04-16 14:02:04 +01:00 committed by GitHub
commit a1bcb2f857
127 changed files with 3627 additions and 1133 deletions

1
.eslintignore Normal file
View file

@ -0,0 +1 @@
packages/server/builder/**/*.js

View file

@ -35,9 +35,10 @@ services:
environment: environment:
SELF_HOSTED: 1 SELF_HOSTED: 1
PORT: 4003 PORT: 4003
JWT_SECRET: ${JWT_SECRET}
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
RAW_MINIO_URL: http://minio-service:9000 MINIO_URL: http://minio-service:9000
COUCH_DB_USERNAME: ${COUCH_DB_USER} COUCH_DB_USERNAME: ${COUCH_DB_USER}
COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD} COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD}
COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984 COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984

View file

@ -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
@ -43,6 +47,10 @@ static_resources:
cluster: builder-dev cluster: builder-dev
prefix_rewrite: "/builder/" prefix_rewrite: "/builder/"
# special case in dev to redirect no path to builder
- match: { path: "/" }
redirect: { path_redirect: "/builder/" }
# minio is on the default route because this works # minio is on the default route because this works
# best, minio + AWS SDK doesn't handle path proxy # best, minio + AWS SDK doesn't handle path proxy
- match: { prefix: "/" } - match: { prefix: "/" }
@ -123,3 +131,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

View file

@ -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:

View file

@ -29,7 +29,7 @@
"clean": "lerna clean", "clean": "lerna clean",
"kill-port": "kill-port 4001", "kill-port": "kill-port 4001",
"dev": "yarn run kill-port && lerna link && lerna run --parallel dev:builder --concurrency 1", "dev": "yarn run kill-port && lerna link && lerna run --parallel dev:builder --concurrency 1",
"dev:noserver": "lerna link && lerna run --parallel dev:builder --concurrency 1 --ignore @budibase/server", "dev:noserver": "lerna link && lerna run dev:stack:up && lerna run --parallel dev:builder --concurrency 1 --ignore @budibase/server --ignore @budibase/worker",
"test": "lerna run test", "test": "lerna run test",
"lint": "eslint packages", "lint": "eslint packages",
"lint:fix": "eslint --fix packages", "lint:fix": "eslint --fix packages",

117
packages/auth/.gitignore vendored Normal file
View file

@ -0,0 +1,117 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

1
packages/auth/README.md Normal file
View file

@ -0,0 +1 @@
# Budibase Authentication Library

View file

@ -0,0 +1,17 @@
{
"name": "@budibase/auth",
"version": "0.0.1",
"description": "Authentication middlewares for budibase builder and apps",
"main": "src/index.js",
"author": "Budibase",
"license": "AGPL-3.0",
"dependencies": {
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^8.5.1",
"koa-passport": "^4.1.4",
"passport-google-auth": "^1.0.2",
"passport-google-oauth": "^2.0.0",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0"
}
}

View file

@ -0,0 +1,9 @@
exports.UserStatus = {
ACTIVE: "active",
INACTIVE: "inactive",
}
exports.Cookies = {
CurrentApp: "budibase:currentapp",
Auth: "budibase:auth",
}

View file

@ -0,0 +1,5 @@
module.exports.setDB = pouch => {
module.exports.CouchDB = pouch
}
module.exports.CouchDB = null

View file

@ -0,0 +1,44 @@
exports.StaticDatabases = {
USER: {
name: "user-db",
},
}
const DocumentTypes = {
USER: "us",
APP: "app",
}
exports.DocumentTypes = DocumentTypes
const UNICODE_MAX = "\ufff0"
const SEPARATOR = "_"
exports.SEPARATOR = 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}`
}
exports.getEmailFromUserID = userId => {
return userId.split(`${DocumentTypes.USER}${SEPARATOR}`)[1]
}
/**
* Gets parameters for retrieving users, this is a utility function for the getDocParams function.
*/
exports.getUserParams = (email = "", otherProps = {}) => {
if (!email) {
email = ""
}
return {
...otherProps,
startkey: `${DocumentTypes.USER}${SEPARATOR}${email}`,
endkey: `${DocumentTypes.USER}${SEPARATOR}${email}${UNICODE_MAX}`,
}
}

View file

@ -0,0 +1,8 @@
module.exports = {
JWT_SECRET: process.env.JWT_SECRET,
COUCH_DB_URL: process.env.COUCH_DB_URL,
SALT_ROUNDS: process.env.SALT_ROUNDS,
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
GOOGLE_AUTH_CALLBACK_URL: process.env.GOOGLE_AUTH_CALLBACK_URL,
}

View file

@ -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)
}

View file

@ -0,0 +1,61 @@
const passport = require("koa-passport")
const LocalStrategy = require("passport-local").Strategy
const JwtStrategy = require("passport-jwt").Strategy
// const GoogleStrategy = require("passport-google-oauth").Strategy
const database = require("./db")
const { StaticDatabases } = require("./db/utils")
const { jwt, local, authenticated } = require("./middleware")
const { Cookies, UserStatus } = require("./constants")
const { hash, compare } = require("./hashing")
const {
getAppId,
setCookie,
getCookie,
clearCookie,
isClient,
} = require("./utils")
const {
generateUserID,
getUserParams,
getEmailFromUserID,
} = require("./db/utils")
// Strategies
passport.use(new LocalStrategy(local.options, local.authenticate))
passport.use(new JwtStrategy(jwt.options, jwt.authenticate))
// passport.use(new GoogleStrategy(google.options, google.authenticate))
passport.serializeUser((user, done) => done(null, user))
passport.deserializeUser(async (user, done) => {
const db = new database.CouchDB(StaticDatabases.USER.name)
try {
const user = await db.get(user._id)
return done(null, user)
} catch (err) {
console.error("User not found", err)
return done(null, false, { message: "User not found" })
}
})
module.exports = {
init(pouch) {
database.setDB(pouch)
},
passport,
Cookies,
UserStatus,
StaticDatabases,
generateUserID,
getUserParams,
getEmailFromUserID,
hash,
compare,
getAppId,
setCookie,
getCookie,
clearCookie,
authenticated,
isClient,
}

View file

@ -0,0 +1,21 @@
const { Cookies } = require("../constants")
const { getCookie } = require("../utils")
const { getEmailFromUserID } = require("../db/utils")
module.exports = async (ctx, next) => {
try {
// check the actual user is authenticated first
const authCookie = getCookie(ctx, Cookies.Auth)
if (authCookie) {
ctx.isAuthenticated = true
ctx.user = authCookie
// make sure email is correct from ID
ctx.user.email = getEmailFromUserID(authCookie.userId)
}
await next()
} catch (err) {
ctx.throw(err.status || 403, err)
}
}

View file

@ -0,0 +1,11 @@
const jwt = require("./passport/jwt")
const local = require("./passport/local")
const google = require("./passport/google")
const authenticated = require("./authenticated")
module.exports = {
google,
jwt,
local,
authenticated,
}

View file

@ -0,0 +1,12 @@
const env = require("../../environment")
exports.options = {
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
callbackURL: env.GOOGLE_AUTH_CALLBACK_URL,
}
// exports.authenticate = async function(token, tokenSecret, profile, done) {
// // retrieve user ...
// fetchUser().then(user => done(null, user))
// }

View file

@ -0,0 +1,17 @@
const { Cookies } = require("../../constants")
const env = require("../../environment")
exports.options = {
secretOrKey: env.JWT_SECRET,
jwtFromRequest: function(ctx) {
return ctx.cookies.get(Cookies.Auth)
},
}
exports.authenticate = async function(jwt, done) {
try {
return done(null, jwt)
} catch (err) {
return done(new Error("JWT invalid."), false)
}
}

View file

@ -0,0 +1,57 @@
const jwt = require("jsonwebtoken")
const { UserStatus } = require("../../constants")
const database = require("../../db")
const { StaticDatabases, generateUserID } = require("../../db/utils")
const { compare } = require("../../hashing")
const env = require("../../environment")
const INVALID_ERR = "Invalid Credentials"
exports.options = {}
/**
* Passport Local Authentication Middleware.
* @param {*} username - username to login with
* @param {*} password - plain text password to log in with
* @param {*} done - callback from passport to return user information and errors
* @returns The authenticated user, or errors if they occur
*/
exports.authenticate = async function(username, password, done) {
if (!username) return done(null, false, "Email Required.")
if (!password) return done(null, false, "Password Required.")
// Check the user exists in the instance DB by email
const db = new database.CouchDB(StaticDatabases.USER.name)
let dbUser
try {
dbUser = await db.get(generateUserID(username))
} catch (err) {
console.error("User not found", err)
return done(null, false, { message: "User not found" })
}
// check that the user is currently inactive, if this is the case throw invalid
if (dbUser.status === UserStatus.INACTIVE) {
return done(null, false, { message: INVALID_ERR })
}
// authenticate
if (await compare(password, dbUser.password)) {
const payload = {
userId: dbUser._id,
builder: dbUser.builder,
email: dbUser.email,
}
dbUser.token = jwt.sign(payload, env.JWT_SECRET, {
expiresIn: "1 day",
})
// Remove users password in payload
delete dbUser.password
return done(null, dbUser)
} else {
done(new Error(INVALID_ERR), false)
}
}

View file

@ -0,0 +1,99 @@
const { DocumentTypes, SEPARATOR } = require("./db/utils")
const jwt = require("jsonwebtoken")
const { options } = require("./middleware/passport/jwt")
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
function confirmAppId(possibleAppId) {
return possibleAppId && possibleAppId.startsWith(APP_PREFIX)
? possibleAppId
: undefined
}
/**
* Given a request tries to find the appId, which can be located in various places
* @param {object} ctx The main request body to look through.
* @returns {string|undefined} If an appId was found it will be returned.
*/
exports.getAppId = ctx => {
const options = [ctx.headers["x-budibase-app-id"], ctx.params.appId]
if (ctx.subdomains) {
options.push(ctx.subdomains[1])
}
let appId
for (let option of options) {
appId = confirmAppId(option)
if (appId) {
break
}
}
// look in body if can't find it in subdomain
if (!appId && ctx.request.body && ctx.request.body.appId) {
appId = confirmAppId(ctx.request.body.appId)
}
let appPath =
ctx.request.headers.referrer ||
ctx.path.split("/").filter(subPath => subPath.startsWith(APP_PREFIX))
if (!appId && appPath.length !== 0) {
appId = confirmAppId(appPath[0])
}
return appId
}
/**
* Get a cookie from context, and decrypt if necessary.
* @param {object} ctx The request which is to be manipulated.
* @param {string} name The name of the cookie to get.
*/
exports.getCookie = (ctx, name) => {
const cookie = ctx.cookies.get(name)
if (!cookie) {
return cookie
}
return jwt.verify(cookie, options.secretOrKey)
}
/**
* Store a cookie for the request, has a hardcoded expiry.
* @param {object} ctx The request which is to be manipulated.
* @param {string} name The name of the cookie to set.
* @param {string|object} value The value of cookie which will be set.
*/
exports.setCookie = (ctx, value, name = "builder") => {
const expires = new Date()
expires.setDate(expires.getDate() + 1)
if (!value) {
ctx.cookies.set(name)
} else {
value = jwt.sign(value, options.secretOrKey, {
expiresIn: "1 day",
})
ctx.cookies.set(name, value, {
expires,
path: "/",
httpOnly: false,
overwrite: true,
})
}
}
/**
* Utility function, simply calls setCookie with an empty string for value
*/
exports.clearCookie = (ctx, name) => {
exports.setCookie(ctx, "", name)
}
/**
* Checks if the API call being made (based on the provided ctx object) is from the client. If
* the call is not from a client app then it is from the builder.
* @param {object} ctx The koa context object to be tested.
* @return {boolean} returns true if the call is from the client lib (a built app rather than the builder).
*/
exports.isClient = ctx => {
return ctx.headers["x-budibase-type"] === "client"
}

594
packages/auth/yarn.lock Normal file
View file

@ -0,0 +1,594 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
ajv@^6.12.3:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
dependencies:
safer-buffer "~2.1.0"
assert-plus@1.0.0, assert-plus@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
async@~2.1.4:
version "2.1.5"
resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc"
integrity sha1-5YfGhYCZSsZ/xW/4bTrFa9voELw=
dependencies:
lodash "^4.14.0"
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
aws4@^1.8.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
base64url@3.x.x:
version "3.0.1"
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
bcrypt-pbkdf@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
dependencies:
tweetnacl "^0.14.3"
bcryptjs@^2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
combined-stream@^1.0.6, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
core-util-is@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
dependencies:
assert-plus "^1.0.0"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
dependencies:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
ecdsa-sig-formatter@1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
extsprintf@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
extsprintf@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
fast-deep-equal@^3.1.1:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.6"
mime-types "^2.1.12"
getpass@^0.1.1:
version "0.1.7"
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
dependencies:
assert-plus "^1.0.0"
google-auth-library@~0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-0.10.0.tgz#6e15babee85fd1dd14d8d128a295b6838d52136e"
integrity sha1-bhW6vuhf0d0U2NEoopW2g41SE24=
dependencies:
gtoken "^1.2.1"
jws "^3.1.4"
lodash.noop "^3.0.1"
request "^2.74.0"
google-p12-pem@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-0.1.2.tgz#33c46ab021aa734fa0332b3960a9a3ffcb2f3177"
integrity sha1-M8RqsCGqc0+gMys5YKmj/8svMXc=
dependencies:
node-forge "^0.7.1"
googleapis@^16.0.0:
version "16.1.0"
resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-16.1.0.tgz#0f19f2d70572d918881a0f626e3b1a2fa8629576"
integrity sha1-Dxny1wVy2RiIGg9ibjsaL6hilXY=
dependencies:
async "~2.1.4"
google-auth-library "~0.10.0"
string-template "~1.0.0"
gtoken@^1.2.1:
version "1.2.3"
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-1.2.3.tgz#5509571b8afd4322e124cf66cf68115284c476d8"
integrity sha512-wQAJflfoqSgMWrSBk9Fg86q+sd6s7y6uJhIvvIPz++RElGlMtEqsdAR2oWwZ/WTEtp7P9xFbJRrT976oRgzJ/w==
dependencies:
google-p12-pem "^0.1.0"
jws "^3.0.0"
mime "^1.4.1"
request "^2.72.0"
har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@~5.1.3:
version "5.1.5"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
dependencies:
ajv "^6.12.3"
har-schema "^2.0.0"
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
dependencies:
assert-plus "^1.0.0"
jsprim "^1.2.2"
sshpk "^1.7.0"
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
jsonwebtoken@^8.2.0, jsonwebtoken@^8.5.1:
version "8.5.1"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
dependencies:
jws "^3.2.2"
lodash.includes "^4.3.0"
lodash.isboolean "^3.0.3"
lodash.isinteger "^4.0.4"
lodash.isnumber "^3.0.3"
lodash.isplainobject "^4.0.6"
lodash.isstring "^4.0.1"
lodash.once "^4.0.0"
ms "^2.1.1"
semver "^5.6.0"
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
dependencies:
assert-plus "1.0.0"
extsprintf "1.3.0"
json-schema "0.2.3"
verror "1.10.0"
jwa@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^3.0.0, jws@^3.1.4, jws@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
dependencies:
jwa "^1.4.1"
safe-buffer "^5.0.1"
koa-passport@^4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/koa-passport/-/koa-passport-4.1.4.tgz#5f1665c1c2a37ace79af9f970b770885ca30ccfa"
integrity sha512-dJBCkl4X+zdYxbI2V2OtoGy0PUenpvp2ZLLWObc8UJhsId0iQpTFT8RVcuA0709AL2txGwRHnSPoT1bYNGa6Kg==
dependencies:
passport "^0.4.0"
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
lodash.isnumber@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
lodash.noop@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c"
integrity sha1-OBiPTWUKOkdCWEObluxFsyYXEzw=
lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
lodash@^4.14.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
mime-db@1.47.0:
version "1.47.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c"
integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==
mime-types@^2.1.12, mime-types@~2.1.19:
version "2.1.30"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d"
integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==
dependencies:
mime-db "1.47.0"
mime@^1.4.1:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
ms@^2.1.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
node-forge@^0.7.1:
version "0.7.6"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"
integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==
oauth-sign@~0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
oauth@0.9.x:
version "0.9.15"
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
passport-google-auth@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/passport-google-auth/-/passport-google-auth-1.0.2.tgz#8b300b5aa442ef433de1d832ed3112877d0b2938"
integrity sha1-izALWqRC70M94dgy7TESh30LKTg=
dependencies:
googleapis "^16.0.0"
passport-strategy "1.x"
passport-google-oauth1@1.x.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-google-oauth1/-/passport-google-oauth1-1.0.0.tgz#af74a803df51ec646f66a44d82282be6f108e0cc"
integrity sha1-r3SoA99R7GRvZqRNgigr5vEI4Mw=
dependencies:
passport-oauth1 "1.x.x"
passport-google-oauth20@2.x.x:
version "2.0.0"
resolved "https://registry.yarnpkg.com/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz#0d241b2d21ebd3dc7f2b60669ec4d587e3a674ef"
integrity sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==
dependencies:
passport-oauth2 "1.x.x"
passport-google-oauth@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/passport-google-oauth/-/passport-google-oauth-2.0.0.tgz#f6eb4bc96dd6c16ec0ecfdf4e05ec48ca54d4dae"
integrity sha512-JKxZpBx6wBQXX1/a1s7VmdBgwOugohH+IxCy84aPTZNq/iIPX6u7Mqov1zY7MKRz3niFPol0KJz8zPLBoHKtYA==
dependencies:
passport-google-oauth1 "1.x.x"
passport-google-oauth20 "2.x.x"
passport-jwt@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.0.tgz#7f0be7ba942e28b9f5d22c2ebbb8ce96ef7cf065"
integrity sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==
dependencies:
jsonwebtoken "^8.2.0"
passport-strategy "^1.0.0"
passport-local@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee"
integrity sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=
dependencies:
passport-strategy "1.x.x"
passport-oauth1@1.x.x:
version "1.1.0"
resolved "https://registry.yarnpkg.com/passport-oauth1/-/passport-oauth1-1.1.0.tgz#a7de988a211f9cf4687377130ea74df32730c918"
integrity sha1-p96YiiEfnPRoc3cTDqdN8ycwyRg=
dependencies:
oauth "0.9.x"
passport-strategy "1.x.x"
utils-merge "1.x.x"
passport-oauth2@1.x.x:
version "1.5.0"
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.5.0.tgz#64babbb54ac46a4dcab35e7f266ed5294e3c4108"
integrity sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ==
dependencies:
base64url "3.x.x"
oauth "0.9.x"
passport-strategy "1.x.x"
uid2 "0.0.x"
utils-merge "1.x.x"
passport-strategy@1.x, passport-strategy@1.x.x, passport-strategy@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=
passport@^0.4.0:
version "0.4.1"
resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270"
integrity sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==
dependencies:
passport-strategy "1.x.x"
pause "0.0.1"
pause@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d"
integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=
performance-now@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
psl@^1.1.28:
version "1.8.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
request@^2.72.0, request@^2.74.0:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
caseless "~0.12.0"
combined-stream "~1.0.6"
extend "~3.0.2"
forever-agent "~0.6.1"
form-data "~2.3.2"
har-validator "~5.1.3"
http-signature "~1.2.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
json-stringify-safe "~5.0.1"
mime-types "~2.1.19"
oauth-sign "~0.9.0"
performance-now "^2.1.0"
qs "~6.5.2"
safe-buffer "^5.1.2"
tough-cookie "~2.5.0"
tunnel-agent "^0.6.0"
uuid "^3.3.2"
safe-buffer@^5.0.1, safe-buffer@^5.1.2:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
semver@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
sshpk@^1.7.0:
version "1.16.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==
dependencies:
asn1 "~0.2.3"
assert-plus "^1.0.0"
bcrypt-pbkdf "^1.0.0"
dashdash "^1.12.0"
ecc-jsbn "~0.1.1"
getpass "^0.1.1"
jsbn "~0.1.0"
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
string-template@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=
tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
dependencies:
psl "^1.1.28"
punycode "^2.1.1"
tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=
dependencies:
safe-buffer "^5.0.1"
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
uid2@0.0.x:
version "0.0.3"
resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82"
integrity sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
dependencies:
punycode "^2.1.0"
utils-merge@1.x.x:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
uuid@^3.3.2:
version "3.4.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
verror@1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
dependencies:
assert-plus "^1.0.0"
core-util-is "1.0.2"
extsprintf "^1.2.0"

View file

@ -1,5 +1,6 @@
context("Create an Application", () => { context("Create an Application", () => {
it("should create a new application", () => { it("should create a new application", () => {
cy.login()
cy.createTestApp() cy.createTestApp()
cy.visit(`localhost:${Cypress.env("PORT")}/builder`) cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
cy.contains("Cypress Tests").should("exist") cy.contains("Cypress Tests").should("exist")

View file

@ -1,5 +1,6 @@
context("Create a automation", () => { context("Create a automation", () => {
before(() => { before(() => {
cy.login()
cy.createTestApp() cy.createTestApp()
}) })

View file

@ -1,5 +1,6 @@
context("Create Bindings", () => { context("Create Bindings", () => {
before(() => { before(() => {
cy.login()
cy.createTestApp() cy.createTestApp()
cy.navigateToFrontend() cy.navigateToFrontend()
}) })
@ -9,7 +10,7 @@ context("Create Bindings", () => {
addSettingBinding("text", "Current User._id") addSettingBinding("text", "Current User._id")
cy.getComponent(componentId).should( cy.getComponent(componentId).should(
"have.text", "have.text",
`ro_ta_users_us_test@test.com` `ro_ta_users_test@test.com`
) )
}) })
}) })

View file

@ -2,6 +2,7 @@ context("Create Components", () => {
let headlineId let headlineId
before(() => { before(() => {
cy.login()
cy.createTestApp() cy.createTestApp()
cy.createTable("dog") cy.createTable("dog")
cy.addColumn("dog", "name", "string") cy.addColumn("dog", "name", "string")

View file

@ -1,5 +1,6 @@
context("Screen Tests", () => { context("Screen Tests", () => {
before(() => { before(() => {
cy.login()
cy.createTestApp() cy.createTestApp()
cy.navigateToFrontend() cy.navigateToFrontend()
}) })

View file

@ -1,5 +1,6 @@
context("Create a Table", () => { context("Create a Table", () => {
before(() => { before(() => {
cy.login()
cy.createTestApp() cy.createTestApp()
}) })

View file

@ -1,5 +1,6 @@
context("Create a User", () => { context("Create a User", () => {
before(() => { before(() => {
cy.login()
cy.createTestApp() cy.createTestApp()
}) })

View file

@ -1,5 +1,6 @@
context("Create a View", () => { context("Create a View", () => {
before(() => { before(() => {
cy.login()
cy.createTestApp() cy.createTestApp()
cy.createTable("data") cy.createTable("data")
cy.addColumn("data", "group", "Text") cy.addColumn("data", "group", "Text")

View file

@ -3,13 +3,16 @@ const path = require("path")
const tmpdir = path.join(require("os").tmpdir(), ".budibase") const tmpdir = path.join(require("os").tmpdir(), ".budibase")
const WORKER_PORT = "4002"
const MAIN_PORT = cypressConfig.env.PORT
process.env.BUDIBASE_API_KEY = "6BE826CB-6B30-4AEC-8777-2E90464633DE" process.env.BUDIBASE_API_KEY = "6BE826CB-6B30-4AEC-8777-2E90464633DE"
process.env.NODE_ENV = "cypress" process.env.NODE_ENV = "cypress"
process.env.ENABLE_ANALYTICS = "false" process.env.ENABLE_ANALYTICS = "false"
process.env.PORT = cypressConfig.env.PORT process.env.PORT = MAIN_PORT
process.env.JWT_SECRET = cypressConfig.env.JWT_SECRET process.env.JWT_SECRET = cypressConfig.env.JWT_SECRET
process.env.COUCH_URL = `leveldb://${tmpdir}/.data/` process.env.COUCH_URL = `leveldb://${tmpdir}/.data/`
process.env.SELF_HOSTED = 1 process.env.SELF_HOSTED = 1
process.env.WORKER_URL = "http://localhost:4002/"
process.env.MINIO_URL = "http://localhost:10000/" process.env.MINIO_URL = "http://localhost:10000/"
process.env.MINIO_ACCESS_KEY = "budibase" process.env.MINIO_ACCESS_KEY = "budibase"
process.env.MINIO_SECRET_KEY = "budibase" process.env.MINIO_SECRET_KEY = "budibase"
@ -25,18 +28,12 @@ async function run() {
// dont make this a variable or top level require // dont make this a variable or top level require
// it will cause environment module to be loaded prematurely // it will cause environment module to be loaded prematurely
const server = require("../../server/src/app") const server = require("../../server/src/app")
process.env.PORT = WORKER_PORT
const worker = require("../../worker/src/index")
// reload main port for rest of system
process.env.PORT = MAIN_PORT
server.on("close", () => console.log("Server Closed")) server.on("close", () => console.log("Server Closed"))
worker.on("close", () => console.log("Worker Closed"))
} }
run() run()
// TODO: ensure that this still works
// initialiseBudibase({ dir: homedir, clientId: "cypress-test" })
// .then(() => {
// delete require.cache[require.resolve("../../server/src/environment")]
// const xPlatHomeDir = homedir.startsWith("~")
// ? join(homedir(), homedir.substring(1))
// : homedir
// run(xPlatHomeDir)
// })
// .catch(e => console.error(e))

View file

@ -24,6 +24,23 @@
// -- This will overwrite an existing command -- // -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
Cypress.Commands.add("login", () => {
cy.getCookie("budibase:auth").then(cookie => {
// Already logged in
if (cookie) return
cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
cy.contains("Create Test User").click()
cy.get("input")
.first()
.type("test@test.com")
cy.get('input[type="password"]').type("test")
cy.contains("Login").click()
})
})
Cypress.Commands.add("createApp", name => { Cypress.Commands.add("createApp", name => {
cy.visit(`localhost:${Cypress.env("PORT")}/builder`) cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
// wait for init API calls on visit // wait for init API calls on visit
@ -44,12 +61,6 @@ Cypress.Commands.add("createApp", name => {
.type(name) .type(name)
.should("have.value", name) .should("have.value", name)
cy.contains("Next").click() cy.contains("Next").click()
cy.get("input[name=email]")
.click()
.type("test@test.com")
cy.get("input[name=password]")
.click()
.type("test")
cy.contains("Submit").click() cy.contains("Submit").click()
cy.get("[data-cy=new-table]", { cy.get("[data-cy=new-table]", {
timeout: 20000, timeout: 20000,

View file

@ -1,3 +1,3 @@
Cypress.Cookies.defaults({ Cypress.Cookies.defaults({
preserve: "budibase:builder:local", preserve: "budibase:auth",
}) })

View file

@ -1,5 +1,6 @@
import { store } from "./index" import { store } from "./index"
import { get as svelteGet } from "svelte/store" import { get as svelteGet } from "svelte/store"
import { removeCookie, Cookies } from "./cookies"
const apiCall = method => async ( const apiCall = method => async (
url, url,
@ -8,11 +9,15 @@ const apiCall = method => async (
) => { ) => {
headers["x-budibase-app-id"] = svelteGet(store).appId headers["x-budibase-app-id"] = svelteGet(store).appId
const json = headers["Content-Type"] === "application/json" const json = headers["Content-Type"] === "application/json"
return await fetch(url, { const resp = await fetch(url, {
method: method, method: method,
body: json ? JSON.stringify(body) : body, body: json ? JSON.stringify(body) : body,
headers, headers,
}) })
if (resp.status === 403) {
removeCookie(Cookies.Auth)
}
return resp
} }
export const post = apiCall("POST") export const post = apiCall("POST")
@ -20,9 +25,6 @@ export const get = apiCall("GET")
export const patch = apiCall("PATCH") export const patch = apiCall("PATCH")
export const del = apiCall("DELETE") export const del = apiCall("DELETE")
export const put = apiCall("PUT") export const put = apiCall("PUT")
export const getBuilderCookie = async () => {
await post("/api/builder/login", {})
}
export default { export default {
post: apiCall("POST"), post: apiCall("POST"),
@ -30,5 +32,4 @@ export default {
patch: apiCall("PATCH"), patch: apiCall("PATCH"),
delete: apiCall("DELETE"), delete: apiCall("DELETE"),
put: apiCall("PUT"), put: apiCall("PUT"),
getBuilderCookie,
} }

View file

@ -0,0 +1,16 @@
export const Cookies = {
Auth: "budibase:auth",
CurrentApp: "budibase:currentapp",
}
export function getCookie(cookieName) {
return document.cookie.split(";").some(cookie => {
return cookie.trim().startsWith(`${cookieName}=`)
})
}
export function removeCookie(cookieName) {
if (getCookie(cookieName)) {
document.cookie = `${cookieName}=; Max-Age=-99999999;`
}
}

View file

@ -6,7 +6,6 @@ import { derived, writable } from "svelte/store"
import analytics from "analytics" import analytics from "analytics"
import { FrontendTypes, LAYOUT_NAMES } from "../constants" import { FrontendTypes, LAYOUT_NAMES } from "../constants"
import { findComponent } from "./storeUtils" import { findComponent } from "./storeUtils"
import { getBuilderCookie } from "./api"
export const store = getFrontendStore() export const store = getFrontendStore()
export const automationStore = getAutomationStore() export const automationStore = getAutomationStore()
@ -58,8 +57,6 @@ export const selectedAccessRole = writable("BASIC")
export const initialise = async () => { export const initialise = async () => {
try { try {
// TODO this needs to be replaced by a real login
await getBuilderCookie()
await analytics.activate() await analytics.activate()
analytics.captureEvent("Builder Started") analytics.captureEvent("Builder Started")
} catch (err) { } catch (err) {

View file

@ -1,7 +1,7 @@
import api from "builderStore/api" import api from "builderStore/api"
export async function createUser(user) { export async function createUser(user) {
const CREATE_USER_URL = `/api/users` const CREATE_USER_URL = `/api/users/metadata`
const response = await api.post(CREATE_USER_URL, user) const response = await api.post(CREATE_USER_URL, user)
return await response.json() return await response.json()
} }
@ -15,8 +15,7 @@ export async function saveRow(row, tableId) {
export async function deleteRow(row) { export async function deleteRow(row) {
const DELETE_ROWS_URL = `/api/${row.tableId}/rows/${row._id}/${row._rev}` const DELETE_ROWS_URL = `/api/${row.tableId}/rows/${row._id}/${row._rev}`
const response = await api.delete(DELETE_ROWS_URL) return api.delete(DELETE_ROWS_URL)
return response
} }
export async function fetchDataForView(view) { export async function fetchDataForView(view) {

View file

@ -0,0 +1,56 @@
<script>
import { Button, Label, Input, Spacer } from "@budibase/bbui"
import { notifier } from "builderStore/store/notifications"
import { auth } from "stores/backend"
let username = ""
let password = ""
async function login() {
try {
await auth.login({
username,
password,
})
notifier.success("Logged in successfully.")
} catch (err) {
console.error(err)
notifier.danger("Invalid credentials")
}
}
async function createTestUser() {
try {
await auth.createUser({
email: "test@test.com",
password: "test",
roles: {},
builder: {
global: true,
},
})
notifier.success("Test user created")
} catch (err) {
console.error(err)
notifier.danger("Could not create test user")
}
}
</script>
<form on:submit|preventDefault data-cy="login-form">
<Spacer large />
<Label small>Email</Label>
<Input outline bind:value={username} />
<Spacer large />
<Label small>Password</Label>
<Input outline type="password" on:change bind:value={password} />
<Spacer large />
<Button primary on:click={login}>Login</Button>
<Button secondary on:click={createTestUser}>Create Test User</Button>
</form>
<style>
form {
width: 60%;
}
</style>

View file

@ -0,0 +1 @@
export { LoginForm } from "./LoginForm.svelte"

View file

@ -25,10 +25,6 @@
applicationName: string().required("Your application must have a name."), applicationName: string().required("Your application must have a name."),
} }
const userValidation = { const userValidation = {
email: string()
.email()
.required("Your application needs a first user."),
password: string().required("Please enter a password for your first user."),
roleId: string().required("You need to select a role for your user."), roleId: string().required("You need to select a role for your user."),
} }
@ -153,11 +149,9 @@
// Create user // Create user
const user = { const user = {
email: $createAppStore.values.email,
password: $createAppStore.values.password,
roleId: $createAppStore.values.roleId, roleId: $createAppStore.values.roleId,
} }
const userResp = await api.post(`/api/users`, user) const userResp = await api.post(`/api/users/metadata`, user)
const json = await userResp.json() const json = await userResp.json()
$goto(`./${appJson._id}`) $goto(`./${appJson._id}`)
} catch (error) { } catch (error) {

View file

@ -0,0 +1,28 @@
<script>
import { TextButton as Button, Modal } from "@budibase/bbui"
import { auth } from "stores/backend"
</script>
<div>
<Button text on:click={auth.logout}>
<i class="ri-logout-box-line" />
<p>Logout</p>
</Button>
</div>
<style>
div i {
font-size: 26px;
color: var(--grey-7);
margin-left: 12px;
margin-top: 10px;
}
div p {
font-family: var(--font-sans);
font-size: var(--font-size-s);
color: var(--ink);
font-weight: 400;
margin: 0 0 0 12px;
}
</style>

View file

@ -1,26 +1,9 @@
<script> <script>
import { Input, Select } from "@budibase/bbui" import { Input, Select } from "@budibase/bbui"
export let validationErrors
let blurred = { email: false, password: false }
</script> </script>
<h2>Create your first User</h2> <h2>What's your role for this app?</h2>
<div class="container"> <div class="container">
<Input
on:input={() => (blurred.email = true)}
label="Email"
name="email"
placeholder="Email"
type="email"
error={blurred.email && validationErrors.email} />
<Input
on:input={() => (blurred.password = true)}
label="Password"
name="password"
placeholder="Password"
type="password"
error={blurred.password && validationErrors.password} />
<Select label="Role" secondary name="roleId"> <Select label="Role" secondary name="roleId">
<option value="ADMIN">Admin</option> <option value="ADMIN">Admin</option>
<option value="POWER_USER">Power User</option> <option value="POWER_USER">Power User</option>

View file

@ -11,9 +11,7 @@
{#if $datasources.list.length === 0} {#if $datasources.list.length === 0}
<i>Connect your first datasource to start building.</i> <i>Connect your first datasource to start building.</i>
{:else} {:else}<i>Select a datasource to edit</i>{/if}
<i>Select a datasource to edit</i>
{/if}
<style> <style>
i { i {

View file

@ -7,44 +7,56 @@
CommunityIcon, CommunityIcon,
BugIcon, BugIcon,
} from "components/common/Icons" } from "components/common/Icons"
import LoginForm from "components/login/LoginForm.svelte"
import BuilderSettingsButton from "components/start/BuilderSettingsButton.svelte" import BuilderSettingsButton from "components/start/BuilderSettingsButton.svelte"
import LogoutButton from "components/start/LogoutButton.svelte"
import Logo from "/assets/budibase-logo.svg" import Logo from "/assets/budibase-logo.svg"
import { auth } from "stores/backend"
let modal let modal
</script> </script>
<div class="root"> {#if $auth}
<div class="ui-nav"> {#if $auth.user}
<div class="home-logo"><img src={Logo} alt="Budibase icon" /></div> <div class="root">
<div class="nav-section"> <div class="ui-nav">
<div class="nav-top"> <div class="home-logo"><img src={Logo} alt="Budibase icon" /></div>
<Link icon={AppsIcon} title="Apps" href="/" active /> <div class="nav-section">
<Link <div class="nav-top">
icon={HostingIcon} <Link icon={AppsIcon} title="Apps" href="/" active />
title="Hosting" <Link
href="https://portal.budi.live/" /> icon={HostingIcon}
<Link title="Hosting"
icon={DocumentationIcon} href="https://portal.budi.live/" />
title="Documentation" <Link
href="https://docs.budibase.com/" /> icon={DocumentationIcon}
<Link title="Documentation"
icon={CommunityIcon} href="https://docs.budibase.com/" />
title="Community" <Link
href="https://github.com/Budibase/budibase/discussions" /> icon={CommunityIcon}
<Link title="Community"
icon={BugIcon} href="https://github.com/Budibase/budibase/discussions" />
title="Raise an issue" <Link
href="https://github.com/Budibase/budibase/issues/new/choose" /> icon={BugIcon}
title="Raise an issue"
href="https://github.com/Budibase/budibase/issues/new/choose" />
</div>
<div class="nav-bottom">
<BuilderSettingsButton />
<LogoutButton />
</div>
</div>
</div> </div>
<div class="nav-bottom"> <div class="main">
<BuilderSettingsButton /> <slot />
</div> </div>
</div> </div>
</div> {:else}
<div class="main"> <section class="login">
<slot /> <LoginForm />
</div> </section>
</div> {/if}
{/if}
<style> <style>
.root { .root {
@ -55,6 +67,14 @@
background: var(--grey-1); background: var(--grey-1);
} }
.login {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
}
.main { .main {
grid-column: 2; grid-column: 2;
overflow: auto; overflow: auto;

View file

@ -0,0 +1,43 @@
import { writable } from "svelte/store"
import api from "../../builderStore/api"
async function checkAuth() {
const response = await api.get("/api/self")
const user = await response.json()
if (response.status === 200) return user
return null
}
export function createAuthStore() {
const { subscribe, set } = writable(null)
checkAuth()
.then(user => set({ user }))
.catch(() => set({ user: null }))
return {
subscribe,
login: async creds => {
const response = await api.post(`/api/admin/auth`, creds)
const json = await response.json()
if (response.status === 200) {
set({ user: json.user })
} else {
throw "Invalid credentials"
}
return json
},
logout: async () => {
const response = await api.post(`/api/admin/auth/logout`)
await response.json()
set({ user: null })
},
createUser: async user => {
const response = await api.post(`/api/admin/users`, user)
await response.json()
},
}
}
export const auth = createAuthStore()

View file

@ -7,3 +7,4 @@ export { roles } from "./roles"
export { datasources } from "./datasources" export { datasources } from "./datasources"
export { integrations } from "./integrations" export { integrations } from "./integrations"
export { queries } from "./queries" export { queries } from "./queries"
export { auth } from "./auth"

View file

@ -13,8 +13,8 @@ export const logIn = async ({ email, password }) => {
return API.error("Please enter your password") return API.error("Please enter your password")
} }
return await API.post({ return await API.post({
url: "/api/authenticate", url: "/api/admin/auth",
body: { email, password }, body: { username: email, password },
}) })
} }

View file

@ -19,8 +19,8 @@ const createAuthStore = () => {
// Logs a user in // Logs a user in
const logIn = async ({ email, password }) => { const logIn = async ({ email, password }) => {
const user = await API.logIn({ email, password }) const auth = await API.logIn({ email, password })
if (!user.error) { if (auth.success) {
await fetchUser() await fetchUser()
await initialise() await initialise()
goToDefaultRoute() goToDefaultRoute()
@ -30,12 +30,7 @@ const createAuthStore = () => {
// Logs a user out // Logs a user out
const logOut = async () => { const logOut = async () => {
store.set(null) store.set(null)
const appId = get(builderStore).appId window.document.cookie = `budibase:auth=; budibase:currentapp=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`
if (appId) {
for (let environment of ["local", "cloud"]) {
window.document.cookie = `budibase:${appId}:${environment}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`
}
}
await initialise() await initialise()
goToDefaultRoute() goToDefaultRoute()
} }

View file

@ -10,8 +10,15 @@ module.exports = async (url, opts) => {
} }
} }
if (url.includes("/api/admin")) {
return json({
email: "test@test.com",
_id: "us_test@test.com",
status: "active",
})
}
// mocked data based on url // mocked data based on url
if (url.includes("api/apps")) { else if (url.includes("api/apps")) {
return json({ return json({
app1: { app1: {
url: "/app1", url: "/app1",

View file

@ -43,7 +43,6 @@
"electron": "electron src/electron.js", "electron": "electron src/electron.js",
"build:electron": "electron-builder --dir", "build:electron": "electron-builder --dir",
"publish:electron": "electron-builder -mwl --publish always", "publish:electron": "electron-builder -mwl --publish always",
"postinstall": "electron-builder install-app-deps",
"lint": "eslint --fix src/", "lint": "eslint --fix src/",
"initialise": "node scripts/initialise.js" "initialise": "node scripts/initialise.js"
}, },
@ -80,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.14", "@budibase/client": "^0.8.14",
"@budibase/string-templates": "^0.8.14", "@budibase/string-templates": "^0.8.14",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",

View file

@ -33,9 +33,6 @@ async function init() {
fs.writeFileSync(envoyOutputPath, processStringSync(contents, config)) fs.writeFileSync(envoyOutputPath, processStringSync(contents, config))
const envFilePath = path.join(process.cwd(), ".env") const envFilePath = path.join(process.cwd(), ".env")
if (fs.existsSync(envFilePath)) {
return
}
const envFileJson = { const envFileJson = {
PORT: 4001, PORT: 4001,
MINIO_URL: "http://localhost:10000/", MINIO_URL: "http://localhost:10000/",
@ -70,7 +67,11 @@ async function nuke() {
console.log( console.log(
"Clearing down your budibase dev environment, including all containers and volumes... 💥" "Clearing down your budibase dev environment, including all containers and volumes... 💥"
) )
await compose.down(CONFIG) await compose.down({
...CONFIG,
// stop containers, delete volumes
commandOptions: ["-v", "--remove-orphans"],
})
} }
const managementCommand = process.argv.slice(2)[0] const managementCommand = process.argv.slice(2)[0]

View file

@ -1,6 +1,5 @@
const CouchDB = require("../../db") const CouchDB = require("../../db")
const env = require("../../environment") const env = require("../../environment")
const setBuilderToken = require("../../utilities/builder/setBuilderToken")
const packageJson = require("../../../package.json") const packageJson = require("../../../package.json")
const { const {
createLinkView, createLinkView,
@ -74,7 +73,7 @@ async function getAppUrlIfNotInUse(ctx) {
if (!env.SELF_HOSTED) { if (!env.SELF_HOSTED) {
return url return url
} }
const deployedApps = await getDeployedApps() const deployedApps = await getDeployedApps(ctx)
if ( if (
deployedApps[url] != null && deployedApps[url] != null &&
deployedApps[url].appId !== ctx.params.appId deployedApps[url].appId !== ctx.params.appId
@ -145,7 +144,6 @@ exports.fetchAppPackage = async function(ctx) {
layouts, layouts,
clientLibPath: clientLibraryPath(ctx.params.appId), clientLibPath: clientLibraryPath(ctx.params.appId),
} }
await setBuilderToken(ctx, ctx.params.appId, application.version)
} }
exports.create = async function(ctx) { exports.create = async function(ctx) {
@ -161,7 +159,6 @@ exports.create = async function(ctx) {
const url = await getAppUrlIfNotInUse(ctx) const url = await getAppUrlIfNotInUse(ctx)
const appId = instance._id const appId = instance._id
const version = packageJson.version
const newApplication = { const newApplication = {
_id: appId, _id: appId,
type: "app", type: "app",
@ -184,7 +181,6 @@ exports.create = async function(ctx) {
await createApp(appId) await createApp(appId)
} }
await setBuilderToken(ctx, appId, version)
ctx.status = 200 ctx.status = 200
ctx.body = newApplication ctx.body = newApplication
ctx.message = `Application ${ctx.request.body.name} created successfully` ctx.message = `Application ${ctx.request.body.name} created successfully`

View file

@ -3,12 +3,12 @@ const CouchDB = require("../../db")
const bcrypt = require("../../utilities/bcrypt") const bcrypt = require("../../utilities/bcrypt")
const env = require("../../environment") const env = require("../../environment")
const { getAPIKey } = require("../../utilities/usageQuota") const { getAPIKey } = require("../../utilities/usageQuota")
const { generateUserID } = require("../../db/utils") const { generateUserMetadataID } = 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 { InternalTables } = require("../../db/utils")
const { UserStatus } = require("../../constants") const { UserStatus } = require("@budibase/auth")
const setBuilderToken = require("../../utilities/builder/setBuilderToken") const { getFullUser } = require("../../utilities/users")
const INVALID_ERR = "Invalid Credentials" const INVALID_ERR = "Invalid Credentials"
@ -27,7 +27,7 @@ exports.authenticate = async ctx => {
let dbUser let dbUser
try { try {
dbUser = await db.get(generateUserID(email)) dbUser = await db.get(generateUserMetadataID(email))
} catch (_) { } catch (_) {
// do not want to throw a 404 - as this could be // do not want to throw a 404 - as this could be
// used to determine valid emails // used to determine valid emails
@ -49,7 +49,7 @@ exports.authenticate = async ctx => {
// if in prod add the user api key, unless self hosted // if in prod add the user api key, unless self hosted
/* istanbul ignore next */ /* istanbul ignore next */
if (env.isProd() && !env.SELF_HOSTED) { if (env.isProd() && !env.SELF_HOSTED) {
const { apiKey } = await getAPIKey(ctx.user.appId) const { apiKey } = await getAPIKey(ctx.appId)
payload.apiKey = apiKey payload.apiKey = apiKey
} }
@ -70,24 +70,36 @@ exports.authenticate = async ctx => {
} }
} }
exports.builderLogin = async ctx => {
await setBuilderToken(ctx)
ctx.status = 200
}
exports.fetchSelf = async ctx => { exports.fetchSelf = async ctx => {
const { userId, appId } = ctx.user if (!ctx.user) {
ctx.throw(403, "No user logged in")
}
const appId = ctx.appId
const { userId } = ctx.user
/* istanbul ignore next */ /* istanbul ignore next */
if (!userId || !appId) { if (!userId) {
ctx.body = {} ctx.body = {}
return return
} }
const db = new CouchDB(appId)
const user = await db.get(userId) const user = await getFullUser({ ctx, userId: userId })
const userTable = await db.get(ViewNames.USERS)
if (user) { if (appId) {
delete user.password const db = new CouchDB(appId)
// remove the full roles structure
delete user.roles
try {
const userTable = await db.get(InternalTables.USER_METADATA)
const metadata = await db.get(userId)
// specifically needs to make sure is enriched
ctx.body = await outputProcessing(appId, userTable, {
...user,
...metadata,
})
} catch (err) {
ctx.body = user
}
} else {
ctx.body = user
} }
// specifically needs to make sure is enriched
ctx.body = await outputProcessing(appId, userTable, user)
} }

View file

@ -34,13 +34,13 @@ function cleanAutomationInputs(automation) {
/** /**
* This function handles checking if any webhooks need to be created or deleted for automations. * This function handles checking if any webhooks need to be created or deleted for automations.
* @param {object} user The user object, including all auth info * @param {string} appId The ID of the app in which we are checking for webhooks
* @param {object|undefined} oldAuto The old automation object if updating/deleting * @param {object|undefined} oldAuto The old automation object if updating/deleting
* @param {object|undefined} newAuto The new automation object if creating/updating * @param {object|undefined} newAuto The new automation object if creating/updating
* @returns {Promise<object|undefined>} After this is complete the new automation object may have been updated and should be * @returns {Promise<object|undefined>} After this is complete the new automation object may have been updated and should be
* written to DB (this does not write to DB as it would be wasteful to repeat). * written to DB (this does not write to DB as it would be wasteful to repeat).
*/ */
async function checkForWebhooks({ user, oldAuto, newAuto }) { async function checkForWebhooks({ appId, oldAuto, newAuto }) {
const oldTrigger = oldAuto ? oldAuto.definition.trigger : null const oldTrigger = oldAuto ? oldAuto.definition.trigger : null
const newTrigger = newAuto ? newAuto.definition.trigger : null const newTrigger = newAuto ? newAuto.definition.trigger : null
function isWebhookTrigger(auto) { function isWebhookTrigger(auto) {
@ -56,11 +56,11 @@ async function checkForWebhooks({ user, oldAuto, newAuto }) {
!isWebhookTrigger(newAuto) && !isWebhookTrigger(newAuto) &&
oldTrigger.webhookId oldTrigger.webhookId
) { ) {
let db = new CouchDB(user.appId) let db = new CouchDB(appId)
// need to get the webhook to get the rev // need to get the webhook to get the rev
const webhook = await db.get(oldTrigger.webhookId) const webhook = await db.get(oldTrigger.webhookId)
const ctx = { const ctx = {
user, appId,
params: { id: webhook._id, rev: webhook._rev }, params: { id: webhook._id, rev: webhook._rev },
} }
// might be updating - reset the inputs to remove the URLs // might be updating - reset the inputs to remove the URLs
@ -73,7 +73,7 @@ async function checkForWebhooks({ user, oldAuto, newAuto }) {
// need to create webhook // need to create webhook
else if (!isWebhookTrigger(oldAuto) && isWebhookTrigger(newAuto)) { else if (!isWebhookTrigger(oldAuto) && isWebhookTrigger(newAuto)) {
const ctx = { const ctx = {
user, appId,
request: { request: {
body: new webhooks.Webhook( body: new webhooks.Webhook(
"Automation webhook", "Automation webhook",
@ -86,17 +86,17 @@ async function checkForWebhooks({ user, oldAuto, newAuto }) {
const id = ctx.body.webhook._id const id = ctx.body.webhook._id
newTrigger.webhookId = id newTrigger.webhookId = id
newTrigger.inputs = { newTrigger.inputs = {
schemaUrl: `api/webhooks/schema/${user.appId}/${id}`, schemaUrl: `api/webhooks/schema/${appId}/${id}`,
triggerUrl: `api/webhooks/trigger/${user.appId}/${id}`, triggerUrl: `api/webhooks/trigger/${appId}/${id}`,
} }
} }
return newAuto return newAuto
} }
exports.create = async function(ctx) { exports.create = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
let automation = ctx.request.body let automation = ctx.request.body
automation.appId = ctx.user.appId automation.appId = ctx.appId
// call through to update if already exists // call through to update if already exists
if (automation._id && automation._rev) { if (automation._id && automation._rev) {
@ -107,7 +107,10 @@ exports.create = async function(ctx) {
automation.type = "automation" automation.type = "automation"
automation = cleanAutomationInputs(automation) automation = cleanAutomationInputs(automation)
automation = await checkForWebhooks({ user: ctx.user, newAuto: automation }) automation = await checkForWebhooks({
appId: ctx.appId,
newAuto: automation,
})
const response = await db.put(automation) const response = await db.put(automation)
automation._rev = response.rev automation._rev = response.rev
@ -122,13 +125,13 @@ exports.create = async function(ctx) {
} }
exports.update = async function(ctx) { exports.update = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
let automation = ctx.request.body let automation = ctx.request.body
automation.appId = ctx.user.appId automation.appId = ctx.appId
const oldAutomation = await db.get(automation._id) const oldAutomation = await db.get(automation._id)
automation = cleanAutomationInputs(automation) automation = cleanAutomationInputs(automation)
automation = await checkForWebhooks({ automation = await checkForWebhooks({
user: ctx.user, appId: ctx.appId,
oldAuto: oldAutomation, oldAuto: oldAutomation,
newAuto: automation, newAuto: automation,
}) })
@ -147,7 +150,7 @@ exports.update = async function(ctx) {
} }
exports.fetch = async function(ctx) { exports.fetch = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const response = await db.allDocs( const response = await db.allDocs(
getAutomationParams(null, { getAutomationParams(null, {
include_docs: true, include_docs: true,
@ -157,14 +160,17 @@ exports.fetch = async function(ctx) {
} }
exports.find = async function(ctx) { exports.find = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
ctx.body = await db.get(ctx.params.id) ctx.body = await db.get(ctx.params.id)
} }
exports.destroy = async function(ctx) { exports.destroy = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const oldAutomation = await db.get(ctx.params.id) const oldAutomation = await db.get(ctx.params.id)
await checkForWebhooks({ user: ctx.user, oldAuto: oldAutomation }) await checkForWebhooks({
appId: ctx.appId,
oldAuto: oldAutomation,
})
ctx.body = await db.remove(ctx.params.id, ctx.params.rev) ctx.body = await db.remove(ctx.params.id, ctx.params.rev)
} }
@ -195,11 +201,11 @@ module.exports.getDefinitionList = async function(ctx) {
*********************/ *********************/
exports.trigger = async function(ctx) { exports.trigger = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
let automation = await db.get(ctx.params.id) let automation = await db.get(ctx.params.id)
await triggers.externalTrigger(automation, { await triggers.externalTrigger(automation, {
...ctx.request.body, ...ctx.request.body,
appId: ctx.user.appId, appId: ctx.appId,
}) })
ctx.status = 200 ctx.status = 200
ctx.body = { ctx.body = {

View file

@ -6,7 +6,7 @@ const {
} = require("../../db/utils") } = require("../../db/utils")
exports.fetch = async function(ctx) { exports.fetch = async function(ctx) {
const database = new CouchDB(ctx.user.appId) const database = new CouchDB(ctx.appId)
ctx.body = ( ctx.body = (
await database.allDocs( await database.allDocs(
getDatasourceParams(null, { getDatasourceParams(null, {
@ -17,7 +17,7 @@ exports.fetch = async function(ctx) {
} }
exports.save = async function(ctx) { exports.save = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const datasource = { const datasource = {
_id: generateDatasourceID(), _id: generateDatasourceID(),
@ -34,7 +34,7 @@ exports.save = async function(ctx) {
} }
exports.destroy = async function(ctx) { exports.destroy = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
// Delete all queries for the datasource // Delete all queries for the datasource
const rows = await db.allDocs(getQueryParams(ctx.params.datasourceId, null)) const rows = await db.allDocs(getQueryParams(ctx.params.datasourceId, null))
@ -48,6 +48,6 @@ exports.destroy = async function(ctx) {
} }
exports.find = async function(ctx) { exports.find = async function(ctx) {
const database = new CouchDB(ctx.user.appId) const database = new CouchDB(ctx.appId)
ctx.body = await database.get(ctx.params.datasourceId) ctx.body = await database.get(ctx.params.datasourceId)
} }

View file

@ -93,7 +93,7 @@ async function deployApp(deployment) {
exports.fetchDeployments = async function(ctx) { exports.fetchDeployments = async function(ctx) {
try { try {
const db = new PouchDB(ctx.user.appId) const db = new PouchDB(ctx.appId)
const deploymentDoc = await db.get("_local/deployments") const deploymentDoc = await db.get("_local/deployments")
const { updated, deployments } = await checkAllDeployments( const { updated, deployments } = await checkAllDeployments(
deploymentDoc, deploymentDoc,
@ -110,7 +110,7 @@ exports.fetchDeployments = async function(ctx) {
exports.deploymentProgress = async function(ctx) { exports.deploymentProgress = async function(ctx) {
try { try {
const db = new PouchDB(ctx.user.appId) const db = new PouchDB(ctx.appId)
const deploymentDoc = await db.get("_local/deployments") const deploymentDoc = await db.get("_local/deployments")
ctx.body = deploymentDoc[ctx.params.deploymentId] ctx.body = deploymentDoc[ctx.params.deploymentId]
} catch (err) { } catch (err) {
@ -128,7 +128,7 @@ exports.deployApp = async function(ctx) {
hostingInfo.type === HostingTypes.CLOUD hostingInfo.type === HostingTypes.CLOUD
? require("./awsDeploy") ? require("./awsDeploy")
: require("./selfDeploy") : require("./selfDeploy")
let deployment = new Deployment(ctx.user.appId) let deployment = new Deployment(ctx.appId)
deployment.setStatus(DeploymentStatus.PENDING) deployment.setStatus(DeploymentStatus.PENDING)
deployment = await storeLocalDeploymentHistory(deployment) deployment = await storeLocalDeploymentHistory(deployment)

View file

@ -0,0 +1,34 @@
const fetch = require("node-fetch")
const env = require("../../environment")
const { checkSlashesInUrl } = require("../../utilities")
const { request } = require("../../utilities/workerRequests")
async function redirect(ctx, method) {
const { path } = ctx.params
const response = await fetch(
checkSlashesInUrl(`${env.WORKER_URL}/api/admin/${path}`),
request(ctx, {
method,
body: ctx.request.body,
})
)
ctx.body = await response.json()
const cookie = response.headers.get("set-cookie")
if (cookie) {
ctx.set("set-cookie", cookie)
}
ctx.status = response.status
ctx.cookies
}
exports.redirectGet = async ctx => {
await redirect(ctx, "GET")
}
exports.redirectPost = async ctx => {
await redirect(ctx, "POST")
}
exports.redirectDelete = async ctx => {
await redirect(ctx, "DELETE")
}

View file

@ -40,5 +40,5 @@ exports.fetchUrls = async ctx => {
} }
exports.getDeployedApps = async ctx => { exports.getDeployedApps = async ctx => {
ctx.body = await getDeployedApps() ctx.body = await getDeployedApps(ctx)
} }

View file

@ -3,7 +3,7 @@ const CouchDB = require("../../db")
const { generateLayoutID, getScreenParams } = require("../../db/utils") const { generateLayoutID, getScreenParams } = require("../../db/utils")
exports.save = async function(ctx) { exports.save = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
let layout = ctx.request.body let layout = ctx.request.body
if (!layout.props) { if (!layout.props) {
@ -22,7 +22,7 @@ exports.save = async function(ctx) {
} }
exports.destroy = async function(ctx) { exports.destroy = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const layoutId = ctx.params.layoutId, const layoutId = ctx.params.layoutId,
layoutRev = ctx.params.layoutRev layoutRev = ctx.params.layoutRev

View file

@ -28,7 +28,7 @@ function formatResponse(resp) {
} }
exports.fetch = async function(ctx) { exports.fetch = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const body = await db.allDocs( const body = await db.allDocs(
getQueryParams(null, { getQueryParams(null, {
@ -39,7 +39,7 @@ exports.fetch = async function(ctx) {
} }
exports.save = async function(ctx) { exports.save = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const query = ctx.request.body const query = ctx.request.body
if (!query._id) { if (!query._id) {
@ -90,7 +90,7 @@ async function enrichQueryFields(fields, parameters) {
} }
exports.find = async function(ctx) { exports.find = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const query = enrichQueries(await db.get(ctx.params.queryId)) const query = enrichQueries(await db.get(ctx.params.queryId))
// remove properties that could be dangerous in real app // remove properties that could be dangerous in real app
if (env.isProd()) { if (env.isProd()) {
@ -102,7 +102,7 @@ exports.find = async function(ctx) {
} }
exports.preview = async function(ctx) { exports.preview = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const datasource = await db.get(ctx.request.body.datasourceId) const datasource = await db.get(ctx.request.body.datasourceId)
@ -130,7 +130,7 @@ exports.preview = async function(ctx) {
} }
exports.execute = async function(ctx) { exports.execute = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const query = await db.get(ctx.params.queryId) const query = await db.get(ctx.params.queryId)
const datasource = await db.get(query.datasourceId) const datasource = await db.get(query.datasourceId)
@ -153,7 +153,7 @@ exports.execute = async function(ctx) {
} }
exports.destroy = async function(ctx) { exports.destroy = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
await db.remove(ctx.params.queryId, ctx.params.revId) await db.remove(ctx.params.queryId, ctx.params.revId)
ctx.message = `Query deleted.` ctx.message = `Query deleted.`
ctx.status = 200 ctx.status = 200

View file

@ -10,8 +10,8 @@ const {
const { const {
generateRoleID, generateRoleID,
getRoleParams, getRoleParams,
getUserParams, getUserMetadataParams,
ViewNames, InternalTables,
} = require("../../db/utils") } = require("../../db/utils")
const UpdateRolesOptions = { const UpdateRolesOptions = {
@ -28,7 +28,7 @@ const EXTERNAL_BUILTIN_ROLE_IDS = [
] ]
async function updateRolesOnUserTable(db, roleId, updateOption) { async function updateRolesOnUserTable(db, roleId, updateOption) {
const table = await db.get(ViewNames.USERS) const table = await db.get(InternalTables.USER_METADATA)
const schema = table.schema const schema = table.schema
const remove = updateOption === UpdateRolesOptions.REMOVED const remove = updateOption === UpdateRolesOptions.REMOVED
let updated = false let updated = false
@ -51,7 +51,7 @@ async function updateRolesOnUserTable(db, roleId, updateOption) {
} }
exports.fetch = async function(ctx) { exports.fetch = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const body = await db.allDocs( const body = await db.allDocs(
getRoleParams(null, { getRoleParams(null, {
include_docs: true, include_docs: true,
@ -79,11 +79,11 @@ exports.fetch = async function(ctx) {
} }
exports.find = async function(ctx) { exports.find = async function(ctx) {
ctx.body = await getRole(ctx.user.appId, ctx.params.roleId) ctx.body = await getRole(ctx.appId, ctx.params.roleId)
} }
exports.save = async function(ctx) { exports.save = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
let { _id, name, inherits, permissionId } = ctx.request.body let { _id, name, inherits, permissionId } = ctx.request.body
if (!_id) { if (!_id) {
_id = generateRoleID() _id = generateRoleID()
@ -104,7 +104,7 @@ exports.save = async function(ctx) {
} }
exports.destroy = async function(ctx) { exports.destroy = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const roleId = ctx.params.roleId const roleId = ctx.params.roleId
if (isBuiltin(roleId)) { if (isBuiltin(roleId)) {
ctx.throw(400, "Cannot delete builtin role.") ctx.throw(400, "Cannot delete builtin role.")
@ -112,7 +112,7 @@ exports.destroy = async function(ctx) {
// first check no users actively attached to role // first check no users actively attached to role
const users = ( const users = (
await db.allDocs( await db.allDocs(
getUserParams(null, { getUserMetadataParams(null, {
include_docs: true, include_docs: true,
}) })
) )

View file

@ -6,10 +6,10 @@ const {
generateRowID, generateRowID,
DocumentTypes, DocumentTypes,
SEPARATOR, SEPARATOR,
ViewNames, InternalTables,
generateUserID, generateUserMetadataID,
} = require("../../db/utils") } = require("../../db/utils")
const usersController = require("./user") const userController = require("./user")
const { const {
inputProcessing, inputProcessing,
outputProcessing, outputProcessing,
@ -37,18 +37,14 @@ validateJs.extend(validateJs.validators.datetime, {
}, },
}) })
async function findRow(db, appId, tableId, rowId) { async function findRow(ctx, db, tableId, rowId) {
let row let row
if (tableId === ViewNames.USERS) { // TODO remove special user case in future
let ctx = { if (tableId === InternalTables.USER_METADATA) {
params: { ctx.params = {
userId: rowId, userId: rowId,
},
user: {
appId,
},
} }
await usersController.find(ctx) await userController.findMetadata(ctx)
row = ctx.body row = ctx.body
} else { } else {
row = await db.get(rowId) row = await db.get(rowId)
@ -60,7 +56,7 @@ async function findRow(db, appId, tableId, rowId) {
} }
exports.patch = async function(ctx) { exports.patch = async function(ctx) {
const appId = ctx.user.appId const appId = ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
let dbRow = await db.get(ctx.params.rowId) let dbRow = await db.get(ctx.params.rowId)
let dbTable = await db.get(dbRow.tableId) let dbTable = await db.get(dbRow.tableId)
@ -96,14 +92,14 @@ exports.patch = async function(ctx) {
table, table,
}) })
// Creation of a new user goes to the user controller // TODO remove special user case in future
if (row.tableId === ViewNames.USERS) { if (row.tableId === InternalTables.USER_METADATA) {
// 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 = { ctx.request.body = {
...row, ...row,
password: ctx.request.body.password, password: ctx.request.body.password,
} }
await usersController.update(ctx) await userController.updateMetadata(ctx)
return return
} }
@ -121,7 +117,7 @@ exports.patch = async function(ctx) {
} }
exports.save = async function(ctx) { exports.save = async function(ctx) {
const appId = ctx.user.appId const appId = ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
let inputs = ctx.request.body let inputs = ctx.request.body
inputs.tableId = ctx.params.tableId inputs.tableId = ctx.params.tableId
@ -134,16 +130,19 @@ exports.save = async function(ctx) {
} }
// if the row obj had an _id then it will have been retrieved // if the row obj had an _id then it will have been retrieved
const existingRow = ctx.preExisting if (inputs._id && inputs._rev) {
if (existingRow) { const existingRow = await db.get(inputs._id)
ctx.params.rowId = inputs._id if (existingRow) {
await exports.patch(ctx) ctx.params.rowId = inputs._id
return await exports.patch(ctx)
return
}
} }
if (!inputs._rev && !inputs._id) { if (!inputs._rev && !inputs._id) {
if (inputs.tableId === ViewNames.USERS) { // TODO remove special user case in future
inputs._id = generateUserID(inputs.email) if (inputs.tableId === InternalTables.USER_METADATA) {
inputs._id = generateUserMetadataID(inputs.email)
} else { } else {
inputs._id = generateRowID(inputs.tableId) inputs._id = generateRowID(inputs.tableId)
} }
@ -175,11 +174,11 @@ exports.save = async function(ctx) {
table, table,
}) })
// Creation of a new user goes to the user controller // TODO remove special user case in future
if (row.tableId === ViewNames.USERS) { if (row.tableId === InternalTables.USER_METADATA) {
// 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 userController.createMetadata(ctx)
return return
} }
@ -197,7 +196,7 @@ exports.save = async function(ctx) {
} }
exports.fetchView = async function(ctx) { exports.fetchView = async function(ctx) {
const appId = ctx.user.appId const appId = ctx.appId
const viewName = ctx.params.viewName const viewName = ctx.params.viewName
// if this is a table view being looked for just transfer to that // if this is a table view being looked for just transfer to that
@ -256,7 +255,7 @@ exports.fetchView = async function(ctx) {
} }
exports.search = async function(ctx) { exports.search = async function(ctx) {
const appId = ctx.user.appId const appId = ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
const { const {
query, query,
@ -287,14 +286,6 @@ exports.search = async function(ctx) {
} }
const response = await search(searchString) const response = await search(searchString)
// delete passwords from users
if (tableId === ViewNames.USERS) {
for (let row of response.rows) {
delete row.password
}
}
const table = await db.get(tableId) const table = await db.get(tableId)
ctx.body = { ctx.body = {
rows: await outputProcessing(appId, table, response.rows), rows: await outputProcessing(appId, table, response.rows),
@ -303,14 +294,14 @@ exports.search = async function(ctx) {
} }
exports.fetchTableRows = async function(ctx) { exports.fetchTableRows = async function(ctx) {
const appId = ctx.user.appId const appId = ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
// special case for users, fetch through the user controller // TODO remove special user case in future
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 === InternalTables.USER_METADATA) {
await usersController.fetch(ctx) await userController.fetchMetadata(ctx)
rows = ctx.body rows = ctx.body
} else { } else {
const response = await db.allDocs( const response = await db.allDocs(
@ -324,11 +315,11 @@ exports.fetchTableRows = async function(ctx) {
} }
exports.find = async function(ctx) { exports.find = async function(ctx) {
const appId = ctx.user.appId const appId = ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
try { try {
const table = await db.get(ctx.params.tableId) const table = await db.get(ctx.params.tableId)
const row = await findRow(db, appId, ctx.params.tableId, ctx.params.rowId) const row = await findRow(ctx, db, ctx.params.tableId, ctx.params.rowId)
ctx.body = await outputProcessing(appId, table, row) ctx.body = await outputProcessing(appId, table, row)
} catch (err) { } catch (err) {
ctx.throw(400, err) ctx.throw(400, err)
@ -336,7 +327,7 @@ exports.find = async function(ctx) {
} }
exports.destroy = async function(ctx) { exports.destroy = async function(ctx) {
const appId = ctx.user.appId const appId = ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
const row = await db.get(ctx.params.rowId) const row = await db.get(ctx.params.rowId)
if (row.tableId !== ctx.params.tableId) { if (row.tableId !== ctx.params.tableId) {
@ -348,17 +339,25 @@ exports.destroy = async function(ctx) {
row, row,
tableId: row.tableId, tableId: row.tableId,
}) })
ctx.body = await db.remove(ctx.params.rowId, ctx.params.revId) // TODO remove special user case in future
ctx.status = 200 if (ctx.params.tableId === InternalTables.USER_METADATA) {
ctx.params = {
userId: ctx.params.rowId,
}
await userController.destroyMetadata(ctx)
} else {
ctx.body = await db.remove(ctx.params.rowId, ctx.params.revId)
}
// for automations include the row that was deleted // for automations include the row that was deleted
ctx.row = row ctx.row = row
ctx.status = 200
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row) ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row)
} }
exports.validate = async function(ctx) { exports.validate = async function(ctx) {
const errors = await validate({ const errors = await validate({
appId: ctx.user.appId, appId: ctx.appId,
tableId: ctx.params.tableId, tableId: ctx.params.tableId,
row: ctx.request.body, row: ctx.request.body,
}) })
@ -388,14 +387,14 @@ async function validate({ appId, tableId, row, table }) {
} }
exports.fetchEnrichedRow = async function(ctx) { exports.fetchEnrichedRow = async function(ctx) {
const appId = ctx.user.appId const appId = ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
const tableId = ctx.params.tableId const tableId = ctx.params.tableId
const rowId = ctx.params.rowId const rowId = ctx.params.rowId
// need table to work out where links go in row // need table to work out where links go in row
let [table, row] = await Promise.all([ let [table, row] = await Promise.all([
db.get(tableId), db.get(tableId),
findRow(db, appId, tableId, rowId), findRow(ctx, db, tableId, rowId),
]) ])
// get the link docs // get the link docs
const linkVals = await linkRows.getLinkDocuments({ const linkVals = await linkRows.getLinkDocuments({
@ -433,11 +432,11 @@ exports.fetchEnrichedRow = async function(ctx) {
} }
async function bulkDelete(ctx) { async function bulkDelete(ctx) {
const appId = ctx.user.appId const appId = ctx.appId
const { rows } = ctx.request.body const { rows } = ctx.request.body
const db = new CouchDB(appId) const db = new CouchDB(appId)
const linkUpdates = rows.map(row => let updates = rows.map(row =>
linkRows.updateLinks({ linkRows.updateLinks({
appId, appId,
eventType: linkRows.EventType.ROW_DELETE, eventType: linkRows.EventType.ROW_DELETE,
@ -445,9 +444,20 @@ async function bulkDelete(ctx) {
tableId: row.tableId, tableId: row.tableId,
}) })
) )
// TODO remove special user case in future
await db.bulkDocs(rows.map(row => ({ ...row, _deleted: true }))) if (ctx.params.tableId === InternalTables.USER_METADATA) {
await Promise.all(linkUpdates) updates = updates.concat(
rows.map(row => {
ctx.params = {
userId: row._id,
}
return userController.destroyMetadata(ctx)
})
)
} else {
await db.bulkDocs(rows.map(row => ({ ...row, _deleted: true })))
}
await Promise.all(updates)
rows.forEach(row => { rows.forEach(row => {
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row) ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row)

View file

@ -3,7 +3,7 @@ const { getScreenParams, generateScreenID } = require("../../db/utils")
const { AccessController } = require("../../utilities/security/roles") const { AccessController } = require("../../utilities/security/roles")
exports.fetch = async ctx => { exports.fetch = async ctx => {
const appId = ctx.user.appId const appId = ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
const screens = ( const screens = (
@ -21,7 +21,7 @@ exports.fetch = async ctx => {
} }
exports.save = async ctx => { exports.save = async ctx => {
const appId = ctx.user.appId const appId = ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
let screen = ctx.request.body let screen = ctx.request.body
@ -39,7 +39,7 @@ exports.save = async ctx => {
} }
exports.destroy = async ctx => { exports.destroy = async ctx => {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
await db.remove(ctx.params.screenId, ctx.params.screenRev) await db.remove(ctx.params.screenId, ctx.params.screenRev)
ctx.body = { ctx.body = {
message: "Screen deleted successfully", message: "Screen deleted successfully",

View file

@ -1,8 +1,7 @@
const { QueryBuilder, buildSearchUrl, search } = require("./utils") const { QueryBuilder, buildSearchUrl, search } = require("./utils")
exports.rowSearch = async ctx => { exports.rowSearch = async ctx => {
// this can't be done through pouch, have to reach for trusty node-fetch const appId = ctx.appId
const appId = ctx.user.appId
const { tableId } = ctx.params const { tableId } = ctx.params
const { bookmark, query, raw } = ctx.request.body const { bookmark, query, raw } = ctx.request.body
let url let url

View file

@ -9,7 +9,6 @@ const { processString } = require("@budibase/string-templates")
const { budibaseTempDir } = require("../../../utilities/budibaseDir") const { budibaseTempDir } = require("../../../utilities/budibaseDir")
const { getDeployedApps } = require("../../../utilities/builder/hosting") const { getDeployedApps } = require("../../../utilities/builder/hosting")
const CouchDB = require("../../../db") const CouchDB = require("../../../db")
const setBuilderToken = require("../../../utilities/builder/setBuilderToken")
const { const {
loadHandlebarsFile, loadHandlebarsFile,
NODE_MODULES_PATH, NODE_MODULES_PATH,
@ -22,7 +21,7 @@ const { objectStoreUrl, clientLibraryPath } = require("../../../utilities")
async function checkForSelfHostedURL(ctx) { async function checkForSelfHostedURL(ctx) {
// the "appId" component of the URL may actually be a specific self hosted URL // the "appId" component of the URL may actually be a specific self hosted URL
let possibleAppUrl = `/${encodeURI(ctx.params.appId).toLowerCase()}` let possibleAppUrl = `/${encodeURI(ctx.params.appId).toLowerCase()}`
const apps = await getDeployedApps() const apps = await getDeployedApps(ctx)
if (apps[possibleAppUrl] && apps[possibleAppUrl].appId) { if (apps[possibleAppUrl] && apps[possibleAppUrl].appId) {
return apps[possibleAppUrl].appId return apps[possibleAppUrl].appId
} else { } else {
@ -35,9 +34,6 @@ const COMP_LIB_BASE_APP_VERSION = "0.2.5"
exports.serveBuilder = async function(ctx) { exports.serveBuilder = async function(ctx) {
let builderPath = resolve(TOP_LEVEL_PATH, "builder") let builderPath = resolve(TOP_LEVEL_PATH, "builder")
if (ctx.file === "index.html") {
await setBuilderToken(ctx)
}
await send(ctx, ctx.file, { root: builderPath }) await send(ctx, ctx.file, { root: builderPath })
} }
@ -60,7 +56,7 @@ exports.uploadFile = async function(ctx) {
return prepareUpload({ return prepareUpload({
file, file,
s3Key: `assets/${ctx.user.appId}/attachments/${processedFileName}`, s3Key: `assets/${ctx.appId}/attachments/${processedFileName}`,
bucket: "prod-budi-app-assets", bucket: "prod-budi-app-assets",
}) })
}) })
@ -110,20 +106,17 @@ exports.serveComponentLibrary = async function(ctx) {
) )
return send(ctx, "/awsDeploy.js", { root: componentLibraryPath }) return send(ctx, "/awsDeploy.js", { root: componentLibraryPath })
} }
const db = new CouchDB(appId)
const appInfo = await db.get(appId)
let componentLib = "componentlibrary" let componentLib = "componentlibrary"
if (ctx.user.version) { if (appInfo && appInfo.version) {
componentLib += `-${ctx.user.version}` componentLib += `-${appInfo.version}`
} else { } else {
componentLib += `-${COMP_LIB_BASE_APP_VERSION}` componentLib += `-${COMP_LIB_BASE_APP_VERSION}`
} }
const S3_URL = encodeURI( const S3_URL = encodeURI(
join( join(objectStoreUrl(), componentLib, ctx.query.library, "dist", "index.js")
objectStoreUrl(appId),
componentLib,
ctx.query.library,
"dist",
"index.js"
)
) )
const response = await fetch(S3_URL) const response = await fetch(S3_URL)
const body = await response.text() const body = await response.text()

View file

@ -10,7 +10,7 @@ const { FieldTypes } = require("../../../constants")
const { TableSaveFunctions } = require("./utils") const { TableSaveFunctions } = require("./utils")
exports.fetch = async function(ctx) { exports.fetch = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const body = await db.allDocs( const body = await db.allDocs(
getTableParams(null, { getTableParams(null, {
include_docs: true, include_docs: true,
@ -20,12 +20,12 @@ exports.fetch = async function(ctx) {
} }
exports.find = async function(ctx) { exports.find = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
ctx.body = await db.get(ctx.params.id) ctx.body = await db.get(ctx.params.id)
} }
exports.save = async function(ctx) { exports.save = async function(ctx) {
const appId = ctx.user.appId const appId = ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
const { dataImport, ...rest } = ctx.request.body const { dataImport, ...rest } = ctx.request.body
let tableToSave = { let tableToSave = {
@ -127,7 +127,7 @@ exports.save = async function(ctx) {
} }
exports.destroy = async function(ctx) { exports.destroy = async function(ctx) {
const appId = ctx.user.appId const appId = ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
const tableToDelete = await db.get(ctx.params.tableId) const tableToDelete = await db.get(ctx.params.tableId)

View file

@ -1,6 +1,10 @@
const CouchDB = require("../../../db") const CouchDB = require("../../../db")
const csvParser = require("../../../utilities/csvParser") const csvParser = require("../../../utilities/csvParser")
const { getRowParams, generateRowID, ViewNames } = require("../../../db/utils") const {
getRowParams,
generateRowID,
InternalTables,
} = require("../../../db/utils")
const { isEqual } = require("lodash/fp") const { isEqual } = require("lodash/fp")
const { AutoFieldSubTypes } = require("../../../constants") const { AutoFieldSubTypes } = require("../../../constants")
const { inputProcessing } = require("../../../utilities/rowProcessor") const { inputProcessing } = require("../../../utilities/rowProcessor")
@ -57,8 +61,8 @@ exports.makeSureTableUpToDate = (table, tableToSave) => {
return tableToSave return tableToSave
} }
exports.handleDataImport = async (user, table, dataImport) => { exports.handleDataImport = async (appId, user, table, dataImport) => {
const db = new CouchDB(user.appId) const db = new CouchDB(appId)
if (dataImport && dataImport.csvString) { if (dataImport && dataImport.csvString) {
// Populate the table with rows imported from CSV in a bulk update // Populate the table with rows imported from CSV in a bulk update
const data = await csvParser.transform(dataImport) const data = await csvParser.transform(dataImport)
@ -136,7 +140,7 @@ exports.handleSearchIndexes = async (appId, table) => {
exports.checkStaticTables = table => { exports.checkStaticTables = table => {
// check user schema has all required elements // check user schema has all required elements
if (table._id === ViewNames.USERS) { if (table._id === InternalTables.USER_METADATA) {
for (let [key, schema] of Object.entries(USERS_TABLE_SCHEMA.schema)) { for (let [key, schema] of Object.entries(USERS_TABLE_SCHEMA.schema)) {
// check if the schema exists on the table to be created/updated // check if the schema exists on the table to be created/updated
if (table.schema[key] == null) { if (table.schema[key] == null) {
@ -152,7 +156,7 @@ class TableSaveFunctions {
this.db = db this.db = db
this.ctx = ctx this.ctx = ctx
if (this.ctx && this.ctx.user) { if (this.ctx && this.ctx.user) {
this.appId = this.ctx.user.appId this.appId = this.ctx.appId
} }
this.oldTable = oldTable this.oldTable = oldTable
this.dataImport = dataImport this.dataImport = dataImport
@ -184,6 +188,7 @@ class TableSaveFunctions {
async after(table) { async after(table) {
table = await exports.handleSearchIndexes(this.appId, table) table = await exports.handleSearchIndexes(this.appId, table)
table = await exports.handleDataImport( table = await exports.handleDataImport(
this.appId,
this.ctx.user, this.ctx.user,
table, table,
this.dataImport this.dataImport

View file

@ -1,115 +1,110 @@
const CouchDB = require("../../db") const CouchDB = require("../../db")
const bcrypt = require("../../utilities/bcrypt") const {
const { generateUserID, getUserParams, ViewNames } = require("../../db/utils") generateUserMetadataID,
getUserMetadataParams,
getEmailFromUserMetadataID,
} = require("../../db/utils")
const { InternalTables } = require("../../db/utils")
const { getRole } = require("../../utilities/security/roles") const { getRole } = require("../../utilities/security/roles")
const { UserStatus } = require("../../constants") const {
getGlobalUsers,
saveGlobalUser,
deleteGlobalUser,
} = require("../../utilities/workerRequests")
const { getFullUser } = require("../../utilities/users")
exports.fetch = async function(ctx) { exports.fetchMetadata = async function(ctx) {
const database = new CouchDB(ctx.user.appId) const database = new CouchDB(ctx.appId)
const users = ( const global = await getGlobalUsers(ctx, ctx.appId)
const metadata = (
await database.allDocs( await database.allDocs(
getUserParams(null, { getUserMetadataParams(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))
// remove these props, not for the correct DB
delete user._id
delete user._rev
users.push({
...user,
...info,
// make sure the ID is always a local ID, not a global one
_id: generateUserMetadataID(user.email),
})
} }
ctx.body = users ctx.body = users
} }
exports.create = async function(ctx) { exports.createMetadata = async function(ctx) {
const db = new CouchDB(ctx.user.appId) const appId = ctx.appId
const { email, password, roleId } = ctx.request.body const db = new CouchDB(appId)
const { roleId } = ctx.request.body
if (!email || !password) { const email = ctx.request.body.email || ctx.user.email
ctx.throw(400, "email and Password Required.")
}
const role = await getRole(ctx.user.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(ctx, appId, email, ctx.request.body)
const user = { const user = {
...ctx.request.body, ...metadata,
// these must all be after the object spread, make sure _id: generateUserMetadataID(email),
// any values are overwritten, generateUserID will always
// generate the same ID for the user as it is not UUID based
_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) // for automations to make it obvious was successful
ctx.status = 200
ctx.message = "User created successfully."
ctx.userId = response.id
ctx.body = {
_rev: response.rev,
email,
}
} catch (err) {
if (err.status === 409) {
ctx.throw(400, "User exists already")
} else {
ctx.throw(err.status, err)
}
}
}
exports.update = async function(ctx) {
const db = new CouchDB(ctx.user.appId)
const user = ctx.request.body
let dbUser
if (user.email && !user._id) {
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({
password: dbUser.password,
...user,
})
user._rev = response.rev
ctx.status = 200 ctx.status = 200
ctx.body = response ctx.body = {
_id: response.id,
_rev: response.rev,
email,
}
} }
exports.destroy = async function(ctx) { exports.updateMetadata = async function(ctx) {
const database = new CouchDB(ctx.user.appId) const appId = ctx.appId
await database.destroy(generateUserID(ctx.params.email)) const db = new CouchDB(appId)
const user = ctx.request.body
let email = user.email || getEmailFromUserMetadataID(user._id)
const metadata = await saveGlobalUser(ctx, appId, email, ctx.request.body)
if (!metadata._id) {
metadata._id = generateUserMetadataID(email)
}
if (!metadata._rev) {
metadata._rev = ctx.request.body._rev
}
ctx.body = await db.put({
...metadata,
})
}
exports.destroyMetadata = async function(ctx) {
const db = new CouchDB(ctx.appId)
const email =
ctx.params.email || getEmailFromUserMetadataID(ctx.params.userId)
await deleteGlobalUser(ctx, email)
try {
const dbUser = await db.get(generateUserMetadataID(email))
await db.remove(dbUser._id, dbUser._rev)
} catch (err) {
// error just means the global user has no config in this app
}
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.user.appId) ctx.body = await getFullUser({
let lookup = ctx.params.email ctx,
? generateUserID(ctx.params.email) email: ctx.params.email,
: ctx.params.userId userId: ctx.params.userId,
const user = await database.get(lookup) })
if (user) {
delete user.password
}
ctx.body = user
} }

View file

@ -7,7 +7,7 @@ const { ViewNames } = require("../../../db/utils")
const controller = { const controller = {
fetch: async ctx => { fetch: async ctx => {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const designDoc = await db.get("_design/database") const designDoc = await db.get("_design/database")
const response = [] const response = []
@ -25,7 +25,7 @@ const controller = {
ctx.body = response ctx.body = response
}, },
save: async ctx => { save: async ctx => {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const { originalName, ...viewToSave } = ctx.request.body const { originalName, ...viewToSave } = ctx.request.body
const designDoc = await db.get("_design/database") const designDoc = await db.get("_design/database")
const view = viewTemplate(viewToSave) const view = viewTemplate(viewToSave)
@ -66,7 +66,7 @@ const controller = {
} }
}, },
destroy: async ctx => { destroy: async ctx => {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const designDoc = await db.get("_design/database") const designDoc = await db.get("_design/database")
const viewName = decodeURI(ctx.params.viewName) const viewName = decodeURI(ctx.params.viewName)
const view = designDoc.views[viewName] const view = designDoc.views[viewName]
@ -81,7 +81,7 @@ const controller = {
ctx.body = view ctx.body = view
}, },
exportView: async ctx => { exportView: async ctx => {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const designDoc = await db.get("_design/database") const designDoc = await db.get("_design/database")
const viewName = decodeURI(ctx.query.view) const viewName = decodeURI(ctx.query.view)

View file

@ -22,7 +22,7 @@ exports.WebhookType = {
} }
exports.fetch = async ctx => { exports.fetch = async ctx => {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const response = await db.allDocs( const response = await db.allDocs(
getWebhookParams(null, { getWebhookParams(null, {
include_docs: true, include_docs: true,
@ -32,9 +32,9 @@ exports.fetch = async ctx => {
} }
exports.save = async ctx => { exports.save = async ctx => {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
const webhook = ctx.request.body const webhook = ctx.request.body
webhook.appId = ctx.user.appId webhook.appId = ctx.appId
// check that the webhook exists // check that the webhook exists
if (webhook._id) { if (webhook._id) {
@ -51,7 +51,7 @@ exports.save = async ctx => {
} }
exports.destroy = async ctx => { exports.destroy = async ctx => {
const db = new CouchDB(ctx.user.appId) const db = new CouchDB(ctx.appId)
ctx.body = await db.remove(ctx.params.id, ctx.params.rev) ctx.body = await db.remove(ctx.params.id, ctx.params.rev)
} }

View file

@ -1,5 +1,6 @@
const Router = require("@koa/router") const Router = require("@koa/router")
const authenticated = require("../middleware/authenticated") const { authenticated } = require("@budibase/auth")
const currentApp = require("../middleware/currentapp")
const compress = require("koa-compress") const compress = require("koa-compress")
const zlib = require("zlib") const zlib = require("zlib")
const { mainRoutes, authRoutes, staticRoutes } = require("./routes") const { mainRoutes, authRoutes, staticRoutes } = require("./routes")
@ -13,10 +14,10 @@ router
compress({ compress({
threshold: 2048, threshold: 2048,
gzip: { gzip: {
flush: zlib.Z_SYNC_FLUSH, flush: zlib.constants.Z_SYNC_FLUSH,
}, },
deflate: { deflate: {
flush: zlib.Z_SYNC_FLUSH, flush: zlib.constants.Z_SYNC_FLUSH,
}, },
br: false, br: false,
}) })
@ -31,6 +32,7 @@ router
.use("/health", ctx => (ctx.status = 200)) .use("/health", ctx => (ctx.status = 200))
.use("/version", ctx => (ctx.body = pkg.version)) .use("/version", ctx => (ctx.body = pkg.version))
.use(authenticated) .use(authenticated)
.use(currentApp)
// error handling middleware // error handling middleware
router.use(async (ctx, next) => { router.use(async (ctx, next) => {

View file

@ -1,14 +1,8 @@
const Router = require("@koa/router") const Router = require("@koa/router")
const controller = require("../controllers/auth") const controller = require("../controllers/auth")
const authorized = require("../../middleware/authorized")
const { BUILDER } = require("../../utilities/security/permissions")
const router = Router() const router = Router()
router.post("/api/authenticate", controller.authenticate)
// TODO: this is a hack simply to make sure builder has a cookie until auth reworked
router.post("/api/builder/login", authorized(BUILDER), controller.builderLogin)
// doesn't need authorization as can only fetch info about self
router.get("/api/self", controller.fetchSelf) router.get("/api/self", controller.fetchSelf)
module.exports = router module.exports = router

View file

@ -0,0 +1,13 @@
const Router = require("@koa/router")
const controller = require("../controllers/dev")
const env = require("../../environment")
const router = Router()
if (env.isDev() || env.isTest()) {
router.get("/api/admin/:path", controller.redirectGet)
router.post("/api/admin/:path", controller.redirectPost)
router.delete("/api/admin/:path", controller.redirectDelete)
}
module.exports = router

View file

@ -22,6 +22,7 @@ const datasourceRoutes = require("./datasource")
const queryRoutes = require("./query") const queryRoutes = require("./query")
const hostingRoutes = require("./hosting") const hostingRoutes = require("./hosting")
const backupRoutes = require("./backup") const backupRoutes = require("./backup")
const devRoutes = require("./dev")
exports.mainRoutes = [ exports.mainRoutes = [
deployRoutes, deployRoutes,
@ -44,6 +45,7 @@ exports.mainRoutes = [
queryRoutes, queryRoutes,
hostingRoutes, hostingRoutes,
backupRoutes, backupRoutes,
devRoutes,
// these need to be handled last as they still use /api/:tableId // these need to be handled last as they still use /api/:tableId
// this could be breaking as koa may recognise other routes as this // this could be breaking as koa may recognise other routes as this
tableRoutes, tableRoutes,

View file

@ -1,5 +1,15 @@
const setup = require("./utilities") const setup = require("./utilities")
require("../../../utilities/workerRequests")
jest.mock("../../../utilities/workerRequests", () => ({
getGlobalUsers: jest.fn(() => {
return {
email: "test@test.com",
}
}),
saveGlobalUser: jest.fn(),
}))
describe("/authenticate", () => { describe("/authenticate", () => {
let request = setup.getRequest() let request = setup.getRequest()
let config = setup.getConfig() let config = setup.getConfig()
@ -10,88 +20,8 @@ describe("/authenticate", () => {
await config.init() await config.init()
}) })
describe("authenticate", () => {
it("should be able to create a layout", async () => {
await config.createUser("test@test.com", "p4ssw0rd")
const res = await request
.post(`/api/authenticate`)
.send({
email: "test@test.com",
password: "p4ssw0rd",
})
.set(config.publicHeaders())
.expect("Content-Type", /json/)
.expect(200)
expect(res.body.token).toBeDefined()
expect(res.body.email).toEqual("test@test.com")
expect(res.body.password).toBeUndefined()
})
it("should error if no app specified", async () => {
await request
.post(`/api/authenticate`)
.expect(400)
})
it("should error if no email specified", async () => {
await request
.post(`/api/authenticate`)
.send({
password: "test",
})
.set(config.publicHeaders())
.expect(400)
})
it("should error if no password specified", async () => {
await request
.post(`/api/authenticate`)
.send({
email: "test",
})
.set(config.publicHeaders())
.expect(400)
})
it("should error if invalid user specified", async () => {
await request
.post(`/api/authenticate`)
.send({
email: "test",
password: "test",
})
.set(config.publicHeaders())
.expect(401)
})
it("should throw same error if wrong password specified", async () => {
await config.createUser("test@test.com", "password")
await request
.post(`/api/authenticate`)
.send({
email: "test@test.com",
password: "test",
})
.set(config.publicHeaders())
.expect(401)
})
it("should throw an error for inactive users", async () => {
await config.createUser("test@test.com", "password")
await config.makeUserInactive("test@test.com")
await request
.post(`/api/authenticate`)
.send({
email: "test@test.com",
password: "password",
})
.set(config.publicHeaders())
.expect(401)
})
})
describe("fetch self", () => { describe("fetch self", () => {
it("should be able to delete the layout", async () => { it("should be able to fetch self", async () => {
await config.createUser("test@test.com", "p4ssw0rd") await config.createUser("test@test.com", "p4ssw0rd")
const headers = await config.login("test@test.com", "p4ssw0rd") const headers = await config.login("test@test.com", "p4ssw0rd")
const res = await request const res = await request

View file

@ -41,10 +41,12 @@ describe("run misc tests", () => {
const dataImport = { const dataImport = {
csvString: "a,b,c,d\n1,2,3,4" csvString: "a,b,c,d\n1,2,3,4"
} }
await tableUtils.handleDataImport({ await tableUtils.handleDataImport(
appId: config.getAppId(), config.getAppId(),
userId: "test", { userId: "test" },
}, table, dataImport) table,
dataImport
)
const rows = await config.getRows() const rows = await config.getRows()
expect(rows[0].a).toEqual("1") expect(rows[0].a).toEqual("1")
expect(rows[0].b).toEqual("2") expect(rows[0].b).toEqual("2")

View file

@ -2,6 +2,12 @@ const setup = require("./utilities")
const { basicScreen } = setup.structures const { basicScreen } = setup.structures
const { checkBuilderEndpoint } = require("./utilities/TestFunctions") const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles") const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles")
const workerRequests = require("../../../utilities/workerRequests")
jest.mock("../../../utilities/workerRequests", () => ({
getGlobalUsers: jest.fn(),
saveGlobalUser: jest.fn(),
}))
const route = "/test" const route = "/test"
@ -25,6 +31,13 @@ describe("/routing", () => {
describe("fetch", () => { describe("fetch", () => {
it("returns the correct routing for basic user", async () => { it("returns the correct routing for basic user", async () => {
workerRequests.getGlobalUsers.mockImplementationOnce((ctx, appId) => {
return {
roles: {
[appId]: BUILTIN_ROLE_IDS.BASIC,
}
}
})
const res = await request const res = await request
.get(`/api/routing/client`) .get(`/api/routing/client`)
.set(await config.roleHeaders("basic@test.com", BUILTIN_ROLE_IDS.BASIC)) .set(await config.roleHeaders("basic@test.com", BUILTIN_ROLE_IDS.BASIC))
@ -42,6 +55,13 @@ describe("/routing", () => {
}) })
it("returns the correct routing for power user", async () => { it("returns the correct routing for power user", async () => {
workerRequests.getGlobalUsers.mockImplementationOnce((ctx, appId) => {
return {
roles: {
[appId]: BUILTIN_ROLE_IDS.POWER,
}
}
})
const res = await request const res = await request
.get(`/api/routing/client`) .get(`/api/routing/client`)
.set(await config.roleHeaders("basic@test.com", BUILTIN_ROLE_IDS.POWER)) .set(await config.roleHeaders("basic@test.com", BUILTIN_ROLE_IDS.POWER))

View file

@ -2,6 +2,15 @@ const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles")
const { checkPermissionsEndpoint } = require("./utilities/TestFunctions") const { checkPermissionsEndpoint } = require("./utilities/TestFunctions")
const setup = require("./utilities") const setup = require("./utilities")
const { basicUser } = setup.structures const { basicUser } = setup.structures
const workerRequests = require("../../../utilities/workerRequests")
jest.mock("../../../utilities/workerRequests", () => ({
getGlobalUsers: jest.fn(),
saveGlobalUser: jest.fn(() => {
return {}
}),
deleteGlobalUser: jest.fn(),
}))
describe("/users", () => { describe("/users", () => {
let request = setup.getRequest() let request = setup.getRequest()
@ -14,11 +23,23 @@ describe("/users", () => {
}) })
describe("fetch", () => { describe("fetch", () => {
beforeEach(() => {
workerRequests.getGlobalUsers.mockImplementationOnce(() => ([
{
email: "brenda@brenda.com"
},
{
email: "pam@pam.com"
}
]
))
})
it("returns a list of users from an instance db", async () => { it("returns a list of users from an instance db", async () => {
await config.createUser("brenda@brenda.com", "brendas_password") await config.createUser("brenda@brenda.com", "brendas_password")
await config.createUser("pam@pam.com", "pam_password") await config.createUser("pam@pam.com", "pam_password")
const res = await request const res = await request
.get(`/api/users`) .get(`/api/users/metadata`)
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
.expect(200) .expect(200)
@ -34,7 +55,7 @@ describe("/users", () => {
config, config,
request, request,
method: "GET", method: "GET",
url: `/api/users`, url: `/api/users/metadata`,
passRole: BUILTIN_ROLE_IDS.ADMIN, passRole: BUILTIN_ROLE_IDS.ADMIN,
failRole: BUILTIN_ROLE_IDS.PUBLIC, failRole: BUILTIN_ROLE_IDS.PUBLIC,
}) })
@ -42,9 +63,21 @@ describe("/users", () => {
}) })
describe("create", () => { describe("create", () => {
beforeEach(() => {
workerRequests.getGlobalUsers.mockImplementationOnce(() => ([
{
email: "bill@budibase.com"
},
{
email: "brandNewUser@user.com"
}
]
))
})
async function create(user, status = 200) { async function create(user, status = 200) {
return request return request
.post(`/api/users`) .post(`/api/users/metadata`)
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.send(user) .send(user)
.expect(status) .expect(status)
@ -56,8 +89,8 @@ describe("/users", () => {
body.email = "bill@budibase.com" body.email = "bill@budibase.com"
const res = await create(body) const res = await create(body)
expect(res.res.statusMessage).toEqual("User created successfully.") expect(res.res.statusMessage).toEqual("OK")
expect(res.body._id).toBeUndefined() expect(res.body._id).toBeDefined()
}) })
it("should apply authorization to endpoint", async () => { it("should apply authorization to endpoint", async () => {
@ -67,18 +100,12 @@ describe("/users", () => {
config, config,
method: "POST", method: "POST",
body, body,
url: `/api/users`, url: `/api/users/metadata`,
passRole: BUILTIN_ROLE_IDS.ADMIN, passRole: BUILTIN_ROLE_IDS.ADMIN,
failRole: BUILTIN_ROLE_IDS.PUBLIC, failRole: BUILTIN_ROLE_IDS.PUBLIC,
}) })
}) })
it("should error if no email provided", async () => {
const user = basicUser(BUILTIN_ROLE_IDS.POWER)
delete user.email
await create(user, 400)
})
it("should error if no role provided", async () => { it("should error if no role provided", async () => {
const user = basicUser(null) const user = basicUser(null)
await create(user, 400) await create(user, 400)
@ -88,16 +115,22 @@ describe("/users", () => {
await config.createUser("test@test.com") await config.createUser("test@test.com")
const user = basicUser(BUILTIN_ROLE_IDS.POWER) const user = basicUser(BUILTIN_ROLE_IDS.POWER)
user.email = "test@test.com" user.email = "test@test.com"
await create(user, 400) await create(user, 409)
}) })
}) })
describe("update", () => { describe("update", () => {
beforeEach(() => {
workerRequests.saveGlobalUser.mockImplementationOnce(() => ({
_id: "us_test@test.com"
}))
})
it("should be able to update the user", async () => { it("should be able to update the user", async () => {
const user = await config.createUser() const user = await config.createUser()
user.roleId = BUILTIN_ROLE_IDS.BASIC user.roleId = BUILTIN_ROLE_IDS.BASIC
const res = await request const res = await request
.put(`/api/users`) .put(`/api/users/metadata`)
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.send(user) .send(user)
.expect(200) .expect(200)
@ -111,20 +144,29 @@ describe("/users", () => {
const email = "test@test.com" const email = "test@test.com"
await config.createUser(email) await config.createUser(email)
const res = await request const res = await request
.delete(`/api/users/${email}`) .delete(`/api/users/metadata/${email}`)
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.expect(200) .expect(200)
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
expect(res.body.message).toBeDefined() expect(res.body.message).toBeDefined()
expect(workerRequests.deleteGlobalUser).toHaveBeenCalled()
}) })
}) })
describe("find", () => { describe("find", () => {
beforeEach(() => {
jest.resetAllMocks()
workerRequests.getGlobalUsers.mockImplementationOnce(() => ({
email: "test@test.com",
roleId: BUILTIN_ROLE_IDS.POWER,
}))
})
it("should be able to find the user", async () => { it("should be able to find the user", async () => {
const email = "test@test.com" const email = "test@test.com"
await config.createUser(email) await config.createUser(email)
const res = await request const res = await request
.get(`/api/users/${email}`) .get(`/api/users/metadata/${email}`)
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.expect(200) .expect(200)
.expect("Content-Type", /json/) .expect("Content-Type", /json/)

View file

@ -3,7 +3,7 @@ const appController = require("../../../controllers/application")
const CouchDB = require("../../../../db") const CouchDB = require("../../../../db")
function Request(appId, params) { function Request(appId, params) {
this.user = { appId } this.appId = appId
this.params = params this.params = params
} }
@ -63,7 +63,11 @@ exports.checkPermissionsEndpoint = async ({
}) => { }) => {
const password = "PASSWORD" const password = "PASSWORD"
await config.createUser("passUser@budibase.com", password, passRole) await config.createUser("passUser@budibase.com", password, passRole)
const passHeader = await config.login("passUser@budibase.com", password) const passHeader = await config.login(
"passUser@budibase.com",
password,
passRole
)
await exports await exports
.createRequest(config.request, method, url, body) .createRequest(config.request, method, url, body)
@ -71,7 +75,11 @@ exports.checkPermissionsEndpoint = async ({
.expect(200) .expect(200)
await config.createUser("failUser@budibase.com", password, failRole) await config.createUser("failUser@budibase.com", password, failRole)
const failHeader = await config.login("failUser@budibase.com", password) const failHeader = await config.login(
"failUser@budibase.com",
password,
failRole
)
await exports await exports
.createRequest(config.request, method, url, body) .createRequest(config.request, method, url, body)

View file

@ -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

View file

@ -1,3 +1,7 @@
// need to load environment first
const env = require("./environment")
const CouchDB = require("./db")
require("@budibase/auth").init(CouchDB)
const Koa = require("koa") const Koa = require("koa")
const destroyable = require("server-destroy") const destroyable = require("server-destroy")
const electron = require("electron") const electron = require("electron")
@ -5,7 +9,6 @@ const koaBody = require("koa-body")
const logger = require("koa-pino-logger") const logger = require("koa-pino-logger")
const http = require("http") const http = require("http")
const api = require("./api") const api = require("./api")
const env = require("./environment")
const eventEmitter = require("./events") const eventEmitter = require("./events")
const automations = require("./automations/index") const automations = require("./automations/index")
const Sentry = require("@sentry/node") const Sentry = require("@sentry/node")

View file

@ -75,7 +75,7 @@ module.exports.run = async function({ inputs, appId, apiKey, emitter }) {
request: { request: {
body: inputs.row, body: inputs.row,
}, },
user: { appId }, appId,
eventEmitter: emitter, eventEmitter: emitter,
} }

View file

@ -61,9 +61,7 @@ module.exports.definition = {
module.exports.run = async function({ inputs, appId, apiKey, emitter }) { module.exports.run = async function({ inputs, appId, apiKey, emitter }) {
const { email, password, roleId } = inputs const { email, password, roleId } = inputs
const ctx = { const ctx = {
user: { appId,
appId: appId,
},
request: { request: {
body: { email, password, roleId }, body: { email, password, roleId },
}, },
@ -74,11 +72,11 @@ 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
id: ctx.userId, id: ctx.body._id,
revision: ctx.body._rev, revision: ctx.body._rev,
success: ctx.status === 200, success: ctx.status === 200,
} }

View file

@ -65,7 +65,7 @@ module.exports.run = async function({ inputs, appId, apiKey, emitter }) {
rowId: inputs.id, rowId: inputs.id,
revId: inputs.revision, revId: inputs.revision,
}, },
user: { appId }, appId,
eventEmitter: emitter, eventEmitter: emitter,
} }

View file

@ -78,7 +78,7 @@ module.exports.run = async function({ inputs, appId, emitter }) {
request: { request: {
body: inputs.row, body: inputs.row,
}, },
user: { appId }, appId,
eventEmitter: emitter, eventEmitter: emitter,
} }

View file

@ -1,10 +1,10 @@
require("../../environment")
const automation = require("../index") const automation = require("../index")
const usageQuota = require("../../utilities/usageQuota") const usageQuota = require("../../utilities/usageQuota")
const thread = require("../thread") const thread = require("../thread")
const triggers = require("../triggers") const triggers = require("../triggers")
const { basicAutomation, basicTable } = require("../../tests/utilities/structures") const { basicAutomation, basicTable } = require("../../tests/utilities/structures")
const { wait } = require("../../utilities") const { wait } = require("../../utilities")
const env = require("../../environment")
const { makePartial } = require("../../tests/utilities") const { makePartial } = require("../../tests/utilities")
const { cleanInputValues } = require("../automationUtils") const { cleanInputValues } = require("../automationUtils")
const setup = require("./utilities") const setup = require("./utilities")

View file

@ -26,6 +26,7 @@ describe("test the create row action", () => {
}) })
expect(res.id).toBeDefined() expect(res.id).toBeDefined()
expect(res.revision).toBeDefined() expect(res.revision).toBeDefined()
expect(res.success).toEqual(true)
const gottenRow = await config.getRow(table._id, res.id) const gottenRow = await config.getRow(table._id, res.id)
expect(gottenRow.name).toEqual("test") expect(gottenRow.name).toEqual("test")
expect(gottenRow.description).toEqual("test") expect(gottenRow.description).toEqual("test")

View file

@ -1,8 +1,7 @@
const usageQuota = require("../../utilities/usageQuota") const usageQuota = require("../../utilities/usageQuota")
const env = require("../../environment")
const setup = require("./utilities") const setup = require("./utilities")
const { BUILTIN_ROLE_IDS } = require("../../utilities/security/roles") const { BUILTIN_ROLE_IDS } = require("../../utilities/security/roles")
const { ViewNames } = require("../../db/utils") const { InternalTables } = require("../../db/utils")
jest.mock("../../utilities/usageQuota") jest.mock("../../utilities/usageQuota")
@ -25,8 +24,7 @@ describe("test the create user action", () => {
const res = await setup.runStep(setup.actions.CREATE_USER.stepId, user) const res = await setup.runStep(setup.actions.CREATE_USER.stepId, user)
expect(res.id).toBeDefined() expect(res.id).toBeDefined()
expect(res.revision).toBeDefined() expect(res.revision).toBeDefined()
const userDoc = await config.getRow(ViewNames.USERS, res.id) const userDoc = await config.getRow(InternalTables.USER_METADATA, res.id)
expect(userDoc.email).toEqual(user.email)
}) })
it("should return an error if no inputs provided", async () => { it("should return an error if no inputs provided", async () => {

View file

@ -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),
}, },
}, },
}, },

View file

@ -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
@ -112,17 +116,19 @@ exports.getRowParams = (tableId = null, rowId = null, otherProps = {}) => {
/** /**
* Gets a new row ID for the specified table. * Gets a new row ID for the specified table.
* @param {string} tableId The table which the row is being created for. * @param {string} tableId The table which the row is being created for.
* @param {string|null} id If an ID is to be used then the UUID can be substituted for this.
* @returns {string} The new ID which a row doc can be stored under. * @returns {string} The new ID which a row doc can be stored under.
*/ */
exports.generateRowID = tableId => { exports.generateRowID = (tableId, id = null) => {
return `${DocumentTypes.ROW}${SEPARATOR}${tableId}${SEPARATOR}${newid()}` id = id || newid()
return `${DocumentTypes.ROW}${SEPARATOR}${tableId}${SEPARATOR}${id}`
} }
/** /**
* Gets parameters for retrieving users, this is a utility function for the getDocParams function. * Gets parameters for retrieving users, this is a utility function for the getDocParams function.
*/ */
exports.getUserParams = (email = "", otherProps = {}) => { exports.getUserMetadataParams = (email = "", otherProps = {}) => {
return exports.getRowParams(ViewNames.USERS, email, otherProps) return exports.getRowParams(InternalTables.USER_METADATA, email, otherProps)
} }
/** /**
@ -130,8 +136,17 @@ exports.getUserParams = (email = "", otherProps = {}) => {
* @param {string} email The email which the ID is going to be built up of. * @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. * @returns {string} The new user ID which the user doc can be stored under.
*/ */
exports.generateUserID = email => { exports.generateUserMetadataID = email => {
return `${DocumentTypes.ROW}${SEPARATOR}${ViewNames.USERS}${SEPARATOR}${DocumentTypes.USER}${SEPARATOR}${email}` return exports.generateRowID(InternalTables.USER_METADATA, email)
}
/**
* Breaks up the ID to get the email address back out of it.
*/
exports.getEmailFromUserMetadataID = id => {
return id.split(
`${DocumentTypes.ROW}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}`
)[1]
} }
/** /**

View file

@ -1,73 +0,0 @@
const jwt = require("jsonwebtoken")
const STATUS_CODES = require("../utilities/statusCodes")
const { getRole, getBuiltinRoles } = require("../utilities/security/roles")
const { AuthTypes } = require("../constants")
const {
getAppId,
getCookieName,
clearCookie,
setCookie,
isClient,
} = require("../utilities")
module.exports = async (ctx, next) => {
if (ctx.path === "/builder") {
await next()
return
}
// do everything we can to make sure the appId is held correctly
// we hold it in state as a
let appId = getAppId(ctx)
const cookieAppId = ctx.cookies.get(getCookieName("currentapp"))
const builtinRoles = getBuiltinRoles()
if (appId && cookieAppId !== appId) {
setCookie(ctx, appId, "currentapp")
} else if (cookieAppId) {
appId = cookieAppId
}
let token, authType
if (!isClient(ctx)) {
token = ctx.cookies.get(getCookieName())
authType = AuthTypes.BUILDER
}
if (!token && appId) {
token = ctx.cookies.get(getCookieName(appId))
authType = AuthTypes.APP
}
if (!token) {
ctx.auth.authenticated = false
ctx.appId = appId
ctx.user = {
appId,
role: builtinRoles.PUBLIC,
}
await next()
return
}
try {
ctx.auth.authenticated = authType
const jwtPayload = jwt.verify(token, ctx.config.jwtSecret)
ctx.appId = appId
ctx.auth.apiKey = jwtPayload.apiKey
ctx.user = {
...jwtPayload,
appId: appId,
role: await getRole(appId, jwtPayload.roleId),
}
} catch (err) {
console.log(err)
if (authType === AuthTypes.BUILDER) {
clearCookie(ctx)
ctx.status = 200
return
} else {
ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text)
}
}
await next()
}

View file

@ -1,58 +1,41 @@
const { const { getUserPermissions } = require("../utilities/security/roles")
BUILTIN_ROLE_IDS,
getUserPermissions,
} = require("../utilities/security/roles")
const { const {
PermissionTypes, PermissionTypes,
doesHaveResourcePermission, doesHaveResourcePermission,
doesHaveBasePermission, doesHaveBasePermission,
} = require("../utilities/security/permissions") } = require("../utilities/security/permissions")
const env = require("../environment")
const { isAPIKeyValid } = require("../utilities/security/apikey")
const { AuthTypes } = require("../constants")
const ADMIN_ROLES = [BUILTIN_ROLE_IDS.ADMIN, BUILTIN_ROLE_IDS.BUILDER]
function hasResource(ctx) { function hasResource(ctx) {
return ctx.resourceId != null return ctx.resourceId != null
} }
module.exports = (permType, permLevel = null) => async (ctx, next) => { const WEBHOOK_ENDPOINTS = new RegExp(
if (env.isProd() && ctx.headers["x-api-key"] && ctx.headers["x-instanceid"]) { ["webhooks/trigger", "webhooks/schema"].join("|")
// api key header passed by external webhook )
if (await isAPIKeyValid(ctx.headers["x-api-key"])) {
ctx.auth = {
authenticated: AuthTypes.EXTERNAL,
apiKey: ctx.headers["x-api-key"],
}
ctx.user = {
appId: ctx.headers["x-instanceid"],
}
return next()
}
return ctx.throw(403, "API key invalid") module.exports = (permType, permLevel = null) => async (ctx, next) => {
// webhooks don't need authentication, each webhook unique
if (WEBHOOK_ENDPOINTS.test(ctx.request.url)) {
return next()
} }
if (!ctx.user) { if (!ctx.user) {
return ctx.throw(403, "No user info found") return ctx.throw(403, "No user info found")
} }
const role = ctx.user.role const isAuthed = ctx.isAuthenticated
const isAdmin = ADMIN_ROLES.includes(role._id)
const isAuthed = ctx.auth.authenticated
const { basePermissions, permissions } = await getUserPermissions( const { basePermissions, permissions } = await getUserPermissions(
ctx.appId, ctx.appId,
role._id ctx.roleId
) )
// this may need to change in the future, right now only admins // builders for now have permission to do anything
// can have access to builder features, this is hard coded into // TODO: in future should consider separating permissions with an require("@budibase/auth").isClient check
// our rules let isBuilder = ctx.user && ctx.user.builder && ctx.user.builder.global
if (isAdmin && isAuthed) { if (isBuilder) {
return next() return next()
} else if (permType === PermissionTypes.BUILDER) { } else if (permType === PermissionTypes.BUILDER && !isBuilder) {
return ctx.throw(403, "Not Authorized") return ctx.throw(403, "Not Authorized")
} }

View file

@ -0,0 +1,59 @@
const { getAppId, setCookie, getCookie, Cookies } = require("@budibase/auth")
const { getRole } = require("../utilities/security/roles")
const { generateUserMetadataID } = require("../db/utils")
const { getGlobalUsers } = require("../utilities/workerRequests")
const { BUILTIN_ROLE_IDS } = require("../utilities/security/roles")
module.exports = async (ctx, next) => {
// try to get the appID from the request
const requestAppId = getAppId(ctx)
// get app cookie if it exists
const appCookie = getCookie(ctx, Cookies.CurrentApp)
if (!appCookie && !requestAppId) {
return next()
}
let updateCookie = false,
appId,
roleId = BUILTIN_ROLE_IDS.PUBLIC
if (!ctx.user) {
// not logged in, try to set a cookie for public apps
updateCookie = true
appId = requestAppId
} else if (
requestAppId != null &&
(appCookie == null ||
requestAppId !== appCookie.appId ||
appCookie.roleId === BUILTIN_ROLE_IDS.PUBLIC)
) {
// Different App ID means cookie needs reset, or if the same public user has logged in
const globalUser = await getGlobalUsers(ctx, requestAppId, ctx.user.email)
updateCookie = true
appId = requestAppId
if (globalUser.roles && globalUser.roles[requestAppId]) {
roleId = globalUser.roles[requestAppId]
}
} else if (appCookie != null) {
appId = appCookie.appId
roleId = appCookie.roleId || BUILTIN_ROLE_IDS.PUBLIC
}
if (appId) {
ctx.appId = appId
if (roleId) {
const userId = ctx.user
? generateUserMetadataID(ctx.user.email)
: undefined
ctx.roleId = roleId
ctx.user = {
...ctx.user,
_id: userId,
userId,
role: await getRole(appId, roleId),
}
}
}
if (updateCookie && appId) {
setCookie(ctx, { appId, roleId }, Cookies.CurrentApp)
}
return next()
}

View file

@ -1,28 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Authenticated middleware sets the correct APP auth type information when the user is not in the builder 1`] = `
Object {
"apiKey": "1234",
"appId": "budibase:app:local",
"role": Role {
"_id": "ADMIN",
"inherits": "POWER",
"name": "Admin",
"permissionId": "admin",
},
"roleId": "ADMIN",
}
`;
exports[`Authenticated middleware sets the correct BUILDER auth type information when the x-budibase-type header is not 'client' 1`] = `
Object {
"apiKey": "1234",
"appId": "budibase:builder:local",
"role": Role {
"_id": "BUILDER",
"name": "Builder",
"permissionId": "admin",
},
"roleId": "BUILDER",
}
`;

View file

@ -1,124 +0,0 @@
const { AuthTypes } = require("../../constants")
const authenticatedMiddleware = require("../authenticated")
const jwt = require("jsonwebtoken")
jest.mock("jsonwebtoken")
class TestConfiguration {
constructor(middleware) {
this.middleware = authenticatedMiddleware
this.ctx = {
config: {},
auth: {},
cookies: {
set: jest.fn(),
get: jest.fn(),
},
headers: {},
params: {},
path: "",
request: {
headers: {},
},
throw: jest.fn(),
}
this.next = jest.fn()
}
setHeaders(headers) {
this.ctx.headers = headers
}
executeMiddleware() {
return this.middleware(this.ctx, this.next)
}
afterEach() {
jest.resetAllMocks()
}
}
describe("Authenticated middleware", () => {
let config
beforeEach(() => {
config = new TestConfiguration()
})
afterEach(() => {
config.afterEach()
})
it("calls next() when on the builder path", async () => {
config.ctx.path = "/builder"
await config.executeMiddleware()
expect(config.next).toHaveBeenCalled()
})
it("sets a new cookie when the current cookie does not match the app id from context", async () => {
const appId = "app_123"
config.setHeaders({
"x-budibase-app-id": appId,
})
config.ctx.cookies.get.mockImplementation(() => "cookieAppId")
await config.executeMiddleware()
expect(config.ctx.cookies.set).toHaveBeenCalledWith(
"budibase:currentapp:local",
appId,
expect.any(Object)
)
})
it("sets the correct BUILDER auth type information when the x-budibase-type header is not 'client'", async () => {
config.ctx.cookies.get.mockImplementation(() => "budibase:builder:local")
jwt.verify.mockImplementationOnce(() => ({
apiKey: "1234",
roleId: "BUILDER",
}))
await config.executeMiddleware()
expect(config.ctx.auth.authenticated).toEqual(AuthTypes.BUILDER)
expect(config.ctx.user).toMatchSnapshot()
})
it("sets the correct APP auth type information when the user is not in the builder", async () => {
config.setHeaders({
"x-budibase-type": "client",
})
config.ctx.cookies.get.mockImplementation(() => `budibase:app:local`)
jwt.verify.mockImplementationOnce(() => ({
apiKey: "1234",
roleId: "ADMIN",
}))
await config.executeMiddleware()
expect(config.ctx.auth.authenticated).toEqual(AuthTypes.APP)
expect(config.ctx.user).toMatchSnapshot()
})
it("marks the user as unauthenticated when a token cannot be determined from the users cookie", async () => {
config.executeMiddleware()
expect(config.ctx.auth.authenticated).toBe(false)
expect(config.ctx.user.role).toEqual({
_id: "PUBLIC",
name: "Public",
permissionId: "public",
})
})
it("clears the cookie when there is an error authenticating in the builder", async () => {
config.ctx.cookies.get.mockImplementation(() => "budibase:builder:local")
jwt.verify.mockImplementationOnce(() => {
throw new Error()
})
await config.executeMiddleware()
expect(config.ctx.cookies.set).toBeCalledWith("budibase:builder:local")
})
})

View file

@ -1,7 +1,5 @@
const authorizedMiddleware = require("../authorized") const authorizedMiddleware = require("../authorized")
const env = require("../../environment") const env = require("../../environment")
const apiKey = require("../../utilities/security/apikey")
const { AuthTypes } = require("../../constants")
const { PermissionTypes, PermissionLevels } = require("../../utilities/security/permissions") const { PermissionTypes, PermissionLevels } = require("../../utilities/security/permissions")
jest.mock("../../environment", () => ({ jest.mock("../../environment", () => ({
prod: false, prod: false,
@ -12,7 +10,6 @@ jest.mock("../../environment", () => ({
} }
}) })
) )
jest.mock("../../utilities/security/apikey")
class TestConfiguration { class TestConfiguration {
constructor(role) { constructor(role) {
@ -92,29 +89,6 @@ describe("Authorization middleware", () => {
"x-instanceid": "instance123", "x-instanceid": "instance123",
}) })
}) })
it("passes to next() if api key is valid", async () => {
apiKey.isAPIKeyValid.mockResolvedValueOnce(true)
await config.executeMiddleware()
expect(config.next).toHaveBeenCalled()
expect(config.ctx.auth).toEqual({
authenticated: AuthTypes.EXTERNAL,
apiKey: config.ctx.headers["x-api-key"],
})
expect(config.ctx.user).toEqual({
appId: config.ctx.headers["x-instanceid"],
})
})
it("throws if api key is invalid", async () => {
apiKey.isAPIKeyValid.mockResolvedValueOnce(false)
await config.executeMiddleware()
expect(config.throw).toHaveBeenCalledWith(403, "API key invalid")
})
}) })
describe("non-webhook call", () => { describe("non-webhook call", () => {

View file

@ -0,0 +1,149 @@
mockAuthWithNoCookie()
mockWorker()
function mockWorker() {
jest.mock("../../utilities/workerRequests", () => ({
getGlobalUsers: () => {
return {
email: "test@test.com",
roles: {
"app_test": "BASIC",
}
}
}
}))
}
function mockReset() {
jest.resetModules()
mockWorker()
}
function mockAuthWithNoCookie() {
jest.resetModules()
mockWorker()
jest.mock("@budibase/auth", () => ({
getAppId: jest.fn(),
setCookie: jest.fn(),
getCookie: jest.fn(),
Cookies: {},
}))
}
function mockAuthWithCookie() {
jest.resetModules()
mockWorker()
jest.mock("@budibase/auth", () => ({
getAppId: () => {
return "app_test"
},
setCookie: jest.fn(),
getCookie: () => ({ appId: "app_different", roleId: "PUBLIC" }),
Cookies: {
Auth: "auth",
CurrentApp: "currentapp",
}
}))
}
class TestConfiguration {
constructor() {
this.next = jest.fn()
this.throw = jest.fn()
this.ctx = {
next: this.next,
throw: this.throw
}
}
setUser() {
this.ctx.user = {
email: "test@test.com",
}
}
executeMiddleware() {
// import as late as possible for mocks
const currentAppMiddleware = require("../currentapp")
return currentAppMiddleware(this.ctx, this.next)
}
}
describe("Current app middleware", () => {
let config
beforeEach(() => {
config = new TestConfiguration()
})
afterEach(() => {
jest.clearAllMocks()
})
describe("test having no cookies or app ID", () => {
it("should be able to proceed with nothing setup", async () => {
await config.executeMiddleware()
expect(config.next).toHaveBeenCalled()
})
})
describe("check get public for app when not logged in", () => {
it("should be able to proceed with no login, but cookies configured", async () => {
mockAuthWithCookie()
await config.executeMiddleware()
expect(config.ctx.roleId).toEqual("PUBLIC")
expect(config.ctx.appId).toEqual("app_test")
expect(config.next).toHaveBeenCalled()
})
})
describe("check functionality when logged in", () => {
async function checkExpected(setCookie) {
config.setUser()
await config.executeMiddleware()
const cookieFn = require("@budibase/auth").setCookie
if (setCookie) {
expect(cookieFn).toHaveBeenCalled()
} else {
expect(cookieFn).not.toHaveBeenCalled()
}
expect(config.ctx.roleId).toEqual("BASIC")
expect(config.ctx.user.role._id).toEqual("BASIC")
expect(config.ctx.appId).toEqual("app_test")
expect(config.next).toHaveBeenCalled()
}
it("should be able to setup an app token when cookie not setup", async () => {
mockAuthWithCookie()
await checkExpected(true)
})
it("should perform correct when no cookie exists", async () => {
mockReset()
jest.mock("@budibase/auth", () => ({
getAppId: () => {
return "app_test"
},
setCookie: jest.fn(),
getCookie: jest.fn(),
Cookies: {},
}))
await checkExpected(true)
})
it("lastly check what occurs when cookie doesn't need updated", async () => {
mockReset()
jest.mock("@budibase/auth", () => ({
getAppId: () => {
return "app_test"
},
setCookie: jest.fn(),
getCookie: () => ({ appId: "app_test", roleId: "BASIC" }),
Cookies: {},
}))
await checkExpected(false)
})
})
})

View file

@ -20,9 +20,7 @@ class TestConfiguration {
this.ctx = { this.ctx = {
throw: this.throw, throw: this.throw,
next: this.next, next: this.next,
user: { appId: "test",
appId: "test"
},
request: { request: {
body: {} body: {}
}, },
@ -78,8 +76,10 @@ describe("usageQuota middleware", () => {
}) })
it("passes through to next middleware if document already exists", async () => { it("passes through to next middleware if document already exists", async () => {
config.setProd(true)
config.setBody({ config.setBody({
_id: "test" _id: "test",
_rev: "test",
}) })
CouchDB.mockImplementationOnce(() => ({ CouchDB.mockImplementationOnce(() => ({
@ -89,13 +89,14 @@ describe("usageQuota middleware", () => {
await config.executeMiddleware() await config.executeMiddleware()
expect(config.next).toHaveBeenCalled() expect(config.next).toHaveBeenCalled()
expect(config.ctx.preExisting).toBe(true)
}) })
it("throws if request has _id, but the document no longer exists", async () => { it("throws if request has _id, but the document no longer exists", async () => {
config.setBody({ config.setBody({
_id: "123" _id: "123",
_rev: "test",
}) })
config.setProd(true)
CouchDB.mockImplementationOnce(() => ({ CouchDB.mockImplementationOnce(() => ({
get: async () => { get: async () => {

View file

@ -27,27 +27,27 @@ function getProperty(url) {
} }
module.exports = async (ctx, next) => { module.exports = async (ctx, next) => {
const db = new CouchDB(ctx.user.appId) // if in development or a self hosted cloud usage quotas should not be executed
if (env.isDev() || env.SELF_HOSTED) {
return next()
}
const db = new CouchDB(ctx.appId)
let usage = METHOD_MAP[ctx.req.method] let usage = METHOD_MAP[ctx.req.method]
const property = getProperty(ctx.req.url) const property = getProperty(ctx.req.url)
if (usage == null || property == null) { if (usage == null || property == null) {
return next() return next()
} }
// post request could be a save of a pre-existing entry // post request could be a save of a pre-existing entry
if (ctx.request.body && ctx.request.body._id) { if (ctx.request.body && ctx.request.body._id && ctx.request.body._rev) {
try { try {
ctx.preExisting = await db.get(ctx.request.body._id) await db.get(ctx.request.body._id)
return next() return next()
} catch (err) { } catch (err) {
ctx.throw(404, `${ctx.request.body._id} does not exist`) ctx.throw(404, `${ctx.request.body._id} does not exist`)
return
} }
} }
// if in development or a self hosted cloud usage quotas should not be executed
if (env.isDev() || env.SELF_HOSTED) {
return next()
}
// update usage for uploads to be the total size // update usage for uploads to be the total size
if (property === usageQuota.Properties.UPLOAD) { if (property === usageQuota.Properties.UPLOAD) {
const files = const files =

View file

@ -15,6 +15,7 @@ const {
const controllers = require("./controllers") const controllers = require("./controllers")
const supertest = require("supertest") const supertest = require("supertest")
const { cleanup } = require("../../utilities/fileSystem") const { cleanup } = require("../../utilities/fileSystem")
const { Cookies } = require("@budibase/auth")
const EMAIL = "babs@babs.com" const EMAIL = "babs@babs.com"
const PASSWORD = "babs_password" const PASSWORD = "babs_password"
@ -68,16 +69,25 @@ class TestConfiguration {
} }
defaultHeaders() { defaultHeaders() {
const builderUser = { const user = {
userId: "BUILDER", userId: "us_test@test.com",
roleId: BUILTIN_ROLE_IDS.BUILDER, email: "test@test.com",
builder: {
global: true,
},
} }
const builderToken = jwt.sign(builderUser, env.JWT_SECRET) const app = {
// can be "production" for test case roleId: BUILTIN_ROLE_IDS.BUILDER,
const type = env.isProd() ? "cloud" : "local" appId: this.appId,
}
const authToken = jwt.sign(user, env.JWT_SECRET)
const appToken = jwt.sign(app, env.JWT_SECRET)
const headers = { const headers = {
Accept: "application/json", Accept: "application/json",
Cookie: [`budibase:builder:${type}=${builderToken}`], Cookie: [
`${Cookies.Auth}=${authToken}`,
`${Cookies.CurrentApp}=${appToken}`,
],
} }
if (this.appId) { if (this.appId) {
headers["x-budibase-app-id"] = this.appId headers["x-budibase-app-id"] = this.appId
@ -101,7 +111,7 @@ class TestConfiguration {
} catch (err) { } catch (err) {
// allow errors here // allow errors here
} }
return this.login(email, PASSWORD) return this.login(email, PASSWORD, roleId)
} }
async createApp(appName) { async createApp(appName) {
@ -279,7 +289,7 @@ class TestConfiguration {
roleId, roleId,
}, },
null, null,
controllers.user.create controllers.user.createMetadata
) )
} }
@ -289,7 +299,7 @@ class TestConfiguration {
{ {
email, email,
}, },
controllers.user.find controllers.user.findMetadata
) )
return this._req( return this._req(
{ {
@ -297,30 +307,35 @@ class TestConfiguration {
status: "inactive", status: "inactive",
}, },
null, null,
controllers.user.update controllers.user.updateMetadata
) )
} }
async login(email, password) { async login(email, password, roleId = BUILTIN_ROLE_IDS.BUILDER) {
if (!this.request) { if (!this.request) {
throw "Server has not been opened, cannot login." throw "Server has not been opened, cannot login."
} }
if (!email || !password) { if (!email || !password) {
await this.createUser() await this.createUser()
email = EMAIL
password = PASSWORD
} }
const result = await this.request const user = {
.post(`/api/authenticate`) userId: `us_${email || EMAIL}`,
.set({ email: email || EMAIL,
"x-budibase-app-id": this.appId, }
}) const app = {
.send({ email, password }) roleId: roleId,
appId: this.appId,
}
const authToken = jwt.sign(user, env.JWT_SECRET)
const appToken = jwt.sign(app, env.JWT_SECRET)
// returning necessary request headers // returning necessary request headers
return { return {
Accept: "application/json", Accept: "application/json",
Cookie: result.headers["set-cookie"], Cookie: [
`${Cookies.Auth}=${authToken}`,
`${Cookies.CurrentApp}=${appToken}`,
],
"x-budibase-app-id": this.appId, "x-budibase-app-id": this.appId,
} }
} }

View file

@ -1,3 +1,5 @@
// TODO: REMOVE
const bcrypt = require("bcryptjs") const bcrypt = require("bcryptjs")
const env = require("../environment") const env = require("../environment")

Some files were not shown because too many files have changed in this diff Show more