Module: Lich::Common::Authentication

Defined in:
documented/common/authentication/cli.rb,
documented/common/authentication/gui.rb,
documented/common/authentication/eaccess.rb,
documented/common/authentication/entry_store.rb,
documented/common/authentication/launch_data.rb,
documented/common/authentication/cli_password.rb,
documented/common/authentication/authenticator.rb,
documented/common/authentication/login_helpers.rb

Overview

Namespace for authentication, login, and session-related utilities.

See Also:

Defined Under Namespace

Modules: CLI, CLIPassword, EAccess, EntryStore, GUI, LaunchData, LoginHelpers Classes: FatalAuthError

Constant Summary collapse

MAX_AUTH_RETRIES =

Retry configuration for transient SSL/network errors These errors are often temporary and resolve on retry:

  • SSL_read: unexpected eof while reading (server closed connection)
  • Connection reset by peer
  • Connection timed out
3
AUTH_RETRY_BASE_DELAY =

seconds, doubles each retry: 5s, 10s, 20s

5
FATAL_ERROR_CODES =

Known fatal error codes that should not be retried REJECT = bad credentials, NORECORD = account not found, INVALID = invalid request PASSWORD = wrong password, CHARACTER_NOT_FOUND = character not in account GENERATOR_NOT_AVAILABLE = account not entitled to create a character on the instance

%w[REJECT NORECORD INVALID PASSWORD CHARACTER_NOT_FOUND GENERATOR_NOT_AVAILABLE].freeze

Class Method Summary collapse

Class Method Details

.authenticate(account:, password:, character: nil, game_code: nil, legacy: false, generator: false) ⇒ Hash, Array

Authenticates a user with the game server Includes automatic retry with exponential backoff for transient errors

Parameters:

  • account (String)

    User account name

  • password (String)

    User password

  • character (String, nil) (defaults to: nil)

    Character name (optional)

  • game_code (String, nil) (defaults to: nil)

    Game code (optional)

  • legacy (Boolean) (defaults to: false)

    Whether to use legacy authentication

  • generator (Boolean) (defaults to: false)

    Whether to enter the character generator instead of selecting a character

Returns:

  • (Hash, Array)

    Authentication data containing connection information

Raises:

  • (StandardError)

    Re-raises the last error after all retries exhausted



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'documented/common/authentication/authenticator.rb', line 41

def self.authenticate(account:, password:, character: nil, game_code: nil, legacy: false, generator: false)
  with_retry do
    if game_code && (character || generator)
      EAccess.auth_with_timeout(
        account: ,
        password: password,
        character: character,
        game_code: game_code,
        generator: generator
      )
    elsif legacy
      EAccess.auth_with_timeout(
        account: ,
        password: password,
        legacy: true
      )
    else
      EAccess.auth_with_timeout(
        account: ,
        password: password
      )
    end
  end
end

.with_retry { ... } ⇒ Object

Executes a block with retry logic for transient errors

Yields:

  • The block to execute with retry

Returns:

  • (Object)

    The result of the block

Raises:

  • (FatalAuthError)

    For fatal auth failures (bad credentials, etc.)

  • (StandardError)

    Re-raises the last error after all retries exhausted



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'documented/common/authentication/authenticator.rb', line 72

def self.with_retry
  last_error = nil

  MAX_AUTH_RETRIES.times do |attempt|
    begin
      result = yield

      # Success - log if this was a retry
      if attempt.positive?
        Lich.log "info: Authentication succeeded on attempt #{attempt + 1}"
      end

      return result
    rescue EAccess::AuthenticationError => e
      # Check if this is a fatal auth failure
      if FATAL_ERROR_CODES.any? { |code| e.error_code&.include?(code) }
        Lich.log "error: Authentication fatally failed: #{e.message}"
        raise FatalAuthError, e.message
      end

      # Transient auth error - allow retry
      last_error = e

      if attempt < MAX_AUTH_RETRIES - 1
        delay = AUTH_RETRY_BASE_DELAY * (2**attempt)
        Lich.log "warn: Authentication attempt #{attempt + 1}/#{MAX_AUTH_RETRIES} failed: " \
                 "#{e.message}, retrying in #{delay}s..."
        sleep(delay)
      end
    rescue FatalAuthError
      # Don't retry fatal auth failures - re-raise immediately
      raise
    rescue StandardError => e
      last_error = e

      if attempt < MAX_AUTH_RETRIES - 1
        delay = AUTH_RETRY_BASE_DELAY * (2**attempt)
        Lich.log "warn: Authentication attempt #{attempt + 1}/#{MAX_AUTH_RETRIES} failed: " \
                 "#{e.message}, retrying in #{delay}s..."
        sleep(delay)
      end
    end
  end

  # All retries exhausted - re-raise the last error
  Lich.log "error: Authentication failed after #{MAX_AUTH_RETRIES} attempts: #{last_error&.message}"
  raise last_error
end