roda 3.9.0 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 39ce926351959abb8a72a7b1c9db596dff7e0e84c26c296db4d2b3c124486356
4
- data.tar.gz: 904ab94da8c67bfd2d4acb5246fa0a3049c89778e2fbc9e1701f6f342f39329a
3
+ metadata.gz: 0fa178463d549cd2a8826a8601eccc793e2a3b5a6bb34293ce8511fb8d357128
4
+ data.tar.gz: 5b099180432b7efc3bc4839883367a454c876d9a2e6809f533f970e83bd32d0d
5
5
  SHA512:
6
- metadata.gz: 590cc6b72c9fbb3fc01e389a6c12a5d09cd0db1cf56fe0d821b79e0e3aaf1d91ca7598d25fbdbdc50b25171f79501087599bd334ce079ad435ee51be31988588
7
- data.tar.gz: 8bcde66bd4ab812a963ace04db8b026df5d5b5c9d79432de4b5780da80f9548f1dc92681e2fe0bdd1330d20f526cab4cea27b4bd0d54dddedbc8b5a9ec864c77
6
+ metadata.gz: e7ec971ff829c784c21c9660b7964f79755841501449c6372260510a367482a9dde5131bf6b488eadefdd24403f7a19576c8d1176d8d8306fecc861388fb9f7f
7
+ data.tar.gz: 7fe25ba12cceccf1b59d9993d4972c0ffb2df2b78b68716f7708ecc58d131bc0858d9b7a1b87ac34bb88a05b57aeb6f0fb68beac648bbdfe2f7c0c3c6933def9
data/CHANGELOG CHANGED
@@ -1,3 +1,21 @@
1
+ = 3.10.0 (2018-07-18)
2
+
3
+ * Remove flash key from session if new flash is empty when rotating flash (jeremyevans)
4
+
5
+ * Speed up RodaRequest initialization by avoiding 1-2 method calls (jeremyevans)
6
+
7
+ * Add roda/session_middleware (RodaSessionMiddleware), usable as a middleware by any Rack app to use Roda's session support (jeremyevans)
8
+
9
+ * Add sessions plugin for more secure (encrypted+signed) sessions (jeremyevans)
10
+
11
+ * Support :json_parser and :json_serializer application options as default implementations for parsing/serializing JSON (jeremyevans)
12
+
13
+ * Add :handle_result option to middleware plugin for modifying rack result before returning it (jeremyevans)
14
+
15
+ * Make the flash plugin work correctly when sessions are serialized with JSON (jeremyevans)
16
+
17
+ * Make Integer in typecast_params handle Numeric input, and require that Numeric input not have fractional parts (jeremyevans) (#146)
18
+
1
19
  = 3.9.0 (2018-06-11)
2
20
 
3
21
  * Add route_csrf plugin for CSRF protection, offering more control, better security, and request-specific tokens compared to rack_csrf (jeremyevans)
@@ -699,8 +699,15 @@ The following options are respected by the default library or multiple plugins:
699
699
  :add_script_name :: Prepend the SCRIPT_NAME for the request to paths. This is
700
700
  useful if you mount the app as a path under another app.
701
701
  :freeze_middleware :: Whether to freeze all middleware when building the rack app.
702
+ :json_parser :: A callable for parsing JSON (+JSON.parse+ in general used by
703
+ default).
704
+ :json_serializer :: A callable for serializing JSON (+to_json+ in general used
705
+ by default).
702
706
  :root :: Set the root path for the app. This defaults to the current working
703
707
  directory of the process.
708
+ :sessions_convert_symbols :: This should be set to +true+ if the sessions in use
709
+ do not support roundtripping of symbols (for
710
+ example, when sessions are serialized via JSON).
704
711
 
705
712
  There may be other options supported by individual plugins, if so it will be
706
713
  mentioned in the documentation for the plugin.
@@ -753,38 +760,51 @@ You can override the default rendering options by passing a hash to the plugin:
753
760
  template_opts: {default_encoding: 'UTF-8'} # Default template options
754
761
  end
755
762
 
756
- == Sessions
763
+ == Security
764
+
765
+ Web application security is a very large topic,
766
+ but here are some things you can do with Roda
767
+ to prevent some common web application vulnerabilities.
768
+
769
+ === Session Security
757
770
 
758
- By default, Roda doesn't turn on sessions,
759
- but most users are going to want to turn on session support.
760
- The simplest way to do this is to use the <tt>Rack::Session::Cookie</tt> middleware
761
- that comes with Rack:
771
+ By default, Roda doesn't turn on sessions, and if you don't need sessions, you can
772
+ skip this section. If you do need sessions, Roda offers two recommended ways to
773
+ implement cookie-based sessions.
762
774
 
763
- require "roda"
775
+ If you do not need any session support in middleware, and only need session support
776
+ in the Roda application, then use the sessions plugin:
764
777
 
778
+ require 'roda'
765
779
  class App < Roda
766
- use Rack::Session::Cookie, secret: ENV['SECRET']
780
+ plugin :sessions, secret: ENV['SESSION_SECRET']
767
781
  end
768
782
 
769
- == Security
783
+ The +:secret+ option should be a randomly generated string of at least 64 bytes.
770
784
 
771
- Web application security is a very large topic,
772
- but here are some things you can do with Roda
773
- to prevent some common web application vulnerabilities.
785
+ If you have middleware that need access to sessions, then use the +RodaSessionMiddleware+
786
+ that ships with Roda:
774
787
 
775
- === Session Security
788
+ require 'roda'
789
+ require 'roda/session_middleware'
790
+ class App < Roda
791
+ use RodaSessionMiddleware, secret: ENV['SESSION_SECRET']
792
+ end
776
793
 
777
- If you are using sessions, you should also always set a session secret,
778
- using the +:secret+ option as shown above.
779
- Make sure that this secret is not disclosed,
780
- because if an attacker knows the +:secret+ value,
781
- they can inject arbitrary session values.
782
- In the worst case scenario, this can lead to remote code execution.
783
-
784
- Keep in mind that with <tt>Rack::Session::Cookie</tt>,
785
- the content in the session cookie is not encrypted,
786
- just signed to prevent tampering.
787
- This means you should not store any secret data in the session.
794
+ If you need non-cookie based sessions (such as sessions stored in a database), you
795
+ should use an appropriate external middleware.
796
+
797
+ It is possible to use other session cookie middleware such as
798
+ <tt>Rack::Session::Cookie</tt>, but other middleware may not have the same security
799
+ features that Roda's session support does. For example, the session cookies used by
800
+ the <tt>Rack::Session::Cookie</tt> middleware are not encrypted, just signed to
801
+ prevent tampering. This means you should not store any secret data in the session
802
+ when using <tt>Rack::Session::Cookie</tt>.
803
+
804
+ For any cookie-based sessions, make sure that the necessary secrets (+:secret+ option)
805
+ are not disclosed to an attacker. Knowledge of the
806
+ secret(s) can allow an attacker to inject arbitrary session values. In the case of
807
+ <tt>Rack::Session::Cookie</tt>, that can also lead remote code execution.
788
808
 
789
809
  === Cross Site Request Forgery (CSRF)
790
810
 
@@ -0,0 +1,132 @@
1
+ = New Features
2
+
3
+ * A sessions plugin has been added that supports encrypted and
4
+ signed sessions. This plugin is now the recommended way to
5
+ implement sessions, replacing the previously recommended
6
+ Rack::Session::Cookie middleware.
7
+
8
+ The sessions plugin encrypts session data using the AES-256-CTR
9
+ cipher, and then signs the encrypted data with HMAC-SHA-256. By
10
+ doing this, attackers must be able to forge a valid HMAC before
11
+ they can try to exploit possible weaknesses in the encryption,
12
+ such as timing attacks during decryption that are dependent on
13
+ attacker chosen initialization vectors or ciphertext.
14
+
15
+ In addition to encryption and a stronger default signature
16
+ algorithm compared to Rack::Session::Cookie, the sessions
17
+ plugin has the following benefits:
18
+
19
+ * Built in session expiration enabeld by default, to mitigate
20
+ possible session replay issues (default: 30 days since session
21
+ creation, 7 days since last update).
22
+
23
+ * Padding by default to minimize information leakage due to
24
+ differing session data sizes (session data padded to
25
+ a multiple of 32 bytes by default before encryption).
26
+
27
+ * Automatic deflate compression of large sessions before
28
+ encryption (by default if session data is over 128 bytes).
29
+
30
+ * JSON is used for serialization instead of Marshal, preventing
31
+ remote code execution vulnerabilities if the session secret
32
+ is disclosed. Note that this means that many ruby types do not
33
+ round trip in the session, such as Symbol and Time instances.
34
+ This will probably be the largest barrier to adoption, as you
35
+ need to make sure your application only uses types that
36
+ round-trip through JSON before you start using the sessions
37
+ plugin.
38
+
39
+ * A plain hash is used for the session, instead of a hash-like
40
+ object. One consequence of this is that keys in the session
41
+ are not automatically converted to strings.
42
+ Rack::Session::Cookie converts session keys to strings for
43
+ keys at the top level, but not for keys in subhashes.
44
+
45
+ * In general sessions are smaller even if deflate compression is not
46
+ used, despite requiring 16 bytes for the cipher initialization
47
+ vector. The main reason for this is that the sessions plugin does
48
+ not set a session id, since one is not needed for cookie sessions.
49
+
50
+ * The sessions plugin requires a :secret option be set that is
51
+ at least 64 bytes, so that users have to make a determined
52
+ effort to use weak secrets.
53
+
54
+ * The HMAC calculation considers the cookie key, so that if the
55
+ same session secret is used for multiple applications with
56
+ different cookie keys, an attacker cannot use the session from
57
+ one application in a different application.
58
+
59
+ The sessions plugin ties into the Roda#session method instead
60
+ of being a rack middleware. This makes it about twice as
61
+ fast as Rack::Session::Cookie if the session is not accessed.
62
+ If the session is accessed, the sessions plugin is roughly
63
+ as fast as Rack::Session::Cookie, even though it uses a
64
+ stronger HMAC and has to encrypt and decrypt the session.
65
+
66
+ Because the sessions plugin is not a middleware, it does not
67
+ offer session support to other middleware, only to the app
68
+ itself. If you would like to use the same approach as the
69
+ sessions plugin uses but would like support for middleware to
70
+ access the sessions, a roda/session_middleware file has been
71
+ added. This file contains RodaSessionMiddleware, which is a
72
+ middleware that can be used by any other Rack app for session
73
+ support, and which uses a SessionHash class similar to the one used
74
+ by Rack::Session::Cookie.
75
+
76
+ To integrate with other plugins that can optionally use symbols
77
+ or strings in sessions, the sessions plugin sets the
78
+ :sessions_convert_symbols application option to true. Other plugins
79
+ can check for this application option, and if set, should use
80
+ strings instead of symbols in the session.
81
+
82
+ The sessions plugin should be loaded after the flash plugin if both
83
+ are used in the same application, so that the flash is rotated
84
+ correctly in the session.
85
+
86
+ * The middleware plugin now supports a :handle_result option, which
87
+ can be any callable object. If set, this object is called with the
88
+ environment of the request and the rack response after either the
89
+ Roda app or next middleware returns the rack response. The rack
90
+ response can be modified by the callable object, and the response
91
+ (after possible modification) will be returned to the previous
92
+ middleware. Example:
93
+
94
+ plugin :middleware, :handle_result=>(proc do |env, res|
95
+ res[1]['MyHeader'] = 'HeaderValue'
96
+ end)
97
+
98
+ * The :json_parser and :json_serializer application options are now
99
+ supported. If set, these options are used for parsing and
100
+ serializing JSON instead of the default of JSON.parse and .to_json.
101
+
102
+ = Other Improvements
103
+
104
+ * RodaRequest initialization is now faster by avoiding 1-2 method
105
+ calls.
106
+
107
+ * typecast_params.Integer in the typecast_params plugin now handles
108
+ numeric input as long the numeric input does not have fractional
109
+ parts. This makes it more usable when handling JSON input.
110
+
111
+ * If the flash is empty after the request is processed, the flash
112
+ session key is removed from the session instead of being left as
113
+ an empty hash. If addition to making the session smaller, this
114
+ makes the session appear empty if there are no other keys in the
115
+ session, which works better with the sessions plugin as empty
116
+ sessions will remove the session cookie completely.
117
+
118
+ = Backwards Compatibility
119
+
120
+ * The flash plugin now uses '_flash' instead of :_flash as the session
121
+ key. When using session middleware that uses
122
+ Rack::Session::Abstract::SessionHash to store the session (e.g.
123
+ Rack::Session::Cookie), session keys are converted internally to
124
+ strings, so this change will not affect you unless you are using
125
+ alternative session support. Even if your session does treat
126
+ :_flash different than '_flash' in keys, the plugin will still work
127
+ because it will try :_flash if there is no value for '_flash'. This
128
+ change was made to support the sessions plugin, which doesn't
129
+ convert keys to strings.
130
+
131
+ * This DEFAULT_PARSER and DEFAULT_SERIALIZER constants from the
132
+ the json_parser and json plugins have been removed.
@@ -228,7 +228,7 @@ class Roda
228
228
  # Add a middleware to use for the rack application. Must be
229
229
  # called before calling #route to have an effect. Example:
230
230
  #
231
- # Roda.use Rack::Session::Cookie, secret: ENV['secret']
231
+ # Roda.use Rack::ShowExceptions
232
232
  def use(*args, &block)
233
233
  @middleware << [args, block].freeze
234
234
  build_rack_app
@@ -372,7 +372,7 @@ class Roda
372
372
  @scope = scope
373
373
  @captures = []
374
374
  @remaining_path = _remaining_path(env)
375
- super(env)
375
+ @env = env
376
376
  end
377
377
 
378
378
  # Handle match block return values. By default, if a string is given
@@ -670,7 +670,7 @@ class Roda
670
670
  # The session for the current request. Raises a RodaError if
671
671
  # a session handler has not been loaded.
672
672
  def session
673
- @env['rack.session'] || raise(RodaError, "You're missing a session handler. You can get started by adding use Rack::Session::Cookie")
673
+ @env['rack.session'] || raise(RodaError, "You're missing a session handler, try using the sessions plugin.")
674
674
  end
675
675
 
676
676
  private
@@ -368,7 +368,7 @@ class Roda
368
368
 
369
369
  if opts[:precompiled] && !opts[:compiled] && ::File.exist?(opts[:precompiled])
370
370
  require 'json'
371
- opts[:compiled] = ::JSON.parse(::File.read(opts[:precompiled]))
371
+ opts[:compiled] = (app.opts[:json_parser] || ::JSON.method(:parse)).call(::File.read(opts[:precompiled]))
372
372
  end
373
373
 
374
374
  if opts[:early_hints]
@@ -451,7 +451,7 @@ class Roda
451
451
  if assets_opts[:precompiled]
452
452
  require 'json'
453
453
  ::FileUtils.mkdir_p(File.dirname(assets_opts[:precompiled]))
454
- ::File.open(assets_opts[:precompiled], 'wb'){|f| f.write(assets_opts[:compiled].to_json)}
454
+ ::File.open(assets_opts[:precompiled], 'wb'){|f| f.write((opts[:json_serializer] || :to_json.to_proc).call(assets_opts[:compiled]))}
455
455
  end
456
456
 
457
457
  assets_opts[:compiled]
@@ -91,7 +91,8 @@ class Roda
91
91
  # Access the flash hash for the current request, loading
92
92
  # it from the session if it is not already loaded.
93
93
  def flash
94
- @_flash ||= FlashHash.new(session[:_flash])
94
+ # :_flash to support transparent upgrades from previous key
95
+ @_flash ||= FlashHash.new(session['_flash'] || (session['_flash'] = session.delete(:_flash)))
95
96
  end
96
97
 
97
98
  # If the routing doesn't raise an error, rotate the flash
@@ -100,7 +101,12 @@ class Roda
100
101
  res = super
101
102
 
102
103
  if f = @_flash
103
- session[:_flash] = f.next
104
+ f = f.next
105
+ if f.empty?
106
+ session.delete('_flash')
107
+ else
108
+ session['_flash'] = f
109
+ end
104
110
  end
105
111
 
106
112
  res
@@ -53,8 +53,6 @@ class Roda
53
53
  #
54
54
  # plugin :json, content_type: 'application/xml'
55
55
  module Json
56
- DEFAULT_SERIALIZER = :to_json.to_proc
57
-
58
56
  # Set the classes to automatically convert to JSON, and the serializer to use.
59
57
  def self.configure(app, opts=OPTS)
60
58
  classes = opts[:classes] || [Array, Hash]
@@ -63,7 +61,7 @@ class Roda
63
61
  app.opts[:json_result_classes].uniq!
64
62
  app.opts[:json_result_classes].freeze
65
63
 
66
- app.opts[:json_result_serializer] = opts[:serializer] || app.opts[:json_result_serializer] || DEFAULT_SERIALIZER
64
+ app.opts[:json_result_serializer] = opts[:serializer] || app.opts[:json_result_serializer] || app.opts[:json_serializer] || :to_json.to_proc
67
65
 
68
66
  app.opts[:json_result_include_request] = opts[:include_request] if opts.has_key?(:include_request)
69
67
 
@@ -12,7 +12,6 @@ class Roda
12
12
  # header for the request includes "json".
13
13
  module JsonParser
14
14
  DEFAULT_ERROR_HANDLER = proc{|r| r.halt [400, {}, []]}
15
- DEFAULT_PARSER = JSON.method(:parse)
16
15
 
17
16
  # Handle options for the json_parser plugin:
18
17
  # :error_handler :: A proc to call if an exception is raised when
@@ -32,7 +31,7 @@ class Roda
32
31
  # only wrap values that are not already hashes.
33
32
  def self.configure(app, opts=OPTS)
34
33
  app.opts[:json_parser_error_handler] = opts[:error_handler] || app.opts[:json_parser_error_handler] || DEFAULT_ERROR_HANDLER
35
- app.opts[:json_parser_parser] = opts[:parser] || app.opts[:json_parser_parser] || DEFAULT_PARSER
34
+ app.opts[:json_parser_parser] = opts[:parser] || app.opts[:json_parser_parser] || app.opts[:json_parser] || JSON.method(:parse)
36
35
  app.opts[:json_parser_include_request] = opts[:include_request] if opts.has_key?(:include_request)
37
36
 
38
37
  case opts[:wrap]
@@ -69,10 +69,15 @@ class Roda
69
69
  # application that the current request is a middleware request.
70
70
  # You should only need to override this if you are using multiple
71
71
  # roda middleware in the same application.
72
+ # :handle_result :: Callable object that will be called with request environment
73
+ # and rack response for all requests passing through the middleware,
74
+ # after either the middleware or next app handles the request
75
+ # and returns a response.
72
76
  def self.configure(app, opts={}, &block)
73
77
  app.opts[:middleware_env_var] = opts[:env_var] if opts.has_key?(:env_var)
74
78
  app.opts[:middleware_env_var] ||= 'roda.forward_next'
75
79
  app.opts[:middleware_configure] = block if block
80
+ app.opts[:middleware_handle_result] = opts[:handle_result]
76
81
  end
77
82
 
78
83
  # Forwarder instances are what is actually used as middleware.
@@ -102,10 +107,14 @@ class Roda
102
107
  end
103
108
 
104
109
  if call_next
105
- @app.call(env)
106
- else
107
- res
110
+ res = @app.call(env)
108
111
  end
112
+
113
+ if handle_result = @mid.opts[:middleware_handle_result]
114
+ handle_result.call(env, res)
115
+ end
116
+
117
+ res
109
118
  end
110
119
  end
111
120
 
@@ -26,9 +26,11 @@ class Roda
26
26
  # sessions, so if the attacker has the ability to read cookie data
27
27
  # and you are using Rack::Session::Cookie, it will still be possible
28
28
  # for an attacker to generate valid CSRF tokens specific to arbitrary
29
- # request method and request path.
29
+ # request method and request path. Roda's session plugin uses
30
+ # encrypted sessions and therefore is safe even if the attacker can
31
+ # read cookie data.
30
32
  #
31
- # # Usage
33
+ # == Usage
32
34
  #
33
35
  # It is recommended to use the plugin defaults, loading the
34
36
  # plugin with no options:
@@ -37,39 +39,39 @@ class Roda
37
39
  #
38
40
  # This plugin supports the following options:
39
41
  #
40
- # :field :: Form input parameter name for CSRF token (default: '_csrf')
41
- # :header :: HTTP header name for CSRF token (default: 'X-CSRF-Token')
42
- # :key :: Session key for CSRF secret (default: '_roda_csrf_secret')
43
- # :require_request_specific_tokens :: Whether request-specific tokens are required (default: true).
44
- # A false value will allow tokens that are not request-specific
45
- # to also work. You should only set this to false if it is
46
- # impossible to use request-specific tokens. If you must
47
- # use non-request-specific tokens in certain cases, it is best
48
- # to leave this option true by default, and override it on a
49
- # per call basis in those specific cases.
50
- # :csrf_failure :: The action to taken if a request fails the CSRF check (default: :raise). Options:
51
- # :raise :: raise a Roda::RodaPlugins::RouteCsrf::InvalidToken exception
52
- # :empty_403 :: return a blank 403 page (rack_csrf's default behavior)
53
- # :clear_session :: Clear the current session
54
- # Proc :: Treated as a routing block, called with request object
55
- # :check_header :: Whether the HTTP header should be checked for the token value (default: false).
56
- # If true, checks the HTTP header after checking for the form input parameter.
57
- # If :only, only checks the HTTP header and doesn't check the form input parameter.
58
- # :check_request_methods :: Which request methods require CSRF protection
59
- # (default: <tt>['POST', 'DELETE', 'PATCH', 'PUT']</tt>)
60
- # :upgrade_from_rack_csrf_key :: If provided, the session key that should be checked for the
61
- # rack_csrf raw token. If the session key is present, the value
62
- # will be checked against the submitted token, and if it matches,
63
- # the CSRF check will be passed. Should only be set temporarily
64
- # if upgrading from using rack_csrf to the route_csrf plugin, and
65
- # should be removed as soon as you are OK with CSRF forms generated
66
- # before the upgrade not longer being usable. The default rack_csrf
67
- # key is <tt>'csrf.token'</tt>.
42
+ # :field :: Form input parameter name for CSRF token (default: '_csrf')
43
+ # :header :: HTTP header name for CSRF token (default: 'X-CSRF-Token')
44
+ # :key :: Session key for CSRF secret (default: '_roda_csrf_secret')
45
+ # :require_request_specific_tokens :: Whether request-specific tokens are required (default: true).
46
+ # A false value will allow tokens that are not request-specific
47
+ # to also work. You should only set this to false if it is
48
+ # impossible to use request-specific tokens. If you must
49
+ # use non-request-specific tokens in certain cases, it is best
50
+ # to leave this option true by default, and override it on a
51
+ # per call basis in those specific cases.
52
+ # :csrf_failure :: The action to taken if a request fails the CSRF check (default: :raise). Options:
53
+ # :raise :: raise a Roda::RodaPlugins::RouteCsrf::InvalidToken exception
54
+ # :empty_403 :: return a blank 403 page (rack_csrf's default behavior)
55
+ # :clear_session :: Clear the current session
56
+ # Proc :: Treated as a routing block, called with request object
57
+ # :check_header :: Whether the HTTP header should be checked for the token value (default: false).
58
+ # If true, checks the HTTP header after checking for the form input parameter.
59
+ # If :only, only checks the HTTP header and doesn't check the form input parameter.
60
+ # :check_request_methods :: Which request methods require CSRF protection
61
+ # (default: <tt>['POST', 'DELETE', 'PATCH', 'PUT']</tt>)
62
+ # :upgrade_from_rack_csrf_key :: If provided, the session key that should be checked for the
63
+ # rack_csrf raw token. If the session key is present, the value
64
+ # will be checked against the submitted token, and if it matches,
65
+ # the CSRF check will be passed. Should only be set temporarily
66
+ # if upgrading from using rack_csrf to the route_csrf plugin, and
67
+ # should be removed as soon as you are OK with CSRF forms generated
68
+ # before the upgrade not longer being usable. The default rack_csrf
69
+ # key is <tt>'csrf.token'</tt>.
68
70
  #
69
71
  # The plugin also supports a block, in which case the block will be used
70
72
  # as the value of the :csrf_failure option.
71
73
  #
72
- # # Methods
74
+ # == Methods
73
75
  #
74
76
  # This adds the following instance methods:
75
77
  #
@@ -116,7 +118,7 @@ class Roda
116
118
  # will not work unless you set the :require_request_specific_tokens option to
117
119
  # false, which is a bad idea from a security standpoint.
118
120
  #
119
- # # Token Cryptography
121
+ # == Token Cryptography
120
122
  #
121
123
  # route_csrf uses HMAC-SHA-256 to generate all CSRF tokens. It generates a random 32-byte secret,
122
124
  # which is stored base64 encoded in the session. For each CSRF token, it generates 31 bytes