forked from tsai/budibase
committed by
GitHub
127 changed files with 3620 additions and 1126 deletions
@ -0,0 +1 @@ |
|||
packages/server/builder/**/*.js |
|||
@ -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.* |
|||
|
|||
@ -0,0 +1 @@ |
|||
# Budibase Authentication Library |
|||
@ -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" |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
exports.UserStatus = { |
|||
ACTIVE: "active", |
|||
INACTIVE: "inactive", |
|||
} |
|||
|
|||
exports.Cookies = { |
|||
CurrentApp: "budibase:currentapp", |
|||
Auth: "budibase:auth", |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
module.exports.setDB = pouch => { |
|||
module.exports.CouchDB = pouch |
|||
} |
|||
|
|||
module.exports.CouchDB = null |
|||
@ -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}`, |
|||
} |
|||
} |
|||
@ -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, |
|||
} |
|||
@ -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) |
|||
} |
|||
@ -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, |
|||
} |
|||
@ -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) |
|||
} |
|||
} |
|||
@ -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, |
|||
} |
|||
@ -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))
|
|||
// }
|
|||
@ -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) |
|||
} |
|||
} |
|||
@ -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) |
|||
} |
|||
} |
|||
@ -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" |
|||
} |
|||
@ -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" |
|||
@ -1,3 +1,3 @@ |
|||
Cypress.Cookies.defaults({ |
|||
preserve: "budibase:builder:local", |
|||
preserve: "budibase:auth", |
|||
}) |
|||
|
|||
@ -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;` |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -0,0 +1 @@ |
|||
export { LoginForm } from "./LoginForm.svelte" |
|||
@ -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> |
|||
@ -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() |
|||
@ -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") |
|||
} |
|||
@ -1,115 +1,110 @@ |
|||
const CouchDB = require("../../db") |
|||
const bcrypt = require("../../utilities/bcrypt") |
|||
const { generateUserID, getUserParams, ViewNames } = require("../../db/utils") |
|||
const { |
|||
generateUserMetadataID, |
|||
getUserMetadataParams, |
|||
getEmailFromUserMetadataID, |
|||
} = require("../../db/utils") |
|||
const { InternalTables } = require("../../db/utils") |
|||
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) { |
|||
const database = new CouchDB(ctx.user.appId) |
|||
const users = ( |
|||
exports.fetchMetadata = async function(ctx) { |
|||
const database = new CouchDB(ctx.appId) |
|||
const global = await getGlobalUsers(ctx, ctx.appId) |
|||
const metadata = ( |
|||
await database.allDocs( |
|||
getUserParams(null, { |
|||
getUserMetadataParams(null, { |
|||
include_docs: true, |
|||
}) |
|||
) |
|||
).rows.map(row => row.doc) |
|||
// user hashed password shouldn't ever be returned
|
|||
for (let user of users) { |
|||
delete user.password |
|||
const users = [] |
|||
for (let user of global) { |
|||
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 |
|||
} |
|||
|
|||
exports.create = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
const { email, password, roleId } = ctx.request.body |
|||
|
|||
if (!email || !password) { |
|||
ctx.throw(400, "email and Password Required.") |
|||
} |
|||
|
|||
const role = await getRole(ctx.user.appId, roleId) |
|||
exports.createMetadata = async function(ctx) { |
|||
const appId = ctx.appId |
|||
const db = new CouchDB(appId) |
|||
const { roleId } = ctx.request.body |
|||
const email = ctx.request.body.email || ctx.user.email |
|||
|
|||
// check role valid
|
|||
const role = await getRole(appId, roleId) |
|||
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 = { |
|||
...ctx.request.body, |
|||
// these must all be after the object spread, make sure
|
|||
// any values are overwritten, generateUserID will always
|
|||
// generate the same ID for the user as it is not UUID based
|
|||
_id: generateUserID(email), |
|||
...metadata, |
|||
_id: generateUserMetadataID(email), |
|||
type: "user", |
|||
password: hashedPassword, |
|||
tableId: ViewNames.USERS, |
|||
} |
|||
// add the active status to a user if its not provided
|
|||
if (user.status == null) { |
|||
user.status = UserStatus.ACTIVE |
|||
tableId: InternalTables.USER_METADATA, |
|||
} |
|||
|
|||
try { |
|||
const response = await db.post(user) |
|||
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) |
|||
} |
|||
const response = await db.post(user) |
|||
// for automations to make it obvious was successful
|
|||
ctx.status = 200 |
|||
ctx.body = { |
|||
_id: response.id, |
|||
_rev: response.rev, |
|||
email, |
|||
} |
|||
} |
|||
|
|||
exports.update = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
exports.updateMetadata = async function(ctx) { |
|||
const appId = ctx.appId |
|||
const db = new CouchDB(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) |
|||
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 (user.password) { |
|||
user.password = await bcrypt.hash(user.password) |
|||
} else { |
|||
delete user.password |
|||
if (!metadata._rev) { |
|||
metadata._rev = ctx.request.body._rev |
|||
} |
|||
|
|||
const response = await db.put({ |
|||
password: dbUser.password, |
|||
...user, |
|||
ctx.body = await db.put({ |
|||
...metadata, |
|||
}) |
|||
user._rev = response.rev |
|||
|
|||
ctx.status = 200 |
|||
ctx.body = response |
|||
} |
|||
|
|||
exports.destroy = async function(ctx) { |
|||
const database = new CouchDB(ctx.user.appId) |
|||
await database.destroy(generateUserID(ctx.params.email)) |
|||
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 = { |
|||
message: `User ${ctx.params.email} deleted.`, |
|||
} |
|||
ctx.status = 200 |
|||
} |
|||
|
|||
exports.find = async function(ctx) { |
|||
const database = new CouchDB(ctx.user.appId) |
|||
let lookup = ctx.params.email |
|||
? generateUserID(ctx.params.email) |
|||
: ctx.params.userId |
|||
const user = await database.get(lookup) |
|||
if (user) { |
|||
delete user.password |
|||
} |
|||
ctx.body = user |
|||
exports.findMetadata = async function(ctx) { |
|||
ctx.body = await getFullUser({ |
|||
ctx, |
|||
email: ctx.params.email, |
|||
userId: ctx.params.userId, |
|||
}) |
|||
} |
|||
|
|||
@ -1,14 +1,8 @@ |
|||
const Router = require("@koa/router") |
|||
const controller = require("../controllers/auth") |
|||
const authorized = require("../../middleware/authorized") |
|||
const { BUILDER } = require("../../utilities/security/permissions") |
|||
|
|||
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) |
|||
|
|||
module.exports = router |
|||
|
|||
@ -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 |
|||
@ -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() |
|||
} |
|||
@ -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() |
|||
} |
|||
@ -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", |
|||
} |
|||
`; |
|||
@ -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") |
|||
}) |
|||
}) |
|||
@ -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) |
|||
}) |
|||
}) |
|||
|
|||
}) |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue