diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..f4dd4c7b5d --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +packages/server/builder/**/*.js diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index 54f8468c14..5e21cc9efd 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -35,9 +35,10 @@ services: environment: SELF_HOSTED: 1 PORT: 4003 + JWT_SECRET: ${JWT_SECRET} MINIO_ACCESS_KEY: ${MINIO_ACCESS_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_PASSWORD: ${COUCH_DB_PASSWORD} COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984 diff --git a/hosting/envoy.dev.yaml.hbs b/hosting/envoy.dev.yaml.hbs index f7f642a244..a4e2a97118 100644 --- a/hosting/envoy.dev.yaml.hbs +++ b/hosting/envoy.dev.yaml.hbs @@ -26,6 +26,10 @@ static_resources: cluster: redis-service prefix_rewrite: "/" + - match: { prefix: "/api/admin/" } + route: + cluster: worker-dev + - match: { prefix: "/api/" } route: cluster: server-dev @@ -42,6 +46,10 @@ static_resources: route: cluster: builder-dev 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 # best, minio + AWS SDK doesn't handle path proxy @@ -123,3 +131,17 @@ static_resources: address: {{ address }} 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 + diff --git a/hosting/envoy.yaml b/hosting/envoy.yaml index 8c6081d1a7..1fbd2070ff 100644 --- a/hosting/envoy.yaml +++ b/hosting/envoy.yaml @@ -25,6 +25,11 @@ static_resources: - match: { path: "/" } route: 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 - match: { prefix: "/api/" } diff --git a/package.json b/package.json index b2f572d202..a4b0993fde 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "clean": "lerna clean", "kill-port": "kill-port 4001", "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", "lint": "eslint packages", "lint:fix": "eslint --fix packages", diff --git a/packages/auth/.gitignore b/packages/auth/.gitignore new file mode 100644 index 0000000000..2528ad91a4 --- /dev/null +++ b/packages/auth/.gitignore @@ -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.* + diff --git a/packages/auth/README.md b/packages/auth/README.md new file mode 100644 index 0000000000..bbe704026a --- /dev/null +++ b/packages/auth/README.md @@ -0,0 +1 @@ +# Budibase Authentication Library \ No newline at end of file diff --git a/packages/auth/package.json b/packages/auth/package.json new file mode 100644 index 0000000000..294b09321d --- /dev/null +++ b/packages/auth/package.json @@ -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" + } +} diff --git a/packages/auth/src/constants.js b/packages/auth/src/constants.js new file mode 100644 index 0000000000..b6946a543b --- /dev/null +++ b/packages/auth/src/constants.js @@ -0,0 +1,9 @@ +exports.UserStatus = { + ACTIVE: "active", + INACTIVE: "inactive", +} + +exports.Cookies = { + CurrentApp: "budibase:currentapp", + Auth: "budibase:auth", +} diff --git a/packages/auth/src/db/index.js b/packages/auth/src/db/index.js new file mode 100644 index 0000000000..9ae48e68b1 --- /dev/null +++ b/packages/auth/src/db/index.js @@ -0,0 +1,5 @@ +module.exports.setDB = pouch => { + module.exports.CouchDB = pouch +} + +module.exports.CouchDB = null diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js new file mode 100644 index 0000000000..043987fcc4 --- /dev/null +++ b/packages/auth/src/db/utils.js @@ -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}`, + } +} diff --git a/packages/auth/src/environment.js b/packages/auth/src/environment.js new file mode 100644 index 0000000000..e6d7ddda65 --- /dev/null +++ b/packages/auth/src/environment.js @@ -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, +} diff --git a/packages/auth/src/hashing.js b/packages/auth/src/hashing.js new file mode 100644 index 0000000000..0711ae67bf --- /dev/null +++ b/packages/auth/src/hashing.js @@ -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) +} diff --git a/packages/auth/src/index.js b/packages/auth/src/index.js new file mode 100644 index 0000000000..705e07f813 --- /dev/null +++ b/packages/auth/src/index.js @@ -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, +} diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js new file mode 100644 index 0000000000..af2c7d5575 --- /dev/null +++ b/packages/auth/src/middleware/authenticated.js @@ -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) + } +} diff --git a/packages/auth/src/middleware/index.js b/packages/auth/src/middleware/index.js new file mode 100644 index 0000000000..519233eda4 --- /dev/null +++ b/packages/auth/src/middleware/index.js @@ -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, +} diff --git a/packages/auth/src/middleware/passport/google.js b/packages/auth/src/middleware/passport/google.js new file mode 100644 index 0000000000..05b435aedd --- /dev/null +++ b/packages/auth/src/middleware/passport/google.js @@ -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)) +// } diff --git a/packages/auth/src/middleware/passport/jwt.js b/packages/auth/src/middleware/passport/jwt.js new file mode 100644 index 0000000000..fdff3f3cfc --- /dev/null +++ b/packages/auth/src/middleware/passport/jwt.js @@ -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) + } +} diff --git a/packages/auth/src/middleware/passport/local.js b/packages/auth/src/middleware/passport/local.js new file mode 100644 index 0000000000..78ee2eafff --- /dev/null +++ b/packages/auth/src/middleware/passport/local.js @@ -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) + } +} diff --git a/packages/auth/src/utils.js b/packages/auth/src/utils.js new file mode 100644 index 0000000000..5f0a135a45 --- /dev/null +++ b/packages/auth/src/utils.js @@ -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" +} diff --git a/packages/auth/yarn.lock b/packages/auth/yarn.lock new file mode 100644 index 0000000000..6771306258 --- /dev/null +++ b/packages/auth/yarn.lock @@ -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" diff --git a/packages/builder/cypress/integration/createApp.spec.js b/packages/builder/cypress/integration/createApp.spec.js index 2cc3dd1ae1..34f152b540 100644 --- a/packages/builder/cypress/integration/createApp.spec.js +++ b/packages/builder/cypress/integration/createApp.spec.js @@ -1,5 +1,6 @@ context("Create an Application", () => { it("should create a new application", () => { + cy.login() cy.createTestApp() cy.visit(`localhost:${Cypress.env("PORT")}/builder`) cy.contains("Cypress Tests").should("exist") diff --git a/packages/builder/cypress/integration/createAutomation.spec.js b/packages/builder/cypress/integration/createAutomation.spec.js index 36db9797c3..b8aab9d52e 100644 --- a/packages/builder/cypress/integration/createAutomation.spec.js +++ b/packages/builder/cypress/integration/createAutomation.spec.js @@ -1,5 +1,6 @@ context("Create a automation", () => { before(() => { + cy.login() cy.createTestApp() }) diff --git a/packages/builder/cypress/integration/createBinding.spec.js b/packages/builder/cypress/integration/createBinding.spec.js index d901dc608d..e35abc9dc3 100644 --- a/packages/builder/cypress/integration/createBinding.spec.js +++ b/packages/builder/cypress/integration/createBinding.spec.js @@ -1,5 +1,6 @@ context("Create Bindings", () => { before(() => { + cy.login() cy.createTestApp() cy.navigateToFrontend() }) @@ -9,7 +10,7 @@ context("Create Bindings", () => { addSettingBinding("text", "Current User._id") cy.getComponent(componentId).should( "have.text", - `ro_ta_users_us_test@test.com` + `ro_ta_users_test@test.com` ) }) }) diff --git a/packages/builder/cypress/integration/createComponents.spec.js b/packages/builder/cypress/integration/createComponents.spec.js index 3fc61f8d1a..8c63d85575 100644 --- a/packages/builder/cypress/integration/createComponents.spec.js +++ b/packages/builder/cypress/integration/createComponents.spec.js @@ -2,6 +2,7 @@ context("Create Components", () => { let headlineId before(() => { + cy.login() cy.createTestApp() cy.createTable("dog") cy.addColumn("dog", "name", "string") diff --git a/packages/builder/cypress/integration/createScreen.js b/packages/builder/cypress/integration/createScreen.js index c658ab51e1..4e166daaec 100644 --- a/packages/builder/cypress/integration/createScreen.js +++ b/packages/builder/cypress/integration/createScreen.js @@ -1,5 +1,6 @@ context("Screen Tests", () => { before(() => { + cy.login() cy.createTestApp() cy.navigateToFrontend() }) diff --git a/packages/builder/cypress/integration/createTable.spec.js b/packages/builder/cypress/integration/createTable.spec.js index bbdb2e67fe..f37b445ab2 100644 --- a/packages/builder/cypress/integration/createTable.spec.js +++ b/packages/builder/cypress/integration/createTable.spec.js @@ -1,5 +1,6 @@ context("Create a Table", () => { before(() => { + cy.login() cy.createTestApp() }) diff --git a/packages/builder/cypress/integration/createUser.spec.js b/packages/builder/cypress/integration/createUser.spec.js index 0beaf5b80f..54baec79a5 100644 --- a/packages/builder/cypress/integration/createUser.spec.js +++ b/packages/builder/cypress/integration/createUser.spec.js @@ -1,5 +1,6 @@ context("Create a User", () => { before(() => { + cy.login() cy.createTestApp() }) diff --git a/packages/builder/cypress/integration/createView.spec.js b/packages/builder/cypress/integration/createView.spec.js index 2d4bd121cb..6c8aa2004d 100644 --- a/packages/builder/cypress/integration/createView.spec.js +++ b/packages/builder/cypress/integration/createView.spec.js @@ -1,5 +1,6 @@ context("Create a View", () => { before(() => { + cy.login() cy.createTestApp() cy.createTable("data") cy.addColumn("data", "group", "Text") diff --git a/packages/builder/cypress/setup.js b/packages/builder/cypress/setup.js index 7b849eb887..c0d0dfa110 100644 --- a/packages/builder/cypress/setup.js +++ b/packages/builder/cypress/setup.js @@ -3,13 +3,16 @@ const path = require("path") 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.NODE_ENV = "cypress" 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.COUCH_URL = `leveldb://${tmpdir}/.data/` process.env.SELF_HOSTED = 1 +process.env.WORKER_URL = "http://localhost:4002/" process.env.MINIO_URL = "http://localhost:10000/" process.env.MINIO_ACCESS_KEY = "budibase" process.env.MINIO_SECRET_KEY = "budibase" @@ -25,18 +28,12 @@ async function run() { // dont make this a variable or top level require // it will cause environment module to be loaded prematurely 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")) + worker.on("close", () => console.log("Worker Closed")) } 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)) diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index 9cebfd81dc..eb34921aff 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -24,6 +24,23 @@ // -- This will overwrite an existing command -- // 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 => { cy.visit(`localhost:${Cypress.env("PORT")}/builder`) // wait for init API calls on visit @@ -44,12 +61,6 @@ Cypress.Commands.add("createApp", name => { .type(name) .should("have.value", name) 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.get("[data-cy=new-table]", { timeout: 20000, diff --git a/packages/builder/cypress/support/cookies.js b/packages/builder/cypress/support/cookies.js index 1245f84960..3e2fba6481 100644 --- a/packages/builder/cypress/support/cookies.js +++ b/packages/builder/cypress/support/cookies.js @@ -1,3 +1,3 @@ Cypress.Cookies.defaults({ - preserve: "budibase:builder:local", + preserve: "budibase:auth", }) diff --git a/packages/builder/src/builderStore/api.js b/packages/builder/src/builderStore/api.js index 8b5206da93..2e683238bc 100644 --- a/packages/builder/src/builderStore/api.js +++ b/packages/builder/src/builderStore/api.js @@ -1,5 +1,6 @@ import { store } from "./index" import { get as svelteGet } from "svelte/store" +import { removeCookie, Cookies } from "./cookies" const apiCall = method => async ( url, @@ -8,11 +9,15 @@ const apiCall = method => async ( ) => { headers["x-budibase-app-id"] = svelteGet(store).appId const json = headers["Content-Type"] === "application/json" - return await fetch(url, { + const resp = await fetch(url, { method: method, body: json ? JSON.stringify(body) : body, headers, }) + if (resp.status === 403) { + removeCookie(Cookies.Auth) + } + return resp } export const post = apiCall("POST") @@ -20,9 +25,6 @@ export const get = apiCall("GET") export const patch = apiCall("PATCH") export const del = apiCall("DELETE") export const put = apiCall("PUT") -export const getBuilderCookie = async () => { - await post("/api/builder/login", {}) -} export default { post: apiCall("POST"), @@ -30,5 +32,4 @@ export default { patch: apiCall("PATCH"), delete: apiCall("DELETE"), put: apiCall("PUT"), - getBuilderCookie, } diff --git a/packages/builder/src/builderStore/cookies.js b/packages/builder/src/builderStore/cookies.js new file mode 100644 index 0000000000..a84f1a4f20 --- /dev/null +++ b/packages/builder/src/builderStore/cookies.js @@ -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;` + } +} diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js index 48f466169b..6fecda84c0 100644 --- a/packages/builder/src/builderStore/index.js +++ b/packages/builder/src/builderStore/index.js @@ -6,7 +6,6 @@ import { derived, writable } from "svelte/store" import analytics from "analytics" import { FrontendTypes, LAYOUT_NAMES } from "../constants" import { findComponent } from "./storeUtils" -import { getBuilderCookie } from "./api" export const store = getFrontendStore() export const automationStore = getAutomationStore() @@ -58,8 +57,6 @@ export const selectedAccessRole = writable("BASIC") export const initialise = async () => { try { - // TODO this needs to be replaced by a real login - await getBuilderCookie() await analytics.activate() analytics.captureEvent("Builder Started") } catch (err) { diff --git a/packages/builder/src/components/backend/DataTable/api.js b/packages/builder/src/components/backend/DataTable/api.js index 91ebc19b26..43c6856540 100644 --- a/packages/builder/src/components/backend/DataTable/api.js +++ b/packages/builder/src/components/backend/DataTable/api.js @@ -1,7 +1,7 @@ import api from "builderStore/api" 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) return await response.json() } @@ -15,8 +15,7 @@ export async function saveRow(row, tableId) { export async function deleteRow(row) { const DELETE_ROWS_URL = `/api/${row.tableId}/rows/${row._id}/${row._rev}` - const response = await api.delete(DELETE_ROWS_URL) - return response + return api.delete(DELETE_ROWS_URL) } export async function fetchDataForView(view) { diff --git a/packages/builder/src/components/login/LoginForm.svelte b/packages/builder/src/components/login/LoginForm.svelte new file mode 100644 index 0000000000..7e32efb7c5 --- /dev/null +++ b/packages/builder/src/components/login/LoginForm.svelte @@ -0,0 +1,56 @@ + + +
+ + + + + + + + + + + + diff --git a/packages/builder/src/components/login/index.js b/packages/builder/src/components/login/index.js new file mode 100644 index 0000000000..6a35637cea --- /dev/null +++ b/packages/builder/src/components/login/index.js @@ -0,0 +1 @@ +export { LoginForm } from "./LoginForm.svelte" diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte index babe7bd0df..fcd5379733 100644 --- a/packages/builder/src/components/start/CreateAppModal.svelte +++ b/packages/builder/src/components/start/CreateAppModal.svelte @@ -25,10 +25,6 @@ applicationName: string().required("Your application must have a name."), } 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."), } @@ -153,11 +149,9 @@ // Create user const user = { - email: $createAppStore.values.email, - password: $createAppStore.values.password, 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() $goto(`./${appJson._id}`) } catch (error) { diff --git a/packages/builder/src/components/start/LogoutButton.svelte b/packages/builder/src/components/start/LogoutButton.svelte new file mode 100644 index 0000000000..732b34e533 --- /dev/null +++ b/packages/builder/src/components/start/LogoutButton.svelte @@ -0,0 +1,28 @@ + + +
+ +
+ + diff --git a/packages/builder/src/components/start/Steps/User.svelte b/packages/builder/src/components/start/Steps/User.svelte index 397a06be94..6bc4db711a 100644 --- a/packages/builder/src/components/start/Steps/User.svelte +++ b/packages/builder/src/components/start/Steps/User.svelte @@ -1,26 +1,9 @@ -

Create your first User

+

What's your role for this app?

- (blurred.email = true)} - label="Email" - name="email" - placeholder="Email" - type="email" - error={blurred.email && validationErrors.email} /> - (blurred.password = true)} - label="Password" - name="password" - placeholder="Password" - type="password" - error={blurred.password && validationErrors.password} />