searls-auth 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b25e2025b4ec1c7eb16c573e4006a079b9073e9837769f1038b62e7b8fc3bfd
4
- data.tar.gz: b09296feab4da00d73216df4a2f5a23b7dd1a97e1a7fd93f824c20acf3fb0379
3
+ metadata.gz: 5e2b6983e76f48e3663946462d67f0dfdf62914aa8187c29a332111f430335f2
4
+ data.tar.gz: 0cfbab5b379a053ff0f23406e25c045caa6d7ca2c249330d0ac24b459e6f1ecf
5
5
  SHA512:
6
- metadata.gz: ac9b6013cec969279330d49c5967de49a79b6c2e5fcab7a8189e5f0e6f9d3aa54b57fc487b59b789f7a03169412c8339ce57e9d4d7314420f8081a0cb5d75be1
7
- data.tar.gz: ff8e2755bac9adc5a6e836b20908a57e5f1a8318aeef1316a30805aef252bc3a103b52729abc87955c5bb62a7cbcfdee843e8308d9406f5e9fa82a89d773f7c5
6
+ metadata.gz: 820ae8d2258753dd6e7f2f9bea7054537f0242aacc5218f220cc3a83d639a11a5204bcca728a95ec06e4ae8094994609abf0859f5df8162684a56c42bd27757b
7
+ data.tar.gz: 9d6f9469c3c4d99cd42f0b410f89934951924c605cff3fe9bf7490064d790d1a814b20e154574de45d525b325acaac6ccd793496f2ffbc83645042e4f2a0268f
data/.standard.yml CHANGED
@@ -1,3 +1,5 @@
1
1
  # For available configuration options, see:
2
2
  # https://github.com/standardrb/standard
3
3
  ruby_version: 3.4
4
+ ignore:
5
+ - '**/vendor/**/*'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.0] - 2025-04-26
4
+
5
+ * Add `max_allowed_short_code_attempts` configuration, beyond which the code is erased from the session and the user needs to login again (default: 10)
6
+ * Allow configuration of flash messages
7
+ * Fix a routing error if the user is already registered
8
+
9
+ ## [0.0.2] - 2025-04-16
10
+
11
+ * Little fixes. Renamed `user_name_field` to `user_name_method`
12
+
3
13
  ## [0.0.1] - 2025-04-15
4
14
 
5
- - Initial release
15
+ * Initial release
@@ -13,22 +13,29 @@ module Searls
13
13
  end
14
14
 
15
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
16
+ session[:searls_auth_short_code_user_id] = user.id
17
+ session[:searls_auth_short_code] = SecureRandom.random_number(1000000).to_s.rjust(6, "0")
18
+ session[:searls_auth_short_code_generated_at] = Time.current
19
+ session[:searls_auth_short_code_verification_attempts] = 0
19
20
  end
20
21
 
21
22
  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
23
+ if session[:searls_auth_short_code_generated_at].present? &&
24
+ Time.zone.parse(session[:searls_auth_short_code_generated_at]) < Searls::Auth.config.token_expiry_minutes.minutes.ago
24
25
  clear_short_code_from_session!
25
26
  end
26
27
  end
27
28
 
28
29
  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)
30
+ session.delete(:searls_auth_short_code_user_id)
31
+ session.delete(:searls_auth_short_code_generated_at)
32
+ session.delete(:searls_auth_short_code)
33
+ session.delete(:searls_auth_short_code_verification_attempts)
34
+ end
35
+
36
+ def log_short_code_verification_attempt!
37
+ session[:searls_auth_short_code_verification_attempts] ||= 0
38
+ session[:searls_auth_short_code_verification_attempts] += 1
32
39
  end
33
40
  end
34
41
  end
@@ -16,19 +16,26 @@ module Searls
16
16
  user:,
17
17
  redirect_path: params[:redirect_path],
18
18
  redirect_subdomain: params[:redirect_subdomain],
19
- short_code: session[:email_auth_short_code]
19
+ short_code: session[:searls_auth_short_code]
20
20
  )
21
- flash[:notice] = "Login details sent to #{params[:email]}"
22
- redirect_to verify_path(
21
+ flash[:notice] = searls_auth_config.resolve(
22
+ :flash_notice_after_login_attempt,
23
+ user, params
24
+ )
25
+ redirect_to searls_auth.verify_path(
23
26
  redirect_path: params[:redirect_path],
24
27
  redirect_subdomain: params[:redirect_subdomain]
25
28
  )
26
29
  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
30
+ flash.now[:error] = searls_auth_config.resolve(
31
+ :flash_error_after_login_attempt_unknown_email,
32
+ searls_auth.register_path(
33
+ email: params[:email],
34
+ redirect_path: params[:redirect_path],
35
+ redirect_subdomain: params[:redirect_subdomain]
36
+ ),
37
+ params
38
+ )
32
39
  render searls_auth_config.login_view, layout: searls_auth_config.layout, status: :unprocessable_entity
33
40
  end
34
41
  end
@@ -36,8 +43,11 @@ module Searls
36
43
  def destroy
37
44
  ResetsSession.new.reset(self, except_for: [:has_logged_in_before])
38
45
 
39
- flash[:notice] = "You've been logged out."
40
- redirect_to login_path
46
+ flash[:notice] = searls_auth_config.resolve(
47
+ :flash_notice_after_logout,
48
+ params
49
+ )
50
+ redirect_to searls_auth.login_path
41
51
  end
42
52
  end
43
53
  end
@@ -20,13 +20,26 @@ module Searls
20
20
 
21
21
  EmailsLink.new.email(
22
22
  user: result.user,
23
- short_code: session[:email_auth_short_code],
23
+ short_code: session[:searls_auth_short_code],
24
24
  **redirect_params
25
25
  )
26
- flash[:notice] = "Verification email sent to #{params[:email]}"
27
- redirect_to verify_path(**redirect_params)
26
+ flash[:notice] = searls_auth_config.resolve(
27
+ :flash_notice_after_registration,
28
+ result.user, params
29
+ )
30
+
31
+ redirect_to searls_auth.verify_path(**redirect_params)
28
32
  else
29
- flash.now[:error] = result.error_messages
33
+ flash.now[:error] = searls_auth_config.resolve(
34
+ :flash_error_after_register_attempt,
35
+ result.error_messages,
36
+ searls_auth.login_path(
37
+ email: params[:email],
38
+ redirect_path: params[:redirect_path],
39
+ redirect_subdomain: params[:redirect_subdomain]
40
+ ),
41
+ params
42
+ )
30
43
  render searls_auth_config.register_view, layout: searls_auth_config.layout, status: :unprocessable_entity
31
44
  end
32
45
  end
@@ -12,6 +12,7 @@ module Searls
12
12
  authenticator = AuthenticatesUser.new
13
13
  result = case auth_method
14
14
  when :short_code
15
+ log_short_code_verification_attempt!
15
16
  authenticator.authenticate_by_short_code(params[:short_code], session)
16
17
  when :token
17
18
  authenticator.authenticate_by_token(params[:token])
@@ -20,6 +21,10 @@ module Searls
20
21
  if result.success?
21
22
  session[:user_id] = result.user.id
22
23
  session[:has_logged_in_before] = true
24
+ flash[:notice] = searls_auth_config.resolve(
25
+ :flash_notice_after_verification,
26
+ result.user, params
27
+ )
23
28
  if params[:redirect_subdomain].present? && params[:redirect_subdomain] != request.subdomain
24
29
  redirect_to generate_full_url(
25
30
  params[:redirect_path],
@@ -32,25 +37,35 @@ module Searls
32
37
  result.user, params, request, main_app)
33
38
  end
34
39
  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
40
+ if result.exceeded_short_code_attempt_limit?
41
+ clear_short_code_from_session!
42
+ flash[:error] = searls_auth_config.resolve(
43
+ :flash_error_after_verify_attempt_exceeds_limit,
44
+ params
45
+ )
46
+ redirect_to searls_auth.login_path(
47
+ redirect_path: params[:redirect_path],
48
+ redirect_subdomain: params[:redirect_subdomain]
49
+ )
50
+ else
51
+ flash[:error] = searls_auth_config.resolve(
52
+ :flash_error_after_verify_attempt_incorrect_short_code,
53
+ params
54
+ )
55
+ render searls_auth_config.verify_view, layout: searls_auth_config.layout, status: :unprocessable_entity
56
+ end
37
57
  else
38
- flash[:error] = "We weren't able to log you in with that link. Try again?"
39
- redirect_to login_path(
58
+ flash[:error] = searls_auth_config.resolve(
59
+ :flash_error_after_verify_attempt_invalid_link,
60
+ params
61
+ )
62
+ redirect_to searls_auth.login_path(
40
63
  redirect_path: params[:redirect_path],
41
64
  redirect_subdomain: params[:redirect_subdomain]
42
65
  )
43
66
  end
44
67
  end
45
68
 
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
69
  private
55
70
 
56
71
  def generate_full_url(path, subdomain)
@@ -51,10 +51,13 @@ module Searls
51
51
  end
52
52
 
53
53
  def attr_for(model, field_name)
54
- model.attributes[field_name.to_s]
54
+ if model.respond_to?(field_name)
55
+ model.send(field_name)
56
+ end
55
57
  end
56
58
 
57
59
  def rpad(s, spacer = " ", times = 1)
60
+ return "" if s.blank?
58
61
  "#{s}#{spacer * times}"
59
62
  end
60
63
 
@@ -23,7 +23,6 @@ export default class SearlsAuthLoginController extends Controller {
23
23
  }
24
24
 
25
25
  updateEmail (e) {
26
- debugger
27
26
  this.emailValue = e.currentTarget.value
28
27
  window.sessionStorage.setItem('__searls_auth_email', this.emailValue)
29
28
  }
@@ -3,6 +3,17 @@ module Searls
3
3
  class BaseMailer < ApplicationMailer # TODO should this be ActionMailer::Base? Trade-offs?
4
4
  helper Searls::Auth::ApplicationHelper
5
5
  include Searls::Auth::ApplicationHelper
6
+
7
+ protected
8
+
9
+ def format_to(user)
10
+ name_field = searls_auth_helper.attr_for(user, Searls::Auth.config.user_name_method)
11
+ if name_field.present?
12
+ "#{user.name} <#{user.email}>"
13
+ else
14
+ user.email
15
+ end
16
+ end
6
17
  end
7
18
  end
8
19
  end
@@ -11,7 +11,7 @@ module Searls
11
11
 
12
12
  mail(
13
13
  to: format_to(@user),
14
- subject: "Your #{searls_auth_helper.rpad(@config.app_name)} login code is #{@short_code}",
14
+ subject: "Your #{searls_auth_helper.rpad(@config.app_name)}login code is #{@short_code}",
15
15
  template_path: @config.mail_login_template_path,
16
16
  template_name: @config.mail_login_template_name
17
17
  ) do |format|
@@ -1,6 +1,6 @@
1
1
  <%= content_for :width, 480 %>
2
2
  <p>
3
- <% if (user_name = searls_auth_helper.attr_for(@user, @config.user_name_field)).present? %>
3
+ <% if (user_name = searls_auth_helper.attr_for(@user, @config.user_name_method)).present? %>
4
4
  Hello, <strong><%= user_name %></strong>!
5
5
  <% else %>
6
6
  Hello!
@@ -1,4 +1,4 @@
1
- <% if (user_name = searls_auth_helper.attr_for(@user, @config.user_name_field)).present? %>
1
+ <% if (user_name = searls_auth_helper.attr_for(@user, @config.user_name_method)).present? %>
2
2
  Hi, <%= user_name %>!
3
3
  <% else %>
4
4
  Hello!
@@ -6,7 +6,7 @@ Hello!
6
6
 
7
7
  You can log in to your <%= searls_auth_helper.rpad(@config.app_name) %>account at this URL:
8
8
 
9
- <%= verify_token_url({
9
+ <%= searls_auth.verify_token_url({
10
10
  token: @token,
11
11
  redirect_path: @redirect_path,
12
12
  redirect_subdomain: @redirect_subdomain
@@ -1,15 +1,9 @@
1
1
  <h1>Log In</h1>
2
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| %>
3
+ <%= form_with url: searls_auth.login_path, method: :post, data: {controller: searls_auth_helper.login_stimulus_controller} do |f| %>
11
4
  <%= f.hidden_field :redirect_path, value: params[:redirect_path] %>
12
5
  <%= f.hidden_field :redirect_subdomain, value: params[:redirect_subdomain] %>
6
+ <%= f.label :email %>
13
7
  <%= f.email_field :email, required: true, value: params[:email], data: searls_auth_helper.email_field_stimulus_data %>
14
8
  <%= f.submit "Log in" %>
15
9
  <% end %>
@@ -1,15 +1,9 @@
1
1
  <h1>Create your account</h1>
2
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| %>
3
+ <%= form_with url: searls_auth.register_path, data: {controller: searls_auth_helper.login_stimulus_controller} do |f| %>
11
4
  <%= f.hidden_field :redirect_path, value: params[:redirect_path] %>
12
5
  <%= f.hidden_field :redirect_subdomain, value: params[:redirect_subdomain] %>
6
+ <%= f.label :email %>
13
7
  <%= f.email_field :email, value: params[:email], required: true, data: searls_auth_helper.email_field_stimulus_data %>
14
8
  <%= f.submit "Register" %>
15
9
  <% end %>
@@ -3,13 +3,14 @@
3
3
  In the next few moments, you should receive an email that will provide you
4
4
  two ways to log in: a link and a six-digit code that you can enter below.
5
5
  </p>
6
- <%= form_with(url: verify_path, method: :post, data: {
6
+ <%= form_with(url: searls_auth.verify_path, method: :post, data: {
7
7
  # Don't use turbo on cross-domain redirects
8
8
  turbo: searls_auth_helper.enable_turbo?
9
9
  }) do |f| %>
10
10
  <%= f.hidden_field :redirect_path, value: params[:redirect_path] %>
11
11
  <%= f.hidden_field :redirect_subdomain, value: params[:redirect_subdomain] %>
12
12
  <div data-controller="<%= searls_auth_helper.otp_stimulus_controller %>">
13
+ <%= f.label :short_code, "Code" %>
13
14
  <%= f.text_field :short_code,
14
15
  maxlength: 6,
15
16
  inputmode: "numeric",
@@ -1,15 +1,17 @@
1
1
  module Searls
2
2
  module Auth
3
3
  class AuthenticatesUser
4
- Result = Struct.new(:success?, :user, keyword_init: true)
4
+ Result = Struct.new(:success?, :user, :exceeded_short_code_attempt_limit?, keyword_init: true)
5
5
 
6
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])
7
+ if session[:searls_auth_short_code_verification_attempts] > Searls::Auth.config.max_allowed_short_code_attempts
8
+ return Result.new(success?: false, exceeded_short_code_attempt_limit?: true)
9
+ end
8
10
 
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]
11
+ if session[:searls_auth_short_code_generated_at].present? &&
12
+ Time.zone.parse(session[:searls_auth_short_code_generated_at]) > Searls::Auth.config.token_expiry_minutes.minutes.ago &&
13
+ short_code == session[:searls_auth_short_code] &&
14
+ (user = Searls::Auth.config.user_finder_by_id.call(session[:searls_auth_short_code_user_id])).present?
13
15
  Searls::Auth.config.after_login_success&.call(user)
14
16
  Result.new(success?: true, user: user)
15
17
  else
@@ -6,11 +6,12 @@ module Searls
6
6
  :user_finder_by_id, # proc (id)
7
7
  :user_finder_by_token, # proc (token)
8
8
  :user_initializer, # proc (params)
9
- :user_name_field, # string
9
+ :user_name_method, # string
10
10
  :token_generator, # proc ()
11
11
  :token_expiry_minutes, # integer
12
12
  # Controller setup
13
13
  :preserve_session_keys_after_logout, # array of symbols
14
+ :max_allowed_short_code_attempts, # integer
14
15
  # View setup
15
16
  :layout, # string
16
17
  :login_view, # string
@@ -32,6 +33,16 @@ module Searls
32
33
  :email_banner_image_path, # string
33
34
  :email_background_color, # string
34
35
  :email_button_color, # string
36
+ # Messages setup
37
+ :flash_notice_after_registration, # string or proc(user, params)
38
+ :flash_error_after_register_attempt, # string or proc(error_messages, login_path, params)
39
+ :flash_notice_after_login_attempt, # string or proc(user, params)
40
+ :flash_error_after_login_attempt_unknown_email, # string or proc(register_path, params)
41
+ :flash_notice_after_logout, # string or proc(params)
42
+ :flash_notice_after_verification, # string or proc(user, params)
43
+ :flash_error_after_verify_attempt_exceeds_limit, # string or proc(params)
44
+ :flash_error_after_verify_attempt_incorrect_short_code, # string or proc(params)
45
+ :flash_error_after_verify_attempt_invalid_link, # string or proc(params)
35
46
  keyword_init: true
36
47
  ) do
37
48
  # Get values from values that might be procs
@@ -1,5 +1,5 @@
1
1
  module Searls
2
2
  module Auth
3
- VERSION = "0.0.1"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
  end
data/lib/searls/auth.rb CHANGED
@@ -16,12 +16,13 @@ module Searls
16
16
  user_finder_by_email: ->(email) { User.find_by(email:) },
17
17
  user_finder_by_id: ->(id) { User.find_by(id:) },
18
18
  user_finder_by_token: ->(token) { User.find_by_token_for(:email_auth, token) },
19
- user_initializer: ->(params) { User.new(params[:email]) },
20
- user_name_field: "name",
19
+ user_initializer: ->(params) { User.new(email: params[:email]) },
20
+ user_name_method: "name",
21
21
  token_generator: ->(user) { user.generate_token_for(:email_auth) },
22
22
  token_expiry_minutes: 30,
23
23
  # Controller setup
24
24
  preserve_session_keys_after_logout: [],
25
+ max_allowed_short_code_attempts: 10,
25
26
  # View setup
26
27
  layout: "application",
27
28
  register_view: "searls/auth/registrations/show",
@@ -48,7 +49,20 @@ module Searls
48
49
  support_email_address: nil,
49
50
  email_background_color: "#d8d7ed",
50
51
  email_button_color: "#c664f3",
51
- email_banner_image_path: nil
52
+ email_banner_image_path: nil,
53
+ # Messages setup
54
+ flash_notice_after_registration: ->(user, params) { "Verification email sent to #{params[:email]}" },
55
+ flash_error_after_register_attempt: ->(error_messages, login_path, params) { error_messages },
56
+ flash_notice_after_login_attempt: ->(user, params) { "Login details sent to #{params[:email]}" },
57
+ flash_error_after_login_attempt_unknown_email: ->(register_path, params) {
58
+ "We don't know that email. <a href=\"#{register_path}\">Sign up</a> instead?".html_safe
59
+ },
60
+ flash_notice_after_logout: "You've been logged out",
61
+ flash_notice_after_verification: "You are now logged in",
62
+ flash_error_after_verify_attempt_exceeds_limit: "Too many verification attempts. Please login again to generate a new code",
63
+ flash_error_after_verify_attempt_incorrect_short_code: "We weren't able to log you in with that code. Try again?",
64
+ flash_error_after_verify_attempt_invalid_link: "We weren't able to log you in with that link. Try again?"
65
+
52
66
  }.freeze
53
67
 
54
68
  CONFIG = Config.new(**DEFAULT_CONFIG)
data/script/setup ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ bundle
6
+
7
+ cd example/simple_app
8
+ bundle
9
+ bin/rake db:setup
10
+ export PLAYWRIGHT_CLI_VERSION=$(bundle exec ruby -e 'require "playwright"; puts Playwright::COMPATIBLE_PLAYWRIGHT_VERSION.strip')
11
+ PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 yarn add -D "playwright@$PLAYWRIGHT_CLI_VERSION"
12
+ yarn run playwright install chromium
13
+
14
+ cd ../..
data/script/setup_ci ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ cd example/simple_app
6
+ bin/rake db:setup
7
+ cd ../..
data/script/test ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ echo "-----> Testing searls-auth gem"
6
+ bundle exec rake
7
+
8
+ echo "-----> Testing example/simple_app"
9
+ cd example/simple_app
10
+ bin/rake
11
+ cd ../..
12
+
13
+ echo "-----> Looks good! 🪩"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searls-auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
@@ -60,6 +60,9 @@ files:
60
60
  - lib/searls/auth/railtie.rb
61
61
  - lib/searls/auth/resets_session.rb
62
62
  - lib/searls/auth/version.rb
63
+ - script/setup
64
+ - script/setup_ci
65
+ - script/test
63
66
  homepage: https://github.com/searls/searls-auth
64
67
  licenses:
65
68
  - GPL-3.0-only