tina4ruby 3.13.37 → 3.13.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +7 -7
- data/lib/tina4/api.rb +43 -1
- data/lib/tina4/auth.rb +118 -7
- data/lib/tina4/cli.rb +110 -2
- data/lib/tina4/database.rb +407 -52
- data/lib/tina4/dev_admin.rb +47 -14
- data/lib/tina4/drivers/sqlite_driver.rb +23 -0
- data/lib/tina4/env.rb +40 -4
- data/lib/tina4/events.rb +54 -8
- data/lib/tina4/field_types.rb +5 -2
- data/lib/tina4/graphql.rb +68 -12
- data/lib/tina4/html_element.rb +55 -7
- data/lib/tina4/log.rb +86 -10
- data/lib/tina4/mcp.rb +35 -8
- data/lib/tina4/messenger.rb +130 -25
- data/lib/tina4/metrics.rb +351 -73
- data/lib/tina4/middleware.rb +136 -13
- data/lib/tina4/migration.rb +113 -24
- data/lib/tina4/orm.rb +196 -32
- data/lib/tina4/query_builder.rb +22 -3
- data/lib/tina4/queue_backends/kafka_backend.rb +39 -2
- data/lib/tina4/rack_app.rb +22 -10
- data/lib/tina4/response.rb +31 -11
- data/lib/tina4/router.rb +34 -4
- data/lib/tina4/seeder.rb +433 -84
- data/lib/tina4/session.rb +94 -17
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4/websocket.rb +458 -21
- data/lib/tina4/wsdl.rb +25 -2
- data/lib/tina4.rb +91 -12
- metadata +6 -47
data/lib/tina4/session.rb
CHANGED
|
@@ -21,7 +21,16 @@ module Tina4
|
|
|
21
21
|
if !options.key?(:cookie_name) && env_name && !env_name.empty?
|
|
22
22
|
@options[:cookie_name] = env_name
|
|
23
23
|
end
|
|
24
|
-
|
|
24
|
+
# No guessable built-in secret. The session never signs with this value
|
|
25
|
+
# (IDs are SecureRandom.hex(32)), so we resolve it from TINA4_SECRET only
|
|
26
|
+
# — nil when unset. This honours the framework's blank-secret discipline
|
|
27
|
+
# (Auth.ensure_dev_secret never uses a guessable default); Python/Node
|
|
28
|
+
# sessions carry no secret field at all.
|
|
29
|
+
@options[:secret] ||= ENV["TINA4_SECRET"]
|
|
30
|
+
# Backend-failure policy strict flag (parity with Python's
|
|
31
|
+
# TINA4_SESSION_STRICT). When truthy, read/write/destroy/gc failures
|
|
32
|
+
# RE-RAISE instead of logging + degrading.
|
|
33
|
+
@strict = Tina4::Env.is_truthy(ENV["TINA4_SESSION_STRICT"])
|
|
25
34
|
@handler = create_handler
|
|
26
35
|
@id = extract_session_id(env) || SecureRandom.hex(32)
|
|
27
36
|
@data = load_session
|
|
@@ -51,14 +60,23 @@ module Tina4
|
|
|
51
60
|
@data.dup
|
|
52
61
|
end
|
|
53
62
|
|
|
63
|
+
# Persist the session if dirty. On a backend write failure the error is
|
|
64
|
+
# logged and false is returned — the @modified (dirty) flag is RETAINED so
|
|
65
|
+
# a later save can retry. Returns true on a successful (or no-op) write.
|
|
54
66
|
def save
|
|
55
|
-
return unless @modified
|
|
56
|
-
|
|
57
|
-
|
|
67
|
+
return true unless @modified
|
|
68
|
+
if safe_write(@id, @data)
|
|
69
|
+
@modified = false
|
|
70
|
+
true
|
|
71
|
+
else
|
|
72
|
+
false # dirty flag retained for retry
|
|
73
|
+
end
|
|
58
74
|
end
|
|
59
75
|
|
|
76
|
+
# Destroy the current session. Should be called right after login or any
|
|
77
|
+
# privilege change to defend against session fixation (see #regenerate).
|
|
60
78
|
def destroy
|
|
61
|
-
|
|
79
|
+
safe_destroy(@id)
|
|
62
80
|
@data = {}
|
|
63
81
|
end
|
|
64
82
|
|
|
@@ -104,12 +122,17 @@ module Tina4
|
|
|
104
122
|
result.nil? ? default : result
|
|
105
123
|
end
|
|
106
124
|
|
|
107
|
-
# Regenerate the session ID while preserving data — returns new ID
|
|
125
|
+
# Regenerate the session ID while preserving data — returns the new ID.
|
|
126
|
+
# Call this right after login or any privilege change to defend against
|
|
127
|
+
# session fixation (a pre-auth session ID must not survive into the
|
|
128
|
+
# authenticated session). Destroys the old backend record (best-effort)
|
|
129
|
+
# and persists under the new ID.
|
|
108
130
|
def regenerate
|
|
109
131
|
old_id = @id
|
|
110
132
|
@id = SecureRandom.hex(32)
|
|
111
|
-
|
|
133
|
+
safe_destroy(old_id)
|
|
112
134
|
@modified = true
|
|
135
|
+
save
|
|
113
136
|
@id
|
|
114
137
|
end
|
|
115
138
|
|
|
@@ -133,24 +156,27 @@ module Tina4
|
|
|
133
156
|
end
|
|
134
157
|
|
|
135
158
|
# Reads raw session data for a given session ID from backend storage.
|
|
136
|
-
# Returns the data hash or
|
|
159
|
+
# Returns the data hash, or {} on a backend failure (logged + degraded).
|
|
137
160
|
def read(session_id)
|
|
138
|
-
|
|
161
|
+
safe_read(session_id)
|
|
139
162
|
end
|
|
140
163
|
|
|
141
164
|
# Writes raw session data for a given session ID to backend storage.
|
|
165
|
+
# Returns true on success, false on a backend failure (logged + degraded).
|
|
142
166
|
def write(session_id, data, ttl = nil)
|
|
143
|
-
|
|
144
|
-
@handler.write(session_id, data, ttl)
|
|
145
|
-
else
|
|
146
|
-
@handler.write(session_id, data)
|
|
147
|
-
end
|
|
167
|
+
safe_write(session_id, data, ttl)
|
|
148
168
|
end
|
|
149
169
|
|
|
150
|
-
# Garbage collection: remove expired sessions from the handler
|
|
170
|
+
# Garbage collection: remove expired sessions from the handler.
|
|
171
|
+
# A backend failure is logged and swallowed (never crashes the request).
|
|
151
172
|
def gc(max_lifetime = nil)
|
|
173
|
+
return unless @handler.respond_to?(:gc)
|
|
152
174
|
max_lifetime ||= @options[:max_age]
|
|
153
|
-
@handler.gc(max_lifetime)
|
|
175
|
+
@handler.gc(max_lifetime)
|
|
176
|
+
rescue StandardError => e
|
|
177
|
+
log_backend_error("gc", e)
|
|
178
|
+
raise if @strict
|
|
179
|
+
nil
|
|
154
180
|
end
|
|
155
181
|
|
|
156
182
|
def cookie_header(cookie_name = nil)
|
|
@@ -181,8 +207,59 @@ module Tina4
|
|
|
181
207
|
end
|
|
182
208
|
|
|
183
209
|
def load_session
|
|
184
|
-
|
|
210
|
+
safe_read(@id)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# ── Backend-failure policy (parity with Python's Session boundary) ──
|
|
214
|
+
#
|
|
215
|
+
# Centralised here, NOT in each handler, so every backend (file, redis,
|
|
216
|
+
# valkey, mongo, database) shares one policy. The rule:
|
|
217
|
+
# read failure → log + return {} (empty session, never a 500)
|
|
218
|
+
# write failure → log + return false (caller retains dirty for retry)
|
|
219
|
+
# destroy failure → log + swallow (return false)
|
|
220
|
+
# gc failure → log + swallow (see #gc)
|
|
221
|
+
# A genuinely-empty but HEALTHY backend (handler returns nil/{} WITHOUT
|
|
222
|
+
# raising) is NOT a failure and logs nothing. TINA4_SESSION_STRICT=true
|
|
223
|
+
# re-raises instead of degrading.
|
|
224
|
+
|
|
225
|
+
def safe_read(session_id)
|
|
226
|
+
existing = @handler.read(session_id)
|
|
185
227
|
existing || {}
|
|
228
|
+
rescue StandardError => e
|
|
229
|
+
log_backend_error("read", e)
|
|
230
|
+
raise if @strict
|
|
231
|
+
{}
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def safe_write(session_id, data, ttl = nil)
|
|
235
|
+
if ttl
|
|
236
|
+
@handler.write(session_id, data, ttl)
|
|
237
|
+
else
|
|
238
|
+
@handler.write(session_id, data)
|
|
239
|
+
end
|
|
240
|
+
true
|
|
241
|
+
rescue StandardError => e
|
|
242
|
+
log_backend_error("write", e)
|
|
243
|
+
raise if @strict
|
|
244
|
+
false
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def safe_destroy(session_id)
|
|
248
|
+
@handler.destroy(session_id)
|
|
249
|
+
true
|
|
250
|
+
rescue StandardError => e
|
|
251
|
+
log_backend_error("destroy", e)
|
|
252
|
+
raise if @strict
|
|
253
|
+
false
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Single source of the backend-failure log line. Names the operation and
|
|
257
|
+
# the concrete handler class so ops can see WHICH backend failed.
|
|
258
|
+
def log_backend_error(operation, error)
|
|
259
|
+
handler_class = @handler.class.name
|
|
260
|
+
Tina4::Log.error("Session #{operation} failed (#{handler_class}): #{error.message}")
|
|
261
|
+
rescue StandardError
|
|
262
|
+
warn("Session #{operation} failed: #{error.message}")
|
|
186
263
|
end
|
|
187
264
|
|
|
188
265
|
def create_handler
|
data/lib/tina4/version.rb
CHANGED