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.
- package/README.md +368 -364
- package/dist/app.js +25 -16
- package/dist/auth/config.d.ts +1 -1
- package/dist/auth/config.js +18 -30
- package/dist/auth/crypto.js +11 -21
- package/dist/auth/middleware.js +9 -16
- package/dist/auth/routes.js +9 -15
- package/dist/cli/args.d.ts +1 -1
- package/dist/cli/args.js +20 -29
- package/dist/cli/config.d.ts +1 -1
- package/dist/cli/config.js +15 -18
- package/dist/cli/runner.js +13 -13
- package/dist/data/chain.d.ts +1 -1
- package/dist/data/chain.js +40 -617
- package/dist/data/datasets.d.ts +1 -1
- package/dist/data/datasets.js +43 -1
- package/dist/data/detector.js +45 -21
- package/dist/data/engine.d.ts +1 -1
- package/dist/data/engine.js +483 -142
- package/dist/data/index.d.ts +1 -1
- package/dist/data/index.js +8 -0
- package/dist/data/prng.d.ts +1 -0
- package/dist/data/prng.js +73 -0
- package/dist/data/registry.d.ts +1 -0
- package/dist/data/registry.js +805 -0
- package/dist/data/schema-graph.d.ts +1 -0
- package/dist/data/schema-graph.js +210 -0
- package/dist/data/strategies.d.ts +1 -1
- package/dist/data/strategies.js +44 -283
- package/dist/data/templates.d.ts +1 -0
- package/dist/data/templates.js +134 -0
- package/dist/data/types.d.ts +1 -1
- package/dist/data/validator.d.ts +1 -0
- package/dist/data/validator.js +160 -0
- package/dist/db/database.js +9 -14
- package/dist/db/export.js +32 -55
- package/dist/db/filters.d.ts +1 -1
- package/dist/db/filters.js +15 -38
- package/dist/db/introspection.js +4 -7
- package/dist/db/manager.d.ts +1 -1
- package/dist/db/manager.js +48 -53
- package/dist/db/migrations.js +7 -14
- package/dist/db/postgres.js +30 -78
- package/dist/index.d.ts +1 -1
- package/dist/public/css/app.css +1 -1
- package/dist/public/js/browse.js +1 -1
- package/dist/public/js/inspector.js +1 -1
- package/dist/public/js/query.js +1 -1
- package/dist/public/js/seed.js +1 -1
- package/dist/routes/api/erd.js +3 -8
- package/dist/routes/api/helpers.d.ts +1 -1
- package/dist/routes/api/helpers.js +75 -25
- package/dist/routes/api/import-export.js +1 -2
- package/dist/routes/api/query.js +2 -6
- package/dist/routes/api/rows.js +4 -8
- package/dist/routes/api/seed.js +35 -21
- package/dist/routes/api/tables.js +15 -30
- package/dist/routes/databases.js +8 -26
- package/dist/routes/pages.js +67 -18
- package/dist/serverless.js +31 -44
- package/dist/sql/generator.d.ts +1 -1
- package/dist/sql/generator.js +35 -109
- package/dist/types/api.d.ts +1 -1
- package/dist/utils/common.d.ts +1 -1
- package/dist/utils/common.js +45 -85
- package/dist/utils/csv.d.ts +1 -1
- package/dist/utils/csv.js +11 -17
- package/dist/utils/datatype.js +46 -60
- package/dist/utils/icons.js +8 -8
- package/dist/views/layouts/main.hbs +1 -87
- package/dist/views/pages/databases.hbs +1 -233
- package/dist/views/pages/designer.hbs +1 -89
- package/dist/views/pages/erd.hbs +1 -1241
- package/dist/views/pages/error.hbs +1 -10
- package/dist/views/pages/form.hbs +1 -59
- package/dist/views/pages/home.hbs +1 -220
- package/dist/views/pages/info.hbs +1 -107
- package/dist/views/pages/login.hbs +1 -98
- package/dist/views/pages/query.hbs +1 -129
- package/dist/views/pages/schema.hbs +1 -298
- package/dist/views/pages/seed-select.hbs +251 -0
- package/dist/views/pages/seed.hbs +256 -414
- package/dist/views/pages/table.hbs +1 -327
- package/dist/views/partials/navbar.hbs +1 -83
- package/dist/views/partials/sidebar.hbs +1 -129
- package/docs/API.md +149 -149
- package/docs/EXAMPLES.md +579 -579
- package/docs/SECURITY.md +265 -265
- 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
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 (!
|
|
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
|
|
212
|
-
const effectiveRo =
|
|
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
|
|
228
|
+
databasesUrl: `${basePath || ''}/`,
|
|
220
229
|
readonly: effectiveRo,
|
|
221
230
|
serverless: isServerless,
|
|
222
231
|
auth: authConfig,
|
package/dist/auth/config.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{type AuthConfig,type ResolvedAuthConfig} from './types';export declare
|
|
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;
|
package/dist/auth/config.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
|
90
|
-
let password = (env.ADMINDB_PASSWORD ?? env.ADMINDB_PASS
|
|
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;
|
package/dist/auth/crypto.js
CHANGED
|
@@ -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,
|
|
41
|
-
return constantTimeCompare(derivedKey,
|
|
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
|
|
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
|
}
|
package/dist/auth/middleware.js
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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
|
|
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 ||
|
|
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,
|
package/dist/auth/routes.js
CHANGED
|
@@ -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 ||
|
|
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 ||
|
|
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
|
-
|
|
58
|
-
res.redirect(target);
|
|
53
|
+
res.redirect(nextUrl.startsWith('/') ? nextUrl : `${basePath}/`);
|
|
59
54
|
});
|
|
60
55
|
const handleLogout = (req, res) => {
|
|
61
|
-
|
|
62
|
-
|
|
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`);
|
package/dist/cli/args.d.ts
CHANGED
|
@@ -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
|
|
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 (
|
|
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
|
|
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
|
-
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
function getPackageVersion() {
|
|
84
|
-
return packageVersion();
|
|
76
|
+
catch { }
|
|
77
|
+
return '';
|
|
85
78
|
}
|
|
86
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
|
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
|
|
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
|
}
|
package/dist/cli/config.d.ts
CHANGED
|
@@ -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;
|
package/dist/cli/config.js
CHANGED
|
@@ -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
|
|
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,
|