onboard form and join check

This commit is contained in:
BuckarooBanzay 2023-01-29 12:13:50 +01:00
parent bb06a28048
commit 9f3633ea58
8 changed files with 96 additions and 19 deletions

View file

@ -17,4 +17,4 @@ jobs:
fetch-depth: 0 fetch-depth: 0
submodules: recursive submodules: recursive
- name: test - name: test
run: docker-compose up --exit-code-from test run: docker-compose up --exit-code-from test test

View file

@ -1,15 +1,46 @@
-- TODO -- builtin auth handler
local auth_handler = minetest.get_auth_handler() local auth_handler = minetest.get_auth_handler()
local old_get_auth = auth_handler.get_auth local old_get_auth = auth_handler.get_auth
-- time for otp to be enabled until properly logged in
local otp_time = 300
-- playername => start_time
local otp_sessions = {}
minetest.register_on_joinplayer(function(player)
-- reset otp session upon login
local playername = player:get_player_name()
otp_sessions[player:get_player_name()] = nil
print("minetest.register_on_joinplayer(" .. playername .. ")")
end)
-- override "get_auth" from builtin auth handler
auth_handler.get_auth = function(name) auth_handler.get_auth = function(name)
local auth = old_get_auth(name) local auth = old_get_auth(name)
if name ~= "singleplayer" then print("auth_handler.get_auth(" .. name .. ")")
-- replace runtime password with legacy password hash if name == "singleplayer" or not auth.privileges.otp_enabled then
auth.password = minetest.get_password_hash(name, "enter") -- singleplayer or otp not set up
return auth
end end
-- minetest.disconnect_player(name, "something, something")
local now = os.time()
local otp_session = otp_sessions[name]
if not otp_session or (now - otp_session) > otp_time then
-- otp session expired or not set up
otp_sessions[name] = now
end
-- replace runtime password with legacy password hash
--auth.password = minetest.get_password_hash(name, "enter")
return auth return auth
end end
minetest.register_on_prejoinplayer(function(name)
print("minetest.register_on_prejoinplayer(" .. name .. ")")
end)

View file

@ -8,8 +8,15 @@ services:
- "./:/root/.minetest/worlds/world/worldmods/otp/" - "./:/root/.minetest/worlds/world/worldmods/otp/"
- "./test/minetest.conf:/minetest.conf" - "./test/minetest.conf:/minetest.conf"
- "world:/root/.minetest/worlds/world" - "world:/root/.minetest/worlds/world"
minetest:
image: registry.gitlab.com/minetest/minetest/server:5.6.1
user: root
ports: ports:
- "30000:30000/udp" - "30000:30000/udp"
volumes:
- "./:/root/.minetest/worlds/world/worldmods/otp/"
- "world:/root/.minetest/worlds/world"
volumes: volumes:
world: {} world: {}

View file

@ -117,7 +117,8 @@ local function left_pad(str, s, len)
return str return str
end end
function otp.generate_totp(key, unix_time) function otp.generate_totp(secret_b32, unix_time)
local key = otp.basexx.from_base32(secret_b32)
unix_time = unix_time or os.time() unix_time = unix_time or os.time()
local tx = 30 local tx = 30
@ -168,3 +169,13 @@ function otp.generate_secret()
end end
return s return s
end end
-- get or generate per-player secret b32 ecoded
function otp.get_player_secret_b32(name)
local secret_b32 = otp.storage:get_string(name .. "_secret")
if secret_b32 == "" then
secret_b32 = otp.basexx.to_base32(otp.generate_secret())
otp.storage:set_string(name .. "_secret", secret_b32)
end
return secret_b32
end

View file

@ -30,14 +30,13 @@ end)
mtt.register("otp.generate_totp", function(callback) mtt.register("otp.generate_totp", function(callback)
local expected_code = 699847 local expected_code = 699847
local secret_b32 = "N6JGKMEKU2E6HQMLLNMJKBRRGVQ2ZKV7" local secret_b32 = "N6JGKMEKU2E6HQMLLNMJKBRRGVQ2ZKV7"
local secret = otp.basexx.from_base32(secret_b32)
local unix_time = 1640995200 local unix_time = 1640995200
local code, valid_seconds = otp.generate_totp(secret, unix_time) local code, valid_seconds = otp.generate_totp(secret_b32, unix_time)
assert(code == ""..expected_code) assert(code == ""..expected_code)
assert(valid_seconds > 0) assert(valid_seconds > 0)
code, valid_seconds = otp.generate_totp(secret) code, valid_seconds = otp.generate_totp(secret_b32)
print("Current code: " .. code .. " valid for " .. valid_seconds .. " seconds") print("Current code: " .. code .. " valid for " .. valid_seconds .. " seconds")
callback() callback()
end) end)

View file

@ -14,6 +14,7 @@ otp = {
dofile(MP.."/functions.lua") dofile(MP.."/functions.lua")
dofile(MP.."/onboard.lua") dofile(MP.."/onboard.lua")
dofile(MP.."/auth.lua") dofile(MP.."/auth.lua")
dofile(MP.."/privs.lua")
if minetest.get_modpath("mtt") and mtt.enabled then if minetest.get_modpath("mtt") and mtt.enabled then
dofile(MP.."/functions.spec.lua") dofile(MP.."/functions.spec.lua")

View file

@ -1,10 +1,23 @@
local FORMNAME = "otp-enable" local FORMNAME = "otp-enable"
local secret = otp.generate_secret() minetest.register_chatcommand("otp_disable", {
local secret_b32 = otp.basexx.to_base32(secret) privs = { otp_enabled = true },
func = function(name)
-- clear priv
local privs = minetest.get_player_privs(name)
privs.otp_enabled = true
minetest.set_player_privs(name, privs)
return true, "OTP login disabled"
end
})
minetest.register_chatcommand("otp_enable", { minetest.register_chatcommand("otp_enable", {
func = function(name) func = function(name)
if name == "singleplayer" then
return false, "OTP not available in singleplayer"
end
-- issuer name
local issuer = "Minetest" local issuer = "Minetest"
if minetest.settings:get("server_name") ~= "" then if minetest.settings:get("server_name") ~= "" then
issuer = minetest.settings:get("server_name") issuer = minetest.settings:get("server_name")
@ -12,12 +25,17 @@ minetest.register_chatcommand("otp_enable", {
issuer = minetest.settings:get("server_address") issuer = minetest.settings:get("server_address")
end end
local secret_b32 = otp.get_player_secret_b32(name)
-- url for the qr code
local url = "otpauth://totp/" .. issuer .. ":" .. name .. "?algorithm=SHA1&" .. local url = "otpauth://totp/" .. issuer .. ":" .. name .. "?algorithm=SHA1&" ..
"digits=6&issuer=" .. issuer .. "&period=30&" .. "digits=6&issuer=" .. issuer .. "&period=30&" ..
"secret=" .. secret_b32 "secret=" .. secret_b32
local ok, code = otp.qrcode(url) local ok, code = otp.qrcode(url)
assert(ok) if not ok then
return false, "qr code generation failed"
end
local png = otp.create_qr_png(code) local png = otp.create_qr_png(code)
local formspec = "size[10,10]" .. local formspec = "size[10,10]" ..
@ -34,12 +52,18 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end end
if fields.code then if fields.code then
print("Validating code for " .. player:get_player_name()) local playername = player:get_player_name()
local expected_code = otp.generate_totp(secret) local secret_b32 = otp.get_player_secret_b32(playername)
local expected_code = otp.generate_totp(secret_b32)
if expected_code == fields.code then if expected_code == fields.code then
print("Valid") -- set priv
local privs = minetest.get_player_privs(playername)
privs.otp_enabled = true
minetest.set_player_privs(playername, privs)
minetest.chat_send_player(playername, "Code validation succeeded, OTP login enabled")
else else
print("Invalid") minetest.chat_send_player(playername, "Code validation failed!")
end end
end end

4
privs.lua Normal file
View file

@ -0,0 +1,4 @@
minetest.register_privilege("otp_enabled", {
description = "otp enabled player"
})