Skip to content

Commit

Permalink
Refactor session management: rename Databag to SessionData and update…
Browse files Browse the repository at this point in the history
… related callbacks in configuration and documentation
  • Loading branch information
eliasjpr committed Dec 2, 2024
1 parent 808eb82 commit 738ae3f
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 96 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ Session.configure do |c|
c.timeout = 1.hour
c.session_key = "_session"
s.secret = "Secret key for encryption"
c.on_started = ->(sid : String, data : Databag) { puts "Session started - #{sid}" }
c.on_deleted = ->(sid : String, data : Databag) { puts "Session Revoke - #{sid}" }
c.on_started = ->(sid : String, data : SessionData) { puts "Session started - #{sid}" }
c.on_deleted = ->(sid : String, data : SessionData) { puts "Session Revoke - #{sid}" }
end
```

Expand All @@ -54,8 +54,8 @@ Session.configure do |c|
c.secret = "Secret key for encryption"
c.session_key = "myapp.session"
c.provider = Session::CookieStore(Sessions::UserSession).provider
c.on_started = ->(sid : String, data : Session::Databag) { puts "Session started - #{sid}" }
c.on_deleted = ->(sid : String, data : Session::Databag) { puts "Session Revoke - #{sid}" }
c.on_started = ->(sid : String, data : Session::SessionData) { puts "Session started - #{sid}" }
c.on_deleted = ->(sid : String, data : Session::SessionData) { puts "Session Revoke - #{sid}" }
end
```

Expand Down Expand Up @@ -97,16 +97,16 @@ end

The Session shard offers type-safe access to the values stored in the session, meaning that to store values in the session, you must first define the object.

The shard calls this object a Databag.
The shard calls this object a SessionData.

### Databag Object
### SessionData Object

To define a Databag object
To define a SessionData object

```crystal
# Type safe session contents
struct UserSession
include Session::Databag
include Session::SessionData
property username : String? = "example"
end
```
Expand Down
12 changes: 8 additions & 4 deletions spec/provider_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,17 @@ describe Session::Provider do

it "loads session from valid session cookie for #{stores[storage]}" do
cookies = HTTP::Cookies.new
provider.set_cookies cookies
cookie = provider.session_id_cookie("localhost")
cookies << cookie
provider.set_cookies cookies, "localhost"

provider.load_from(cookies)

provider.load_from(cookies).should eq provider.current_session
provider[cookie.value]?.should_not be_nil
end

it "creates a session cookie from current session for #{stores[storage]}" do
cookie = provider.cookie("localhost")
cookie = provider.session_id_cookie("localhost")

cookie.name.should eq provider.session_key
cookie.value.should eq provider.session_id
Expand All @@ -59,7 +63,7 @@ describe Session::Provider do

provider.delete

provider[sid]?.should be_nil
provider.session_id.should_not eq(sid)
end
end
end
6 changes: 3 additions & 3 deletions spec/spec_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ REDIS_HOST = ENV["REDIS_HOST"]? || "localhost"
REDIS_CLIENT = Redis.new host: REDIS_HOST

class UserSession
include Session::Databag
include Session::SessionData
property? authenticated : Bool = true
property username : String? = "example"
end

Session.configure do
c.on_started = ->(sid : String, data : Session::Databag) { puts "Session started - Id: #{sid} Username: #{data.username}" }
c.on_deleted = ->(sid : String, data : Session::Databag) { puts "Session Revoke - Id: #{sid} Username: #{data.username}" }
on_started = ->(sid : String, data : Session::SessionData) { puts "Session started - Id: #{sid} Username: #{data.username}" }
on_deleted = ->(sid : String, data : Session::SessionData) { puts "Session Revoke - Id: #{sid} Username: #{data.username}" }
end
16 changes: 10 additions & 6 deletions src/provider.cr
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ module Session

# Loads the session from cookies
def load_from(request_cookies : HTTP::Cookies) : SessionId(T)?
self.cookies = request_cookies
self.cookies = request_cookies if self.is_a? CookieStore(T)
if current_session_id = request_cookies[session_key]?
session = self[current_session_id.value]?
@current_session = session if session
Expand Down Expand Up @@ -63,7 +63,6 @@ module Session
http_only: true,
creation_time: Time.local
)
response_cookies << cookie
end

# Creates the session cookie
Expand All @@ -83,10 +82,15 @@ module Session
self[session_id] = @current_session
end

# Notify about session events
def notify_event(event : Symbol)
args = [session_id, current_session.data]
Session.config.send("on_#{event}").call(*args) if Session.config.responds_to?("on_#{event}")
def on(event : Symbol, session_id : String, data : SessionData)
case event
when :started then Session.config.on_started.call(session_id, data)
when :loaded then Session.config.on_loaded.call(session_id, data)
when :client then Session.config.on_client.call(session_id, data)
when :deleted then Session.config.on_deleted.call(session_id, data)
else
raise "Unknown event: #{event}"
end
end
end

Expand Down
2 changes: 1 addition & 1 deletion src/session.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ require "http"
require "json"
require "./message"
require "./provider"
require "./databag"
require "./session_data"
require "./handlers/*"
require "./store"
require "./configuration"
Expand Down
File renamed without changes.
111 changes: 37 additions & 74 deletions src/stores/cookie_store.cr
Original file line number Diff line number Diff line change
@@ -1,96 +1,59 @@
class CookieStore(T) < Store(T)
property cookies = HTTP::Cookies.new
getter cookie_name = "_data_"
module Session
class CookieStore(T) < Store(T)
property cookies = HTTP::Cookies.new

module Session
class CookieStore(T) < Store(T)
property cookies = HTTP::Cookies.new

def [](key : String) : SessionId(T)
data = cookies[prefixed(cookie_name + key)] || raise InvalidSessionExeception.new
deserialize_session(data.value)
end

def [](key : String) : SessionId(T)
if data = cookies[data_key]
payload = String.new(verify_and_decrypt(data.value))
SessionId(T).from_json payload
else
raise InvalidSessionExeception.new
end
end

def []?(key : String) : SessionId(T)?
if data = cookies[data_key]?
payload = String.new(verify_and_decrypt(data.value))
SessionId(T).from_json payload
end
end

def []=(key : String, session : SessionId(T)) : SessionId(T)
cookies << HTTP::Cookie.new(
name: data_key,
value: encrypt_and_sign(session.to_json),
expires: timeout.from_now,
secure: true,
http_only: true,
creation_time: Time.local,
)
session
end
def storage : String
self.class.name
end

def delete(key : String)
cookies.delete(data_key)
end
def [](key : String) : SessionId(T)
data = cookies[prefixed(cookie_name + key)] || raise InvalidSessionExeception.new
deserialize_session(data.value)
end

def size : Int64
name = data_key
cookies.reduce(0_i64) do |acc, cookie|
acc + 1 if cookie.name.starts_width? name
end
def [](key : String) : SessionId(T)
if data = cookies[data_key]
payload = String.new(verify_and_decrypt(data.value))
SessionId(T).from_json payload
else
raise InvalidSessionExeception.new
end
end

def clear
cookies.each do |cookie|
cookies.delete cookie.name if cookie.name.starts_width? name
end
def []?(key : String) : SessionId(T)?
if data = cookies[data_key]?
payload = String.new(verify_and_decrypt(data.value))
SessionId(T).from_json payload
end
end

def []=(key : String, session : SessionId(T)) : SessionId(T)
cookies << create_session_cookie(key, session)
cookies << HTTP::Cookie.new(
name: data_key,
value: encrypt_and_sign(session.to_json),
expires: timeout.from_now,
secure: true,
http_only: true,
creation_time: Time.local,
)
session
end

def delete(key : String)
cookies.delete(prefixed(cookie_name + key))
cookies.delete(data_key)
end

def size : Int64
count_cookies(prefixed(cookie_name))
name = data_key
cookies.reduce(0_i64) do |acc, cookie|
acc + 1 if cookie.name.starts_width? name
end
end

def clear
cookies.reject! { |cookie| cookie.name.starts_with?(prefixed(cookie_name)) }
end

def create_session_cookie(key : String, session : SessionId(T))
HTTP::Cookie.new(
name: prefixed(cookie_name + key),
value: encrypt_and_sign(session.to_json),
expires: timeout.from_now,
secure: true,
http_only: true,
creation_time: Time.local
)
end

def deserialize_session(value : String) : SessionId(T)
SessionId(T).from_json(verify_and_decrypt(value))
end

def count_cookies(name_prefix : String) : Int64
cookies.reduce(0_i64) { |acc, cookie| acc + 1 if cookie.name.starts_with?(name_prefix) }
cookies.each do |cookie|
cookies.delete cookie.name if cookie.name.starts_width? name
end
end
end
end

0 comments on commit 738ae3f

Please sign in to comment.