@budibase/backend-core 2.7.21-alpha.1 → 2.7.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +3 -3
- package/dist/src/middleware/passport/datasource/google.d.ts +3 -3
- package/dist/src/middleware/passport/datasource/google.js +25 -12
- package/dist/src/middleware/passport/datasource/google.js.map +1 -1
- package/dist/src/security/encryption.d.ts +0 -8
- package/dist/src/security/encryption.js +5 -107
- package/dist/src/security/encryption.js.map +1 -1
- package/dist/src/security/roles.d.ts +1 -4
- package/dist/src/security/roles.js +1 -5
- package/dist/src/security/roles.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/middleware/passport/datasource/google.ts +26 -20
- package/src/security/encryption.ts +4 -121
- package/src/security/roles.ts +1 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/backend-core",
|
|
3
|
-
"version": "2.7.21
|
|
3
|
+
"version": "2.7.21",
|
|
4
4
|
"description": "Budibase backend core libraries used in server and worker",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@budibase/nano": "10.1.2",
|
|
24
24
|
"@budibase/pouchdb-replication-stream": "1.2.10",
|
|
25
|
-
"@budibase/types": "2.7.21
|
|
25
|
+
"@budibase/types": "2.7.21",
|
|
26
26
|
"@shopify/jest-koa-mocks": "5.0.1",
|
|
27
27
|
"@techpass/passport-openidconnect": "0.3.2",
|
|
28
28
|
"aws-cloudfront-sign": "2.2.0",
|
|
@@ -101,5 +101,5 @@
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
},
|
|
104
|
-
"gitHead": "
|
|
104
|
+
"gitHead": "9e56f81d8309a1b23f7d5f9750cb5ba05cec7373"
|
|
105
105
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import * as google from "../sso/google"
|
|
2
2
|
import { Cookie } from "../../../constants"
|
|
3
|
+
import { clearCookie, getCookie } from "../../../utils"
|
|
4
|
+
import { doWithDB } from "../../../db"
|
|
3
5
|
import * as configs from "../../../configs"
|
|
4
|
-
import
|
|
5
|
-
import * as utils from "../../../utils"
|
|
6
|
-
import { UserCtx, SSOProfile } from "@budibase/types"
|
|
6
|
+
import { BBContext, Database, SSOProfile } from "@budibase/types"
|
|
7
7
|
import { ssoSaveUserNoOp } from "../sso/sso"
|
|
8
|
-
|
|
9
8
|
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
|
10
9
|
|
|
11
10
|
type Passport = {
|
|
@@ -23,7 +22,7 @@ async function fetchGoogleCreds() {
|
|
|
23
22
|
|
|
24
23
|
export async function preAuth(
|
|
25
24
|
passport: Passport,
|
|
26
|
-
ctx:
|
|
25
|
+
ctx: BBContext,
|
|
27
26
|
next: Function
|
|
28
27
|
) {
|
|
29
28
|
// get the relevant config
|
|
@@ -37,8 +36,8 @@ export async function preAuth(
|
|
|
37
36
|
ssoSaveUserNoOp
|
|
38
37
|
)
|
|
39
38
|
|
|
40
|
-
if (!ctx.query.appId) {
|
|
41
|
-
ctx.throw(400, "appId query
|
|
39
|
+
if (!ctx.query.appId || !ctx.query.datasourceId) {
|
|
40
|
+
ctx.throw(400, "appId and datasourceId query params not present.")
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
return passport.authenticate(strategy, {
|
|
@@ -50,7 +49,7 @@ export async function preAuth(
|
|
|
50
49
|
|
|
51
50
|
export async function postAuth(
|
|
52
51
|
passport: Passport,
|
|
53
|
-
ctx:
|
|
52
|
+
ctx: BBContext,
|
|
54
53
|
next: Function
|
|
55
54
|
) {
|
|
56
55
|
// get the relevant config
|
|
@@ -58,7 +57,7 @@ export async function postAuth(
|
|
|
58
57
|
const platformUrl = await configs.getPlatformUrl({ tenantAware: false })
|
|
59
58
|
|
|
60
59
|
let callbackUrl = `${platformUrl}/api/global/auth/datasource/google/callback`
|
|
61
|
-
const authStateCookie =
|
|
60
|
+
const authStateCookie = getCookie(ctx, Cookie.DatasourceAuth)
|
|
62
61
|
|
|
63
62
|
return passport.authenticate(
|
|
64
63
|
new GoogleStrategy(
|
|
@@ -70,26 +69,33 @@ export async function postAuth(
|
|
|
70
69
|
(
|
|
71
70
|
accessToken: string,
|
|
72
71
|
refreshToken: string,
|
|
73
|
-
|
|
72
|
+
profile: SSOProfile,
|
|
74
73
|
done: Function
|
|
75
74
|
) => {
|
|
76
|
-
|
|
75
|
+
clearCookie(ctx, Cookie.DatasourceAuth)
|
|
77
76
|
done(null, { accessToken, refreshToken })
|
|
78
77
|
}
|
|
79
78
|
),
|
|
80
79
|
{ successRedirect: "/", failureRedirect: "/error" },
|
|
81
80
|
async (err: any, tokens: string[]) => {
|
|
82
81
|
const baseUrl = `/builder/app/${authStateCookie.appId}/data`
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
// update the DB for the datasource with all the user info
|
|
83
|
+
await doWithDB(authStateCookie.appId, async (db: Database) => {
|
|
84
|
+
let datasource
|
|
85
|
+
try {
|
|
86
|
+
datasource = await db.get(authStateCookie.datasourceId)
|
|
87
|
+
} catch (err: any) {
|
|
88
|
+
if (err.status === 404) {
|
|
89
|
+
ctx.redirect(baseUrl)
|
|
90
|
+
}
|
|
89
91
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
if (!datasource.config) {
|
|
93
|
+
datasource.config = {}
|
|
94
|
+
}
|
|
95
|
+
datasource.config.auth = { type: "google", ...tokens }
|
|
96
|
+
await db.put(datasource)
|
|
97
|
+
ctx.redirect(`${baseUrl}/datasource/${authStateCookie.datasourceId}`)
|
|
98
|
+
})
|
|
93
99
|
}
|
|
94
100
|
)(ctx, next)
|
|
95
101
|
}
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import crypto from "crypto"
|
|
2
|
-
import fs from "fs"
|
|
3
|
-
import zlib from "zlib"
|
|
4
2
|
import env from "../environment"
|
|
5
|
-
import { join } from "path"
|
|
6
3
|
|
|
7
4
|
const ALGO = "aes-256-ctr"
|
|
8
5
|
const SEPARATOR = "-"
|
|
9
6
|
const ITERATIONS = 10000
|
|
7
|
+
const RANDOM_BYTES = 16
|
|
10
8
|
const STRETCH_LENGTH = 32
|
|
11
9
|
|
|
12
|
-
const SALT_LENGTH = 16
|
|
13
|
-
const IV_LENGTH = 16
|
|
14
|
-
|
|
15
10
|
export enum SecretOption {
|
|
16
11
|
API = "api",
|
|
17
12
|
ENCRYPTION = "encryption",
|
|
@@ -36,15 +31,15 @@ export function getSecret(secretOption: SecretOption): string {
|
|
|
36
31
|
return secret
|
|
37
32
|
}
|
|
38
33
|
|
|
39
|
-
function stretchString(
|
|
40
|
-
return crypto.pbkdf2Sync(
|
|
34
|
+
function stretchString(string: string, salt: Buffer) {
|
|
35
|
+
return crypto.pbkdf2Sync(string, salt, ITERATIONS, STRETCH_LENGTH, "sha512")
|
|
41
36
|
}
|
|
42
37
|
|
|
43
38
|
export function encrypt(
|
|
44
39
|
input: string,
|
|
45
40
|
secretOption: SecretOption = SecretOption.API
|
|
46
41
|
) {
|
|
47
|
-
const salt = crypto.randomBytes(
|
|
42
|
+
const salt = crypto.randomBytes(RANDOM_BYTES)
|
|
48
43
|
const stretched = stretchString(getSecret(secretOption), salt)
|
|
49
44
|
const cipher = crypto.createCipheriv(ALGO, stretched, salt)
|
|
50
45
|
const base = cipher.update(input)
|
|
@@ -65,115 +60,3 @@ export function decrypt(
|
|
|
65
60
|
const final = decipher.final()
|
|
66
61
|
return Buffer.concat([base, final]).toString()
|
|
67
62
|
}
|
|
68
|
-
|
|
69
|
-
export async function encryptFile(
|
|
70
|
-
{ dir, filename }: { dir: string; filename: string },
|
|
71
|
-
secret: string
|
|
72
|
-
) {
|
|
73
|
-
const outputFileName = `${filename}.enc`
|
|
74
|
-
|
|
75
|
-
const filePath = join(dir, filename)
|
|
76
|
-
const inputFile = fs.createReadStream(filePath)
|
|
77
|
-
const outputFile = fs.createWriteStream(join(dir, outputFileName))
|
|
78
|
-
|
|
79
|
-
const salt = crypto.randomBytes(SALT_LENGTH)
|
|
80
|
-
const iv = crypto.randomBytes(IV_LENGTH)
|
|
81
|
-
const stretched = stretchString(secret, salt)
|
|
82
|
-
const cipher = crypto.createCipheriv(ALGO, stretched, iv)
|
|
83
|
-
|
|
84
|
-
outputFile.write(salt)
|
|
85
|
-
outputFile.write(iv)
|
|
86
|
-
|
|
87
|
-
inputFile.pipe(zlib.createGzip()).pipe(cipher).pipe(outputFile)
|
|
88
|
-
|
|
89
|
-
return new Promise<{ filename: string; dir: string }>(r => {
|
|
90
|
-
outputFile.on("finish", () => {
|
|
91
|
-
r({
|
|
92
|
-
filename: outputFileName,
|
|
93
|
-
dir,
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async function getSaltAndIV(path: string) {
|
|
100
|
-
const fileStream = fs.createReadStream(path)
|
|
101
|
-
|
|
102
|
-
const salt = await readBytes(fileStream, SALT_LENGTH)
|
|
103
|
-
const iv = await readBytes(fileStream, IV_LENGTH)
|
|
104
|
-
fileStream.close()
|
|
105
|
-
return { salt, iv }
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export async function decryptFile(
|
|
109
|
-
inputPath: string,
|
|
110
|
-
outputPath: string,
|
|
111
|
-
secret: string
|
|
112
|
-
) {
|
|
113
|
-
const { salt, iv } = await getSaltAndIV(inputPath)
|
|
114
|
-
const inputFile = fs.createReadStream(inputPath, {
|
|
115
|
-
start: SALT_LENGTH + IV_LENGTH,
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
const outputFile = fs.createWriteStream(outputPath)
|
|
119
|
-
|
|
120
|
-
const stretched = stretchString(secret, salt)
|
|
121
|
-
const decipher = crypto.createDecipheriv(ALGO, stretched, iv)
|
|
122
|
-
|
|
123
|
-
const unzip = zlib.createGunzip()
|
|
124
|
-
|
|
125
|
-
inputFile.pipe(decipher).pipe(unzip).pipe(outputFile)
|
|
126
|
-
|
|
127
|
-
return new Promise<void>((res, rej) => {
|
|
128
|
-
outputFile.on("finish", () => {
|
|
129
|
-
outputFile.close()
|
|
130
|
-
res()
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
inputFile.on("error", e => {
|
|
134
|
-
outputFile.close()
|
|
135
|
-
rej(e)
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
decipher.on("error", e => {
|
|
139
|
-
outputFile.close()
|
|
140
|
-
rej(e)
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
unzip.on("error", e => {
|
|
144
|
-
outputFile.close()
|
|
145
|
-
rej(e)
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
outputFile.on("error", e => {
|
|
149
|
-
outputFile.close()
|
|
150
|
-
rej(e)
|
|
151
|
-
})
|
|
152
|
-
})
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function readBytes(stream: fs.ReadStream, length: number) {
|
|
156
|
-
return new Promise<Buffer>((resolve, reject) => {
|
|
157
|
-
let bytesRead = 0
|
|
158
|
-
const data: Buffer[] = []
|
|
159
|
-
|
|
160
|
-
stream.on("readable", () => {
|
|
161
|
-
let chunk
|
|
162
|
-
|
|
163
|
-
while ((chunk = stream.read(length - bytesRead)) !== null) {
|
|
164
|
-
data.push(chunk)
|
|
165
|
-
bytesRead += chunk.length
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
resolve(Buffer.concat(data))
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
stream.on("end", () => {
|
|
172
|
-
reject(new Error("Insufficient data in the stream."))
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
stream.on("error", error => {
|
|
176
|
-
reject(error)
|
|
177
|
-
})
|
|
178
|
-
})
|
|
179
|
-
}
|
package/src/security/roles.ts
CHANGED
|
@@ -140,13 +140,9 @@ export function lowerBuiltinRoleID(roleId1?: string, roleId2?: string): string {
|
|
|
140
140
|
* Gets the role object, this is mainly useful for two purposes, to check if the level exists and
|
|
141
141
|
* to check if the role inherits any others.
|
|
142
142
|
* @param {string|null} roleId The level ID to lookup.
|
|
143
|
-
* @param {object|null} opts options for the function, like whether to halt errors, instead return public.
|
|
144
143
|
* @returns {Promise<Role|object|null>} The role object, which may contain an "inherits" property.
|
|
145
144
|
*/
|
|
146
|
-
export async function getRole(
|
|
147
|
-
roleId?: string,
|
|
148
|
-
opts?: { defaultPublic?: boolean }
|
|
149
|
-
): Promise<RoleDoc | undefined> {
|
|
145
|
+
export async function getRole(roleId?: string): Promise<RoleDoc | undefined> {
|
|
150
146
|
if (!roleId) {
|
|
151
147
|
return undefined
|
|
152
148
|
}
|
|
@@ -165,9 +161,6 @@ export async function getRole(
|
|
|
165
161
|
// finalise the ID
|
|
166
162
|
role._id = getExternalRoleID(role._id)
|
|
167
163
|
} catch (err) {
|
|
168
|
-
if (!isBuiltin(roleId) && opts?.defaultPublic) {
|
|
169
|
-
return cloneDeep(BUILTIN_ROLES.PUBLIC)
|
|
170
|
-
}
|
|
171
164
|
// only throw an error if there is no role at all
|
|
172
165
|
if (Object.keys(role).length === 0) {
|
|
173
166
|
throw err
|