86 lines
2.2 KiB
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")
|