mirror of
https://github.com/wanderer-industries/wanderer
synced 2026-03-13 15:16:03 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e49471fb94 | ||
|
|
ad35d9e172 | ||
|
|
d8fb980a3b | ||
|
|
b8b3bc60ad | ||
|
|
80d5dd1eb1 | ||
|
|
1ab0e96cbb | ||
|
|
e3a13b9554 |
18
CHANGELOG.md
18
CHANGELOG.md
@@ -2,6 +2,24 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v1.96.6](https://github.com/wanderer-industries/wanderer/compare/v1.96.5...v1.96.6) (2026-03-13)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: Fixed tracking issues
|
||||
|
||||
## [v1.96.5](https://github.com/wanderer-industries/wanderer/compare/v1.96.4...v1.96.5) (2026-02-27)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: Fixed access token refresh issues
|
||||
|
||||
## [v1.96.4](https://github.com/wanderer-industries/wanderer/compare/v1.96.3...v1.96.4) (2026-02-17)
|
||||
|
||||
|
||||
|
||||
@@ -265,6 +265,10 @@ defmodule WandererApp.Character.Tracker do
|
||||
end
|
||||
|
||||
_ ->
|
||||
Logger.debug(fn ->
|
||||
"[Tracker] update_online skipped for character #{character_id} - no valid access token"
|
||||
end)
|
||||
|
||||
{:error, :skipped}
|
||||
end
|
||||
end
|
||||
@@ -601,6 +605,10 @@ defmodule WandererApp.Character.Tracker do
|
||||
end
|
||||
|
||||
_ ->
|
||||
Logger.debug(fn ->
|
||||
"[Tracker] update_location skipped for character #{character_id} - no valid access token"
|
||||
end)
|
||||
|
||||
{:error, :skipped}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -215,8 +215,11 @@ defmodule WandererApp.Character.TrackingUtils do
|
||||
end
|
||||
end
|
||||
|
||||
# Check if a character has permission to be tracked on a map
|
||||
defp check_character_tracking_permission(character, map_id) do
|
||||
@doc """
|
||||
Checks if a character has permission to be tracked on a map.
|
||||
Returns {:ok, :allowed} or {:error, reason}.
|
||||
"""
|
||||
def check_character_tracking_permission(character, map_id) do
|
||||
with {:ok, %{acls: acls, owner_id: owner_id}} <-
|
||||
WandererApp.MapRepo.get(map_id,
|
||||
acls: [
|
||||
|
||||
@@ -754,6 +754,9 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
new_expires_at: token.expires_at
|
||||
)
|
||||
|
||||
# Clear any previous invalid_grant failure counter on successful refresh
|
||||
WandererApp.Cache.delete("character:#{character_id}:invalid_grant_count")
|
||||
|
||||
{:ok, _character} =
|
||||
character
|
||||
|> WandererApp.Api.Character.update(%{
|
||||
@@ -786,12 +789,12 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
expires_at_datetime = DateTime.from_unix!(expires_at)
|
||||
time_since_expiry = DateTime.diff(DateTime.utc_now(), expires_at_datetime, :second)
|
||||
|
||||
Logger.warning("TOKEN_REFRESH_FAILED: Invalid grant error during token refresh",
|
||||
character_id: character_id,
|
||||
error_message: error_message,
|
||||
time_since_expiry_seconds: time_since_expiry,
|
||||
original_expires_at: expires_at
|
||||
)
|
||||
# Track consecutive invalid_grant failures before permanently invalidating tokens.
|
||||
# EVE SSO can return invalid_grant for transient server issues, so we require
|
||||
# 3 consecutive failures before wiping tokens.
|
||||
fail_key = "character:#{character_id}:invalid_grant_count"
|
||||
count = WandererApp.Cache.lookup!(fail_key, 0) + 1
|
||||
WandererApp.Cache.put(fail_key, count, ttl: :timer.hours(2))
|
||||
|
||||
# Emit telemetry for token refresh failures
|
||||
:telemetry.execute([:wanderer_app, :token, :refresh_failed], %{count: 1}, %{
|
||||
@@ -800,8 +803,28 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
time_since_expiry: time_since_expiry
|
||||
})
|
||||
|
||||
invalidate_character_tokens(character, character_id, expires_at, scopes)
|
||||
{:error, :invalid_grant}
|
||||
if count >= 3 do
|
||||
Logger.warning("TOKEN_REFRESH_FAILED: Invalid grant error (#{count}/3, invalidating tokens)",
|
||||
character_id: character_id,
|
||||
error_message: error_message,
|
||||
time_since_expiry_seconds: time_since_expiry,
|
||||
original_expires_at: expires_at
|
||||
)
|
||||
|
||||
WandererApp.Cache.delete(fail_key)
|
||||
invalidate_character_tokens(character, character_id, expires_at, scopes)
|
||||
{:error, :invalid_grant}
|
||||
else
|
||||
Logger.warning(
|
||||
"TOKEN_REFRESH_FAILED: Invalid grant error (#{count}/3, deferring invalidation)",
|
||||
character_id: character_id,
|
||||
error_message: error_message,
|
||||
time_since_expiry_seconds: time_since_expiry,
|
||||
original_expires_at: expires_at
|
||||
)
|
||||
|
||||
{:error, :token_refresh_failed}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_refresh_token_result(
|
||||
@@ -833,20 +856,44 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
|
||||
defp handle_refresh_token_result(
|
||||
{:error, %OAuth2.Error{} = error},
|
||||
character,
|
||||
_character,
|
||||
character_id,
|
||||
expires_at,
|
||||
scopes
|
||||
_scopes
|
||||
) do
|
||||
invalidate_character_tokens(character, character_id, expires_at, scopes)
|
||||
Logger.warning("Failed to refresh token for #{character_id}: #{inspect(error)}")
|
||||
{:error, :invalid_grant}
|
||||
time_since_expiry = DateTime.diff(DateTime.utc_now(), DateTime.from_unix!(expires_at), :second)
|
||||
|
||||
Logger.warning("TOKEN_REFRESH_FAILED: Transient OAuth2 error during token refresh",
|
||||
character_id: character_id,
|
||||
error: inspect(error),
|
||||
time_since_expiry_seconds: time_since_expiry
|
||||
)
|
||||
|
||||
:telemetry.execute([:wanderer_app, :token, :refresh_failed], %{count: 1}, %{
|
||||
character_id: character_id,
|
||||
error_type: "oauth2_error",
|
||||
time_since_expiry: time_since_expiry
|
||||
})
|
||||
|
||||
{:error, :token_refresh_failed}
|
||||
end
|
||||
|
||||
defp handle_refresh_token_result(error, character, character_id, expires_at, scopes) do
|
||||
Logger.warning("Failed to refresh token for #{character_id}: #{inspect(error)}")
|
||||
invalidate_character_tokens(character, character_id, expires_at, scopes)
|
||||
{:error, :failed}
|
||||
defp handle_refresh_token_result(error, _character, character_id, expires_at, _scopes) do
|
||||
time_since_expiry = DateTime.diff(DateTime.utc_now(), DateTime.from_unix!(expires_at), :second)
|
||||
|
||||
Logger.warning("TOKEN_REFRESH_FAILED: Unexpected error during token refresh",
|
||||
character_id: character_id,
|
||||
error: inspect(error),
|
||||
time_since_expiry_seconds: time_since_expiry
|
||||
)
|
||||
|
||||
:telemetry.execute([:wanderer_app, :token, :refresh_failed], %{count: 1}, %{
|
||||
character_id: character_id,
|
||||
error_type: "unexpected_error",
|
||||
time_since_expiry: time_since_expiry
|
||||
})
|
||||
|
||||
{:error, :token_refresh_failed}
|
||||
end
|
||||
|
||||
defp invalidate_character_tokens(character, character_id, expires_at, scopes) do
|
||||
@@ -859,6 +906,12 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
Logger.error("Failed to clear tokens for #{character_id}: #{inspect(error)}")
|
||||
end
|
||||
|
||||
Phoenix.PubSub.broadcast(
|
||||
WandererApp.PubSub,
|
||||
"character:#{character_id}",
|
||||
:character_token_invalid
|
||||
)
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -159,7 +159,7 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
|
||||
if is_same_user_as_owner do
|
||||
# All characters from the map owner's account have full access
|
||||
:ok
|
||||
{:ok, character_id}
|
||||
else
|
||||
[character_permissions] =
|
||||
WandererApp.Permissions.check_characters_access([character], acls)
|
||||
@@ -179,12 +179,12 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
{:remove_character, character_id, :no_track_permission}
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
{:ok, character_id}
|
||||
end
|
||||
end
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
{:ok, character_id}
|
||||
end
|
||||
end,
|
||||
timeout: :timer.seconds(60),
|
||||
@@ -193,7 +193,26 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
)
|
||||
|> Enum.reduce([], fn
|
||||
{:ok, {:remove_character, character_id, reason}}, acc ->
|
||||
[{character_id, reason} | acc]
|
||||
# Track consecutive permission failures - only remove after 3 consecutive hourly failures
|
||||
fail_key = "map_#{map_id}:char_#{character_id}:perm_fail_count"
|
||||
count = WandererApp.Cache.lookup!(fail_key, 0) + 1
|
||||
WandererApp.Cache.put(fail_key, count, ttl: :timer.hours(4))
|
||||
|
||||
if count >= 3 do
|
||||
WandererApp.Cache.delete(fail_key)
|
||||
[{character_id, reason} | acc]
|
||||
else
|
||||
Logger.info(
|
||||
"[CharacterCleanup] Character #{character_id} permission fail #{count}/3 on map #{map_id}, deferring removal"
|
||||
)
|
||||
|
||||
acc
|
||||
end
|
||||
|
||||
{:ok, {:ok, character_id}}, acc ->
|
||||
# Character passed permission check - clear any previous failure counter
|
||||
WandererApp.Cache.delete("map_#{map_id}:char_#{character_id}:perm_fail_count")
|
||||
acc
|
||||
|
||||
{:ok, _result}, acc ->
|
||||
acc
|
||||
@@ -966,13 +985,48 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
character_id: character_id
|
||||
}) do
|
||||
{:ok, %{tracked: false}} ->
|
||||
# Was explicitly untracked (e.g., by permission cleanup) - don't re-enable
|
||||
Logger.debug(fn ->
|
||||
"[CharactersImpl] Skipping re-track for character #{character_id} on map #{map_id} - " <>
|
||||
"character was explicitly untracked"
|
||||
end)
|
||||
# Was previously untracked (by system cleanup or user).
|
||||
# If character now has valid tokens, check permissions and auto-restore tracking.
|
||||
# This handles the case where re-auth gives fresh tokens but DB still says tracked: false.
|
||||
if not is_nil(character.access_token) do
|
||||
case WandererApp.Character.TrackingUtils.check_character_tracking_permission(
|
||||
character,
|
||||
map_id
|
||||
) do
|
||||
{:ok, :allowed} ->
|
||||
Logger.info(
|
||||
"[CharactersImpl] Auto-restoring tracking for character #{character_id} on map #{map_id} - " <>
|
||||
"character has valid tokens and permissions after reconnect"
|
||||
)
|
||||
|
||||
add_character(map_id, character, false)
|
||||
add_character(map_id, character, true)
|
||||
|
||||
WandererApp.MapCharacterSettingsRepo.track(%{
|
||||
map_id: map_id,
|
||||
character_id: character_id
|
||||
})
|
||||
|
||||
WandererApp.Character.TrackerManager.update_track_settings(character_id, %{
|
||||
map_id: map_id,
|
||||
track: true
|
||||
})
|
||||
|
||||
_ ->
|
||||
Logger.debug(fn ->
|
||||
"[CharactersImpl] Skipping re-track for character #{character_id} on map #{map_id} - " <>
|
||||
"character lacks permissions"
|
||||
end)
|
||||
|
||||
add_character(map_id, character, false)
|
||||
end
|
||||
else
|
||||
Logger.debug(fn ->
|
||||
"[CharactersImpl] Skipping re-track for character #{character_id} on map #{map_id} - " <>
|
||||
"character has no valid access token"
|
||||
end)
|
||||
|
||||
add_character(map_id, character, false)
|
||||
end
|
||||
|
||||
_ ->
|
||||
# New character or already tracked - enable tracking
|
||||
|
||||
Reference in New Issue
Block a user