admindb 2.2.1 → 2.3.1

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.
Files changed (89) hide show
  1. package/README.md +368 -364
  2. package/dist/app.js +25 -16
  3. package/dist/auth/config.d.ts +1 -1
  4. package/dist/auth/config.js +18 -30
  5. package/dist/auth/crypto.js +11 -21
  6. package/dist/auth/middleware.js +9 -16
  7. package/dist/auth/routes.js +9 -15
  8. package/dist/cli/args.d.ts +1 -1
  9. package/dist/cli/args.js +20 -29
  10. package/dist/cli/config.d.ts +1 -1
  11. package/dist/cli/config.js +15 -18
  12. package/dist/cli/runner.js +13 -13
  13. package/dist/data/chain.d.ts +1 -1
  14. package/dist/data/chain.js +40 -617
  15. package/dist/data/datasets.d.ts +1 -1
  16. package/dist/data/datasets.js +43 -1
  17. package/dist/data/detector.js +45 -21
  18. package/dist/data/engine.d.ts +1 -1
  19. package/dist/data/engine.js +483 -142
  20. package/dist/data/index.d.ts +1 -1
  21. package/dist/data/index.js +8 -0
  22. package/dist/data/prng.d.ts +1 -0
  23. package/dist/data/prng.js +73 -0
  24. package/dist/data/registry.d.ts +1 -0
  25. package/dist/data/registry.js +805 -0
  26. package/dist/data/schema-graph.d.ts +1 -0
  27. package/dist/data/schema-graph.js +210 -0
  28. package/dist/data/strategies.d.ts +1 -1
  29. package/dist/data/strategies.js +44 -283
  30. package/dist/data/templates.d.ts +1 -0
  31. package/dist/data/templates.js +134 -0
  32. package/dist/data/types.d.ts +1 -1
  33. package/dist/data/validator.d.ts +1 -0
  34. package/dist/data/validator.js +160 -0
  35. package/dist/db/database.js +9 -14
  36. package/dist/db/export.js +32 -55
  37. package/dist/db/filters.d.ts +1 -1
  38. package/dist/db/filters.js +15 -38
  39. package/dist/db/introspection.js +4 -7
  40. package/dist/db/manager.d.ts +1 -1
  41. package/dist/db/manager.js +48 -53
  42. package/dist/db/migrations.js +7 -14
  43. package/dist/db/postgres.js +30 -78
  44. package/dist/index.d.ts +1 -1
  45. package/dist/public/css/app.css +1 -1
  46. package/dist/public/js/browse.js +1 -1
  47. package/dist/public/js/inspector.js +1 -1
  48. package/dist/public/js/query.js +1 -1
  49. package/dist/public/js/seed.js +1 -1
  50. package/dist/routes/api/erd.js +3 -8
  51. package/dist/routes/api/helpers.d.ts +1 -1
  52. package/dist/routes/api/helpers.js +75 -25
  53. package/dist/routes/api/import-export.js +1 -2
  54. package/dist/routes/api/query.js +2 -6
  55. package/dist/routes/api/rows.js +4 -8
  56. package/dist/routes/api/seed.js +35 -21
  57. package/dist/routes/api/tables.js +15 -30
  58. package/dist/routes/databases.js +8 -26
  59. package/dist/routes/pages.js +67 -18
  60. package/dist/serverless.js +31 -44
  61. package/dist/sql/generator.d.ts +1 -1
  62. package/dist/sql/generator.js +35 -109
  63. package/dist/types/api.d.ts +1 -1
  64. package/dist/utils/common.d.ts +1 -1
  65. package/dist/utils/common.js +45 -85
  66. package/dist/utils/csv.d.ts +1 -1
  67. package/dist/utils/csv.js +11 -17
  68. package/dist/utils/datatype.js +46 -60
  69. package/dist/utils/icons.js +8 -8
  70. package/dist/views/layouts/main.hbs +1 -87
  71. package/dist/views/pages/databases.hbs +1 -233
  72. package/dist/views/pages/designer.hbs +1 -89
  73. package/dist/views/pages/erd.hbs +1 -1241
  74. package/dist/views/pages/error.hbs +1 -10
  75. package/dist/views/pages/form.hbs +1 -59
  76. package/dist/views/pages/home.hbs +1 -220
  77. package/dist/views/pages/info.hbs +1 -107
  78. package/dist/views/pages/login.hbs +1 -98
  79. package/dist/views/pages/query.hbs +1 -129
  80. package/dist/views/pages/schema.hbs +1 -298
  81. package/dist/views/pages/seed-select.hbs +251 -0
  82. package/dist/views/pages/seed.hbs +256 -414
  83. package/dist/views/pages/table.hbs +1 -327
  84. package/dist/views/partials/navbar.hbs +1 -83
  85. package/dist/views/partials/sidebar.hbs +1 -129
  86. package/docs/API.md +149 -149
  87. package/docs/EXAMPLES.md +579 -579
  88. package/docs/SECURITY.md +265 -265
  89. package/package.json +80 -79
package/dist/app.js CHANGED
@@ -68,27 +68,35 @@ function addErrorHandlers(app, logger) {
68
68
  }
69
69
  function createSingleDbApp(options) {
70
70
  const isServerless = options.serverless !== undefined ? options.serverless : (0, serverless_1.isServerlessEnvironment)();
71
- const rawTarget = options.connection || options.dbPath || '';
71
+ const envTarget = process.env.ADMINDB_CONNECTION ||
72
+ process.env.DATABASE_URL ||
73
+ process.env.POSTGRES_URL ||
74
+ process.env.PG_CONNECTION ||
75
+ '';
76
+ const rawTarget = options.connection || options.dbPath || ((0, common_1.isPostgresConnectionString)(envTarget) ? envTarget : '');
72
77
  const isPg = Boolean(options.pgOptions) ||
73
78
  (0, common_1.isPostgresConnectionString)(rawTarget) ||
74
79
  options.db?.dialect === 'postgres';
75
80
  const readonly = options.readonly !== undefined
76
81
  ? Boolean(options.readonly)
77
- : (isServerless && !isPg);
82
+ : (isServerless ? !isPg : false);
78
83
  const basePath = (options.basePath ?? '').replace(/\/+$/, '');
79
84
  const logger = options.logger ?? (0, logger_1.createLogger)(options.logLevel ?? 'info', 'admindb');
80
85
  let db;
81
86
  if (options.db) {
82
87
  db = options.db;
88
+ if (options.readonly !== undefined && db.isReadOnly !== Boolean(options.readonly)) {
89
+ try {
90
+ Object.defineProperty(db, 'isReadOnly', { value: Boolean(options.readonly), configurable: true });
91
+ }
92
+ catch { }
93
+ }
94
+ }
95
+ else if (isPg) {
96
+ db = new postgres_1.PostgresDatabase(options.pgOptions || rawTarget, logger, { readonly });
83
97
  }
84
98
  else {
85
- if (isPg) {
86
- const conn = options.pgOptions || rawTarget;
87
- db = new postgres_1.PostgresDatabase(conn, logger, { readonly });
88
- }
89
- else {
90
- db = new database_1.SqliteDatabase(options.dbPath ?? 'admindb.db', logger, { readonly });
91
- }
99
+ db = new database_1.SqliteDatabase(options.dbPath ?? 'admindb.db', logger, { readonly });
92
100
  }
93
101
  const databasesUrl = options.databasesUrl;
94
102
  const authConfig = (0, auth_1.resolveAuthConfig)(options.auth);
@@ -138,8 +146,9 @@ function createSingleDbApp(options) {
138
146
  }
139
147
  function createManagerApp(options) {
140
148
  const isServerless = options.serverless !== undefined ? options.serverless : (0, serverless_1.isServerlessEnvironment)();
141
- const readonly = isServerless || Boolean(options.readonly);
149
+ const explicitReadonly = options.readonly !== undefined ? Boolean(options.readonly) : undefined;
142
150
  const manager = options.manager;
151
+ const globalReadonly = explicitReadonly ?? manager.isReadOnly;
143
152
  const basePath = (options.basePath ?? '').replace(/\/+$/, '');
144
153
  const logger = options.logger ?? (0, logger_1.createLogger)(options.logLevel ?? 'info', 'admindb');
145
154
  const authConfig = (0, auth_1.resolveAuthConfig)(options.auth);
@@ -160,7 +169,7 @@ function createManagerApp(options) {
160
169
  res.locals.dbId = null;
161
170
  res.locals.databasesUrl = null;
162
171
  res.locals.databasesMode = true;
163
- res.locals.readonly = readonly;
172
+ res.locals.readonly = globalReadonly;
164
173
  res.locals.serverless = isServerless;
165
174
  res.locals.version = APP_VERSION;
166
175
  res.locals.authEnabled = authConfig.enabled;
@@ -181,7 +190,7 @@ function createManagerApp(options) {
181
190
  manager,
182
191
  logger,
183
192
  basePath,
184
- readonly,
193
+ readonly: globalReadonly,
185
194
  allowBrowse: isServerless ? false : options.allowBrowse,
186
195
  browseRoot: options.browseRoot,
187
196
  invalidate: (id) => subApps.delete(id),
@@ -201,22 +210,22 @@ function createManagerApp(options) {
201
210
  const rawRo = req.query.readonly;
202
211
  if (rawRo !== undefined) {
203
212
  const explicitRo = rawRo === '1' || rawRo === 'true';
204
- if (!readonly || explicitRo) {
213
+ if (!globalReadonly || explicitRo) {
205
214
  manager.setReadonly(dbId, explicitRo);
206
215
  subApps.delete(dbId);
207
216
  }
208
217
  }
209
218
  let sub = subApps.get(dbId);
210
219
  if (!sub) {
211
- const subBase = `${basePath ? basePath : ''}/${encodeURIComponent(dbId)}`;
212
- const effectiveRo = readonly || manager.isDbReadOnly(dbId);
220
+ const subBase = `${basePath || ''}/${encodeURIComponent(dbId)}`;
221
+ const effectiveRo = globalReadonly ? true : manager.isDbReadOnly(dbId);
213
222
  sub = createSingleDbApp({
214
223
  db: manager.open(dbId, effectiveRo),
215
224
  basePath: subBase,
216
225
  logger,
217
226
  logLevel: options.logLevel,
218
227
  dbId,
219
- databasesUrl: `${basePath ? basePath : ''}/`,
228
+ databasesUrl: `${basePath || ''}/`,
220
229
  readonly: effectiveRo,
221
230
  serverless: isServerless,
222
231
  auth: authConfig,
@@ -1 +1 @@
1
- import{type AuthConfig,type ResolvedAuthConfig} from './types';export declare function resetPersistentSecretCache():void;export declare function getOrCreatePersistentSecret(env?:NodeJS.ProcessEnv):string;export declare function resolveAuthConfig(optionsAuth?:boolean|AuthConfig,env?:NodeJS.ProcessEnv):ResolvedAuthConfig;
1
+ import{type AuthConfig,type ResolvedAuthConfig} from './types';export declare const resetPersistentSecretCache:()=>void;export declare function getOrCreatePersistentSecret(env?:NodeJS.ProcessEnv):string;export declare function resolveAuthConfig(optionsAuth?:boolean|AuthConfig,env?:NodeJS.ProcessEnv):ResolvedAuthConfig;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.resetPersistentSecretCache = resetPersistentSecretCache;
6
+ exports.resetPersistentSecretCache = void 0;
7
7
  exports.getOrCreatePersistentSecret = getOrCreatePersistentSecret;
8
8
  exports.resolveAuthConfig = resolveAuthConfig;
9
9
  const node_crypto_1 = require("node:crypto");
@@ -12,18 +12,18 @@ const node_os_1 = __importDefault(require("node:os"));
12
12
  const node_path_1 = __importDefault(require("node:path"));
13
13
  const types_1 = require("./types");
14
14
  let cachedFallbackSecret = null;
15
- function resetPersistentSecretCache() {
15
+ const resetPersistentSecretCache = () => {
16
16
  cachedFallbackSecret = null;
17
- }
17
+ };
18
+ exports.resetPersistentSecretCache = resetPersistentSecretCache;
18
19
  function getOrCreatePersistentSecret(env = process.env) {
19
20
  const envSecret = env.ADMINDB_SECRET ?? env.SESSION_SECRET;
20
21
  if (envSecret && String(envSecret).trim()) {
21
22
  return String(envSecret).trim();
22
23
  }
23
24
  const customSecretFile = env.ADMINDB_SECRET_FILE ? String(env.ADMINDB_SECRET_FILE).trim() : undefined;
24
- if (!customSecretFile && cachedFallbackSecret) {
25
+ if (!customSecretFile && cachedFallbackSecret)
25
26
  return cachedFallbackSecret;
26
- }
27
27
  const candidatePaths = [];
28
28
  if (customSecretFile) {
29
29
  candidatePaths.push(node_path_1.default.resolve(customSecretFile));
@@ -31,66 +31,54 @@ function getOrCreatePersistentSecret(env = process.env) {
31
31
  else {
32
32
  try {
33
33
  const home = node_os_1.default.homedir();
34
- if (home) {
34
+ if (home)
35
35
  candidatePaths.push(node_path_1.default.join(home, '.admindb-secret'));
36
- }
37
- }
38
- catch {
39
36
  }
37
+ catch { }
40
38
  try {
41
39
  const tmp = node_os_1.default.tmpdir();
42
- if (tmp) {
40
+ if (tmp)
43
41
  candidatePaths.push(node_path_1.default.join(tmp, '.admindb-secret'));
44
- }
45
- }
46
- catch {
47
42
  }
43
+ catch { }
48
44
  }
49
45
  for (const filePath of candidatePaths) {
50
46
  try {
51
47
  if ((0, node_fs_1.existsSync)(filePath)) {
52
48
  const content = (0, node_fs_1.readFileSync)(filePath, 'utf8').trim();
53
49
  if (content.length >= 16) {
54
- if (!customSecretFile) {
50
+ if (!customSecretFile)
55
51
  cachedFallbackSecret = content;
56
- }
57
52
  return content;
58
53
  }
59
54
  }
60
55
  }
61
- catch {
62
- }
56
+ catch { }
63
57
  }
64
58
  const newSecret = (0, node_crypto_1.randomBytes)(32).toString('hex');
65
59
  for (const filePath of candidatePaths) {
66
60
  try {
67
61
  const dir = node_path_1.default.dirname(filePath);
68
- if (!(0, node_fs_1.existsSync)(dir)) {
62
+ if (!(0, node_fs_1.existsSync)(dir))
69
63
  (0, node_fs_1.mkdirSync)(dir, { recursive: true });
70
- }
71
64
  (0, node_fs_1.writeFileSync)(filePath, newSecret, { encoding: 'utf8', mode: 0o600 });
72
- if (!customSecretFile) {
65
+ if (!customSecretFile)
73
66
  cachedFallbackSecret = newSecret;
74
- }
75
67
  return newSecret;
76
68
  }
77
- catch {
78
- }
69
+ catch { }
79
70
  }
80
- if (!customSecretFile) {
71
+ if (!customSecretFile)
81
72
  cachedFallbackSecret = newSecret;
82
- }
83
73
  return newSecret;
84
74
  }
85
75
  function resolveAuthConfig(optionsAuth, env = process.env) {
86
76
  const envDisabled = ['0', 'false', 'no', 'off'].includes(String(env.ADMINDB_AUTH ?? '').trim().toLowerCase()) ||
87
77
  ['1', 'true', 'yes'].includes(String(env.ADMINDB_NO_AUTH ?? env.ADMINDB_DISABLE_AUTH ?? '').trim().toLowerCase());
88
78
  let enabled = true;
89
- let username = (env.ADMINDB_USERNAME ?? env.ADMINDB_USER) ? String(env.ADMINDB_USERNAME ?? env.ADMINDB_USER).trim() : types_1.DEFAULT_USERNAME;
90
- let password = (env.ADMINDB_PASSWORD ?? env.ADMINDB_PASS) ? String(env.ADMINDB_PASSWORD ?? env.ADMINDB_PASS).trim() : types_1.DEFAULT_PASSWORD_HASH;
91
- let secret = (env.ADMINDB_SECRET ?? env.SESSION_SECRET)
92
- ? String(env.ADMINDB_SECRET ?? env.SESSION_SECRET).trim()
93
- : getOrCreatePersistentSecret(env);
79
+ let username = String(env.ADMINDB_USERNAME ?? env.ADMINDB_USER ?? types_1.DEFAULT_USERNAME).trim();
80
+ let password = String(env.ADMINDB_PASSWORD ?? env.ADMINDB_PASS ?? types_1.DEFAULT_PASSWORD_HASH).trim();
81
+ let secret = String(env.ADMINDB_SECRET ?? env.SESSION_SECRET ?? '').trim() || getOrCreatePersistentSecret(env);
94
82
  let sessionDurationMs = types_1.DEFAULT_SESSION_DURATION_MS;
95
83
  if (optionsAuth === false || envDisabled) {
96
84
  enabled = false;
@@ -28,17 +28,14 @@ function constantTimeCompare(a, b) {
28
28
  }
29
29
  }
30
30
  function verifyPassword(candidatePassword, storedPasswordOrHash) {
31
- if (typeof candidatePassword !== 'string' || typeof storedPasswordOrHash !== 'string') {
31
+ if (typeof candidatePassword !== 'string' || typeof storedPasswordOrHash !== 'string')
32
32
  return false;
33
- }
34
33
  if (storedPasswordOrHash.startsWith('scrypt:')) {
35
34
  const parts = storedPasswordOrHash.split(':');
36
35
  if (parts.length === 3 && parts[1] && parts[2]) {
37
- const salt = parts[1];
38
- const expectedKeyHex = parts[2];
39
36
  try {
40
- const derivedKey = (0, node_crypto_1.scryptSync)(candidatePassword, salt, 64).toString('hex');
41
- return constantTimeCompare(derivedKey, expectedKeyHex);
37
+ const derivedKey = (0, node_crypto_1.scryptSync)(candidatePassword, parts[1], 64).toString('hex');
38
+ return constantTimeCompare(derivedKey, parts[2]);
42
39
  }
43
40
  catch {
44
41
  return false;
@@ -57,9 +54,8 @@ function parseCookies(cookieHeader) {
57
54
  continue;
58
55
  const key = pair.slice(0, idx).trim();
59
56
  let val = pair.slice(idx + 1).trim();
60
- if (val.startsWith('"') && val.endsWith('"') && val.length >= 2) {
57
+ if (val.startsWith('"') && val.endsWith('"') && val.length >= 2)
61
58
  val = val.slice(1, -1);
62
- }
63
59
  if (key) {
64
60
  try {
65
61
  cookies[key] = decodeURIComponent(val);
@@ -72,9 +68,7 @@ function parseCookies(cookieHeader) {
72
68
  return cookies;
73
69
  }
74
70
  function createSessionToken(username, secret, durationMs = types_1.DEFAULT_SESSION_DURATION_MS) {
75
- const expiresAt = Date.now() + durationMs;
76
- const userPayload = Buffer.from(username, 'utf8').toString('base64url');
77
- const data = `${userPayload}.${expiresAt}`;
71
+ const data = `${Buffer.from(username, 'utf8').toString('base64url')}.${Date.now() + durationMs}`;
78
72
  const signature = (0, node_crypto_1.createHmac)('sha256', secret).update(data).digest('base64url');
79
73
  return `${data}.${signature}`;
80
74
  }
@@ -87,22 +81,18 @@ function verifySessionToken(token, secret, expectedUser) {
87
81
  const [userPayload, expiresStr, providedSignature] = parts;
88
82
  const data = `${userPayload}.${expiresStr}`;
89
83
  const expectedSignature = (0, node_crypto_1.createHmac)('sha256', secret).update(data).digest('base64url');
90
- if (!constantTimeCompare(providedSignature, expectedSignature)) {
84
+ if (!constantTimeCompare(providedSignature, expectedSignature))
91
85
  return null;
92
- }
93
86
  const expiresAt = Number.parseInt(expiresStr, 10);
94
- if (!Number.isFinite(expiresAt) || Date.now() > expiresAt) {
87
+ if (!Number.isFinite(expiresAt) || Date.now() > expiresAt)
95
88
  return null;
96
- }
97
- let username;
98
89
  try {
99
- username = Buffer.from(userPayload, 'base64url').toString('utf8');
90
+ const username = Buffer.from(userPayload, 'base64url').toString('utf8');
91
+ if (expectedUser && !constantTimeCompare(username, expectedUser))
92
+ return null;
93
+ return username;
100
94
  }
101
95
  catch {
102
96
  return null;
103
97
  }
104
- if (expectedUser && !constantTimeCompare(username, expectedUser)) {
105
- return null;
106
- }
107
- return username;
108
98
  }
@@ -5,14 +5,14 @@ exports.createAuthMiddleware = createAuthMiddleware;
5
5
  const types_1 = require("./types");
6
6
  const crypto_1 = require("./crypto");
7
7
  function authenticateRequest(req, config) {
8
- if (!config.enabled) {
8
+ if (!config.enabled)
9
9
  return { authenticated: true, user: 'anonymous' };
10
- }
11
10
  const authHeader = req.headers.authorization;
12
11
  if (authHeader) {
13
12
  const [scheme, credentials] = authHeader.split(/\s+/, 2);
14
13
  if (scheme && credentials) {
15
- if (scheme.toLowerCase() === 'basic') {
14
+ const lowerScheme = scheme.toLowerCase();
15
+ if (lowerScheme === 'basic') {
16
16
  try {
17
17
  const decoded = Buffer.from(credentials, 'base64').toString('utf8');
18
18
  const colonIdx = decoded.indexOf(':');
@@ -27,24 +27,21 @@ function authenticateRequest(req, config) {
27
27
  catch {
28
28
  }
29
29
  }
30
- else if (scheme.toLowerCase() === 'bearer') {
30
+ else if (lowerScheme === 'bearer') {
31
31
  const user = (0, crypto_1.verifySessionToken)(credentials, config.secret, config.username);
32
- if (user) {
32
+ if (user)
33
33
  return { authenticated: true, user };
34
- }
35
34
  if ((0, crypto_1.verifyPassword)(credentials, config.password)) {
36
35
  return { authenticated: true, user: config.username };
37
36
  }
38
37
  }
39
38
  }
40
39
  }
41
- const cookies = (0, crypto_1.parseCookies)(req.headers.cookie);
42
- const sessionCookie = cookies[types_1.SESSION_COOKIE_NAME];
40
+ const sessionCookie = (0, crypto_1.parseCookies)(req.headers.cookie)[types_1.SESSION_COOKIE_NAME];
43
41
  if (sessionCookie) {
44
42
  const user = (0, crypto_1.verifySessionToken)(sessionCookie, config.secret, config.username);
45
- if (user) {
43
+ if (user)
46
44
  return { authenticated: true, user };
47
- }
48
45
  }
49
46
  return { authenticated: false };
50
47
  }
@@ -57,11 +54,7 @@ function createAuthMiddleware(config, basePath, _logger) {
57
54
  return next();
58
55
  }
59
56
  const p = req.path;
60
- if (p.startsWith('/css/') ||
61
- p.startsWith('/js/') ||
62
- p === '/favicon.ico' ||
63
- p === '/login' ||
64
- p === '/logout') {
57
+ if (p.startsWith('/css/') || p.startsWith('/js/') || p === '/favicon.ico' || p === '/login' || p === '/logout') {
65
58
  return next();
66
59
  }
67
60
  const auth = authenticateRequest(req, config);
@@ -70,7 +63,7 @@ function createAuthMiddleware(config, basePath, _logger) {
70
63
  req.user = auth.user;
71
64
  return next();
72
65
  }
73
- if (p.startsWith('/api/') || req.xhr || (req.headers.accept && req.headers.accept.includes('application/json'))) {
66
+ if (p.startsWith('/api/') || req.xhr || req.headers.accept?.includes('application/json')) {
74
67
  res.setHeader('WWW-Authenticate', 'Basic realm="AdminDB", Bearer');
75
68
  res.status(401).json({
76
69
  success: false,
@@ -6,15 +6,13 @@ const middleware_1 = require("./middleware");
6
6
  const crypto_1 = require("./crypto");
7
7
  function registerAuthRoutes(router, config, basePath, logger) {
8
8
  router.get('/login', (req, res) => {
9
- if (!config.enabled) {
9
+ if (!config.enabled)
10
10
  return res.redirect(`${basePath}/`);
11
- }
12
11
  const auth = (0, middleware_1.authenticateRequest)(req, config);
12
+ const nextUrl = String(req.query.next ?? '').trim();
13
13
  if (auth.authenticated) {
14
- const nextUrl = String(req.query.next ?? '').trim();
15
14
  return res.redirect(nextUrl.startsWith('/') ? nextUrl : `${basePath}/`);
16
15
  }
17
- const nextUrl = String(req.query.next ?? '').trim();
18
16
  res.render('pages/login', {
19
17
  title: 'Sign in',
20
18
  noSidebar: true,
@@ -24,17 +22,15 @@ function registerAuthRoutes(router, config, basePath, logger) {
24
22
  });
25
23
  });
26
24
  router.post('/login', (req, res) => {
27
- if (!config.enabled) {
25
+ if (!config.enabled)
28
26
  return res.redirect(`${basePath}/`);
29
- }
30
27
  const username = String(req.body?.username ?? '').trim();
31
28
  const password = String(req.body?.password ?? '').trim();
32
29
  const nextUrl = String(req.body?.next ?? req.query.next ?? '').trim();
33
- const isValid = (0, crypto_1.constantTimeCompare)(username, config.username) &&
34
- (0, crypto_1.verifyPassword)(password, config.password);
30
+ const isValid = (0, crypto_1.constantTimeCompare)(username, config.username) && (0, crypto_1.verifyPassword)(password, config.password);
35
31
  if (!isValid) {
36
32
  logger.warn(`Failed login attempt for user "${username || '<empty>'}"`);
37
- if (req.xhr || (req.headers.accept && req.headers.accept.includes('application/json'))) {
33
+ if (req.xhr || req.headers.accept?.includes('application/json')) {
38
34
  return res.status(401).json({ success: false, error: 'Invalid username or password.' });
39
35
  }
40
36
  return res.status(401).render('pages/login', {
@@ -51,16 +47,14 @@ function registerAuthRoutes(router, config, basePath, logger) {
51
47
  const expiresUtc = new Date(Date.now() + config.sessionDurationMs).toUTCString();
52
48
  const cookiePath = basePath || '/';
53
49
  res.setHeader('Set-Cookie', `${types_1.SESSION_COOKIE_NAME}=${encodeURIComponent(token)}; Path=${cookiePath}; Max-Age=${maxAgeSec}; Expires=${expiresUtc}; HttpOnly; SameSite=Lax`);
54
- if (req.xhr || (req.headers.accept && req.headers.accept.includes('application/json'))) {
50
+ if (req.xhr || req.headers.accept?.includes('application/json')) {
55
51
  return res.json({ success: true, message: 'Logged in successfully.', user: config.username, token });
56
52
  }
57
- const target = nextUrl.startsWith('/') ? nextUrl : `${basePath}/`;
58
- res.redirect(target);
53
+ res.redirect(nextUrl.startsWith('/') ? nextUrl : `${basePath}/`);
59
54
  });
60
55
  const handleLogout = (req, res) => {
61
- const cookiePath = basePath || '/';
62
- res.setHeader('Set-Cookie', `${types_1.SESSION_COOKIE_NAME}=; Path=${cookiePath}; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; SameSite=Lax`);
63
- if (req.xhr || (req.headers.accept && req.headers.accept.includes('application/json'))) {
56
+ res.setHeader('Set-Cookie', `${types_1.SESSION_COOKIE_NAME}=; Path=${basePath || '/'}; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; SameSite=Lax`);
57
+ if (req.xhr || req.headers.accept?.includes('application/json')) {
64
58
  return res.json({ success: true, message: 'Logged out successfully.' });
65
59
  }
66
60
  res.redirect(`${basePath}/login`);
@@ -1 +1 @@
1
- import type {LogLevel} from '../utils/logger';export interface CliArgs{help:boolean;version:boolean;host?:string;port?:number;dbPath?:string;connection?:string;connections?:Record<string,string>;configPath?:string;dbDir?:string;dbFiles?:string[];basePath?:string;logLevel?:LogLevel;readonly :boolean;serverless?:boolean;auth?:boolean;authUsername?:string;authPassword?:string;authSecret?:string;}export declare function helpText():string;export declare function getPackageVersion():string;export declare function versionText():string;export declare function parseArgs(argv:string[]):{args:CliArgs;error?:string;};
1
+ import type {LogLevel} from '../utils/logger';export interface CliArgs{help:boolean;version:boolean;host?:string;port?:number;dbPath?:string;connection?:string;connections?:Record<string,string>;configPath?:string;dbDir?:string;dbFiles?:string[];basePath?:string;logLevel?:LogLevel;readonly :boolean;explicitReadonly?:boolean;serverless?:boolean;auth?:boolean;authUsername?:string;authPassword?:string;authSecret?:string;}export declare function helpText():string;export declare const getPackageVersion:()=>string;export declare const versionText:()=>string;export declare function parseArgs(argv:string[]):{args:CliArgs;error?:string;};
package/dist/cli/args.js CHANGED
@@ -3,9 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.versionText = exports.getPackageVersion = void 0;
6
7
  exports.helpText = helpText;
7
- exports.getPackageVersion = getPackageVersion;
8
- exports.versionText = versionText;
9
8
  exports.parseArgs = parseArgs;
10
9
  const node_fs_1 = require("node:fs");
11
10
  const node_path_1 = __importDefault(require("node:path"));
@@ -39,7 +38,7 @@ function helpText() {
39
38
  ' --files, --db-files <list> Comma-separated database file paths [DB_FILES / ADMINDB_DB_FILES]',
40
39
  ' -b, --base-path, --base <p> URL prefix to serve under (default: /) [BASE_PATH / ADMINDB_BASE_PATH]',
41
40
  ' -r, --readonly, --read-only Open databases read-only (writes disabled) [READONLY / ADMINDB_READONLY]',
42
- ' --serverless Enable serverless mode (enforces read-only, disables writes) [SERVERLESS / ADMINDB_SERVERLESS]',
41
+ ' --serverless Enable serverless mode (SQLite defaults to read-only; PostgreSQL is editable unless --readonly is specified) [SERVERLESS / ADMINDB_SERVERLESS]',
43
42
  ' --auth Enable authentication (default: on) [ADMINDB_AUTH=true]',
44
43
  ' --no-auth, --disable-auth Disable authentication completely [ADMINDB_NO_AUTH=1 / ADMINDB_AUTH=false]',
45
44
  ' -u, --username, --user <user> Admin username (default: admin) [ADMINDB_USERNAME / ADMINDB_USER]',
@@ -68,28 +67,23 @@ function packageVersion() {
68
67
  ];
69
68
  for (const p of candidates) {
70
69
  if ((0, node_fs_1.existsSync)(p)) {
71
- const raw = (0, node_fs_1.readFileSync)(p, 'utf8');
72
- const pkg = JSON.parse(raw);
70
+ const pkg = JSON.parse((0, node_fs_1.readFileSync)(p, 'utf8'));
73
71
  if (pkg.version)
74
72
  return pkg.version;
75
73
  }
76
74
  }
77
- return '';
78
75
  }
79
- catch {
80
- return '';
81
- }
82
- }
83
- function getPackageVersion() {
84
- return packageVersion();
76
+ catch { }
77
+ return '';
85
78
  }
86
- function versionText() {
79
+ const getPackageVersion = () => packageVersion();
80
+ exports.getPackageVersion = getPackageVersion;
81
+ const versionText = () => {
87
82
  const v = packageVersion();
88
83
  return v ? `admindb v${v}` : 'admindb';
89
- }
90
- function isFlag(tok, ...names) {
91
- return names.includes(tok) || names.some((n) => n.length > 2 && tok.startsWith(n + '='));
92
- }
84
+ };
85
+ exports.versionText = versionText;
86
+ const isFlag = (tok, ...names) => names.includes(tok) || names.some((n) => n.length > 2 && tok.startsWith(`${n}=`));
93
87
  function parseArgs(argv) {
94
88
  const args = { help: false, version: false, readonly: false };
95
89
  const positional = [];
@@ -99,7 +93,7 @@ function parseArgs(argv) {
99
93
  const eq = argv[i].indexOf('=');
100
94
  if (eq !== -1)
101
95
  return argv[i].slice(eq + 1);
102
- i += 1;
96
+ i++;
103
97
  return argv[i];
104
98
  };
105
99
  while (i < argv.length) {
@@ -128,8 +122,10 @@ function parseArgs(argv) {
128
122
  args.host = fileConf.host;
129
123
  if (fileConf.basePath !== undefined)
130
124
  args.basePath = fileConf.basePath;
131
- if (fileConf.readonly !== undefined)
125
+ if (fileConf.readonly !== undefined) {
132
126
  args.readonly = fileConf.readonly;
127
+ args.explicitReadonly = fileConf.readonly;
128
+ }
133
129
  if (fileConf.serverless !== undefined)
134
130
  args.serverless = fileConf.serverless;
135
131
  if (fileConf.logLevel !== undefined)
@@ -178,9 +174,8 @@ function parseArgs(argv) {
178
174
  if (v === undefined)
179
175
  return invalid('Missing value for --open / --db-path.');
180
176
  args.dbPath = v;
181
- if ((0, common_1.isPostgresConnectionString)(v)) {
177
+ if ((0, common_1.isPostgresConnectionString)(v))
182
178
  args.connection = v;
183
- }
184
179
  }
185
180
  else if (isFlag(tok, '-d', '--dir', '--folder', '--db-dir')) {
186
181
  const v = takeValue('--dir');
@@ -192,10 +187,7 @@ function parseArgs(argv) {
192
187
  const v = takeValue('--files');
193
188
  if (v === undefined)
194
189
  return invalid('Missing value for --files / --db-files.');
195
- args.dbFiles = v
196
- .split(',')
197
- .map((s) => s.trim())
198
- .filter(Boolean);
190
+ args.dbFiles = v.split(',').map((s) => s.trim()).filter(Boolean);
199
191
  }
200
192
  else if (isFlag(tok, '-b', '--base-path', '--base')) {
201
193
  const v = takeValue('--base-path');
@@ -214,10 +206,10 @@ function parseArgs(argv) {
214
206
  }
215
207
  else if (tok === '-r' || tok === '--readonly' || tok === '--read-only') {
216
208
  args.readonly = true;
209
+ args.explicitReadonly = true;
217
210
  }
218
211
  else if (tok === '--serverless') {
219
212
  args.serverless = true;
220
- args.readonly = true;
221
213
  }
222
214
  else if (tok === '--no-auth' || tok === '--disable-auth') {
223
215
  args.auth = false;
@@ -249,7 +241,7 @@ function parseArgs(argv) {
249
241
  else {
250
242
  positional.push(tok);
251
243
  }
252
- i += 1;
244
+ i++;
253
245
  }
254
246
  if (positional.length > 1) {
255
247
  return invalid(`Unexpected extra arguments: ${positional.slice(1).join(' ')}.`);
@@ -306,9 +298,8 @@ function parseArgs(argv) {
306
298
  }
307
299
  }
308
300
  catch (err) {
309
- if (p.endsWith('.json')) {
301
+ if (p.endsWith('.json'))
310
302
  return invalid(err.message);
311
- }
312
303
  args.dbPath = p;
313
304
  }
314
305
  }
@@ -1 +1 @@
1
- import type {LogLevel} from '../utils/logger';export interface DatabaseConfigFile{connections?:Record<string,string>;connection?:string;port?:number;host?:string;basePath?:string;readonly ?:boolean;serverless?:boolean;auth?:boolean|{username?:string;password?:string;secret?:string;};logLevel?:LogLevel;}export declare function parseConfigFile(filePath:string):DatabaseConfigFile|null;export interface Config{host:string;port:number;dbPath:string;connection?:string;connections?:Record<string,string>;dbDir?:string;dbFiles?:string[];basePath:string;logLevel:LogLevel;readonly :boolean;serverless:boolean;auth:boolean;authUsername?:string;authPassword?:string;authSecret?:string;}export declare function loadConfig(env?:NodeJS.ProcessEnv):Config;
1
+ import type {LogLevel} from '../utils/logger';export interface DatabaseConfigFile{connections?:Record<string,string>;connection?:string;port?:number;host?:string;basePath?:string;readonly ?:boolean;serverless?:boolean;auth?:boolean|{username?:string;password?:string;secret?:string;};logLevel?:LogLevel;}export declare function parseConfigFile(filePath:string):DatabaseConfigFile|null;export interface Config{host:string;port:number;dbPath:string;connection?:string;connections?:Record<string,string>;dbDir?:string;dbFiles?:string[];basePath:string;logLevel:LogLevel;readonly :boolean;explicitReadonly?:boolean;serverless:boolean;auth:boolean;authUsername?:string;authPassword?:string;authSecret?:string;}export declare function loadConfig(env?:NodeJS.ProcessEnv):Config;
@@ -41,9 +41,8 @@ function parseConfigFile(filePath) {
41
41
  conns[k.trim()] = v.connection.trim();
42
42
  }
43
43
  }
44
- if (typeof parsed.connection === 'string' && parsed.connection.trim()) {
44
+ if (typeof parsed.connection === 'string' && parsed.connection.trim())
45
45
  result.connection = parsed.connection.trim();
46
- }
47
46
  if (typeof parsed.port === 'number')
48
47
  result.port = parsed.port;
49
48
  if (typeof parsed.host === 'string')
@@ -54,14 +53,12 @@ function parseConfigFile(filePath) {
54
53
  result.readonly = parsed.readonly;
55
54
  if (typeof parsed.serverless === 'boolean')
56
55
  result.serverless = parsed.serverless;
57
- if (typeof parsed.auth === 'boolean' || (parsed.auth && typeof parsed.auth === 'object')) {
56
+ if (typeof parsed.auth === 'boolean' || (parsed.auth && typeof parsed.auth === 'object'))
58
57
  result.auth = parsed.auth;
59
- }
60
58
  if (typeof parsed.logLevel === 'string')
61
59
  result.logLevel = parsed.logLevel;
62
- if (Object.keys(conns).length > 0) {
60
+ if (Object.keys(conns).length > 0)
63
61
  result.connections = conns;
64
- }
65
62
  return result;
66
63
  }
67
64
  catch (err) {
@@ -82,19 +79,23 @@ function loadConfig(env = process.env) {
82
79
  const serverlessRaw = env.ADMINDB_SERVERLESS ?? env.SERVERLESS ?? env.IS_SERVERLESS ?? '';
83
80
  const serverless = ['1', 'true', 'yes', 'on'].includes(String(serverlessRaw).trim().toLowerCase()) ||
84
81
  (0, serverless_1.isServerlessEnvironment)(env);
85
- const readonlyRaw = env.ADMINDB_READONLY ?? env.READONLY ?? '';
86
- const readonly = serverless || ['1', 'true', 'yes', 'on'].includes(String(readonlyRaw).trim().toLowerCase());
82
+ const readonlyRaw = env.ADMINDB_READONLY ?? env.READONLY;
83
+ const explicitReadonly = readonlyRaw !== undefined && readonlyRaw !== ''
84
+ ? ['1', 'true', 'yes', 'on'].includes(String(readonlyRaw).trim().toLowerCase())
85
+ : undefined;
86
+ const isPg = Boolean((connectionRaw && (0, common_1.isPostgresConnectionString)(connectionRaw)) ||
87
+ (dbPathRaw && (0, common_1.isPostgresConnectionString)(dbPathRaw)));
88
+ const defaultReadonly = serverless ? !isPg : false;
89
+ const readonly = explicitReadonly !== undefined ? explicitReadonly : defaultReadonly;
87
90
  let connections;
88
91
  const configJsonRaw = env.ADMINDB_CONFIG_JSON ?? env.ADMINDB_CONNECTIONS;
89
92
  if (configJsonRaw) {
90
93
  try {
91
94
  const parsed = JSON.parse(configJsonRaw);
92
- if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
95
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
93
96
  connections = parsed;
94
- }
95
- }
96
- catch {
97
97
  }
98
+ catch { }
98
99
  }
99
100
  const configPath = env.ADMINDB_CONFIG;
100
101
  if (configPath && (0, node_fs_1.existsSync)(configPath)) {
@@ -110,15 +111,11 @@ function loadConfig(env = process.env) {
110
111
  connection: connectionRaw ? String(connectionRaw) : undefined,
111
112
  connections,
112
113
  dbDir: dbDirRaw ? String(dbDirRaw) : undefined,
113
- dbFiles: dbFilesRaw
114
- ? String(dbFilesRaw)
115
- .split(',')
116
- .map((s) => s.trim())
117
- .filter(Boolean)
118
- : undefined,
114
+ dbFiles: dbFilesRaw ? String(dbFilesRaw).split(',').map((s) => s.trim()).filter(Boolean) : undefined,
119
115
  basePath: String(basePathRaw).replace(/\/+$/, ''),
120
116
  logLevel: logLevelRaw,
121
117
  readonly,
118
+ explicitReadonly,
122
119
  serverless,
123
120
  auth: !authDisabled,
124
121
  authUsername: (env.ADMINDB_USERNAME ?? env.ADMINDB_USER) ? String(env.ADMINDB_USERNAME ?? env.ADMINDB_USER).trim() : undefined,