lato 3.5.9 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2096e6aab4a75e7edce579a7a0864de28f146053291970b49099e8103427e050
4
- data.tar.gz: 81b8ea778664362775b8beb2a095085eb5e0e32c94a2ded4ead6bf8a2fc9bde6
3
+ metadata.gz: 124f38d5389e35689d3829980d788e5531af092d45fd378b5fbaabdc039ebe51
4
+ data.tar.gz: e6a938bc4549803deb41efd7df596e85148d84488d442f75ac46b9bb8c0191bd
5
5
  SHA512:
6
- metadata.gz: 7bbfd89a3199d1d1714050f1e5485b4ed3e54948322de3d409458e5c34cfdd59215ddac0a25d44b1cc0a750057c534d70346ba74c3d1eadce86ca4540288673c
7
- data.tar.gz: 5c079279e86b805d1a072c6a1d1f01c50f9690b1073212cc70cc030652a4f1bb6ca03917f73e3f6cad74d5357730ebaa6955985583fbcc7c24bb1adbe54f8a57
6
+ metadata.gz: fed883559fd4d1859aacc7ca6ca2dc2d65935c1d91d769c23ba71b940d2067cc8d320b84512a9adbd6b0a1540f7e3b33098664c89298a18277fda7fa548a7dc9
7
+ data.tar.gz: 9b87edd528d4c10bb7c82f4b8fa7769d6625560668657dea98b085661d22696c215d138fc3a5af0947d22cfb20c63b014503f472d99516bf6b0594398809e0ca
data/README.md CHANGED
@@ -107,4 +107,3 @@ $ ruby ./bin/publish.rb
107
107
 
108
108
  ## License
109
109
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
110
-
@@ -55,6 +55,20 @@ module Lato
55
55
  end
56
56
  end
57
57
 
58
+ def update_authenticator_action
59
+ return respond_to_with_not_found unless Lato.config.authenticator_connection
60
+
61
+ respond_to do |format|
62
+ if @session.user.generate_authenticator_secret
63
+ format.html { redirect_to lato.account_path }
64
+ format.json { render json: @session.user }
65
+ else
66
+ format.html { render :index, status: :unprocessable_entity }
67
+ format.json { render json: @session.user.errors, status: :unprocessable_entity }
68
+ end
69
+ end
70
+ end
71
+
58
72
  def request_verify_email_action
59
73
  respond_to do |format|
60
74
  if @session.user.request_verify_email
@@ -10,6 +10,7 @@ module Lato
10
10
  before_action :lock_signup_if_disabled, only: %i[signup signup_action]
11
11
  before_action :lock_recover_password_if_disabled, only: %i[recover_password recover_password_action update_password update_password_action]
12
12
  before_action :lock_web3_if_disabled, only: %i[web3_signin web3_signin_action]
13
+ before_action :lock_authenticator_if_disabled, only: %i[authenticator authenticator_action]
13
14
 
14
15
  before_action :hide_sidebar
15
16
 
@@ -28,10 +29,13 @@ module Lato
28
29
  ip_address: request.remote_ip,
29
30
  user_agent: request.user_agent
30
31
  ))
31
- session_create(@user.id)
32
-
33
- format.html { redirect_to lato.root_path }
34
- format.json { render json: @user }
32
+ if create_session_or_start_authenticator(@user)
33
+ format.html { redirect_to lato.root_path }
34
+ format.json { render json: @user }
35
+ else
36
+ format.html { redirect_to lato.authentication_authenticator_path }
37
+ format.json { render json: @user }
38
+ end
35
39
  else
36
40
  format.html { render :signin, status: :unprocessable_entity }
37
41
  format.json { render json: @user.errors, status: :unprocessable_entity }
@@ -54,10 +58,13 @@ module Lato
54
58
  web3_nonce: session[:web3_nonce]
55
59
  ))
56
60
  session[:web3_nonce] = nil
57
- session_create(@user.id)
58
-
59
- format.html { redirect_to lato.root_path }
60
- format.json { render json: @user }
61
+ if create_session_or_start_authenticator(@user)
62
+ format.html { redirect_to lato.root_path }
63
+ format.json { render json: @user }
64
+ else
65
+ format.html { redirect_to lato.authentication_authenticator_path }
66
+ format.json { render json: @user }
67
+ end
61
68
  else
62
69
  session[:web3_nonce] = nil
63
70
  format.html { render :web3_signin, status: :unprocessable_entity }
@@ -183,6 +190,31 @@ module Lato
183
190
  end
184
191
  end
185
192
 
193
+ # Authenticator
194
+ ##
195
+
196
+ def authenticator
197
+ @user = Lato::User.find_by_id(session[:authenticator_user_id])
198
+ return respond_to_with_not_found unless @user
199
+ end
200
+
201
+ def authenticator_action
202
+ @user = Lato::User.find_by_id(session[:authenticator_user_id])
203
+
204
+ respond_to do |format|
205
+ if @user.authenticator(params.require(:user).permit(:authenticator_code))
206
+ session[:authenticator_user_id] = nil
207
+ session_create(@user.id)
208
+
209
+ format.html { redirect_to lato.root_path }
210
+ format.json { render json: @user }
211
+ else
212
+ format.html { render :authenticator, status: :unprocessable_entity }
213
+ format.json { render json: @user.errors, status: :unprocessable_entity }
214
+ end
215
+ end
216
+ end
217
+
186
218
  private
187
219
 
188
220
  def registration_params
@@ -199,6 +231,16 @@ module Lato
199
231
  respond_to_with_not_found unless @invitation
200
232
  end
201
233
 
234
+ def create_session_or_start_authenticator(user)
235
+ if !Lato.config.authenticator_connection || Lato.config.auth_disable_authenticator || !user.authenticator_enabled?
236
+ session_create(user.id)
237
+ return true
238
+ end
239
+
240
+ session[:authenticator_user_id] = user.id
241
+ false
242
+ end
243
+
202
244
  def lock_signup_if_disabled
203
245
  return unless Lato.config.auth_disable_signup
204
246
 
@@ -215,6 +257,12 @@ module Lato
215
257
  return if Lato.config.web3_connection && !Lato.config.auth_disable_web3
216
258
 
217
259
 
260
+ respond_to_with_not_found
261
+ end
262
+
263
+ def lock_authenticator_if_disabled
264
+ return if Lato.config.authenticator_connection && !Lato.config.auth_disable_authenticator
265
+
218
266
  respond_to_with_not_found
219
267
  end
220
268
  end
@@ -54,6 +54,10 @@ module Lato
54
54
  @valid_accepted_terms_and_conditions_version ||= accepted_terms_and_conditions_version >= Lato.config.legal_terms_and_conditions_version
55
55
  end
56
56
 
57
+ def authenticator_enabled?
58
+ !authenticator_secret.blank?
59
+ end
60
+
57
61
  # Helpers
58
62
  ##
59
63
 
@@ -65,6 +69,10 @@ module Lato
65
69
  @gravatar_image_url ||= "https://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(email)}?s=#{size}"
66
70
  end
67
71
 
72
+ def authenticator_qr_code_base64(size = 200)
73
+ "data:image/png;base64,#{Base64.strict_encode64(RQRCode::QRCode.new(ROTP::TOTP.new(authenticator_secret, :issuer => Lato.config.application_title).provisioning_uri(email).to_s).as_png(size: size, border_modules: 0).to_s)}"
74
+ end
75
+
68
76
  # Operations
69
77
  ##
70
78
 
@@ -224,7 +232,9 @@ module Lato
224
232
 
225
233
  c_password_update_code('')
226
234
 
227
- update(params.permit(:password, :password_confirmation))
235
+ update(params.permit(:password, :password_confirmation).merge(
236
+ authenticator_secret: nil # Reset authenticator secret when password is updated
237
+ ))
228
238
  end
229
239
 
230
240
  def update_accepted_privacy_policy_version(params)
@@ -292,6 +302,23 @@ module Lato
292
302
  true
293
303
  end
294
304
 
305
+ def generate_authenticator_secret
306
+ update(authenticator_secret: ROTP::Base32.random)
307
+ end
308
+
309
+ def authenticator(params)
310
+ return false unless authenticator_enabled?
311
+
312
+ totp = ROTP::TOTP.new(authenticator_secret)
313
+ result = totp.verify(params[:authenticator_code])
314
+ unless result
315
+ errors.add(:base, :authenticator_code_invalid)
316
+ return
317
+ end
318
+
319
+ true
320
+ end
321
+
295
322
  # Cache
296
323
  ##
297
324
 
@@ -0,0 +1,41 @@
1
+ <%
2
+
3
+ user ||= Lato::User.new
4
+
5
+ %>
6
+
7
+ <%= turbo_frame_tag 'account_form-authenticator' do %>
8
+ <%= form_with model: user, url: lato.account_update_authenticator_action_path, data: { turbo_frame: '_self', controller: 'lato-form' } do |form| %>
9
+ <%= lato_form_notices class: %w[mb-3] %>
10
+ <%= lato_form_errors user, class: %w[mb-3] %>
11
+
12
+ <% if user.authenticator_secret %>
13
+ <div class="d-block d-md-flex align-items-stretch">
14
+ <div class="text-center mb-3 mb-md-0 me-md-3">
15
+ <img src="<%= user.authenticator_qr_code_base64 %>" />
16
+ </div>
17
+ <div class="d-flex flex-column justify-content-between">
18
+ <div class="alert alert-light">
19
+ <p>
20
+ <%= raw I18n.t('lato.account_authenticator_ready_qr') %>
21
+ </p>
22
+ </div>
23
+
24
+ <div class="d-flex justify-content-end">
25
+ <%= lato_form_submit form, I18n.t('lato.reset_qr_code'), class: %w[btn-danger] %>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ <% else %>
30
+ <div class="alert alert-light mb-0">
31
+ <h4 class="alert-heading"><%= I18n.t('lato.account_authenticator_start_title') %></h4>
32
+ <p>
33
+ <%= raw I18n.t('lato.account_authenticator_start_description') %>
34
+ </p>
35
+ <p class="mb-0">
36
+ <%= lato_form_submit form, I18n.t('lato.generate_qr_code'), class: %w[btn-primary] %>
37
+ </p>
38
+ </div>
39
+ <% end %>
40
+ <% end %>
41
+ <% end %>
@@ -32,6 +32,17 @@
32
32
  </div>
33
33
  <% end %>
34
34
 
35
+ <% if Lato.config.authenticator_connection %>
36
+ <div class="card mb-4">
37
+ <div class="card-header">
38
+ <h2 class="fs-4 mb-0"><%= I18n.t('lato.account_authenticator') %></h2>
39
+ </div>
40
+ <div class="card-body">
41
+ <%= render 'lato/account/form-authenticator', user: @session.user %>
42
+ </div>
43
+ </div>
44
+ <% end %>
45
+
35
46
  <div class="card mb-4">
36
47
  <div class="card-header">
37
48
  <h2 class="fs-4 mb-0"><%= I18n.t('lato.account_delete') %></h2>
@@ -0,0 +1,28 @@
1
+ <%
2
+
3
+ user ||= Lato::User.new
4
+
5
+ %>
6
+
7
+ <%= turbo_frame_tag 'authentication_form-authenticator' do %>
8
+ <%= form_with model: user, url: lato.authentication_authenticator_action_path, method: :post, data: { turbo_frame: '_self', controller: 'lato-form' } do |form| %>
9
+ <%= lato_form_notices class: %w[mb-3] %>
10
+ <%= lato_form_errors user, class: %w[mb-3] %>
11
+
12
+ <div class="mb-3 text-center">
13
+ <p><%= raw I18n.t('lato.authenticator_code_help', email: user.email) %></p>
14
+ </div>
15
+
16
+ <div class="mb-3">
17
+ <%= lato_form_item_input_text form, :authenticator_code, required: true %>
18
+ </div>
19
+
20
+ <div>
21
+ <%= lato_form_submit form, I18n.t('lato.confirm'), class: %w[d-block w-100] %>
22
+ </div>
23
+
24
+ <div class="text-center mt-3 mb-3">
25
+ <%= I18n.t('lato.or').downcase %> <%= link_to I18n.t('lato.reset_password').downcase, lato.authentication_recover_password_path %>
26
+ </div>
27
+ <% end %>
28
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <div class="w-100 h-100 d-flex justify-content-center align-items-center" style="min-height: calc(100vh - 54px - 2rem)">
2
+ <div class="card w-100" style="max-width: 400px">
3
+ <div class="card-header">
4
+ <h1 class="fs-3 mb-0 text-center"><%= I18n.t('lato.authenticator') %></h1>
5
+ </div>
6
+ <div class="card-body">
7
+ <%= render 'lato/authentication/form-authenticator', user: @user %>
8
+ </div>
9
+ </div>
10
+ </div>
@@ -49,6 +49,15 @@ en:
49
49
  web3_signin: Web3 Login
50
50
  retry: Retry
51
51
  back: Go back
52
+ account_authenticator: Google Authenticator
53
+ account_authenticator_start_title: Enable Google Authenticator
54
+ account_authenticator_start_description: Generate a QR code by clicking the button below and scan it with the Google Authenticator app on your phone.<br>This will allow you to use the protect your account with a second factor authentication.
55
+ account_authenticator_ready_qr: Scan the QR code with the Google Authenticator app to use the account protection with a second factor authentication.
56
+ generate_qr_code: Generate QR code
57
+ reset_qr_code: Reset QR code
58
+ authenticator: Two-factor authentication
59
+ authenticator_code_help: Insert the code generated by the Google Authenticator app for the account <b>%{email}</b>
60
+ reset_password: Reset your password
52
61
 
53
62
  account_controller:
54
63
  update_user_action_notice: Account information properly updated
@@ -82,6 +91,7 @@ en:
82
91
  terms_and_conditions_invalid: To accept the terms and conditions you must select the confirmation checkbox
83
92
  web3_address_invalid: The address you send is not corretly signed
84
93
  web3_connection_error: Impossible to connect the wallet
94
+ authenticator_code_invalid: The code you inserted is not correct
85
95
  password:
86
96
  not_correct: not correct
87
97
  email:
@@ -51,6 +51,15 @@ it:
51
51
  web3_signin: Accedi con Web3
52
52
  retry: Riprova
53
53
  back: Torna indietro
54
+ account_authenticator: Google Authenticator
55
+ account_authenticator_start_title: Abilita Google Authenticator
56
+ account_authenticator_start_description: Genera un codice QR cliccando il pulsante sottostante e scansionalo con l'app Google Authenticator sul tuo telefono.<br>Questo ti permetterà di proteggere il tuo account con un'autenticazione a due fattori.
57
+ account_authenticator_ready_qr: Scansiona il codice QR con l'app Google Authenticator per utilizzare la protezione dell'account con un'autenticazione a due fattori.
58
+ generate_qr_code: Genera codice QR
59
+ reset_qr_code: Resetta codice QR
60
+ authenticator: Autenticazione a due fattori
61
+ authenticator_code_help: Inserisci il codice generato dall'app Google Authenticator per l'account <b>%{email}</b>
62
+ reset_password: Reimposta la tua password
54
63
 
55
64
  account_controller:
56
65
  update_user_action_notice: Informazioni account aggiornate correttamente
@@ -90,6 +99,7 @@ it:
90
99
  invitation_invalid: Invito non valido
91
100
  web3_address_invalid: L'inidirizzo inviato non è correttamente firmato
92
101
  web3_connection_error: Impossibile connettere il wallet
102
+ authenticator_code_invalid: Il codice inserito non è corretto
93
103
  password:
94
104
  not_correct: non corretta
95
105
  password_confirmation:
data/config/routes.rb CHANGED
@@ -26,6 +26,8 @@ Lato::Engine.routes.draw do
26
26
  patch 'update_password_action', to: 'authentication#update_password_action', as: :authentication_update_password_action
27
27
  get 'accept_invitation', to: 'authentication#accept_invitation', as: :authentication_accept_invitation
28
28
  post 'accept_invitation_action', to: 'authentication#accept_invitation_action', as: :authentication_accept_invitation_action
29
+ get 'authenticator', to: 'authentication#authenticator', as: :authentication_authenticator
30
+ post 'authenticator_action', to: 'authentication#authenticator_action', as: :authentication_authenticator_action
29
31
  end
30
32
 
31
33
  # Account
@@ -35,6 +37,7 @@ Lato::Engine.routes.draw do
35
37
  get '', to: 'account#index', as: :account
36
38
  patch 'update_user_action', to: 'account#update_user_action', as: :account_update_user_action
37
39
  patch 'update_web3_action', to: 'account#update_web3_action', as: :account_update_web3_action
40
+ patch 'update_authenticator_action', to: 'account#update_authenticator_action', as: :account_update_authenticator_action
38
41
  patch 'request_verify_email_action', to: 'account#request_verify_email_action', as: :account_request_verify_email_action
39
42
  patch 'update_password_action', to: 'account#update_password_action', as: :account_update_password_action
40
43
  delete 'destroy_action', to: 'account#destroy_action', as: :account_destroy_action
@@ -0,0 +1,5 @@
1
+ class AddAuthenticatorSecretToUser < ActiveRecord::Migration[7.1]
2
+ def change
3
+ add_column :lato_users, :authenticator_secret, :string
4
+ end
5
+ end
data/lib/lato/config.rb CHANGED
@@ -10,7 +10,7 @@ module Lato
10
10
  attr_accessor :session_lifetime, :session_root_path
11
11
 
12
12
  # Authentication configs
13
- attr_accessor :auth_disable_signup, :auth_disable_recover_password, :auth_disable_web3
13
+ attr_accessor :auth_disable_signup, :auth_disable_recover_password, :auth_disable_web3, :auth_disable_authenticator
14
14
 
15
15
  # Assets configs
16
16
  attr_accessor :assets_stylesheet_entry
@@ -25,6 +25,9 @@ module Lato
25
25
  # NOTE: It requires the gem 'eth' to be installed in the application Gemfile
26
26
  attr_accessor :web3_connection
27
27
 
28
+ # Authenticator connection
29
+ attr_accessor :authenticator_connection
30
+
28
31
  def initialize
29
32
  @application_title = 'Lato'
30
33
  @application_version = '1.0.0'
@@ -35,6 +38,7 @@ module Lato
35
38
  @auth_disable_signup = false
36
39
  @auth_disable_recover_password = false
37
40
  @auth_disable_web3 = false
41
+ @auth_disable_authenticator = false
38
42
 
39
43
  @assets_stylesheet_entry = 'application'
40
44
 
@@ -49,6 +53,7 @@ module Lato
49
53
  @legal_terms_and_conditions_version = 1
50
54
 
51
55
  @web3_connection = false
56
+ @authenticator_connection = false
52
57
  end
53
58
  end
54
59
  end
data/lib/lato/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Lato
2
- VERSION = "3.5.9"
2
+ VERSION = "3.6.0"
3
3
  end
data/lib/lato.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require "kaminari"
2
2
  require "bootstrap"
3
3
  require "browser"
4
+ require "rotp"
5
+ require "rqrcode"
4
6
 
5
7
  require "lato/version"
6
8
  require "lato/engine"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lato
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.9
4
+ version: 3.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregorio Galante
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-22 00:00:00.000000000 Z
11
+ date: 2024-03-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -94,6 +94,34 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rqrcode
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rotp
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
97
125
  - !ruby/object:Gem::Dependency
98
126
  name: eth
99
127
  requirement: !ruby/object:Gem::Requirement
@@ -162,6 +190,7 @@ files:
162
190
  - app/models/lato/user.rb
163
191
  - app/views/lato/account/_alert-accepted-privacy-policy-version.html.erb
164
192
  - app/views/lato/account/_alert-accepted-terms-and-conditions-version.html.erb
193
+ - app/views/lato/account/_form-authenticator.html.erb
165
194
  - app/views/lato/account/_form-destroy.html.erb
166
195
  - app/views/lato/account/_form-password.html.erb
167
196
  - app/views/lato/account/_form-user.html.erb
@@ -169,6 +198,7 @@ files:
169
198
  - app/views/lato/account/index.html.erb
170
199
  - app/views/lato/authentication/_fields-registration.html.erb
171
200
  - app/views/lato/authentication/_form-accept-invitation.html.erb
201
+ - app/views/lato/authentication/_form-authenticator.html.erb
172
202
  - app/views/lato/authentication/_form-recover-password.html.erb
173
203
  - app/views/lato/authentication/_form-signin.html.erb
174
204
  - app/views/lato/authentication/_form-signup.html.erb
@@ -176,6 +206,7 @@ files:
176
206
  - app/views/lato/authentication/_form-verify-email.html.erb
177
207
  - app/views/lato/authentication/_form-web3-signin.html.erb
178
208
  - app/views/lato/authentication/accept_invitation.html.erb
209
+ - app/views/lato/authentication/authenticator.html.erb
179
210
  - app/views/lato/authentication/recover_password.html.erb
180
211
  - app/views/lato/authentication/signin.html.erb
181
212
  - app/views/lato/authentication/signout.html.erb
@@ -226,6 +257,7 @@ files:
226
257
  - db/migrate/20230823165716_create_lato_log_user_signups.rb
227
258
  - db/migrate/20240222125124_add_web3_to_lato_users.rb
228
259
  - db/migrate/20240222171418_add_indexes_on_lato_users_email.rb
260
+ - db/migrate/20240318074025_add_authenticator_secret_to_user.rb
229
261
  - lib/lato.rb
230
262
  - lib/lato/btstrap.rb
231
263
  - lib/lato/config.rb