[object Object]

← back to Norma Platform

scripts: reversible set-admin-password helper (bcrypt, captures old hash)

c5e8f58582eded04122da52f302b35361595d6b2 · 2026-08-28 00:25:12 -0700 · Steve Abrams

Files touched

Diff

commit c5e8f58582eded04122da52f302b35361595d6b2
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Aug 28 00:25:12 2026 -0700

    scripts: reversible set-admin-password helper (bcrypt, captures old hash)
---
 scripts/set-admin-password.mjs | 57 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 57 insertions(+)

diff --git a/scripts/set-admin-password.mjs b/scripts/set-admin-password.mjs
new file mode 100644
index 0000000..e666085
--- /dev/null
+++ b/scripts/set-admin-password.mjs
@@ -0,0 +1,57 @@
+#!/usr/bin/env node
+// One-off: set a tier_credentials account's password using Norma's own bcryptjs.
+// Reversible-by-design: captures the CURRENT password_hash to a timestamped
+// restore file BEFORE writing, and prints the exact rollback SQL.
+//
+//   TARGET_USER=admin NEW_PW='DW2024!' node scripts/set-admin-password.mjs
+//
+// Reads DATABASE_URL from the environment, or from ./.env if not already set.
+import bcrypt from 'bcryptjs';
+import pg from 'pg';
+import fs from 'node:fs';
+import path from 'node:path';
+
+if (!process.env.DATABASE_URL && fs.existsSync('.env')) {
+  for (const line of fs.readFileSync('.env', 'utf8').split('\n')) {
+    const m = line.match(/^\s*DATABASE_URL\s*=\s*(.*)$/);
+    if (m) { process.env.DATABASE_URL = m[1].trim().replace(/^["']|["']$/g, ''); break; }
+  }
+}
+if (!process.env.DATABASE_URL) { console.error('FATAL: DATABASE_URL not set (env or .env)'); process.exit(2); }
+
+const username = process.env.TARGET_USER || 'admin';
+const newPw = process.env.NEW_PW || 'DW2024!';
+
+const { Client } = pg;
+const c = new Client({ connectionString: process.env.DATABASE_URL });
+await c.connect();
+
+// 1. Capture current hash(es) for rollback
+const before = await c.query(
+  'SELECT username, role, password_hash FROM tier_credentials WHERE username = $1',
+  [username],
+);
+if (before.rows.length === 0) {
+  console.error(`NO ROW for username='${username}' — nothing changed.`);
+  await c.end();
+  process.exit(3);
+}
+const stamp = new Date().toISOString().replace(/[:.]/g, '-');
+const restoreDir = path.join('scripts', 'password-restore');
+fs.mkdirSync(restoreDir, { recursive: true });
+const restoreFile = path.join(restoreDir, `${username}-${stamp}.json`);
+fs.writeFileSync(restoreFile, JSON.stringify(before.rows, null, 2));
+
+// 2. Hash new password with Norma's bcryptjs (rounds are encoded in the hash)
+const hash = await bcrypt.hash(newPw, 12);
+
+// 3. Write
+const upd = await c.query(
+  'UPDATE tier_credentials SET password_hash = $1 WHERE username = $2 RETURNING username, role',
+  [hash, username],
+);
+await c.end();
+
+console.log(`OK: set password for ${upd.rowCount} row(s) — ${upd.rows.map(r => `${r.username}(role=${r.role})`).join(', ')}`);
+console.log(`ROLLBACK: old hash saved to ${restoreFile}`);
+console.log(`  to undo:  UPDATE tier_credentials SET password_hash='${before.rows[0].password_hash}' WHERE username='${username}';`);

← 1ea90de auto-data-snapshot: 2026-08-27T20:01:28 (1 data files) — age  ·  back to Norma Platform  ·  fix(set-admin-password): read .env.local first, echo DB host 7c027a1 →