Persist login cookies for seamless password login restart
All checks were successful
Build plugin / build (push) Successful in 1m8s
All checks were successful
Build plugin / build (push) Successful in 1m8s
This commit is contained in:
parent
e2a4464d0d
commit
2b07195789
3 changed files with 112 additions and 15 deletions
|
|
@ -56,6 +56,7 @@ signal app_installed(cave_id: String, success: bool)
|
||||||
signal app_updated(cave_id: String, success: bool)
|
signal app_updated(cave_id: String, success: bool)
|
||||||
signal app_uninstalled(cave_id: String, success: bool)
|
signal app_uninstalled(cave_id: String, success: bool)
|
||||||
signal launch_exited(cave_id: String)
|
signal launch_exited(cave_id: String)
|
||||||
|
signal login_cookies_saved(username: String, cookie: String)
|
||||||
|
|
||||||
var proc: InteractiveProcess
|
var proc: InteractiveProcess
|
||||||
var socket: StreamPeerTCP
|
var socket: StreamPeerTCP
|
||||||
|
|
@ -376,13 +377,98 @@ func login_with_password(username: String, password: String) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _login_with_password(username: String, password: String) -> void:
|
func _login_with_password(username: String, password: String) -> void:
|
||||||
var res := await _rpc_call("Profile.LoginWithPassword", {"username": username, "password": password})
|
var params := {"username": username, "password": password}
|
||||||
|
var res := await _rpc_call("Profile.LoginWithPassword", params)
|
||||||
if "error" in res:
|
if "error" in res:
|
||||||
is_logged_in = false
|
is_logged_in = false
|
||||||
emit_signal.call_deferred("logged_in", LOGIN_STATUS.INVALID_KEY, {})
|
emit_signal.call_deferred("logged_in", LOGIN_STATUS.INVALID_KEY, {})
|
||||||
return
|
return
|
||||||
profile = res.get("profile", {})
|
profile = res.get("profile", {})
|
||||||
is_logged_in = true
|
is_logged_in = true
|
||||||
|
# Persist the cookie so we can re-authenticate on restart without the
|
||||||
|
# password. Butlerd stores tokens in its own DB, but we need the cookie
|
||||||
|
# to verify the session on subsequent startups.
|
||||||
|
var cookie: String = res.get("cookie", "")
|
||||||
|
if cookie != "":
|
||||||
|
emit_signal.call_deferred("login_cookies_saved", username, cookie)
|
||||||
|
emit_signal.call_deferred("logged_in", LOGIN_STATUS.OK, profile)
|
||||||
|
|
||||||
|
|
||||||
|
## Re-authenticates using a previously saved cookie. The cookie is verified
|
||||||
|
## against itch.io's API directly (bypassing butlerd) because butlerd does
|
||||||
|
## not expose a "login with cookie" method.
|
||||||
|
func login_with_cookie(cookie: String) -> void:
|
||||||
|
await thread_group.exec(_login_with_cookie.bind(cookie))
|
||||||
|
|
||||||
|
|
||||||
|
func _login_with_cookie(cookie: String) -> void:
|
||||||
|
var http := HTTPClient.new()
|
||||||
|
var err := http.connect_to_host("api.itch.io", 443, TLSOptions.client())
|
||||||
|
if err != OK:
|
||||||
|
is_logged_in = false
|
||||||
|
emit_signal.call_deferred("logged_in", LOGIN_STATUS.INVALID_KEY, {})
|
||||||
|
return
|
||||||
|
var deadline := Time.get_ticks_msec() + 15000
|
||||||
|
while http.get_status() == HTTPClient.STATUS_CONNECTING or http.get_status() == HTTPClient.STATUS_RESOLVING:
|
||||||
|
http.poll()
|
||||||
|
if Time.get_ticks_msec() > deadline:
|
||||||
|
http.close()
|
||||||
|
is_logged_in = false
|
||||||
|
emit_signal.call_deferred("logged_in", LOGIN_STATUS.INVALID_KEY, {})
|
||||||
|
return
|
||||||
|
OS.delay_msec(50)
|
||||||
|
if http.get_status() != HTTPClient.STATUS_CONNECTED:
|
||||||
|
http.close()
|
||||||
|
is_logged_in = false
|
||||||
|
emit_signal.call_deferred("logged_in", LOGIN_STATUS.INVALID_KEY, {})
|
||||||
|
return
|
||||||
|
|
||||||
|
var headers := PackedStringArray([
|
||||||
|
"Authorization: Bearer " + cookie,
|
||||||
|
"User-Agent: opencode-itch-plugin/1.0",
|
||||||
|
])
|
||||||
|
http.request(HTTPClient.METHOD_GET, "/profile", headers)
|
||||||
|
deadline = Time.get_ticks_msec() + 15000
|
||||||
|
while http.get_status() == HTTPClient.STATUS_REQUESTING:
|
||||||
|
http.poll()
|
||||||
|
if Time.get_ticks_msec() > deadline:
|
||||||
|
http.close()
|
||||||
|
is_logged_in = false
|
||||||
|
emit_signal.call_deferred("logged_in", LOGIN_STATUS.INVALID_KEY, {})
|
||||||
|
return
|
||||||
|
OS.delay_msec(10)
|
||||||
|
|
||||||
|
var body := PackedByteArray()
|
||||||
|
while http.get_status() == HTTPClient.STATUS_BODY:
|
||||||
|
http.poll()
|
||||||
|
var chunk: PackedByteArray = http.read_response_body_chunk()
|
||||||
|
if chunk.is_empty():
|
||||||
|
if Time.get_ticks_msec() > deadline:
|
||||||
|
http.close()
|
||||||
|
is_logged_in = false
|
||||||
|
emit_signal.call_deferred("logged_in", LOGIN_STATUS.INVALID_KEY, {})
|
||||||
|
return
|
||||||
|
OS.delay_msec(10)
|
||||||
|
continue
|
||||||
|
body.append_array(chunk)
|
||||||
|
http.close()
|
||||||
|
|
||||||
|
var response_code := http.get_response_code()
|
||||||
|
var json_text := body.get_string_from_utf8()
|
||||||
|
if response_code != 200 or json_text.is_empty():
|
||||||
|
is_logged_in = false
|
||||||
|
emit_signal.call_deferred("logged_in", LOGIN_STATUS.INVALID_KEY, {})
|
||||||
|
return
|
||||||
|
|
||||||
|
var json := JSON.new()
|
||||||
|
if json.parse(json_text) != OK:
|
||||||
|
is_logged_in = false
|
||||||
|
emit_signal.call_deferred("logged_in", LOGIN_STATUS.INVALID_KEY, {})
|
||||||
|
return
|
||||||
|
|
||||||
|
var data: Dictionary = json.data
|
||||||
|
profile = data.get("user", data)
|
||||||
|
is_logged_in = true
|
||||||
emit_signal.call_deferred("logged_in", LOGIN_STATUS.OK, profile)
|
emit_signal.call_deferred("logged_in", LOGIN_STATUS.OK, profile)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,8 @@ func _on_save_button() -> void:
|
||||||
|
|
||||||
if method == 0:
|
if method == 0:
|
||||||
# API key login
|
# API key login
|
||||||
|
settings_manager.set_value("plugin.itch", "cookie", "")
|
||||||
|
settings_manager.set_value("plugin.itch", "username", "")
|
||||||
var api_key: String = api_key_box.text.strip_edges()
|
var api_key: String = api_key_box.text.strip_edges()
|
||||||
settings_manager.set_value("plugin.itch", "api_key", api_key)
|
settings_manager.set_value("plugin.itch", "api_key", api_key)
|
||||||
if api_key == "":
|
if api_key == "":
|
||||||
|
|
@ -107,12 +109,13 @@ func _on_save_button() -> void:
|
||||||
itch.login_with_api_key(api_key)
|
itch.login_with_api_key(api_key)
|
||||||
else:
|
else:
|
||||||
# Username/password login
|
# Username/password login
|
||||||
var username: String = username_box.text.strip_edges()
|
var uname: String = username_box.text.strip_edges()
|
||||||
var password: String = password_box.text
|
var password: String = password_box.text
|
||||||
settings_manager.set_value("plugin.itch", "username", username)
|
settings_manager.set_value("plugin.itch", "username", uname)
|
||||||
if username == "" or password == "":
|
settings_manager.set_value("plugin.itch", "cookie", "")
|
||||||
|
if uname == "" or password == "":
|
||||||
return
|
return
|
||||||
itch.login_with_password(username, password)
|
itch.login_with_password(uname, password)
|
||||||
|
|
||||||
|
|
||||||
## Fetches the user's collections and builds a checkbox for each one.
|
## Fetches the user's collections and builds a checkbox for each one.
|
||||||
|
|
|
||||||
22
plugin.gd
22
plugin.gd
|
|
@ -18,6 +18,7 @@ var itch: ItchClient
|
||||||
var api_key := settings_manager.get_value("plugin.itch", "api_key", "") as String
|
var api_key := settings_manager.get_value("plugin.itch", "api_key", "") as String
|
||||||
var login_method := settings_manager.get_value("plugin.itch", "login_method", 0) as int
|
var login_method := settings_manager.get_value("plugin.itch", "login_method", 0) as int
|
||||||
var username := settings_manager.get_value("plugin.itch", "username", "") as String
|
var username := settings_manager.get_value("plugin.itch", "username", "") as String
|
||||||
|
var saved_cookie := settings_manager.get_value("plugin.itch", "cookie", "") as String
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|
@ -29,6 +30,7 @@ func _ready() -> void:
|
||||||
itch.bootstrap_finished.connect(_on_client_start)
|
itch.bootstrap_finished.connect(_on_client_start)
|
||||||
itch.client_ready.connect(_on_client_ready)
|
itch.client_ready.connect(_on_client_ready)
|
||||||
itch.logged_in.connect(_on_client_logged_in)
|
itch.logged_in.connect(_on_client_logged_in)
|
||||||
|
itch.login_cookies_saved.connect(_on_cookies_saved)
|
||||||
add_child(itch)
|
add_child(itch)
|
||||||
|
|
||||||
# Load the Library implementation
|
# Load the Library implementation
|
||||||
|
|
@ -58,16 +60,17 @@ func _on_client_ready() -> void:
|
||||||
return
|
return
|
||||||
itch.login_with_api_key(api_key)
|
itch.login_with_api_key(api_key)
|
||||||
else:
|
else:
|
||||||
# Username/password login
|
# Username/password login — try saved cookie first.
|
||||||
if username == "":
|
if username == "":
|
||||||
var notify := Notification.new("itch.io username required")
|
var notify := Notification.new("itch.io username required")
|
||||||
notify.icon = icon
|
notify.icon = icon
|
||||||
logger.info(notify.text)
|
logger.info(notify.text)
|
||||||
notification_manager.show(notify)
|
notification_manager.show(notify)
|
||||||
return
|
return
|
||||||
# Password is not persisted; prompt via settings if needed.
|
if saved_cookie != "":
|
||||||
# For now, just log the message — the user must save from settings.
|
itch.login_with_cookie(saved_cookie)
|
||||||
var notify := Notification.new("itch.io: open plugin settings to log in with username/password")
|
else:
|
||||||
|
var notify := Notification.new("itch.io: open plugin settings to log in")
|
||||||
notify.icon = icon
|
notify.icon = icon
|
||||||
logger.info(notify.text)
|
logger.info(notify.text)
|
||||||
notification_manager.show(notify)
|
notification_manager.show(notify)
|
||||||
|
|
@ -79,17 +82,22 @@ func _on_client_logged_in(status: ItchClient.LOGIN_STATUS, profile: Dictionary)
|
||||||
notify.icon = icon
|
notify.icon = icon
|
||||||
|
|
||||||
if status == ItchClient.LOGIN_STATUS.OK:
|
if status == ItchClient.LOGIN_STATUS.OK:
|
||||||
var username: String = profile.get("user", {}).get("username", "")
|
var uname: String = profile.get("user", {}).get("username", "")
|
||||||
notify.text = "Successfully logged in to itch.io as " + username
|
notify.text = "Successfully logged in to itch.io as " + uname
|
||||||
logger.info(notify.text)
|
logger.info(notify.text)
|
||||||
notification_manager.show(notify)
|
notification_manager.show(notify)
|
||||||
return
|
return
|
||||||
|
|
||||||
notify.text = "Failed to log in to itch.io. Check your API key in plugin settings."
|
notify.text = "Failed to log in to itch.io. Check your credentials in plugin settings."
|
||||||
logger.warn(notify.text)
|
logger.warn(notify.text)
|
||||||
notification_manager.show(notify)
|
notification_manager.show(notify)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_cookies_saved(uname: String, cookie: String) -> void:
|
||||||
|
settings_manager.set_value("plugin.itch", "username", uname)
|
||||||
|
settings_manager.set_value("plugin.itch", "cookie", cookie)
|
||||||
|
|
||||||
|
|
||||||
# Return the settings menu scene
|
# Return the settings menu scene
|
||||||
func get_settings_menu() -> Control:
|
func get_settings_menu() -> Control:
|
||||||
return settings_menu.instantiate()
|
return settings_menu.instantiate()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue