no_password_auth 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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") %>