@mostajs/orm-cli 0.3.0 → 0.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/bin/mostajs.sh +165 -5
- package/package.json +2 -1
package/bin/mostajs.sh
CHANGED
|
@@ -1508,6 +1508,7 @@ menu_seeding() {
|
|
|
1508
1508
|
echo -e " ${CYAN}7${RESET}) Dump current DB rows → .mostajs/seeds-dump/"
|
|
1509
1509
|
echo -e " ${CYAN}8${RESET}) Clear the seeds directory"
|
|
1510
1510
|
echo -e " ${CYAN}9${RESET}) Show a seed file"
|
|
1511
|
+
echo -e " ${CYAN}h${RESET}) ${BOLD}Hash plain-text passwords in seed files${RESET} (bcrypt)"
|
|
1511
1512
|
echo
|
|
1512
1513
|
echo -e " ${CYAN}b${RESET}) Back"
|
|
1513
1514
|
echo
|
|
@@ -1523,12 +1524,105 @@ menu_seeding() {
|
|
|
1523
1524
|
7) action_seed_dump ;;
|
|
1524
1525
|
8) action_seed_clear ;;
|
|
1525
1526
|
9) action_seed_show ;;
|
|
1527
|
+
h|H) action_seed_hash_passwords ;;
|
|
1526
1528
|
b|B) return ;;
|
|
1527
1529
|
*) warn "Unknown"; pause ;;
|
|
1528
1530
|
esac
|
|
1529
1531
|
menu_seeding
|
|
1530
1532
|
}
|
|
1531
1533
|
|
|
1534
|
+
# ------------------------------------------------------------
|
|
1535
|
+
# seed : hash plain-text passwords
|
|
1536
|
+
# ------------------------------------------------------------
|
|
1537
|
+
|
|
1538
|
+
action_seed_hash_passwords() {
|
|
1539
|
+
header
|
|
1540
|
+
echo -e "${BOLD}${MAGENTA}▶ Hash plain-text passwords in seed files${RESET}"
|
|
1541
|
+
echo
|
|
1542
|
+
|
|
1543
|
+
local seed_dir="$CONFIG_DIR/seeds"
|
|
1544
|
+
local count
|
|
1545
|
+
count=$(ls -1 "$seed_dir"/*.json 2>/dev/null | wc -l)
|
|
1546
|
+
if [[ $count -eq 0 ]]; then
|
|
1547
|
+
err "No seed files in $seed_dir (menu S → 1 first)"
|
|
1548
|
+
pause; return
|
|
1549
|
+
fi
|
|
1550
|
+
|
|
1551
|
+
echo "Auto-detects fields named :"
|
|
1552
|
+
dim " password, passwordHash, hashedPassword, pwd, userPassword"
|
|
1553
|
+
echo
|
|
1554
|
+
echo "Skips values that are already bcrypt hashes (start with \$2a\$ / \$2b\$ / \$2y\$, 60 chars)"
|
|
1555
|
+
echo
|
|
1556
|
+
|
|
1557
|
+
local cost
|
|
1558
|
+
cost=$(ask "bcrypt cost factor" "${BCRYPT_COST:-10}")
|
|
1559
|
+
if ! confirm "Hash all plain passwords in $seed_dir ?"; then
|
|
1560
|
+
return
|
|
1561
|
+
fi
|
|
1562
|
+
|
|
1563
|
+
# Ensure bcryptjs is available
|
|
1564
|
+
ensure_pkg "bcryptjs" || { pause; return; }
|
|
1565
|
+
|
|
1566
|
+
cat > "$CONFIG_DIR/seed-hash.mjs" <<EOF
|
|
1567
|
+
import { readdirSync, readFileSync, writeFileSync } from 'fs';
|
|
1568
|
+
import { join } from 'path';
|
|
1569
|
+
import bcrypt from 'bcryptjs';
|
|
1570
|
+
|
|
1571
|
+
const SEEDDIR = '$seed_dir';
|
|
1572
|
+
const COST = $cost;
|
|
1573
|
+
|
|
1574
|
+
// Field names commonly used for passwords
|
|
1575
|
+
const PASSWORD_FIELDS = ['password', 'passwordHash', 'hashedPassword', 'pwd', 'userPassword'];
|
|
1576
|
+
|
|
1577
|
+
// Test if a string is already a bcrypt hash
|
|
1578
|
+
const isBcrypt = v => typeof v === 'string' && /^\\\$2[ayb]\\\$\\d{1,2}\\\$/.test(v) && v.length === 60;
|
|
1579
|
+
|
|
1580
|
+
let totalHashed = 0;
|
|
1581
|
+
let totalSkipped = 0;
|
|
1582
|
+
const summary = [];
|
|
1583
|
+
|
|
1584
|
+
for (const f of readdirSync(SEEDDIR).filter(x => x.endsWith('.json'))) {
|
|
1585
|
+
const path = join(SEEDDIR, f);
|
|
1586
|
+
let data;
|
|
1587
|
+
try { data = JSON.parse(readFileSync(path, 'utf8')); }
|
|
1588
|
+
catch { continue; }
|
|
1589
|
+
|
|
1590
|
+
if (!Array.isArray(data)) continue;
|
|
1591
|
+
|
|
1592
|
+
let hashed = 0, skipped = 0;
|
|
1593
|
+
for (const row of data) {
|
|
1594
|
+
for (const field of PASSWORD_FIELDS) {
|
|
1595
|
+
const val = row[field];
|
|
1596
|
+
if (typeof val !== 'string' || val.length === 0) continue;
|
|
1597
|
+
if (isBcrypt(val)) { skipped++; continue; }
|
|
1598
|
+
row[field] = bcrypt.hashSync(val, COST);
|
|
1599
|
+
hashed++;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
if (hashed > 0) {
|
|
1604
|
+
writeFileSync(path, JSON.stringify(data, null, 2));
|
|
1605
|
+
summary.push({ file: f, hashed, skipped });
|
|
1606
|
+
}
|
|
1607
|
+
totalHashed += hashed;
|
|
1608
|
+
totalSkipped += skipped;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
for (const s of summary) {
|
|
1612
|
+
console.log(' \u2713 ' + s.file + ' : ' + s.hashed + ' hashed' + (s.skipped ? ' (+ ' + s.skipped + ' already hashed)' : ''));
|
|
1613
|
+
}
|
|
1614
|
+
console.log();
|
|
1615
|
+
console.log('Total : ' + totalHashed + ' hashed, ' + totalSkipped + ' already-hashed skipped');
|
|
1616
|
+
if (totalHashed === 0 && totalSkipped === 0) {
|
|
1617
|
+
console.log('No password fields found.');
|
|
1618
|
+
}
|
|
1619
|
+
EOF
|
|
1620
|
+
cd "$PROJECT_ROOT"
|
|
1621
|
+
node "$CONFIG_DIR/seed-hash.mjs" 2>&1 | tee "$LOG_DIR/seed-hash.log"
|
|
1622
|
+
echo
|
|
1623
|
+
pause
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1532
1626
|
# ------------------------------------------------------------
|
|
1533
1627
|
# seed : upload / import
|
|
1534
1628
|
# ------------------------------------------------------------
|
|
@@ -1750,6 +1844,9 @@ for (const f of readdirSync(SEEDDIR).filter(x => x.endsWith('.json'))) {
|
|
|
1750
1844
|
}
|
|
1751
1845
|
|
|
1752
1846
|
// ---------- Validate ----------
|
|
1847
|
+
const PASSWORD_FIELDS = ['password', 'passwordHash', 'hashedPassword', 'pwd', 'userPassword'];
|
|
1848
|
+
const isBcrypt = v => typeof v === 'string' && /^\\\$2[ayb]\\\$\\d{1,2}\\\$/.test(v) && v.length === 60;
|
|
1849
|
+
|
|
1753
1850
|
function validateRow(row, entity) {
|
|
1754
1851
|
const errors = [];
|
|
1755
1852
|
const fieldNames = new Set(Object.keys(entity.fields ?? {}));
|
|
@@ -1780,6 +1877,12 @@ function validateRow(row, entity) {
|
|
|
1780
1877
|
warnings.push('unknown field "' + k + '" (not in schema)');
|
|
1781
1878
|
}
|
|
1782
1879
|
}
|
|
1880
|
+
// Password fields that look like plain-text (not bcrypt)
|
|
1881
|
+
for (const pf of PASSWORD_FIELDS) {
|
|
1882
|
+
if (row[pf] !== undefined && row[pf] !== null && row[pf] !== '' && !isBcrypt(row[pf])) {
|
|
1883
|
+
warnings.push('field "' + pf + '" does not look like a bcrypt hash — run menu S \u2192 h to hash');
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1783
1886
|
return { errors, warnings };
|
|
1784
1887
|
}
|
|
1785
1888
|
|
|
@@ -2122,6 +2225,55 @@ EOF
|
|
|
2122
2225
|
|
|
2123
2226
|
run_subcommand() {
|
|
2124
2227
|
case "$1" in
|
|
2228
|
+
hash|h)
|
|
2229
|
+
# mostajs hash <plaintext> [cost]
|
|
2230
|
+
local pw="${2:-}"
|
|
2231
|
+
local cost="${3:-10}"
|
|
2232
|
+
[[ -z "$pw" ]] && { echo "Usage: mostajs hash <password> [cost=10]" >&2; exit 1; }
|
|
2233
|
+
# Try local project, then CLI's own node_modules (bcryptjs is a dep)
|
|
2234
|
+
local bcrypt_dir=""
|
|
2235
|
+
if [[ -d "$PROJECT_ROOT/node_modules/bcryptjs" ]]; then
|
|
2236
|
+
bcrypt_dir="$PROJECT_ROOT/node_modules/bcryptjs"
|
|
2237
|
+
else
|
|
2238
|
+
local cli_dir
|
|
2239
|
+
cli_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
2240
|
+
[[ -d "$cli_dir/node_modules/bcryptjs" ]] && bcrypt_dir="$cli_dir/node_modules/bcryptjs"
|
|
2241
|
+
fi
|
|
2242
|
+
if [[ -z "$bcrypt_dir" ]]; then
|
|
2243
|
+
ensure_pkg "bcryptjs" >/dev/null 2>&1 || {
|
|
2244
|
+
err "bcryptjs not available. Install it manually : npm install bcryptjs"
|
|
2245
|
+
exit 1
|
|
2246
|
+
}
|
|
2247
|
+
bcrypt_dir="$PROJECT_ROOT/node_modules/bcryptjs"
|
|
2248
|
+
fi
|
|
2249
|
+
BCRYPT_PASSWORD="$pw" BCRYPT_COST="$cost" BCRYPT_DIR="$bcrypt_dir" node -e "
|
|
2250
|
+
const bcrypt = require(process.env.BCRYPT_DIR);
|
|
2251
|
+
const h = bcrypt.hashSync(process.env.BCRYPT_PASSWORD, parseInt(process.env.BCRYPT_COST, 10));
|
|
2252
|
+
console.log(h);
|
|
2253
|
+
"
|
|
2254
|
+
;;
|
|
2255
|
+
verify|v)
|
|
2256
|
+
# mostajs verify <plaintext> <hash>
|
|
2257
|
+
local pw="${2:-}"
|
|
2258
|
+
local hashval="${3:-}"
|
|
2259
|
+
[[ -z "$pw" || -z "$hashval" ]] && { echo "Usage: mostajs verify <password> <hash>" >&2; exit 1; }
|
|
2260
|
+
local bcrypt_dir=""
|
|
2261
|
+
if [[ -d "$PROJECT_ROOT/node_modules/bcryptjs" ]]; then
|
|
2262
|
+
bcrypt_dir="$PROJECT_ROOT/node_modules/bcryptjs"
|
|
2263
|
+
else
|
|
2264
|
+
local cli_dir
|
|
2265
|
+
cli_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
2266
|
+
[[ -d "$cli_dir/node_modules/bcryptjs" ]] && bcrypt_dir="$cli_dir/node_modules/bcryptjs"
|
|
2267
|
+
fi
|
|
2268
|
+
if [[ -z "$bcrypt_dir" ]]; then
|
|
2269
|
+
ensure_pkg "bcryptjs" >/dev/null 2>&1 || exit 1
|
|
2270
|
+
bcrypt_dir="$PROJECT_ROOT/node_modules/bcryptjs"
|
|
2271
|
+
fi
|
|
2272
|
+
BCRYPT_PASSWORD="$pw" BCRYPT_HASH="$hashval" BCRYPT_DIR="$bcrypt_dir" node -e "
|
|
2273
|
+
const bcrypt = require(process.env.BCRYPT_DIR);
|
|
2274
|
+
console.log(bcrypt.compareSync(process.env.BCRYPT_PASSWORD, process.env.BCRYPT_HASH) ? 'match' : 'no match');
|
|
2275
|
+
"
|
|
2276
|
+
;;
|
|
2125
2277
|
convert|c)
|
|
2126
2278
|
detect_project
|
|
2127
2279
|
[[ ${#DETECTED_TYPES[@]} -eq 0 ]] && { err "No schema found"; exit 1; }
|
|
@@ -2150,11 +2302,17 @@ run_subcommand() {
|
|
|
2150
2302
|
help|-h|--help)
|
|
2151
2303
|
cat <<EOF
|
|
2152
2304
|
Usage :
|
|
2153
|
-
$CLI_NAME
|
|
2154
|
-
$CLI_NAME convert
|
|
2155
|
-
$CLI_NAME detect
|
|
2156
|
-
$CLI_NAME health
|
|
2157
|
-
$CLI_NAME
|
|
2305
|
+
$CLI_NAME Interactive menu
|
|
2306
|
+
$CLI_NAME convert Run conversion (auto-detect schema type)
|
|
2307
|
+
$CLI_NAME detect Print detected schemas
|
|
2308
|
+
$CLI_NAME health Run health checks
|
|
2309
|
+
$CLI_NAME hash <password> [cost] Hash a password with bcrypt (cost default 10)
|
|
2310
|
+
$CLI_NAME verify <password> <hash> Check if a plain password matches a bcrypt hash
|
|
2311
|
+
$CLI_NAME version Print version
|
|
2312
|
+
|
|
2313
|
+
Examples:
|
|
2314
|
+
$CLI_NAME hash 'Admin@123456' → \$2b\$10\$N9qo8uLOickgx2ZMRZoMyeIjZA...
|
|
2315
|
+
$CLI_NAME verify 'Admin@123456' '\$2b\$10\$N9qo...'
|
|
2158
2316
|
EOF
|
|
2159
2317
|
;;
|
|
2160
2318
|
*)
|
|
@@ -2175,6 +2333,8 @@ EOF
|
|
|
2175
2333
|
|
|
2176
2334
|
# Non-interactive mode if args provided
|
|
2177
2335
|
if [[ $# -gt 0 ]]; then
|
|
2336
|
+
detect_project # populates PKG_MANAGER, PROJECT_ROOT, etc.
|
|
2337
|
+
load_env # optional config for subcommands that need it
|
|
2178
2338
|
run_subcommand "$@"
|
|
2179
2339
|
exit 0
|
|
2180
2340
|
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/orm-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Universal CLI to integrate @mostajs/orm into any project — auto-detects Prisma, OpenAPI, JSON Schema. Interactive menu + subcommands. 13 databases.",
|
|
5
5
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@mostajs/orm": "^1.9.2",
|
|
46
|
+
"bcryptjs": "^2.4.3",
|
|
46
47
|
"better-sqlite3": "^12.9.0"
|
|
47
48
|
}
|
|
48
49
|
}
|