Merge branch 'master' of github.com:Budibase/budibase into ak-fixes
This commit is contained in:
commit
6477b2d2ed
|
@ -105,6 +105,8 @@ services:
|
||||||
restart: always
|
restart: always
|
||||||
image: redis
|
image: redis
|
||||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||||
|
ports:
|
||||||
|
- "${REDIS_PORT}:6379"
|
||||||
volumes:
|
volumes:
|
||||||
- redis_data:/data
|
- redis_data:/data
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/auth",
|
"name": "@budibase/auth",
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"description": "Authentication middlewares for budibase builder and apps",
|
"description": "Authentication middlewares for budibase builder and apps",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
|
|
@ -34,11 +34,15 @@ function sanitizeKey(input) {
|
||||||
return sanitize(sanitizeBucket(input)).replace(/\\/g, "/")
|
return sanitize(sanitizeBucket(input)).replace(/\\/g, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.sanitizeKey = sanitizeKey
|
||||||
|
|
||||||
// simply handles the dev app to app conversion
|
// simply handles the dev app to app conversion
|
||||||
function sanitizeBucket(input) {
|
function sanitizeBucket(input) {
|
||||||
return input.replace(new RegExp(APP_DEV_PREFIX, "g"), APP_PREFIX)
|
return input.replace(new RegExp(APP_DEV_PREFIX, "g"), APP_PREFIX)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.sanitizeBucket = sanitizeBucket
|
||||||
|
|
||||||
function publicPolicy(bucketName) {
|
function publicPolicy(bucketName) {
|
||||||
return {
|
return {
|
||||||
Version: "2012-10-17",
|
Version: "2012-10-17",
|
||||||
|
|
|
@ -3,22 +3,51 @@ const env = require("../environment")
|
||||||
const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis")
|
const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis")
|
||||||
const { addDbPrefix, removeDbPrefix, getRedisOptions } = require("./utils")
|
const { addDbPrefix, removeDbPrefix, getRedisOptions } = require("./utils")
|
||||||
|
|
||||||
|
const RETRY_PERIOD_MS = 2000
|
||||||
|
const STARTUP_TIMEOUT_MS = 5000
|
||||||
const CLUSTERED = false
|
const CLUSTERED = false
|
||||||
|
|
||||||
// for testing just generate the client once
|
// for testing just generate the client once
|
||||||
let CONNECTED = false
|
let CLOSED = false
|
||||||
let CLIENT = env.isTest() ? new Redis(getRedisOptions()) : null
|
let CLIENT = env.isTest() ? new Redis(getRedisOptions()) : null
|
||||||
|
// if in test always connected
|
||||||
|
let CONNECTED = !!env.isTest()
|
||||||
|
|
||||||
|
function connectionError(timeout, err) {
|
||||||
|
// manually shut down, ignore errors
|
||||||
|
if (CLOSED) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// always clear this on error
|
||||||
|
clearTimeout(timeout)
|
||||||
|
CONNECTED = false
|
||||||
|
console.error("Redis connection failed - " + err)
|
||||||
|
setTimeout(() => {
|
||||||
|
init()
|
||||||
|
}, RETRY_PERIOD_MS)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inits the system, will error if unable to connect to redis cluster (may take up to 10 seconds) otherwise
|
* 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.
|
* will return the ioredis client which will be ready to use.
|
||||||
* @return {Promise<object>} The ioredis client.
|
|
||||||
*/
|
*/
|
||||||
function init() {
|
function init() {
|
||||||
return new Promise((resolve, reject) => {
|
let timeout
|
||||||
|
CLOSED = false
|
||||||
// testing uses a single in memory client
|
// testing uses a single in memory client
|
||||||
if (env.isTest() || (CLIENT && CONNECTED)) {
|
if (env.isTest() || (CLIENT && CONNECTED)) {
|
||||||
return resolve(CLIENT)
|
return
|
||||||
|
}
|
||||||
|
// start the timer - only allowed 5 seconds to connect
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
if (!CONNECTED) {
|
||||||
|
connectionError(timeout)
|
||||||
|
}
|
||||||
|
}, STARTUP_TIMEOUT_MS)
|
||||||
|
|
||||||
|
// disconnect any lingering client
|
||||||
|
if (CLIENT) {
|
||||||
|
CLIENT.disconnect()
|
||||||
}
|
}
|
||||||
const { opts, host, port } = getRedisOptions(CLUSTERED)
|
const { opts, host, port } = getRedisOptions(CLUSTERED)
|
||||||
if (CLUSTERED) {
|
if (CLUSTERED) {
|
||||||
|
@ -26,18 +55,34 @@ function init() {
|
||||||
} else {
|
} else {
|
||||||
CLIENT = new Redis(opts)
|
CLIENT = new Redis(opts)
|
||||||
}
|
}
|
||||||
|
// attach handlers
|
||||||
CLIENT.on("end", err => {
|
CLIENT.on("end", err => {
|
||||||
reject(err)
|
connectionError(timeout, err)
|
||||||
CONNECTED = false
|
|
||||||
})
|
})
|
||||||
CLIENT.on("error", err => {
|
CLIENT.on("error", err => {
|
||||||
reject(err)
|
connectionError(timeout, err)
|
||||||
CONNECTED = false
|
|
||||||
})
|
})
|
||||||
CLIENT.on("connect", () => {
|
CLIENT.on("connect", () => {
|
||||||
resolve(CLIENT)
|
clearTimeout(timeout)
|
||||||
CONNECTED = true
|
CONNECTED = true
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForConnection() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (CLIENT == null) {
|
||||||
|
init()
|
||||||
|
} else if (CONNECTED) {
|
||||||
|
resolve()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// check if the connection is ready
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if (CONNECTED) {
|
||||||
|
clearInterval(interval)
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,31 +130,32 @@ class RedisWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
this._client = await init()
|
CLOSED = false
|
||||||
|
init()
|
||||||
|
await waitForConnection()
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
async finish() {
|
async finish() {
|
||||||
this._client.disconnect()
|
CLOSED = true
|
||||||
|
CLIENT.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
async scan() {
|
async scan() {
|
||||||
const db = this._db,
|
const db = this._db
|
||||||
client = this._client
|
|
||||||
let stream
|
let stream
|
||||||
if (CLUSTERED) {
|
if (CLUSTERED) {
|
||||||
let node = client.nodes("master")
|
let node = CLIENT.nodes("master")
|
||||||
stream = node[0].scanStream({ match: db + "-*", count: 100 })
|
stream = node[0].scanStream({ match: db + "-*", count: 100 })
|
||||||
} else {
|
} else {
|
||||||
stream = client.scanStream({ match: db + "-*", count: 100 })
|
stream = CLIENT.scanStream({ match: db + "-*", count: 100 })
|
||||||
}
|
}
|
||||||
return promisifyStream(stream)
|
return promisifyStream(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(key) {
|
async get(key) {
|
||||||
const db = this._db,
|
const db = this._db
|
||||||
client = this._client
|
let response = await CLIENT.get(addDbPrefix(db, key))
|
||||||
let response = await client.get(addDbPrefix(db, key))
|
|
||||||
// overwrite the prefixed key
|
// overwrite the prefixed key
|
||||||
if (response != null && response.key) {
|
if (response != null && response.key) {
|
||||||
response.key = key
|
response.key = key
|
||||||
|
@ -123,22 +169,20 @@ class RedisWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
async store(key, value, expirySeconds = null) {
|
async store(key, value, expirySeconds = null) {
|
||||||
const db = this._db,
|
const db = this._db
|
||||||
client = this._client
|
|
||||||
if (typeof value === "object") {
|
if (typeof value === "object") {
|
||||||
value = JSON.stringify(value)
|
value = JSON.stringify(value)
|
||||||
}
|
}
|
||||||
const prefixedKey = addDbPrefix(db, key)
|
const prefixedKey = addDbPrefix(db, key)
|
||||||
await client.set(prefixedKey, value)
|
await CLIENT.set(prefixedKey, value)
|
||||||
if (expirySeconds) {
|
if (expirySeconds) {
|
||||||
await client.expire(prefixedKey, expirySeconds)
|
await CLIENT.expire(prefixedKey, expirySeconds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(key) {
|
async delete(key) {
|
||||||
const db = this._db,
|
const db = this._db
|
||||||
client = this._client
|
await CLIENT.del(addDbPrefix(db, key))
|
||||||
await client.del(addDbPrefix(db, key))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async clear() {
|
async clear() {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
|
|
@ -11,12 +11,23 @@ Cypress.Commands.add("login", () => {
|
||||||
if (cookie) return
|
if (cookie) return
|
||||||
|
|
||||||
cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
|
cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
|
||||||
cy.contains("Create Test User").click()
|
|
||||||
|
// cy.get("button").then(btn => {
|
||||||
|
// const adminUserButton = "Create super admin user"
|
||||||
|
// console.log(btn.first().first())
|
||||||
|
// if (!btn.first().contains(adminUserButton)) {
|
||||||
|
// // create admin user
|
||||||
|
// cy.get("input").first().type("test@test.com")
|
||||||
|
// cy.get('input[type="password"]').first().type("test")
|
||||||
|
// cy.get('input[type="password"]').eq(1).type("test")
|
||||||
|
// cy.contains(adminUserButton).click()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// login
|
||||||
cy.get("input").first().type("test@test.com")
|
cy.get("input").first().type("test@test.com")
|
||||||
|
|
||||||
cy.get('input[type="password"]').type("test")
|
cy.get('input[type="password"]').type("test")
|
||||||
|
|
||||||
cy.contains("Login").click()
|
cy.contains("Login").click()
|
||||||
|
// })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -65,10 +65,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^0.9.1",
|
"@budibase/bbui": "^0.9.3",
|
||||||
"@budibase/client": "^0.9.1",
|
"@budibase/client": "^0.9.3",
|
||||||
"@budibase/colorpicker": "1.1.2",
|
"@budibase/colorpicker": "1.1.2",
|
||||||
"@budibase/string-templates": "^0.9.1",
|
"@budibase/string-templates": "^0.9.3",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
|
|
@ -10,13 +10,13 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if show}
|
{#if show}
|
||||||
<ActionButton>
|
<ActionButton
|
||||||
<a target="_blank" href="/api/admin/auth/google">
|
on:click={() => window.open("/api/admin/auth/google", "_blank")}
|
||||||
|
>
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<img src={GoogleLogo} alt="google icon" />
|
<img src={GoogleLogo} alt="google icon" />
|
||||||
<p>Sign in with Google</p>
|
<p>Sign in with Google</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
Select,
|
Select,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
notifications,
|
notifications,
|
||||||
|
Toggle,
|
||||||
|
Label,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { createValidationStore, emailValidator } from "helpers/validation"
|
import { createValidationStore, emailValidator } from "helpers/validation"
|
||||||
import { users } from "stores/portal"
|
import { users } from "stores/portal"
|
||||||
|
@ -13,12 +15,12 @@
|
||||||
|
|
||||||
const options = ["Email onboarding", "Basic onboarding"]
|
const options = ["Email onboarding", "Basic onboarding"]
|
||||||
let selected = options[0]
|
let selected = options[0]
|
||||||
|
let builder, admin
|
||||||
|
|
||||||
const [email, error, touched] = createValidationStore("", emailValidator)
|
const [email, error, touched] = createValidationStore("", emailValidator)
|
||||||
|
|
||||||
async function createUserFlow() {
|
async function createUserFlow() {
|
||||||
const res = await users.invite($email)
|
const res = await users.invite({ email: $email, builder, admin })
|
||||||
console.log(res)
|
|
||||||
if (res.status) {
|
if (res.status) {
|
||||||
notifications.error(res.message)
|
notifications.error(res.message)
|
||||||
} else {
|
} else {
|
||||||
|
@ -56,4 +58,23 @@
|
||||||
placeholder="john@doe.com"
|
placeholder="john@doe.com"
|
||||||
label="Email"
|
label="Email"
|
||||||
/>
|
/>
|
||||||
|
<div>
|
||||||
|
<div class="toggle">
|
||||||
|
<Label size="L">Development access</Label>
|
||||||
|
<Toggle text="" bind:value={builder} />
|
||||||
|
</div>
|
||||||
|
<div class="toggle">
|
||||||
|
<Label size="L">Administration access</Label>
|
||||||
|
<Toggle text="" bind:value={admin} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.toggle {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 78% 1fr;
|
||||||
|
align-items: center;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
<script>
|
<script>
|
||||||
import { ModalContent, Body, Input, notifications } from "@budibase/bbui"
|
import {
|
||||||
|
ModalContent,
|
||||||
|
Body,
|
||||||
|
Input,
|
||||||
|
notifications,
|
||||||
|
Toggle,
|
||||||
|
Label,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { createValidationStore, emailValidator } from "helpers/validation"
|
import { createValidationStore, emailValidator } from "helpers/validation"
|
||||||
import { users } from "stores/portal"
|
import { users } from "stores/portal"
|
||||||
|
|
||||||
const [email, error, touched] = createValidationStore("", emailValidator)
|
const [email, error, touched] = createValidationStore("", emailValidator)
|
||||||
const password = Math.random().toString(36).substr(2, 20)
|
const password = Math.random().toString(36).substr(2, 20)
|
||||||
|
let builder = false,
|
||||||
|
admin = false
|
||||||
|
|
||||||
async function createUser() {
|
async function createUser() {
|
||||||
const res = await users.create({ email: $email, password })
|
const res = await users.create({ email: $email, password, builder, admin })
|
||||||
if (res.status) {
|
if (res.status) {
|
||||||
notifications.error(res.message)
|
notifications.error(res.message)
|
||||||
} else {
|
} else {
|
||||||
|
@ -37,4 +46,23 @@
|
||||||
error={$touched && $error}
|
error={$touched && $error}
|
||||||
/>
|
/>
|
||||||
<Input disabled label="Password" value={password} />
|
<Input disabled label="Password" value={password} />
|
||||||
|
<div>
|
||||||
|
<div class="toggle">
|
||||||
|
<Label size="L">Development access</Label>
|
||||||
|
<Toggle text="" bind:value={builder} />
|
||||||
|
</div>
|
||||||
|
<div class="toggle">
|
||||||
|
<Label size="L">Administration access</Label>
|
||||||
|
<Toggle text="" bind:value={admin} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.toggle {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 78% 1fr;
|
||||||
|
align-items: center;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -11,10 +11,22 @@ export function createUsersStore() {
|
||||||
set(json)
|
set(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function invite(email) {
|
async function invite({ email, builder, admin }) {
|
||||||
const response = await api.post(`/api/admin/users/invite`, { email })
|
const body = { email, userInfo: {} }
|
||||||
|
if (admin) {
|
||||||
|
body.userInfo.admin = {
|
||||||
|
global: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (builder) {
|
||||||
|
body.userInfo.builder = {
|
||||||
|
global: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const response = await api.post(`/api/admin/users/invite`, body)
|
||||||
return await response.json()
|
return await response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function acceptInvite(inviteCode, password) {
|
async function acceptInvite(inviteCode, password) {
|
||||||
const response = await api.post("/api/admin/users/invite/accept", {
|
const response = await api.post("/api/admin/users/invite/accept", {
|
||||||
inviteCode,
|
inviteCode,
|
||||||
|
@ -23,14 +35,20 @@ export function createUsersStore() {
|
||||||
return await response.json()
|
return await response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function create({ email, password }) {
|
async function create({ email, password, admin, builder }) {
|
||||||
const response = await api.post("/api/admin/users", {
|
const body = {
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
builder: { global: true },
|
|
||||||
roles: {},
|
roles: {},
|
||||||
})
|
}
|
||||||
init()
|
if (builder) {
|
||||||
|
body.builder = { global: true }
|
||||||
|
}
|
||||||
|
if (admin) {
|
||||||
|
body.admin = { global: true }
|
||||||
|
}
|
||||||
|
const response = await api.post("/api/admin/users", body)
|
||||||
|
await init()
|
||||||
return await response.json()
|
return await response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,8 +61,7 @@ export function createUsersStore() {
|
||||||
async function save(data) {
|
async function save(data) {
|
||||||
try {
|
try {
|
||||||
const res = await post(`/api/admin/users`, data)
|
const res = await post(`/api/admin/users`, data)
|
||||||
const json = await res.json()
|
return await res.json()
|
||||||
return json
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
return error
|
return error
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
@ -101,10 +101,15 @@ async function init(type) {
|
||||||
async function start() {
|
async function start() {
|
||||||
await checkDockerConfigured()
|
await checkDockerConfigured()
|
||||||
checkInitComplete()
|
checkInitComplete()
|
||||||
console.log(info("Starting services, this may take a moment."))
|
console.log(
|
||||||
|
info(
|
||||||
|
"Starting services, this may take a moment - first time this may take a few minutes to download images."
|
||||||
|
)
|
||||||
|
)
|
||||||
const port = makeEnv.get("MAIN_PORT")
|
const port = makeEnv.get("MAIN_PORT")
|
||||||
await handleError(async () => {
|
await handleError(async () => {
|
||||||
await compose.upAll({ cwd: "./", log: false })
|
// need to log as it makes it more clear
|
||||||
|
await compose.upAll({ cwd: "./", log: true })
|
||||||
})
|
})
|
||||||
console.log(
|
console.log(
|
||||||
success(
|
success(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -18,13 +18,13 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/string-templates": "^0.9.1",
|
"@budibase/string-templates": "^0.9.3",
|
||||||
"regexparam": "^1.3.0",
|
"regexparam": "^1.3.0",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"svelte-spa-router": "^3.0.5"
|
"svelte-spa-router": "^3.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@budibase/standard-components": "^0.9.1",
|
"@budibase/standard-components": "^0.9.3",
|
||||||
"@rollup/plugin-commonjs": "^18.0.0",
|
"@rollup/plugin-commonjs": "^18.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^11.2.1",
|
"@rollup/plugin-node-resolve": "^11.2.1",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
|
|
|
@ -1,36 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/electron.js",
|
"main": "src/electron.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Budibase/budibase.git"
|
"url": "https://github.com/Budibase/budibase.git"
|
||||||
},
|
},
|
||||||
"build": {
|
|
||||||
"icon": "./build/icons/512x512.png",
|
|
||||||
"appId": "com.budibase.builder",
|
|
||||||
"productName": "Budibase Builder",
|
|
||||||
"afterSign": "electron-builder-notarize",
|
|
||||||
"mac": {
|
|
||||||
"icon": "./assets/icons/icon.icns",
|
|
||||||
"category": "public.app-category.developer-tools",
|
|
||||||
"hardenedRuntime": true
|
|
||||||
},
|
|
||||||
"linux": {
|
|
||||||
"maintainer": "Budibase",
|
|
||||||
"icon": "./build/icons/",
|
|
||||||
"target": [
|
|
||||||
"deb",
|
|
||||||
"AppImage"
|
|
||||||
],
|
|
||||||
"category": "Development"
|
|
||||||
},
|
|
||||||
"extraMetadata": {
|
|
||||||
"name": "Budibase Builder"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest --testPathIgnorePatterns=routes && yarn run test:integration",
|
"test": "jest --testPathIgnorePatterns=routes && yarn run test:integration",
|
||||||
"test:integration": "jest --coverage --detectOpenHandles",
|
"test:integration": "jest --coverage --detectOpenHandles",
|
||||||
|
@ -41,9 +18,6 @@
|
||||||
"dev:stack:down": "node scripts/dev/manage.js down",
|
"dev:stack:down": "node scripts/dev/manage.js down",
|
||||||
"dev:stack:nuke": "node scripts/dev/manage.js nuke",
|
"dev:stack:nuke": "node scripts/dev/manage.js nuke",
|
||||||
"dev:builder": "yarn run dev:stack:up && nodemon src/index.js",
|
"dev:builder": "yarn run dev:stack:up && nodemon src/index.js",
|
||||||
"electron": "electron src/electron.js",
|
|
||||||
"build:electron": "electron-builder --dir",
|
|
||||||
"publish:electron": "electron-builder -mwl --publish always",
|
|
||||||
"lint": "eslint --fix src/",
|
"lint": "eslint --fix src/",
|
||||||
"initialise": "node scripts/initialise.js"
|
"initialise": "node scripts/initialise.js"
|
||||||
},
|
},
|
||||||
|
@ -81,9 +55,9 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/auth": "^0.9.1",
|
"@budibase/auth": "^0.9.3",
|
||||||
"@budibase/client": "^0.9.1",
|
"@budibase/client": "^0.9.3",
|
||||||
"@budibase/string-templates": "^0.9.1",
|
"@budibase/string-templates": "^0.9.3",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
"@koa/router": "8.0.0",
|
"@koa/router": "8.0.0",
|
||||||
"@sendgrid/mail": "7.1.1",
|
"@sendgrid/mail": "7.1.1",
|
||||||
|
@ -133,7 +107,7 @@
|
||||||
"zlib": "1.0.5"
|
"zlib": "1.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@budibase/standard-components": "^0.9.1",
|
"@budibase/standard-components": "^0.9.3",
|
||||||
"@jest/test-sequencer": "^24.8.0",
|
"@jest/test-sequencer": "^24.8.0",
|
||||||
"docker-compose": "^0.23.6",
|
"docker-compose": "^0.23.6",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
|
|
|
@ -23,7 +23,16 @@ async function redirect(ctx, method) {
|
||||||
if (cookie) {
|
if (cookie) {
|
||||||
ctx.set("set-cookie", cookie)
|
ctx.set("set-cookie", cookie)
|
||||||
}
|
}
|
||||||
|
let body
|
||||||
|
try {
|
||||||
|
body = await response.json()
|
||||||
|
} catch (err) {
|
||||||
|
// don't worry about errors, likely no JSON
|
||||||
|
}
|
||||||
ctx.status = response.status
|
ctx.status = response.status
|
||||||
|
if (body) {
|
||||||
|
ctx.body = body
|
||||||
|
}
|
||||||
ctx.cookies
|
ctx.cookies
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -384,7 +384,7 @@ describe("/rows", () => {
|
||||||
name: "test",
|
name: "test",
|
||||||
description: "test",
|
description: "test",
|
||||||
attachment: [{
|
attachment: [{
|
||||||
key: `/assets/${config.getAppId()}/attachment/test/thing.csv`,
|
key: `${config.getAppId()}/attachment/test/thing.csv`,
|
||||||
}],
|
}],
|
||||||
tableId: table._id,
|
tableId: table._id,
|
||||||
})
|
})
|
||||||
|
@ -392,7 +392,7 @@ describe("/rows", () => {
|
||||||
await setup.switchToSelfHosted(async () => {
|
await setup.switchToSelfHosted(async () => {
|
||||||
const enriched = await outputProcessing(config.getAppId(), table, [row])
|
const enriched = await outputProcessing(config.getAppId(), table, [row])
|
||||||
expect(enriched[0].attachment[0].url).toBe(
|
expect(enriched[0].attachment[0].url).toBe(
|
||||||
`/prod-budi-app-assets/assets/${config.getAppId()}/attachment/test/thing.csv`
|
`/prod-budi-app-assets/${config.getAppId()}/attachment/test/thing.csv`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -73,10 +73,11 @@ if (env.isProd()) {
|
||||||
const server = http.createServer(app.callback())
|
const server = http.createServer(app.callback())
|
||||||
destroyable(server)
|
destroyable(server)
|
||||||
|
|
||||||
server.on("close", () => {
|
server.on("close", async () => {
|
||||||
if (env.NODE_ENV !== "jest") {
|
if (env.NODE_ENV !== "jest") {
|
||||||
console.log("Server Closed")
|
console.log("Server Closed")
|
||||||
}
|
}
|
||||||
|
await redis.shutdown()
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = server.listen(env.PORT || 0, async () => {
|
module.exports = server.listen(env.PORT || 0, async () => {
|
||||||
|
|
|
@ -101,7 +101,7 @@ exports.AutoFieldSubTypes = {
|
||||||
AUTO_ID: "autoID",
|
AUTO_ID: "autoID",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.OBJ_STORE_DIRECTORY = "/app-assets/assets"
|
exports.OBJ_STORE_DIRECTORY = "/prod-budi-app-assets"
|
||||||
exports.BaseQueryVerbs = {
|
exports.BaseQueryVerbs = {
|
||||||
CREATE: "create",
|
CREATE: "create",
|
||||||
READ: "read",
|
READ: "read",
|
||||||
|
|
|
@ -13,7 +13,7 @@ const {
|
||||||
deleteFolder,
|
deleteFolder,
|
||||||
downloadTarball,
|
downloadTarball,
|
||||||
} = require("./utilities")
|
} = require("./utilities")
|
||||||
const { downloadLibraries, newAppPublicPath } = require("./newApp")
|
const { downloadLibraries, uploadClientLibrary } = require("./newApp")
|
||||||
const download = require("download")
|
const download = require("download")
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
const { homedir } = require("os")
|
const { homedir } = require("os")
|
||||||
|
@ -134,7 +134,7 @@ exports.performBackup = async (appId, backupName) => {
|
||||||
*/
|
*/
|
||||||
exports.createApp = async appId => {
|
exports.createApp = async appId => {
|
||||||
await downloadLibraries(appId)
|
await downloadLibraries(appId)
|
||||||
await newAppPublicPath(appId)
|
await uploadClientLibrary(appId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -26,10 +26,9 @@ exports.downloadLibraries = async appId => {
|
||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.newAppPublicPath = async appId => {
|
exports.uploadClientLibrary = async appId => {
|
||||||
const path = join(appId, "public")
|
|
||||||
const sourcepath = require.resolve("@budibase/client")
|
const sourcepath = require.resolve("@budibase/client")
|
||||||
const destPath = join(path, "budibase-client.js")
|
const destPath = join(appId, "budibase-client.js")
|
||||||
|
|
||||||
await streamUpload(BUCKET_NAME, destPath, fs.createReadStream(sourcepath))
|
await streamUpload(BUCKET_NAME, destPath, fs.createReadStream(sourcepath))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
const { OBJ_STORE_DIRECTORY, ObjectStoreBuckets } = require("../constants")
|
const { OBJ_STORE_DIRECTORY, ObjectStoreBuckets } = require("../constants")
|
||||||
const { getAllApps } = require("@budibase/auth/db")
|
const { getAllApps } = require("@budibase/auth/db")
|
||||||
|
const { sanitizeKey } = require("@budibase/auth/src/objectStore")
|
||||||
|
|
||||||
const BB_CDN = "https://cdn.app.budi.live/assets"
|
const BB_CDN = "https://cdn.app.budi.live/assets"
|
||||||
|
|
||||||
|
@ -43,7 +44,9 @@ exports.objectStoreUrl = () => {
|
||||||
*/
|
*/
|
||||||
exports.clientLibraryPath = appId => {
|
exports.clientLibraryPath = appId => {
|
||||||
if (env.isProd()) {
|
if (env.isProd()) {
|
||||||
return `${exports.objectStoreUrl()}/${appId}/budibase-client.js`
|
return `${exports.objectStoreUrl()}/${sanitizeKey(
|
||||||
|
appId
|
||||||
|
)}/budibase-client.js`
|
||||||
} else {
|
} else {
|
||||||
return `/api/assets/client`
|
return `/api/assets/client`
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,11 @@ exports.init = async () => {
|
||||||
debounceClient = await new Client(utils.Databases.DEBOUNCE).init()
|
debounceClient = await new Client(utils.Databases.DEBOUNCE).init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.shutdown = async () => {
|
||||||
|
await devAppClient.finish()
|
||||||
|
await debounceClient.finish()
|
||||||
|
}
|
||||||
|
|
||||||
exports.doesUserHaveLock = async (devAppId, user) => {
|
exports.doesUserHaveLock = async (devAppId, user) => {
|
||||||
const value = await devAppClient.get(devAppId)
|
const value = await devAppClient.get(devAppId)
|
||||||
if (!value) {
|
if (!value) {
|
||||||
|
|
|
@ -29,11 +29,11 @@
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"svelte"
|
"svelte"
|
||||||
],
|
],
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc",
|
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^0.9.1",
|
"@budibase/bbui": "^0.9.3",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
"apexcharts": "^3.22.1",
|
"apexcharts": "^3.22.1",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"description": "Handlebars wrapper for Budibase templating.",
|
"description": "Handlebars wrapper for Budibase templating.",
|
||||||
"main": "src/index.cjs",
|
"main": "src/index.cjs",
|
||||||
"module": "dist/bundle.mjs",
|
"module": "dist/bundle.mjs",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -21,8 +21,8 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/auth": "^0.9.1",
|
"@budibase/auth": "^0.9.3",
|
||||||
"@budibase/string-templates": "^0.9.1",
|
"@budibase/string-templates": "^0.9.3",
|
||||||
"@koa/router": "^8.0.0",
|
"@koa/router": "^8.0.0",
|
||||||
"aws-sdk": "^2.811.0",
|
"aws-sdk": "^2.811.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
|
|
|
@ -6,10 +6,8 @@ const {
|
||||||
getGlobalUserParams,
|
getGlobalUserParams,
|
||||||
getScopedFullConfig,
|
getScopedFullConfig,
|
||||||
} = require("@budibase/auth").db
|
} = require("@budibase/auth").db
|
||||||
const fetch = require("node-fetch")
|
|
||||||
const { Configs } = require("../../../constants")
|
const { Configs } = require("../../../constants")
|
||||||
const email = require("../../../utilities/email")
|
const email = require("../../../utilities/email")
|
||||||
const env = require("../../../environment")
|
|
||||||
const { upload, ObjectStoreBuckets } = require("@budibase/auth").objectStore
|
const { upload, ObjectStoreBuckets } = require("@budibase/auth").objectStore
|
||||||
|
|
||||||
const APP_PREFIX = "app_"
|
const APP_PREFIX = "app_"
|
||||||
|
@ -155,12 +153,7 @@ exports.configChecklist = async function (ctx) {
|
||||||
// TODO: Watch get started video
|
// TODO: Watch get started video
|
||||||
|
|
||||||
// Apps exist
|
// Apps exist
|
||||||
let allDbs
|
let allDbs = await CouchDB.allDbs()
|
||||||
if (env.COUCH_DB_URL) {
|
|
||||||
allDbs = await (await fetch(`${env.COUCH_DB_URL}/_all_dbs`)).json()
|
|
||||||
} else {
|
|
||||||
allDbs = await CouchDB.allDbs()
|
|
||||||
}
|
|
||||||
const appDbNames = allDbs.filter(dbName => dbName.startsWith(APP_PREFIX))
|
const appDbNames = allDbs.filter(dbName => dbName.startsWith(APP_PREFIX))
|
||||||
|
|
||||||
// They have set up SMTP
|
// They have set up SMTP
|
||||||
|
|
|
@ -167,13 +167,14 @@ exports.find = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.invite = async ctx => {
|
exports.invite = async ctx => {
|
||||||
const { email } = ctx.request.body
|
const { email, userInfo } = ctx.request.body
|
||||||
const existing = await getGlobalUserByEmail(email)
|
const existing = await getGlobalUserByEmail(email)
|
||||||
if (existing) {
|
if (existing) {
|
||||||
ctx.throw(400, "Email address already in use.")
|
ctx.throw(400, "Email address already in use.")
|
||||||
}
|
}
|
||||||
await sendEmail(email, EmailTemplatePurpose.INVITATION, {
|
await sendEmail(email, EmailTemplatePurpose.INVITATION, {
|
||||||
subject: "{{ company }} platform invitation",
|
subject: "{{ company }} platform invitation",
|
||||||
|
info: userInfo,
|
||||||
})
|
})
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: "Invitation has been sent.",
|
message: "Invitation has been sent.",
|
||||||
|
@ -183,13 +184,15 @@ exports.invite = async ctx => {
|
||||||
exports.inviteAccept = async ctx => {
|
exports.inviteAccept = async ctx => {
|
||||||
const { inviteCode, password, firstName, lastName } = ctx.request.body
|
const { inviteCode, password, firstName, lastName } = ctx.request.body
|
||||||
try {
|
try {
|
||||||
const email = await checkInviteCode(inviteCode)
|
// info is an extension of the user object that was stored by admin
|
||||||
|
const { email, info } = await checkInviteCode(inviteCode)
|
||||||
// only pass through certain props for accepting
|
// only pass through certain props for accepting
|
||||||
ctx.request.body = {
|
ctx.request.body = {
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
password,
|
password,
|
||||||
email,
|
email,
|
||||||
|
...info,
|
||||||
}
|
}
|
||||||
// this will flesh out the body response
|
// this will flesh out the body response
|
||||||
await exports.save(ctx)
|
await exports.save(ctx)
|
||||||
|
|
|
@ -6,6 +6,17 @@ const Joi = require("joi")
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
|
function buildAdminInitValidation() {
|
||||||
|
return joiValidator.body(
|
||||||
|
Joi.object({
|
||||||
|
email: Joi.string().required(),
|
||||||
|
password: Joi.string().required(),
|
||||||
|
})
|
||||||
|
.required()
|
||||||
|
.unknown(false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function buildUserSaveValidation(isSelf = false) {
|
function buildUserSaveValidation(isSelf = false) {
|
||||||
let schema = {
|
let schema = {
|
||||||
email: Joi.string().allow(null, ""),
|
email: Joi.string().allow(null, ""),
|
||||||
|
@ -36,6 +47,7 @@ function buildInviteValidation() {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return joiValidator.body(Joi.object({
|
return joiValidator.body(Joi.object({
|
||||||
email: Joi.string().required(),
|
email: Joi.string().required(),
|
||||||
|
userInfo: Joi.object().optional(),
|
||||||
}).required())
|
}).required())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +86,11 @@ router
|
||||||
buildInviteAcceptValidation(),
|
buildInviteAcceptValidation(),
|
||||||
controller.inviteAccept
|
controller.inviteAccept
|
||||||
)
|
)
|
||||||
.post("/api/admin/users/init", controller.adminUser)
|
.post(
|
||||||
|
"/api/admin/users/init",
|
||||||
|
buildAdminInitValidation(),
|
||||||
|
controller.adminUser
|
||||||
|
)
|
||||||
.get("/api/admin/users/self", controller.getSelf)
|
.get("/api/admin/users/self", controller.getSelf)
|
||||||
// admin endpoint but needs to come at end (blocks other endpoints otherwise)
|
// admin endpoint but needs to come at end (blocks other endpoints otherwise)
|
||||||
.get("/api/admin/users/:id", adminOnly, controller.find)
|
.get("/api/admin/users/:id", adminOnly, controller.find)
|
||||||
|
|
|
@ -9,6 +9,7 @@ const { passport } = require("@budibase/auth").auth
|
||||||
const logger = require("koa-pino-logger")
|
const logger = require("koa-pino-logger")
|
||||||
const http = require("http")
|
const http = require("http")
|
||||||
const api = require("./api")
|
const api = require("./api")
|
||||||
|
const redis = require("./utilities/redis")
|
||||||
|
|
||||||
const app = new Koa()
|
const app = new Koa()
|
||||||
|
|
||||||
|
@ -34,10 +35,16 @@ app.use(api.routes())
|
||||||
const server = http.createServer(app.callback())
|
const server = http.createServer(app.callback())
|
||||||
destroyable(server)
|
destroyable(server)
|
||||||
|
|
||||||
server.on("close", () => console.log("Server Closed"))
|
server.on("close", async () => {
|
||||||
|
if (env.isProd()) {
|
||||||
|
console.log("Server Closed")
|
||||||
|
}
|
||||||
|
await redis.shutdown()
|
||||||
|
})
|
||||||
|
|
||||||
module.exports = server.listen(parseInt(env.PORT || 4002), async () => {
|
module.exports = server.listen(parseInt(env.PORT || 4002), async () => {
|
||||||
console.log(`Worker running on ${JSON.stringify(server.address())}`)
|
console.log(`Worker running on ${JSON.stringify(server.address())}`)
|
||||||
|
await redis.init()
|
||||||
})
|
})
|
||||||
|
|
||||||
process.on("uncaughtException", err => {
|
process.on("uncaughtException", err => {
|
||||||
|
|
|
@ -46,12 +46,12 @@ function createSMTPTransport(config) {
|
||||||
return nodemailer.createTransport(options)
|
return nodemailer.createTransport(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLinkCode(purpose, email, user) {
|
async function getLinkCode(purpose, email, user, info = null) {
|
||||||
switch (purpose) {
|
switch (purpose) {
|
||||||
case EmailTemplatePurpose.PASSWORD_RECOVERY:
|
case EmailTemplatePurpose.PASSWORD_RECOVERY:
|
||||||
return getResetPasswordCode(user._id)
|
return getResetPasswordCode(user._id)
|
||||||
case EmailTemplatePurpose.INVITATION:
|
case EmailTemplatePurpose.INVITATION:
|
||||||
return getInviteCode(email)
|
return getInviteCode(email, info)
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -136,13 +136,14 @@ exports.isEmailConfigured = async (groupId = null) => {
|
||||||
* @param {string|undefined} from If sending from an address that is not what is configured in the SMTP config.
|
* @param {string|undefined} from If sending from an address that is not what is configured in the SMTP config.
|
||||||
* @param {string|undefined} contents If sending a custom email then can supply contents which will be added to it.
|
* @param {string|undefined} contents If sending a custom email then can supply contents which will be added to it.
|
||||||
* @param {string|undefined} subject A custom subject can be specified if the config one is not desired.
|
* @param {string|undefined} subject A custom subject can be specified if the config one is not desired.
|
||||||
|
* @param {object|undefined} info Pass in a structure of information to be stored alongside the invitation.
|
||||||
* @return {Promise<object>} returns details about the attempt to send email, e.g. if it is successful; based on
|
* @return {Promise<object>} returns details about the attempt to send email, e.g. if it is successful; based on
|
||||||
* nodemailer response.
|
* nodemailer response.
|
||||||
*/
|
*/
|
||||||
exports.sendEmail = async (
|
exports.sendEmail = async (
|
||||||
email,
|
email,
|
||||||
purpose,
|
purpose,
|
||||||
{ groupId, user, from, contents, subject } = {}
|
{ groupId, user, from, contents, subject, info } = {}
|
||||||
) => {
|
) => {
|
||||||
const db = new CouchDB(GLOBAL_DB)
|
const db = new CouchDB(GLOBAL_DB)
|
||||||
let config = (await getSmtpConfiguration(db, groupId)) || {}
|
let config = (await getSmtpConfiguration(db, groupId)) || {}
|
||||||
|
@ -151,7 +152,7 @@ exports.sendEmail = async (
|
||||||
}
|
}
|
||||||
const transport = createSMTPTransport(config)
|
const transport = createSMTPTransport(config)
|
||||||
// if there is a link code needed this will retrieve it
|
// if there is a link code needed this will retrieve it
|
||||||
const code = await getLinkCode(purpose, email, user)
|
const code = await getLinkCode(purpose, email, user, info)
|
||||||
const context = await getSettingsTemplateContext(purpose, code)
|
const context = await getSettingsTemplateContext(purpose, code)
|
||||||
const message = {
|
const message = {
|
||||||
from: from || config.from,
|
from: from || config.from,
|
||||||
|
|
|
@ -12,15 +12,21 @@ function getExpirySecondsForDB(db) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getClient(db) {
|
let pwResetClient, invitationClient
|
||||||
return await new Client(db).init()
|
|
||||||
|
function getClient(db) {
|
||||||
|
switch (db) {
|
||||||
|
case utils.Databases.PW_RESETS:
|
||||||
|
return pwResetClient
|
||||||
|
case utils.Databases.INVITATIONS:
|
||||||
|
return invitationClient
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeACode(db, value) {
|
async function writeACode(db, value) {
|
||||||
const client = await getClient(db)
|
const client = await getClient(db)
|
||||||
const code = newid()
|
const code = newid()
|
||||||
await client.store(code, value, getExpirySecondsForDB(db))
|
await client.store(code, value, getExpirySecondsForDB(db))
|
||||||
client.finish()
|
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,10 +39,22 @@ async function getACode(db, code, deleteCode = true) {
|
||||||
if (deleteCode) {
|
if (deleteCode) {
|
||||||
await client.delete(code)
|
await client.delete(code)
|
||||||
}
|
}
|
||||||
client.finish()
|
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.init = async () => {
|
||||||
|
pwResetClient = await new Client(utils.Databases.PW_RESETS).init()
|
||||||
|
invitationClient = await new Client(utils.Databases.PW_RESETS).init()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* make sure redis connection is closed.
|
||||||
|
*/
|
||||||
|
exports.shutdown = async () => {
|
||||||
|
await pwResetClient.finish()
|
||||||
|
await invitationClient.finish()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a user ID this will store a code (that is returned) for an hour in redis.
|
* Given a user ID this will store a code (that is returned) for an hour in redis.
|
||||||
* The user can then return this code for resetting their password (through their reset link).
|
* The user can then return this code for resetting their password (through their reset link).
|
||||||
|
@ -64,17 +82,18 @@ exports.checkResetPasswordCode = async (resetCode, deleteCode = true) => {
|
||||||
/**
|
/**
|
||||||
* Generates an invitation code and writes it to redis - which can later be checked for user creation.
|
* Generates an invitation code and writes it to redis - which can later be checked for user creation.
|
||||||
* @param {string} email the email address which the code is being sent to (for use later).
|
* @param {string} email the email address which the code is being sent to (for use later).
|
||||||
|
* @param {object|null} info Information to be carried along with the invitation.
|
||||||
* @return {Promise<string>} returns the code that was stored to redis.
|
* @return {Promise<string>} returns the code that was stored to redis.
|
||||||
*/
|
*/
|
||||||
exports.getInviteCode = async email => {
|
exports.getInviteCode = async (email, info) => {
|
||||||
return writeACode(utils.Databases.INVITATIONS, email)
|
return writeACode(utils.Databases.INVITATIONS, { email, info })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks that the provided invite code is valid - will return the email address of user that was invited.
|
* Checks that the provided invite code is valid - will return the email address of user that was invited.
|
||||||
* @param {string} inviteCode the invite code that was provided as part of the link.
|
* @param {string} inviteCode the invite code that was provided as part of the link.
|
||||||
* @param {boolean} deleteCode whether or not the code should be deleted after retrieval - defaults to true.
|
* @param {boolean} deleteCode whether or not the code should be deleted after retrieval - defaults to true.
|
||||||
* @return {Promise<string>} If the code is valid then an email address will be returned.
|
* @return {Promise<object>} If the code is valid then an email address will be returned.
|
||||||
*/
|
*/
|
||||||
exports.checkInviteCode = async (inviteCode, deleteCode = true) => {
|
exports.checkInviteCode = async (inviteCode, deleteCode = true) => {
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in New Issue