no_password_auth 0.2.1

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.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README-ES.md +230 -0
  4. data/README.md +230 -0
  5. data/Rakefile +18 -0
  6. data/app/assets/config/no_password/manifest.js +4 -0
  7. data/app/assets/config/no_password/tailwind.config.js +61 -0
  8. data/app/assets/images/no_password/aoo.svg +1 -0
  9. data/app/assets/javascripts/no_password/application.js +4 -0
  10. data/app/assets/javascripts/no_password/controllers/alert_controller.js +31 -0
  11. data/app/assets/javascripts/no_password/controllers/application.js +10 -0
  12. data/app/assets/javascripts/no_password/controllers/index.js +11 -0
  13. data/app/assets/stylesheets/no_password/application.css +15 -0
  14. data/app/assets/stylesheets/no_password/application.tailwind.css +34 -0
  15. data/app/controllers/concerns/no_password/controller_helpers.rb +45 -0
  16. data/app/controllers/concerns/no_password/web_tokens.rb +33 -0
  17. data/app/controllers/no_password/application_controller.rb +4 -0
  18. data/app/controllers/no_password/session_confirmations_controller.rb +46 -0
  19. data/app/controllers/no_password/sessions_controller.rb +49 -0
  20. data/app/helpers/no_password/application_helper.rb +4 -0
  21. data/app/helpers/no_password/no_password_helper.rb +13 -0
  22. data/app/jobs/no_password/application_job.rb +4 -0
  23. data/app/mailers/no_password/application_mailer.rb +6 -0
  24. data/app/mailers/no_password/sessions_mailer.rb +16 -0
  25. data/app/models/no_password/application_record.rb +5 -0
  26. data/app/models/no_password/session.rb +20 -0
  27. data/app/use_cases/no_password/session_manager.rb +51 -0
  28. data/app/views/layouts/no_password/application.html.erb +26 -0
  29. data/app/views/layouts/no_password/mailer.html.erb +106 -0
  30. data/app/views/layouts/no_password/mailer.text.erb +6 -0
  31. data/app/views/no_password/application/_notification.html.erb +55 -0
  32. data/app/views/no_password/session_confirmations/_form.html.erb +19 -0
  33. data/app/views/no_password/session_confirmations/edit.html.erb +15 -0
  34. data/app/views/no_password/session_confirmations/update.turbo_stream.erb +3 -0
  35. data/app/views/no_password/sessions/_form.html.erb +16 -0
  36. data/app/views/no_password/sessions/create.turbo_stream.erb +3 -0
  37. data/app/views/no_password/sessions/new.html.erb +15 -0
  38. data/app/views/no_password/sessions_mailer/send_token.html.erb +14 -0
  39. data/app/views/no_password/sessions_mailer/send_token.text.erb +12 -0
  40. data/config/initializers/importmap.rb +17 -0
  41. data/config/locales/en/flash.en.yml +11 -0
  42. data/config/locales/en/forms.en.yml +9 -0
  43. data/config/locales/en/mailers.en.yml +21 -0
  44. data/config/locales/en/views.en.yml +12 -0
  45. data/config/locales/es/flash.es.yml +11 -0
  46. data/config/locales/es/forms.es.yml +9 -0
  47. data/config/locales/es/mailers.es.yml +18 -0
  48. data/config/locales/es/views.es.yml +13 -0
  49. data/config/locales/models.en.yml +8 -0
  50. data/config/locales/models.es.yml +8 -0
  51. data/config/locales/models.yml +8 -0
  52. data/config/routes.rb +7 -0
  53. data/db/migrate/20211202211706_create_no_password_sessions.rb +16 -0
  54. data/docs/aoorora-demo.gif +0 -0
  55. data/docs/dummy-app.png +0 -0
  56. data/lib/generators/no_password/install_generator.rb +30 -0
  57. data/lib/generators/no_password/install_templates_generator.rb +27 -0
  58. data/lib/generators/no_password/tailwind_config_generator.rb +9 -0
  59. data/lib/generators/no_password/templates/app/assets/config/no_password/tailwind.config.js.tt +57 -0
  60. data/lib/generators/no_password/templates/config/initializers/no_password.rb +10 -0
  61. data/lib/no_password/engine.rb +11 -0
  62. data/lib/no_password/railtie.rb +7 -0
  63. data/lib/no_password/version.rb +3 -0
  64. data/lib/no_password.rb +42 -0
  65. data/lib/tasks/install.rake +17 -0
  66. data/lib/tasks/no_password_tasks.rake +4 -0
  67. data/lib/tasks/tailwind.rake +23 -0
  68. metadata +227 -0
@@ -0,0 +1,34 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --color-base: 31, 27, 54;
8
+ --color-accented: 80, 70, 137;
9
+ --color-inverted: 255, 255, 255;
10
+ --color-accented-hover: 60, 53, 103;
11
+ --color-muted: 55, 65, 81;
12
+ --color-dimmed: 75, 85, 99;
13
+ --color-error: 220, 38, 38;
14
+
15
+ --color-border-base: 209, 213, 219;
16
+ --color-border-accented: 80, 70, 137;
17
+ }
18
+
19
+ .input {
20
+ @apply border-skin-base rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-skin-accented focus:border-skin-accented sm:text-sm;
21
+ }
22
+
23
+ .check {
24
+ @apply border-skin-base h-4 w-4 text-skin-accented focus:ring-skin-accented rounded;
25
+ }
26
+
27
+ .button {
28
+ @apply inline-flex justify-center py-2 px-4 border border-skin-base rounded-md shadow-sm text-sm font-medium text-skin-muted bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-skin-accented;
29
+ }
30
+
31
+ .button-accented {
32
+ @apply border-transparent bg-skin-button-accented hover:bg-skin-button-accented-hover text-skin-inverted;
33
+ }
34
+ }
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoPassword
4
+ module ControllerHelpers
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ def current_session
9
+ @session ||= begin
10
+ session_id = session[session_key]
11
+ return nil if session_id.blank?
12
+
13
+ current_session = find_session(session_id)
14
+ session[session_key] = nil if current_session.blank?
15
+
16
+ current_session
17
+ end
18
+ end
19
+
20
+ def signed_in_session?
21
+ current_session.present?
22
+ end
23
+
24
+ def authenticate_session!
25
+ redirect_to no_password.new_session_path(return_to: CGI.escape(request.fullpath)), alert: t("flash.update.session.alert") unless signed_in_session?
26
+ end
27
+
28
+ helper_method :current_session, :signed_in_session?
29
+
30
+ protected
31
+
32
+ def session_key(value = "id")
33
+ "—-no_password_session_#{value}"
34
+ end
35
+
36
+ private
37
+
38
+ def find_session(session_id)
39
+ NoPassword::Session.where.not(claimed_at: nil)
40
+ .where("expires_at > ?", Time.zone.now)
41
+ .find_by_id(session_id)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module NoPassword
6
+ module WebTokens
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ def sign_token(data)
11
+ secret_key = NoPassword.configuration.secret_key || Rails.application.secret_key_base
12
+ verifier = ActiveSupport::MessageVerifier.new(secret_key)
13
+ verifier.generate(data, expires_in: NoPassword.configuration.session_expiration, purpose: :no_password_login)
14
+ end
15
+
16
+ def verify_token(data)
17
+ secret_key = NoPassword.configuration.secret_key || Rails.application.secret_key_base
18
+ verifier = ActiveSupport::MessageVerifier.new(secret_key)
19
+
20
+ token = token_from_url(data)
21
+ verifier.verified(token, purpose: :no_password_login)
22
+ end
23
+
24
+ def token_to_url(token)
25
+ CGI.escape(token)
26
+ end
27
+
28
+ def token_from_url(token)
29
+ CGI.unescape(token)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,4 @@
1
+ module NoPassword
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoPassword
4
+ class SessionConfirmationsController < ApplicationController
5
+ include NoPassword::ControllerHelpers
6
+ include NoPassword::WebTokens
7
+
8
+ def edit
9
+ if params[:token].present?
10
+ token = verify_token(params[:token])
11
+
12
+ sign_in_session(token, true)
13
+ end
14
+ end
15
+
16
+ def update
17
+ result = sign_in_session(params[:token])
18
+ return result if result.present?
19
+
20
+ response.status = :unprocessable_entity
21
+ render turbo_stream: turbo_stream.update("notifications", partial: "notification")
22
+ end
23
+
24
+ private
25
+
26
+ def sign_in_session(token, by_url = false)
27
+ current_session = SessionManager.new.claim(token)
28
+
29
+ flash.now.alert = t("flash.update.invalid_code.alert") if current_session.blank?
30
+
31
+ result = if respond_to?(:after_sign_in!)
32
+ after_sign_in!(current_session.present?, by_url, current_session&.return_url)
33
+ elsif current_session.present?
34
+ save_session_to_cookie(current_session)
35
+ redirect_to(current_session.return_url || main_app.root_path)
36
+ end
37
+
38
+ result if result.present?
39
+ end
40
+
41
+ def save_session_to_cookie(current_session, key = nil, data = nil)
42
+ session[session_key] = current_session.id
43
+ session[session_key(key)] = data if key.present? && data.present?
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoPassword
4
+ class SessionsController < ApplicationController
5
+ include NoPassword::ControllerHelpers
6
+
7
+ def new
8
+ @return_to = params[:return_to].to_s
9
+ @resource = Session.new
10
+ end
11
+
12
+ def create
13
+ return_to = params[:return_to].to_s
14
+ current_session = SessionManager.new.create(request.user_agent, params.dig(:session, :email), request.remote_ip, referrer_path(return_to))
15
+
16
+ if current_session.present?
17
+ SessionsMailer.with(session: current_session).send_token.deliver_later
18
+
19
+ after_session_request if respond_to?(:after_session_request)
20
+
21
+ respond_to do |format|
22
+ format.html { redirect_to no_password.edit_session_confirmations_path }
23
+ format.turbo_stream
24
+ end
25
+ end
26
+ end
27
+
28
+ def destroy
29
+ sign_out
30
+ redirect_to main_app.root_path
31
+ end
32
+
33
+ private
34
+
35
+ def referrer_path(return_to)
36
+ referrer = CGI.unescape(return_to)
37
+ return nil if referrer.blank?
38
+
39
+ referrer.include?(no_password.new_session_path) || referrer.include?(no_password.edit_session_confirmations_path) ? nil : referrer
40
+ end
41
+
42
+ def sign_out(key = nil)
43
+ session.delete(session_key)
44
+ session.delete(session_key(key)) if key.present?
45
+ @session = nil
46
+ true
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,4 @@
1
+ module NoPassword
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,13 @@
1
+ module NoPassword
2
+ module NoPasswordHelper
3
+ def no_password_importmap_tags(entry_point = "application", shim: true)
4
+ safe_join [
5
+ javascript_inline_importmap_tag(NoPassword.configuration.importmap.to_json(resolver: self)),
6
+ javascript_importmap_module_preload_tags(NoPassword.configuration.importmap),
7
+ (javascript_importmap_shim_nonce_configuration_tag if shim),
8
+ (javascript_importmap_shim_tag if shim),
9
+ javascript_import_module_tag(entry_point)
10
+ ].compact, "\n"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ module NoPassword
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module NoPassword
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: I18n.t("mailers.default_from")
4
+ layout "no_password/mailer"
5
+ end
6
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoPassword
4
+ class SessionsMailer < ApplicationMailer
5
+ include NoPassword::WebTokens
6
+
7
+ def send_token
8
+ @session = params[:session]
9
+ @title = t("mailers.send_token.subject")
10
+ @friendly_token = @session.token
11
+ @signed_token = token_to_url(sign_token(@friendly_token))
12
+
13
+ mail(to: @session.email, subject: t("mailers.send_token.subject"))
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ module NoPassword
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoPassword
4
+ class Session < ApplicationRecord
5
+ validates :token, :user_agent, :remote_addr, :email, presence: true
6
+
7
+ def claimed?
8
+ claimed_at.present?
9
+ end
10
+
11
+ def expired?
12
+ current_time = Time.zone.now
13
+ expires_at <= current_time
14
+ end
15
+
16
+ def invalid?
17
+ claimed? || expired?
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoPassword
4
+ class SessionManager
5
+ def create(user_agent, email, remote_addr, return_url = nil)
6
+ expire_unclaimed_session(email)
7
+
8
+ Session.create(
9
+ user_agent: user_agent,
10
+ email: email,
11
+ expires_at: NoPassword.configuration.session_expiration.from_now,
12
+ token: generate_friendly_token,
13
+ remote_addr: remote_addr,
14
+ return_url: return_url
15
+ )
16
+ end
17
+
18
+ def claim(token)
19
+ current_time = Time.zone.now
20
+
21
+ session = Session
22
+ .where(token: token, claimed_at: nil)
23
+ .where("expires_at > ?", current_time)
24
+ .first
25
+
26
+ if session.present?
27
+ session.claimed_at = current_time
28
+ session.save
29
+
30
+ return session
31
+ end
32
+
33
+ nil
34
+ end
35
+
36
+ private
37
+
38
+ def expire_unclaimed_session(email)
39
+ current_time = Time.zone.now
40
+
41
+ Session
42
+ .where(email: email, claimed_at: nil)
43
+ .where("expires_at > ?", current_time)
44
+ .update_all(expires_at: current_time)
45
+ end
46
+
47
+ def generate_friendly_token
48
+ "#{SecureRandom.alphanumeric(4)}-#{SecureRandom.random_number(10_000)}-#{SecureRandom.alphanumeric(4)}"
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>No Password</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <%= csrf_meta_tags %>
7
+ <%= csp_meta_tag %>
8
+
9
+ <%= stylesheet_link_tag "no_password/tailwind", "inter-font", "data-turbo-track": "reload" %>
10
+ <%= stylesheet_link_tag "no_password/application", media: "all", "data-turbo-track": "reload" %>
11
+
12
+ <%= no_password_importmap_tags %>
13
+ </head>
14
+
15
+ <body>
16
+ <div aria-live="assertive" class="fixed inset-0 z-50 flex items-end px-4 py-6 pointer-events-none sm:p-6 sm:items-start">
17
+ <%= turbo_frame_tag "notifications", class: "w-full flex flex-col items-center space-y-4 sm:items-end" do %>
18
+ <%= render partial: "notification" %>
19
+ <% end %>
20
+ </div>
21
+
22
+ <section>
23
+ <%= yield %>
24
+ </section>
25
+ </body>
26
+ </html>
@@ -0,0 +1,106 @@
1
+ <!DOCTYPE html>
2
+ <html lang="es" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="x-apple-disable-message-reformatting">
7
+ <meta http-equiv="x-ua-compatible" content="ie=edge">
8
+ <meta name="viewport" content="width=device-width, initial-scale=1">
9
+ <meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
10
+ <!--[if mso]>
11
+ <xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml>
12
+ <style>
13
+ td,th,div,p,a,h1,h2,h3,h4,h5,h6 {font-family: "Segoe UI", sans-serif; mso-line-height-rule: exactly;}
14
+ </style>
15
+ <![endif]-->
16
+ <title>
17
+ <%= @title %>
18
+ </title>
19
+ <style>
20
+ .hover-underline:hover {
21
+ text-decoration: underline !important;
22
+ }
23
+
24
+ @media (max-width: 600px) {
25
+ .sm-h-32 {
26
+ height: 32px !important;
27
+ }
28
+
29
+ .sm-w-full {
30
+ width: 100% !important;
31
+ }
32
+
33
+ .sm-px-24 {
34
+ padding-left: 24px !important;
35
+ padding-right: 24px !important;
36
+ }
37
+
38
+ .sm-pb-12 {
39
+ padding-bottom: 12px !important;
40
+ }
41
+ }
42
+ </style>
43
+ </head>
44
+
45
+ <body
46
+ style="margin: 0; width: 100%; padding: 0; word-break: break-word; -webkit-font-smoothing: antialiased; height: 100%; background-color: #f3f4f6;">
47
+ <div role="article" aria-roledescription="email" aria-label="<%= @title %>" lang="es">
48
+ <table
49
+ style="height: 100%; width: 100%; font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;"
50
+ cellpadding="0" cellspacing="0" role="presentation">
51
+ <tr>
52
+ <td align="center"
53
+ style="background-color: #f3f4f6; padding-top: 24px; padding-bottom: 24px; vertical-align: middle;"
54
+ valign="middle">
55
+ <table class="sm-w-full" style="width: 600px;" cellpadding="0" cellspacing="0" role="presentation">
56
+ <tr>
57
+ <td class="sm-pb-12" style="padding-bottom: 24px; text-align: center;">
58
+ <h1 style="font-size: 24px; font-weight: 600; color: #000000;">
59
+ <%= @title %>
60
+ </h1>
61
+ </td>
62
+ </tr>
63
+ <tr>
64
+ <td align="center" class="sm-px-24">
65
+ <table class="sm-w-full" style="width: 75%;" cellpadding="0" cellspacing="0" role="presentation">
66
+ <tr>
67
+ <td>
68
+ <div class="sm-px-24" style="overflow: hidden; border-radius: 6px; background-color: #ffffff; padding: 36px; box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);">
69
+ <%= image_tag("no_password/aoo.svg", style: "height: 3rem; width: auto; margin-bottom: 2rem;")%>
70
+ <%= yield %>
71
+ </div>
72
+ </td>
73
+ </tr>
74
+ <tr>
75
+ <td style="padding: 32px; text-align: center; font-size: 12px; color: #4b5563;">
76
+ <%= link_to "https://aoorora.com" do %>
77
+ <%= image_tag("no_password/aoo.svg",
78
+ style: "width: 120px;margin-bottom: 24px;") %>
79
+ <% end %>
80
+ <p style="margin: 0 0 4px; text-transform: uppercase;">
81
+ <%= t(".service") %>
82
+ </p>
83
+ <p style="margin: 0; font-style: italic;">
84
+ <%= t(".platform") %>
85
+ </p>
86
+ <p style="cursor: default;">
87
+ <%= link_to "Aoorora" , "https://aoorora.com" , class: "hover-underline" ,
88
+ style: "color: #4b5563; text-decoration: none;" %> &bull;
89
+ <%= link_to "OnBoarding" , "https://aoorora.com" , class: "hover-underline" ,
90
+ style: "color: #4b5563; text-decoration: none;" %> &bull;
91
+ <%= link_to "Identidad" , "https://aoorora.com/identidad" , class: "hover-underline" ,
92
+ style: "color: #4b5563; text-decoration: none;" %>
93
+ </p>
94
+ </td>
95
+ </tr>
96
+ </table>
97
+ </td>
98
+ </tr>
99
+ </table>
100
+ </td>
101
+ </tr>
102
+ </table>
103
+ </div>
104
+ </body>
105
+
106
+ </html>
@@ -0,0 +1,6 @@
1
+ <%= yield %>
2
+
3
+ -----------
4
+
5
+ https://aoorora.com
6
+ <%= t(".service") %> <%= t(".platform") %>
@@ -0,0 +1,55 @@
1
+ <% unless flash.empty? %>
2
+ <% if flash.alert.present? && flash.alert.is_a?(Hash) %>
3
+ <% title = flash.alert&.dig(:title) || flash.alert&.dig("title") %>
4
+ <% description = flash.alert&.dig(:description) || flash.alert&.dig("description") %>
5
+ <% elsif flash.notice.present? && flash.notice.is_a?(Hash) %>
6
+ <% title = flash.notice&.dig(:title) || flash.notice&.dig("title") %>
7
+ <% description = flash.notice&.dig(:description) || flash.notice&.dig("description") %>
8
+ <% else %>
9
+ <% title = "Aplicación" %>
10
+ <% description = flash.notice || flash.alert %>
11
+ <% end %>
12
+
13
+ <div class="flex flex-col items-center w-full space-y-4 sm:items-end" data-controller="alert">
14
+ <div class="w-full max-w-sm overflow-hidden bg-white rounded-lg shadow-lg pointer-events-auto ring-1 ring-black ring-opacity-5 hidden"
15
+ data-alert-target="notification"
16
+ data-transition-enter="transform ease-out duration-300 transition"
17
+ data-transition-enter-active="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
18
+ data-transition-enter-to="translate-y-0 opacity-100 sm:translate-x-0"
19
+ data-transition-leave="transition ease-in duration-100"
20
+ data-transition-leave-active="opacity-100"
21
+ data-transition-leave-to="opacity-0">
22
+ <div class="p-4">
23
+ <div class="flex items-start">
24
+ <div class="flex-shrink-0">
25
+ <% if alert %>
26
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
27
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
28
+ </svg>
29
+ <% else %>
30
+ <svg class="w-6 h-6 text-green-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
31
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
32
+ </svg>
33
+ <% end %>
34
+ </div>
35
+ <div class="ml-3 w-0 flex-1 pt-0.5">
36
+ <p class="text-sm font-medium text-gray-900">
37
+ <%= title %>
38
+ </p>
39
+ <p class="mt-1 text-sm text-gray-500">
40
+ <%= description %>
41
+ </p>
42
+ </div>
43
+ <div class="flex flex-shrink-0 ml-4">
44
+ <button data-action="alert#close" class="inline-flex text-gray-400 bg-white rounded-md hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-creditario-500">
45
+ <span class="sr-only">Close</span>
46
+ <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
47
+ <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
48
+ </svg>
49
+ </button>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ <% end %>
@@ -0,0 +1,19 @@
1
+ <%= turbo_frame_tag :form do %>
2
+ <%= form_with url: no_password.session_confirmations_path, method: "patch", class: "space-y-6", html: { "data-turbo-frame": "_top" } do |form| %>
3
+ <p class="text-skin-muted px-4 text-center">
4
+ <%= t("no_password.session_confirmations.edit.description") %>
5
+ </p>
6
+ <div>
7
+ <label class="block text-sm font-medium text-skin-muted"><%= form.label t("form.labels.token") %></label>
8
+ <div class="mt-1">
9
+ <%= form.text_field :token, maxlength: 60, required: true, class: "input w-full", type: "text", placeholder: t("form.placeholders.token") %>
10
+ </div>
11
+ </div>
12
+ <div>
13
+ <%= form.submit t("form.submit.login"), class: "w-full flex button button-accented" %>
14
+ </div>
15
+ <div>
16
+ <%= link_to t("no_password.session_confirmations.edit.request_token"), no_password.new_session_path, class: "w-full block text-center text-skin-accented hover:text-skin-accented-hover", "data-turbo": false %>
17
+ </div>
18
+ <% end %>
19
+ <% end %>
@@ -0,0 +1,15 @@
1
+ <div class="h-screen bg-gray-50">
2
+ <div class="min-h-full flex flex-col justify-center py-12 sm:px-6 lg:px-8">
3
+ <div class="sm:mx-auto sm:w-full sm:max-w-md">
4
+ <%= image_tag("no_password/aoo.svg", class:"mx-auto h-12 w-auto", alt: "Workflow")%>
5
+ <h2 class="mt-6 text-center text-3xl font-bold text-skin-base leading-3">
6
+ <%= t("no_password.session_confirmations.edit.title") %>
7
+ </h2>
8
+ </div>
9
+ <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
10
+ <div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
11
+ <%= render partial: "form" %>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ </div>
@@ -0,0 +1,3 @@
1
+ <%= turbo_stream.replace "notifications" do %>
2
+ <%= render partial: "notification" %>
3
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <%= turbo_frame_tag :form do %>
2
+ <%= form_with model: @resource, local: true, url: no_password.session_path(return_to: @return_to), class: "space-y-6", html: {"data-turbo": false} do |form| %>
3
+ <p class="text-skin-muted px-4 text-center">
4
+ <%= t("no_password.sessions.new.description") %>
5
+ </p>
6
+ <div>
7
+ <label class="block text-sm font-medium text-skin-muted"><%= form.label :email %></label>
8
+ <div class="mt-1">
9
+ <%= form.text_field :email, maxlength: 60, required: true, class: "input w-full", type: "email", placeholder: t("form.placeholders.email") %>
10
+ </div>
11
+ </div>
12
+ <div>
13
+ <%= form.submit t("form.submit.login"), class: "w-full flex button button-accented" %>
14
+ </div>
15
+ <% end %>
16
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <%= turbo_stream.replace :form do %>
2
+ <%= render partial: "no_password/session_confirmations/form" %>
3
+ <% end %>
@@ -0,0 +1,15 @@
1
+ <div class="h-screen bg-gray-50">
2
+ <div class="min-h-full flex flex-col justify-center py-12 sm:px-6 lg:px-8">
3
+ <div class="sm:mx-auto sm:w-full sm:max-w-md">
4
+ <%= image_tag("no_password/aoo.svg", class:"mx-auto h-12 w-auto", alt: "Workflow")%>
5
+ <h2 class="mt-6 text-center text-3xl font-bold text-skin-base leading-3">
6
+ <%= t("no_password.sessions.new.title") %>
7
+ </h2>
8
+ </div>
9
+ <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
10
+ <div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
11
+ <%= render partial: "form" %>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ </div>
@@ -0,0 +1,14 @@
1
+ <p style="margin: 0; font-size: 18px;"><%= t("mailers.send_token.greetings") %></p>
2
+ <p style="font-size: 16px; color: #374151;"><%= t("mailers.send_token.instructions_1") %></p>
3
+ <p style="text-align:center; font-size: 20px; color: #374151; font-weight: 600;"><%= @friendly_token %></p>
4
+ <p style="font-size: 16px; color: #374151;"><%= t("mailers.send_token.instructions_2") %></p>
5
+ <div style="margin: auto 0; text-align: center;">
6
+ <%= link_to no_password.session_confirmation_url(token: @signed_token), class: "hover-bg-skin-button-accented-hover", style: "display: inline-block; margin: auto; border-radius: 6px; background-color: rgb(80, 70, 137); padding: 16px 24px; font-size: 16px; font-weight: 500; color: rgb(255, 255, 255); text-decoration: none; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);" do %>
7
+ <!--[if mso]><i style="letter-spacing: 24px; mso-font-width: -100%; mso-text-raise: 26pt;">&nbsp;</i><![endif]-->
8
+ <span style="mso-text-raise: 13pt;"><%= t("mailers.send_token.start_session") %></span>
9
+ <!--[if mso]><i style="letter-spacing: 24px; mso-font-width: -100%;">&nbsp;</i><![endif]-->
10
+ <% end %>
11
+ </div>
12
+
13
+ <hr style="margin-top: 25px; margin-bottom: 25px; border: 1px solid #f3f4f6" />
14
+ <p style="font-size: 16px; color: #374151;"><%= t("mailers.send_token.instructions_3") %></p>
@@ -0,0 +1,12 @@
1
+ <%= t("mailers.send_token.subject") %>
2
+
3
+ <%= t("mailers.send_token.greetings") %>
4
+
5
+ <%= t("mailers.send_token.instructions_1") %>
6
+
7
+ <%= @friendly_token %>
8
+
9
+ <%= t("mailers.send_token.instructions_2_text") %>
10
+ <%= no_password.session_confirmation_url(token: @signed_token)%>
11
+
12
+ <%= t("mailers.send_token.instructions_3") %>