Class: ApiKeys::Helpers::TokenSession
- Inherits:
-
Object
- Object
- ApiKeys::Helpers::TokenSession
- Defined in:
- lib/api_keys/helpers/token_session.rb
Overview
Helper for managing API key tokens in the session.
Secret keys can only be shown once (immediately after creation) because the plaintext token is not stored in the database. This helper provides a clean interface for the "show token once" pattern:
- After creating a key, store an encrypted, short-lived handoff in the session
- On the success page, decrypt, retrieve, and clear the token
- If the user refreshes, the token is gone
Constant Summary collapse
- DEFAULT_SESSION_KEY =
Default session key for storing the token
:api_keys_new_token- MAX_TOKEN_BYTESIZE =
512- MAX_CIPHERTEXT_BYTESIZE =
4096- MAX_KEY_ID_BYTESIZE =
128- HANDOFF_VERSION =
2- HANDOFF_TTL =
10.minutes
- ENCRYPTION_CIPHER =
"aes-256-gcm"- ENCRYPTION_SALT =
"api_keys/token_session/v2"- ENCRYPTION_PURPOSE =
"api_keys.token_session"- JSON_SERIALIZER =
Module.new do module_function def dump(value) JSON.generate(value) end def load(value) JSON.parse(value) end end
Class Method Summary collapse
-
.available?(session, key: DEFAULT_SESSION_KEY, api_key: nil, api_key_id: nil) ⇒ Boolean
Check if a token is available in the session without removing it.
-
.retrieve_once(session, key: DEFAULT_SESSION_KEY, api_key: nil, api_key_id: nil) ⇒ String?
Retrieve and clear the token from the session.
-
.store(session, api_key, key: DEFAULT_SESSION_KEY) ⇒ String
Store an encrypted API key-token handoff in the session for later retrieval.
Class Method Details
.available?(session, key: DEFAULT_SESSION_KEY, api_key: nil, api_key_id: nil) ⇒ Boolean
Check if a token is available in the session without removing it. Useful for conditional rendering.
113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/api_keys/helpers/token_session.rb', line 113 def available?(session, key: DEFAULT_SESSION_KEY, api_key: nil, api_key_id: nil) payload = session[key] expected_id = normalize_api_key_id(api_key_id || (api_key.id if api_key.respond_to?(:id))) return valid_token_payload?(payload) if payload.is_a?(String) && expected_id.nil? decoded_payload = decode_payload(payload) return false unless decoded_payload token = decoded_payload["token"] || decoded_payload[:token] stored_id = decoded_payload["api_key_id"] || decoded_payload[:api_key_id] return false if expected_id && stored_id.to_s != expected_id.to_s valid_token_payload?(token) end |
.retrieve_once(session, key: DEFAULT_SESSION_KEY, api_key: nil, api_key_id: nil) ⇒ String?
Retrieve and clear the token from the session. Returns nil if no token is stored (e.g., page was refreshed).
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/api_keys/helpers/token_session.rb', line 89 def retrieve_once(session, key: DEFAULT_SESSION_KEY, api_key: nil, api_key_id: nil) payload = session.delete(key) expected_id = normalize_api_key_id(api_key_id || (api_key.id if api_key.respond_to?(:id))) # Plain string payloads from older versions remain readable only when # the caller does not request ID binding. return payload if expected_id.nil? && valid_token_payload?(payload) decoded_payload = decode_payload(payload) return nil unless decoded_payload token = decoded_payload["token"] || decoded_payload[:token] stored_id = decoded_payload["api_key_id"] || decoded_payload[:api_key_id] return nil if expected_id && stored_id.to_s != expected_id.to_s return nil unless valid_token_payload?(token) token end |
.store(session, api_key, key: DEFAULT_SESSION_KEY) ⇒ String
Store an encrypted API key-token handoff in the session for later retrieval.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/api_keys/helpers/token_session.rb', line 63 def store(session, api_key, key: DEFAULT_SESSION_KEY) token = api_key.respond_to?(:token) ? api_key.token : api_key.to_s unless valid_token_payload?(token) raise ArgumentError, "Cannot store an invalid API key token in the session" end api_key_id = normalize_api_key_id(api_key.id) if api_key.respond_to?(:id) encrypted_payload = token_encryptor.encrypt_and_sign( { "token" => token, "api_key_id" => api_key_id }, expires_in: HANDOFF_TTL, purpose: ENCRYPTION_PURPOSE ) session[key] = { "version" => HANDOFF_VERSION, "ciphertext" => encrypted_payload, "api_key_id" => api_key_id } token end |