--------------------------------------------------------
-- Minetest :: Auth Redux Mod v2.13 (auth_rx)
--
-- See README.txt for licensing and release notes.
-- Copyright (c) 2017-2018, Leslie E. Krause
--------------------------------------------------------

local world_path = minetest.get_worldpath( )
local mod_name = minetest.get_current_modname() or "auth_rx"
local mod_path = minetest.get_modpath( mod_name )

dofile( mod_path .. "/helpers.lua" )
dofile( mod_path .. "/filter.lua" )
dofile( mod_path .. "/db.lua" )
dofile( mod_path .. "/watchdog.lua" )
local __commands = dofile( mod_path .. "/commands.lua" )

AuthInitFile( world_path, "auth.db" )

-----------------------------------------------------
-- Registered Authentication Handler
-----------------------------------------------------
local auth_filter = AuthFilter( world_path, "greenlist.mt" )
local auth_db = AuthDatabase( world_path, "auth.db" )
local auth_watchdog = AuthWatchdog( )
local is_50 = minetest.has_feature("object_use_texture_alpha")
local is_54 = minetest.has_feature("direct_velocity_on_players")

if is_50 then
	if minetest.register_on_auth_fail then
		minetest.register_on_auth_fail( function ( player_name, player_ip )
			auth_db.on_login_failure( player_name, player_ip )
			auth_watchdog.on_failure( convert_ipv4( player_ip ) )
		end )
	end
end

if is_54 then
	if minetest.register_on_authplayer then
		minetest.register_on_authplayer( function ( player_name, player_ip, is_success )
			if is_success then
				return
			end
			auth_db.on_login_failure( player_name, player_ip )
			auth_watchdog.on_failure( convert_ipv4( player_ip ) )
		end )
	end
end

minetest.register_on_prejoinplayer( function ( player_name, player_ip )
	local rec = auth_db.select_record( player_name )
	local meta = auth_watchdog.get_metadata( convert_ipv4( player_ip ) )

	if rec then
		auth_db.on_login_attempt( player_name, player_ip )
	else
		-- prevent creation of case-insensitive duplicate accounts
		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

	local res = auth_filter.is_enabled and auth_filter.process( {
		name = { type = FILTER_TYPE_STRING, value = player_name },
		addr = { type = FILTER_TYPE_ADDRESS, value = convert_ipv4( player_ip ) },
		is_new = { type = FILTER_TYPE_BOOLEAN, value = rec == nil },
		privs_list = { type = FILTER_TYPE_SERIES, value = rec and rec.assigned_privs or { } },
		users_list = { type = FILTER_TYPE_SERIES, value = auth_db.search( true ) },
		cur_users = { type = FILTER_TYPE_NUMBER, value = #auth_db.search( true ) },
		max_users = { type = FILTER_TYPE_NUMBER, value = get_minetest_config( "max_users" ) },
		lifetime = { type = FILTER_TYPE_PERIOD, value = rec and rec.lifetime or 0 },
		sessions = { type = FILTER_TYPE_NUMBER, value = rec and rec.total_sessions or 0 },
		failures = { type = FILTER_TYPE_NUMBER, value = rec and rec.total_failures or 0 },
		attempts = { type = FILTER_TYPE_NUMBER, value = rec and rec.total_attempts or 0 },
		owner = { type = FILTER_TYPE_STRING, value = get_minetest_config( "name" ) },
		uptime = { type = FILTER_TYPE_PERIOD, value = minetest.get_server_uptime( ) },
		oldlogin = { type = FILTER_TYPE_MOMENT, value = rec and rec.oldlogin or 0 },
		newlogin = { type = FILTER_TYPE_MOMENT, value = rec and rec.newlogin or 0 },
		ip_names_list = { type = FILTER_TYPE_SERIES, value = meta.previous_names or { } },
		ip_prelogin = { type = FILTER_TYPE_MOMENT, value = meta.prelogin or 0 },
		ip_oldcheck = { type = FILTER_TYPE_MOMENT, value = meta.oldcheck or 0 },
		ip_newcheck = { type = FILTER_TYPE_MOMENT, value = meta.newcheck or 0 },
		ip_failures = { type = FILTER_TYPE_NUMBER, value = meta.count_failures or 0 },
		ip_attempts = { type = FILTER_TYPE_NUMBER, value = meta.count_attempts or 0 }
	}, true ) or nil

	auth_watchdog.on_attempt( convert_ipv4( player_ip ), player_name )

	return res 
end )

minetest.register_on_joinplayer( function ( player )
	local player_name
	local player_oj
	local player_ip
	if player ~= nil then
		player_name = player:get_player_name( )
		player_oj =  minetest.get_player_information( player_name )
		player_ip =  player_oj.address	 -- this doesn't work in singleplayer!
		auth_db.on_login_success( player_name, player_ip )
		auth_db.on_session_opened( player_name )
		auth_watchdog.on_success( convert_ipv4( player_ip ) )
		minetest.log( "warning", "[auth_rx] in authentication database players success joined ".. player_name )
	else
		minetest.log( "error", "[auth_rx] incomplete auth process on player obj nil ip obj address" )
	end
end )

minetest.register_on_leaveplayer( function ( player )

	local name
	if player ~= nil then
		name = player:get_player_name( )
		auth_db.on_session_closed( name )
		minetest.log( "warning", "[auth_rx] player "..name.." leaving" )
	else
		minetest.log( "error", "[auth_rx] incomplete player leaving without writing in dbx due nil object" )
	end
end )

minetest.register_on_shutdown( function( )
	auth_db.disconnect( )
end )

minetest.register_authentication_handler( {
	-- translate old auth hooks to new database backend
	get_auth = function( username )
		minetest.log( "verbose" , "[auth_rx] get_auth handler access to some resource for " .. username )
		local rec = auth_db.select_record( username )
		if rec then
			local assigned_privs = rec.assigned_privs

			if get_minetest_config( "name" ) == username then
				-- grant server operator all privileges
				assigned_privs = { }
				for priv in pairs( minetest.registered_privileges ) do
					table.insert( assigned_privs, priv )
				end
			end

			return { password = rec.password, privileges = unpack_privileges( assigned_privs ), last_login = rec.newlogin }
		end
	end,
	create_auth = function( username, password )
		minetest.log( "verbose" , "[auth_rx] create_auth handler new user over server for " .. username )
		local rec = auth_db.create_record( username, password )
		if rec then
			local assigned_privs = get_default_privs( )

			if get_minetest_config( "name" ) == username then
				-- grant server operator all privileges
				assigned_privs = { }
				for priv in pairs( minetest.registered_privileges ) do
					table.insert( assigned_privs, priv )
				end
			end

			auth_db.set_assigned_privs( username, assigned_privs )
		end
	end,
	delete_auth = function( username )
		minetest.log( "verbose" , "[auth_rx] delete_auth handler for " .. username )
		-- server operator's privileges are immutable
		if get_minetest_config( "name" ) ~= username then
			local rec =  auth_db.delete_record( username )
			if rec then
				minetest.log( "info", "Deleted player '" .. username .. "' in authenatication database" )
			end
		end
	end,
	set_password = function ( username, password )
		minetest.log( "verbose" , "[auth_rx] set_password handler for " .. username )
		local rec =  auth_db.set_password( username, password )
		if rec then
			minetest.log( "info", "Reset password of player '" .. username .. "' in authentication database" )
		end
	end,
	set_privileges = function ( username, privileges )
		minetest.log( "verbose" , "[auth_rx] set_privileges handler grants for " .. username )
		local assigned_privs = pack_privileges( privileges )
		-- server operator's privileges are immutable
		if get_minetest_config( "name" ) == username then
			assigned_privs = { }
			for priv in pairs( minetest.registered_privileges ) do
				table.insert( assigned_privs, priv )
			end
		end

		if auth_db.set_assigned_privs( username, assigned_privs ) then
			minetest.notify_authentication_modified( username )
		end
	end,
	record_login = function ( ) end,
	reload = function ( ) end,
	iterate = auth_db.records
} )

auth_db.connect( )
auth_filter.is_enabled = true

__commands( { auth_db = auth_db, auth_filter = auth_filter } )

print("[auth_rx] mod authentication database loaded" )
