# frozen_string_literal: true
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
# 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.
#
# [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
# )
#
module Session
SECRET_MIN_LENGTH = 30 # :nodoc:
def self.setup(app, options = {}) # :nodoc:
secret = options[:secret]
if secret.nil?
raise <<~MSG
No secret option provided to Tynn::Session.
Tynn::Session uses a secret token to sign the cookie data, thus
unauthorized means can't alter it. Please, add the secret option
to your code:
#{ app }.plugin(Tynn::Session, secret: "__a_long_random_secret__", ...)
If you're sharing your code publicly, make sure the secret key
is kept private. Knowing the secret allows an attacker to tamper
the data. You can use environment variables to store the secret:
#{ app }.plugin(Tynn::Session, secret: ENV.fetch("SESSION_SECRET"), ...)
MSG
end
if secret.length < SECRET_MIN_LENGTH
raise <<~MSG
The secret provided is shorter than the minimum length.
Make sure the secret is long and all random. You can generate a
secure secret key with:
$ ruby -r securerandom -e "puts SecureRandom.hex(64)"
MSG
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