clowk 0.7.1 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33d1a92eb50a3515f9c312ccd3ba71dbc9bedfd11935797f70497aa076dbb55b
4
- data.tar.gz: 38aa689ff1303246487acaad73cf02c34955bafa9a44bc44544b6ce289e758a3
3
+ metadata.gz: a1fa17ef38ffc2cce253e598ab2c2478ba05f5f7c638601ec833bafa00377ebc
4
+ data.tar.gz: 396b0e5c36dcaa01a10397f6227af24dcdf5274a06145222da54855aba71a076
5
5
  SHA512:
6
- metadata.gz: b334fda452268ee1ae58a897ddbd07dfe6c0f715f4a2693ddadffbeb882af3d906bf4e76ce655160eb5c204c2de6d175c596ecc182b4261ef9edfc21c31f6b51
7
- data.tar.gz: 4516f8fdc4dd79291fa895c2a203d7920b3d44ee69bf89f1a8c3f11877f65e36d1e8dd70b67b5a99c49462e15c07d33f25b42d85466fd87f44e2a3244745248e
6
+ metadata.gz: 1fd3f7de008dd50644494e85e0771113ae98be1efdc1abcae7d97888b6af84f7aa2f5fd086aa711d3be4b2984b0a626c9bdddc5d66c6c8bc41955b21325c3717
7
+ data.tar.gz: 36067b774a1d57d833909bcee4316d29a25ee5a907b03063f8ee52545adbf36b09a9857b4b374065a60956f2a5a386044138592b9643e0cf5a7b2f48dec50da8
data/README.md CHANGED
@@ -239,15 +239,18 @@ those actions and they get a live one:
239
239
 
240
240
  ```ruby
241
241
  class ApiKeysController < ApplicationController
242
- clowk_require_fresh_session only: [:create, :update, :destroy]
242
+ before_action :clowk_enforce_fresh_session!, only: [:create, :update, :destroy]
243
243
  end
244
244
  ```
245
245
 
246
- It takes the same options as `before_action`. Everything not named keeps the
247
- cached check, so an app pays for the round trip on the few actions it cannot
248
- undo and nowhere else. `clowk_enforce_fresh_session!` is the same thing as a
249
- method, and `clowk_session_active?(force: true)` returns the answer instead of
250
- enforcing it.
246
+ An ordinary `before_action`, so the filter options you already know all work.
247
+ Everything not named keeps the cached check, so an app pays for the round trip on
248
+ the few actions it cannot undo and nowhere else.
249
+
250
+ Under a configured `prefix_by` the method is named for the scope, like every
251
+ other one here — `clowk_user_enforce_fresh_session!` beside
252
+ `clowk_user_enforce_session!`. And `clowk_session_active?(force: true)` returns
253
+ the answer instead of enforcing it.
251
254
 
252
255
  Two settings decide what happens when Clowk itself cannot be reached:
253
256
 
@@ -269,6 +272,31 @@ with no round trip at all.
269
272
  Both ends — the broker said inactive, the ceiling passed — go through
270
273
  `config.on_session_expired` when you set one.
271
274
 
275
+ ### Where the token is kept
276
+
277
+ Clowk writes the token to its own cookie, and — by default, as every version
278
+ before 0.9 did — mirrors a copy into the app's Rails session.
279
+
280
+ ```ruby
281
+ config.token_store = :cookie # Clowk's cookie only
282
+ config.token_store = :session # also mirrored into the session (the default)
283
+ ```
284
+
285
+ The copy is not free. A Rails session lives in **one** cookie with about 4096
286
+ bytes to its name, and in production, where tokens are RS256, the token is most
287
+ of what a session weighs. Hand a browser more than it will hold and it discards
288
+ the whole cookie in silence — no error server-side, none in the console, the
289
+ previous cookie simply stays. Everything written on that request goes with it: a
290
+ flash message, a selected tenant, a CSRF rotation. What a person sees is a button
291
+ that does nothing.
292
+
293
+ Under `:cookie` the session keeps the claims and the sign-in time, nothing else.
294
+ `current_token` reads Clowk's cookie instead, which is where an API-only app has
295
+ always read it from. Sessions written before the switch are pruned on their next
296
+ request, so an app does not have to wait for everyone to sign out.
297
+
298
+ The default stays `:session` so an upgrade changes nothing until you ask.
299
+
272
300
  ### Token verification
273
301
 
274
302
  Tokens signed with `RS256` are verified against Clowk's public key set, fetched
@@ -67,22 +67,6 @@ module Clowk
67
67
  Clowk::Authenticable.install_dynamic_methods(self)
68
68
  end
69
69
 
70
- class_methods do
71
- # Demand a live answer from Clowk before these actions, whatever a cached
72
- # status says.
73
- #
74
- # class ApiKeysController < ApplicationController
75
- # clowk_require_fresh_session only: [:create, :update, :destroy]
76
- # end
77
- #
78
- # Takes the same options as before_action. Everything NOT listed keeps the
79
- # cached check, which is the point: an app pays for a round trip on the few
80
- # actions that cannot be undone, and nowhere else.
81
- def clowk_require_fresh_session(**options)
82
- before_action(**options) { clowk_enforce_fresh_session! }
83
- end
84
- end
85
-
86
70
  # Per-request credentials — for apps whose keys are not a boot constant:
87
71
  # an operator pastes a publishable key into a settings screen, or one
88
72
  # process serves several tenants.
@@ -106,10 +90,17 @@ module Clowk
106
90
  def clowk_current_resource
107
91
  @clowk_current_resource ||= begin
108
92
  payload = stored_user_payload || verified_request_payload
93
+
94
+ drop_mirrored_token!
95
+
109
96
  payload ? Current.new(payload) : nil
110
97
  end
111
98
  end
112
99
 
100
+ # The session first, which holds a copy only under token_store :session —
101
+ # and, for one request after an app switches to :cookie, in a session
102
+ # written before the switch. drop_mirrored_token! clears those. Clowk's own
103
+ # cookie is the source either way.
113
104
  def current_token
114
105
  stored_session&.dig("token") || extracted_token
115
106
  end
@@ -143,7 +134,10 @@ module Clowk
143
134
  # rotating a secret, deleting an account, removing a member. Everything else
144
135
  # should take the cached check — this is a round trip, on purpose.
145
136
  #
146
- # clowk_require_fresh_session only: [:destroy, :rotate_secret]
137
+ # before_action :clowk_enforce_fresh_session!, only: [:destroy]
138
+ #
139
+ # Under a configured prefix_by it is named for the scope, like every other
140
+ # method here: `clowk_user_enforce_fresh_session!`.
147
141
  #
148
142
  # Before 0.7 the only way to get this was `session_status_ttl = 0`, which
149
143
  # bought freshness here by paying a round trip on every page instead.
@@ -324,11 +318,10 @@ module Clowk
324
318
  store = clowk_session_store
325
319
  return if store.nil?
326
320
 
327
- store[Clowk.config.session_key] = {
328
- token:,
329
- user: payload,
330
- signed_in_at: Time.now.to_i
331
- }
321
+ claims = {user: payload, signed_in_at: Time.now.to_i}
322
+
323
+ store[Clowk.config.session_key] =
324
+ (Clowk.config.token_store == :cookie) ? claims : claims.merge(token:)
332
325
 
333
326
  clowk_cookie_jar&.[]=(Clowk.config.cookie_key, {
334
327
  value: token,
@@ -338,6 +331,23 @@ module Clowk
338
331
  })
339
332
  end
340
333
 
334
+ # A session written under :session still carries the token, and
335
+ # persist_clowk_session does not run again while that session stands — so
336
+ # without this, switching an app to :cookie would shrink nothing until every
337
+ # person signed out and back in. Drops the copy once, on the first request
338
+ # after the switch.
339
+ def drop_mirrored_token!
340
+ return unless Clowk.config.token_store == :cookie
341
+
342
+ store = clowk_session_store
343
+ held = stored_session
344
+
345
+ return if store.nil? || held.nil?
346
+ return unless held.key?("token") || held.key?(:token)
347
+
348
+ store[Clowk.config.session_key] = held.except("token", :token)
349
+ end
350
+
341
351
  def resolve_session_status(force: false)
342
352
  @clowk_session_check_unavailable = false
343
353
  cached = force ? nil : clowk_read_cached_session_status
@@ -27,6 +27,7 @@ module Clowk
27
27
  attr_accessor :session_status_ttl
28
28
  attr_accessor :max_session_age
29
29
  attr_accessor :fail_open_on_broker_error
30
+ attr_accessor :token_store
30
31
  attr_writer :session_status_cache
31
32
 
32
33
  def initialize
@@ -64,6 +65,25 @@ module Clowk
64
65
  # cannot be made at all, the session is left standing and checked again on
65
66
  # the next request; max_session_age is what bounds that.
66
67
  @fail_open_on_broker_error = true
68
+
69
+ # Where the token is kept for the next request: :session or :cookie.
70
+ #
71
+ # Clowk's own cookie is written either way — it is what `current_token`
72
+ # reads when the session has none, and the only place an API-only app has
73
+ # ever had. The setting decides whether a COPY is also mirrored into the
74
+ # app's Rails session, as every version before 0.9 did.
75
+ #
76
+ # That copy is not free. A Rails session lives in ONE cookie with about
77
+ # 4096 bytes to its name, and in production, where tokens are RS256, the
78
+ # token is most of what a session weighs. Hand a browser more than it will
79
+ # hold and it discards the whole cookie in silence: no error server-side,
80
+ # none in the console, the previous cookie simply stays. Everything written
81
+ # on that request goes with it — a flash message, a selected tenant — which
82
+ # reads as a button that does nothing.
83
+ #
84
+ # :session stays the default so an upgrade changes nothing until an app
85
+ # asks for :cookie.
86
+ @token_store = :session
67
87
  end
68
88
 
69
89
  # Where API-only apps cache session status, since they have no Rails session
data/lib/clowk/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clowk
4
- VERSION = "0.7.1"
4
+ VERSION = "0.9.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clowk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clowk