dbgate-api-premium 6.5.4 → 6.5.6
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/package.json +5 -5
- package/src/auth/authProvider.js +2 -2
- package/src/auth/storageAuthProvider.js +263 -66
- package/src/controllers/auth.js +89 -18
- package/src/controllers/cloud.js +18 -0
- package/src/controllers/config.js +57 -18
- package/src/controllers/connections.js +5 -5
- package/src/controllers/databaseConnections.js +102 -8
- package/src/controllers/files.js +2 -2
- package/src/controllers/runners.js +24 -1
- package/src/controllers/serverConnections.js +12 -0
- package/src/controllers/sessions.js +14 -1
- package/src/controllers/storage.js +133 -4
- package/src/controllers/storageDb.js +3 -1
- package/src/currentVersion.js +2 -2
- package/src/shell/deployDb.js +1 -1
- package/src/shell/generateDeploySql.js +1 -1
- package/src/shell/importDbFromFolder.js +2 -5
- package/src/storageModel.js +250 -2
- package/src/utility/auditlog.js +288 -0
- package/src/utility/authProxy.js +31 -2
- package/src/utility/checkLicense.js +3 -0
- package/src/utility/cloudIntf.js +46 -3
- package/src/utility/getChartExport.js +11 -3
- package/src/utility/getMapExport.js +1 -1
- package/src/utility/loginchecker.js +69 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// *** This file is part of DbGate Premium ***
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const { getLogger, extractErrorLogData } = require('dbgate-tools');
|
|
5
|
+
const { storageSqlCommandFmt, storageSelectFmt } = require('../controllers/storageDb');
|
|
6
|
+
const logger = getLogger('auditLog');
|
|
7
|
+
|
|
8
|
+
let auditLogQueue = [];
|
|
9
|
+
let isProcessing = false;
|
|
10
|
+
let isPlanned = false;
|
|
11
|
+
|
|
12
|
+
function nullableSum(a, b) {
|
|
13
|
+
const res = (a || 0) + (b || 0);
|
|
14
|
+
return res == 0 ? null : res;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function processAuditLogQueue() {
|
|
18
|
+
do {
|
|
19
|
+
isProcessing = true;
|
|
20
|
+
const elements = [...auditLogQueue];
|
|
21
|
+
auditLogQueue = [];
|
|
22
|
+
|
|
23
|
+
while (elements.length > 0) {
|
|
24
|
+
const element = elements.shift();
|
|
25
|
+
if (!element) continue;
|
|
26
|
+
if (element.sessionId && element.sessionGroup && element.sessionParam) {
|
|
27
|
+
const existingRows = await storageSelectFmt(
|
|
28
|
+
'^select ~id, ~sumint_1, ~sumint_2 from ~audit_log where ~session_id = %v and ~session_group = %v and ~session_param = %v',
|
|
29
|
+
element.sessionId,
|
|
30
|
+
element.sessionGroup,
|
|
31
|
+
element.sessionParam
|
|
32
|
+
);
|
|
33
|
+
if (existingRows && existingRows.length > 0) {
|
|
34
|
+
const existing = existingRows[0];
|
|
35
|
+
await storageSqlCommandFmt(
|
|
36
|
+
'^update ~audit_log set ~sumint_1 = %v, ~sumint_2 = %v, ~modified = %v where ~id = %v',
|
|
37
|
+
nullableSum(element.sumint1, existing.sumint_1),
|
|
38
|
+
nullableSum(element.sumint2, existing.sumint_2),
|
|
39
|
+
element.created,
|
|
40
|
+
existing.id
|
|
41
|
+
);
|
|
42
|
+
// only update existing session
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
let connectionData = null;
|
|
48
|
+
if (element.conid) {
|
|
49
|
+
const connections = await storageSelectFmt('^select * from ~connections where ~conid = %v', element.conid);
|
|
50
|
+
if (connections[0])
|
|
51
|
+
connectionData = _.pick(connections[0], [
|
|
52
|
+
'displayName',
|
|
53
|
+
'engine',
|
|
54
|
+
'displayName',
|
|
55
|
+
'databaseUrl',
|
|
56
|
+
'singleDatabase',
|
|
57
|
+
'server',
|
|
58
|
+
'databaseFile',
|
|
59
|
+
'useSshTunnel',
|
|
60
|
+
'sshHost',
|
|
61
|
+
'defaultDatabase',
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const detailText = _.isPlainObject(element.detail) ? JSON.stringify(element.detail) : element.detail || null;
|
|
66
|
+
const connectionDataText = connectionData ? JSON.stringify(connectionData) : null;
|
|
67
|
+
await storageSqlCommandFmt(
|
|
68
|
+
`^insert ^into ~audit_log (
|
|
69
|
+
~user_id, ~user_login, ~created, ~category, ~component, ~event, ~detail, ~detail_full_length, ~action, ~severity,
|
|
70
|
+
~conid, ~database, ~schema_name, ~pure_name, ~sumint_1, ~sumint_2, ~session_id, ~session_group, ~session_param, ~connection_data, ~message)
|
|
71
|
+
values (%v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v)`,
|
|
72
|
+
element.userId || null,
|
|
73
|
+
element.login || null,
|
|
74
|
+
element.created,
|
|
75
|
+
element.category || null,
|
|
76
|
+
element.component || null,
|
|
77
|
+
element.event || null,
|
|
78
|
+
detailText?.slice(0, 1000) || null,
|
|
79
|
+
detailText?.length || null,
|
|
80
|
+
element.action || null,
|
|
81
|
+
element.severity || 'info',
|
|
82
|
+
element.conid || null,
|
|
83
|
+
element.database || null,
|
|
84
|
+
element.schemaName || null,
|
|
85
|
+
element.pureName || null,
|
|
86
|
+
element.sumint1 || null,
|
|
87
|
+
element.sumint2 || null,
|
|
88
|
+
element.sessionId || null,
|
|
89
|
+
element.sessionGroup || null,
|
|
90
|
+
element.sessionParam || null,
|
|
91
|
+
connectionDataText?.slice(0, 1000) || null,
|
|
92
|
+
element.message || null
|
|
93
|
+
);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
logger.error(extractErrorLogData(err), 'Error processing audit log entry');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
isProcessing = false;
|
|
100
|
+
} while (auditLogQueue.length > 0);
|
|
101
|
+
isPlanned = false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function sendToAuditLog(
|
|
105
|
+
req,
|
|
106
|
+
{
|
|
107
|
+
category,
|
|
108
|
+
component,
|
|
109
|
+
event,
|
|
110
|
+
detail = null,
|
|
111
|
+
action,
|
|
112
|
+
severity = 'info',
|
|
113
|
+
conid = null,
|
|
114
|
+
database = null,
|
|
115
|
+
schemaName = null,
|
|
116
|
+
pureName = null,
|
|
117
|
+
sumint1 = null,
|
|
118
|
+
sumint2 = null,
|
|
119
|
+
sessionGroup = null,
|
|
120
|
+
sessionParam = null,
|
|
121
|
+
message = null,
|
|
122
|
+
}
|
|
123
|
+
) {
|
|
124
|
+
if (!process.env.STORAGE_DATABASE) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const config = require('../controllers/config');
|
|
128
|
+
const settings = await config.getCachedSettings();
|
|
129
|
+
if (settings?.['storage.useAuditLog'] != 'true') {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const { login, userId } = req?.user || {};
|
|
134
|
+
const sessionId = req?.headers?.['x-api-session-id'];
|
|
135
|
+
|
|
136
|
+
auditLogQueue.push({
|
|
137
|
+
userId,
|
|
138
|
+
login,
|
|
139
|
+
created: new Date().getTime(),
|
|
140
|
+
category,
|
|
141
|
+
component,
|
|
142
|
+
event,
|
|
143
|
+
detail,
|
|
144
|
+
action,
|
|
145
|
+
severity,
|
|
146
|
+
conid,
|
|
147
|
+
database,
|
|
148
|
+
schemaName,
|
|
149
|
+
pureName,
|
|
150
|
+
sumint1,
|
|
151
|
+
sumint2,
|
|
152
|
+
sessionId,
|
|
153
|
+
sessionGroup,
|
|
154
|
+
sessionParam,
|
|
155
|
+
message,
|
|
156
|
+
});
|
|
157
|
+
if (!isProcessing && !isPlanned) {
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
isPlanned = true;
|
|
160
|
+
processAuditLogQueue();
|
|
161
|
+
}, 0);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function maskLogFields(script) {
|
|
166
|
+
return _.cloneDeepWith(script, (value, key) => {
|
|
167
|
+
if (_.isString(key) && key.toLowerCase().includes('password')) {
|
|
168
|
+
return '****';
|
|
169
|
+
}
|
|
170
|
+
if (key == 'query') {
|
|
171
|
+
return '****';
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function extractConnectionId(connection) {
|
|
177
|
+
if (
|
|
178
|
+
connection?.server == process.env.STORAGE_SERVER &&
|
|
179
|
+
connection?.database == process.env.STORAGE_DATABASE &&
|
|
180
|
+
connection?.port == process.env.STORAGE_PORT
|
|
181
|
+
) {
|
|
182
|
+
return '__storage';
|
|
183
|
+
}
|
|
184
|
+
if (connection?.conid) {
|
|
185
|
+
return connection.conid;
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function analyseJsonRunnerScript(script) {
|
|
191
|
+
const [assignSource, assignTarget, copyStream] = _.isArray(script?.commands) ? script?.commands : [];
|
|
192
|
+
if (assignSource?.type != 'assign') {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
if (assignTarget?.type != 'assign') {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
if (copyStream?.type != 'copyStream') {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (assignTarget?.functionName == 'tableWriter') {
|
|
203
|
+
const pureName = assignTarget?.props?.pureName;
|
|
204
|
+
const schemaName = assignTarget?.props?.schemaName;
|
|
205
|
+
const connection = assignTarget?.props?.connection;
|
|
206
|
+
if (pureName && connection) {
|
|
207
|
+
return {
|
|
208
|
+
category: 'import',
|
|
209
|
+
component: 'RunnersController',
|
|
210
|
+
event: 'import.table',
|
|
211
|
+
action: 'import',
|
|
212
|
+
severity: 'info',
|
|
213
|
+
message: `Importing table ${pureName}`,
|
|
214
|
+
pureName: pureName,
|
|
215
|
+
conid: extractConnectionId(connection),
|
|
216
|
+
database: connection?.database,
|
|
217
|
+
schemaName: schemaName,
|
|
218
|
+
detail: maskLogFields(script),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (assignSource?.functionName == 'tableReader') {
|
|
225
|
+
const pureName = assignSource?.props?.pureName;
|
|
226
|
+
const schemaName = assignSource?.props?.schemaName;
|
|
227
|
+
const connection = assignSource?.props?.connection;
|
|
228
|
+
if (pureName && connection) {
|
|
229
|
+
return {
|
|
230
|
+
category: 'export',
|
|
231
|
+
component: 'RunnersController',
|
|
232
|
+
event: 'export.table',
|
|
233
|
+
action: 'export',
|
|
234
|
+
severity: 'info',
|
|
235
|
+
message: `Exporting table ${pureName}`,
|
|
236
|
+
pureName: pureName,
|
|
237
|
+
conid: extractConnectionId(connection),
|
|
238
|
+
database: connection?.database,
|
|
239
|
+
schemaName: schemaName,
|
|
240
|
+
detail: maskLogFields(script),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (assignSource?.functionName == 'queryReader') {
|
|
247
|
+
const connection = assignSource?.props?.connection;
|
|
248
|
+
if (connection) {
|
|
249
|
+
return {
|
|
250
|
+
category: 'export',
|
|
251
|
+
component: 'RunnersController',
|
|
252
|
+
event: 'export.query',
|
|
253
|
+
action: 'export',
|
|
254
|
+
severity: 'info',
|
|
255
|
+
message: 'Exporting query',
|
|
256
|
+
conid: extractConnectionId(connection),
|
|
257
|
+
database: connection?.database,
|
|
258
|
+
detail: maskLogFields(script),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function logJsonRunnerScript(req, script) {
|
|
268
|
+
const analysed = analyseJsonRunnerScript(script);
|
|
269
|
+
|
|
270
|
+
if (analysed) {
|
|
271
|
+
sendToAuditLog(req, analysed);
|
|
272
|
+
} else {
|
|
273
|
+
sendToAuditLog(req, {
|
|
274
|
+
category: 'shell',
|
|
275
|
+
component: 'RunnersController',
|
|
276
|
+
event: 'script.run.json',
|
|
277
|
+
action: 'script',
|
|
278
|
+
severity: 'info',
|
|
279
|
+
detail: maskLogFields(script),
|
|
280
|
+
message: 'Running JSON script',
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
module.exports = {
|
|
286
|
+
sendToAuditLog,
|
|
287
|
+
logJsonRunnerScript,
|
|
288
|
+
};
|
package/src/utility/authProxy.js
CHANGED
|
@@ -31,12 +31,16 @@ function isAuthProxySupported() {
|
|
|
31
31
|
return true;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
function getE2ETestHeaders() {
|
|
35
|
+
return processArgs.runE2eTests ? { 'x-api-key': 'bcf6e1a0-5763-4060-9391-18fda005722d' } : {};
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
function getAxiosParamsWithLicense() {
|
|
35
39
|
return {
|
|
36
40
|
headers: {
|
|
37
41
|
'Content-Type': 'application/json',
|
|
38
42
|
Authorization: `Bearer ${licenseKey ?? process.env.DBGATE_LICENSE}`,
|
|
39
|
-
|
|
43
|
+
...getE2ETestHeaders(),
|
|
40
44
|
},
|
|
41
45
|
};
|
|
42
46
|
}
|
|
@@ -44,10 +48,13 @@ function getAxiosParamsWithLicense() {
|
|
|
44
48
|
function getLicenseHttpHeaders() {
|
|
45
49
|
const licenseValue = licenseKey ?? process.env.DBGATE_LICENSE;
|
|
46
50
|
if (!licenseValue) {
|
|
47
|
-
return {
|
|
51
|
+
return {
|
|
52
|
+
...getE2ETestHeaders(),
|
|
53
|
+
};
|
|
48
54
|
}
|
|
49
55
|
return {
|
|
50
56
|
'x-license': licenseValue,
|
|
57
|
+
...getE2ETestHeaders(),
|
|
51
58
|
};
|
|
52
59
|
}
|
|
53
60
|
|
|
@@ -178,6 +185,27 @@ async function obtainRefreshedLicense() {
|
|
|
178
185
|
}
|
|
179
186
|
}
|
|
180
187
|
|
|
188
|
+
async function tryToGetRefreshedLicense(oldLicenseKey) {
|
|
189
|
+
try {
|
|
190
|
+
const respToken = await axios.default.post(
|
|
191
|
+
`${AUTH_PROXY_URL}/refresh-license`,
|
|
192
|
+
{},
|
|
193
|
+
{
|
|
194
|
+
headers: {
|
|
195
|
+
'Content-Type': 'application/json',
|
|
196
|
+
Authorization: `Bearer ${oldLicenseKey}`,
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
return respToken.data;
|
|
201
|
+
} catch (err) {
|
|
202
|
+
return {
|
|
203
|
+
status: 'error',
|
|
204
|
+
message: err.message,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
181
209
|
/**
|
|
182
210
|
* @param {import('dbgate-types').DatabaseInfo} structure
|
|
183
211
|
* @returns {import('dbgate-types').DatabaseInfoTiny}
|
|
@@ -296,4 +324,5 @@ module.exports = {
|
|
|
296
324
|
callCompleteOnCursorApi,
|
|
297
325
|
callRefactorSqlQueryApi,
|
|
298
326
|
getLicenseHttpHeaders,
|
|
327
|
+
tryToGetRefreshedLicense,
|
|
299
328
|
};
|
|
@@ -147,6 +147,8 @@ function checkLicenseKey(licenseKey) {
|
|
|
147
147
|
expiration: new Date(decoded.exp * 1000).toISOString(),
|
|
148
148
|
daysLeft: Math.round((decoded.exp * 1000 - Date.now()) / (24 * 60 * 60 * 1000)),
|
|
149
149
|
licenseTypeObj,
|
|
150
|
+
licenseId: decoded.licenseId,
|
|
151
|
+
users: decoded.users,
|
|
150
152
|
};
|
|
151
153
|
} catch (err) {
|
|
152
154
|
try {
|
|
@@ -159,6 +161,7 @@ function checkLicenseKey(licenseKey) {
|
|
|
159
161
|
status: 'error',
|
|
160
162
|
isExpired: true,
|
|
161
163
|
errorMessage: 'License key is expired',
|
|
164
|
+
expiration: exp * 1000,
|
|
162
165
|
};
|
|
163
166
|
}
|
|
164
167
|
}
|
package/src/utility/cloudIntf.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
|
+
const crypto = require('crypto');
|
|
2
3
|
const fs = require('fs-extra');
|
|
3
4
|
const _ = require('lodash');
|
|
4
5
|
const path = require('path');
|
|
@@ -34,11 +35,12 @@ const DBGATE_CLOUD_URL = process.env.LOCAL_DBGATE_CLOUD
|
|
|
34
35
|
? 'https://cloud.dbgate.udolni.net'
|
|
35
36
|
: 'https://cloud.dbgate.io';
|
|
36
37
|
|
|
37
|
-
async function createDbGateIdentitySession(client) {
|
|
38
|
+
async function createDbGateIdentitySession(client, redirectUri) {
|
|
38
39
|
const resp = await axios.default.post(
|
|
39
40
|
`${DBGATE_IDENTITY_URL}/api/create-session`,
|
|
40
41
|
{
|
|
41
42
|
client,
|
|
43
|
+
redirectUri,
|
|
42
44
|
},
|
|
43
45
|
{
|
|
44
46
|
headers: {
|
|
@@ -70,7 +72,7 @@ function startCloudTokenChecking(sid, callback) {
|
|
|
70
72
|
});
|
|
71
73
|
// console.log('CHECK RESP:', resp.data);
|
|
72
74
|
|
|
73
|
-
if (resp.data
|
|
75
|
+
if (resp.data?.email) {
|
|
74
76
|
clearInterval(interval);
|
|
75
77
|
callback(resp.data);
|
|
76
78
|
}
|
|
@@ -80,6 +82,34 @@ function startCloudTokenChecking(sid, callback) {
|
|
|
80
82
|
}, 500);
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
async function readCloudTokenHolder(sid) {
|
|
86
|
+
const resp = await axios.default.get(`${DBGATE_IDENTITY_URL}/api/get-token/${sid}`, {
|
|
87
|
+
headers: {
|
|
88
|
+
...getLicenseHttpHeaders(),
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
if (resp.data?.email) {
|
|
92
|
+
return resp.data;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function readCloudTestTokenHolder(email) {
|
|
98
|
+
const resp = await axios.default.post(
|
|
99
|
+
`${DBGATE_IDENTITY_URL}/api/test-token`,
|
|
100
|
+
{ email },
|
|
101
|
+
{
|
|
102
|
+
headers: {
|
|
103
|
+
...getLicenseHttpHeaders(),
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
if (resp.data?.email) {
|
|
108
|
+
return resp.data;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
83
113
|
async function loadCloudFiles() {
|
|
84
114
|
try {
|
|
85
115
|
const fileContent = await fs.readFile(path.join(datadir(), 'cloud-files.jsonl'), 'utf-8');
|
|
@@ -187,7 +217,7 @@ async function updateCloudFiles(isRefresh) {
|
|
|
187
217
|
{
|
|
188
218
|
headers: {
|
|
189
219
|
...getLicenseHttpHeaders(),
|
|
190
|
-
...(await
|
|
220
|
+
...(await getCloudInstanceHeaders()),
|
|
191
221
|
'x-app-version': currentVersion.version,
|
|
192
222
|
},
|
|
193
223
|
}
|
|
@@ -271,6 +301,17 @@ async function callCloudApiGet(endpoint, signinHolder = null, additionalHeaders
|
|
|
271
301
|
return resp.data;
|
|
272
302
|
}
|
|
273
303
|
|
|
304
|
+
async function getCloudInstanceHeaders() {
|
|
305
|
+
if (!(await fs.exists(path.join(datadir(), 'cloud-instance.txt')))) {
|
|
306
|
+
const newInstanceId = crypto.randomUUID();
|
|
307
|
+
await fs.writeFile(path.join(datadir(), 'cloud-instance.txt'), newInstanceId);
|
|
308
|
+
}
|
|
309
|
+
const instanceId = await fs.readFile(path.join(datadir(), 'cloud-instance.txt'), 'utf-8');
|
|
310
|
+
return {
|
|
311
|
+
'x-cloud-instance': instanceId,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
274
315
|
async function callCloudApiPost(endpoint, body, signinHolder = null) {
|
|
275
316
|
if (!signinHolder) {
|
|
276
317
|
signinHolder = await getCloudSigninHolder();
|
|
@@ -396,4 +437,6 @@ module.exports = {
|
|
|
396
437
|
loadCachedCloudConnection,
|
|
397
438
|
putCloudContent,
|
|
398
439
|
removeCloudCachedConnection,
|
|
440
|
+
readCloudTokenHolder,
|
|
441
|
+
readCloudTestTokenHolder,
|
|
399
442
|
};
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
const getChartExport = (title, config, imageFile) => {
|
|
1
|
+
const getChartExport = (title, config, imageFile, plugins) => {
|
|
2
|
+
const PLUGIN_TAGS = {
|
|
3
|
+
zoom: '<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-zoom/1.2.0/chartjs-plugin-zoom.min.js" integrity="sha512-TT0wAMqqtjXVzpc48sI0G84rBP+oTkBZPgeRYIOVRGUdwJsyS3WPipsNh///ay2LJ+onCM23tipnz6EvEy2/UA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>',
|
|
4
|
+
dataLabels:
|
|
5
|
+
'<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.2.0/chartjs-plugin-datalabels.min.js" integrity="sha512-JPcRR8yFa8mmCsfrw4TNte1ZvF1e3+1SdGMslZvmrzDYxS69J7J49vkFL8u6u8PlPJK+H3voElBtUCzaXj+6ig==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>',
|
|
6
|
+
outlabels:
|
|
7
|
+
'<script src="https://cdn.jsdelivr.net/npm/@energiency/chartjs-plugin-piechart-outlabels@1.3.4/dist/chartjs-plugin-piechart-outlabels.min.js"></script>',
|
|
8
|
+
};
|
|
9
|
+
|
|
2
10
|
return `<html>
|
|
3
11
|
<meta charset='utf-8'>
|
|
4
12
|
|
|
@@ -8,7 +16,7 @@ const getChartExport = (title, config, imageFile) => {
|
|
|
8
16
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js" integrity="sha512-qTXRIMyZIFb8iQcfjXWCO8+M5Tbc38Qi5WzdPOYZHIlZpzBHG3L3by84BBBOiRGiEb7KKtAOAs5qYdUiZiQNNQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
9
17
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-adapter-moment/1.0.0/chartjs-adapter-moment.min.js" integrity="sha512-oh5t+CdSBsaVVAvxcZKy3XJdP7ZbYUBSRCXDTVn0ODewMDDNnELsrG9eDm8rVZAQg7RsDD/8K3MjPAFB13o6eA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
10
18
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js" integrity="sha512-UXumZrZNiOwnTcZSHLOfcTs0aos2MzBWHXOHOuB0J/R44QB0dwY5JgfbvljXcklVf65Gc4El6RjZ+lnwd2az2g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
11
|
-
|
|
19
|
+
${plugins.map(plugin => PLUGIN_TAGS[plugin] ?? '')}
|
|
12
20
|
|
|
13
21
|
<style>
|
|
14
22
|
a { text-decoration: none }
|
|
@@ -45,7 +53,7 @@ const getChartExport = (title, config, imageFile) => {
|
|
|
45
53
|
</div>
|
|
46
54
|
|
|
47
55
|
<div class="footer">
|
|
48
|
-
Exported from <a href='https://dbgate.
|
|
56
|
+
Exported from <a href='https://dbgate.io/' target='_blank'>DbGate</a>, powered by <a href='https://www.chartjs.org/' target='_blank'>Chart.js</a>
|
|
49
57
|
</div>
|
|
50
58
|
</body>
|
|
51
59
|
|
|
@@ -18,7 +18,7 @@ const getMapExport = (geoJson) => {
|
|
|
18
18
|
leaflet
|
|
19
19
|
.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
20
20
|
maxZoom: 19,
|
|
21
|
-
attribution: '<a href="https://dbgate.
|
|
21
|
+
attribution: '<a href="https://dbgate.io" title="Exported from DbGate">DbGate</a> | © OpenStreetMap',
|
|
22
22
|
})
|
|
23
23
|
.addTo(map);
|
|
24
24
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// *** This file is part of DbGate Premium ***
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const { sendToAuditLog } = require('./auditlog');
|
|
5
|
+
const { checkLicense } = require('./checkLicense');
|
|
6
|
+
const LOGIN_LIMIT_ERROR = 'Your limit of concurrent logins has been reached';
|
|
7
|
+
const jwt = require('jsonwebtoken');
|
|
8
|
+
|
|
9
|
+
// map string (user key) => time to expiration
|
|
10
|
+
let activeLoggedUsers = {};
|
|
11
|
+
|
|
12
|
+
// map string (user key) => token expiration time
|
|
13
|
+
let activeLoggedTokens = {};
|
|
14
|
+
|
|
15
|
+
function markUserAsActive(licenseUid, accessToken) {
|
|
16
|
+
activeLoggedUsers[licenseUid] = Date.now() + 60 * 1000; // mark user as active for 1 minute
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function isLoginLicensed(req, licenseUid) {
|
|
20
|
+
const license = await checkLicense();
|
|
21
|
+
activeLoggedUsers = _.pickBy(activeLoggedUsers, value => value > Date.now());
|
|
22
|
+
activeLoggedTokens = _.pickBy(activeLoggedTokens, value => value > Date.now());
|
|
23
|
+
if (licenseUid in activeLoggedUsers) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
if (licenseUid in activeLoggedTokens) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (license.licenseId == 'f0346efe-ebc2-4822-9a83-4b4668a897e5' && license?.users != null) {
|
|
30
|
+
const uniqUsers = _.uniq([...Object.keys(activeLoggedUsers), ...Object.keys(activeLoggedTokens)]);
|
|
31
|
+
const currentUserCount = uniqUsers.length;
|
|
32
|
+
|
|
33
|
+
if (currentUserCount >= license?.users) {
|
|
34
|
+
sendToAuditLog(req, {
|
|
35
|
+
category: 'auth',
|
|
36
|
+
component: 'StorageAuthProvider',
|
|
37
|
+
action: 'loginFail',
|
|
38
|
+
event: 'login.licenseFailed',
|
|
39
|
+
severity: 'warn',
|
|
40
|
+
detail: { licenseUid, activeUsers: currentUserCount, licenseUsers: license?.users },
|
|
41
|
+
message: `Login failed, too many active users: ${currentUserCount + 1}, license allows only ${license?.users}`,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function markTokenAsLoggedIn(licenseUid, token) {
|
|
51
|
+
if (licenseUid != 'anonymous') {
|
|
52
|
+
const decoded = jwt.decode(token);
|
|
53
|
+
activeLoggedTokens[licenseUid] = decoded.exp * 1000;
|
|
54
|
+
}
|
|
55
|
+
activeLoggedUsers[licenseUid] = Date.now() + 60 * 1000; // mark user as active for 1 minute
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function markLoginAsLoggedOut(licenseUid) {
|
|
59
|
+
delete activeLoggedUsers[licenseUid];
|
|
60
|
+
delete activeLoggedTokens[licenseUid];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
markUserAsActive,
|
|
65
|
+
markTokenAsLoggedIn,
|
|
66
|
+
isLoginLicensed,
|
|
67
|
+
LOGIN_LIMIT_ERROR,
|
|
68
|
+
markLoginAsLoggedOut,
|
|
69
|
+
};
|