const assert = require('node:assert/strict') const fs = require('node:fs') const path = require('node:path') const { execFileSync } = require('node:child_process') const root = path.resolve(__dirname, '../..') const credentialsPath = path.join(root, 'acceptance/super-admin-login.json') const base = 'http://127.0.0.1:18080/api/v1' const literal = value => `'${String(value).replaceAll("'", "''")}'` const sql = statement => execFileSync('docker', ['--context', 'colima-yawan', 'exec', '-i', 'WeKnora-postgres', 'psql', '-U', 'postgres', '-d', 'yawan_native_acceptance_20260915', '-v', 'ON_ERROR_STOP=1', '-At'], { input: statement, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim() async function request(route, data, token) { const response = await fetch(base + route, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) }, body: JSON.stringify(data), signal: AbortSignal.timeout(30000), }) const body = await response.json() assert.ok(response.ok && body.success !== false, `${route}: HTTP ${response.status}`) return body } async function main() { const desired = JSON.parse(fs.readFileSync(0, 'utf8')) assert.match(desired.username, /^[a-zA-Z0-9_-]{2,50}$/) assert.ok(typeof desired.password === 'string' && [...desired.password].length >= 3) const credentials = JSON.parse(fs.readFileSync(credentialsPath, 'utf8')) const oldLogin = await request('/auth/login', { email: credentials.email, password: credentials.password }) assert.equal(oldLogin.user.is_system_admin, true, 'Saved identity must already be a system administrator') assert.equal(oldLogin.user.is_active, true) const userID = oldLogin.user.id assert.match(userID, /^[a-f0-9-]{36}$/) assert.equal(sql(`SELECT count(*) FROM users WHERE id<>${literal(userID)} AND LOWER(username)=LOWER(${literal(desired.username)});`), '0', 'Requested account name already exists') const backup = path.join(root, 'acceptance', `super-admin-login-before-${Date.now()}.json`) fs.writeFileSync(backup, JSON.stringify(credentials, null, 2) + '\n', { flag: 'wx', mode: 0o600 }) // Keep the user ID and all ownership links; only change the login name. sql(`BEGIN; SELECT id FROM users WHERE id=${literal(userID)} AND is_system_admin=true AND is_active=true AND deleted_at IS NULL FOR UPDATE; UPDATE users SET username=${literal(desired.username)},updated_at=now() WHERE id=${literal(userID)} AND is_system_admin=true AND is_active=true AND deleted_at IS NULL; INSERT INTO audit_logs (tenant_id,actor_user_id,actor_role,action,target_type,target_id,target_user_id,outcome,details,created_at) SELECT tenant_id,id,'admin','system.user_updated','user',id,id,'success',${literal(JSON.stringify({ username_before: oldLogin.user.username, username_after: desired.username, source: 'authorized_account_update' }))}::jsonb,now() FROM users WHERE id=${literal(userID)}; COMMIT;`) const renamed = await request('/auth/login', { username: desired.username, password: credentials.password }) assert.equal(renamed.user.id, userID) if (credentials.password !== desired.password) { await request('/auth/change-password', { old_password: credentials.password, new_password: desired.password }, renamed.token) } const updated = { ...credentials, username: desired.username, password: desired.password } const temporary = credentialsPath + '.next' fs.writeFileSync(temporary, JSON.stringify(updated, null, 2) + '\n', { mode: 0o600 }) fs.renameSync(temporary, credentialsPath) const current = await request('/auth/login', { username: desired.username, password: desired.password }) assert.equal(current.user.id, userID) assert.equal(current.user.is_system_admin, true) if (credentials.password !== desired.password) { const revoked = await fetch(base + '/auth/me', { headers: { Authorization: `Bearer ${oldLogin.token}` }, signal: AbortSignal.timeout(15000) }) assert.equal(revoked.status, 401, 'Previous login must be revoked after password rotation') } console.log(JSON.stringify({ passed: true, username: current.user.username, identity_preserved: true, system_admin: true, password_rotated: credentials.password !== desired.password })) } main().catch(error => { console.error(error.message); process.exitCode = 1 })