dbgate-api-premium 7.0.3 → 7.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dbgate-api-premium",
3
3
  "main": "src/index.js",
4
- "version": "7.0.3",
4
+ "version": "7.0.6",
5
5
  "homepage": "https://dbgate.org/",
6
6
  "repository": {
7
7
  "type": "git",
@@ -30,10 +30,10 @@
30
30
  "compare-versions": "^3.6.0",
31
31
  "cors": "^2.8.5",
32
32
  "cross-env": "^6.0.3",
33
- "dbgate-datalib": "^7.0.3",
33
+ "dbgate-datalib": "^7.0.6",
34
34
  "dbgate-query-splitter": "^4.11.9",
35
- "dbgate-sqltree": "^7.0.3",
36
- "dbgate-tools": "^7.0.3",
35
+ "dbgate-sqltree": "^7.0.6",
36
+ "dbgate-tools": "^7.0.6",
37
37
  "debug": "^4.3.4",
38
38
  "diff": "^5.0.0",
39
39
  "diff2html": "^3.4.13",
@@ -87,7 +87,7 @@
87
87
  "devDependencies": {
88
88
  "@types/fs-extra": "^9.0.11",
89
89
  "@types/lodash": "^4.14.149",
90
- "dbgate-types": "^7.0.3",
90
+ "dbgate-types": "^7.0.6",
91
91
  "env-cmd": "^10.1.0",
92
92
  "jsdoc-to-markdown": "^9.0.5",
93
93
  "node-loader": "^1.0.2",
@@ -55,6 +55,8 @@ function authMiddleware(req, res, next) {
55
55
  '/stream',
56
56
  '/storage/get-connections-for-login-page',
57
57
  '/storage/set-admin-password',
58
+ '/storage/request-password-reset',
59
+ '/storage/reset-password',
58
60
  '/auth/get-providers',
59
61
  '/connections/dblogin-web',
60
62
  '/connections/dblogin-app',
@@ -205,7 +205,7 @@ module.exports = {
205
205
  return storageConnections;
206
206
  }
207
207
  if (portalConnections) {
208
- if (platformInfo.allowShellConnection) return portalConnections;
208
+ if (platformInfo.allowShellConnection) return portalConnections.map(x => encryptConnection(x));
209
209
  return portalConnections.map(maskConnection).filter(x => connectionHasPermission(x, loadedPermissions));
210
210
  }
211
211
  return (await this.datastore.find()).filter(x => connectionHasPermission(x, loadedPermissions));
@@ -243,13 +243,13 @@ module.exports = {
243
243
  subprocess.send({ ...connection, requestDbList });
244
244
  return new Promise((resolve, reject) => {
245
245
  let isWaitingForVolatile = false;
246
-
246
+
247
247
  const cleanup = () => {
248
248
  if (connection._id && pendingTestSubprocesses[connection._id]) {
249
249
  delete pendingTestSubprocesses[connection._id];
250
250
  }
251
251
  };
252
-
252
+
253
253
  subprocess.on('message', resp => {
254
254
  if (handleProcessCommunication(resp, subprocess)) return;
255
255
  // @ts-ignore
@@ -279,8 +279,8 @@ module.exports = {
279
279
  reject(new MissingCredentialsError(missingCredentialsDetail));
280
280
  }
281
281
  });
282
-
283
- subprocess.on('exit', (code) => {
282
+
283
+ subprocess.on('exit', code => {
284
284
  // If exit happens while waiting for volatile, that's expected
285
285
  if (isWaitingForVolatile && code === 0) {
286
286
  cleanup();
@@ -291,8 +291,8 @@ module.exports = {
291
291
  reject(new Error(`Test subprocess exited with code ${code}`));
292
292
  }
293
293
  });
294
-
295
- subprocess.on('error', (err) => {
294
+
295
+ subprocess.on('error', err => {
296
296
  cleanup();
297
297
  reject(err);
298
298
  });
@@ -327,7 +327,7 @@ module.exports = {
327
327
  return testRes;
328
328
  } else {
329
329
  volatileConnections[res._id] = res;
330
-
330
+
331
331
  // Check if there's a pending test subprocess waiting for this volatile connection
332
332
  const pendingTest = pendingTestSubprocesses[conid];
333
333
  if (pendingTest) {
@@ -335,7 +335,7 @@ module.exports = {
335
335
  try {
336
336
  // Send the volatile connection to the waiting subprocess
337
337
  subprocess.send({ ...res, requestDbList, isVolatileResolved: true });
338
-
338
+
339
339
  // Wait for the test result and emit it as an event
340
340
  subprocess.once('message', resp => {
341
341
  if (handleProcessCommunication(resp, subprocess)) return;
@@ -358,7 +358,7 @@ module.exports = {
358
358
  delete pendingTestSubprocesses[conid];
359
359
  }
360
360
  }
361
-
361
+
362
362
  return res;
363
363
  }
364
364
  },
@@ -489,7 +489,7 @@ module.exports = {
489
489
 
490
490
  if (portalConnections) {
491
491
  const res = portalConnections.find(x => x._id == conid) || null;
492
- return mask && !platformInfo.allowShellConnection ? maskConnection(res) : res;
492
+ return mask && !platformInfo.allowShellConnection ? maskConnection(res) : encryptConnection(res);
493
493
  }
494
494
  const res = await this.datastore.get(conid);
495
495
  return res || null;
@@ -16,6 +16,7 @@ const {
16
16
  storageSaveDetailPermissionsDiff,
17
17
  saveStorageTeamFilesPermissions,
18
18
  saveStorageTeamFoldersPermissions,
19
+ storageSqlCommandFmt,
19
20
  } = require('./storageDb');
20
21
  const { hasPermission, loadPermissionsFromRequest } = require('../utility/hasPermission');
21
22
  const { changeSetToSql, removeSchemaFromChangeSet } = require('dbgate-datalib');
@@ -28,9 +29,10 @@ const {
28
29
  getPredefinedPermissions,
29
30
  runQueryOnDriver,
30
31
  safeJsonParse,
32
+ extractErrorLogData,
31
33
  } = require('dbgate-tools');
32
34
  const socket = require('../utility/socket');
33
- const { obtainRefreshedLicense } = require('../utility/authProxy');
35
+ const { obtainRefreshedLicense, sendEmailViaApi } = require('../utility/authProxy');
34
36
  const { datadir } = require('../utility/directories');
35
37
  const {
36
38
  loadEncryptionKeyFromExternal,
@@ -43,6 +45,7 @@ const dataReplicator = require('../shell/dataReplicator');
43
45
  const storageReplicatorItems = require('../utility/storageReplicatorItems');
44
46
  const { sendToAuditLog } = require('../utility/auditlog');
45
47
  const { extractImportEntitiesFromEnv, createStorageFromEnvReplicatorItems } = require('../utility/envtools');
48
+ const { format } = require('date-fns');
46
49
 
47
50
  const logger = getLogger('storage');
48
51
 
@@ -1150,4 +1153,133 @@ module.exports = {
1150
1153
  });
1151
1154
  socket.emitChanged('connection-list-changed');
1152
1155
  },
1156
+
1157
+ requestPasswordReset_meta: true,
1158
+ async requestPasswordReset({ email }, req) {
1159
+ // Check if user exists with this email
1160
+ const users = await storageSelectFmt('select * from ~users where ~email = %v', email);
1161
+
1162
+ if (users.length === 0) {
1163
+ // Don't reveal whether user exists or not for security reasons
1164
+ return { success: true };
1165
+ }
1166
+
1167
+ const user = users[0];
1168
+
1169
+ // Generate a secure random token
1170
+ const token = crypto.randomBytes(32).toString('hex');
1171
+ const now = new Date();
1172
+ const expiresAt = new Date(now.getTime() + 60 * 60 * 1000); // 1 hour expiry
1173
+
1174
+ // Store token in database
1175
+ await storageSqlCommandFmt(
1176
+ 'insert into ~password_reset_tokens (~user_id, ~token, ~created_at, ~expires_at) values (%v, %v, %v, %v)',
1177
+ user.id,
1178
+ token,
1179
+ format(now, "yyyy-MM-dd'T'HH:mm:ss"),
1180
+ format(expiresAt, "yyyy-MM-dd'T'HH:mm:ss")
1181
+ );
1182
+
1183
+ // Generate reset link
1184
+ const resetUrl = `${req.headers.origin || 'http://localhost:3000'}/reset-password.html?token=${token}`;
1185
+
1186
+ // Send email via api.dbgate.io
1187
+ try {
1188
+ await sendEmailViaApi({
1189
+ receiver: email,
1190
+ subject: 'DbGate Password Reset',
1191
+ body: `
1192
+ Hello,
1193
+
1194
+ You requested a password reset for your DbGate account.
1195
+
1196
+ Click the following link to reset your password:
1197
+ ${resetUrl}
1198
+
1199
+ This link will expire in 1 hour.
1200
+
1201
+ If you did not request this password reset, please ignore this email.
1202
+
1203
+ Best regards,
1204
+ DbGate Team
1205
+ `.trim(),
1206
+ });
1207
+
1208
+ sendToAuditLog(req, {
1209
+ category: 'auth',
1210
+ component: 'StorageController',
1211
+ action: 'requestPasswordReset',
1212
+ event: 'password.resetRequested',
1213
+ severity: 'info',
1214
+ detail: { email },
1215
+ message: 'Password reset requested',
1216
+ });
1217
+ } catch (err) {
1218
+ logger.error(extractErrorLogData(err), 'DBGM-00270 Failed to send password reset email');
1219
+ // Cleanup: delete the password reset token that was inserted before sending the email
1220
+ try {
1221
+ await storageSqlCommandFmt(
1222
+ 'delete from ~password_reset_tokens where ~token = %v and ~used_at is null',
1223
+ token
1224
+ );
1225
+ } catch (cleanupErr) {
1226
+ logger.error(
1227
+ extractErrorLogData(cleanupErr),
1228
+ 'DBGM-00274 Failed to clean up password reset token after email failure'
1229
+ );
1230
+ }
1231
+ return { error: 'Failed to send email' };
1232
+ }
1233
+
1234
+ return { success: true };
1235
+ },
1236
+
1237
+ resetPassword_meta: true,
1238
+ async resetPassword({ token, newPassword }, req) {
1239
+ // Find valid token
1240
+ const tokens = await storageSelectFmt(
1241
+ 'select * from ~password_reset_tokens where ~token = %v and ~used_at is null and ~expires_at > %v',
1242
+ token,
1243
+ format(new Date(), "yyyy-MM-dd'T'HH:mm:ss")
1244
+ );
1245
+
1246
+ if (tokens.length === 0) {
1247
+ return { error: 'Invalid or expired token' };
1248
+ }
1249
+
1250
+ const resetToken = tokens[0];
1251
+
1252
+ // Get user
1253
+ const users = await storageSelectFmt('select * from ~users where ~id = %v', resetToken.user_id);
1254
+ if (users.length === 0) {
1255
+ return { error: 'User not found' };
1256
+ }
1257
+
1258
+ const user = users[0];
1259
+
1260
+ await storageSqlCommandFmt(
1261
+ 'update ~users set ~password = %v where ~id = %v',
1262
+ encryptPasswordString(newPassword),
1263
+ user.id
1264
+ );
1265
+
1266
+ // Mark token as used
1267
+ await storageSqlCommandFmt(
1268
+ 'update ~password_reset_tokens set ~used_at = %v where ~id = %v',
1269
+ format(new Date(), "yyyy-MM-dd'T'HH:mm:ss"),
1270
+ resetToken.id
1271
+ );
1272
+
1273
+ sendToAuditLog(req, {
1274
+ category: 'auth',
1275
+ component: 'StorageController',
1276
+ action: 'resetPassword',
1277
+ event: 'password.resetCompleted',
1278
+ severity: 'info',
1279
+ detail: { userId: user.id, login: user.login },
1280
+ message: 'Password reset completed',
1281
+ });
1282
+
1283
+ return { success: true };
1284
+ },
1153
1285
  };
@@ -1,5 +1,5 @@
1
1
 
2
2
  module.exports = {
3
- version: '7.0.3',
4
- buildTime: '2026-02-05T12:32:21.606Z'
3
+ version: '7.0.6',
4
+ buildTime: '2026-02-13T08:57:35.650Z'
5
5
  };
@@ -850,6 +850,84 @@ module.exports = {
850
850
  }
851
851
  ]
852
852
  },
853
+ {
854
+ "pureName": "password_reset_tokens",
855
+ "columns": [
856
+ {
857
+ "pureName": "password_reset_tokens",
858
+ "columnName": "id",
859
+ "dataType": "int",
860
+ "autoIncrement": true,
861
+ "notNull": true
862
+ },
863
+ {
864
+ "pureName": "password_reset_tokens",
865
+ "columnName": "user_id",
866
+ "dataType": "int",
867
+ "notNull": true
868
+ },
869
+ {
870
+ "pureName": "password_reset_tokens",
871
+ "columnName": "token",
872
+ "dataType": "varchar(500)",
873
+ "notNull": true
874
+ },
875
+ {
876
+ "pureName": "password_reset_tokens",
877
+ "columnName": "created_at",
878
+ "dataType": "datetime",
879
+ "notNull": true
880
+ },
881
+ {
882
+ "pureName": "password_reset_tokens",
883
+ "columnName": "expires_at",
884
+ "dataType": "datetime",
885
+ "notNull": true
886
+ },
887
+ {
888
+ "pureName": "password_reset_tokens",
889
+ "columnName": "used_at",
890
+ "dataType": "datetime",
891
+ "notNull": false
892
+ }
893
+ ],
894
+ "foreignKeys": [
895
+ {
896
+ "constraintType": "foreignKey",
897
+ "constraintName": "FK_password_reset_tokens_user_id",
898
+ "pureName": "password_reset_tokens",
899
+ "refTableName": "users",
900
+ "columns": [
901
+ {
902
+ "columnName": "user_id",
903
+ "refColumnName": "id"
904
+ }
905
+ ]
906
+ }
907
+ ],
908
+ "indexes": [
909
+ {
910
+ "constraintName": "idx_token",
911
+ "pureName": "password_reset_tokens",
912
+ "constraintType": "index",
913
+ "columns": [
914
+ {
915
+ "columnName": "token"
916
+ }
917
+ ]
918
+ }
919
+ ],
920
+ "primaryKey": {
921
+ "pureName": "password_reset_tokens",
922
+ "constraintType": "primaryKey",
923
+ "constraintName": "PK_password_reset_tokens",
924
+ "columns": [
925
+ {
926
+ "columnName": "id"
927
+ }
928
+ ]
929
+ }
930
+ },
853
931
  {
854
932
  "pureName": "roles",
855
933
  "columns": [
@@ -23,6 +23,14 @@ const AI_GATEWAY_URL = process.env.LOCAL_AI_GATEWAY
23
23
  ? 'https://aigw.dbgate.udolni.net'
24
24
  : 'https://aigw.dbgate.io';
25
25
 
26
+ const DBGATE_API_URL = process.env.LOCAL_DBGATE_API
27
+ ? 'http://localhost:3115'
28
+ : process.env.PROD_DBGATE_API
29
+ ? 'https://api.dbgate.io'
30
+ : process.env.DEVWEB || process.env.DEVMODE
31
+ ? 'https://api.dbgate.udolni.net'
32
+ : 'https://api.dbgate.io';
33
+
26
34
  let licenseKey = null;
27
35
 
28
36
  function setAuthProxyLicense(value) {
@@ -339,6 +347,22 @@ function getAiGatewayServer() {
339
347
  };
340
348
  }
341
349
 
350
+ async function sendEmailViaApi({ receiver, subject, body }) {
351
+ await axios.default.post(
352
+ `${DBGATE_API_URL}/send-email`,
353
+ {
354
+ receiver,
355
+ subject,
356
+ body,
357
+ },
358
+ {
359
+ headers: {
360
+ 'x-api-key': '88506e22-3b01-4fe7-b1c3-e8779fc8cb90',
361
+ },
362
+ }
363
+ );
364
+ }
365
+
342
366
  module.exports = {
343
367
  isAuthProxySupported,
344
368
  authProxyGetRedirectUrl,
@@ -355,5 +379,6 @@ module.exports = {
355
379
  getLicenseHttpHeaders,
356
380
  tryToGetRefreshedLicense,
357
381
  getAiGatewayServer,
382
+ sendEmailViaApi,
358
383
  // callChatStream,
359
384
  };