mirror of
https://git.phreedom.club/localhost_frssoft/fediauth.git
synced 2024-11-25 01:11:30 +00:00
fork creation done
This commit is contained in:
parent
376d163d08
commit
3466db227f
12
.github/workflows/luacheck.yml
vendored
12
.github/workflows/luacheck.yml
vendored
|
@ -1,12 +0,0 @@
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
name: luacheck
|
|
||||||
jobs:
|
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@master
|
|
||||||
- name: lint
|
|
||||||
uses: Roang-zero1/factorio-mod-luacheck@master
|
|
||||||
with:
|
|
||||||
luacheckrc_url: ""
|
|
20
.github/workflows/test.yml
vendored
20
.github/workflows/test.yml
vendored
|
@ -1,20 +0,0 @@
|
||||||
name: test
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 10
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
ENGINE_VERSION: [5.3.0, 5.4.0, 5.5.0, 5.6.1, latest]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
submodules: recursive
|
|
||||||
- name: test
|
|
||||||
run: docker-compose up --exit-code-from test test
|
|
|
@ -1,5 +1,5 @@
|
||||||
globals = {
|
globals = {
|
||||||
"otp",
|
"fediauth",
|
||||||
"minetest"
|
"minetest"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,10 +13,5 @@ read_globals = {
|
||||||
"dump", "dump2",
|
"dump", "dump2",
|
||||||
"VoxelArea",
|
"VoxelArea",
|
||||||
|
|
||||||
-- testing
|
|
||||||
"mtt"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
files["qrencode.lua"] = {
|
|
||||||
ignore = {"631"}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
version: "3.6"
|
|
||||||
|
|
||||||
services:
|
|
||||||
test:
|
|
||||||
build: ./test
|
|
||||||
user: root
|
|
||||||
volumes:
|
|
||||||
- "./:/root/.minetest/worlds/world/worldmods/otp/"
|
|
||||||
- "./test/minetest.conf:/minetest.conf"
|
|
||||||
- "world:/root/.minetest/worlds/world"
|
|
||||||
|
|
||||||
minetest:
|
|
||||||
image: registry.gitlab.com/minetest/minetest/server:5.6.1
|
|
||||||
user: root
|
|
||||||
ports:
|
|
||||||
- "30000:30000/udp"
|
|
||||||
volumes:
|
|
||||||
- "./:/root/.minetest/worlds/world/worldmods/otp/"
|
|
||||||
- "world:/root/.minetest/worlds/world"
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
world: {}
|
|
|
@ -51,7 +51,7 @@ local function lshift(x, by)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- big-endian uint64 of a number
|
-- big-endian uint64 of a number
|
||||||
function otp.write_uint64_be(v)
|
function fediauth.write_uint64_be(v)
|
||||||
local b1 = bitand( rshift(v, 56), 0xFF )
|
local b1 = bitand( rshift(v, 56), 0xFF )
|
||||||
local b2 = bitand( rshift(v, 48), 0xFF )
|
local b2 = bitand( rshift(v, 48), 0xFF )
|
||||||
local b3 = bitand( rshift(v, 40), 0xFF )
|
local b3 = bitand( rshift(v, 40), 0xFF )
|
||||||
|
@ -73,7 +73,7 @@ for _=1,64 do
|
||||||
end
|
end
|
||||||
|
|
||||||
-- hmac generation
|
-- hmac generation
|
||||||
function otp.hmac(key, message)
|
function fediauth.hmac(key, message)
|
||||||
local i_key_pad = ""
|
local i_key_pad = ""
|
||||||
for i=1,64 do
|
for i=1,64 do
|
||||||
i_key_pad = i_key_pad .. string.char(bitxor(string.byte(key, i) or 0x00, string.byte(i_pad, i)))
|
i_key_pad = i_key_pad .. string.char(bitxor(string.byte(key, i) or 0x00, string.byte(i_pad, i)))
|
||||||
|
@ -117,16 +117,16 @@ local function left_pad(str, s, len)
|
||||||
return str
|
return str
|
||||||
end
|
end
|
||||||
|
|
||||||
function otp.generate_totp(secret_b32, unix_time)
|
function fediauth.generate_tfediauth(secret_b32, unix_time)
|
||||||
local key = otp.basexx.from_base32(secret_b32)
|
local key = fediauth.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
|
||||||
local ct = math.floor(unix_time / tx)
|
local ct = math.floor(unix_time / tx)
|
||||||
local counter = otp.write_uint64_be(ct)
|
local counter = fediauth.write_uint64_be(ct)
|
||||||
local valid_seconds = ((ct * tx) + tx) - unix_time
|
local valid_seconds = ((ct * tx) + tx) - unix_time
|
||||||
|
|
||||||
local hmac = otp.hmac(key, counter)
|
local hmac = fediauth.hmac(key, counter)
|
||||||
|
|
||||||
-- https://www.rfc-editor.org/rfc/rfc4226#section-5.4
|
-- https://www.rfc-editor.org/rfc/rfc4226#section-5.4
|
||||||
local offset = bitand(string.byte(hmac, #hmac), 0xF)
|
local offset = bitand(string.byte(hmac, #hmac), 0xF)
|
||||||
|
@ -141,7 +141,7 @@ function otp.generate_totp(secret_b32, unix_time)
|
||||||
return padded_code, valid_seconds
|
return padded_code, valid_seconds
|
||||||
end
|
end
|
||||||
|
|
||||||
function otp.create_qr_png(data)
|
function fediauth.create_qr_png(data)
|
||||||
local height = #data + 2
|
local height = #data + 2
|
||||||
local width = height
|
local width = height
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ function otp.create_qr_png(data)
|
||||||
return minetest.encode_png(width, height, png_data, 2)
|
return minetest.encode_png(width, height, png_data, 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
function otp.generate_secret()
|
function fediauth.generate_secret()
|
||||||
local buf = minetest.sha1("" .. math.random(10000), true)
|
local buf = minetest.sha1("" .. math.random(10000), true)
|
||||||
local s = ""
|
local s = ""
|
||||||
for i=1,20 do
|
for i=1,20 do
|
||||||
|
@ -184,30 +184,46 @@ function otp.generate_secret()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- get or generate per-player secret b32 ecoded
|
-- get or generate per-player secret b32 ecoded
|
||||||
function otp.get_player_secret_b32(name)
|
function fediauth.get_player_secret_b32(name)
|
||||||
local secret_b32 = otp.storage:get_string(name .. "_secret")
|
local secret_b32 = fediauth.storage:get_string(name .. "_secret")
|
||||||
if secret_b32 == "" then
|
if secret_b32 == "" then
|
||||||
secret_b32 = otp.basexx.to_base32(otp.generate_secret())
|
secret_b32 = fediauth.basexx.to_base32(fediauth.generate_secret())
|
||||||
otp.storage:set_string(name .. "_secret", secret_b32)
|
fediauth.storage:set_string(name .. "_secret", secret_b32)
|
||||||
end
|
end
|
||||||
return secret_b32
|
return secret_b32
|
||||||
end
|
end
|
||||||
|
|
||||||
-- returns true if the player is otp enabled _and_ set up properly
|
-- returns true if the player is fediauth enabled _and_ set up properly
|
||||||
function otp.is_player_enabled(name)
|
function fediauth.is_player_enabled(name)
|
||||||
local has_secret = otp.storage:get_string(name .. "_secret") ~= ""
|
local has_secret = fediauth.storage:get_string(name .. "_secret") ~= ""
|
||||||
local has_priv = minetest.check_player_privs(name, "otp_enabled")
|
local has_priv = minetest.check_player_privs(name, "fediauth_enabled")
|
||||||
|
|
||||||
return has_secret and has_priv
|
return has_secret and has_priv
|
||||||
end
|
end
|
||||||
|
|
||||||
function otp.check_code(secret_b32, code, time)
|
function fediauth.is_player_bypassed(name)
|
||||||
|
local has_priv = minetest.check_player_privs(name, "fediauth_bypass")
|
||||||
|
|
||||||
|
return has_priv
|
||||||
|
end
|
||||||
|
|
||||||
|
function fediauth.check_code(secret_b32, code, time)
|
||||||
time = time or os.time()
|
time = time or os.time()
|
||||||
for _, t_offset in ipairs({0, -30, 30}) do
|
for _, t_offset in ipairs({0, -30, 30, -60, 60}) do
|
||||||
local expected_code = otp.generate_totp(secret_b32, time + t_offset)
|
local expected_code = fediauth.generate_tfediauth(secret_b32, time + t_offset)
|
||||||
if expected_code == code then
|
if expected_code == code then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function fediauth.give_code(secret_b32, time)
|
||||||
|
time = time or os.time()
|
||||||
|
local codeseq = {}
|
||||||
|
for _, t_offset in ipairs({60, 30, 0, -30, -60}) do
|
||||||
|
local expected_code = fediauth.generate_tfediauth(secret_b32, time + t_offset)
|
||||||
|
table.insert(codeseq, expected_code)
|
||||||
|
end
|
||||||
|
return codeseq
|
||||||
|
end
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
mtt.register("otp.hmac", function(callback)
|
|
||||||
local secret_b32 = "N6JGKMEKU2E6HQMLLNMJKBRRGVQ2ZKV7"
|
|
||||||
local secret = otp.basexx.from_base32(secret_b32)
|
|
||||||
local unix_time = 1640995200
|
|
||||||
|
|
||||||
local expected_hmac = otp.basexx.from_base64("m04YheEb7i+ThPMUJEfVXVybZFo=")
|
|
||||||
assert(#expected_hmac == 20)
|
|
||||||
|
|
||||||
local tx = 30
|
|
||||||
local ct = math.floor(unix_time / tx)
|
|
||||||
local counter = otp.write_uint64_be(ct)
|
|
||||||
|
|
||||||
assert( #secret == 20 )
|
|
||||||
assert( otp.basexx.to_base64(secret) == "b5JlMIqmiePBi1tYlQYxNWGsqr8=" )
|
|
||||||
assert( #counter == 8 )
|
|
||||||
assert( otp.basexx.to_base64(counter) == "AAAAAANCp0A=" )
|
|
||||||
|
|
||||||
local hmac = otp.hmac(secret, counter)
|
|
||||||
assert(#hmac == 20)
|
|
||||||
|
|
||||||
for i=1,20 do
|
|
||||||
assert( string.byte(expected_hmac,i) == string.byte(hmac, i) )
|
|
||||||
end
|
|
||||||
|
|
||||||
callback()
|
|
||||||
end)
|
|
||||||
|
|
||||||
mtt.register("otp.generate_totp", function(callback)
|
|
||||||
local expected_code = "699847"
|
|
||||||
local secret_b32 = "N6JGKMEKU2E6HQMLLNMJKBRRGVQ2ZKV7"
|
|
||||||
local unix_time = 1640995200
|
|
||||||
|
|
||||||
local code, valid_seconds = otp.generate_totp(secret_b32, unix_time)
|
|
||||||
assert(code == expected_code)
|
|
||||||
assert(valid_seconds > 0)
|
|
||||||
|
|
||||||
code, valid_seconds = otp.generate_totp(secret_b32)
|
|
||||||
print("Current code: " .. code .. " valid for " .. valid_seconds .. " seconds")
|
|
||||||
callback()
|
|
||||||
end)
|
|
||||||
|
|
||||||
mtt.register("otp.check_code", function(callback)
|
|
||||||
local expected_code = "699847"
|
|
||||||
local secret_b32 = "N6JGKMEKU2E6HQMLLNMJKBRRGVQ2ZKV7"
|
|
||||||
local unix_time = 1640995200
|
|
||||||
|
|
||||||
assert(otp.check_code(secret_b32, expected_code, unix_time))
|
|
||||||
assert(otp.check_code(secret_b32, expected_code, unix_time+30))
|
|
||||||
assert(otp.check_code(secret_b32, expected_code, unix_time-30))
|
|
||||||
assert(not otp.check_code(secret_b32, expected_code, unix_time-60))
|
|
||||||
assert(not otp.check_code(secret_b32, expected_code, unix_time+60))
|
|
||||||
assert(not otp.check_code(secret_b32, expected_code))
|
|
||||||
|
|
||||||
callback()
|
|
||||||
end)
|
|
||||||
|
|
||||||
mtt.register("otp.create_qr_png", function(callback)
|
|
||||||
local url = "otpauth://totp/abc:myaccount?algorithm=SHA1&digits=6&issuer=abc&period=30&"
|
|
||||||
.. "secret=N6JGKMEKU2E6HQMLLNMJKBRRGVQ2ZKV7"
|
|
||||||
|
|
||||||
local ok, code = otp.qrcode(url)
|
|
||||||
assert(ok)
|
|
||||||
assert(code)
|
|
||||||
|
|
||||||
local png = otp.create_qr_png(code)
|
|
||||||
assert(png)
|
|
||||||
|
|
||||||
local f = io.open(minetest.get_worldpath() .. "/qr.png", "w")
|
|
||||||
f:write(png)
|
|
||||||
f:close()
|
|
||||||
callback()
|
|
||||||
end)
|
|
||||||
|
|
||||||
mtt.register("otp.generate_secret", function(callback)
|
|
||||||
local s = otp.generate_secret()
|
|
||||||
assert(#s == 20)
|
|
||||||
callback()
|
|
||||||
end)
|
|
29
init.lua
29
init.lua
|
@ -1,25 +1,28 @@
|
||||||
local MP = minetest.get_modpath("otp")
|
local MP = minetest.get_modpath("fediauth")
|
||||||
|
local http = minetest.request_http_api()
|
||||||
|
|
||||||
-- sanity checks
|
-- sanity checks
|
||||||
assert(type(minetest.encode_png) == "function")
|
assert(type(minetest.encode_png) == "function")
|
||||||
|
|
||||||
otp = {
|
fediauth = {
|
||||||
-- mod storage
|
-- mod storage
|
||||||
storage = minetest.get_mod_storage(),
|
storage = minetest.get_mod_storage(),
|
||||||
|
|
||||||
-- baseXX functions
|
-- baseXX functions
|
||||||
basexx = loadfile(MP.."/basexx.lua")(),
|
basexx = loadfile(MP.."/basexx.lua")(),
|
||||||
|
|
||||||
-- qr code
|
|
||||||
qrcode = loadfile(MP.."/qrencode.lua")().qrcode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dofile(MP.."/functions.lua")
|
dofile(MP.."/mastoapi.lua")
|
||||||
dofile(MP.."/onboard.lua")
|
local instance = minetest.settings:get("fediauth.instance")
|
||||||
dofile(MP.."/join.lua")
|
local key = minetest.settings:get("fediauth.api_token")
|
||||||
dofile(MP.."/privs.lua")
|
if not instance or not key then
|
||||||
dofile(MP.."/priv_revoke.lua")
|
minetest.log("warning", "[fediauth] For working fediauth you should specify fediauth.instance and fediauth.api_token")
|
||||||
|
else
|
||||||
if minetest.get_modpath("mtt") and mtt.enabled then
|
mastoapi_init(http, instance, key)
|
||||||
dofile(MP.."/functions.spec.lua")
|
dofile(MP.."/functions.lua")
|
||||||
|
dofile(MP.."/onboard.lua")
|
||||||
|
dofile(MP.."/join.lua")
|
||||||
|
dofile(MP.."/privs.lua")
|
||||||
|
dofile(MP.."/priv_revoke.lua")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
133
join.lua
133
join.lua
|
@ -1,26 +1,73 @@
|
||||||
local FORMNAME = "otp-check"
|
local FORMNAME = "fediauth-check"
|
||||||
|
local FORMNAMEFEDI = "fediauth-check-fedi"
|
||||||
|
|
||||||
-- time for otp code verification
|
-- time for fediauth code verification
|
||||||
local otp_time = 300
|
local fediauth_time = 300
|
||||||
|
|
||||||
-- playername => start_time
|
-- playername => start_time
|
||||||
local otp_sessions = {}
|
local fediauth_sessions = {}
|
||||||
|
|
||||||
-- Code formspec on join for otp enabled players
|
local formspecfediadd = "size[9,10]" ..
|
||||||
|
"label[1,7;Input your fediverse account handle]" ..
|
||||||
|
"image[1.5,0.6;7,7;fediverse.png]" ..
|
||||||
|
"field[1,9;4,1;fediverse_account_url;@nick@example.com;]" ..
|
||||||
|
"button[5,8.7;3,1;submit;Send code]"
|
||||||
|
|
||||||
|
local feditempstore = {}
|
||||||
|
|
||||||
|
minetest.register_entity("fediauth:checkmark", {
|
||||||
|
initial_properties = {
|
||||||
|
pointable = false,
|
||||||
|
armor_groups = { immortal = 1 },
|
||||||
|
visual = "sprite",
|
||||||
|
visual_size = {x = 0.5, y = 0.5},
|
||||||
|
textures = { "checkmark.png^[opacity:180" },
|
||||||
|
use_texture_alpha = true,
|
||||||
|
static_save = false,
|
||||||
|
glow = 5,
|
||||||
|
},
|
||||||
|
on_detach = function(self, parent) self.object:remove() end
|
||||||
|
})
|
||||||
|
|
||||||
|
function fediauth.verified_checkmark(player, verified)
|
||||||
|
local tag = player:get_player_name()
|
||||||
|
local props = player:get_properties()
|
||||||
|
if verified then
|
||||||
|
local obj = minetest.add_entity({x=0,y=2,z=0}, "fediauth:checkmark", nil)
|
||||||
|
obj:set_attach(player, "Head", {x = 0, y = 12, z = 0})
|
||||||
|
player:set_properties({nametag = props.nametag .. " [FEDI]", nametag_color = "#00ff00" })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Code formspec on join for fediauth enabled players
|
||||||
minetest.register_on_joinplayer(function(player)
|
minetest.register_on_joinplayer(function(player)
|
||||||
local playername = player:get_player_name()
|
local playername = player:get_player_name()
|
||||||
if otp.is_player_enabled(playername) then
|
if fediauth.is_player_bypassed(playername) then return end
|
||||||
minetest.log("action", "[otp] session start for player: '" .. playername .. "'")
|
if fediauth.is_player_enabled(playername) or minetest.settings:get_bool("fediauth.fedi_required", false) then
|
||||||
|
minetest.log("action", "[fediauth] session start for player: '" .. playername .. "'")
|
||||||
|
|
||||||
-- start otp session time
|
-- start fediauth session time
|
||||||
otp_sessions[player:get_player_name()] = os.time()
|
fediauth_sessions[player:get_player_name()] = os.time()
|
||||||
|
|
||||||
-- revoke important privs and re-grant again on code-verification
|
-- revoke important privs and re-grant again on code-verification
|
||||||
otp.revoke_privs(playername)
|
fediauth.revoke_privs(playername)
|
||||||
|
|
||||||
|
-- if fedi only allowed
|
||||||
|
if minetest.settings:get_bool("fediauth.fedi_required", false) then
|
||||||
|
local existsfedi = fediauth.storage:get_string(playername .. "_fedi")
|
||||||
|
if existsfedi == "" or not existsfedi then
|
||||||
|
minetest.log("action", "[fediauth] request fedi account for player: '" .. playername .. "'")
|
||||||
|
minetest.show_formspec(playername, FORMNAMEFEDI, formspecfediadd)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local secret_b32 = fediauth.get_player_secret_b32(playername)
|
||||||
|
local codeseq = fediauth.give_code(secret_b32)
|
||||||
|
fediauth.send_code(codeseq[1], fediauth.storage:get_string(playername .. "_fedi"))
|
||||||
-- send verification formspec
|
-- send verification formspec
|
||||||
local formspec = "size[10,2]" ..
|
local formspec = "size[10,2]" ..
|
||||||
"label[1,0;Please enter your OTP code below]" ..
|
"label[1,0;Please check your fedi account and enter code]" ..
|
||||||
"field[1,1.3;4,1;code;Code;]" ..
|
"field[1,1.3;4,1;code;Code;]" ..
|
||||||
"button_exit[5,1;3,1;submit;Verify]"
|
"button_exit[5,1;3,1;submit;Verify]"
|
||||||
|
|
||||||
|
@ -28,39 +75,73 @@ minetest.register_on_joinplayer(function(player)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- clear otp session on leave
|
-- clear fediauth session on leave
|
||||||
minetest.register_on_leaveplayer(function(player)
|
minetest.register_on_leaveplayer(function(player)
|
||||||
local playername = player:get_player_name()
|
local playername = player:get_player_name()
|
||||||
otp_sessions[playername] = nil
|
fediauth_sessions[playername] = nil
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- check sessions periodically and kick if timed out
|
-- check sessions periodically and kick if timed out
|
||||||
local function session_check()
|
local function session_check()
|
||||||
local now = os.time()
|
local now = os.time()
|
||||||
for name, start_time in pairs(otp_sessions) do
|
for name, start_time in pairs(fediauth_sessions) do
|
||||||
if (now - start_time) > otp_time then
|
if (now - start_time) > fediauth_time then
|
||||||
minetest.kick_player(name, "OTP Code validation timed out")
|
minetest.kick_player(name, "fediauth code validation timed out")
|
||||||
otp_sessions[name] = nil
|
fediauth_sessions[name] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
minetest.after(5, session_check)
|
minetest.after(5, session_check)
|
||||||
end
|
end
|
||||||
minetest.after(5, session_check)
|
minetest.after(5, session_check)
|
||||||
|
|
||||||
-- otp check
|
-- fediauth check
|
||||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||||
if formname ~= FORMNAME then
|
if formname ~= FORMNAME and formname ~= FORMNAMEFEDI then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local playername = player:get_player_name()
|
local playername = player:get_player_name()
|
||||||
local secret_b32 = otp.get_player_secret_b32(playername)
|
local secret_b32 = fediauth.get_player_secret_b32(playername)
|
||||||
if otp.check_code(secret_b32, fields.code) then
|
|
||||||
minetest.chat_send_player(playername, "OTP Code validation succeeded")
|
-- check for new player or doesn't have fedi account
|
||||||
otp_sessions[playername] = nil
|
if fields.fediverse_account_url then
|
||||||
otp.regrant_privs(playername)
|
if not string.starts(fields.fediverse_account_url, "@") or string.len(fields.fediverse_account_url) < 3 or string.len(fields.fediverse_account_url) > 100 then
|
||||||
|
minetest.chat_send_player(playername, minetest.colorize("#ff0000", "Try again, your input is incorrect"))
|
||||||
|
minetest.show_formspec(playername, FORMNAMEFEDI, formspecfediadd)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local secret_b32 = fediauth.get_player_secret_b32(playername)
|
||||||
|
local codeseq = fediauth.give_code(secret_b32)
|
||||||
|
fediauth.send_code(codeseq[1], fields.fediverse_account_url)
|
||||||
|
feditempstore[playername] = fields.fediverse_account_url
|
||||||
|
local formspec = "size[9,10]" ..
|
||||||
|
"label[1,7;Check code on " .. minetest.formspec_escape(fields.fediverse_account_url) .. "]" ..
|
||||||
|
"field[1,9;4,1;code;Code;]" ..
|
||||||
|
"button_exit[5,8.7;3,1;submit;Verify]"
|
||||||
|
|
||||||
|
minetest.show_formspec(playername, FORMNAME, formspec)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if fediauth.check_code(secret_b32, fields.code) then
|
||||||
|
local fedi_account = fediauth.storage:get_string(playername .. "_fedi")
|
||||||
|
|
||||||
|
-- for account without fediverse (for prevent write account if code incorrect
|
||||||
|
if fedi_account == "" and feditempstore[playername] then
|
||||||
|
fediauth.storage:set_string(playername .. "_fedi", feditempstore[playername])
|
||||||
|
fedi_account = feditempstore[playername]
|
||||||
|
feditempstore[playername] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.chat_send_player(playername, minetest.colorize("#00ff00", "fediauth code validation succeeded for " .. fedi_account))
|
||||||
|
fediauth_sessions[playername] = nil
|
||||||
|
fediauth.regrant_privs(playername)
|
||||||
|
fediauth.verified_checkmark(player, true)
|
||||||
else
|
else
|
||||||
minetest.kick_player(playername, "OTP Code validation failed")
|
minetest.kick_player(playername, "fediauth code validation failed")
|
||||||
otp.regrant_privs(playername)
|
fediauth.regrant_privs(playername)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
135
license.txt
135
license.txt
|
@ -1,25 +1,122 @@
|
||||||
License of source code
|
Creative Commons Legal Code
|
||||||
----------------------
|
|
||||||
|
|
||||||
The MIT License (MIT)
|
CC0 1.0 Universal
|
||||||
Copyright (C) 2023 BuckarooBanzay
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||||
software and associated documentation files (the "Software"), to deal in the Software
|
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||||
without restriction, including without limitation the rights to use, copy, modify, merge,
|
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||||
publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||||
persons to whom the Software is furnished to do so, subject to the following conditions:
|
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||||
|
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||||
|
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||||
|
HEREUNDER.
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or
|
Statement of Purpose
|
||||||
substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
The laws of most jurisdictions throughout the world automatically confer
|
||||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||||
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
authorship and/or a database (each, a "Work").
|
||||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
For more details:
|
Certain owners wish to permanently relinquish those rights to a Work for
|
||||||
https://opensource.org/licenses/MIT
|
the purpose of contributing to a commons of creative, cultural and
|
||||||
|
scientific works ("Commons") that the public can reliably and without fear
|
||||||
|
of later claims of infringement build upon, modify, incorporate in other
|
||||||
|
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||||
|
and for any purposes, including without limitation commercial purposes.
|
||||||
|
These owners may contribute to the Commons to promote the ideal of a free
|
||||||
|
culture and the further production of creative, cultural and scientific
|
||||||
|
works, or to gain reputation or greater distribution for their Work in
|
||||||
|
part through the use and efforts of others.
|
||||||
|
|
||||||
|
For these and/or other purposes and motivations, and without any
|
||||||
|
expectation of additional consideration or compensation, the person
|
||||||
|
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||||
|
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||||
|
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||||
|
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||||
|
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||||
|
|
||||||
|
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||||
|
protected by copyright and related or neighboring rights ("Copyright and
|
||||||
|
Related Rights"). Copyright and Related Rights include, but are not
|
||||||
|
limited to, the following:
|
||||||
|
|
||||||
|
i. the right to reproduce, adapt, distribute, perform, display,
|
||||||
|
communicate, and translate a Work;
|
||||||
|
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||||
|
iii. publicity and privacy rights pertaining to a person's image or
|
||||||
|
likeness depicted in a Work;
|
||||||
|
iv. rights protecting against unfair competition in regards to a Work,
|
||||||
|
subject to the limitations in paragraph 4(a), below;
|
||||||
|
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||||
|
in a Work;
|
||||||
|
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||||
|
European Parliament and of the Council of 11 March 1996 on the legal
|
||||||
|
protection of databases, and under any national implementation
|
||||||
|
thereof, including any amended or successor version of such
|
||||||
|
directive); and
|
||||||
|
vii. other similar, equivalent or corresponding rights throughout the
|
||||||
|
world based on applicable law or treaty, and any national
|
||||||
|
implementations thereof.
|
||||||
|
|
||||||
|
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||||
|
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||||
|
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||||
|
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||||
|
of action, whether now known or unknown (including existing as well as
|
||||||
|
future claims and causes of action), in the Work (i) in all territories
|
||||||
|
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||||
|
treaty (including future time extensions), (iii) in any current or future
|
||||||
|
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||||
|
including without limitation commercial, advertising or promotional
|
||||||
|
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||||
|
member of the public at large and to the detriment of Affirmer's heirs and
|
||||||
|
successors, fully intending that such Waiver shall not be subject to
|
||||||
|
revocation, rescission, cancellation, termination, or any other legal or
|
||||||
|
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||||
|
as contemplated by Affirmer's express Statement of Purpose.
|
||||||
|
|
||||||
|
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||||
|
be judged legally invalid or ineffective under applicable law, then the
|
||||||
|
Waiver shall be preserved to the maximum extent permitted taking into
|
||||||
|
account Affirmer's express Statement of Purpose. In addition, to the
|
||||||
|
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||||
|
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||||
|
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||||
|
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||||
|
maximum duration provided by applicable law or treaty (including future
|
||||||
|
time extensions), (iii) in any current or future medium and for any number
|
||||||
|
of copies, and (iv) for any purpose whatsoever, including without
|
||||||
|
limitation commercial, advertising or promotional purposes (the
|
||||||
|
"License"). The License shall be deemed effective as of the date CC0 was
|
||||||
|
applied by Affirmer to the Work. Should any part of the License for any
|
||||||
|
reason be judged legally invalid or ineffective under applicable law, such
|
||||||
|
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||||
|
of the License, and in such case Affirmer hereby affirms that he or she
|
||||||
|
will not (i) exercise any of his or her remaining Copyright and Related
|
||||||
|
Rights in the Work or (ii) assert any associated claims and causes of
|
||||||
|
action with respect to the Work, in either case contrary to Affirmer's
|
||||||
|
express Statement of Purpose.
|
||||||
|
|
||||||
|
4. Limitations and Disclaimers.
|
||||||
|
|
||||||
|
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||||
|
surrendered, licensed or otherwise affected by this document.
|
||||||
|
b. Affirmer offers the Work as-is and makes no representations or
|
||||||
|
warranties of any kind concerning the Work, express, implied,
|
||||||
|
statutory or otherwise, including without limitation warranties of
|
||||||
|
title, merchantability, fitness for a particular purpose, non
|
||||||
|
infringement, or the absence of latent or other defects, accuracy, or
|
||||||
|
the present or absence of errors, whether or not discoverable, all to
|
||||||
|
the greatest extent permissible under applicable law.
|
||||||
|
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||||
|
that may apply to the Work or any use thereof, including without
|
||||||
|
limitation any person's Copyright and Related Rights in the Work.
|
||||||
|
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||||
|
consents, permissions or other rights required for any use of the
|
||||||
|
Work.
|
||||||
|
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||||
|
party to this document and has no duty or obligation with respect to
|
||||||
|
this CC0 or use of the Work.
|
||||||
|
|
||||||
|
|
27
mastoapi.lua
Normal file
27
mastoapi.lua
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
local http, instance, key
|
||||||
|
|
||||||
|
function fediauth.send_code(code, account_handle)
|
||||||
|
local status = {
|
||||||
|
visibility = "direct",
|
||||||
|
status = "" .. code .. " " .. account_handle .. " code for minetest fediauth, do not share it!"
|
||||||
|
}
|
||||||
|
local json = minetest.write_json(status)
|
||||||
|
http.fetch({
|
||||||
|
url = "https://" .. instance .. "/api/v1/statuses",
|
||||||
|
extra_headers = { "Content-Type: application/json", "Authorization: Bearer " .. key },
|
||||||
|
timeout = 15,
|
||||||
|
post_data = json
|
||||||
|
}, function(res)
|
||||||
|
if res then
|
||||||
|
minetest.log("action", "[fediauth] code sent to: '" .. account_handle .. "'")
|
||||||
|
else
|
||||||
|
minetest.log("error", "[fediauth] code not sent to: '" .. account_handle .. "'")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function mastoapi_init(h, i, k)
|
||||||
|
http = h
|
||||||
|
instance = i
|
||||||
|
key = k
|
||||||
|
end
|
3
mod.conf
3
mod.conf
|
@ -1,3 +1,2 @@
|
||||||
name = otp
|
name = fediauth
|
||||||
optional_depends = mtt
|
|
||||||
min_minetest_version = 5.3
|
min_minetest_version = 5.3
|
103
onboard.lua
103
onboard.lua
|
@ -1,75 +1,78 @@
|
||||||
local FORMNAME = "otp-onboard"
|
local FORMNAME = "fediauth-onboard"
|
||||||
|
local FORMNAMEFEDI = "fediauth-onboard-fedi"
|
||||||
|
|
||||||
minetest.register_chatcommand("otp_disable", {
|
local feditempstore = {}
|
||||||
description = "Disable the otp verification",
|
|
||||||
privs = { otp_enabled = true, interact = true },
|
function string.starts(String,Start)
|
||||||
|
return string.sub(String,1,string.len(Start))==Start
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.register_chatcommand("fediauth_off", {
|
||||||
|
description = "Disable the fediauth verification",
|
||||||
|
privs = { fediauth_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 = nil
|
privs.fediauth_enabled = nil
|
||||||
minetest.set_player_privs(name, privs)
|
minetest.set_player_privs(name, privs)
|
||||||
return true, "OTP login disabled"
|
return true, "fediauth login disabled"
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
minetest.register_chatcommand("otp_enable", {
|
minetest.register_chatcommand("fediauth_on", {
|
||||||
description = "Enable the otp verification",
|
description = "Enable the fediauth verification",
|
||||||
func = function(name)
|
func = function(name)
|
||||||
-- issuer name
|
local secret_b32 = fediauth.get_player_secret_b32(name)
|
||||||
local issuer = minetest.settings:get("otp.issuer") or "Minetest"
|
|
||||||
local server_name = minetest.settings:get("server_name")
|
local formspec_account = "size[9,10]" ..
|
||||||
local server_address = minetest.settings:get("server_address")
|
"label[1,7;Input your fediverse account handle]" ..
|
||||||
if server_name and server_name ~= "" then
|
"image[1.5,0.6;7,7;fediverse.png]" ..
|
||||||
issuer = server_name
|
"field[1,9;4,1;fediverse_account_url;@nick@example.com;]" ..
|
||||||
elseif server_address and server_address ~= "" then
|
"button[5,8.7;3,1;submit;Send code]"
|
||||||
issuer = server_address
|
|
||||||
|
minetest.show_formspec(name, FORMNAMEFEDI, formspec_account)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- authenticator image
|
|
||||||
local image = minetest.settings:get("otp.authenticator_image") or
|
|
||||||
"https://raw.githubusercontent.com/minetest/minetest/master/misc/minetest-xorg-icon-128.png"
|
|
||||||
|
|
||||||
local secret_b32 = otp.get_player_secret_b32(name)
|
|
||||||
|
|
||||||
-- url for the qr code
|
|
||||||
local url = "otpauth://totp/" .. issuer .. ":" .. name .. "?algorithm=SHA1" ..
|
|
||||||
"&digits=6" ..
|
|
||||||
"&issuer=" .. issuer ..
|
|
||||||
"&period=30" ..
|
|
||||||
"&secret=" .. secret_b32 ..
|
|
||||||
"&image=" .. image
|
|
||||||
|
|
||||||
local ok, code = otp.qrcode(url)
|
|
||||||
if not ok then
|
|
||||||
return false, "qr code generation failed"
|
|
||||||
end
|
|
||||||
|
|
||||||
local png = otp.create_qr_png(code)
|
|
||||||
local formspec = "size[9,10]" ..
|
|
||||||
"image[1.5,0.6;7,7;^[png:" .. minetest.encode_base64(png) .. "]" ..
|
|
||||||
"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)
|
|
||||||
end
|
|
||||||
})
|
})
|
||||||
|
|
||||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||||
if formname ~= FORMNAME then
|
if formname ~= FORMNAME and formname ~= FORMNAMEFEDI then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if fields.fediverse_account_url then
|
||||||
|
local playername = player:get_player_name()
|
||||||
|
if not string.starts(fields.fediverse_account_url, "@") or string.len(fields.fediverse_account_url) < 3 or string.len(fields.fediverse_account_url) > 100 then
|
||||||
|
minetest.chat_send_player(playername, minetest.colorize("#ff0000", "Try again, your input is incorrect"))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local secret_b32 = fediauth.get_player_secret_b32(playername)
|
||||||
|
local codeseq = fediauth.give_code(secret_b32)
|
||||||
|
fediauth.send_code(codeseq[1], fields.fediverse_account_url)
|
||||||
|
feditempstore[playername] = fields.fediverse_account_url
|
||||||
|
local formspec = "size[9,10]" ..
|
||||||
|
"label[1,7;Check code on " .. minetest.formspec_escape(fields.fediverse_account_url) .. "]" ..
|
||||||
|
"field[1,9;4,1;code;Code;]" ..
|
||||||
|
"button_exit[5,8.7;3,1;submit;Verify]"
|
||||||
|
|
||||||
|
minetest.show_formspec(playername, FORMNAME, formspec)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
if fields.code then
|
if fields.code then
|
||||||
local playername = player:get_player_name()
|
local playername = player:get_player_name()
|
||||||
local secret_b32 = otp.get_player_secret_b32(playername)
|
local secret_b32 = fediauth.get_player_secret_b32(playername)
|
||||||
if otp.check_code(secret_b32, fields.code) then
|
if fediauth.check_code(secret_b32, fields.code) then
|
||||||
-- set priv
|
-- set priv
|
||||||
local privs = minetest.get_player_privs(playername)
|
local privs = minetest.get_player_privs(playername)
|
||||||
privs.otp_enabled = true
|
privs.fediauth_enabled = true
|
||||||
minetest.set_player_privs(playername, privs)
|
minetest.set_player_privs(playername, privs)
|
||||||
|
fediauth.verified_checkmark(player, true)
|
||||||
|
if feditempstore[playername] then
|
||||||
|
fediauth.storage:set_string(playername .. "_fedi", feditempstore[playername])
|
||||||
|
feditempstore[playername] = nil
|
||||||
|
end
|
||||||
|
|
||||||
minetest.chat_send_player(playername, "Code validation succeeded, OTP login enabled")
|
minetest.chat_send_player(playername, "Code validation succeeded, fediauth login enabled")
|
||||||
else
|
else
|
||||||
minetest.chat_send_player(playername, "Code validation failed!")
|
minetest.chat_send_player(playername, "Code validation failed!")
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,18 +2,18 @@
|
||||||
-- privs to revoke until the verification code is validated
|
-- privs to revoke until the verification code is validated
|
||||||
local temp_revoke_privs = {}
|
local temp_revoke_privs = {}
|
||||||
|
|
||||||
-- mark player health-related privs as "otp_keep" (they don't get removed while entering the otp code)
|
-- mark player health-related privs as "fediauth_keep" (they don't get removed while entering the fediauth code)
|
||||||
for _, name in ipairs({"fly", "noclip"}) do
|
for _, name in ipairs({"fly", "noclip"}) do
|
||||||
local priv_def = minetest.registered_privileges[name]
|
local priv_def = minetest.registered_privileges[name]
|
||||||
if priv_def then
|
if priv_def then
|
||||||
priv_def.otp_keep = true
|
priv_def.fediauth_keep = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
minetest.register_on_mods_loaded(function()
|
minetest.register_on_mods_loaded(function()
|
||||||
-- collect all privs to revoke while entering the otp code
|
-- collect all privs to revoke while entering the fediauth code
|
||||||
for name, priv_def in pairs(minetest.registered_privileges) do
|
for name, priv_def in pairs(minetest.registered_privileges) do
|
||||||
if not priv_def.otp_keep then
|
if not priv_def.fediauth_keep then
|
||||||
-- not marked explicitly as "keep"
|
-- not marked explicitly as "keep"
|
||||||
table.insert(temp_revoke_privs, name)
|
table.insert(temp_revoke_privs, name)
|
||||||
end
|
end
|
||||||
|
@ -21,9 +21,9 @@ minetest.register_on_mods_loaded(function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- moves all "temp_revoke_privs" to mod-storage
|
-- moves all "temp_revoke_privs" to mod-storage
|
||||||
function otp.revoke_privs(playername)
|
function fediauth.revoke_privs(playername)
|
||||||
local privs = minetest.get_player_privs(playername)
|
local privs = minetest.get_player_privs(playername)
|
||||||
if otp.storage:get_string(playername .. "_privs") == "" then
|
if fediauth.storage:get_string(playername .. "_privs") == "" then
|
||||||
local moved_privs = {}
|
local moved_privs = {}
|
||||||
|
|
||||||
for _, priv_name in ipairs(temp_revoke_privs) do
|
for _, priv_name in ipairs(temp_revoke_privs) do
|
||||||
|
@ -33,15 +33,15 @@ function otp.revoke_privs(playername)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
minetest.log("action", "[otp] revoking privs of '" .. playername .. "' list: " .. dump(moved_privs))
|
minetest.log("action", "[fediauth] revoking privs of '" .. playername .. "' list: " .. dump(moved_privs))
|
||||||
minetest.set_player_privs(playername, privs)
|
minetest.set_player_privs(playername, privs)
|
||||||
otp.storage:set_string(playername .. "_privs", minetest.serialize(moved_privs))
|
fediauth.storage:set_string(playername .. "_privs", minetest.serialize(moved_privs))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- moves all privs from mod-storage into the live privs
|
-- moves all privs from mod-storage into the live privs
|
||||||
function otp.regrant_privs(playername)
|
function fediauth.regrant_privs(playername)
|
||||||
local stored_priv_str = otp.storage:get_string(playername .. "_privs")
|
local stored_priv_str = fediauth.storage:get_string(playername .. "_privs")
|
||||||
if stored_priv_str ~= "" then
|
if stored_priv_str ~= "" then
|
||||||
local privs = minetest.get_player_privs(playername)
|
local privs = minetest.get_player_privs(playername)
|
||||||
local stored_privs = minetest.deserialize(stored_priv_str)
|
local stored_privs = minetest.deserialize(stored_priv_str)
|
||||||
|
@ -51,8 +51,8 @@ function otp.regrant_privs(playername)
|
||||||
privs[priv_name] = true
|
privs[priv_name] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
minetest.log("action", "[otp] regranting privs of '" .. playername .. "' list: " .. dump(stored_privs))
|
minetest.log("action", "[fediauth] regranting privs of '" .. playername .. "' list: " .. dump(stored_privs))
|
||||||
minetest.set_player_privs(playername, privs)
|
minetest.set_player_privs(playername, privs)
|
||||||
otp.storage:set_string(playername .. "_privs", "")
|
fediauth.storage:set_string(playername .. "_privs", "")
|
||||||
end
|
end
|
||||||
end
|
end
|
12
privs.lua
12
privs.lua
|
@ -1,6 +1,12 @@
|
||||||
|
|
||||||
minetest.register_privilege("otp_enabled", {
|
minetest.register_privilege("fediauth_enabled", {
|
||||||
description = "otp enabled player",
|
description = "fediauth enabled player",
|
||||||
give_to_singleplayer = false,
|
give_to_singleplayer = false,
|
||||||
otp_keep = true
|
fediauth_keep = true
|
||||||
|
})
|
||||||
|
|
||||||
|
minetest.register_privilege("fediauth_bypass", {
|
||||||
|
description = "fediauth bypass for players who not want type code on each log in (when enabled fediauth.fedi_required)",
|
||||||
|
give_to_singleplayer = false,
|
||||||
|
fediauth_keep = true
|
||||||
})
|
})
|
||||||
|
|
1325
qrencode.lua
1325
qrencode.lua
File diff suppressed because it is too large
Load diff
49
readme.md
49
readme.md
|
@ -1,49 +1,50 @@
|
||||||
|
|
||||||
# (T)OTP mod for minetest
|
# FediAuth mod for minetest
|
||||||
|
|
||||||
* State: **Stable**
|
2FA via Fediverse account, based on https://content.minetest.net/packages/mt-mods/otp/
|
||||||
|
|
||||||
# Overview
|
# Overview
|
||||||
|
|
||||||
Lets security-aware players use the `/otp_enable` command to protect their account with a second factor.
|
Lets Fediverse players use the `/fediauth_on` 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.
|
Players that have the FediAuth enabled have to enter a verification code upon joining the game, the code will be sent to their account handle (@nick@example.com).
|
||||||
|
|
||||||
# OTP Authenticator apps
|
That mod requires add to `secure.http_mods = fediauth` for sending codes from service account (any mastodon API compatible instance)
|
||||||
|
|
||||||
* https://freeotp.github.io/
|
Add `fediauth.instance = example.com` and `fediauth.api_token` = secret` for work this mod.
|
||||||
* https://github.com/helloworld1/FreeOTPPlus
|
|
||||||
|
Also you can enable fediauth.fedi_required option and players who not have fediverse account can't play on server
|
||||||
|
|
||||||
# Screenshots
|
# Screenshots
|
||||||
|
|
||||||
OTP verification form
|
FediAuth verification form
|
||||||
![](./screenshot1.png)
|
![](./screenshot1.jpg)
|
||||||
|
|
||||||
|
FediAuth Setup form
|
||||||
|
![](./screenshot2.jpg)
|
||||||
|
|
||||||
|
FediAuth checkmark if verified success
|
||||||
|
![](./screenshot3.jpg)
|
||||||
|
|
||||||
OTP Setup form
|
|
||||||
![](./screenshot2.png)
|
|
||||||
|
|
||||||
# Temporary privilege revocation
|
# Temporary privilege revocation
|
||||||
|
|
||||||
All of the privileges get revoked when logging in with the otp enabled (until the proper code is entered).
|
All of the privileges get revoked when logging in with the fediauth enabled (until the proper code is entered).
|
||||||
Some exceptions:
|
Some exceptions:
|
||||||
* `fly` (otherwise the player would literally fall from the sky)
|
* `fly` (otherwise the player would literally fall from the sky)
|
||||||
* `noclip`
|
* `noclip`
|
||||||
|
|
||||||
To disable revokation on custom privs the field `otp_keep` can be set to true on the definition:
|
To disable revokation on custom privs the field `fediauth_keep` can be set to true on the definition:
|
||||||
```lua
|
```lua
|
||||||
minetest.register_privilege("my_super_important_priv", {
|
minetest.register_privilege("my_super_important_priv", {
|
||||||
description = "something something",
|
description = "something something",
|
||||||
otp_keep = true
|
fediauth_keep = true
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
# Settings
|
|
||||||
|
|
||||||
* `otp.authenticator_image` The image to use in the QR code for the otp app (defaults to "https://raw.githubusercontent.com/minetest/minetest/master/misc/minetest-xorg-icon-128.png")
|
|
||||||
* `otp.issuer` The issuer name, defaults to server name, address or just "Minetest"
|
|
||||||
|
|
||||||
# Links / References
|
# Links / References
|
||||||
|
|
||||||
|
* https://fedi.tips/
|
||||||
* https://en.wikipedia.org/wiki/Time-based_one-time_password
|
* 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-based_one-time_password
|
||||||
* https://en.wikipedia.org/wiki/HMAC
|
* https://en.wikipedia.org/wiki/HMAC
|
||||||
|
@ -51,16 +52,16 @@ minetest.register_privilege("my_super_important_priv", {
|
||||||
|
|
||||||
# Chatcommands
|
# Chatcommands
|
||||||
|
|
||||||
* `/otp_enable` Starts the OTP onboarding process
|
* `/fediauth_on` Starts the FediAuth
|
||||||
* `/otp_disable` Disables the OTP Login
|
* `/fediauth_off` Disables the FediAuth login
|
||||||
|
|
||||||
# Privileges
|
# Privileges
|
||||||
|
|
||||||
* `otp_enabled` Players with this privilege have to verify the OTP Code upon login (automatically granted on successful `/otp_enable`)
|
* `fediauth_enabled` Players with this privilege have to verify the Fediverse code upon login (automatically granted on successful `/fediauth_enable`)
|
||||||
|
* `fediauth_bypass` Players with this privilege can bypass verification for any reason, and the privilege can only granted manually by administrator
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
* Code: `MIT`
|
* Code: `CC0-1.0`
|
||||||
* Textures: `CC-BY-SA 3.0`
|
* 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
|
|
||||||
|
|
BIN
screenshot1.jpg
Normal file
BIN
screenshot1.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 164 KiB |
BIN
screenshot2.jpg
Normal file
BIN
screenshot2.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 172 KiB |
BIN
screenshot3.jpg
Normal file
BIN
screenshot3.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 147 KiB |
9
settingtypes.txt
Normal file
9
settingtypes.txt
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Instance domain for service account (code sender)
|
||||||
|
# example.com
|
||||||
|
# Must be mastodon API compatible
|
||||||
|
fediauth.instance (Instance domain) string
|
||||||
|
|
||||||
|
fediauth.api_token (Token for account) string
|
||||||
|
|
||||||
|
# If no fediverse account - no access to server
|
||||||
|
fediauth.fedi_required (Require Fediverse account for each user) bool false
|
|
@ -1,11 +0,0 @@
|
||||||
ARG ENGINE_VERSION=5.6.1
|
|
||||||
FROM registry.gitlab.com/minetest/minetest/server:${ENGINE_VERSION}
|
|
||||||
|
|
||||||
USER root
|
|
||||||
|
|
||||||
RUN apk add git &&\
|
|
||||||
mkdir -p /root/.minetest/worlds/world/worldmods/ &&\
|
|
||||||
cd /root/.minetest/worlds/world/worldmods/ &&\
|
|
||||||
git clone https://github.com/BuckarooBanzay/mtt
|
|
||||||
|
|
||||||
ENTRYPOINT minetestserver --config /minetest.conf
|
|
|
@ -1,3 +0,0 @@
|
||||||
default_game = minetest_game
|
|
||||||
mg_name = v7
|
|
||||||
mtt_enable = true
|
|
BIN
textures/checkmark.png
Normal file
BIN
textures/checkmark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
textures/fediverse.png
Normal file
BIN
textures/fediverse.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Loading…
Reference in a new issue