diff --git a/hosting/docker-compose.dev.yaml b/hosting/docker-compose.dev.yaml index 9b4c353981..3b99ef796c 100644 --- a/hosting/docker-compose.dev.yaml +++ b/hosting/docker-compose.dev.yaml @@ -59,6 +59,7 @@ services: container_name: budi-redis-dev restart: always image: redis + command: redis-server --requirepass ${REDIS_PASSWORD} ports: - "${REDIS_PORT}:6379" volumes: diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index 5e21cc9efd..6d9f64c07e 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -23,6 +23,8 @@ services: LOG_LEVEL: info SENTRY_DSN: https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131 ENABLE_ANALYTICS: "true" + REDIS_URL: redis-service:6379 + REDIS_PASSWORD: ${REDIS_PASSWORD} depends_on: - worker-service @@ -43,6 +45,8 @@ services: COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD} COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984 SELF_HOST_KEY: ${HOSTING_KEY} + REDIS_URL: redis-service:6379 + REDIS_PASSWORD: ${REDIS_PASSWORD} depends_on: - minio-service - couch-init @@ -100,8 +104,7 @@ services: redis-service: restart: always image: redis - ports: - - "${REDIS_PORT}:6379" + command: redis-server --requirepass ${REDIS_PASSWORD} volumes: - redis_data:/data diff --git a/hosting/envoy.dev.yaml.hbs b/hosting/envoy.dev.yaml.hbs index 9c924b995b..3a7e73c89c 100644 --- a/hosting/envoy.dev.yaml.hbs +++ b/hosting/envoy.dev.yaml.hbs @@ -21,11 +21,6 @@ static_resources: cluster: couchdb-service prefix_rewrite: "/" - - match: { prefix: "/cache/" } - route: - cluster: redis-service - prefix_rewrite: "/" - - match: { prefix: "/api/admin/" } route: cluster: worker-dev @@ -85,20 +80,6 @@ static_resources: address: couchdb-service port_value: 5984 - - name: redis-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: redis-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: redis-service - port_value: 6379 - - name: server-dev connect_timeout: 0.25s type: strict_dns diff --git a/hosting/envoy.yaml b/hosting/envoy.yaml index 1fbd2070ff..d2202ff870 100644 --- a/hosting/envoy.yaml +++ b/hosting/envoy.yaml @@ -41,11 +41,6 @@ static_resources: cluster: worker-service prefix_rewrite: "/" - - match: { prefix: "/cache/" } - route: - cluster: redis-service - prefix_rewrite: "/" - - match: { prefix: "/db/" } route: cluster: couchdb-service @@ -117,18 +112,3 @@ static_resources: address: couchdb-service port_value: 5984 - - name: redis-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: redis-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: redis-service - port_value: 6379 - - diff --git a/hosting/hosting.properties b/hosting/hosting.properties index 138e66d629..4297ec60a1 100644 --- a/hosting/hosting.properties +++ b/hosting/hosting.properties @@ -12,6 +12,7 @@ MINIO_ACCESS_KEY=budibase MINIO_SECRET_KEY=budibase COUCH_DB_PASSWORD=budibase COUCH_DB_USER=budibase +REDIS_PASSWORD=budibase # This section contains variables that do not need to be altered under normal circumstances APP_PORT=4002 diff --git a/packages/auth/package.json b/packages/auth/package.json index b4f4b1cb33..dfbb63c431 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,12 +1,13 @@ { "name": "@budibase/auth", - "version": "0.0.1", + "version": "0.18.6", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", "license": "AGPL-3.0", "dependencies": { "bcryptjs": "^2.4.3", + "ioredis": "^4.27.1", "jsonwebtoken": "^8.5.1", "koa-passport": "^4.1.4", "passport-google-auth": "^1.0.2", @@ -14,5 +15,8 @@ "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "uuid": "^8.3.2" + }, + "devDependencies": { + "ioredis-mock": "^5.5.5" } } diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 4bc0427976..40f5f05b70 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -123,7 +123,7 @@ const getConfigParams = ({ type, group, user }, otherProps = {}) => { * @param {Object} scopes - the type, group and userID scopes of the configuration. * @returns The most granular configuration document based on the scope. */ -const determineScopedConfig = async function (db, { type, user, group }) { +const getScopedFullConfig = async function (db, { type, user, group }) { const response = await db.allDocs( getConfigParams( { type, user, group }, @@ -160,6 +160,12 @@ const determineScopedConfig = async function (db, { type, user, group }) { return scopedConfig.doc } +async function getScopedConfig(db, params) { + const configDoc = await getScopedFullConfig(db, params) + return configDoc && configDoc.config ? configDoc.config : configDoc +} + +exports.getScopedConfig = getScopedConfig exports.generateConfigID = generateConfigID exports.getConfigParams = getConfigParams -exports.determineScopedConfig = determineScopedConfig +exports.getScopedFullConfig = getScopedFullConfig diff --git a/packages/auth/src/environment.js b/packages/auth/src/environment.js index 3a5c81ea8b..5b0b141019 100644 --- a/packages/auth/src/environment.js +++ b/packages/auth/src/environment.js @@ -1,5 +1,16 @@ +function isTest() { + return ( + process.env.NODE_ENV === "jest" || + process.env.NODE_ENV === "cypress" || + process.env.JEST_WORKER_ID != null + ) +} + module.exports = { JWT_SECRET: process.env.JWT_SECRET, COUCH_DB_URL: process.env.COUCH_DB_URL, SALT_ROUNDS: process.env.SALT_ROUNDS, + REDIS_URL: process.env.REDIS_URL, + REDIS_PASSWORD: process.env.REDIS_PASSWORD, + isTest, } diff --git a/packages/auth/src/index.js b/packages/auth/src/index.js index 348f911f80..e330684197 100644 --- a/packages/auth/src/index.js +++ b/packages/auth/src/index.js @@ -28,6 +28,10 @@ module.exports = { setDB(pouch) }, db: require("./db/utils"), + redis: { + Client: require("./redis"), + utils: require("./redis/utils"), + }, utils: { ...require("./utils"), ...require("./hashing"), diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index d64c30a70a..5d8d4e7e13 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -3,11 +3,35 @@ const database = require("../db") const { getCookie, clearCookie } = require("../utils") const { StaticDatabases } = require("../db/utils") -module.exports = (noAuthPatterns = []) => { - const regex = new RegExp(noAuthPatterns.join("|")) +const PARAM_REGEX = /\/:(.*?)\//g + +function buildNoAuthRegex(patterns) { + return patterns.map(pattern => { + const isObj = typeof pattern === "object" && pattern.route + const method = isObj ? pattern.method : "GET" + let route = isObj ? pattern.route : pattern + + const matches = route.match(PARAM_REGEX) + if (matches) { + for (let match of matches) { + route = route.replace(match, "/.*/") + } + } + return { regex: new RegExp(route), method } + }) +} + +module.exports = (noAuthPatterns = [], opts) => { + const noAuthOptions = noAuthPatterns ? buildNoAuthRegex(noAuthPatterns) : [] return async (ctx, next) => { // the path is not authenticated - if (regex.test(ctx.request.url)) { + const found = noAuthOptions.find(({ regex, method }) => { + return ( + regex.test(ctx.request.url) && + ctx.request.method.toLowerCase() === method.toLowerCase() + ) + }) + if (found != null) { return next() } try { @@ -30,10 +54,14 @@ module.exports = (noAuthPatterns = []) => { if (ctx.isAuthenticated !== true) { ctx.isAuthenticated = false } - return next() } catch (err) { - ctx.throw(err.status || 403, err) + // allow configuring for public access + if (opts && opts.publicAllowed) { + ctx.isAuthenticated = false + } else { + ctx.throw(err.status || 403, err) + } } } } diff --git a/packages/auth/src/redis/index.js b/packages/auth/src/redis/index.js new file mode 100644 index 0000000000..dc670c07fb --- /dev/null +++ b/packages/auth/src/redis/index.js @@ -0,0 +1,152 @@ +const env = require("../environment") +// ioredis mock is all in memory +const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis") +const { addDbPrefix, removeDbPrefix, getRedisOptions } = require("./utils") + +const CLUSTERED = false + +// for testing just generate the client once +let CLIENT = env.isTest() ? new Redis(getRedisOptions()) : null + +/** + * Inits the system, will error if unable to connect to redis cluster (may take up to 10 seconds) otherwise + * will return the ioredis client which will be ready to use. + * @return {Promise} The ioredis client. + */ +function init() { + return new Promise((resolve, reject) => { + // testing uses a single in memory client + if (env.isTest()) { + return resolve(CLIENT) + } + // if a connection existed, close it and re-create it + if (CLIENT) { + CLIENT.disconnect() + CLIENT = null + } + const { opts, host, port } = getRedisOptions(CLUSTERED) + if (CLUSTERED) { + CLIENT = new Redis.Cluster([{ host, port }], opts) + } else { + CLIENT = new Redis(opts) + } + CLIENT.on("end", err => { + reject(err) + }) + CLIENT.on("error", err => { + reject(err) + }) + CLIENT.on("connect", () => { + resolve(CLIENT) + }) + }) +} + +/** + * Utility function, takes a redis stream and converts it to a promisified response - + * this can only be done with redis streams because they will have an end. + * @param stream A redis stream, specifically as this type of stream will have an end. + * @return {Promise} The final output of the stream + */ +function promisifyStream(stream) { + return new Promise((resolve, reject) => { + const outputKeys = new Set() + stream.on("data", keys => { + keys.forEach(key => { + outputKeys.add(key) + }) + }) + stream.on("error", err => { + reject(err) + }) + stream.on("end", async () => { + const keysArray = Array.from(outputKeys) + try { + let getPromises = [] + for (let key of keysArray) { + getPromises.push(CLIENT.get(key)) + } + const jsonArray = await Promise.all(getPromises) + resolve( + keysArray.map(key => ({ + key: removeDbPrefix(key), + value: JSON.parse(jsonArray.shift()), + })) + ) + } catch (err) { + reject(err) + } + }) + }) +} + +class RedisWrapper { + constructor(db) { + this._db = db + } + + async init() { + this._client = await init() + return this + } + + async finish() { + this._client.disconnect() + } + + async scan() { + const db = this._db, + client = this._client + let stream + if (CLUSTERED) { + let node = client.nodes("master") + stream = node[0].scanStream({ match: db + "-*", count: 100 }) + } else { + stream = client.scanStream({ match: db + "-*", count: 100 }) + } + return promisifyStream(stream) + } + + async get(key) { + const db = this._db, + client = this._client + let response = await client.get(addDbPrefix(db, key)) + // overwrite the prefixed key + if (response != null && response.key) { + response.key = key + } + // if its not an object just return the response + try { + return JSON.parse(response) + } catch (err) { + return response + } + } + + async store(key, value, expirySeconds = null) { + const db = this._db, + client = this._client + if (typeof value === "object") { + value = JSON.stringify(value) + } + const prefixedKey = addDbPrefix(db, key) + await client.set(prefixedKey, value) + if (expirySeconds) { + await client.expire(prefixedKey, expirySeconds) + } + } + + async delete(key) { + const db = this._db, + client = this._client + await client.del(addDbPrefix(db, key)) + } + + async clear() { + const db = this._db + let items = await this.scan(db) + await Promise.all(items.map(obj => this.delete(db, obj.key))) + } +} + +module.exports = RedisWrapper diff --git a/packages/auth/src/redis/utils.js b/packages/auth/src/redis/utils.js new file mode 100644 index 0000000000..bd4a762e1d --- /dev/null +++ b/packages/auth/src/redis/utils.js @@ -0,0 +1,46 @@ +const env = require("../environment") + +const SLOT_REFRESH_MS = 2000 +const CONNECT_TIMEOUT_MS = 10000 +const SEPARATOR = "-" +const REDIS_URL = !env.REDIS_URL ? "localhost:6379" : env.REDIS_URL +const REDIS_PASSWORD = !env.REDIS_PASSWORD ? "budibase" : env.REDIS_PASSWORD + +exports.Databases = { + PW_RESETS: "pwReset", + INVITATIONS: "invitation", +} + +exports.getRedisOptions = (clustered = false) => { + const [host, port] = REDIS_URL.split(":") + const opts = { + connectTimeout: CONNECT_TIMEOUT_MS, + } + if (clustered) { + opts.redisOptions = {} + opts.redisOptions.tls = {} + opts.redisOptions.password = REDIS_PASSWORD + opts.slotsRefreshTimeout = SLOT_REFRESH_MS + opts.dnsLookup = (address, callback) => callback(null, address) + } else { + opts.host = host + opts.port = port + opts.password = REDIS_PASSWORD + } + return { opts, host, port } +} + +exports.addDbPrefix = (db, key) => { + return `${db}${SEPARATOR}${key}` +} + +exports.removeDbPrefix = key => { + let parts = key.split(SEPARATOR) + if (parts.length >= 2) { + parts.shift() + return parts.join(SEPARATOR) + } else { + // return the only part + return parts[0] + } +} diff --git a/packages/auth/src/utils.js b/packages/auth/src/utils.js index 10507410b1..a0ba0d25b5 100644 --- a/packages/auth/src/utils.js +++ b/packages/auth/src/utils.js @@ -105,6 +105,12 @@ exports.isClient = ctx => { return ctx.headers["x-budibase-type"] === "client" } +/** + * Given an email address this will use a view to search through + * all the users to find one with this email address. + * @param {string} email the email to lookup the user by. + * @return {Promise} + */ exports.getGlobalUserByEmail = async email => { const db = getDB(StaticDatabases.GLOBAL.name) try { diff --git a/packages/auth/yarn.lock b/packages/auth/yarn.lock index c3066ebdc1..a3c95bf42c 100644 --- a/packages/auth/yarn.lock +++ b/packages/auth/yarn.lock @@ -46,6 +46,11 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + base64url@3.x.x: version "3.0.1" resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" @@ -63,6 +68,14 @@ bcryptjs@^2.4.3: resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + 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" @@ -73,6 +86,11 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +cluster-key-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" + integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== + 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" @@ -80,6 +98,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + 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" @@ -92,11 +115,23 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +debug@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + 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= +denque@^1.1.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de" + integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ== + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -137,6 +172,20 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fengari-interop@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/fengari-interop/-/fengari-interop-0.1.2.tgz#f7731dcdd2ff4449073fb7ac3c451a8841ce1e87" + integrity sha512-8iTvaByZVoi+lQJhHH9vC+c/Yaok9CwOqNQZN6JrVpjmWwW4dDkeblBXhnHC+BoI6eF4Cy5NKW3z6ICEjvgywQ== + +fengari@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/fengari/-/fengari-0.1.4.tgz#72416693cd9e43bd7d809d7829ddc0578b78b0bb" + integrity sha512-6ujqUuiIYmcgkGz8MGAdERU57EIluGGPSUgGPTsco657EHa+srq0S3/YUl/r9kx1+D+d4rGfYObd+m8K22gB1g== + dependencies: + readline-sync "^1.4.9" + sprintf-js "^1.1.1" + tmp "^0.0.33" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -216,6 +265,33 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +ioredis-mock@^5.5.5: + version "5.5.5" + resolved "https://registry.yarnpkg.com/ioredis-mock/-/ioredis-mock-5.5.5.tgz#dec9fedd238c6ab9f56c026fc366533144f8a256" + integrity sha512-7SxCAwNtDLC8IFDptqIhOC7ajp3fciVtCrXOEOkpyjPboAGRQkJbnpNPy1NYORoWi+0/iOtUPUQckSKtSQj4DA== + dependencies: + fengari "^0.1.4" + fengari-interop "^0.1.2" + lodash "^4.17.21" + minimatch "^3.0.4" + standard-as-callback "^2.1.0" + +ioredis@^4.27.1: + version "4.27.1" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.27.1.tgz#4ef947b455a1b995baa4b0d7e2c4e4f75f746421" + integrity sha512-PaFNFeBbOcEYHXAdrJuy7uesJcyvzStTM1aYMchTuky+VgKqDbXhnTJHaDsjAwcTwPx8Asatx+l2DW8zZ2xlsQ== + dependencies: + cluster-key-slot "^1.1.0" + debug "^4.3.1" + denque "^1.1.0" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + p-map "^2.1.0" + redis-commands "1.7.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -296,6 +372,16 @@ koa-passport@^4.1.4: dependencies: passport "^0.4.0" +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -336,7 +422,7 @@ lodash.once@^4.0.0: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash@^4.14.0: +lodash@^4.14.0, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -358,6 +444,18 @@ mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" @@ -378,6 +476,16 @@ oauth@0.9.x: resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE= +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +p-map@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + 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" @@ -481,6 +589,28 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +readline-sync@^1.4.9: + version "1.4.10" + resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b" + integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw== + +redis-commands@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89" + integrity sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ== + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + dependencies: + redis-errors "^1.0.0" + request@^2.72.0, request@^2.74.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" @@ -522,6 +652,11 @@ semver@^5.6.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== +sprintf-js@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" + integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== + sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -537,11 +672,23 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + 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= +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" diff --git a/packages/bbui/src/Avatar/Avatar.svelte b/packages/bbui/src/Avatar/Avatar.svelte index 1b2310b61f..7a6ad5f004 100644 --- a/packages/bbui/src/Avatar/Avatar.svelte +++ b/packages/bbui/src/Avatar/Avatar.svelte @@ -45,12 +45,7 @@ display: grid; place-items: center; font-weight: 500; - background: rgb(63, 94, 251); - background: linear-gradient( - 155deg, - rgba(63, 94, 251, 1) 0%, - rgba(53, 199, 86, 1) 47% - ); + background: #3aab87; border-radius: 50%; overflow: hidden; user-select: none; diff --git a/packages/bbui/src/Layout/Page.svelte b/packages/bbui/src/Layout/Page.svelte new file mode 100644 index 0000000000..d8c8cbfce4 --- /dev/null +++ b/packages/bbui/src/Layout/Page.svelte @@ -0,0 +1,23 @@ + + +
+ +
+ + diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js index e1b68cb35f..9d47958461 100644 --- a/packages/bbui/src/index.js +++ b/packages/bbui/src/index.js @@ -28,6 +28,7 @@ export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte" export { default as ProgressCircle } from "./ProgressCircle/ProgressCircle.svelte" export { default as Label } from "./Label/Label.svelte" export { default as Layout } from "./Layout/Layout.svelte" +export { default as Page } from "./Layout/Page.svelte" export { default as Link } from "./Link/Link.svelte" export { default as Menu } from "./Menu/Menu.svelte" export { default as MenuSection } from "./Menu/Section.svelte" diff --git a/packages/builder/src/global.css b/packages/builder/src/global.css index 298513bd23..726f422a48 100644 --- a/packages/builder/src/global.css +++ b/packages/builder/src/global.css @@ -13,8 +13,8 @@ html, body { body { --background: var(--spectrum-alias-background-color-primary); --background-alt: var(--spectrum-alias-background-color-secondary); - --border-light: 1px solid var(--spectrum-global-color-gray-200); - --border-dark: 1px solid var(--spectrum-global-color-gray-300); + --border-light: 1px solid var(--spectrum-global-color-gray-300); + --border-dark: 1px solid var(--spectrum-global-color-gray-400); --ink: var(--spectrum-alias-text-color); --grey-1: var(--spectrum-global-color-gray-100); --grey-2: var(--spectrum-global-color-gray-200); diff --git a/packages/builder/src/pages/portal/_layout.svelte b/packages/builder/src/pages/portal/_layout.svelte index a60a786678..8e25b002b4 100644 --- a/packages/builder/src/pages/portal/_layout.svelte +++ b/packages/builder/src/pages/portal/_layout.svelte @@ -23,7 +23,7 @@ onMount(getInfo) let menu = [ - { title: "Apps", href: "/portal/" }, + { title: "Apps", href: "/portal/apps" }, { title: "Drafts", href: "/portal/drafts" }, { title: "Users", href: "/portal/manage/users", heading: "Manage" }, { title: "Groups", href: "/portal/manage/groups" }, @@ -36,15 +36,15 @@
- -