- create standalone mod from code snippet
master
Leslie Krause 2020-08-20 19:23:00 -04:00
commit f165dedba6
3 changed files with 511 additions and 0 deletions

77
README.txt Normal file
View File

@ -0,0 +1,77 @@
Auth Redux Lite Mod v2.7
By Leslie Krause
Auth Redux Lite is available for server operators that want a barebones, no-frills
authentication handler for their server. The entire mod consists of a single 13 kB file.
This fork uses the same database API and architecture as Auth Redux, so migration is
completely seamless. If you are already using Auth Redux, simply backup your existing
"auth_rx"' subdirectory, and then save the included "init.lua" file in its place.
However, if you are migrating from the builtin auth handler of Minetest 5.x, then there
are a few additional steps depending on the specific database backend you are using.
Convert auth.txt
---------------------
1) Create an "auth_rx_lite" subdirectory under mods in your game.
2) Run the included Database Import Script to convert the database.
awk -f convert.awk -v mode=install ~/.minetest/worlds/world/auth.txt
Convert auth.sqlite
----------------------
1) Create an "auth_rx_lite" subdirectory under mods in your game.
2) Run the Minetest server to migrate the SQLite3 auth database backend.
minetestserver --migrate-auth files --worldname world
3) Run the included Database Import Script to convert the database.
awk -f convert.awk -v mode=install ~/.minetest/worlds/world/auth.txt
In the examples above, you will need to provide your world directory. I'd also recommend
moving or simply renaming the auth.txt file to "auth.txt.bak" for safekeeping.
That's all there is to it!
Compatability
----------------------
Minetest 5.3+ required
Repository
----------------------
Browse source code:
https://bitbucket.org/sorcerykid/auth_rx_lite
Download archive:
https://bitbucket.org/sorcerykid/auth_rx_lite/get/master.zip
https://bitbucket.org/sorcerykid/auth_rx_lite/get/master.tar.gz
Source Code License
----------------------
The MIT License (MIT)
Copyright (c) 2016-2020, Leslie Krause (leslie@searstower.org)
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
For more details:
https://opensource.org/licenses/MIT

430
init.lua Normal file
View File

@ -0,0 +1,430 @@
--------------------------------------------------------
-- Minetest :: Auth Redux Lite Mod v2.7 (auth_rx)
--
-- See README.txt for licensing and release notes.
-- Copyright (c) 2017-2020, Leslie E. Krause
--------------------------------------------------------
local LOG_STARTED = 10
local LOG_CHECKED = 11
local LOG_STOPPED = 12
local TX_CREATE = 20
local TX_DELETE = 21
local TX_SET_PASSWORD = 40
local TX_SET_APPROVED_ADDRS = 41
local TX_SET_ASSIGNED_PRIVS = 42
local TX_SESSION_OPENED = 50
local TX_SESSION_CLOSED = 51
local TX_LOGIN_ATTEMPT = 30
local TX_LOGIN_FAILURE = 31
local TX_LOGIN_SUCCESS = 32
function Journal( path, name, is_rollback )
local file, err = io.open( path .. "/" .. name, "r+b" )
local self = { }
local cursor = 0
local rtime = 1.0
if not file then
minetest.log( "error", "Cannot open " .. path .. "/" .. name .. " for writing." )
error( "Fatal exception in Journal( ), aborting." )
end
self.audit = function ( update_proc, is_rollback )
if not is_rollback then
minetest.log( "action", "Advancing database transaction log...." )
for line in file:lines( ) do
local fields = string.split( line, " ", true )
if tonumber( fields[ 2 ] ) == LOG_STOPPED then
cursor = file:seek( )
end
end
file:seek( "set", cursor )
end
local meta = { }
minetest.log( "action", "Replaying database transaction log...." )
for line in file:lines( ) do
local fields = string.split( line, " ", true )
local optime = tonumber( fields[ 1 ] )
local opcode = tonumber( fields[ 2 ] )
update_proc( meta, optime, opcode, select( 3, unpack( fields ) ) )
if opcode == LOG_CHECKED then
minetest.log( "action", "Resetting database transaction log..." )
file:seek( "set", cursor )
file:write( optime .. " " .. LOG_STOPPED .. "\n" )
return optime
end
cursor = file:seek( )
end
end
self.start = function ( )
self.optime = os.time( )
file:seek( "end", 0 )
file:write( self.optime .. " " .. LOG_STARTED .. "\n" )
cursor = file:seek( )
file:write( self.optime .. " " .. LOG_CHECKED .. "\n" )
end
self.reset = function ( )
file:seek( "set", cursor )
file:write( self.optime .. " " .. LOG_STOPPED .. "\n" )
self.optime = nil
end
self.record_raw = function ( opcode, ... )
file:seek( "set", cursor )
file:write( table.concat( { self.optime, opcode, ... }, " " ) .. "\n" )
cursor = file:seek( )
file:write( self.optime .. " " .. LOG_CHECKED .. "\n" )
end
minetest.register_globalstep( function( dtime )
rtime = rtime - dtime
if rtime <= 0.0 then
if self.optime then
self.optime = os.time( )
file:seek( "set", cursor )
file:write( self.optime .. " " .. LOG_CHECKED .. "\n" )
end
rtime = 1.0
end
end )
return self
end
function AuthDatabase( path, name )
local data, users, index
local self = { }
local journal = Journal( path, name .. "x" )
local db_update = function( meta, optime, opcode, ... )
local fields = { ... }
if opcode == TX_CREATE then
local rec = { password = fields[ 2 ], oldlogin = -1, newlogin = -1, lifetime = 0, total_sessions = 0, total_attempts = 0, total_failures = 0, approved_addrs = { }, assigned_privs = { } }
data[ fields[ 1 ] ] = rec
elseif opcode == TX_DELETE then
data[ fields[ 1 ] ] = nil
elseif opcode == TX_SET_PASSWORD then
data[ fields[ 1 ] ].password = fields[ 2 ]
elseif opcode == TX_SET_APPROVED_ADDRS then
data[ fields[ 1 ] ].filered_addrs = string.split( fields[ 2 ], ",", true )
elseif opcode == TX_SET_ASSIGNED_PRIVS then
data[ fields[ 1 ] ].assigned_privs = string.split( fields[ 2 ], ",", true )
elseif opcode == TX_LOGIN_ATTEMPT then
data[ fields[ 1 ] ].total_attempts = data[ fields[ 1 ] ].total_attempts + 1
elseif opcode == TX_LOGIN_FAILURE then
data[ fields[ 1 ] ].total_failures = data[ fields[ 1 ] ].total_failures + 1
elseif opcode == TX_LOGIN_SUCCESS then
if data[ fields[ 1 ] ].oldlogin == -1 then
data[ fields[ 1 ] ].oldlogin = optime
end
meta.users[ fields[ 1 ] ] = data[ fields[ 1 ] ].newlogin
data[ fields[ 1 ] ].newlogin = optime
elseif opcode == TX_SESSION_OPENED then
data[ fields[ 1 ] ].total_sessions = data[ fields[ 1 ] ].total_sessions + 1
elseif opcode == TX_SESSION_CLOSED then
data[ fields[ 1 ] ].lifetime = data[ fields[ 1 ] ].lifetime + ( optime - data[ fields[ 1 ] ].newlogin )
meta.users[ fields[ 1 ] ] = nil
elseif opcode == LOG_STARTED then
meta.users = { }
elseif opcode == LOG_CHECKED or opcode == LOG_STOPPED then
for u, t in pairs( meta.users ) do
data[ u ].lifetime = data[ u ].lifetime + ( optime - data[ u ].newlogin )
end
meta.users = nil
end
end
local db_reload = function ( )
minetest.log( "action", "Reading authentication data from disk..." )
local file, errmsg = io.open( path .. "/" .. name, "r+b" )
if not file then
minetest.log( "error", "Cannot open " .. path .. "/" .. name .. " for reading." )
error( "Fatal exception in AuthDatabase:db_reload( ), aborting." )
end
local head = assert( file:read( "*line" ) )
index = tonumber( string.match( head, "^auth_rx/2.1 @(%d+)$" ) )
if not index or index < 0 then
minetest.log( "error", "Invalid header in authentication database." )
error( "Fatal exception in AuthDatabase:db_reload( ), aborting." )
end
for line in file:lines( ) do
if line ~= "" then
local fields = string.split( line, ":", true )
if #fields ~= 10 then
minetest.log( "error", "Invalid record in authentication database." )
error( "Fatal exception in AuthDatabase:db_reload( ), aborting." )
end
data[ fields[ 1 ] ] = {
password = fields[ 2 ],
oldlogin = tonumber( fields[ 3 ] ),
newlogin = tonumber( fields[ 4 ] ),
lifetime = tonumber( fields[ 5 ] ),
total_sessions = tonumber( fields[ 6 ] ),
total_attempts = tonumber( fields[ 7 ] ),
total_failures = tonumber( fields[ 8 ] ),
approved_addrs = string.split( fields[ 9 ], "," ),
assigned_privs = string.split( fields[ 10 ], "," ),
}
end
end
file:close( )
end
local db_commit = function ( )
minetest.log( "action", "Writing authentication data to disk..." )
local file, errmsg = io.open( path .. "/~" .. name, "w+b" )
if not file then
minetest.log( "error", "Cannot open " .. path .. "/~" .. name .. " for writing." )
error( "Fatal exception in AuthDatabase:db_commit( ), aborting." )
end
index = index + 1
file:write( "auth_rx/2.1 @" .. index .. "\n" )
for username, rec in pairs( data ) do
assert( file:write(
table.concat( { username, rec.password, rec.oldlogin, rec.newlogin, rec.lifetime, rec.total_sessions, rec.total_attempts, rec.total_failures, table.concat( rec.approved_addrs, "," ), table.concat( rec.assigned_privs, "," ) }, ":" )
.. "\n" ) )
end
file:close( )
assert( os.remove( path .. "/" .. name ) )
assert( os.rename( path .. "/~" .. name, path .. "/" .. name ) )
end
self.rollback = function ( )
data = { }
db_reload( )
journal.audit( db_update, true )
db_commit( )
data = nil
end
self.connect = function ( )
data = { }
users = { }
db_reload( )
if journal.audit( db_update, false ) then
db_commit( )
end
journal.start( )
end
self.disconnect = function ( )
for u, t in pairs( users ) do
data[ u ].lifetime = data[ u ].lifetime + ( journal.optime - data[ u ].newlogin )
end
db_commit( )
journal.reset( )
data = nil
users = nil
end
self.select_record = function ( username )
return data[ username ]
end
self.create_record = function ( username, password )
if data[ username ] then return false end
local rec = { password = password, oldlogin = -1, newlogin = -1, lifetime = 0, total_sessions = 0, total_attempts = 0, total_failures = 0, approved_addrs = { }, assigned_privs = { } }
data[ username ] = rec
journal.record_raw( TX_CREATE, username, password )
return true
end
self.delete_record = function ( username )
if not data[ username ] or users[ username ] then return false end
data[ username ] = nil
journal.record_raw( TX_DELETE, username )
return true
end
self.set_password = function ( username, password )
if not data[ username ] then return false end
data[ username ].password = password
journal.record_raw( TX_SET_PASSWORD, username, password )
return true
end
self.set_assigned_privs = function ( username, assigned_privs )
if not data[ username ] then return false end
data[ username ].assigned_privs = assigned_privs
journal.record_raw( TX_SET_ASSIGNED_PRIVS, username, table.concat( assigned_privs, "," ) )
return true
end
self.set_approved_addrs = function ( username, approved_addrs )
if not data[ username ] then return false end
data[ username ].approved_addrs = approved_addrs
journal.record_raw( TX_SET_APPROVED_ADDRS, username, table.concat( approved_addrs, "," ) )
return true
end
self.on_session_opened = function ( username )
data[ username ].total_sessions = data[ username ].total_sessions + 1
journal.record_raw( TX_SESSION_OPENED, username )
end
self.on_session_closed = function ( username )
data[ username ].lifetime = data[ username ].lifetime + ( journal.optime - data[ username ].newlogin )
users[ username ] = nil
journal.record_raw( TX_SESSION_CLOSED, username )
end
self.on_login_attempt = function ( username, ip )
data[ username ].total_attempts = data[ username ].total_attempts + 1
journal.record_raw( TX_LOGIN_ATTEMPT, username, ip )
end
self.on_login_failure = function ( username, ip )
data[ username ].total_failures = data[ username ].total_failures + 1
journal.record_raw( TX_LOGIN_FAILURE, username, ip )
end
self.on_login_success = function ( username, ip )
if data[ username ].oldlogin == -1 then
data[ username ].oldlogin = journal.optime
end
users[ username ] = data[ username ].newlogin
data[ username ].newlogin = journal.optime
journal.record_raw( TX_LOGIN_SUCCESS, username, ip )
end
self.records = function ( )
return pairs( data )
end
return self
end
local auth_db = AuthDatabase( minetest.get_worldpath( ), "auth.db" )
local function get_default_privs( )
local default_privs = { }
for _, p in pairs( string.split( minetest.settings:get( "default_privs" ), "," ) ) do
table.insert( default_privs, string.trim( p ) )
end
return default_privs
end
local function unpack_privileges( assigned_privs )
local privileges = { }
for _, p in ipairs( assigned_privs ) do
privileges[ p ] = true
end
return privileges
end
local function pack_privileges( privileges )
local assigned_privs = { }
for p, _ in pairs( privileges ) do
table.insert( assigned_privs, p )
end
return assigned_privs
end
minetest.register_on_authplayer( function ( player_name, player_ip, is_success )
if is_success then
auth_db.on_login_success( player_name, player_ip )
else
auth_db.on_login_failure( player_name, player_ip )
end
end )
minetest.register_on_prejoinplayer( function ( player_name, player_ip )
local rec = auth_db.select_record( player_name )
if rec then
auth_db.on_login_attempt( player_name, player_ip )
else
local uname = string.lower( player_name )
for cname in auth_db.records( ) do
if string.lower( cname ) == uname then
return string.format( "A player named %s already exists on this server.", cname )
end
end
end
end )
minetest.register_on_joinplayer( function ( player, last_login )
auth_db.on_session_opened( player:get_player_name( ) )
end )
minetest.register_on_leaveplayer( function ( player )
auth_db.on_session_closed( player:get_player_name( ) )
end )
minetest.register_on_shutdown( function( )
auth_db.disconnect( )
end )
minetest.register_authentication_handler( {
get_auth = function( username )
local rec = auth_db.select_record( username )
if not rec then return end
local assigned_privs = rec.assigned_privs
if minetest.settings:get( "name" ) == username then
assigned_privs = { }
for priv in pairs( core.registered_privileges ) do
table.insert( assigned_privs, priv )
end
end
return { password = rec.password, privileges = unpack_privileges( assigned_privs ), last_login = rec.newlogin }
end,
create_auth = function( username, password )
if auth_db.create_record( username, password ) then
auth_db.set_assigned_privs( username, get_default_privs( ) )
end
end,
delete_auth = function( username )
auth_db.delete_record( username )
end,
set_password = function ( username, password )
return auth_db.set_password( username, password )
end,
set_privileges = function ( username, privileges )
if minetest.settings:get( "name" ) == username then return end
if auth_db.set_assigned_privs( username, pack_privileges( privileges ) ) then
minetest.notify_authentication_modified( username )
end
end,
record_login = function ( ) end,
reload = function ( ) end,
iterate = auth_db.records
} )
auth_db.connect( )

4
mod.conf Normal file
View File

@ -0,0 +1,4 @@
name = auth_rx_lite
title = Auth Redux Lite
author = sorcerykid
license = MIT