diff --git a/.gitignore b/.gitignore index 7fa100ed53..45e7120ec4 100644 --- a/.gitignore +++ b/.gitignore @@ -138,3 +138,6 @@ lib/irrlichtmt # Generated mod storage database client/mod_storage.sqlite + +# Client keyring +client/keyring.json diff --git a/builtin/mainmenu/dlg_register.lua b/builtin/mainmenu/dlg_register.lua index 5047eda3c0..e1b0cac7d1 100644 --- a/builtin/mainmenu/dlg_register.lua +++ b/builtin/mainmenu/dlg_register.lua @@ -81,6 +81,7 @@ local function register_buttonhandler(this, fields) core.settings:set("name", fields.name) core.settings:set("address", gamedata.address) core.settings:set("remote_port", gamedata.port) + keyringmgr.set_login(gamedata.address, gamedata.port, fields.name, fields.password) core.start() end diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua index c1bbc0ae42..c7cf4af554 100644 --- a/builtin/mainmenu/init.lua +++ b/builtin/mainmenu/init.lua @@ -23,6 +23,7 @@ dofile(basepath .. "fstk" .. DIR_DELIM .. "ui.lua") dofile(menupath .. DIR_DELIM .. "async_event.lua") dofile(menupath .. DIR_DELIM .. "common.lua") dofile(menupath .. DIR_DELIM .. "serverlistmgr.lua") +dofile(menupath .. DIR_DELIM .. "keyringmgr.lua") dofile(menupath .. DIR_DELIM .. "game_theme.lua") dofile(menupath .. DIR_DELIM .. "content" .. DIR_DELIM .. "init.lua") diff --git a/builtin/mainmenu/keyringmgr.lua b/builtin/mainmenu/keyringmgr.lua new file mode 100644 index 0000000000..05dfb769b8 --- /dev/null +++ b/builtin/mainmenu/keyringmgr.lua @@ -0,0 +1,169 @@ +keyringmgr = { + keyring = nil +} + +local function get_keyring_path() + return core.get_user_path() .. DIR_DELIM .. "client" .. DIR_DELIM .. core.settings:get("keyring_file") +end + +local function save_keyring(keyring) + core.safe_file_write(get_keyring_path(), core.write_json(keyring)) +end + +local function read_keyring() + local path = get_keyring_path() + + local file = io.open(path, "r") + if file then + local json = file:read("*all") + file:close() + return core.parse_json(json) + end +end + +local function is_for_server(keys, address, port) + return keys.address == address and keys.port == port +end + +local function delete_keys(keyring, address, port) + for i = 1, #keyring do + local keys = keyring[i] + + if is_for_server(keys, address, port) then + table.remove(keyring, i) + return + end + end +end + +local function get_keys(keyring, address, port) + for i = 1, #keyring do + local keys = keyring[i] + + if is_for_server(keys, address, port) then + return keys + end + end + -- If we don't find any existing keys, we make a blank set for the server + local keys = { + address = address, + port = port, + logins = {}, + last_login = false + } + table.insert(keyring, 1, keys) + save_keyring(keyring) + return keys +end + +local function rewrite_keys(keyring, address, port, new_keys) + delete_keys(keyring, address, port) + table.insert(keyring, 1, new_keys) + save_keyring(keyring) +end + +function keyringmgr.get_keyring() + if keyringmgr.keyring then + return keyringmgr.keyring + end + + keyringmgr.keyring = {} + + -- Add keyring, removing duplicates + local seen = {} + for _, keys in ipairs(read_keyring() or {}) do + local key = ("%s:%d"):format(keys.address:lower(), keys.port) + if not seen[key] then + seen[key] = true + keyringmgr.keyring[#keyringmgr.keyring + 1] = keys + end + end + + return keyringmgr.keyring +end + +function keyringmgr.set_last_login(address, port, playername) + local keyring = keyringmgr.get_keyring() + local new_keys = get_keys(keyring, address, port) + new_keys.last_login = playername + + rewrite_keys(keyring, address, port, new_keys) +end + +function keyringmgr.set_login(address, port, playername, password) + -- If the user doesn't want to remember logins, we completely skip the process + if not core.settings:get_bool("remember_login") then + return + end + assert(type(port) == "number") + + local keyring = keyringmgr.get_keyring() + local new_keys = get_keys(keyring, address, port); + + -- Check for existing entires for playername + for _, login in ipairs(new_keys.logins or {}) do + if login.playername and login.playername == playername then + login.password = password + break + end + end + + -- If not, we add a new key + if not new_keys.logins then + new_keys.logins = {} + end + table.insert(new_keys.logins, 1, { + playername = playername, + password = password + }) + + keyringmgr.set_last_login(address, port, playername) + + rewrite_keys(keyring, address, port, new_keys) +end + +function keyringmgr.get_login(address, port, playername) + assert(type(port) == "number") + + local keyring = keyringmgr.get_keyring() + local new_keys = get_keys(keyring, address, port); + + -- Check for existing entires for playername + for _, login in ipairs(new_keys.logins) do + if login.playername and login.playername == playername then + return login + end + end + error("No login found on " .. address .. ":" .. port .. " for player " .. playername) +end + +function keyringmgr.remove_login(address, port, playername) + assert(type(port) == "number") + + local keyring = keyringmgr.get_keyring() + local new_keys = get_keys(keyring, address, port); + for i = 1, #new_keys.logins do + local login = new_keys.logins[i] + + if (login.playername == playername) then + table.remove(new_keys.logins, i) + break + end + end + + rewrite_keys(keyring, address, port, new_keys) +end + +function keyringmgr.delete_keys(address, port) + local keyring = keyringmgr.get_keyring() + delete_keys(keyring, address, port) + save_keyring(keyring) +end + +function keyringmgr.get_last_login(address, port) + local playername = get_keys(keyringmgr.get_keyring(), address, port).last_login + if playername then + return keyringmgr.get_login(address, port, playername) + end + return false +end diff --git a/builtin/mainmenu/tab_online.lua b/builtin/mainmenu/tab_online.lua index ab5ddfe11e..5d2df5524f 100644 --- a/builtin/mainmenu/tab_online.lua +++ b/builtin/mainmenu/tab_online.lua @@ -55,7 +55,15 @@ local function is_selected_fav(server) end -- Persists the selected server in the "address" and "remote_port" settings +local function get_default_playername() + return core.settings:get("name") +end +local function get_default_password() + return "" +end +local input_playername = get_default_playername() +local input_password = get_default_password() local function set_selected_server(server) if server == nil then -- reset selection core.settings:remove("address") @@ -69,6 +77,18 @@ local function set_selected_server(server) if address and port then core.settings:set("address", address) core.settings:set("remote_port", port) + + -- Pull info from last login (if exists) + -- This will always fail if remember_login is false + -- because nothing has been stored, nor will it ever be + local login = keyringmgr.get_last_login(address, port) + if login then + input_playername = login.playername + input_password = login.password + else + input_playername = get_default_playername() + input_password = get_default_password() + end end end @@ -129,8 +149,8 @@ local function get_formspec(tabview, name, tabdata) "container[0,4.8]" .. "label[0.25,0;" .. fgettext("Name") .. "]" .. "label[2.875,0;" .. fgettext("Password") .. "]" .. - "field[0.25,0.2;2.625,0.75;te_name;;" .. core.formspec_escape(core.settings:get("name")) .. "]" .. - "pwdfield[2.875,0.2;2.625,0.75;te_pwd;]" .. + "field[0.25,0.2;2.625,0.75;te_name;;" .. core.formspec_escape(input_playername) .. "]" .. + "pwdfield[2.875,0.2;2.625,0.75;te_pwd;;" .. core.formspec_escape(input_password) .. "]" .. "container_end[]" .. -- Connect @@ -440,7 +460,6 @@ end local function main_button_handler(tabview, fields, name, tabdata) if fields.te_name then gamedata.playername = fields.te_name - core.settings:set("name", fields.te_name) end if fields.servers then @@ -555,6 +574,7 @@ local function main_button_handler(tabview, fields, name, tabdata) if server and server.address == gamedata.address and server.port == gamedata.port then + keyringmgr.set_login(server.address, server.port, gamedata.playername, gamedata.password) serverlistmgr.add_favorite(server) gamedata.servername = server.name diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index aad629900a..422435c7f3 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -874,6 +874,10 @@ tooltip_append_itemname (Append item name) bool false # Use a cloud animation for the main menu background. menu_clouds (Clouds in menu) bool true +# Remember each server's entered login info in the 'Join Game' tab +# ***WARNING, THIS IS NOT SECURE WHATSOEVER. DO NOT ENABLE THIS***: +remember_login (Remember logins) bool false + [**HUD] # Modifies the size of the HUD elements. @@ -2444,6 +2448,9 @@ enable_remote_media_server (Connect to external media server) [client] bool true # Multiplayer Tab. serverlist_file (Serverlist file) [client] string favoriteservers.json +# File in client/ that contains your saved usernames and passwords for Multiplayer +keyring_file (Keyring file) [client] string keyring.json + [*Gamepads] [client] diff --git a/doc/lua_api.md b/doc/lua_api.md index 7ce6a543e6..e840a824cc 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -3108,7 +3108,7 @@ Elements (`x` and `y` are used as offset values, `w` and `h` are ignored) * Available since formspec version 2 -### `pwdfield[,;,;;