Class: Ksef::Auth::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/ksef/auth/client.rb

Overview

The six authentication endpoint calls: the five HTTP steps of §4.2's flow — its sixth step, signing, is offline — plus POST /auth/ksef-token for the other authentication method (docs/REFERENCE.md §4.2, §4.5).

Deliberately thin: it maps requests and responses and does nothing else. Deciding which credential to present, and caching the result, belongs a layer up in Client, which does exactly that.

Namespaced Ksef::Auth::Client rather than folded into Ksef::Client so that each subsystem's endpoints stay together, matching how the official clients are organised.

Three of these calls are unauthenticated — the challenge and both authentication submissions declare no security in the contract. Of the rest, #status and #redeem take the temporary authentication token and #refresh takes the refresh token, so the bearer is passed per call rather than held by the instance.

Constant Summary collapse

POLL_INTERVAL =

Polling defaults. There is deliberately no timeout: on DEMO and PROD the operation legitimately stays "in progress" while the certificate's status is checked with its issuer over OCSP/CRL, and the docs say the duration depends on that issuer. A client that gives up after a fixed interval reports failure for authentications that were about to succeed (§4.2).

2

Instance Method Summary collapse

Constructor Details

#initialize(connection) ⇒ Client

Returns a new instance of Client.

Parameters:



31
32
33
# File 'lib/ksef/auth/client.rb', line 31

def initialize(connection)
  @connection = connection
end

Instance Method Details

#authenticate!(reference_number, token:) ⇒ Object

As #wait_until_complete, but insists on success.

Raises:



109
110
111
112
113
114
115
116
# File 'lib/ksef/auth/client.rb', line 109

def authenticate!(reference_number, token:, **, &)
  result = wait_until_complete(reference_number, token: token, **, &)
  return result if result.success?

  detail = result.details.empty? ? "" : " (#{result.details.join("; ")})"
  raise AuthenticationError,
        "Authentication #{reference_number} failed with status #{result.code}: #{result.explain}#{detail}"
end

#challenge ⇒ Challenge

Returns:



36
37
38
# File 'lib/ksef/auth/client.rb', line 36

def challenge
  Challenge.from(post("auth/challenge").body)
end

#redeem(token:) ⇒ Tokens

Exchanges a completed authentication for the token pair. Single-use — a second call with the same authentication token is a 400, so this is never auto-retried (it is a POST, which the retry policy already excludes).

Returns:



81
82
83
# File 'lib/ksef/auth/client.rb', line 81

def redeem(token:)
  Tokens.from(post("auth/token/redeem", token: token).body)
end

#refresh(refresh_token:) ⇒ TokenInfo

Returns a fresh access token.

Returns:



86
87
88
# File 'lib/ksef/auth/client.rb', line 86

def refresh(refresh_token:)
  TokenInfo.from(post("auth/token/refresh", token: refresh_token).body["accessToken"])
end

#status(reference_number, token:) ⇒ OperationStatus

Parameters:

Returns:



72
73
74
# File 'lib/ksef/auth/client.rb', line 72

def status(reference_number, token:)
  OperationStatus.from(get("auth/#{reference_number}", token: token).body)
end

#submit_ksef_token(request) ⇒ Initiation

Submits a KSeF-token authentication. Like #submit_xades this takes an already-built request — Token#authentication_request assembles it, because the encryption depends on which published key was selected and that is not this layer's decision.

A 400 here carries 21111 for a bad challenge or 21470 for a stale key identifier; the latter is worth wrapping in Crypto::PublicKeys#with_key_rotation.

Parameters:

  • request (Hash) —

    the contract's InitTokenAuthenticationRequest

Returns:



66
67
68
# File 'lib/ksef/auth/client.rb', line 66

def submit_ksef_token(request)
  Initiation.from(post("auth/ksef-token", body: request).body)
end

#submit_xades(signed_xml, verify_certificate_chain: nil) ⇒ Initiation

Submits the XAdES-signed AuthTokenRequest. Returns 202 Accepted; the operation is asynchronous from here.

Parameters:

  • signed_xml (String)
  • verify_certificate_chain (Boolean, nil) (defaults to: nil) —

    the contract's optional query flag

Returns:



46
47
48
49
50
51
52
53
# File 'lib/ksef/auth/client.rb', line 46

def submit_xades(signed_xml, verify_certificate_chain: nil)
  response = @connection.post("auth/xades-signature") do |request|
    request.params["verifyCertificateChain"] = verify_certificate_chain unless verify_certificate_chain.nil?
    request.headers["Content-Type"] = "application/xml"
    request.body = signed_xml
  end
  Initiation.from(response.body)
end

#wait_until_complete(reference_number, token:, interval: POLL_INTERVAL, sleeper: method(:sleep)) {|status| ... } ⇒ OperationStatus

Polls until the operation stops being in progress.

Parameters:

  • interval (Numeric) (defaults to: POLL_INTERVAL) —

    seconds between polls

  • sleeper (#call) (defaults to: method(:sleep)) —

    injected for tests; receives the interval

Yield Parameters:

Returns:



96
97
98
99
100
101
102
103
104
# File 'lib/ksef/auth/client.rb', line 96

def wait_until_complete(reference_number, token:, interval: POLL_INTERVAL, sleeper: method(:sleep))
  loop do
    current = status(reference_number, token: token)
    yield current if block_given?
    return current if current.terminal?

    sleeper.call(interval)
  end
end