Class: Ksef::Auth::Token
- Inherits:
-
Object
- Object
- Ksef::Auth::Token
- Defined in:
- lib/ksef/auth/token.rb
Overview
A KSeF token and the context it authenticates into — the credential object of DESIGN.md §8:
Ksef::Auth::Token.new(context_nip: "9999999999", token: ENV["KSEF_TOKEN"])
A KSeF token is the second of the API's two authentication methods
(docs/REFERENCE.md §4). It is not a bearer token and is never sent as one: it is
RSA-OAEP-encrypted together with the challenge's timestamp and posted to
POST /auth/ksef-token, which starts the same asynchronous operation the XAdES flow
does and ends at the same POST /auth/token/redeem.
It cannot be the first credential
A KSeF token can only be issued after a one-time XAdES authentication
(tokeny-ksef.md; POST /tokens requires a bearer, /auth/xades-signature requires
nothing). So this class is the everyday path and Signer is the bootstrap — which is
why the certificate flow shipped first (§6a.2).
Treat instances as secrets. #to_s and #inspect are redacted, and the token is
reachable only by the deliberate #authentication_request.
Constant Summary collapse
- REDACTED =
"[REDACTED]"- CONTEXT_TYPES =
Only two of the contract's four
AuthenticationContextIdentifierTypevalues are reachable here, and that is not a simplification:tokeny-ksef.mdrecords that a token can only be issued in aNiporInternalIdcontext, so a token for the other two cannot exist to be presented (§4.1). { nip: "Nip", internal_id: "InternalId" }.freeze
Instance Attribute Summary collapse
-
#context_type ⇒ Object
readonly
Returns the value of attribute context_type.
-
#context_value ⇒ Object
readonly
Returns the value of attribute context_value.
Instance Method Summary collapse
-
#authentication_request(challenge:, certificate:, allowed_ips: nil) ⇒ Hash
The
InitTokenAuthenticationRequestbody forPOST /auth/ksef-token. -
#context_identifier ⇒ Object
The contract's
AuthenticationContextIdentifier. -
#encrypted_token(challenge:, certificate:) ⇒ String
{ksefToken}|{timestampMs}, UTF-8, RSA-OAEP-encrypted and base64-encoded (§4.5). -
#initialize(token:, context_nip: nil, internal_id: nil) ⇒ Token
constructor
A new instance of Token.
- #inspect ⇒ Object
- #to_s ⇒ Object
Constructor Details
#initialize(token:, context_nip: nil, internal_id: nil) ⇒ Token
Returns a new instance of Token.
40 41 42 43 44 |
# File 'lib/ksef/auth/token.rb', line 40 def initialize(token:, context_nip: nil, internal_id: nil) @token = validate_token(token) @context_type, @context_value = resolve_context(context_nip, internal_id) freeze end |
Instance Attribute Details
#context_type ⇒ Object (readonly)
Returns the value of attribute context_type.
34 35 36 |
# File 'lib/ksef/auth/token.rb', line 34 def context_type @context_type end |
#context_value ⇒ Object (readonly)
Returns the value of attribute context_value.
34 35 36 |
# File 'lib/ksef/auth/token.rb', line 34 def context_value @context_value end |
Instance Method Details
#authentication_request(challenge:, certificate:, allowed_ips: nil) ⇒ Hash
The InitTokenAuthenticationRequest body for POST /auth/ksef-token.
Built here rather than inside Client for the same reason Client#submit_xades takes an already-signed document: the HTTP layer maps requests and responses, and the credential is what knows how to present itself.
62 63 64 65 66 67 68 69 70 71 |
# File 'lib/ksef/auth/token.rb', line 62 def authentication_request(challenge:, certificate:, allowed_ips: nil) request = { challenge: Challenge.validate_format!(challenge.to_s), contextIdentifier: context_identifier, encryptedToken: encrypted_token(challenge: challenge, certificate: certificate), publicKeyId: certificate.public_key_id } policy = AuthorizationPolicy.coerce(allowed_ips) policy ? request.merge(authorizationPolicy: { allowedIps: policy.to_h }) : request end |
#context_identifier ⇒ Object
The contract's AuthenticationContextIdentifier.
47 |
# File 'lib/ksef/auth/token.rb', line 47 def context_identifier = { type: CONTEXT_TYPES.fetch(context_type), value: context_value } |
#encrypted_token(challenge:, certificate:) ⇒ String
{ksefToken}|{timestampMs}, UTF-8, RSA-OAEP-encrypted and base64-encoded (§4.5).
The timestamp is not decoration and not ours to generate: the docs are
explicit that it acts as a nonce, so that a captured ciphertext cannot be replayed
into a later session. It has to be the timestampMs the challenge response
carried, which is why this takes a Challenge and not a String — a locally
generated millisecond count will not match, and the authentication fails with
nothing to point at.
83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/ksef/auth/token.rb', line 83 def encrypted_token(challenge:, certificate:) = challenge.respond_to?(:timestamp_ms) ? challenge. : nil if .nil? raise AuthenticationError, "The KSeF-token flow needs the challenge's own timestampMs, so pass the " \ "Ksef::Auth::Challenge returned by POST /auth/challenge rather than its string. " \ "The timestamp is a replay nonce (docs/REFERENCE.md §4.5)." end Crypto.encode(certificate.encrypt("#{@token}|#{timestamp}")) end |
#inspect ⇒ Object
97 98 99 |
# File 'lib/ksef/auth/token.rb', line 97 def inspect "#<Ksef::Auth::Token context=#{CONTEXT_TYPES.fetch(context_type)}:#{context_value} token=#{REDACTED}>" end |
#to_s ⇒ Object
95 |
# File 'lib/ksef/auth/token.rb', line 95 def to_s = REDACTED |