diff --git a/scripts/autoload/auth_state.gd b/scripts/autoload/auth_state.gd index 0e99b6f..e4b12ad 100644 --- a/scripts/autoload/auth_state.gd +++ b/scripts/autoload/auth_state.gd @@ -12,6 +12,7 @@ const EndpointConfig := preload("res://scripts/autoload/api_endpoint_config.gd") const DEFAULT_AUTH_BASE_URL := "https://us3q.ranaze.com/api/Auth" const WARNING_SECONDS := 120 const REFRESH_TIMEOUT_SECONDS := 20.0 +const ACTIVE_RECENTLY_SECONDS := 60 var is_logged_in := false var username := "" @@ -32,6 +33,7 @@ var expiry_timer: Timer var refresh_request: HTTPRequest var session_revision := 0 var refresh_revision := -1 +var last_activity_unix := 0.0 func _ready() -> void: @@ -46,6 +48,12 @@ func _ready() -> void: refresh_request.timeout = REFRESH_TIMEOUT_SECONDS refresh_request.request_completed.connect(_on_refresh_completed) add_child(refresh_request) + last_activity_unix = Time.get_unix_time_from_system() + + +func _input(_event: InputEvent) -> void: + last_activity_unix = Time.get_unix_time_from_system() + func set_session(new_username: String, token: String, new_refresh_token: String = "", new_role: String = "", token_expiration: String = "", legal: Dictionary = {}) -> void: session_revision += 1 @@ -59,6 +67,7 @@ func set_session(new_username: String, token: String, new_refresh_token: String refresh_in_progress = false refresh_revision = -1 warning_active = false + last_activity_unix = Time.get_unix_time_from_system() set_legal_state(legal, false) if is_logged_in and expiration_unix > 0.0: expiry_timer.start() @@ -151,6 +160,10 @@ func _evaluate_expiry() -> void: expire_session("expired") return if remaining <= WARNING_SECONDS: + var seconds_since_activity := Time.get_unix_time_from_system() - last_activity_unix + if seconds_since_activity <= ACTIVE_RECENTLY_SECONDS and not refresh_in_progress: + request_session_refresh() + return if not warning_active: warning_active = true session_warning.emit(remaining) diff --git a/scripts/ui/session_expiry_dialog.gd b/scripts/ui/session_expiry_dialog.gd index a81987b..a93826a 100644 --- a/scripts/ui/session_expiry_dialog.gd +++ b/scripts/ui/session_expiry_dialog.gd @@ -117,8 +117,8 @@ func _refresh_succeeded() -> void: func _refresh_failed(message: String, terminal: bool) -> void: - if not visible: - return + visible = true + move_to_front() error_label.text = message stay_button.text = "Stay logged in" stay_button.disabled = terminal diff --git a/tests/auth_session_test.gd b/tests/auth_session_test.gd index f58931d..6349df7 100644 --- a/tests/auth_session_test.gd +++ b/tests/auth_session_test.gd @@ -14,6 +14,7 @@ func _run() -> void: await process_frame _test_expiration_parsing() _test_warning_and_expiration() + _test_active_user_silently_refreshes_instead_of_warning() _test_refresh_preserves_identity() _test_refresh_in_flight_has_expiry_grace() _test_stale_responses_cannot_restore_or_end_session() @@ -40,6 +41,7 @@ func _test_warning_and_expiration() -> void: auth.session_warning.connect(warning_callback) auth.session_expired.connect(expiration_callback) auth.set_session("tester", "warning-token", "refresh-token", "USER", str(Time.get_unix_time_from_system() + 90.0)) + auth.last_activity_unix = Time.get_unix_time_from_system() - 3600.0 auth._evaluate_expiry() _assert(int(observed["warning_seconds"]) > 0 and int(observed["warning_seconds"]) <= 90, "session should warn inside the two-minute renewal window") auth.expiration_unix = Time.get_unix_time_from_system() - 1.0 @@ -49,6 +51,25 @@ func _test_warning_and_expiration() -> void: auth.session_expired.disconnect(expiration_callback) +func _test_active_user_silently_refreshes_instead_of_warning() -> void: + var observed := {"warning_seconds": -1} + var warning_callback := func(seconds: int): observed["warning_seconds"] = seconds + auth.session_warning.connect(warning_callback) + # Point at a local, always-refused port so the real refresh attempt this triggers fails + # fast and never touches the real network - the point of this test is that + # request_session_refresh() gets called at all, not that it succeeds. + auth.auth_base_url = "http://127.0.0.1:1" + auth.set_session("tester", "active-token", "refresh-token", "USER", str(Time.get_unix_time_from_system() + 90.0)) + auth.last_activity_unix = Time.get_unix_time_from_system() + auth._evaluate_expiry() + _assert(int(observed["warning_seconds"]) == -1 and not auth.warning_active, "an actively-used session should not show the expiry warning") + _assert(auth.refresh_in_progress, "an actively-used session nearing expiry should silently attempt a refresh instead of warning") + auth.session_warning.disconnect(warning_callback) + auth.refresh_in_progress = false + auth.auth_base_url = auth.DEFAULT_AUTH_BASE_URL + auth.clear_session() + + func _test_refresh_preserves_identity() -> void: var observed := {"refreshed": false} var refreshed_callback := func(): observed["refreshed"] = true