@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.
Files changed (2) hide show
  1. package/bin/mostajs.sh +165 -5
  2. 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 Interactive menu
2154
- $CLI_NAME convert Run conversion (auto-detect schema type)
2155
- $CLI_NAME detect Print detected schemas
2156
- $CLI_NAME health Run health checks
2157
- $CLI_NAME version Print version
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.0",
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
  }