monkrb 0.18.0 → 0.19.0
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 +77 -0
- data/README.md +2 -2
- data/lib/monk/live/client/monk_live.js +2 -2
- data/lib/monk/live/client/protocol.js +5 -0
- data/lib/monk/live.rb +10 -0
- data/lib/monk/scaffold.rb +152 -77
- data/lib/monk/templates/auth/app/mailers/app_mailer.rb +22 -0
- data/lib/monk/templates/auth/config/auth.rb +7 -20
- data/lib/monk/templates/base/app/app.rb +9 -0
- data/lib/monk/templates/base/app/broadcasts/.keep +0 -0
- data/lib/monk/templates/base/app/helpers/.keep +0 -0
- data/lib/monk/templates/base/app/jobs/.keep +0 -0
- data/lib/monk/templates/base/app/mailers/.keep +0 -0
- data/lib/monk/templates/base/app/models/.keep +0 -0
- data/lib/monk/templates/base/app/presenters/.keep +0 -0
- data/lib/monk/templates/base/bin/websocket_server +6 -0
- data/lib/monk/templates/base/config/load.rb +18 -0
- data/lib/monk/templates/base/config.ru +3 -12
- data/lib/monk/templates/base/public/js/app.js +1 -1
- data/lib/monk/templates/jobs/{jobs → app/jobs}/send_login_link.rb +2 -1
- data/lib/monk/templates/jobs/bin/jobs +5 -13
- data/lib/monk/templates/jobs/config/jobs.rb +2 -4
- data/lib/monk/templates/live/{config.ru → app/app.rb} +1 -6
- data/lib/monk/templates/live/{views → app/views}/index.erb +2 -2
- data/lib/monk/templates/live/bin/websocket_server +14 -2
- data/lib/monk/templates/live/config/live.rb +1 -1
- data/lib/monk/templates/live/config/live_pg.rb +1 -1
- data/lib/monk/templates/mail/config/mail.rb +2 -2
- data/lib/monk/templates/postgres/bin/console +4 -2
- data/lib/monk/version.rb +1 -1
- data/lib/monk/websocket/errors.rb +11 -0
- data/lib/monk/websocket/listeners.rb +23 -0
- data/lib/monk/websocket/pg_fanout.rb +55 -20
- data/lib/monk/websocket/redis_fanout.rb +57 -14
- data/lib/monk/websocket/registry.rb +6 -0
- data/lib/monk/websocket/server.rb +77 -17
- metadata +19 -9
- /data/lib/monk/templates/auth/{views → app/views}/mail/magic_link.erb +0 -0
- /data/lib/monk/templates/base/{views → app/views}/index.erb +0 -0
- /data/lib/monk/templates/base/{views → app/views}/layouts/app.erb +0 -0
- /data/lib/monk/templates/jobs/{jobs → app/jobs}/hello_job.rb +0 -0
- /data/lib/monk/templates/live/{views → app/views}/live/_hits.erb +0 -0
|
@@ -25,9 +25,20 @@ module Monk
|
|
|
25
25
|
# docs/history/chat-gap-analysis.md) -- authenticate: true only checks the
|
|
26
26
|
# credential once, at the handshake, so a session revoked or
|
|
27
27
|
# expired afterward would otherwise leave the socket live
|
|
28
|
-
# indefinitely. Requires authenticate: true (there's no
|
|
29
|
-
# to reverify without it) and defaults to nil (disabled),
|
|
30
|
-
# ping_interval:.
|
|
28
|
+
# indefinitely. Requires authenticate: true or :optional (there's no
|
|
29
|
+
# credential to reverify without it) and defaults to nil (disabled),
|
|
30
|
+
# same as ping_interval:. Under :optional it watches only the
|
|
31
|
+
# connections that have a session.
|
|
32
|
+
#
|
|
33
|
+
# authenticate: :optional is the middle mode (docs/design/websocket.md,
|
|
34
|
+
# "Anonymous connections"): a connection with a valid session gets its
|
|
35
|
+
# subject, and one without -- no credential, or a revoked or expired
|
|
36
|
+
# one -- comes in anonymous (subject nil) instead of being refused.
|
|
37
|
+
# What an anonymous connection may then do is the handler's call:
|
|
38
|
+
# Monk::Live's rules deny it everything a rule doesn't open with
|
|
39
|
+
# `anonymous: true`. The Origin check still applies: to a cookie, as
|
|
40
|
+
# under true, and to an anonymous connection that sends an Origin,
|
|
41
|
+
# since only a browser does.
|
|
31
42
|
#
|
|
32
43
|
# max_payload_size: (bytes) -- see Connection::DEFAULT_MAX_PAYLOAD_SIZE
|
|
33
44
|
# for what it guards against (gap 5). Unlike ping_interval:/
|
|
@@ -35,6 +46,11 @@ module Monk
|
|
|
35
46
|
# hostile client on a public endpoint, not an opt-in behavior
|
|
36
47
|
# change, so a server that doesn't mention it still gets the
|
|
37
48
|
# default cap rather than no cap at all.
|
|
49
|
+
AUTHENTICATE_MODES = [false, true, :optional].freeze
|
|
50
|
+
|
|
51
|
+
# The signals #run treats as "stop": Ctrl+C and TERM.
|
|
52
|
+
STOP_SIGNALS = [Signal.list.fetch("INT"), Signal.list.fetch("TERM")].freeze
|
|
53
|
+
|
|
38
54
|
def initialize(
|
|
39
55
|
port:, bind: "0.0.0.0", allowed_origins: nil, authenticate: false, ping_interval: nil,
|
|
40
56
|
reverify_interval: nil, max_payload_size: Connection::DEFAULT_MAX_PAYLOAD_SIZE
|
|
@@ -43,11 +59,15 @@ module Monk
|
|
|
43
59
|
raise ArgumentError, "ping_interval must be positive, got #{ping_interval.inspect}"
|
|
44
60
|
end
|
|
45
61
|
|
|
62
|
+
unless AUTHENTICATE_MODES.include?(authenticate)
|
|
63
|
+
raise ArgumentError, "authenticate must be false, true or :optional, got #{authenticate.inspect}"
|
|
64
|
+
end
|
|
65
|
+
|
|
46
66
|
if reverify_interval
|
|
47
67
|
unless reverify_interval.positive?
|
|
48
68
|
raise ArgumentError, "reverify_interval must be positive, got #{reverify_interval.inspect}"
|
|
49
69
|
end
|
|
50
|
-
raise ArgumentError, "reverify_interval requires authenticate: true" unless authenticate
|
|
70
|
+
raise ArgumentError, "reverify_interval requires authenticate: true or :optional" unless authenticate
|
|
51
71
|
end
|
|
52
72
|
|
|
53
73
|
unless max_payload_size.positive?
|
|
@@ -57,8 +77,8 @@ module Monk
|
|
|
57
77
|
if authenticate
|
|
58
78
|
unless defined?(Monk::Auth) && Monk::Auth.config
|
|
59
79
|
raise Monk::AuthNotConfiguredError,
|
|
60
|
-
"Monk::WebSocket::Server.new(authenticate:
|
|
61
|
-
"configured -- call Monk::Auth.configure first"
|
|
80
|
+
"Monk::WebSocket::Server.new(authenticate: #{authenticate.inspect}) requires Monk::Auth to " \
|
|
81
|
+
"already be configured -- call Monk::Auth.configure first"
|
|
62
82
|
end
|
|
63
83
|
|
|
64
84
|
# Monk::Auth's config is a plain, unfrozen Hash until this runs
|
|
@@ -107,15 +127,22 @@ module Monk
|
|
|
107
127
|
end
|
|
108
128
|
ractor.send(socket, move: true)
|
|
109
129
|
end
|
|
110
|
-
rescue
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
130
|
+
rescue SignalException => e
|
|
131
|
+
raise unless STOP_SIGNALS.include?(e.signo)
|
|
132
|
+
|
|
133
|
+
# Ctrl+C (INT, raised as Interrupt) and TERM (what docker stop,
|
|
134
|
+
# Kubernetes, Fly, Render and systemd send on every deploy) both
|
|
135
|
+
# land here: Ruby raises them in the thread blocked in
|
|
136
|
+
# TCPServer#accept. Unrescued, INT prints a backtrace and TERM ends
|
|
137
|
+
# the process by the signal (exit 143) -- either reads as a crash
|
|
138
|
+
# though it's the normal way to stop a long-running server. Here
|
|
139
|
+
# the server stops accepting and #run returns, so the script ends
|
|
140
|
+
# with exit 0, as bin/server and bin/jobs do. Open connections are
|
|
141
|
+
# dropped when the process exits, with no close frame
|
|
142
|
+
# (docs/design/websocket.md, "Stopping the server").
|
|
143
|
+
# Registry#ask's Ractor::ClosedError rescue (registry.rb) guards a
|
|
144
|
+
# different failure point in this same shutdown -- a live
|
|
145
|
+
# connection's cleanup racing the registry Ractor's teardown.
|
|
119
146
|
@tcp_server.close
|
|
120
147
|
end
|
|
121
148
|
|
|
@@ -138,9 +165,14 @@ module Monk
|
|
|
138
165
|
end
|
|
139
166
|
|
|
140
167
|
subject = nil
|
|
141
|
-
|
|
168
|
+
case authenticate
|
|
169
|
+
when true
|
|
142
170
|
subject = authenticate!(socket, headers, allowed_origins)
|
|
143
171
|
return unless subject
|
|
172
|
+
when :optional
|
|
173
|
+
return unless origin_allowed_for_optional?(socket, headers, allowed_origins)
|
|
174
|
+
|
|
175
|
+
subject = identify(headers)
|
|
144
176
|
end
|
|
145
177
|
|
|
146
178
|
socket.write(Handshake.response_for(request))
|
|
@@ -148,7 +180,7 @@ module Monk
|
|
|
148
180
|
connection = Connection.new(socket, subject: subject, max_payload_size: max_payload_size)
|
|
149
181
|
connection.start_heartbeat(ping_interval) if ping_interval
|
|
150
182
|
reverify_thread =
|
|
151
|
-
if reverify_interval
|
|
183
|
+
if reverify_interval && subject
|
|
152
184
|
start_reverify(connection, Handshake.credential_from(headers)[:token], reverify_interval)
|
|
153
185
|
end
|
|
154
186
|
begin
|
|
@@ -194,6 +226,34 @@ module Monk
|
|
|
194
226
|
end
|
|
195
227
|
private_class_method :authenticate!
|
|
196
228
|
|
|
229
|
+
# authenticate: :optional's Origin check, before anything is verified.
|
|
230
|
+
# A Bearer connection is exempt, as under true. A cookie needs an
|
|
231
|
+
# allowed Origin, as under true. An anonymous connection that sends an
|
|
232
|
+
# Origin needs an allowed one too: only a browser sends it, and this
|
|
233
|
+
# stops another site's page from opening sockets here on its
|
|
234
|
+
# visitors' behalf; one that sends none (not a browser) gets in.
|
|
235
|
+
# Writes the 403 itself and returns false when refused.
|
|
236
|
+
def self.origin_allowed_for_optional?(socket, headers, allowed_origins)
|
|
237
|
+
return true unless allowed_origins
|
|
238
|
+
|
|
239
|
+
credential = Handshake.credential_from(headers)
|
|
240
|
+
return true if credential&.fetch(:via) == :bearer
|
|
241
|
+
|
|
242
|
+
origin = headers["origin"]
|
|
243
|
+
allowed = credential ? origin && allowed_origins.include?(origin) : origin.nil? || allowed_origins.include?(origin)
|
|
244
|
+
socket.write("HTTP/1.1 403 Forbidden\r\n\r\n") unless allowed
|
|
245
|
+
allowed
|
|
246
|
+
end
|
|
247
|
+
private_class_method :origin_allowed_for_optional?
|
|
248
|
+
|
|
249
|
+
# The subject of a valid session, or nil -- no credential, or one
|
|
250
|
+
# Monk::Auth no longer accepts, both mean anonymous under :optional.
|
|
251
|
+
def self.identify(headers)
|
|
252
|
+
credential = Handshake.credential_from(headers)
|
|
253
|
+
credential && Monk::Auth.verify(credential[:token])
|
|
254
|
+
end
|
|
255
|
+
private_class_method :identify
|
|
256
|
+
|
|
197
257
|
# Sleeps in `interval`-sized steps, re-running Monk::Auth.verify
|
|
198
258
|
# against the same raw credential each time, until it comes back
|
|
199
259
|
# nil (revoked or expired) -- then closes the connection with the
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: monkrb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.19.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matteo Folin
|
|
@@ -215,35 +215,44 @@ files:
|
|
|
215
215
|
- lib/monk/scaffold.rb
|
|
216
216
|
- lib/monk/settings.rb
|
|
217
217
|
- lib/monk/state_ractor.rb
|
|
218
|
+
- lib/monk/templates/auth/app/mailers/app_mailer.rb
|
|
219
|
+
- lib/monk/templates/auth/app/views/mail/magic_link.erb
|
|
218
220
|
- lib/monk/templates/auth/config/auth.rb
|
|
219
221
|
- lib/monk/templates/auth/db/migrate/00000000000001_create_auth_tables.down.sql
|
|
220
222
|
- lib/monk/templates/auth/db/migrate/00000000000001_create_auth_tables.up.sql
|
|
221
|
-
- lib/monk/templates/auth/views/mail/magic_link.erb
|
|
222
223
|
- lib/monk/templates/base/.dockerignore
|
|
223
224
|
- lib/monk/templates/base/.gitignore
|
|
224
225
|
- lib/monk/templates/base/.ruby-version
|
|
225
226
|
- lib/monk/templates/base/Dockerfile
|
|
226
227
|
- lib/monk/templates/base/Gemfile
|
|
228
|
+
- lib/monk/templates/base/app/app.rb
|
|
229
|
+
- lib/monk/templates/base/app/broadcasts/.keep
|
|
230
|
+
- lib/monk/templates/base/app/helpers/.keep
|
|
231
|
+
- lib/monk/templates/base/app/jobs/.keep
|
|
232
|
+
- lib/monk/templates/base/app/mailers/.keep
|
|
233
|
+
- lib/monk/templates/base/app/models/.keep
|
|
234
|
+
- lib/monk/templates/base/app/presenters/.keep
|
|
235
|
+
- lib/monk/templates/base/app/views/index.erb
|
|
236
|
+
- lib/monk/templates/base/app/views/layouts/app.erb
|
|
227
237
|
- lib/monk/templates/base/bin/server
|
|
228
238
|
- lib/monk/templates/base/bin/websocket_server
|
|
229
239
|
- lib/monk/templates/base/config.ru
|
|
240
|
+
- lib/monk/templates/base/config/load.rb
|
|
230
241
|
- lib/monk/templates/base/config/settings.rb
|
|
231
242
|
- lib/monk/templates/base/public/css/app.css
|
|
232
243
|
- lib/monk/templates/base/public/js/app.js
|
|
233
|
-
- lib/monk/templates/
|
|
234
|
-
- lib/monk/templates/
|
|
244
|
+
- lib/monk/templates/jobs/app/jobs/hello_job.rb
|
|
245
|
+
- lib/monk/templates/jobs/app/jobs/send_login_link.rb
|
|
235
246
|
- lib/monk/templates/jobs/bin/jobs
|
|
236
247
|
- lib/monk/templates/jobs/config/jobs.rb
|
|
237
248
|
- lib/monk/templates/jobs/db/migrate/00000000000002_create_jobs_tables.down.sql
|
|
238
249
|
- lib/monk/templates/jobs/db/migrate/00000000000002_create_jobs_tables.up.sql
|
|
239
|
-
- lib/monk/templates/
|
|
240
|
-
- lib/monk/templates/
|
|
250
|
+
- lib/monk/templates/live/app/app.rb
|
|
251
|
+
- lib/monk/templates/live/app/views/index.erb
|
|
252
|
+
- lib/monk/templates/live/app/views/live/_hits.erb
|
|
241
253
|
- lib/monk/templates/live/bin/websocket_server
|
|
242
|
-
- lib/monk/templates/live/config.ru
|
|
243
254
|
- lib/monk/templates/live/config/live.rb
|
|
244
255
|
- lib/monk/templates/live/config/live_pg.rb
|
|
245
|
-
- lib/monk/templates/live/views/index.erb
|
|
246
|
-
- lib/monk/templates/live/views/live/_hits.erb
|
|
247
256
|
- lib/monk/templates/mail/Gemfile.extra
|
|
248
257
|
- lib/monk/templates/mail/config/mail.rb
|
|
249
258
|
- lib/monk/templates/postgres/Dockerfile
|
|
@@ -260,6 +269,7 @@ files:
|
|
|
260
269
|
- lib/monk/websocket/errors.rb
|
|
261
270
|
- lib/monk/websocket/frame.rb
|
|
262
271
|
- lib/monk/websocket/handshake.rb
|
|
272
|
+
- lib/monk/websocket/listeners.rb
|
|
263
273
|
- lib/monk/websocket/pg_fanout.rb
|
|
264
274
|
- lib/monk/websocket/redis_fanout.rb
|
|
265
275
|
- lib/monk/websocket/registry.rb
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|