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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README-ES.md +230 -0
- data/README.md +230 -0
- data/Rakefile +18 -0
- data/app/assets/config/no_password/manifest.js +4 -0
- data/app/assets/config/no_password/tailwind.config.js +61 -0
- data/app/assets/images/no_password/aoo.svg +1 -0
- data/app/assets/javascripts/no_password/application.js +4 -0
- data/app/assets/javascripts/no_password/controllers/alert_controller.js +31 -0
- data/app/assets/javascripts/no_password/controllers/application.js +10 -0
- data/app/assets/javascripts/no_password/controllers/index.js +11 -0
- data/app/assets/stylesheets/no_password/application.css +15 -0
- data/app/assets/stylesheets/no_password/application.tailwind.css +34 -0
- data/app/controllers/concerns/no_password/controller_helpers.rb +45 -0
- data/app/controllers/concerns/no_password/web_tokens.rb +33 -0
- data/app/controllers/no_password/application_controller.rb +4 -0
- data/app/controllers/no_password/session_confirmations_controller.rb +46 -0
- data/app/controllers/no_password/sessions_controller.rb +49 -0
- data/app/helpers/no_password/application_helper.rb +4 -0
- data/app/helpers/no_password/no_password_helper.rb +13 -0
- data/app/jobs/no_password/application_job.rb +4 -0
- data/app/mailers/no_password/application_mailer.rb +6 -0
- data/app/mailers/no_password/sessions_mailer.rb +16 -0
- data/app/models/no_password/application_record.rb +5 -0
- data/app/models/no_password/session.rb +20 -0
- data/app/use_cases/no_password/session_manager.rb +51 -0
- data/app/views/layouts/no_password/application.html.erb +26 -0
- data/app/views/layouts/no_password/mailer.html.erb +106 -0
- data/app/views/layouts/no_password/mailer.text.erb +6 -0
- data/app/views/no_password/application/_notification.html.erb +55 -0
- data/app/views/no_password/session_confirmations/_form.html.erb +19 -0
- data/app/views/no_password/session_confirmations/edit.html.erb +15 -0
- data/app/views/no_password/session_confirmations/update.turbo_stream.erb +3 -0
- data/app/views/no_password/sessions/_form.html.erb +16 -0
- data/app/views/no_password/sessions/create.turbo_stream.erb +3 -0
- data/app/views/no_password/sessions/new.html.erb +15 -0
- data/app/views/no_password/sessions_mailer/send_token.html.erb +14 -0
- data/app/views/no_password/sessions_mailer/send_token.text.erb +12 -0
- data/config/initializers/importmap.rb +17 -0
- data/config/locales/en/flash.en.yml +11 -0
- data/config/locales/en/forms.en.yml +9 -0
- data/config/locales/en/mailers.en.yml +21 -0
- data/config/locales/en/views.en.yml +12 -0
- data/config/locales/es/flash.es.yml +11 -0
- data/config/locales/es/forms.es.yml +9 -0
- data/config/locales/es/mailers.es.yml +18 -0
- data/config/locales/es/views.es.yml +13 -0
- data/config/locales/models.en.yml +8 -0
- data/config/locales/models.es.yml +8 -0
- data/config/locales/models.yml +8 -0
- data/config/routes.rb +7 -0
- data/db/migrate/20211202211706_create_no_password_sessions.rb +16 -0
- data/docs/aoorora-demo.gif +0 -0
- data/docs/dummy-app.png +0 -0
- data/lib/generators/no_password/install_generator.rb +30 -0
- data/lib/generators/no_password/install_templates_generator.rb +27 -0
- data/lib/generators/no_password/tailwind_config_generator.rb +9 -0
- data/lib/generators/no_password/templates/app/assets/config/no_password/tailwind.config.js.tt +57 -0
- data/lib/generators/no_password/templates/config/initializers/no_password.rb +10 -0
- data/lib/no_password/engine.rb +11 -0
- data/lib/no_password/railtie.rb +7 -0
- data/lib/no_password/version.rb +3 -0
- data/lib/no_password.rb +42 -0
- data/lib/tasks/install.rake +17 -0
- data/lib/tasks/no_password_tasks.rake +4 -0
- data/lib/tasks/tailwind.rake +23 -0
- 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,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,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,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,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;" %> •
|
89
|
+
<%= link_to "OnBoarding" , "https://aoorora.com" , class: "hover-underline" ,
|
90
|
+
style: "color: #4b5563; text-decoration: none;" %> •
|
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,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,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,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;"> </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%;"> </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") %>
|