Files
btmx/setup_admin.lua

86 lines
2.2 KiB
Lua

-- setup_admin.lua — one-shot script to set the admin password.
--
-- Run with: ./blog.com -i setup_admin.lua
-- (Redbean executes the script in the same Lua environment as the app,
-- so require "db" and the argon2 module are available.)
--
-- Uses argon2id with OWASP Password Storage Cheat Sheet parameters:
-- m_cost = 65536 (64 MiB memory)
-- t_cost = 3 (3 passes)
-- parallelism = 4
-- hash_len = 32
local db = require "db"
db.init()
-- Toggle terminal echo so the password is not shown while typing.
-- Returns true when echo state was actually changed (i.e. stdin is a
-- tty and stty exists); on Windows or piped stdin it fails silently
-- and input is read as-is.
local function set_echo(enabled)
local ok = os.execute("stty " .. (enabled and "echo" or "-echo") .. " 2>/dev/null")
return ok == true or ok == 0
end
local function read_password(prompt)
io.write(prompt)
io.flush()
local hidden = set_echo(false)
local pw = io.read("*l")
if hidden then
set_echo(true)
io.write("\n") -- the user's Enter was not echoed
io.flush()
end
return pw
end
local password = read_password("Enter new admin password: ")
if not password or password:match("^%s*$") then
io.stderr:write("Error: password cannot be empty.\n")
os.exit(1)
end
local confirm = read_password("Confirm admin password: ")
if password ~= confirm then
io.stderr:write("Error: passwords do not match.\n")
os.exit(1)
end
-- Generate a 16-byte random salt.
local salt
local ok_r, bytes = pcall(unix.getrandom, 16)
if ok_r and bytes and #bytes == 16 then
salt = bytes
else
local f = io.open("/dev/urandom", "rb")
if f then
salt = f:read(16)
f:close()
end
end
if not salt or #salt < 16 then
io.stderr:write("Error: could not generate a random salt.\n")
os.exit(1)
end
-- Hash with argon2id using OWASP-recommended parameters.
local ok_h, hash = pcall(argon2.hash_encoded, password, salt, {
variant = argon2.variants.argon2_id,
m_cost = 65536, -- 64 MiB
t_cost = 3,
parallelism = 4,
hash_len = 32,
})
if not ok_h or not hash then
io.stderr:write("Error: argon2 hashing failed: " .. tostring(hash) .. "\n")
os.exit(1)
end
db.set_setting("admin_password_hash", hash)
io.write("Admin password set successfully.\n")