searls-auth 0.0.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/.standard.yml +3 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +675 -0
- data/README.md +1 -0
- data/Rakefile +6 -0
- data/app/controllers/searls/auth/base_controller.rb +35 -0
- data/app/controllers/searls/auth/logins_controller.rb +44 -0
- data/app/controllers/searls/auth/registrations_controller.rb +35 -0
- data/app/controllers/searls/auth/verifications_controller.rb +64 -0
- data/app/helpers/searls/auth/application_helper.rb +82 -0
- data/app/javascript/controllers/searls_auth_login_controller.js +37 -0
- data/app/javascript/controllers/searls_auth_otp_controller.js +21 -0
- data/app/mailers/searls/auth/base_mailer.rb +8 -0
- data/app/mailers/searls/auth/login_link_mailer.rb +24 -0
- data/app/views/searls/auth/layouts/mailer.html.erb +54 -0
- data/app/views/searls/auth/login_link_mailer/login_link.html.erb +37 -0
- data/app/views/searls/auth/login_link_mailer/login_link.text.erb +20 -0
- data/app/views/searls/auth/logins/show.html.erb +20 -0
- data/app/views/searls/auth/registrations/show.html.erb +19 -0
- data/app/views/searls/auth/verifications/show.html.erb +23 -0
- data/config/importmap.rb +2 -0
- data/config/routes.rb +12 -0
- data/lib/searls/auth/authenticates_user.rb +32 -0
- data/lib/searls/auth/config.rb +47 -0
- data/lib/searls/auth/creates_user.rb +37 -0
- data/lib/searls/auth/emails_link.rb +15 -0
- data/lib/searls/auth/engine.rb +28 -0
- data/lib/searls/auth/railtie.rb +20 -0
- data/lib/searls/auth/resets_session.rb +13 -0
- data/lib/searls/auth/version.rb +5 -0
- data/lib/searls/auth.rb +63 -0
- metadata +87 -0
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
|
3
|
+
module Searls
|
4
|
+
module Auth
|
5
|
+
class BaseController < ApplicationController # TODO should this be ActionController::Base? Trade-offs?
|
6
|
+
helper Rails.application.helpers
|
7
|
+
helper Rails.application.routes.url_helpers
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
def searls_auth_config
|
12
|
+
Searls::Auth.config
|
13
|
+
end
|
14
|
+
|
15
|
+
def attach_short_code_to_session!(user)
|
16
|
+
session[:email_auth_short_code_user_id] = user.id
|
17
|
+
session[:email_auth_short_code] = SecureRandom.random_number(1000000).to_s.rjust(6, "0")
|
18
|
+
session[:email_auth_short_code_generated_at] = Time.current
|
19
|
+
end
|
20
|
+
|
21
|
+
def reset_expired_short_code
|
22
|
+
if session[:email_auth_short_code_generated_at].present? &&
|
23
|
+
Time.zone.parse(session[:email_auth_short_code_generated_at]) < Searls::Auth.config.token_expiry_minutes.minutes.ago
|
24
|
+
clear_short_code_from_session!
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def clear_short_code_from_session!
|
29
|
+
session.delete(:email_auth_short_code_user_id)
|
30
|
+
session.delete(:email_auth_short_code_generated_at)
|
31
|
+
session.delete(:email_auth_short_code)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Searls
|
2
|
+
module Auth
|
3
|
+
class LoginsController < BaseController
|
4
|
+
before_action :reset_expired_short_code
|
5
|
+
|
6
|
+
def show
|
7
|
+
render searls_auth_config.login_view, layout: searls_auth_config.layout
|
8
|
+
end
|
9
|
+
|
10
|
+
def create
|
11
|
+
user = searls_auth_config.user_finder_by_email.call(params[:email])
|
12
|
+
|
13
|
+
if user.present?
|
14
|
+
attach_short_code_to_session!(user)
|
15
|
+
EmailsLink.new.email(
|
16
|
+
user:,
|
17
|
+
redirect_path: params[:redirect_path],
|
18
|
+
redirect_subdomain: params[:redirect_subdomain],
|
19
|
+
short_code: session[:email_auth_short_code]
|
20
|
+
)
|
21
|
+
flash[:notice] = "Login details sent to #{params[:email]}"
|
22
|
+
redirect_to verify_path(
|
23
|
+
redirect_path: params[:redirect_path],
|
24
|
+
redirect_subdomain: params[:redirect_subdomain]
|
25
|
+
)
|
26
|
+
else
|
27
|
+
flash.now[:error] = "We don't know that email. <a href=\"#{register_path(
|
28
|
+
email: params[:email],
|
29
|
+
redirect_path: params[:redirect_path],
|
30
|
+
redirect_subdomain: params[:redirect_subdomain]
|
31
|
+
)}\">Sign up</a> instead?".html_safe
|
32
|
+
render searls_auth_config.login_view, layout: searls_auth_config.layout, status: :unprocessable_entity
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def destroy
|
37
|
+
ResetsSession.new.reset(self, except_for: [:has_logged_in_before])
|
38
|
+
|
39
|
+
flash[:notice] = "You've been logged out."
|
40
|
+
redirect_to login_path
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Searls
|
2
|
+
module Auth
|
3
|
+
class RegistrationsController < BaseController
|
4
|
+
def show
|
5
|
+
render searls_auth_config.register_view, layout: searls_auth_config.layout
|
6
|
+
end
|
7
|
+
|
8
|
+
def create
|
9
|
+
result = CreatesUser.new.call(params)
|
10
|
+
|
11
|
+
if result.success?
|
12
|
+
attach_short_code_to_session!(result.user)
|
13
|
+
|
14
|
+
redirect_params = {
|
15
|
+
redirect_path: searls_auth_config.resolve(
|
16
|
+
:redirect_path_after_register,
|
17
|
+
result.user, params, request, main_app
|
18
|
+
)
|
19
|
+
}
|
20
|
+
|
21
|
+
EmailsLink.new.email(
|
22
|
+
user: result.user,
|
23
|
+
short_code: session[:email_auth_short_code],
|
24
|
+
**redirect_params
|
25
|
+
)
|
26
|
+
flash[:notice] = "Verification email sent to #{params[:email]}"
|
27
|
+
redirect_to verify_path(**redirect_params)
|
28
|
+
else
|
29
|
+
flash.now[:error] = result.error_messages
|
30
|
+
render searls_auth_config.register_view, layout: searls_auth_config.layout, status: :unprocessable_entity
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Searls
|
2
|
+
module Auth
|
3
|
+
class VerificationsController < BaseController
|
4
|
+
before_action :reset_expired_short_code
|
5
|
+
|
6
|
+
def show
|
7
|
+
render searls_auth_config.verify_view, layout: searls_auth_config.layout
|
8
|
+
end
|
9
|
+
|
10
|
+
def create
|
11
|
+
auth_method = params[:short_code].present? ? :short_code : :token
|
12
|
+
authenticator = AuthenticatesUser.new
|
13
|
+
result = case auth_method
|
14
|
+
when :short_code
|
15
|
+
authenticator.authenticate_by_short_code(params[:short_code], session)
|
16
|
+
when :token
|
17
|
+
authenticator.authenticate_by_token(params[:token])
|
18
|
+
end
|
19
|
+
|
20
|
+
if result.success?
|
21
|
+
session[:user_id] = result.user.id
|
22
|
+
session[:has_logged_in_before] = true
|
23
|
+
if params[:redirect_subdomain].present? && params[:redirect_subdomain] != request.subdomain
|
24
|
+
redirect_to generate_full_url(
|
25
|
+
params[:redirect_path],
|
26
|
+
params[:redirect_subdomain]
|
27
|
+
), allow_other_host: true
|
28
|
+
elsif params[:redirect_path].present?
|
29
|
+
redirect_to params[:redirect_path]
|
30
|
+
else
|
31
|
+
redirect_to searls_auth_config.resolve(:default_redirect_path_after_login,
|
32
|
+
result.user, params, request, main_app)
|
33
|
+
end
|
34
|
+
elsif auth_method == :short_code
|
35
|
+
flash[:error] = "We weren't able to log you in with that code. Try again?"
|
36
|
+
render searls_auth_config.verify_view, layout: searls_auth_config.layout, status: :unprocessable_entity
|
37
|
+
else
|
38
|
+
flash[:error] = "We weren't able to log you in with that link. Try again?"
|
39
|
+
redirect_to login_path(
|
40
|
+
redirect_path: params[:redirect_path],
|
41
|
+
redirect_subdomain: params[:redirect_subdomain]
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def destroy
|
47
|
+
ResetsSession.new.reset(self,
|
48
|
+
except_for: searls_auth_config.preserve_session_keys_after_logout)
|
49
|
+
|
50
|
+
flash[:notice] = "You've been logged out."
|
51
|
+
redirect_to login_path
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def generate_full_url(path, subdomain)
|
57
|
+
port = request.port
|
58
|
+
port_string = (port == 80 || port == 443) ? "" : ":#{port}"
|
59
|
+
|
60
|
+
"#{request.protocol}#{subdomain}.#{request.domain}#{port_string}#{path}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Searls
|
2
|
+
module Auth
|
3
|
+
class HelperMethods
|
4
|
+
def initialize(view_context)
|
5
|
+
@view_context = view_context
|
6
|
+
end
|
7
|
+
|
8
|
+
def routes
|
9
|
+
Searls::Auth::Engine.routes.url_helpers
|
10
|
+
end
|
11
|
+
|
12
|
+
def login_path(**kwargs)
|
13
|
+
routes.login_path(forwardable_params.merge(kwargs))
|
14
|
+
end
|
15
|
+
|
16
|
+
def login_url(**kwargs)
|
17
|
+
routes.login_url(forwardable_params.merge(kwargs))
|
18
|
+
end
|
19
|
+
|
20
|
+
def register_path(**kwargs)
|
21
|
+
routes.register_path(forwardable_params.merge(kwargs))
|
22
|
+
end
|
23
|
+
|
24
|
+
def register_url(**kwargs)
|
25
|
+
routes.register_url(forwardable_params.merge(kwargs))
|
26
|
+
end
|
27
|
+
|
28
|
+
def login_stimulus_controller
|
29
|
+
"searls-auth-login"
|
30
|
+
end
|
31
|
+
|
32
|
+
def email_field_stimulus_data
|
33
|
+
{
|
34
|
+
action: "change->searls-auth-login#updateEmail"
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def otp_stimulus_controller
|
39
|
+
"searls-auth-otp"
|
40
|
+
end
|
41
|
+
|
42
|
+
def otp_field_stimulus_data
|
43
|
+
{
|
44
|
+
action: "paste->searls-auth-otp#pasted input->otp#caret click->searls-auth-otp#caret keydown->searls-auth-otp#caret keyup->searls-auth-otp#caret",
|
45
|
+
searls_auth_otp_target: "input"
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def enable_turbo?
|
50
|
+
params[:redirect_subdomain].blank? || params[:redirect_subdomain] == request.subdomain
|
51
|
+
end
|
52
|
+
|
53
|
+
def attr_for(model, field_name)
|
54
|
+
model.attributes[field_name.to_s]
|
55
|
+
end
|
56
|
+
|
57
|
+
def rpad(s, spacer = " ", times = 1)
|
58
|
+
"#{s}#{spacer * times}"
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def forwardable_params
|
64
|
+
{
|
65
|
+
email: params[:email],
|
66
|
+
redirect_path: params[:redirect_path],
|
67
|
+
redirect_subdomain: params[:redirect_subdomain]
|
68
|
+
}.compact_blank
|
69
|
+
end
|
70
|
+
|
71
|
+
def params
|
72
|
+
@view_context.params
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
module ApplicationHelper
|
77
|
+
def searls_auth_helper
|
78
|
+
@__searls_auth_helper ||= HelperMethods.new(self)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
export default class SearlsAuthLoginController extends Controller {
|
4
|
+
static values = {
|
5
|
+
email: String,
|
6
|
+
emailFieldName: {
|
7
|
+
type: String,
|
8
|
+
default: 'email'
|
9
|
+
},
|
10
|
+
timeZoneFieldName: {
|
11
|
+
type: String,
|
12
|
+
default: 'time_zone'
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
connect () {
|
17
|
+
this.emailValue = window.sessionStorage.getItem('__searls_auth_email') || undefined
|
18
|
+
if (this.emailValue) {
|
19
|
+
this.element.querySelector(`[name=${this.emailFieldNameValue}]`).value = this.emailValue
|
20
|
+
}
|
21
|
+
|
22
|
+
this.#setTimeZoneFieldMaybe()
|
23
|
+
}
|
24
|
+
|
25
|
+
updateEmail (e) {
|
26
|
+
debugger
|
27
|
+
this.emailValue = e.currentTarget.value
|
28
|
+
window.sessionStorage.setItem('__searls_auth_email', this.emailValue)
|
29
|
+
}
|
30
|
+
|
31
|
+
#setTimeZoneFieldMaybe () {
|
32
|
+
const hiddenField = this.element.querySelector(`[name=${this.timeZoneFieldNameValue}]`)
|
33
|
+
if (hiddenField) {
|
34
|
+
hiddenField.value ||= Intl.DateTimeFormat().resolvedOptions().timeZone
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
export default class SearlsAuthOtpController extends Controller {
|
4
|
+
static targets = ['input']
|
5
|
+
|
6
|
+
pasted (e) {
|
7
|
+
const content = e.clipboardData?.getData('text')
|
8
|
+
if (content && content.trim().match(/^\d{6}$/)) {
|
9
|
+
this.inputTarget.value = content.trim()
|
10
|
+
this.inputTarget.closest('form').requestSubmit()
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
// Maybe not every design needs this but mine does: hide the caret if it's after the end of the max length
|
15
|
+
caret () {
|
16
|
+
const shouldHideCaret = this.inputTarget.maxLength === this.inputTarget.selectionStart &&
|
17
|
+
this.inputTarget.maxLength === this.inputTarget.selectionEnd
|
18
|
+
|
19
|
+
this.inputTarget.style.caretColor = shouldHideCaret ? 'transparent' : ''
|
20
|
+
}
|
21
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Searls
|
2
|
+
module Auth
|
3
|
+
class LoginLinkMailer < BaseMailer
|
4
|
+
def login_link
|
5
|
+
@config = Searls::Auth.config
|
6
|
+
@user = params[:user]
|
7
|
+
@token = params[:token]
|
8
|
+
@redirect_path = params[:redirect_path]
|
9
|
+
@redirect_subdomain = params[:redirect_subdomain]
|
10
|
+
@short_code = params[:short_code]
|
11
|
+
|
12
|
+
mail(
|
13
|
+
to: format_to(@user),
|
14
|
+
subject: "Your #{searls_auth_helper.rpad(@config.app_name)} login code is #{@short_code}",
|
15
|
+
template_path: @config.mail_login_template_path,
|
16
|
+
template_name: @config.mail_login_template_name
|
17
|
+
) do |format|
|
18
|
+
format.html { render layout: @config.mail_layout }
|
19
|
+
format.text
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
6
|
+
<meta name="color-scheme" content="light">
|
7
|
+
<meta name="supported-color-schemes" content="light only">
|
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, url=no">
|
10
|
+
<meta name="x-apple-disable-message-reformatting">
|
11
|
+
<title><%= message.subject %></title>
|
12
|
+
</head>
|
13
|
+
<body style="background-color: <%= Searls::Auth.config.email_background_color %>;">
|
14
|
+
<div role="article" aria-roledescription="email" aria-label="<%= message.subject %>" lang="en">
|
15
|
+
<table border="0" cellpadding="0" cellspacing="0" style="font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: <%= Searls::Auth.config.email_background_color %>; color: #111827;" width="100%">
|
16
|
+
<tr>
|
17
|
+
<td height="96" align="center">
|
18
|
+
<% if Searls::Auth.config.email_banner_image_path.present? %>
|
19
|
+
<% banner_img = image_tag(
|
20
|
+
email_image_data_url(Searls::Auth.config.email_banner_image_path),
|
21
|
+
style: "height: 48px;"
|
22
|
+
) %>
|
23
|
+
<% if Searls::Auth.config.app_url.present? %>
|
24
|
+
<%= link_to Searls::Auth.config.app_url, style: "text-align: center;" do %>
|
25
|
+
<%= banner_img %>
|
26
|
+
<% end %>
|
27
|
+
<% else %>
|
28
|
+
<div style="text-align: center;">
|
29
|
+
<%= banner_img %>
|
30
|
+
</div>
|
31
|
+
<% end %>
|
32
|
+
<% end %>
|
33
|
+
</td>
|
34
|
+
</tr>
|
35
|
+
<tr>
|
36
|
+
<td align="center">
|
37
|
+
<table width="<%= content_for?(:width) ? yield(:width) : 480 %>" border="0" cellpadding="0" cellspacing="0" style="padding-left: 0.5rem; padding-right: 0.5rem; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); border-radius: 0.75rem; background-color: white; width: 100%; max-width: <%= content_for?(:width) ? yield(:width) : 480 %>px;">
|
38
|
+
<tr>
|
39
|
+
<td>
|
40
|
+
<%= yield %>
|
41
|
+
</td>
|
42
|
+
</tr>
|
43
|
+
</table>
|
44
|
+
</td>
|
45
|
+
</tr>
|
46
|
+
<tr>
|
47
|
+
<td height="32">
|
48
|
+
<%= yield :footer %>
|
49
|
+
</td>
|
50
|
+
</tr>
|
51
|
+
</table>
|
52
|
+
</div>
|
53
|
+
</body>
|
54
|
+
</html>
|
@@ -0,0 +1,37 @@
|
|
1
|
+
<%= content_for :width, 480 %>
|
2
|
+
<p>
|
3
|
+
<% if (user_name = searls_auth_helper.attr_for(@user, @config.user_name_field)).present? %>
|
4
|
+
Hello, <strong><%= user_name %></strong>!
|
5
|
+
<% else %>
|
6
|
+
Hello!
|
7
|
+
<% end %>
|
8
|
+
</p>
|
9
|
+
<p style="margin-top: 1rem;">
|
10
|
+
You can click the button below to log in to your account:
|
11
|
+
<div style="margin-top: 2rem; text-align: center;">
|
12
|
+
<%= link_to verify_token_url({
|
13
|
+
token: @token,
|
14
|
+
redirect_path: @redirect_path,
|
15
|
+
redirect_subdomain: @redirect_subdomain
|
16
|
+
}.compact_blank) do %>
|
17
|
+
<span style="width: 70%; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); display: inline-block; padding-left: 2rem; padding-right: 2rem; padding-top: 1rem; padding-bottom: 1rem; font-size: 1.5rem; font-weight: 700; border-radius: 0.75rem; background-color: <%= @config.email_button_color %>; color: white;">Log in</span>
|
18
|
+
<% end %>
|
19
|
+
<div style="margin-top: 0.5rem; font-size: 0.875rem; color: #6b7280;">
|
20
|
+
(This link will expire in <%= @config.token_expiry_minutes %> minutes.)
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
</p>
|
24
|
+
<p style="margin-top: 2rem;">
|
25
|
+
Alternatively, you can type or paste the following code into your browser:
|
26
|
+
<div style="margin-top: 2rem; text-align: center;">
|
27
|
+
<span id="one-time-code" style="width: 70%; display: inline-block; padding-left: 2rem; padding-right: 2rem; padding-top: 1rem; padding-bottom: 1rem; font-size: 1.5rem; font-weight: 700; border: 1px solid; border-radius: 0.75rem; background-color: #f3f4f6; border-color: #e5e7eb;">
|
28
|
+
<%= @short_code %>
|
29
|
+
</span>
|
30
|
+
<div style="margin-top: 0.5rem; font-size: 0.875rem; color: #6b7280;">
|
31
|
+
(So will this code.)
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
</p>
|
35
|
+
<p style="margin-top: 2rem; font-size: 0.875rem; color: #6b7280;">
|
36
|
+
p.s. Didn't try to log in? You can safely ignore this email. If this keeps happening, please <%= link_to "let us know", "mailto:#{@config.support_email_address}" %>.
|
37
|
+
</p>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<% if (user_name = searls_auth_helper.attr_for(@user, @config.user_name_field)).present? %>
|
2
|
+
Hi, <%= user_name %>!
|
3
|
+
<% else %>
|
4
|
+
Hello!
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
You can log in to your <%= searls_auth_helper.rpad(@config.app_name) %>account at this URL:
|
8
|
+
|
9
|
+
<%= verify_token_url({
|
10
|
+
token: @token,
|
11
|
+
redirect_path: @redirect_path,
|
12
|
+
redirect_subdomain: @redirect_subdomain
|
13
|
+
}.compact_blank) %>
|
14
|
+
|
15
|
+
Alternatively, you can type or paste the following code into your browser:
|
16
|
+
|
17
|
+
<%= @short_code %>
|
18
|
+
|
19
|
+
Didn't try to log in? You can safely ignore this email.<%= " If this keeps
|
20
|
+
happening, please let us know at #{@config.support_email_address}" if @config.support_email_address.present? %>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<h1>Log In</h1>
|
2
|
+
|
3
|
+
<div style="background: rgba(255,0,0, 0.1)">
|
4
|
+
<%= flash[:error] %>
|
5
|
+
</div>
|
6
|
+
<div style="background: rgba(0,0,255, 0.1)">
|
7
|
+
<%= flash[:notice] %>
|
8
|
+
</div>
|
9
|
+
|
10
|
+
<%= form_with url: login_path, method: :post, data: {controller: searls_auth_helper.login_stimulus_controller} do |f| %>
|
11
|
+
<%= f.hidden_field :redirect_path, value: params[:redirect_path] %>
|
12
|
+
<%= f.hidden_field :redirect_subdomain, value: params[:redirect_subdomain] %>
|
13
|
+
<%= f.email_field :email, required: true, value: params[:email], data: searls_auth_helper.email_field_stimulus_data %>
|
14
|
+
<%= f.submit "Log in" %>
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
<p>
|
18
|
+
Not registered yet? <%= link_to "Sign up", searls_auth_helper.register_path %> instead!
|
19
|
+
</p>
|
20
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<h1>Create your account</h1>
|
2
|
+
|
3
|
+
<div style="background: rgba(255,0,0, 0.1)">
|
4
|
+
<%= flash[:error] %>
|
5
|
+
</div>
|
6
|
+
<div style="background: rgba(0,0,255, 0.1)">
|
7
|
+
<%= flash[:notice] %>
|
8
|
+
</div>
|
9
|
+
|
10
|
+
<%= form_with url: register_path, data: {controller: searls_auth_helper.login_stimulus_controller} do |f| %>
|
11
|
+
<%= f.hidden_field :redirect_path, value: params[:redirect_path] %>
|
12
|
+
<%= f.hidden_field :redirect_subdomain, value: params[:redirect_subdomain] %>
|
13
|
+
<%= f.email_field :email, value: params[:email], required: true, data: searls_auth_helper.email_field_stimulus_data %>
|
14
|
+
<%= f.submit "Register" %>
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
<p>
|
18
|
+
Already have an account? <%= link_to "Log in", searls_auth_helper.login_path %> instead.
|
19
|
+
</p>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<h1>Check your email!</h1>
|
2
|
+
<p>
|
3
|
+
In the next few moments, you should receive an email that will provide you
|
4
|
+
two ways to log in: a link and a six-digit code that you can enter below.
|
5
|
+
</p>
|
6
|
+
<%= form_with(url: verify_path, method: :post, data: {
|
7
|
+
# Don't use turbo on cross-domain redirects
|
8
|
+
turbo: searls_auth_helper.enable_turbo?
|
9
|
+
}) do |f| %>
|
10
|
+
<%= f.hidden_field :redirect_path, value: params[:redirect_path] %>
|
11
|
+
<%= f.hidden_field :redirect_subdomain, value: params[:redirect_subdomain] %>
|
12
|
+
<div data-controller="<%= searls_auth_helper.otp_stimulus_controller %>">
|
13
|
+
<%= f.text_field :short_code,
|
14
|
+
maxlength: 6,
|
15
|
+
inputmode: "numeric",
|
16
|
+
pattern: "\\d{6}",
|
17
|
+
autocomplete: "one-time-code",
|
18
|
+
title: "six-digit code that was emailed to you",
|
19
|
+
data: searls_auth_helper.otp_field_stimulus_data
|
20
|
+
%>
|
21
|
+
</div>
|
22
|
+
<%= f.submit "Log in" %>
|
23
|
+
<% end %>
|
data/config/importmap.rb
ADDED
data/config/routes.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Searls::Auth::Engine.routes.draw do
|
2
|
+
get "register", to: "registrations#show"
|
3
|
+
post "register", to: "registrations#create"
|
4
|
+
|
5
|
+
get "login", to: "logins#show"
|
6
|
+
post "login", to: "logins#create"
|
7
|
+
get "logout", to: "logins#destroy"
|
8
|
+
|
9
|
+
get "login/verify", to: "verifications#show", as: :verify
|
10
|
+
post "login/verify", to: "verifications#create"
|
11
|
+
get "login/verify_token", to: "verifications#create", as: :verify_token
|
12
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Searls
|
2
|
+
module Auth
|
3
|
+
class AuthenticatesUser
|
4
|
+
Result = Struct.new(:success?, :user, keyword_init: true)
|
5
|
+
|
6
|
+
def authenticate_by_short_code(short_code, session)
|
7
|
+
user = Searls::Auth.config.user_finder_by_id.call(session[:email_auth_short_code_user_id])
|
8
|
+
|
9
|
+
if session[:email_auth_short_code_generated_at].present? &&
|
10
|
+
Time.zone.parse(session[:email_auth_short_code_generated_at]) > Searls::Auth.config.token_expiry_minutes.minutes.ago &&
|
11
|
+
user.present? &&
|
12
|
+
short_code == session[:email_auth_short_code]
|
13
|
+
Searls::Auth.config.after_login_success&.call(user)
|
14
|
+
Result.new(success?: true, user: user)
|
15
|
+
else
|
16
|
+
Result.new(success?: false)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def authenticate_by_token(token)
|
21
|
+
user = Searls::Auth.config.user_finder_by_token.call(token)
|
22
|
+
|
23
|
+
if user.present?
|
24
|
+
Searls::Auth.config.after_login_success&.call(user)
|
25
|
+
Result.new(success?: true, user: user)
|
26
|
+
else
|
27
|
+
Result.new(success?: false)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Searls
|
2
|
+
module Auth
|
3
|
+
Config = Struct.new(
|
4
|
+
# Data setup
|
5
|
+
:user_finder_by_email, # proc (email)
|
6
|
+
:user_finder_by_id, # proc (id)
|
7
|
+
:user_finder_by_token, # proc (token)
|
8
|
+
:user_initializer, # proc (params)
|
9
|
+
:user_name_field, # string
|
10
|
+
:token_generator, # proc ()
|
11
|
+
:token_expiry_minutes, # integer
|
12
|
+
# Controller setup
|
13
|
+
:preserve_session_keys_after_logout, # array of symbols
|
14
|
+
# View setup
|
15
|
+
:layout, # string
|
16
|
+
:login_view, # string
|
17
|
+
:register_view, # string
|
18
|
+
:verify_view, # string
|
19
|
+
:mail_layout, # string
|
20
|
+
:mail_login_template_path, # string
|
21
|
+
:mail_login_template_name, # string
|
22
|
+
# Routing setup
|
23
|
+
:redirect_path_after_register, # string or proc, all new registrations redirect here
|
24
|
+
:default_redirect_path_after_login, # string or proc, only redirected here if redirect_path param not set
|
25
|
+
# Hook setup
|
26
|
+
:validate_registration, # proc (user, params, errors = []), must return an array of error messages where empty means valid
|
27
|
+
:after_login_success, # proc (user)
|
28
|
+
# Branding setup
|
29
|
+
:app_name, # string
|
30
|
+
:app_url, # string
|
31
|
+
:support_email_address, # string
|
32
|
+
:email_banner_image_path, # string
|
33
|
+
:email_background_color, # string
|
34
|
+
:email_button_color, # string
|
35
|
+
keyword_init: true
|
36
|
+
) do
|
37
|
+
# Get values from values that might be procs
|
38
|
+
def resolve(option, *args)
|
39
|
+
if self[option].respond_to?(:call)
|
40
|
+
self[option].call(*args)
|
41
|
+
else
|
42
|
+
self[option]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|