diff --git a/spec/config_spec.cr b/spec/config_spec.cr index fbfb685..9ed90ee 100644 --- a/spec/config_spec.cr +++ b/spec/config_spec.cr @@ -23,14 +23,6 @@ describe "Config" do config.host_binding.should eq "127.0.0.1" end - it "sets session values" do - config = Kemal.config - config.session["name"] = "kemal" - config.session["expire_time"] = 1.hours - config.session["name"].as(String).should eq "kemal" - config.session["expire_time"].as(Time::Span).should eq 1.hours - end - it "adds a custom handler" do config = Kemal.config config.add_handler CustomTestHandler.new diff --git a/src/kemal.cr b/src/kemal.cr index 98d90e3..4b3809f 100644 --- a/src/kemal.cr +++ b/src/kemal.cr @@ -18,8 +18,6 @@ module Kemal config.server.tls = config.ssl {% end %} - Kemal::Sessions.run_reaper! - unless Kemal.config.error_handlers.has_key?(404) error 404 do |env| render_404 diff --git a/src/kemal/config.cr b/src/kemal/config.cr index 79a398b..4d05471 100644 --- a/src/kemal/config.cr +++ b/src/kemal/config.cr @@ -15,14 +15,13 @@ module Kemal {% end %} property host_binding, ssl, port, env, public_folder, logging, running, - always_rescue, serve_static : (Bool | Hash(String, Bool)), server, session : Hash(String, Time::Span | String), extra_options + always_rescue, serve_static : (Bool | Hash(String, Bool)), server, extra_options def initialize @host_binding = "0.0.0.0" @port = 3000 @env = "development" @serve_static = {"dir_listing" => false, "gzip" => true} - @session = {"name" => "kemal_session", "expire_time" => 48.hours} @public_folder = "./public" @logging = true @logger = nil diff --git a/src/kemal/session.cr b/src/kemal/session.cr deleted file mode 100644 index d1e1ccc..0000000 --- a/src/kemal/session.cr +++ /dev/null @@ -1,113 +0,0 @@ -require "secure_random" - -module Kemal - # Kemal's default session is in-memory only and holds simple String values only. - # The client-side cookie stores a random ID. - # - # Kemal handlers can access the session like so: - # - # get("/") do |env| - # env.session["abc"] = "xyz" - # uid = env.session["user_id"]?.as(Int32) - # end - # - # Note that only String values are allowed. - # - # Sessions are pruned hourly after 48 hours of inactivity. - class Sessions - # :nodoc: - alias SessionTypes = String | Int32 | Float64 | Bool - - # In-memory, ephemeral datastore only. - # - # Implementing Redis or Memcached as a datastore - # is left as an exercise to another reader. - # - # Note that the only thing we store on the client-side - # is an opaque, random String. If we actually wanted to - # store any data, we'd need to implement encryption, key - # rotation, tamper-detection and that whole iceberg. - STORE = Hash(String, Session).new - - class Session - getter! id : String - property! last_access_at : Int64 - - def initialize(@id) - @last_access_at = Time.new.epoch_ms - @store = Hash(String, SessionTypes).new - end - - def [](key : String) - @last_access_at = Time.now.epoch_ms - @store[key] - end - - def []?(key : String) - @last_access_at = Time.now.epoch_ms - @store[key]? - end - - def []=(key : String, value : SessionTypes) - @last_access_at = Time.now.epoch_ms - @store[key] = value - end - - def delete(key : String) - @last_access_at = Time.now.epoch_ms - @store.delete(key) - end - end - - getter! id : String - - def initialize(ctx : HTTP::Server::Context) - id = ctx.request.cookies[Kemal.config.session["name"].as(String)]?.try &.value - if id && id.size == 32 - # valid - else - # new or invalid - id = SecureRandom.hex - end - - ctx.response.cookies << HTTP::Cookie.new(name: Kemal.config.session["name"].as(String), value: id, http_only: true) - @id = id - end - - def []=(key : String, value : SessionTypes) - store = STORE[id]? || begin - STORE[id] = Session.new(id) - end - store[key] = value - end - - def [](key : String) - STORE[@id][key] - end - - def []?(key : String) - STORE[@id]?.try &.[key]? - end - - def delete(key : String) - STORE[@id]?.try &.delete(key) - end - - def self.prune!(before = (Time.now - Kemal.config.session["expire_time"].as(Time::Span)).epoch_ms) - Kemal::Sessions::STORE.delete_if { |id, entry| entry.last_access_at < before } - nil - end - - # This is an hourly job to prune the in-memory hash of any - # sessions which have expired due to inactivity, otherwise - # we'll have a slow memory leak and possible DDoS vector. - def self.run_reaper! - spawn do - loop do - prune! - sleep 3600 - end - end - end - end -end