tina4ruby 3.13.153 → 3.13.154
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/CHANGELOG.md +27 -0
- data/lib/tina4/csp.rb +11 -0
- data/lib/tina4/frond.rb +34 -3
- data/lib/tina4/realtime.rb +15 -3
- data/lib/tina4/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 22d42b434e0c3d217a6df121286a6f1097d98ddf891cedc552c39857205fde76
|
|
4
|
+
data.tar.gz: 6509d126521868fc0a353f8b855d2ff0bdae2bd6d0338a34ac6dfe8cae71316e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b548d403d07725623371d20143d92406d7a8de3f8121c238050684e10c0eb7e4e5a8721b7d6f93c53239104e12c1556b12116085eb2da70a30797c218c9c7f7f
|
|
7
|
+
data.tar.gz: e8f0a0d68a58c3dc030a960f4f488085ce9af6db6b3543142185874869a083a3190ca6e7a88bd3a80e21c8c517c211f1f9cdb74ffbca776c930755999bd59aea
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,33 @@ number means the same thing everywhere.
|
|
|
6
6
|
**The authoritative release notes for every shipped version live in the documentation:**
|
|
7
7
|
https://tina4.com/ruby/36-releases
|
|
8
8
|
|
|
9
|
+
## 3.13.154 - 2026-10-23
|
|
10
|
+
|
|
11
|
+
### BREAKING
|
|
12
|
+
- Realtime (WebRTC): `GET /api/rtc/config` (which mints ephemeral TURN
|
|
13
|
+
credentials) and the `/ws/rtc/{room}` signalling socket now require a valid
|
|
14
|
+
token by default (ADR-0101, tina4-python#252). They were public, with no way
|
|
15
|
+
for an app to close them, so the TURN relay was an open resource. Opt out with
|
|
16
|
+
`TINA4_RTC_REQUIRE_AUTH=false` (or `0`/`no`/`off`) for anonymous calling; any
|
|
17
|
+
other value keeps them secured. If your app relied on the open routes, set
|
|
18
|
+
that env var.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- Frond: a bare reference to a zero-argument callable global (a zero-arity Proc,
|
|
22
|
+
lambda or Method) is now invoked and its return value used for both `{{ g }}`
|
|
23
|
+
output and `{% if g %}` truthiness, matching Node (ADR-0085,
|
|
24
|
+
tina4-documentation#90). `g()` still calls exactly once; a non-callable global
|
|
25
|
+
and a callable that declares parameters are unchanged.
|
|
26
|
+
- CSP: the per-response nonce is no longer injected into a `script-src` /
|
|
27
|
+
`style-src` directive that already carries `'unsafe-inline'` (ADR-0100,
|
|
28
|
+
tina4-php#330). A browser ignores `'unsafe-inline'` once a nonce joins the same
|
|
29
|
+
directive, so inline content an app deliberately allowed through `TINA4_CSP`
|
|
30
|
+
was silently blocked from 3.13.143 on. The app's explicit choice now wins; the
|
|
31
|
+
strict default policy is still nonced.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- Skills: the maintainer skill gains an off-skill self-audit reflex.
|
|
35
|
+
|
|
9
36
|
## 3.13.153 - 2026-10-21
|
|
10
37
|
|
|
11
38
|
### Fixed
|
data/lib/tina4/csp.rb
CHANGED
|
@@ -95,9 +95,20 @@ module Tina4
|
|
|
95
95
|
%w[style-src script-src].each do |directive|
|
|
96
96
|
existing = directives.find { |name, _| name == directive }
|
|
97
97
|
if existing
|
|
98
|
+
# ADR-0100: the app opted this directive into inline via
|
|
99
|
+
# 'unsafe-inline'. A browser ignores 'unsafe-inline' the moment a
|
|
100
|
+
# nonce joins the same directive (CSP Level 2+), so adding the nonce
|
|
101
|
+
# would silently block the inline the app asked for. The app wins.
|
|
102
|
+
next if existing[1].split(/\s+/).include?("'unsafe-inline'")
|
|
103
|
+
|
|
98
104
|
existing[1] = "#{existing[1]} #{token}".strip unless existing[1].split(/\s+/).include?(token)
|
|
99
105
|
else
|
|
100
106
|
base = default_value.empty? ? "'self'" : default_value
|
|
107
|
+
# ADR-0100: a directive derived from an 'unsafe-inline' default-src
|
|
108
|
+
# must not be spawned with a nonce - it would cancel the inline the
|
|
109
|
+
# app allowed through default-src.
|
|
110
|
+
next if base.split(/\s+/).include?("'unsafe-inline'")
|
|
111
|
+
|
|
101
112
|
directives << [directive, "#{base} #{token}".strip]
|
|
102
113
|
end
|
|
103
114
|
end
|
data/lib/tina4/frond.rb
CHANGED
|
@@ -441,7 +441,35 @@ module Tina4
|
|
|
441
441
|
# Same dual-callable semantics as ``add_filter`` — see that method for
|
|
442
442
|
# the static-facade pattern.
|
|
443
443
|
def self.add_global(name, value)
|
|
444
|
-
@@class_globals[name.to_s] = value
|
|
444
|
+
@@class_globals[name.to_s] = wrap_global(value) # ADR-0085
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# Wrap a bare zero-argument Proc/lambda/Method so a bare reference to it
|
|
448
|
+
# auto-calls (ADR-0085). Node auto-calls any function reached by a bare
|
|
449
|
+
# reference and uses the return value; Ruby stored the proc instead, so
|
|
450
|
+
# `{% if admin_only %}` with `add_global("admin_only", -> { ... })` was
|
|
451
|
+
# always truthy. A callable that declares parameters is left unwrapped and
|
|
452
|
+
# behaves exactly as before; anything that is not a Proc/Method is untouched
|
|
453
|
+
# (matching Node, which only auto-calls plain functions).
|
|
454
|
+
def self.wrap_global(value)
|
|
455
|
+
if (value.is_a?(Proc) || value.is_a?(Method)) && value.arity.zero?
|
|
456
|
+
AutoGlobal.new(value)
|
|
457
|
+
else
|
|
458
|
+
value
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
# Marker for a zero-argument callable global (see wrap_global). `resolve`
|
|
463
|
+
# invokes it once for a bare reference; `admin_only()` still calls it via
|
|
464
|
+
# the ordinary function-call path (`call` forwards).
|
|
465
|
+
class AutoGlobal
|
|
466
|
+
def initialize(fn)
|
|
467
|
+
@fn = fn
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def call(*args)
|
|
471
|
+
@fn.call(*args)
|
|
472
|
+
end
|
|
445
473
|
end
|
|
446
474
|
|
|
447
475
|
# Register a custom filter.
|
|
@@ -465,7 +493,7 @@ module Tina4
|
|
|
465
493
|
#
|
|
466
494
|
# Updates this instance's live globals map only.
|
|
467
495
|
def add_global(name, value)
|
|
468
|
-
@globals[name.to_s] = value
|
|
496
|
+
@globals[name.to_s] = self.class.wrap_global(value) # ADR-0085
|
|
469
497
|
self
|
|
470
498
|
end
|
|
471
499
|
|
|
@@ -2311,7 +2339,10 @@ module Tina4
|
|
|
2311
2339
|
return nil if value.nil?
|
|
2312
2340
|
end
|
|
2313
2341
|
|
|
2314
|
-
|
|
2342
|
+
# ADR-0085: a bare reference to a zero-argument callable global uses its
|
|
2343
|
+
# return value. `g()` never reaches here as a marker -- the function-call
|
|
2344
|
+
# path invokes it directly -- so each global is called at most once.
|
|
2345
|
+
value.is_a?(AutoGlobal) ? value.call : value
|
|
2315
2346
|
end
|
|
2316
2347
|
|
|
2317
2348
|
# -----------------------------------------------------------------------
|
data/lib/tina4/realtime.rb
CHANGED
|
@@ -114,8 +114,17 @@ module Tina4
|
|
|
114
114
|
|
|
115
115
|
# ----- route registration -------------------------------------------------
|
|
116
116
|
|
|
117
|
+
# ADR-0101: the config route mints TURN credentials and the signalling socket
|
|
118
|
+
# relays calls, so both require a valid token UNLESS the app opts out with
|
|
119
|
+
# TINA4_RTC_REQUIRE_AUTH=false (anonymous calling). Secure-by-default: only an
|
|
120
|
+
# explicit false/0/no/off opens them.
|
|
121
|
+
def rtc_require_auth?
|
|
122
|
+
raw = (ENV["TINA4_RTC_REQUIRE_AUTH"] || "true").strip.downcase
|
|
123
|
+
!%w[false 0 no off].include?(raw)
|
|
124
|
+
end
|
|
125
|
+
|
|
117
126
|
def register_config(paths, features)
|
|
118
|
-
Tina4::Router.get(paths["config"]) do |_request, response|
|
|
127
|
+
route = Tina4::Router.get(paths["config"]) do |_request, response|
|
|
119
128
|
body = { "backend" => "mesh" }
|
|
120
129
|
if features.include?("calls")
|
|
121
130
|
body["iceServers"] = Tina4::Realtime.ice_servers
|
|
@@ -128,12 +137,14 @@ module Tina4
|
|
|
128
137
|
body["files"] = paths["files"] if features.include?("files")
|
|
129
138
|
response.json(body, Tina4::HTTP_OK)
|
|
130
139
|
end
|
|
140
|
+
route.secure if rtc_require_auth? # ADR-0101
|
|
131
141
|
end
|
|
132
142
|
|
|
133
143
|
def register_calls(paths)
|
|
134
144
|
# WebRTC signalling relay (mesh). Tina4 never parses the SDP; peers filter
|
|
135
|
-
# by `to`. The
|
|
136
|
-
|
|
145
|
+
# by `to`. The media is E2E between peers; the signalling room requires auth
|
|
146
|
+
# by default (ADR-0101) unless TINA4_RTC_REQUIRE_AUTH=false.
|
|
147
|
+
route = Tina4::Router.websocket("#{paths['signalling']}/{room}") do |connection, event, data|
|
|
137
148
|
room = connection.params[:room].to_s
|
|
138
149
|
next if room.empty?
|
|
139
150
|
|
|
@@ -145,6 +156,7 @@ module Tina4
|
|
|
145
156
|
connection.broadcast_to_room(key, data, exclude_self: true)
|
|
146
157
|
end
|
|
147
158
|
end
|
|
159
|
+
route.secure if rtc_require_auth? # ADR-0101
|
|
148
160
|
end
|
|
149
161
|
|
|
150
162
|
def register_chat(paths)
|
data/lib/tina4/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tina4ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.13.
|
|
4
|
+
version: 3.13.154
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tina4 Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sqlite3
|