lato 3.5.9 → 3.6.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 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