Class: Terminalwire::V2::Rails::Session
- Inherits:
-
Object
- Object
- Terminalwire::V2::Rails::Session
- Extended by:
- Forwardable
- Defined in:
- lib/terminalwire/v2/rails.rb
Overview
A session stored on the CLIENT — the v2-native version of v1's Terminalwire::Rails::Session, so a v2-only app needs no v1 gem. It reads/writes via the context (file/directory/storage_path, which v2 implements identically to v1).
The payload is ENCRYPTED and signed (ActiveSupport::MessageEncryptor, AES-256-GCM — the same primitive behind Rails' encrypted cookies), so the contents are confidential on the client machine, not just tamper-proof. The key is derived from the app's secret_key_base with a Terminalwire-specific salt, so it is never the raw secret and never shared with any other verifier/encryptor in the app. Sessions expire (default 30 days, clock reset on every write); an expired session reads as empty.
Earlier releases stored the session as an HS256 JWT — signed but NOT encrypted, despite docs saying otherwise. Those legacy sessions are read one last time and immediately rewritten encrypted, so upgrading logs nobody out.
Resilient by design: a missing, empty, tampered, expired, or wrong-key session reads as EMPTY, so the user simply logs in again — upgrading (or rotating the secret) never crashes a command, it just signs them out.
Constant Summary collapse
- FILENAME =
Kept from the JWT era so upgraded clients keep using the same file.
"session.jwt"- EMPTY_SESSION =
{}.freeze
- KEY_SALT =
Key-derivation salt — Terminalwire-specific so the derived key can't collide with any key the app derives from secret_key_base elsewhere.
"terminalwire session"- CIPHER =
AEAD cipher: encrypts AND authenticates in one primitive (the same one behind Rails' encrypted cookies). Pinned explicitly, not left to the app's ActiveSupport default (which is aes-256-cbc unless configured otherwise).
"aes-256-gcm"- DEFAULT_EXPIRES_IN =
30 days, refreshed on every write
60 * 60 * 24 * 30
Class Method Summary collapse
Instance Method Summary collapse
- #[]=(key, value) ⇒ Object
- #edit {|config| ... } ⇒ Object
-
#initialize(context:, path: nil, secret_key: self.class.secret_key, expires_in: DEFAULT_EXPIRES_IN) ⇒ Session
constructor
A new instance of Session.
-
#read ⇒ Object
The session payload, or EMPTY_SESSION when there isn't a valid one (missing, empty, tampered, expired, wrong key, unreadable).
- #reset ⇒ Object
- #write(config) ⇒ Object
Constructor Details
#initialize(context:, path: nil, secret_key: self.class.secret_key, expires_in: DEFAULT_EXPIRES_IN) ⇒ Session
Returns a new instance of Session.
94 95 96 97 98 99 100 101 |
# File 'lib/terminalwire/v2/rails.rb', line 94 def initialize(context:, path: nil, secret_key: self.class.secret_key, expires_in: DEFAULT_EXPIRES_IN) @context = context @path = Pathname.new(path || context.storage_path) @config_file_path = @path.join(FILENAME) @secret_key = secret_key @expires_in = expires_in ensure_file end |
Class Method Details
.secret_key ⇒ Object
138 139 140 |
# File 'lib/terminalwire/v2/rails.rb', line 138 def self.secret_key ::Rails.application.secret_key_base end |
Instance Method Details
#[]=(key, value) ⇒ Object
129 130 131 |
# File 'lib/terminalwire/v2/rails.rb', line 129 def []=(key, value) edit { |config| config[key] = value } end |
#edit {|config| ... } ⇒ Object
123 124 125 126 127 |
# File 'lib/terminalwire/v2/rails.rb', line 123 def edit config = read.dup yield config write(config) end |
#read ⇒ Object
The session payload, or EMPTY_SESSION when there isn't a valid one (missing, empty, tampered, expired, wrong key, unreadable). To the user these all mean the same thing — log in again — so none of them raise.
106 107 108 109 110 111 112 113 114 115 |
# File 'lib/terminalwire/v2/rails.rb', line 106 def read token = @context.file.read(@config_file_path) return EMPTY_SESSION if token.nil? || token.to_s.empty? encryptor.decrypt_and_verify(token) || EMPTY_SESSION rescue ActiveSupport::MessageEncryptor::InvalidMessage read_and_migrate_legacy(token) rescue StandardError EMPTY_SESSION end |
#reset ⇒ Object
117 118 119 120 121 |
# File 'lib/terminalwire/v2/rails.rb', line 117 def reset @context.file.delete(@config_file_path) rescue StandardError nil end |
#write(config) ⇒ Object
133 134 135 136 |
# File 'lib/terminalwire/v2/rails.rb', line 133 def write(config) token = encryptor.encrypt_and_sign(config, expires_in: @expires_in) @context.file.write(@config_file_path, token) end |