tachiban 1.0.0 → 2.0.0
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/README.md +190 -76
- data/Rakefile +0 -2
- data/lib/tachiban/version.rb +1 -1
- data/lib/tachiban.rb +85 -42
- data/tachiban.gemspec +7 -10
- metadata +22 -70
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4f726149a2c73b44a9eb4ead5ea276752babfe6e627b71ff059604c1c3a81581
|
|
4
|
+
data.tar.gz: a598cc9a3425cfd5b5fc9f27e9abd3b1e32a8fbf9bcc6707e4f93ebb069a9739
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 75a5e0d5e5596859d868a29368fbb228d20badf0f67eca13dbcbf6f00b56c579f22da3b5855fc3f14577dcb61cf75ce000c6e802586639a0c1269a8a3681277a
|
|
7
|
+
data.tar.gz: e559a58ebd9907225906166d222da3f95e5071e7d2cb2ae3f00ea2763502911d20aceadb1f89e909565a2ecd71014034000abac46d2086942b2fa56675f281aa
|
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Tachiban
|
|
2
2
|
|
|
3
|
-
[](https://gitter.im/sebastjan-hribar/tachiban?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://badge.fury.io/rb/tachiban)
|
|
3
|
+
[](https://gitter.im/sebastjan-hribar/tachiban?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://badge.fury.io/rb/tachiban)
|
|
4
4
|
|
|
5
|
-
Tachiban (立ち番 - standing watch) provides simple authentication system for [Hanami web applications](http://hanamirb.org/) by using Argon2 for password hashing and
|
|
5
|
+
Tachiban (立ち番 - standing watch) provides simple authentication system for [Hanami 2.x web applications](http://hanamirb.org/) by using Argon2 for password hashing and
|
|
6
6
|
offers the following functionalities (with methods listed below
|
|
7
7
|
under Methods by features):
|
|
8
8
|
- Signup
|
|
@@ -12,7 +12,10 @@ offers the following functionalities (with methods listed below
|
|
|
12
12
|
- Password reset
|
|
13
13
|
- Authorization has been moved to [Rokku](https://github.com/sebastjan-hribar/rokku)
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
**Note:** For Hanami 1.3 support, see the [1.0.0 branch](https://github.com/sebastjan-hribar/tachiban/tree/1.0.0) or install Tachiban 1.0.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## 1. Installation
|
|
16
19
|
|
|
17
20
|
Add this line to your application's Gemfile:
|
|
18
21
|
|
|
@@ -28,21 +31,43 @@ Or install it yourself as:
|
|
|
28
31
|
|
|
29
32
|
$ gem install tachiban
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
|
|
35
|
+
Tachiban 2.0 needs to be included in the action:
|
|
32
36
|
|
|
33
37
|
```ruby
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
# app/action.rb
|
|
39
|
+
# auto_register: false
|
|
40
|
+
# frozen_string_literal: true
|
|
41
|
+
|
|
42
|
+
require "hanami/action"
|
|
43
|
+
require "dry/monads"
|
|
44
|
+
require "tachiban"
|
|
45
|
+
|
|
46
|
+
module MyApplication
|
|
47
|
+
class Action < Hanami::Action
|
|
48
|
+
# Provide `Success` and `Failure` for pattern matching on operation results
|
|
49
|
+
include Dry::Monads[:result]
|
|
36
50
|
include Hanami::Tachiban
|
|
51
|
+
|
|
52
|
+
handle_exception "ROM::TupleCountMismatchError" => :handle_not_found
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def handle_not_found(request, response, exception)
|
|
57
|
+
response.status = 404
|
|
58
|
+
response.format = :html
|
|
59
|
+
response.body = "Not found"
|
|
60
|
+
end
|
|
37
61
|
end
|
|
38
62
|
end
|
|
39
63
|
```
|
|
40
64
|
|
|
41
|
-
## Usage
|
|
65
|
+
## 2. Usage
|
|
42
66
|
|
|
43
|
-
### Prerequisites
|
|
67
|
+
### 2.1 Prerequisites
|
|
44
68
|
Prior to logging in or authenticating the user, retrieve the entity from the
|
|
45
|
-
database and assign it to
|
|
69
|
+
database and assign it to a variable (e.g. `user`), which you then pass to
|
|
70
|
+
the methods as required.
|
|
46
71
|
|
|
47
72
|
In addition to that, the user entity must have the following attributes:
|
|
48
73
|
|
|
@@ -51,136 +76,225 @@ In addition to that, the user entity must have the following attributes:
|
|
|
51
76
|
* **hashed_pass** (to hold the generated hashed password)
|
|
52
77
|
|
|
53
78
|
|
|
54
|
-
### Usage
|
|
79
|
+
### 2.2 Usage
|
|
55
80
|
|
|
56
|
-
#### Signup
|
|
81
|
+
#### 2.2.3 Signup
|
|
57
82
|
To create a user with a hashed password use the `hashed_password(password)`
|
|
58
83
|
method for the password and store it as the user's attribute `hashed_pass`.
|
|
59
84
|
|
|
60
85
|
*Example*
|
|
61
86
|
|
|
62
87
|
```ruby
|
|
63
|
-
# Create action for
|
|
64
|
-
def
|
|
65
|
-
password = params[:newuser][:password]
|
|
88
|
+
# Create action for the user
|
|
89
|
+
def handle(request, response)
|
|
90
|
+
password = request.params[:newuser][:password]
|
|
66
91
|
hashed_pass = hashed_password(password)
|
|
67
|
-
repository = UserRepository.new
|
|
68
92
|
|
|
69
|
-
|
|
70
|
-
hashed_pass: hashed_pass)
|
|
93
|
+
user = user_repo.create(name: name, surname: surname, email: email,
|
|
94
|
+
hashed_pass: hashed_pass)
|
|
71
95
|
end
|
|
72
96
|
```
|
|
73
97
|
|
|
74
|
-
#### Authentication and login
|
|
75
|
-
To authenticate a user use the `authenticated?(input_password)` method and log
|
|
76
|
-
them in with the `login
|
|
98
|
+
#### 2.2.4 Authentication and login
|
|
99
|
+
To authenticate a user use the `authenticated?(input_password, user)` method and log
|
|
100
|
+
them in with the `login(request, response, user_id, flash_message: nil, login_redirect_url: nil)` method.
|
|
101
|
+
|
|
102
|
+
Authentication is successful if the user exists and passwords match. It's possible to provide your own flash message and / or redirect url. Otherwise, the **default values** will be used (see the table below).
|
|
77
103
|
|
|
78
|
-
The user is logged in by setting the user object ID as the `session[:current_user]`.
|
|
79
|
-
After the user is logged in the session start time is defined as
|
|
80
|
-
`session[:session_start_time] = Time.now`. A default flash message is also
|
|
104
|
+
The user is logged in by setting the user object ID as the `request.session[:current_user]`.
|
|
105
|
+
After the user is logged in, the session start time is defined as
|
|
106
|
+
`request.session[:session_start_time] = Time.now`. A default flash message is also
|
|
81
107
|
assigned as 'You have been successfully logged in.'.
|
|
82
108
|
|
|
83
|
-
The `session[:session_start_time]` is then used by the `session_expired
|
|
84
|
-
method to determine whether the session has expired or not.
|
|
109
|
+
The `request.session[:session_start_time]` is then used by the `session_expired?(request, response)` method to determine whether the session has expired or not.
|
|
85
110
|
|
|
86
|
-
|
|
111
|
+
**_Example of session creation for an entity_**
|
|
87
112
|
|
|
88
113
|
```ruby
|
|
89
|
-
# Create action for
|
|
90
|
-
email = params[:entity_session][:email]
|
|
91
|
-
password = params[:entity_session][:password]
|
|
114
|
+
# Create action for the user session
|
|
115
|
+
email = request.params[:entity_session][:email]
|
|
116
|
+
password = request.params[:entity_session][:password]
|
|
92
117
|
|
|
93
|
-
|
|
94
|
-
login if authenticated?(password)
|
|
118
|
+
user = user_repo.find_by_email(email) #required by login
|
|
119
|
+
login(request, response, user.id) if authenticated?(password, user)
|
|
95
120
|
```
|
|
96
121
|
|
|
97
|
-
To check whether
|
|
98
|
-
If the user is not logged in the `logout` method takes over.
|
|
122
|
+
To check whether a user is logged in, use the `check_for_logged_in_user(request, response)` method. If the user is not logged in, the `logout(request, response, logout_redirect_url: nil)` method takes over.
|
|
99
123
|
|
|
100
124
|
|
|
101
|
-
#### Session handling
|
|
125
|
+
#### 2.2.5 Session handling
|
|
102
126
|
Tachiban handles session expiration by checking if a session has
|
|
103
127
|
expired and then restarts the session start time if the session
|
|
104
128
|
is still valid or proceeds with the following if the session
|
|
105
129
|
has expired:
|
|
106
130
|
|
|
107
|
-
- setting the `session[:current_user]` to `nil`,
|
|
108
|
-
- a flash message is set: `flash[:failed_notice] = "Your session has expired"`,
|
|
109
|
-
- redirects to the
|
|
110
|
-
a different url to @redirect_url.
|
|
131
|
+
- setting the `request.session[:current_user]` to `nil`,
|
|
132
|
+
- a flash message is set: `response.flash[:failed_notice] = "Your session has expired"`,
|
|
133
|
+
- redirects to the root path `/`, which can be overwritten.
|
|
111
134
|
|
|
112
135
|
|
|
113
|
-
The `session_expired
|
|
114
|
-
increased for the defined
|
|
136
|
+
The `session_expired?(request, validity_time: nil)` method compares the session start time
|
|
137
|
+
increased for the defined `validity_time` (set to 10 minutes
|
|
115
138
|
by default, but can be overwritten) with the current time.
|
|
116
139
|
|
|
117
|
-
`handle_session` method:
|
|
140
|
+
`handle_session(request, response, redirect_url: nil)` method:
|
|
118
141
|
```ruby
|
|
119
|
-
def handle_session
|
|
120
|
-
if session_expired?
|
|
121
|
-
|
|
122
|
-
session[:current_user] = nil
|
|
123
|
-
flash[:failed_notice]
|
|
124
|
-
redirect_to
|
|
142
|
+
def handle_session(request, response, redirect_url: nil)
|
|
143
|
+
if session_expired?(request)
|
|
144
|
+
redirect_url ||= '/'
|
|
145
|
+
request.session[:current_user] = nil
|
|
146
|
+
response.flash[:failed_notice] ||= "Your session has expired."
|
|
147
|
+
response.redirect_to redirect_url
|
|
125
148
|
else
|
|
126
|
-
restart_session_counter
|
|
149
|
+
restart_session_counter(request)
|
|
127
150
|
end
|
|
128
151
|
end
|
|
129
152
|
```
|
|
130
153
|
|
|
131
|
-
|
|
154
|
+
#### 2.2.6 Session handling in a share code module
|
|
155
|
+
It is possible to enable session handling in a share code module as provided by Hanami.
|
|
156
|
+
To do this, create an authentication module in **app/actions/authentication.rb**.
|
|
157
|
+
The example below shows also how to custom values to replace default values in
|
|
158
|
+
actions.
|
|
132
159
|
|
|
133
160
|
```ruby
|
|
134
|
-
module
|
|
135
|
-
module
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
161
|
+
module MyApplication
|
|
162
|
+
module Actions
|
|
163
|
+
module Authentication
|
|
164
|
+
def self.included(action_class)
|
|
165
|
+
action_class.class_eval do
|
|
166
|
+
before :check_for_logged_in_user
|
|
167
|
+
before :handle_session
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
def custom_handle_session_redirect_url
|
|
174
|
+
'/login'
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def custom_logout_redirect_url
|
|
178
|
+
'/login'
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def custom_login_redirect_url
|
|
182
|
+
'/'
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def custom_session_validity_time
|
|
186
|
+
if ENV['HANAMI_ENV'] == 'test'
|
|
187
|
+
600
|
|
188
|
+
else
|
|
189
|
+
1800
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
143
193
|
end
|
|
144
194
|
end
|
|
145
195
|
```
|
|
196
|
+
We can then simply include the `Authentication` module in actions, where required.
|
|
146
197
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
The password reset feature provides a few simple methods to generate a
|
|
150
|
-
token, email subject and body. It is also possible to specify and
|
|
151
|
-
check the validity of the password reset url.
|
|
198
|
+
However, if we include this in the base action class, it will be available in all
|
|
199
|
+
actions and there is no need for separate includes in actions:
|
|
152
200
|
|
|
153
201
|
```ruby
|
|
154
|
-
|
|
155
|
-
|
|
202
|
+
#
|
|
203
|
+
#
|
|
204
|
+
module MyApplication
|
|
205
|
+
class Action < Hanami::Action
|
|
206
|
+
# Provide `Success` and `Failure` for pattern matching on operation results
|
|
207
|
+
include Dry::Monads[:result]
|
|
208
|
+
include Hanami::Tachiban
|
|
209
|
+
include MyApplication::Actions::Authentication
|
|
156
210
|
|
|
157
|
-
|
|
158
|
-
|
|
211
|
+
handle_exception "ROM::TupleCountMismatchError" => :handle_not_found
|
|
212
|
+
|
|
213
|
+
private
|
|
214
|
+
#
|
|
215
|
+
#
|
|
159
216
|
```
|
|
160
217
|
|
|
161
218
|
|
|
162
|
-
|
|
163
|
-
|
|
219
|
+
**_Disabling the authentication shared module in specific actions_**
|
|
220
|
+
|
|
221
|
+
Sometimes we might not want to check for authenticated user. For example,
|
|
222
|
+
doing so in the `login` action will cause an infinite loop. There we can
|
|
223
|
+
disable the module by overwriting the desired methods in the action:
|
|
164
224
|
|
|
165
225
|
```ruby
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
226
|
+
private
|
|
227
|
+
|
|
228
|
+
def check_for_logged_in_user; end
|
|
229
|
+
def handle_session; end
|
|
169
230
|
```
|
|
170
231
|
|
|
232
|
+
#### 2.2.7 Password reset
|
|
233
|
+
The password reset feature provides a few simple methods to:
|
|
234
|
+
* generate a token, email subject and body (text and html part)
|
|
235
|
+
* specify and check the validity of the password reset url and
|
|
236
|
+
* set the default application name for the email subject (it can be
|
|
237
|
+
overwritten or set as a ENV variable).
|
|
238
|
+
|
|
171
239
|
The link validity must me specified in seconds. The method compares the
|
|
172
240
|
current time with the time when the password reset link was sent increased
|
|
173
|
-
by the link validity: `Time.now >
|
|
241
|
+
by the link validity: `Time.now > user.password_reset_sent_at + link_validity`.
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
token # => "YbRucc8YUlFJrYYp04eQKQ"
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
```ruby
|
|
249
|
+
email_subject("SomeApp") # => "SomeApp -- password reset request"
|
|
250
|
+
```
|
|
174
251
|
|
|
175
252
|
```ruby
|
|
176
253
|
password_reset_url_valid?(link_validity)
|
|
177
254
|
```
|
|
178
255
|
|
|
179
|
-
### Example of use in an application
|
|
180
|
-
[Using Tachiban with a Hanami app](https://sebastjan-hribar.github.io/programming/2021/09/03/tachiban-with-hanami.html)
|
|
181
256
|
|
|
182
257
|
|
|
183
|
-
|
|
258
|
+
Provide the following values when building the email body: reset url, user's name,
|
|
259
|
+
link validity, time unit and optionally the application name. Below is an example of
|
|
260
|
+
html_body in a mailer class:
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
html_body = email_body_html(
|
|
264
|
+
reset_url: reset_url,
|
|
265
|
+
user_name: "#{user.name} #{user.surname}",
|
|
266
|
+
link_validity: 2,
|
|
267
|
+
time_unit: "hour",
|
|
268
|
+
app_name: nil
|
|
269
|
+
)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### 3. Default values
|
|
273
|
+
There are a few default values set which can be overwritten. See the table below.
|
|
274
|
+
|
|
275
|
+
|Required by method |Variable |Default value |
|
|
276
|
+
|--- |--- |--- |
|
|
277
|
+
|login |flash_message |'You have been successfully logged in.'|
|
|
278
|
+
|login |login_redirect_url |'/' |
|
|
279
|
+
|logout |logout_redirect_url |'/login' |
|
|
280
|
+
|session_expired? |validity_time |600 |
|
|
281
|
+
|handle_session |redirect_url |'/' |
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
### 4. Changelog
|
|
287
|
+
|
|
288
|
+
#### 2.0.0
|
|
289
|
+
|
|
290
|
+
**Breaking Changes:**
|
|
291
|
+
- Supports Hanami ~> 2.0 applications only.
|
|
292
|
+
- Method signatures updated: `login`, `logout`, `check_for_logged_in_user`, and `handle_session` now require `(request, response)` parameters.
|
|
293
|
+
- Methods `session_expired?` and `restart_session_counter` now require `(request)` parameter.
|
|
294
|
+
- Tachiban must be explicitly included in the base action: `include Hanami::Tachiban`.
|
|
295
|
+
- Tachiban 2.0.0 doesn't rely on instance variable like `@user` anymore. Instead, a `user` variable must be passed as an argument to a method.
|
|
296
|
+
|
|
297
|
+
For Hanami 1.3 support, use Tachiban 1.0.
|
|
184
298
|
|
|
185
299
|
#### 1.0.0
|
|
186
300
|
|
data/Rakefile
CHANGED
data/lib/tachiban/version.rb
CHANGED
data/lib/tachiban.rb
CHANGED
|
@@ -28,8 +28,8 @@ private
|
|
|
28
28
|
# - a user exists
|
|
29
29
|
# - a user's hashed password from the database matches the input password
|
|
30
30
|
|
|
31
|
-
def authenticated?(input_pass)
|
|
32
|
-
|
|
31
|
+
def authenticated?(input_pass, user)
|
|
32
|
+
user && Argon2::Password.verify_password(input_pass, user.hashed_pass)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
# The login method can be used in combination with the authenticated? method to
|
|
@@ -41,30 +41,30 @@ private
|
|
|
41
41
|
|
|
42
42
|
# There are two defualt values set: one for flash message and
|
|
43
43
|
# the other for redirect url. Both can be overwritten by assigning
|
|
44
|
-
# new values for
|
|
44
|
+
# new values for flash_message and login_redirect_url arguments.
|
|
45
45
|
|
|
46
46
|
# Example:
|
|
47
47
|
# login if authenticated?(input_pass)
|
|
48
48
|
|
|
49
|
-
def login
|
|
50
|
-
session[:current_user] =
|
|
51
|
-
session[:session_start_time] = Time.now
|
|
52
|
-
|
|
53
|
-
flash[:success_notice] =
|
|
54
|
-
|
|
55
|
-
redirect_to
|
|
49
|
+
def login(request, response, user_id, flash_message: nil, login_redirect_url: nil)
|
|
50
|
+
request.session[:current_user] = user_id
|
|
51
|
+
request.session[:session_start_time] = Time.now
|
|
52
|
+
flash_message ||= 'You have been successfully logged in.'
|
|
53
|
+
response.flash[:success_notice] = flash_message
|
|
54
|
+
login_redirect_url ||= "/"
|
|
55
|
+
response.redirect_to login_redirect_url
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# The logout method sets the current user in the session to nil
|
|
59
59
|
# and performs a redirect to the redirect_url which is set to
|
|
60
60
|
# /login, but can be overwritten as needed with a specific url
|
|
61
|
-
# by setting a new value for
|
|
61
|
+
# by setting a new value for logout_redirect_url.
|
|
62
62
|
|
|
63
|
-
def logout
|
|
64
|
-
session[:current_user] = nil
|
|
65
|
-
session.clear
|
|
66
|
-
|
|
67
|
-
redirect_to
|
|
63
|
+
def logout(request, response, logout_redirect_url: nil)
|
|
64
|
+
request.session[:current_user] = nil
|
|
65
|
+
request.session.clear
|
|
66
|
+
logout_redirect_url ||= '/login'
|
|
67
|
+
response.redirect_to logout_redirect_url
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
# ### Authentication ###
|
|
@@ -73,8 +73,8 @@ private
|
|
|
73
73
|
# request whether the user is logged in. If the user is not logged in
|
|
74
74
|
# the logout method takes over.
|
|
75
75
|
|
|
76
|
-
def check_for_logged_in_user
|
|
77
|
-
logout unless session[:current_user]
|
|
76
|
+
def check_for_logged_in_user(request, response)
|
|
77
|
+
logout(request, response) unless request.session[:current_user]
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
# ### Session handling ###
|
|
@@ -86,18 +86,18 @@ private
|
|
|
86
86
|
# increased for the defined validity time (set to 10 minutes
|
|
87
87
|
# by default and can be overwritten) with the current time.
|
|
88
88
|
|
|
89
|
-
def session_expired?
|
|
90
|
-
if session[:current_user]
|
|
91
|
-
|
|
92
|
-
session[:session_start_time] +
|
|
89
|
+
def session_expired?(request, validity_time: nil)
|
|
90
|
+
if request.session[:current_user]
|
|
91
|
+
validity_time ||= 600
|
|
92
|
+
request.session[:session_start_time] + validity_time.to_i < Time.now
|
|
93
93
|
end
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
# The restart_session_counter method resets the session start time to
|
|
97
97
|
# Time.now. It's used in the handle session method.
|
|
98
98
|
|
|
99
|
-
def restart_session_counter
|
|
100
|
-
session[:session_start_time] = Time.now
|
|
99
|
+
def restart_session_counter(request)
|
|
100
|
+
request.session[:session_start_time] = Time.now
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
# The handle_session method is used to handle the incoming requests
|
|
@@ -109,40 +109,83 @@ private
|
|
|
109
109
|
# If the session hasn't expired the restart_session_counter method is
|
|
110
110
|
# called to reset the session start time.
|
|
111
111
|
|
|
112
|
-
def handle_session
|
|
113
|
-
if session_expired?
|
|
114
|
-
|
|
115
|
-
session[:current_user] = nil
|
|
116
|
-
flash[:failed_notice]
|
|
117
|
-
redirect_to
|
|
112
|
+
def handle_session(request, response, redirect_url: nil)
|
|
113
|
+
if session_expired?(request)
|
|
114
|
+
redirect_url ||= "/"
|
|
115
|
+
request.session[:current_user] = nil
|
|
116
|
+
response.flash[:failed_notice] ||= 'Your session has expired.'
|
|
117
|
+
response.redirect_to redirect_url
|
|
118
118
|
else
|
|
119
|
-
restart_session_counter
|
|
119
|
+
restart_session_counter(request)
|
|
120
120
|
end
|
|
121
121
|
end
|
|
122
122
|
|
|
123
123
|
# ### Password reset ###
|
|
124
|
+
# The password reset functionalities include token generation, email subject,
|
|
125
|
+
# email body in the text as well as in the html format, checking the reset link
|
|
126
|
+
# validity and getting the app name.
|
|
127
|
+
|
|
124
128
|
def token
|
|
125
129
|
SecureRandom.urlsafe_base64
|
|
126
130
|
end
|
|
127
131
|
|
|
128
132
|
def email_subject(app_name)
|
|
133
|
+
app_name ||= default_app_name
|
|
129
134
|
"#{app_name} -- password reset request"
|
|
130
135
|
end
|
|
131
136
|
|
|
132
|
-
def
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
def email_body_text(reset_url:, user_name:, link_validity:, time_unit:, app_name: nil)
|
|
138
|
+
app_name ||= default_app_name
|
|
139
|
+
|
|
140
|
+
<<~TEXT
|
|
141
|
+
Hello #{user_name},
|
|
142
|
+
|
|
143
|
+
Click the link below to reset your password:
|
|
144
|
+
|
|
145
|
+
#{reset_url}
|
|
146
|
+
|
|
147
|
+
This link will expire in #{link_validity} #{time_unit}(s).
|
|
148
|
+
|
|
149
|
+
Kind regards,
|
|
150
|
+
The #{app_name} Team
|
|
151
|
+
TEXT
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def email_body_html(reset_url:, user_name:, link_validity:, time_unit:, app_name: nil)
|
|
155
|
+
app_name ||= default_app_name
|
|
156
|
+
|
|
157
|
+
<<~HTML
|
|
158
|
+
<!DOCTYPE html>
|
|
159
|
+
<html>
|
|
160
|
+
<body style="font-family: Arial, sans-serif;">
|
|
161
|
+
<h2>Password reset request</h2>
|
|
162
|
+
<br>
|
|
163
|
+
<p>Hello #{user_name},</p>
|
|
164
|
+
<br>
|
|
165
|
+
<p>Click the button below to reset your password:</p>
|
|
166
|
+
<p>
|
|
167
|
+
<a href="#{reset_url}" style="background: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;">
|
|
168
|
+
Reset Password
|
|
169
|
+
</a>
|
|
170
|
+
</p>
|
|
171
|
+
<br>
|
|
172
|
+
<p>Or copy this link: #{reset_url}</p>
|
|
173
|
+
<p style="color: #666; font-size: 12px;">This link expires in #{link_validity} #{time_unit}(s).</p>
|
|
174
|
+
</body>
|
|
175
|
+
</html>
|
|
176
|
+
HTML
|
|
135
177
|
end
|
|
136
178
|
|
|
137
179
|
# State the link_validity in seconds.
|
|
138
|
-
def password_reset_url_valid?(link_validity)
|
|
139
|
-
Time.now <
|
|
180
|
+
def password_reset_url_valid?(link_validity, user)
|
|
181
|
+
Time.now < user.password_reset_sent_at + link_validity
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def default_app_name
|
|
185
|
+
ENV.fetch("APP_NAME") { Hanami.app.namespace.to_s }
|
|
186
|
+
rescue
|
|
187
|
+
"Application"
|
|
140
188
|
end
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
189
|
|
|
144
|
-
::Hanami::Controller.configure do
|
|
145
|
-
prepare do
|
|
146
|
-
include Hanami::Tachiban
|
|
147
190
|
end
|
|
148
|
-
end
|
|
191
|
+
end
|
data/tachiban.gemspec
CHANGED
|
@@ -17,16 +17,13 @@ Gem::Specification.new do |spec|
|
|
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
18
|
spec.require_paths = ["lib"]
|
|
19
19
|
|
|
20
|
-
spec.add_development_dependency "
|
|
21
|
-
spec.add_development_dependency "rake", "~> 12.3", ">= 12.3.3"
|
|
20
|
+
spec.add_development_dependency "rake"
|
|
22
21
|
spec.add_development_dependency "minitest", "~> 5.0"
|
|
23
|
-
spec.add_development_dependency "
|
|
24
|
-
spec.add_development_dependency
|
|
25
|
-
spec.add_development_dependency '
|
|
26
|
-
spec.add_development_dependency 'hanami-router', "~> 1.0"
|
|
27
|
-
spec.add_development_dependency 'pry', "~> 0"
|
|
22
|
+
spec.add_development_dependency "timecop", "0.9.0"
|
|
23
|
+
spec.add_development_dependency 'hanami', "~> 2.0"
|
|
24
|
+
spec.add_development_dependency 'pry', "~> 0.16.0"
|
|
28
25
|
|
|
29
26
|
spec.add_runtime_dependency "argon2", "~> 2.3"
|
|
30
|
-
spec.add_runtime_dependency 'hanami
|
|
31
|
-
spec.add_runtime_dependency
|
|
32
|
-
end
|
|
27
|
+
spec.add_runtime_dependency 'hanami', "~> 2.0"
|
|
28
|
+
spec.add_runtime_dependency "hanami-controller", "~> 2.0"
|
|
29
|
+
end
|
metadata
CHANGED
|
@@ -1,49 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tachiban
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sebastjan Hribar
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-03-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: bundler
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - "~>"
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '2.0'
|
|
20
|
-
type: :development
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - "~>"
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '2.0'
|
|
27
13
|
- !ruby/object:Gem::Dependency
|
|
28
14
|
name: rake
|
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
|
30
16
|
requirements:
|
|
31
|
-
- - "~>"
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '12.3'
|
|
34
17
|
- - ">="
|
|
35
18
|
- !ruby/object:Gem::Version
|
|
36
|
-
version:
|
|
19
|
+
version: '0'
|
|
37
20
|
type: :development
|
|
38
21
|
prerelease: false
|
|
39
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
40
23
|
requirements:
|
|
41
|
-
- - "~>"
|
|
42
|
-
- !ruby/object:Gem::Version
|
|
43
|
-
version: '12.3'
|
|
44
24
|
- - ">="
|
|
45
25
|
- !ruby/object:Gem::Version
|
|
46
|
-
version:
|
|
26
|
+
version: '0'
|
|
47
27
|
- !ruby/object:Gem::Dependency
|
|
48
28
|
name: minitest
|
|
49
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -58,76 +38,48 @@ dependencies:
|
|
|
58
38
|
- - "~>"
|
|
59
39
|
- !ruby/object:Gem::Version
|
|
60
40
|
version: '5.0'
|
|
61
|
-
- !ruby/object:Gem::Dependency
|
|
62
|
-
name: hanami-model
|
|
63
|
-
requirement: !ruby/object:Gem::Requirement
|
|
64
|
-
requirements:
|
|
65
|
-
- - "~>"
|
|
66
|
-
- !ruby/object:Gem::Version
|
|
67
|
-
version: '1.0'
|
|
68
|
-
type: :development
|
|
69
|
-
prerelease: false
|
|
70
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
71
|
-
requirements:
|
|
72
|
-
- - "~>"
|
|
73
|
-
- !ruby/object:Gem::Version
|
|
74
|
-
version: '1.0'
|
|
75
41
|
- !ruby/object:Gem::Dependency
|
|
76
42
|
name: timecop
|
|
77
43
|
requirement: !ruby/object:Gem::Requirement
|
|
78
44
|
requirements:
|
|
79
45
|
- - '='
|
|
80
46
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: 0.
|
|
47
|
+
version: 0.9.0
|
|
82
48
|
type: :development
|
|
83
49
|
prerelease: false
|
|
84
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
85
51
|
requirements:
|
|
86
52
|
- - '='
|
|
87
53
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: 0.
|
|
89
|
-
- !ruby/object:Gem::Dependency
|
|
90
|
-
name: hanami-controller
|
|
91
|
-
requirement: !ruby/object:Gem::Requirement
|
|
92
|
-
requirements:
|
|
93
|
-
- - "~>"
|
|
94
|
-
- !ruby/object:Gem::Version
|
|
95
|
-
version: '1.0'
|
|
96
|
-
type: :development
|
|
97
|
-
prerelease: false
|
|
98
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
99
|
-
requirements:
|
|
100
|
-
- - "~>"
|
|
101
|
-
- !ruby/object:Gem::Version
|
|
102
|
-
version: '1.0'
|
|
54
|
+
version: 0.9.0
|
|
103
55
|
- !ruby/object:Gem::Dependency
|
|
104
|
-
name: hanami
|
|
56
|
+
name: hanami
|
|
105
57
|
requirement: !ruby/object:Gem::Requirement
|
|
106
58
|
requirements:
|
|
107
59
|
- - "~>"
|
|
108
60
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: '
|
|
61
|
+
version: '2.0'
|
|
110
62
|
type: :development
|
|
111
63
|
prerelease: false
|
|
112
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
113
65
|
requirements:
|
|
114
66
|
- - "~>"
|
|
115
67
|
- !ruby/object:Gem::Version
|
|
116
|
-
version: '
|
|
68
|
+
version: '2.0'
|
|
117
69
|
- !ruby/object:Gem::Dependency
|
|
118
70
|
name: pry
|
|
119
71
|
requirement: !ruby/object:Gem::Requirement
|
|
120
72
|
requirements:
|
|
121
73
|
- - "~>"
|
|
122
74
|
- !ruby/object:Gem::Version
|
|
123
|
-
version:
|
|
75
|
+
version: 0.16.0
|
|
124
76
|
type: :development
|
|
125
77
|
prerelease: false
|
|
126
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
127
79
|
requirements:
|
|
128
80
|
- - "~>"
|
|
129
81
|
- !ruby/object:Gem::Version
|
|
130
|
-
version:
|
|
82
|
+
version: 0.16.0
|
|
131
83
|
- !ruby/object:Gem::Dependency
|
|
132
84
|
name: argon2
|
|
133
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -143,34 +95,34 @@ dependencies:
|
|
|
143
95
|
- !ruby/object:Gem::Version
|
|
144
96
|
version: '2.3'
|
|
145
97
|
- !ruby/object:Gem::Dependency
|
|
146
|
-
name: hanami
|
|
98
|
+
name: hanami
|
|
147
99
|
requirement: !ruby/object:Gem::Requirement
|
|
148
100
|
requirements:
|
|
149
101
|
- - "~>"
|
|
150
102
|
- !ruby/object:Gem::Version
|
|
151
|
-
version: '
|
|
103
|
+
version: '2.0'
|
|
152
104
|
type: :runtime
|
|
153
105
|
prerelease: false
|
|
154
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
155
107
|
requirements:
|
|
156
108
|
- - "~>"
|
|
157
109
|
- !ruby/object:Gem::Version
|
|
158
|
-
version: '
|
|
110
|
+
version: '2.0'
|
|
159
111
|
- !ruby/object:Gem::Dependency
|
|
160
|
-
name: hanami-
|
|
112
|
+
name: hanami-controller
|
|
161
113
|
requirement: !ruby/object:Gem::Requirement
|
|
162
114
|
requirements:
|
|
163
115
|
- - "~>"
|
|
164
116
|
- !ruby/object:Gem::Version
|
|
165
|
-
version: '
|
|
117
|
+
version: '2.0'
|
|
166
118
|
type: :runtime
|
|
167
119
|
prerelease: false
|
|
168
120
|
version_requirements: !ruby/object:Gem::Requirement
|
|
169
121
|
requirements:
|
|
170
122
|
- - "~>"
|
|
171
123
|
- !ruby/object:Gem::Version
|
|
172
|
-
version: '
|
|
173
|
-
description:
|
|
124
|
+
version: '2.0'
|
|
125
|
+
description:
|
|
174
126
|
email:
|
|
175
127
|
- sebastjan.hribar@gmail.com
|
|
176
128
|
executables:
|
|
@@ -197,7 +149,7 @@ homepage: https://github.com/sebastjan-hribar/tachiban
|
|
|
197
149
|
licenses:
|
|
198
150
|
- MIT
|
|
199
151
|
metadata: {}
|
|
200
|
-
post_install_message:
|
|
152
|
+
post_install_message:
|
|
201
153
|
rdoc_options: []
|
|
202
154
|
require_paths:
|
|
203
155
|
- lib
|
|
@@ -212,8 +164,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
212
164
|
- !ruby/object:Gem::Version
|
|
213
165
|
version: '0'
|
|
214
166
|
requirements: []
|
|
215
|
-
rubygems_version: 3.
|
|
216
|
-
signing_key:
|
|
167
|
+
rubygems_version: 3.5.9
|
|
168
|
+
signing_key:
|
|
217
169
|
specification_version: 4
|
|
218
170
|
summary: Tachiban provides simple password hashing for user authentication with Argon2
|
|
219
171
|
for Hanami web applications.
|