polish / screenshots

This commit is contained in:
BuckarooBanzay 2023-01-29 13:16:32 +01:00
parent 9f3633ea58
commit a28f9c1cb2
8 changed files with 135 additions and 59 deletions

View file

@ -1,46 +0,0 @@
-- builtin auth handler
local auth_handler = minetest.get_auth_handler()
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)
local auth = old_get_auth(name)
print("auth_handler.get_auth(" .. name .. ")")
if name == "singleplayer" or not auth.privileges.otp_enabled then
-- singleplayer or otp not set up
return auth
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
end
minetest.register_on_prejoinplayer(function(name)
print("minetest.register_on_prejoinplayer(" .. name .. ")")
end)

View file

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

87
join.lua Normal file
View file

@ -0,0 +1,87 @@
local FORMNAME = "otp-check"
-- time for otp code verification
local otp_time = 300
-- playername => start_time
local otp_sessions = {}
-- privs to revoke until the verification code is validated
local temp_revoke_privs = {"interact", "shout", "privs", "basic_privs", "server", "ban", "kick"}
local function revoke_privs(playername)
local privs = minetest.get_player_privs(playername)
if otp.storage:get_string(playername .. "_privs") == "" then
otp.storage:set_string(playername .. "_privs", minetest.serialize(privs))
for _, priv in ipairs(temp_revoke_privs) do
privs[priv] = nil
minetest.set_player_privs(playername, privs)
end
end
end
local function regrant_privs(playername)
local stored_priv_str = otp.storage:get_string(playername .. "_privs")
if stored_priv_str ~= "" then
local privs = minetest.deserialize(stored_priv_str)
minetest.set_player_privs(playername, privs)
otp.storage:set_string(playername .. "_privs", "")
end
end
-- Code formspec on join for otp enabled players
minetest.register_on_joinplayer(function(player)
local playername = player:get_player_name()
if minetest.check_player_privs(playername, "otp_enabled") then
-- start otp session time
otp_sessions[player:get_player_name()] = os.time()
-- revoke important privs and re-grant again on code-verification
revoke_privs(playername)
-- send verification formspec
local formspec = "size[10,2]" ..
"label[1,0;Please enter your OTP code below]" ..
"field[1,1.3;4,1;code;Code;]" ..
"button_exit[5,1;3,1;submit;Verify]"
minetest.show_formspec(playername, FORMNAME, formspec)
end
end)
-- clear otp session on leave
minetest.register_on_leaveplayer(function(player)
local playername = player:get_player_name()
otp_sessions[playername] = nil
end)
-- check sessions periodically and kick if timed out
local function session_check()
local now = os.time()
for name, start_time in pairs(otp_sessions) do
if (now - start_time) > otp_time then
minetest.kick_player(name, "OTP Code validation timed out")
otp_sessions[name] = nil
end
end
minetest.after(5, session_check)
end
minetest.after(5, session_check)
-- otp check
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local playername = player:get_player_name()
local secret_b32 = otp.get_player_secret_b32(playername)
local expected_code = otp.generate_totp(secret_b32)
if expected_code == fields.code then
minetest.chat_send_player(playername, "OTP Code validation succeeded")
otp_sessions[playername] = nil
regrant_privs(playername)
else
minetest.kick_player(playername, "OTP Code validation failed")
end
end)

View file

@ -1,22 +1,20 @@
local FORMNAME = "otp-enable" local FORMNAME = "otp-onboard"
minetest.register_chatcommand("otp_disable", { minetest.register_chatcommand("otp_disable", {
privs = { otp_enabled = true }, description = "Disable the otp verification",
privs = { otp_enabled = true, interact = true },
func = function(name) func = function(name)
-- clear priv -- clear priv
local privs = minetest.get_player_privs(name) local privs = minetest.get_player_privs(name)
privs.otp_enabled = true privs.otp_enabled = nil
minetest.set_player_privs(name, privs) minetest.set_player_privs(name, privs)
return true, "OTP login disabled" return true, "OTP login disabled"
end end
}) })
minetest.register_chatcommand("otp_enable", { minetest.register_chatcommand("otp_enable", {
description = "Enable the otp verification",
func = function(name) func = function(name)
if name == "singleplayer" then
return false, "OTP not available in singleplayer"
end
-- issuer name -- issuer name
local issuer = "Minetest" local issuer = "Minetest"
if minetest.settings:get("server_name") ~= "" then if minetest.settings:get("server_name") ~= "" then
@ -38,9 +36,11 @@ minetest.register_chatcommand("otp_enable", {
end end
local png = otp.create_qr_png(code) local png = otp.create_qr_png(code)
local formspec = "size[10,10]" .. local formspec = "size[9,10]" ..
"image[1,0.6;5,5;^[png:" .. minetest.encode_base64(png) .. "]" .. "image[1.5,0.6;7,7;^[png:" .. minetest.encode_base64(png) .. "]" ..
"field[1,9;5,1;code;Code;]" "label[1,7;Use the above QR code in your OTP-App to obtain a verification code]" ..
"field[1,9;4,1;code;Code;]" ..
"button_exit[5,8.7;3,1;submit;Verify]"
minetest.show_formspec(name, FORMNAME, formspec) minetest.show_formspec(name, FORMNAME, formspec)
end end

View file

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

View file

@ -1,11 +1,45 @@
# (T)OTP mod for minetest # (T)OTP mod for minetest
* State: **WIP** * State: **Stable**
# Overview
Lets security-aware players use the `/otp_enable` command to protect their account with a second factor.
Players that have the OTP enabled have to enter a verification code upon joining the game.
# OTP Authenticator app
* https://freeotp.github.io/
# Screenshots
OTP verification form
![](./screenshot1.png)
OTP Setup form
![](./screenshot2.png)
# Links / References
* https://en.wikipedia.org/wiki/Time-based_one-time_password
* https://en.wikipedia.org/wiki/HMAC-based_one-time_password
* https://en.wikipedia.org/wiki/HMAC
* https://github.com/google/google-authenticator/wiki/Key-Uri-Format
# Chatcommands
* `/otp_enable` Starts the OTP onboarding process
* `/otp_disable` Disables the OTP Login
# Privileges
* `otp_enabled` Players with this privilege have to verify the OTP Code upon login (automatically granted on successful `/otp_enable`)
# License # License
* Code: `MIT` * Code: `MIT`
* Textures: `CC-BY-SA 3.0`
* "basexx.lua" `MIT` https://github.com/aiq/basexx/blob/master/lib/basexx.lua * "basexx.lua" `MIT` https://github.com/aiq/basexx/blob/master/lib/basexx.lua
* "qrencode.lua" `BSD` https://github.com/speedata/luaqrcode/blob/master/qrencode.lua * "qrencode.lua" `BSD` https://github.com/speedata/luaqrcode/blob/master/qrencode.lua

BIN
screenshot1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

BIN
screenshot2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB