Skip to content

Commit

Permalink
Refactor session management and cookie handling; streamline event not…
Browse files Browse the repository at this point in the history
…ifications and improve session lifecycle methods
  • Loading branch information
eliasjpr committed Dec 1, 2024
1 parent 1515db0 commit 369219b
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 97 deletions.
88 changes: 38 additions & 50 deletions src/provider.cr
Original file line number Diff line number Diff line change
Expand Up @@ -17,60 +17,52 @@ module Session
@current_session.data
end

# Creates a new session for the given data
# Data is generic
# Creates a new session
def create
@current_session = SessionId(T).new
notify_event(:started)
@current_session
ensure
on :started, session_id, current_session.data
end

# Loads the session from a HTTP::Cookie
# Loads the session from cookies
def load_from(request_cookies : HTTP::Cookies) : SessionId(T)?
self.cookies = request_cookies if self.is_a? CookieStore(T)
self.cookies = request_cookies if self.is_a?(CookieStore(T))
cookie = request_cookies[session_key]?
return @current_session = create if cookie.nil?
@current_session = if store_session = self[cookie.not_nil!.value]?
store_session
else
create
end
ensure
on(:loaded, session_id, data)
@current_session = cookie ? fetch_session(cookie.value) : create
notify_event(:loaded)
@current_session
end

# Deletes the current session
def delete
delete session_id
remove_session(session_id)
@current_session = SessionId(T).new
ensure
on :deleted, session_id, current_session.data
notify_event(:deleted)
end

# Sets the session cookie and the session data cookie
# if not exists creates a new one
# Sets session cookies
def set_cookies(response_cookies : HTTP::Cookies, host : String = "")
set_session_id_cookie(response_cookies, host)
set_data_cookie(response_cookies, host: host)
ensure
on(:client, session_id, data)
set_data_cookie(response_cookies, host)
notify_event(:client)
end

def set_data_cookie(response_cookies : HTTP::Cookies, host : String = "", cookie_name : String = "_data_")
cookie_name = "#{prefixed(session_id)}.#{cookie_name}"
private

if data_cookie = response_cookies[cookie_name]?
data_cookie.value = encrypt_and_sign(current_session.to_json)
data_cookie.expires = timeout.from_now
response_cookies << data_cookie
else
response_cookies << create_data_cookie(cookie_name, host)
end
# Fetches session data or creates a new session
def fetch_session(cookie_value : String) : SessionId(T)
self[cookie_value]? || create
end

def create_data_cookie(cookie_name : String = "", host : String = "")
HTTP::Cookie.new(
# Removes session by key
def remove_session(key : String)
delete key
end

# Creates or updates the data cookie
def set_data_cookie(response_cookies : HTTP::Cookies, host : String = "")
cookie_name = "#{session_key}._data_"
cookie = HTTP::Cookie.new(
name: cookie_name,
value: encrypt_and_sign(current_session.to_json),
expires: timeout.from_now,
Expand All @@ -79,17 +71,18 @@ module Session
path: "/",
samesite: HTTP::Cookie::SameSite::Strict,
http_only: true,
creation_time: Time.local,
creation_time: Time.local
)
response_cookies << cookie
end

# Creates or updates the session ID cookie
def set_session_id_cookie(response_cookies : HTTP::Cookies, host : String = "")
cookie = response_cookies[session_id]? || cookie(host)
response_cookies << cookie(host)
response_cookies << create_session_cookie(host)
end

# Creates the session cookie
def cookie(host : String)
# Creates the session ID cookie
def create_session_cookie(host : String)
HTTP::Cookie.new(
name: session_key,
value: session_id,
Expand All @@ -99,11 +92,17 @@ module Session
domain: host,
path: "/",
samesite: HTTP::Cookie::SameSite::Strict,
creation_time: Time.local,
creation_time: Time.local
)
ensure
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}")
end
end

def timeout
Expand All @@ -118,16 +117,6 @@ module Session
"#{session_key}.#{session_id}"
end

def on(event : Symbol, *args)
case event
when :started then Session.config.on_started.call *args
when :deleted then Session.config.on_deleted.call *args
when :loaded then Session.config.on_loaded.call *args
when :client then Session.config.on_client.call *args
else raise InvalidSessionEventException.new
end
end

def encrypt_and_sign(value)
Session.config.encryptor.encrypt_and_sign(value)
end
Expand All @@ -136,4 +125,3 @@ module Session
Session.config.encryptor.verify_and_decrypt(value)
end
end
end
94 changes: 47 additions & 47 deletions src/stores/cookie_store.cr
Original file line number Diff line number Diff line change
@@ -1,58 +1,58 @@
require "redis"
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
getter cookie_name = "_data_"
def storage : String
self.class.name
end

# Gets the session manager store type
def storage : String
self.class.name
end
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[prefixed(cookie_name + key)]
payload = String.new(verify_and_decrypt(data.value))
SessionId(T).from_json payload
else
raise InvalidSessionExeception.new
end
def []?(key : String) : SessionId(T)?
if data = cookies[prefixed(cookie_name + key)]?
deserialize_session(data.value)
end
end

def []?(key : String) : SessionId(T)?
if data = cookies[prefixed(cookie_name + 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)
session
end

def []=(key : String, session : SessionId(T)) : SessionId(T)
cookies << HTTP::Cookie.new(
name: prefixed(cookie_name + session.session_id),
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))
end

def delete(key : String)
cookies.delete prefixed(cookie_name + key)
end
def size : Int64
count_cookies(prefixed(cookie_name))
end

def size : Int64
name = prefixed(cookie_name)
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 clear
cookies.each do |cookie|
cookies.delete cookie.name if cookie.name.starts_width? name
end
end
private

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) }
end
end
end

0 comments on commit 369219b

Please sign in to comment.