Persist login cookies for seamless password login restart
All checks were successful
Build plugin / build (push) Successful in 1m8s

This commit is contained in:
Jose Falanga 2026-08-17 02:04:00 -03:00
parent e2a4464d0d
commit 2b07195789
3 changed files with 112 additions and 15 deletions

View file

@ -56,6 +56,7 @@ signal app_installed(cave_id: String, success: bool)
signal app_updated(cave_id: String, success: bool)
signal app_uninstalled(cave_id: String, success: bool)
signal launch_exited(cave_id: String)
signal login_cookies_saved(username: String, cookie: String)
var proc: InteractiveProcess
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:
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:
is_logged_in = false
emit_signal.call_deferred("logged_in", LOGIN_STATUS.INVALID_KEY, {})
return
profile = res.get("profile", {})
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)