Class: Utopia::Session::Middleware

Inherits:
Protocol::HTTP::Middleware
  • Object
show all
Defined in:
lib/utopia/session/middleware.rb

Overview

A middleware which provides a secure client-side session storage using a private symmetric encrpytion key.

Defined Under Namespace

Classes: PayloadError

Constant Summary collapse

SIZE_LIMIT =
1024*32
SECRET_KEY =
"UTOPIA_SESSION_SECRET".freeze
SESSION_KEY =
"utopia.session".freeze
CIPHER_ALGORITHM =
"aes-256-gcm"
PAYLOAD_VERSION =
"v1".freeze
AUTHENTICATION_TAG_SIZE =
16
DEFAULT_EXPIRES_AFTER =

The session will expire if no requests were made within 24 hours:

3600*24
DEFAULT_UPDATE_TIMEOUT =

At least, the session will be updated every 1 hour:

3600

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, session_name: SESSION_KEY, secret: nil, expires_after: DEFAULT_EXPIRES_AFTER, update_timeout: DEFAULT_UPDATE_TIMEOUT, domain: nil, path: "/", max_age: nil, secure: false, http_only: true, same_site: :lax, partitioned: false, size_limit: SIZE_LIMIT) ⇒ Middleware

Returns a new instance of Middleware.

Parameters:

  • session_name (String) (defaults to: SESSION_KEY)

    The name of the session cookie.

  • secret (String) (defaults to: nil)

    The secret text used to generate a symmetric encryption key for the cookie data.

  • expires_after (Numeric | Nil) (defaults to: DEFAULT_EXPIRES_AFTER)

    The maximum session inactivity in seconds.

  • update_timeout (Numeric | Nil) (defaults to: DEFAULT_UPDATE_TIMEOUT)

    The maximum interval between session cookie updates.

  • domain (String | Nil) (defaults to: nil)

    The domain for which the cookie is valid.

  • path (String | Nil) (defaults to: "/")

    The path for which the cookie is valid.

  • max_age (Integer | Nil) (defaults to: nil)

    The browser cookie lifetime in seconds.

  • secure (Boolean) (defaults to: false)

    Whether the cookie requires a secure connection.

  • http_only (Boolean) (defaults to: true)

    Whether client-side scripts may access the cookie.

  • same_site (Symbol | String | Boolean | Nil) (defaults to: :lax)

    Controls whether the cookie is sent with cross-site requests.

  • partitioned (Boolean) (defaults to: false)

    Whether the cookie uses partitioned storage.

  • size_limit (Integer | Nil) (defaults to: SIZE_LIMIT)

    The encoded session payload size limit.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/utopia/session/middleware.rb', line 54

def initialize(app, session_name: SESSION_KEY, secret: nil, expires_after: DEFAULT_EXPIRES_AFTER, update_timeout: DEFAULT_UPDATE_TIMEOUT, domain: nil, path: "/", max_age: nil, secure: false, http_only: true, same_site: :lax, partitioned: false, size_limit: SIZE_LIMIT)
	super(app)
	
	@session_name = session_name
	@cookie_name = @session_name + ".encrypted"
	
	if secret.nil? or secret.empty?
		raise ArgumentError, "invalid session secret: #{secret.inspect}"
	end
	
	# This generates a 32-byte key suitable for aes.
	@key = Digest::SHA2.digest(secret)
	@authentication_context = "#{@cookie_name}\0#{PAYLOAD_VERSION}".b.freeze
	
	@expires_after = expires_after
	@update_timeout = update_timeout
	
	@cookie_defaults = {
		domain: domain,
		path: path,
		max_age: max_age,
		
		# The SameSite attribute controls when the cookie is sent to the server, from 3rd parties (None), from requests with external referrers (Lax) or from within the site itself (Strict).
		same_site: normalize_same_site(same_site),
		
		# The Secure attribute is meant to keep cookie communication limited to encrypted transmission, directing browsers to use cookies only via secure/encrypted connections. However, if a web server sets a cookie with a secure attribute from a non-secure connection, the cookie can still be intercepted when it is sent to the user by man-in-the-middle attacks. Therefore, for maximum security, cookies with the Secure attribute should only be set over a secure connection.
		secure: secure,
		
		# The HttpOnly attribute directs browsers not to expose cookies through channels other than HTTP (and HTTPS) requests. This means that the cookie cannot be accessed via client-side scripting languages (notably JavaScript), and therefore cannot be stolen easily via cross-site scripting (a pervasive attack technique).
		http_only: http_only,
		partitioned: partitioned,
	}
	
	@serialization = Serialization.new
	@size_limit = size_limit
end

Instance Attribute Details

Returns the value of attribute cookie_defaults.



97
98
99
# File 'lib/utopia/session/middleware.rb', line 97

def cookie_defaults
  @cookie_defaults
end

Returns the value of attribute cookie_name.



91
92
93
# File 'lib/utopia/session/middleware.rb', line 91

def cookie_name
  @cookie_name
end

#expires_afterObject (readonly)

Returns the value of attribute expires_after.



94
95
96
# File 'lib/utopia/session/middleware.rb', line 94

def expires_after
  @expires_after
end

#keyObject (readonly)

Returns the value of attribute key.



92
93
94
# File 'lib/utopia/session/middleware.rb', line 92

def key
  @key
end

#update_timeoutObject (readonly)

Returns the value of attribute update_timeout.



95
96
97
# File 'lib/utopia/session/middleware.rb', line 95

def update_timeout
  @update_timeout
end

Instance Method Details

#call(request) ⇒ Object

Attach a lazily loaded session to the request, then persist it.



117
118
119
120
121
122
123
124
125
# File 'lib/utopia/session/middleware.rb', line 117

def call(request)
	request.session = prepare_session(request)
	
	response = Response.wrap(@delegate.call(request))
	
	update_session(request.session, response.headers)
	
	return response
end

#freezeObject

Freeze this object and its internal state.



101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/utopia/session/middleware.rb', line 101

def freeze
	return self if frozen?
	
	@cookie_name.freeze
	@key.freeze
	@authentication_context.freeze
	@expires_after.freeze
	@update_timeout.freeze
	@cookie_defaults.freeze
	
	super
end