simple_session 0.1.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +49 -42
- data/lib/simple_session/base.rb +70 -102
- data/lib/simple_session/version.rb +1 -1
- data/lib/simple_session.rb +7 -6
- data/simple_session.gemspec +0 -1
- metadata +2 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22b41938246e413705405ae2f83ebb0104761677
|
4
|
+
data.tar.gz: cc6b51706663df70fb814a27e74b12babd7ee0b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75250ae913ae857b5b9e4375bbff4b851d246f2b2cc8f810627c3c0f587d72ac9c7b396af6a810f531d7c10d05bf98993a147875fab3d1a48e5e2eef524bc1f0
|
7
|
+
data.tar.gz: a0a1dbedc75f826d0eda5077833b2f9986cf94a4f058702a626d1924ef36b4c512335b28d1df0d667d153aebeea334c0bc8fc202b6153cc4689ac73efa4ec4ff
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# SimpleSession
|
2
|
+

|
2
3
|
|
3
4
|
This is a drop in replacement for rack session. By default
|
4
5
|
the session cookie is encrypted in AES-256-CBC and requires a secret
|
@@ -8,10 +9,9 @@ which is recommended to be kept in an .env file or something similar.
|
|
8
9
|
|
9
10
|
<a href='#usage-sect'><h4>Usage</h4></a>
|
10
11
|
|
11
|
-
<a href='#overview-sect'><h4>Overview</h4></a>
|
12
|
-
|
13
12
|
<a href='#default-sect'><h4>Default Options</h4></a>
|
14
13
|
|
14
|
+
<a href='#overview-sect'><h4>Overview</h4></a>
|
15
15
|
|
16
16
|
<h2 id='install-sect'>Installation</h2>
|
17
17
|
|
@@ -34,71 +34,78 @@ Full examples are in the *test/simple_session_test.rb* and
|
|
34
34
|
*test/simple_app.rb*. It's just a middleware so throw it on top of the stack.
|
35
35
|
|
36
36
|
```ruby
|
37
|
-
use SimpleSession::Session, secret:
|
37
|
+
use SimpleSession::Session, secret: SecureRandom.hex
|
38
|
+
```
|
39
|
+
**NOTE:** `:secret` must be 32 chars long.
|
40
|
+
|
41
|
+
<h4 id='default-sect'>Default Options</h4>
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
secret: nil
|
45
|
+
key: 'rack.session',
|
46
|
+
options_key: 'rack.session.options' ,
|
47
|
+
max_age: 172800,
|
48
|
+
path: '/',
|
49
|
+
domain: 'nil',
|
50
|
+
secure: false,
|
51
|
+
http_only: false
|
52
|
+
```
|
53
|
+
**NOTE:** For persistent options `:max_age` is excepted and the default is 2 days.
|
54
|
+
Because there are still IE versions that don't support max-age we inject both **max-age** and **expires** into the cookie and let the browser handle it.
|
55
|
+
|
56
|
+
The following is a simple example. The only **required argument is :secret**.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
require 'sinatra'
|
60
|
+
require 'simple_session'
|
61
|
+
|
62
|
+
class SimpleApp < Sinatra::Base
|
63
|
+
|
64
|
+
SECRET = SecureRandom.hex
|
65
|
+
use SimpleSession::Session, secret: SECRET
|
66
|
+
|
67
|
+
get '/signin' do
|
68
|
+
if session[:user_id]
|
69
|
+
"Already Signed in"
|
70
|
+
else
|
71
|
+
session[:user_id] = '!Green3ggsandHam!'
|
72
|
+
"Id: #{ session[:user_id] }"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
38
77
|
```
|
39
78
|
|
40
79
|
<h4 id='overview-sect'>Overview</h4>
|
41
80
|
SimpleSession is a simple Middleware that processes the session cookie
|
42
|
-
with
|
81
|
+
with 4 steps.
|
43
82
|
|
44
|
-
|
83
|
+
* Extract the session from the request if there is one. If there is no session
|
45
84
|
create a new one that looks like this:
|
46
85
|
|
47
86
|
```ruby
|
48
87
|
{ session_id: 'some secret id' }
|
49
88
|
```
|
50
|
-
|
51
|
-
2. Load the session data into the app environment so they are accessible with racks request methods like this:
|
52
|
-
|
89
|
+
* Load the session data into the app environment so they are accessible with racks request methods like this:
|
53
90
|
```ruby
|
54
91
|
get '/'
|
55
|
-
request.session
|
92
|
+
request.session
|
56
93
|
session
|
57
94
|
request.session_options
|
58
95
|
end
|
59
96
|
```
|
60
|
-
|
61
|
-
3. Clear the session if the time has expired and create a new one.
|
62
|
-
|
63
|
-
4. Update the options if they have been changed like this.
|
97
|
+
* Update the options if they have been changed like this.
|
64
98
|
|
65
99
|
```ruby
|
66
100
|
# This changes the session to expire one minute after
|
67
101
|
# the current time.
|
68
102
|
get '/'
|
69
|
-
request.session_options[:
|
103
|
+
request.session_options[:max_age] = 60
|
70
104
|
end
|
71
105
|
```
|
72
106
|
|
73
|
-
|
74
|
-
|
75
|
-
<h4 id='default-sect'>Default Options</h4>
|
107
|
+
* Create the new session cookie, encrypt it and return the response.
|
76
108
|
|
77
|
-
* secret: nil
|
78
|
-
* key: 'rack.session'
|
79
|
-
* expire_after: 7200
|
80
|
-
|
81
|
-
The following is a simple example. The only **required argument is :secret**.
|
82
|
-
|
83
|
-
```ruby
|
84
|
-
require 'sinatra'
|
85
|
-
require 'simple_session'
|
86
|
-
|
87
|
-
class SimpleApp < Sinatra::Base
|
88
|
-
|
89
|
-
use SimpleSession::Session, secret: 'Your Secret'
|
90
|
-
|
91
|
-
get '/signin' do
|
92
|
-
if session[:user_id]
|
93
|
-
"Already Signed in"
|
94
|
-
else
|
95
|
-
session[:user_id] = '!Green3ggsandHam!'
|
96
|
-
"Id: #{ session[:user_id] }"
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
end
|
101
|
-
```
|
102
109
|
|
103
110
|
## Development
|
104
111
|
|
data/lib/simple_session/base.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'rack/request'
|
2
2
|
require 'securerandom'
|
3
3
|
require 'openssl'
|
4
|
-
require 'byebug'
|
5
4
|
|
6
5
|
module SimpleSession
|
7
6
|
|
@@ -12,10 +11,25 @@ module SimpleSession
|
|
12
11
|
def initialize app, options = {}
|
13
12
|
@app = app
|
14
13
|
@key = options.fetch :key, 'rack.session'
|
15
|
-
@secret = options
|
16
|
-
@cipher_key = cipher_key
|
14
|
+
@secret = options.fetch :secret, get_secret_errors(options)
|
17
15
|
@options_key = options.fetch :options_key, 'rack.session.options'
|
18
|
-
@default_opts = options
|
16
|
+
@default_opts = DEFAULT_OPTS.merge!(options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_secret_errors options
|
20
|
+
secret = options[:secret]
|
21
|
+
missing_msg = %[
|
22
|
+
SimpleSession requires a secret like this:
|
23
|
+
use SimpleSession::Session, secret: 'some secret'
|
24
|
+
]
|
25
|
+
|
26
|
+
short_msg = %[
|
27
|
+
SimpleSession require a secret with a minimum length of 32
|
28
|
+
use SimpleSession::Session, secret: SecureRandom.hex(32)
|
29
|
+
]
|
30
|
+
|
31
|
+
raise ArgumentError, missing_msg unless secret
|
32
|
+
raise ArgumentError, short_msg unless secret.length >= 32
|
19
33
|
end
|
20
34
|
|
21
35
|
def req_session
|
@@ -26,15 +40,6 @@ module SimpleSession
|
|
26
40
|
session[:options] if session
|
27
41
|
end
|
28
42
|
|
29
|
-
def clear_session
|
30
|
-
@session = new_session_hash
|
31
|
-
@options = {options: OptionHash.new(@default_opts).opts}
|
32
|
-
end
|
33
|
-
|
34
|
-
def time_expired?
|
35
|
-
req_options && req_options[:expire] && req_options[:expire] <= Time.now
|
36
|
-
end
|
37
|
-
|
38
43
|
def new_session_hash
|
39
44
|
{ session_id: SecureRandom.hex(32) }
|
40
45
|
end
|
@@ -43,19 +48,15 @@ module SimpleSession
|
|
43
48
|
# Decrypt request session and store it
|
44
49
|
extract_session env
|
45
50
|
|
46
|
-
original_opts = @options[:options].dup
|
47
|
-
|
48
|
-
# Process options
|
49
|
-
clear_session if time_expired?
|
50
|
-
|
51
51
|
# Load session into app env
|
52
52
|
load_environment env
|
53
53
|
|
54
54
|
# Pass on request
|
55
55
|
status, headers, body = @app.call env
|
56
56
|
|
57
|
-
# Check session for changes and update
|
58
|
-
update_options if options_changed?
|
57
|
+
# Check session for changes and update
|
58
|
+
update_options if options_changed?
|
59
|
+
update_session if session_changed?
|
59
60
|
|
60
61
|
# Encrypt and add session to headers
|
61
62
|
add_session headers
|
@@ -64,27 +65,12 @@ module SimpleSession
|
|
64
65
|
end
|
65
66
|
|
66
67
|
private
|
67
|
-
|
68
|
-
def update_options
|
69
|
-
@options = {options: OptionHash.new(@options[:options]).opts}
|
70
|
-
end
|
71
|
-
|
72
|
-
def options_changed? original
|
73
|
-
original != @options[:options]
|
74
|
-
end
|
75
|
-
|
76
|
-
# If the session is nil create a new one
|
77
|
-
# If the session is unable to be decrypted throw an
|
78
|
-
# error and create a new one.
|
79
|
-
# encrypted data is not allowed in the app env
|
80
68
|
def extract_session env
|
81
69
|
begin
|
82
70
|
@request = Rack::Request.new env
|
83
71
|
@session = req_session ? decrypt(req_session) : new_session_hash
|
84
72
|
|
85
|
-
unless @session
|
86
|
-
raise ArgumentError, "Unable to decrypt session #{caller[0]}"
|
87
|
-
end
|
73
|
+
raise ArgumentError, "Unable to decrypt session" unless @session
|
88
74
|
|
89
75
|
rescue => e
|
90
76
|
@session = new_session_hash
|
@@ -94,63 +80,64 @@ module SimpleSession
|
|
94
80
|
@options = options_hash
|
95
81
|
end
|
96
82
|
|
83
|
+
def options_hash
|
84
|
+
o = session[:options] || OptionHash.new(@default_opts).opts
|
85
|
+
{ options: o }
|
86
|
+
end
|
87
|
+
|
97
88
|
def load_environment env
|
98
|
-
env[@key] = session
|
99
|
-
env[@options_key] = @options[:options]
|
89
|
+
env[@key] = session.dup
|
90
|
+
env[@options_key] = @options[:options].dup
|
100
91
|
end
|
101
92
|
|
102
93
|
def add_session headers
|
103
94
|
cookie = Hash.new
|
104
|
-
cookie[:value]
|
105
|
-
cookie
|
106
|
-
|
95
|
+
cookie[:value] = encrypt session.merge(@options)
|
96
|
+
cookie.merge!(@options[:options])
|
97
|
+
|
98
|
+
set_cookie_header headers, @key, cookie
|
107
99
|
end
|
108
100
|
|
109
101
|
def set_cookie_header headers, key, cookie
|
110
102
|
Rack::Utils.set_cookie_header! headers, key, cookie
|
111
103
|
end
|
112
104
|
|
113
|
-
def
|
114
|
-
|
115
|
-
|
105
|
+
def update_options
|
106
|
+
@options = {options: OptionHash.new(request.session_options).opts}
|
107
|
+
end
|
108
|
+
|
109
|
+
def options_changed?
|
110
|
+
request.session_options != @options[:options]
|
111
|
+
end
|
112
|
+
|
113
|
+
def session_changed?
|
114
|
+
request.session != @session
|
115
|
+
end
|
116
|
+
|
117
|
+
def update_session
|
118
|
+
@session = request.session
|
116
119
|
end
|
117
120
|
|
118
121
|
def encrypt data
|
119
|
-
|
120
|
-
|
121
|
-
# Serialize
|
122
|
-
m = Marshal.dump data
|
122
|
+
# Serialize
|
123
|
+
m = Marshal.dump data
|
123
124
|
|
124
|
-
|
125
|
-
|
125
|
+
# Cipher
|
126
|
+
c = load_cipher m
|
126
127
|
|
127
|
-
|
128
|
-
|
129
|
-
else
|
130
|
-
raise ArgumentError, "Internal session data must be a hash"
|
131
|
-
end
|
132
|
-
rescue => e
|
133
|
-
puts e.message
|
134
|
-
end
|
128
|
+
# Encode Base64
|
129
|
+
[c].pack('m')
|
135
130
|
end
|
136
131
|
|
137
132
|
def decrypt data
|
138
|
-
|
139
|
-
|
140
|
-
# Decode Base64
|
141
|
-
b = data.unpack('m').first
|
133
|
+
# Decode Base64
|
134
|
+
b = data.unpack('m').first
|
142
135
|
|
143
|
-
|
144
|
-
|
136
|
+
# Decipher
|
137
|
+
c = unload_cipher b
|
145
138
|
|
146
|
-
|
147
|
-
|
148
|
-
else
|
149
|
-
raise ArgumentError, "External session data must be a string."
|
150
|
-
end
|
151
|
-
rescue => e
|
152
|
-
puts e.message
|
153
|
-
end
|
139
|
+
# Deserialize
|
140
|
+
Marshal.load c
|
154
141
|
end
|
155
142
|
|
156
143
|
def digest
|
@@ -158,7 +145,7 @@ module SimpleSession
|
|
158
145
|
end
|
159
146
|
|
160
147
|
def hmac data
|
161
|
-
OpenSSL::HMAC.digest digest, @
|
148
|
+
OpenSSL::HMAC.digest digest, @secret, data
|
162
149
|
end
|
163
150
|
|
164
151
|
def new_cipher
|
@@ -170,7 +157,7 @@ module SimpleSession
|
|
170
157
|
cipher.encrypt
|
171
158
|
iv = cipher.random_iv
|
172
159
|
cipher.iv = iv
|
173
|
-
cipher.key =
|
160
|
+
cipher.key = cipher_key
|
174
161
|
|
175
162
|
iv + cipher.update(marshaled_data) + cipher.final
|
176
163
|
end
|
@@ -179,7 +166,7 @@ module SimpleSession
|
|
179
166
|
cipher = new_cipher
|
180
167
|
cipher.decrypt
|
181
168
|
cipher.iv = data[0, 16]
|
182
|
-
cipher.key =
|
169
|
+
cipher.key = cipher_key
|
183
170
|
cipher.update(data[16..-1]) + cipher.final
|
184
171
|
end
|
185
172
|
|
@@ -192,28 +179,17 @@ module SimpleSession
|
|
192
179
|
SANITATION = [:key, :secret, :options_key]
|
193
180
|
|
194
181
|
def initialize args
|
195
|
-
@opts =
|
182
|
+
@opts = args
|
196
183
|
process_request_options
|
197
|
-
|
198
|
-
create_readers
|
184
|
+
sanitize_opts
|
199
185
|
end
|
200
186
|
|
201
187
|
def opts
|
202
188
|
@opts
|
203
189
|
end
|
204
190
|
|
205
|
-
def
|
206
|
-
|
207
|
-
end
|
208
|
-
|
209
|
-
def create_readers
|
210
|
-
@opts.keys.each do |k|
|
211
|
-
create_reader(k) { instance_variable_get "@#{k}"}
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
def set_instance_variables
|
216
|
-
@opts.each { |k, v| instance_variable_set "@#{k.to_s}", v}
|
191
|
+
def sanitize_opts
|
192
|
+
@opts = sanitize @opts
|
217
193
|
end
|
218
194
|
|
219
195
|
def process_request_options
|
@@ -225,26 +201,18 @@ module SimpleSession
|
|
225
201
|
end
|
226
202
|
|
227
203
|
def p_time
|
228
|
-
@opts[:
|
229
|
-
@opts[:
|
204
|
+
time = Time.now + @opts[:max_age].to_i
|
205
|
+
@opts[:expires] = time if @opts[:max_age]
|
230
206
|
end
|
231
207
|
|
232
208
|
private
|
233
209
|
def sanitize args
|
234
|
-
dup_args = args
|
235
210
|
SANITATION.each do |key|
|
236
|
-
|
211
|
+
args.delete(key) if args[key]
|
237
212
|
end
|
238
|
-
|
239
|
-
end
|
240
|
-
|
241
|
-
def default_time
|
242
|
-
60 * 60 * 2
|
213
|
+
args
|
243
214
|
end
|
244
215
|
|
245
216
|
end
|
246
|
-
|
247
217
|
end
|
248
|
-
|
249
218
|
end
|
250
|
-
|
data/lib/simple_session.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
require "simple_session/version"
|
2
2
|
require "simple_session/base.rb"
|
3
3
|
|
4
|
-
# Options
|
5
|
-
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
6
|
-
# key = options.fetch :key, 'rack.session'
|
7
|
-
# secret = options[:secret]
|
8
|
-
# options_key = options.fetch :options_key, 'rack.session.options'
|
9
|
-
|
10
4
|
module SimpleSession
|
5
|
+
DEFAULT_OPTS = {
|
6
|
+
max_age: 172800 ,
|
7
|
+
domain: nil,
|
8
|
+
path: '/',
|
9
|
+
http_only: false,
|
10
|
+
secure: false,
|
11
|
+
}
|
11
12
|
|
12
13
|
class Session < Base
|
13
14
|
|
data/simple_session.gemspec
CHANGED
@@ -24,7 +24,6 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_development_dependency "bundler", "~> 1.10"
|
25
25
|
spec.add_development_dependency "rake", "~> 10.0"
|
26
26
|
spec.add_development_dependency "minitest"
|
27
|
-
spec.add_development_dependency "byebug"
|
28
27
|
spec.add_development_dependency "rack-test"
|
29
28
|
spec.add_development_dependency "sinatra"
|
30
29
|
spec.add_development_dependency "timecop"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_session
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- hayduke19us
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-11-
|
11
|
+
date: 2015-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: byebug
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: rack-test
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|