lato 3.15.1 → 3.17.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.
@@ -14,6 +14,7 @@ module Lato
14
14
  validates :accepted_privacy_policy_version, presence: true
15
15
  validates :accepted_terms_and_conditions_version, presence: true
16
16
  validates :web3_address, uniqueness: true, allow_blank: true
17
+ validates :webauthn_id, uniqueness: true, allow_blank: true
17
18
 
18
19
  # Relations
19
20
  ##
@@ -58,6 +59,10 @@ module Lato
58
59
  !authenticator_secret.blank?
59
60
  end
60
61
 
62
+ def webauthn_enabled?
63
+ webauthn_id.present? && webauthn_public_key.present?
64
+ end
65
+
61
66
  # Helpers
62
67
  ##
63
68
 
@@ -251,7 +256,9 @@ module Lato
251
256
  c_password_update_code('')
252
257
 
253
258
  update(params.permit(:password, :password_confirmation).merge(
254
- authenticator_secret: nil # Reset authenticator secret when password is updated
259
+ authenticator_secret: nil, # Reset authenticator secret when password is updated
260
+ webauthn_id: nil, # Reset webauthn credential when password is updated
261
+ webauthn_public_key: nil # Reset webauthn credential when password is updated
255
262
  ))
256
263
  end
257
264
 
@@ -320,6 +327,93 @@ module Lato
320
327
  true
321
328
  end
322
329
 
330
+ def webauthn_registration_options
331
+ WebAuthn::Credential.options_for_create(
332
+ user: {
333
+ id: Base64.strict_encode64(webauthn_user_handle),
334
+ name: email,
335
+ display_name: full_name
336
+ },
337
+ exclude: webauthn_exclude_credentials.map { |cred| Base64.strict_encode64(cred) }
338
+ )
339
+ end
340
+
341
+ def webauthn_authentication_options
342
+ WebAuthn::Credential.options_for_get(
343
+ allow: webauthn_allow_credentials.map { |cred| Base64.strict_encode64(cred) }
344
+ )
345
+ end
346
+
347
+ def register_webauthn_credential(credential_payload, encoded_challenge)
348
+ if credential_payload.blank?
349
+ errors.add(:base, :webauthn_payload_missing)
350
+ return false
351
+ end
352
+
353
+ if encoded_challenge.blank?
354
+ errors.add(:base, :webauthn_challenge_missing)
355
+ return false
356
+ end
357
+
358
+ parsed_payload = JSON.parse(credential_payload)
359
+ credential = WebAuthn::Credential.from_create(parsed_payload)
360
+ credential.verify(Base64.strict_decode64(encoded_challenge))
361
+
362
+ update(
363
+ webauthn_id: Base64.strict_encode64(credential.raw_id),
364
+ webauthn_public_key: credential.public_key
365
+ )
366
+ rescue JSON::ParserError, WebAuthn::Error => e
367
+ Rails.logger.error(e)
368
+ errors.add(:base, :webauthn_registration_failed)
369
+ false
370
+ end
371
+
372
+ def webauthn_authentication(params, encoded_challenge)
373
+ return false unless webauthn_enabled?
374
+
375
+ if params[:webauthn_credential].blank?
376
+ errors.add(:base, :webauthn_payload_missing)
377
+ return false
378
+ end
379
+
380
+ if encoded_challenge.blank?
381
+ errors.add(:base, :webauthn_challenge_missing)
382
+ return false
383
+ end
384
+
385
+ parsed_payload = JSON.parse(params[:webauthn_credential])
386
+
387
+ # Converti i campi da base64url a base64 standard per il gem webauthn
388
+ parsed_payload['rawId'] = base64url_to_base64(parsed_payload['rawId'])
389
+ parsed_payload['response']['clientDataJSON'] = base64url_to_base64(parsed_payload['response']['clientDataJSON'])
390
+ parsed_payload['response']['authenticatorData'] = base64url_to_base64(parsed_payload['response']['authenticatorData'])
391
+ parsed_payload['response']['signature'] = base64url_to_base64(parsed_payload['response']['signature'])
392
+ parsed_payload['response']['userHandle'] = base64url_to_base64(parsed_payload['response']['userHandle']) if parsed_payload['response']['userHandle']
393
+
394
+ credential = WebAuthn::Credential.from_get(parsed_payload)
395
+
396
+ credential.verify(
397
+ encoded_challenge,
398
+ public_key: webauthn_public_key,
399
+ sign_count: 0
400
+ )
401
+
402
+ true
403
+ rescue JSON::ParserError, WebAuthn::Error => e
404
+ Rails.logger.error(e)
405
+ Rails.logger.error(e.backtrace.join("\n"))
406
+ errors.add(:base, :webauthn_authentication_failed)
407
+ false
408
+ end
409
+
410
+ def remove_webauthn_credential
411
+ update(
412
+ webauthn_id: nil,
413
+ webauthn_public_key: nil
414
+ )
415
+ end
416
+
323
417
  def generate_authenticator_secret
324
418
  update(authenticator_secret: ROTP::Base32.random)
325
419
  end
@@ -367,5 +461,41 @@ module Lato
367
461
  Rails.cache.write(cache_key, value, expires_in: 30.minutes)
368
462
  value
369
463
  end
464
+
465
+ private
466
+
467
+ def webauthn_user_handle
468
+ Digest::SHA256.digest("lato-user-#{id}")
469
+ end
470
+
471
+ def webauthn_exclude_credentials
472
+ return [] unless webauthn_id.present?
473
+
474
+ [Base64.strict_decode64(webauthn_id)]
475
+ rescue ArgumentError
476
+ []
477
+ end
478
+
479
+ def webauthn_allow_credentials
480
+ return [] unless webauthn_id.present?
481
+
482
+ [Base64.strict_decode64(webauthn_id)]
483
+ rescue ArgumentError
484
+ []
485
+ end
486
+
487
+ def base64url_to_base64(str)
488
+ return nil if str.nil?
489
+ # Converti base64url in base64 standard
490
+ base64 = str.tr('-_', '+/')
491
+ # Aggiungi padding se necessario
492
+ case base64.length % 4
493
+ when 2
494
+ base64 += '=='
495
+ when 3
496
+ base64 += '='
497
+ end
498
+ base64
499
+ end
370
500
  end
371
501
  end
@@ -15,14 +15,15 @@ user ||= Lato::User.new
15
15
  <img class="shadow-sm rounded" src="<%= user.authenticator_qr_code_base64 %>" />
16
16
  </div>
17
17
  <div class="d-flex flex-column justify-content-between w-100">
18
- <div class="alert alert-light">
18
+ <div class="alert alert-success">
19
+ <h4 class="alert-heading"><%= I18n.t('lato.account_authenticator_ready_title') %></h4>
19
20
  <p class="mb-0">
20
21
  <%= raw I18n.t('lato.account_authenticator_ready_qr') %>
21
22
  </p>
22
23
  </div>
23
24
 
24
25
  <div class="d-flex justify-content-end">
25
- <%= lato_form_submit form, I18n.t('lato.cancel_authenticator'), class: %w[btn-danger] %>
26
+ <%= lato_form_submit form, I18n.t('lato.account_authenticator_disable'), class: %w[btn-danger] %>
26
27
  </div>
27
28
  </div>
28
29
  </div>
@@ -33,7 +34,7 @@ user ||= Lato::User.new
33
34
  <%= raw I18n.t('lato.account_authenticator_start_description') %>
34
35
  </p>
35
36
  <p class="mb-0">
36
- <%= lato_form_submit form, I18n.t('lato.generate_qr_code'), class: %w[btn-primary] %>
37
+ <%= lato_form_submit form, I18n.t('lato.account_authenticator_generate_qr_code'), class: %w[btn-primary] %>
37
38
  </p>
38
39
  </div>
39
40
  <% end %>
@@ -0,0 +1,66 @@
1
+ <%
2
+
3
+ user ||= Lato::User.new
4
+ registration_options = session[:webauthn_registration_options]
5
+ form_data = { turbo_frame: '_self', controller: 'lato-form' }
6
+
7
+ if registration_options.present?
8
+ form_data[:controller] += ' lato-account-webauthn'
9
+ form_data['lato-account-webauthn-options-value'] = registration_options.to_json
10
+ form_data['lato-account-webauthn-error-message-value'] = I18n.t('lato.account_webauthn_in_progress_error')
11
+ end
12
+
13
+ %>
14
+
15
+ <%= turbo_frame_tag 'account_form-webauthn' do %>
16
+ <%= form_with model: user, url: lato.account_update_webauthn_action_path, data: form_data do |form| %>
17
+ <%= lato_form_notices class: %w[mb-3] %>
18
+ <%= lato_form_errors user, class: %w[mb-3] %>
19
+
20
+ <% if user.webauthn_enabled? %>
21
+ <div class="alert alert-success mb-3">
22
+ <h4 class="alert-heading"><%= I18n.t('lato.account_webauthn_ready_title') %></h4>
23
+ <p class="mb-0">
24
+ <%= raw I18n.t('lato.account_webauthn_ready_description') %>
25
+ </p>
26
+ </div>
27
+
28
+ <%= form.hidden_field :webauthn_command, value: 'remove' %>
29
+
30
+ <div class="d-flex justify-content-end">
31
+ <%= lato_form_submit form, I18n.t('lato.account_webauthn_disable'), class: %w[btn-danger] %>
32
+ </div>
33
+ <% elsif registration_options.present? %>
34
+ <%= form.hidden_field :webauthn_command, value: 'complete' %>
35
+ <%= form.hidden_field :webauthn_credential, data: { lato_account_webauthn_target: 'credentialInput' } %>
36
+
37
+ <div class="alert alert-light mb-3" data-lato-account-webauthn-target="status">
38
+ <h4 class="alert-heading"><%= I18n.t('lato.account_webauthn_in_progress_title') %></h4>
39
+ <p class="mb-0">
40
+ <%= raw I18n.t('lato.account_webauthn_in_progress_description') %>
41
+ </p>
42
+ </div>
43
+
44
+ <%= lato_form_submit form, I18n.t('lato.account_webauthn_finalize'), class: %w[btn-primary d-none], data: { lato_account_webauthn_target: 'submit' } %>
45
+
46
+ <div class="d-flex justify-content-between align-items-center">
47
+ <%= link_to I18n.t('lato.account_webauthn_cancel'), lato.account_update_webauthn_action_path(user: { webauthn_command: 'cancel' }), class: 'btn btn-link px-0 text-decoration-none', data: { turbo_frame: '_self', turbo_method: :patch } %>
48
+ <div class="text-muted small d-flex align-items-center">
49
+ <span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
50
+ <span><%= I18n.t('lato.account_webauthn_waiting_browser') %></span>
51
+ </div>
52
+ </div>
53
+ <% else %>
54
+ <div class="alert alert-light mb-0">
55
+ <h4 class="alert-heading"><%= I18n.t('lato.account_webauthn_start_title') %></h4>
56
+ <p>
57
+ <%= raw I18n.t('lato.account_webauthn_start_description') %>
58
+ </p>
59
+ <p class="mb-0">
60
+ <%= form.hidden_field :webauthn_command, value: 'start' %>
61
+ <%= lato_form_submit form, I18n.t('lato.account_webauthn_enable'), class: %w[btn-primary] %>
62
+ </p>
63
+ </div>
64
+ <% end %>
65
+ <% end %>
66
+ <% end %>
@@ -21,24 +21,35 @@
21
21
  </div>
22
22
  </div>
23
23
 
24
- <% if Lato.config.web3_connection %>
24
+ <% if Lato.config.authenticator_connection %>
25
25
  <div class="card mb-4">
26
26
  <div class="card-header">
27
- <h2 class="fs-4 mb-0"><%= I18n.t('lato.account_web3') %></h2>
27
+ <h2 class="fs-4 mb-0"><%= I18n.t('lato.account_authenticator') %></h2>
28
28
  </div>
29
29
  <div class="card-body">
30
- <%= render 'lato/account/form-web3', user: @session.user %>
30
+ <%= render 'lato/account/form-authenticator', user: @session.user %>
31
31
  </div>
32
32
  </div>
33
33
  <% end %>
34
34
 
35
- <% if Lato.config.authenticator_connection %>
35
+ <% if Lato.config.webauthn_connection %>
36
36
  <div class="card mb-4">
37
37
  <div class="card-header">
38
- <h2 class="fs-4 mb-0"><%= I18n.t('lato.account_authenticator') %></h2>
38
+ <h2 class="fs-4 mb-0"><%= I18n.t('lato.account_webauthn') %></h2>
39
39
  </div>
40
40
  <div class="card-body">
41
- <%= render 'lato/account/form-authenticator', user: @session.user %>
41
+ <%= render 'lato/account/form-webauthn', user: @session.user %>
42
+ </div>
43
+ </div>
44
+ <% end %>
45
+
46
+ <% if Lato.config.web3_connection %>
47
+ <div class="card mb-4">
48
+ <div class="card-header">
49
+ <h2 class="fs-4 mb-0"><%= I18n.t('lato.account_web3') %></h2>
50
+ </div>
51
+ <div class="card-body">
52
+ <%= render 'lato/account/form-web3', user: @session.user %>
42
53
  </div>
43
54
  </div>
44
55
  <% end %>
@@ -0,0 +1,36 @@
1
+ <%
2
+
3
+ user ||= Lato::User.new
4
+
5
+ %>
6
+
7
+ <%= turbo_frame_tag 'authentication_form-authentication-method' do %>
8
+ <%= form_with url: lato.authentication_authentication_method_action_path, method: :post, data: { turbo_frame: '_self' } do |form| %>
9
+ <%= lato_form_notices class: %w[mb-3] %>
10
+ <%= lato_form_errors user, class: %w[mb-3] %>
11
+
12
+ <% if user.authenticator_enabled? %>
13
+ <div class="mb-3">
14
+ <%= form.button I18n.t('lato.use_google_authenticator'),
15
+ type: 'submit',
16
+ name: 'method',
17
+ value: 'authenticator',
18
+ class: 'btn btn-primary w-100' %>
19
+ </div>
20
+ <% end %>
21
+
22
+ <% if user.webauthn_enabled? %>
23
+ <div class="mb-3">
24
+ <%= form.button I18n.t('lato.use_webauthn'),
25
+ type: 'submit',
26
+ name: 'method',
27
+ value: 'webauthn',
28
+ class: 'btn btn-primary w-100' %>
29
+ </div>
30
+ <% end %>
31
+
32
+ <div class="text-center mt-3 mb-3">
33
+ <%= I18n.t('lato.or').downcase %> <%= link_to I18n.t('lato.reset_password').downcase, lato.authentication_recover_password_path %>
34
+ </div>
35
+ <% end %>
36
+ <% end %>
@@ -0,0 +1,27 @@
1
+ <%
2
+
3
+ user ||= Lato::User.new
4
+ options ||= {}
5
+
6
+ %>
7
+
8
+ <%= turbo_frame_tag 'authentication_form-webauthn' do %>
9
+ <%= form_with model: user, url: lato.authentication_webauthn_action_path, method: :post, data: { turbo_frame: '_self', controller: 'lato-form lato-webauthn-auth', 'lato-webauthn-auth-options-value': options.to_json } do |form| %>
10
+ <%= lato_form_notices class: %w[mb-3] %>
11
+ <%= lato_form_errors user, class: %w[mb-3] %>
12
+
13
+ <div class="mb-4 text-center">
14
+ <p><%= raw I18n.t('lato.webauthn_help', email: h(user.email_protected)) %></p>
15
+ </div>
16
+
17
+ <%= form.hidden_field :webauthn_credential, data: { 'lato-webauthn-auth-target': 'credential' } %>
18
+
19
+ <div class="mb-3">
20
+ <%= lato_form_submit form, I18n.t('lato.authenticate'), class: %w[d-block w-100], data: { action: 'lato-webauthn-auth#authenticate' } %>
21
+ </div>
22
+
23
+ <div class="text-center mt-3 mb-3">
24
+ <%= I18n.t('lato.or').downcase %> <%= link_to I18n.t('lato.reset_password').downcase, lato.authentication_recover_password_path %>
25
+ </div>
26
+ <% end %>
27
+ <% 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.choose_authentication_method') %></h1>
5
+ </div>
6
+ <div class="card-body">
7
+ <%= render 'lato/authentication/form-authentication-method', user: @user %>
8
+ </div>
9
+ </div>
10
+ </div>
@@ -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.webauthn') %></h1>
5
+ </div>
6
+ <div class="card-body">
7
+ <%= render 'lato/authentication/form-webauthn', user: @user, options: @options %>
8
+ </div>
9
+ </div>
10
+ </div>
@@ -0,0 +1,66 @@
1
+ <%
2
+
3
+ form ||= nil
4
+ key ||= nil
5
+ partial ||= nil
6
+ partial_params ||= {}
7
+
8
+ existing_records = form.object.send(key) || []
9
+
10
+ %>
11
+
12
+ <% unless partial %>
13
+ <div class="alert alert-warning">
14
+ Missing partial parameter for form_item_input_list partial.
15
+ </div>
16
+ <% else %>
17
+ <div data-controller="lato-input-list">
18
+ <div data-lato-input-list-target="list" id="<%= "#{form.object_name}_#{key}" %>">
19
+ <% existing_records.each_with_index do |record, index| %>
20
+ <%
21
+ item_nested_form = form.fields_for(key, record) do |f|
22
+ partial_params[:form] = f
23
+ end
24
+ %>
25
+
26
+ <div class="border p-2 mb-2 rounded" data-lato-input-list-target="listItem" data-index="<%= index %>">
27
+ <input type="hidden" name="<%= "#{form.object_name}[#{key}_attributes][#{index}][id]" %>" value="<%= record.id %>">
28
+
29
+ <%= render partial: partial, locals: {
30
+ form: item_nested_form,
31
+ **partial_params
32
+ } %>
33
+
34
+ <div class="d-flex justify-content-end mt-2">
35
+ <button type="button" class="btn btn-danger btn-sm" data-action="click->lato-input-list#removeItem" title="<%= I18n.t('lato.remove_item') %>" data-controller="lato-tooltip">
36
+ <i class="bi bi-trash"></i>
37
+ </button>
38
+ </div>
39
+ </div>
40
+ <% end %>
41
+ </div>
42
+
43
+ <div class="d-flex justify-content-end mt-2 pt-2 border-top">
44
+ <button type="button" class="btn btn-outline-primary btn-sm" data-action="click->lato-input-list#addItem">
45
+ <i class="bi bi-plus-circle"></i> <%= I18n.t('lato.add_item') %>
46
+ </button>
47
+ </div>
48
+
49
+ <template data-lato-input-list-target="template">
50
+ <div class="template border p-2 mb-2 rounded">
51
+ <%= render partial: partial, locals: {
52
+ form: form.fields_for(key, form.object.send(key).new) do |f|
53
+ partial_params[:form] = f
54
+ end,
55
+ **partial_params
56
+ } %>
57
+
58
+ <div class="d-flex justify-content-end mt-2">
59
+ <button type="button" class="btn btn-danger btn-sm" data-action="click->lato-input-list#removeItem" title="<%= I18n.t('lato.remove_item') %>" data-controller="lato-tooltip">
60
+ <i class="bi bi-trash"></i>
61
+ </button>
62
+ </div>
63
+ </div>
64
+ </template>
65
+ </div>
66
+ <% end %>
@@ -1,6 +1,6 @@
1
1
  <% 10.times do %>
2
2
 
3
- <div class="modal fade" data-lato-action-target="modal" tabindex="-1" aria-hidden="true" data-bs-keyboard="false">
3
+ <div class="modal fade" data-lato-action-target="modal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
4
4
  <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" data-lato-action-target="modalDialog">
5
5
  <div class="modal-content">
6
6
  <div class="modal-header">
@@ -53,12 +53,32 @@ en:
53
53
  account_authenticator: Google Authenticator
54
54
  account_authenticator_start_title: Enable Google Authenticator
55
55
  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.
56
+ account_authenticator_ready_title: Google Authenticator ready
56
57
  account_authenticator_ready_qr: Scan the QR code with the Google Authenticator app to use the account protection with a second factor authentication.
57
- generate_qr_code: Generate QR code
58
- cancel_authenticator: Disconnect
59
- authenticator: Two-factor authentication
58
+ account_authenticator_generate_qr_code: Generate QR code
59
+ account_authenticator_disable: Disconnect
60
+ authenticator: Google Authenticator
60
61
  authenticator_code_help: Insert the code generated by the Google Authenticator app for the account <b>%{email}</b>
61
62
  reset_password: Reset your password
63
+ choose_authentication_method: Choose authentication method
64
+ use_google_authenticator: Use Google Authenticator
65
+ use_webauthn: Use Passkey
66
+ authenticate: Authenticate
67
+ webauthn: Passkey Authentication
68
+ webauthn_help: Use your device to authenticate for account <b>%{email}</b>
69
+ account_webauthn: Passkey authentication
70
+ account_webauthn_start_title: Enable passkeys
71
+ account_webauthn_start_description: Use your device security (Touch ID, Face ID, Windows Hello, etc.) to approve future logins.<br>Click the button below and follow the browser prompts to register a passkey.
72
+ account_webauthn_enable: Register passkey
73
+ account_webauthn_in_progress_title: Complete the registration from your browser
74
+ account_webauthn_in_progress_description: Approve the prompt that appears in your browser to finish linking this passkey to your account.
75
+ account_webauthn_in_progress_error: Unable to register the passkey. Please restart the procedure and try again.
76
+ account_webauthn_waiting_browser: Waiting for your browser confirmation...
77
+ account_webauthn_cancel: Cancel request
78
+ account_webauthn_finalize: Complete registration
79
+ account_webauthn_ready_title: Passkey ready
80
+ account_webauthn_ready_description: This account now requires a WebAuthn challenge right after the classic login flow.
81
+ account_webauthn_disable: Disconnect
62
82
  per_page: Per page
63
83
  per_page_description: Number of elements per page
64
84
  confirm_title: Confirm
@@ -71,6 +91,8 @@ en:
71
91
  operation_failed_title: Operation failed
72
92
  operation_failed_subtitle: The operation has failed because of an error
73
93
  dropzone_drag_and_drop_or_click: Drag and drop files here or click to upload
94
+ add_item: Add item
95
+ remove_item: Remove item
74
96
 
75
97
  invitation_mailer:
76
98
  invite_mail_subject: You received an invitation
@@ -112,6 +134,10 @@ en:
112
134
  web3_address_invalid: The address you send is not corretly signed
113
135
  web3_connection_error: Impossible to connect the wallet
114
136
  authenticator_code_invalid: The code you inserted is not correct
137
+ webauthn_payload_missing: Passkey response missing. Please try again.
138
+ webauthn_challenge_missing: Passkey challenge expired. Start the registration again.
139
+ webauthn_registration_failed: Unable to verify the passkey response. Please restart the procedure.
140
+ webauthn_authentication_failed: Passkey authentication failed. Please try again or use another method.
115
141
  password:
116
142
  not_correct: not correct
117
143
  email:
@@ -55,12 +55,32 @@ fr:
55
55
  account_authenticator: Google Authenticator
56
56
  account_authenticator_start_title: Activer Google Authenticator
57
57
  account_authenticator_start_description: Générez un code QR en cliquant sur le bouton ci-dessous et scannez-le avec l'application Google Authenticator sur votre téléphone.<br>Cela vous permettra de protéger votre compte avec une authentification à deux facteurs.
58
+ account_authenticator_ready_title: Google Authenticator prêt
58
59
  account_authenticator_ready_qr: Scannez le code QR avec l'application Google Authenticator pour activer la protection de votre compte avec une authentification à deux facteurs.
59
- generate_qr_code: Générer un code QR
60
- cancel_authenticator: Déconnecter
61
- authenticator: Authentification à deux facteurs
60
+ account_authenticator_generate_qr_code: Générer un code QR
61
+ account_authenticator_disable: Déconnecter
62
+ authenticator: Google Authenticator
62
63
  authenticator_code_help: Saisissez le code généré par l'application Google Authenticator pour le compte <b>%{email}</b>
63
64
  reset_password: Réinitialisez votre mot de passe
65
+ choose_authentication_method: Choisir la méthode d'authentification
66
+ use_google_authenticator: Utiliser Google Authenticator
67
+ use_webauthn: Utiliser Passkey
68
+ authenticate: Authentifier
69
+ webauthn: Authentification Passkey
70
+ webauthn_help: Utilisez votre appareil pour vous authentifier sur le compte <b>%{email}</b>
71
+ account_webauthn: Authentification Passkey
72
+ account_webauthn_start_title: Activer les passkeys
73
+ account_webauthn_start_description: Utilisez la sécurité de votre appareil (Touch ID, Face ID, Windows Hello, etc.) pour approuver les connexions futures.<br>Cliquez sur le bouton ci-dessous et suivez les instructions de votre navigateur pour enregistrer une passkey.
74
+ account_webauthn_enable: Enregistrer la passkey
75
+ account_webauthn_in_progress_title: Validez l'inscription depuis votre navigateur
76
+ account_webauthn_in_progress_description: Approuvez la demande qui apparaît dans votre navigateur pour terminer l'association de la passkey à votre compte.
77
+ account_webauthn_in_progress_error: Impossible d'enregistrer la passkey. Redémarrez la procédure et réessayez.
78
+ account_webauthn_waiting_browser: En attente de la confirmation du navigateur...
79
+ account_webauthn_cancel: Annuler la demande
80
+ account_webauthn_finalize: Terminer l'inscription
81
+ account_webauthn_ready_title: Passkey activée
82
+ account_webauthn_ready_description: Ce compte nécessite désormais un défi WebAuthn juste après le processus de connexion classique.
83
+ account_webauthn_disable: Supprimer la passkey
64
84
  per_page: Par page
65
85
  per_page_description: Éléments par page
66
86
  confirm_title: Confirmation
@@ -73,6 +93,8 @@ fr:
73
93
  operation_failed_title: Échec de l'opération
74
94
  operation_failed_subtitle: Une erreur s'est produite lors de l'opération
75
95
  dropzone_drag_and_drop_or_click: Faites glisser et déposez les fichiers ici ou cliquez pour télécharger
96
+ add_item: Ajouter un élément
97
+ remove_item: Supprimer un élément
76
98
 
77
99
  invitation_mailer:
78
100
  invite_mail_subject: Vous avez reçu une invitation
@@ -120,6 +142,10 @@ fr:
120
142
  web3_address_invalid: L'adresse envoyée n'est pas correctement signée
121
143
  web3_connection_error: Impossible de connecter le portefeuille
122
144
  authenticator_code_invalid: Le code saisi n'est pas correct
145
+ webauthn_payload_missing: Réponse passkey manquante. Veuillez réessayer.
146
+ webauthn_challenge_missing: Le défi passkey a expiré. Veuillez relancer la procédure.
147
+ webauthn_registration_failed: Impossible de vérifier la réponse passkey. Redémarrez la procédure.
148
+ webauthn_authentication_failed: Échec de l'authentification passkey. Veuillez réessayer ou utiliser une autre méthode.
123
149
  password:
124
150
  not_correct: incorrect
125
151
  password_confirmation:
@@ -55,12 +55,32 @@ it:
55
55
  account_authenticator: Google Authenticator
56
56
  account_authenticator_start_title: Abilita Google Authenticator
57
57
  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.
58
+ account_authenticator_ready_title: Google Authenticator pronto
58
59
  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.
59
- generate_qr_code: Genera codice QR
60
- cancel_authenticator: Disconnetti
61
- authenticator: Autenticazione a due fattori
60
+ account_authenticator_generate_qr_code: Genera codice QR
61
+ account_authenticator_disable: Disconnetti
62
+ authenticator: Google Authenticator
62
63
  authenticator_code_help: Inserisci il codice generato dall'app Google Authenticator per l'account <b>%{email}</b>
63
64
  reset_password: Reimposta la tua password
65
+ choose_authentication_method: Scegli il metodo di autenticazione
66
+ use_google_authenticator: Usa Google Authenticator
67
+ use_webauthn: Usa Passkey
68
+ authenticate: Autentica
69
+ webauthn: Autenticazione Passkey
70
+ webauthn_help: Usa il tuo dispositivo per autenticarti nell'account <b>%{email}</b>
71
+ account_webauthn: Autenticazione Passkey
72
+ account_webauthn_start_title: Abilita le Passkey
73
+ account_webauthn_start_description: Utilizza la sicurezza del tuo dispositivo (Touch ID, Face ID, Windows Hello, ecc.) per approvare gli accessi futuri.<br>Clicca il pulsante qui sotto e segui le istruzioni del browser per registrare una Passkey.
74
+ account_webauthn_enable: Registra Passkey
75
+ account_webauthn_in_progress_title: Completa la registrazione dal browser
76
+ account_webauthn_in_progress_description: Approva la richiesta che appare nel browser per terminare l'associazione della Passkey al tuo account.
77
+ account_webauthn_in_progress_error: Impossibile registrare la Passkey. Riavvia la procedura e riprova.
78
+ account_webauthn_waiting_browser: In attesa della conferma dal browser...
79
+ account_webauthn_cancel: Annulla richiesta
80
+ account_webauthn_finalize: Concludi registrazione
81
+ account_webauthn_ready_title: Passkey attiva
82
+ account_webauthn_ready_description: Questo account richiederà ora una sfida WebAuthn subito dopo il flusso di login classico.
83
+ account_webauthn_disable: Rimuovi Passkey
64
84
  per_page: Per pagina
65
85
  per_page_description: Elementi per pagina
66
86
  confirm_title: Conferma
@@ -73,6 +93,8 @@ it:
73
93
  operation_failed_title: Operazione fallita
74
94
  operation_failed_subtitle: Si è verificato un errore durante l'operazione
75
95
  dropzone_drag_and_drop_or_click: Trascina qui i file o clicca per caricare
96
+ add_item: Aggiungi elemento
97
+ remove_item: Rimuovi elemento
76
98
 
77
99
  invitation_mailer:
78
100
  invite_mail_subject: Hai ricevuto un invito
@@ -120,6 +142,10 @@ it:
120
142
  web3_address_invalid: L'inidirizzo inviato non è correttamente firmato
121
143
  web3_connection_error: Impossibile connettere il wallet
122
144
  authenticator_code_invalid: Il codice inserito non è corretto
145
+ webauthn_payload_missing: Risposta Passkey mancante. Riprovare.
146
+ webauthn_challenge_missing: La sfida Passkey è scaduta. Avvia nuovamente la procedura.
147
+ webauthn_registration_failed: Impossibile verificare la risposta Passkey. Riavvia la procedura.
148
+ webauthn_authentication_failed: Autenticazione Passkey fallita. Riprova o usa un altro metodo.
123
149
  password:
124
150
  not_correct: non corretta
125
151
  password_confirmation: