# frozen_string_literal: true require "rack/session/cookie" require_relative "utils" class Tynn # Adds simple cookie based session management. You can pass a secret # token to sign the cookie data, thus unauthorized means can't alter it. # # require "tynn" # require "tynn/session" # # Tynn.plugin(Tynn::Session, secret: "__change_me_not_secure__") # # Tynn.define do # on "login" do # on post do # # ... # # session[:user_id] = user.id # # res.redirect("/admin") # end # end # end # # The following command generates a cryptographically secure secret ready # to use: # # $ ruby -r securerandom -e "puts SecureRandom.hex(64)" # # It's important to keep the token secret. Knowing the token allows an # attacker to tamper the data. So, it's recommended to load the token # from the environment. # # Tynn.plugin(Tynn::Session, secret: ENV["SESSION_SECRET"]) # # Under the hood, Tynn::Session uses the Rack::Session::Cookie # middleware. Thus, supports all the options available for this middleware: # # [key] # The name of the cookie. Defaults to "rack.session". # # [httponly] # If true, sets the HttpOnly flag. This mitigates the # risk of client side scripting accessing the cookie. Defaults to true. # # [secure] # If true, sets the Secure flag. This tells the browser # to only transmit the cookie over HTTPS. Defaults to false. # # [same_site] # Disables third-party usage for cookies. There are two possible values # :Lax and :Strict. In Strict mode, the cookie # is restrain to any cross-site usage; in Lax mode, some cross-site # usage is allowed. Defaults to :Lax. If nil is passed, # the flag is not included. Check this article[http://www.sjoerdlangkemper.nl/2016/04/14/preventing-csrf-with-samesite-cookie-attribute/] # for more information. # # [expire_after] # The lifespan of the cookie. If nil, the session cookie is temporary # and is no retained after the browser is closed. Defaults to nil. # # # # Tynn.plugin( # Tynn::Session, # key: "app", # secret: ENV["SESSION_SECRET"], # expire_after: 36_000, # seconds # httponly: true, # secure: true, # same_site: :Strict # ) # module Session SECRET_MIN_LENGTH = 30 # :nodoc: def self.setup(app, options = {}) # :nodoc: secret = options[:secret] if secret.nil? Tynn::Utils.raise_error( "Secret key is required", error: ArgumentError, tag: :no_secret_key ) end if secret.length < SECRET_MIN_LENGTH Tynn::Utils.raise_error( "Secret key is shorter than #{ SECRET_MIN_LENGTH } characters", error: ArgumentError, tag: :short_secret_key ) end app.use(Rack::Session::Cookie, { coder: Rack::Session::Cookie::Base64::JSON.new, hmac: OpenSSL::Digest::SHA256, same_site: :Lax }.merge(options)) end module InstanceMethods # Returns the session hash. # # session # # => {} # # session[:foo] = "foo" # session[:foo] # # => "foo" # def session req.session end end end end