auth_master 0.0.3 → 0.0.5

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: 1363f58683049a2b3b1c729e7dd49cd6e3dd660308da0edc5bdb6959a56ac4cd
4
- data.tar.gz: 2d54954c9b106e59a0693503a524affcf547c270c73764661b66decdc2f9682d
3
+ metadata.gz: af811daf24f09f98a0a9c93aa4eba5f7fbebafe0a08f1fc68f3ce11491de2c14
4
+ data.tar.gz: 90418adf94d7341733371256330cb504c956aac0b358d4b1b323a5a220220056
5
5
  SHA512:
6
- metadata.gz: d801bac756973c1c96a44ba403c8e2c163e4b73805bb59b7e5ab8d2c8ebc9294035737c70a035a19fdc8f577f6d5457e25686e7f9c9600f78f62b290a5de3f9e
7
- data.tar.gz: 55e9962df8c4d9279296d37f654109f2a2fc339b46769f25002529247e2d5a1f94009fe508b8f28abbb8e0bab5eb485ba8b274f7b3c3a939441b99d9ac118216
6
+ metadata.gz: 4f5fe2fc28562f1128a3f41e9a783b6b7983dac6753215d69629d3faea0ebf5148bde172f8afbe634a188825cd3422584179cf1b903f09ed74618f6224dbfa9b
7
+ data.tar.gz: fae32da2226266542bb294e57f925b86ab5d29a50073ee23fa6d394b91066b5cb606bb1d357b7e260ea93c94039e381fff8e50589d0f44e139e771c78be70e0d
@@ -1,5 +1,9 @@
1
1
  module AuthMaster
2
2
  class ApplicationController < ActionController::Base
3
+ # NOTE: Ability to use main app url helpers from main app's layout
4
+ helper Rails.application.routes.url_helpers
5
+ helper_method :target_route
6
+
3
7
  def target_scoped_class
4
8
  target_scope = config_for(:scope)
5
9
  target_scope.present? ? target_accessor.send(target_scope) : target_accessor
@@ -20,12 +24,20 @@ module AuthMaster
20
24
  params[:target].to_sym
21
25
  end
22
26
 
27
+ def target_param_name
28
+ params[:target].to_sym
29
+ end
30
+
31
+ def target_route
32
+ config_for(:route) || :auth_master
33
+ end
34
+
23
35
  def config_for(name)
24
36
  AuthMaster.targets[target_param][name.to_sym]
25
37
  end
26
38
 
27
39
  def check_target_configuration
28
- raise ActionController::RoutingError.new("Not Found") if AuthMaster.targets[target_param].blank?
40
+ raise ActionController::RoutingError.new("Not Found Target Config") if AuthMaster.targets[target_param].blank?
29
41
  end
30
42
  end
31
43
  end
@@ -3,14 +3,21 @@ module AuthMaster
3
3
  TIMING_ATTACK_INTERVAL = 1
4
4
 
5
5
  before_action :check_target_configuration
6
- around_action :prevent_timing_attack, only: :send_link
6
+
7
+ before_action :check_token_presence, only: :link
8
+ before_action :check_pre_session_id, only: :link
9
+
10
+ around_action :prevent_timing_attack, only: :create
7
11
 
8
12
  # NOTE: Show input email form
9
13
  def new
10
14
  end
11
15
 
12
- def send_link
13
- AuthMaster::SendLinkOperation.call!(params[:email], target_scoped_class:)
16
+ def create
17
+ uuid = Random.uuid
18
+ session[session_key] = uuid
19
+
20
+ AuthMaster::SendLinkOperation.call!(params[:email], target_scoped_class:, uuid:)
14
21
  redirect_to auth_master_sent_url(target: target_param)
15
22
  end
16
23
 
@@ -20,8 +27,35 @@ module AuthMaster
20
27
  def link
21
28
  end
22
29
 
30
+ def activate
31
+ uuid = session[session_key]
32
+ auth_master_session = AuthMaster::CheckLinkOperation.call!(params[:token], uuid:, target_param_name:)
33
+ (redirect_to(auth_master_denied_path(target: target_param_name)) and return) if auth_master_session.blank?
34
+
35
+ session.delete(session_key)
36
+ session[target_session_key] = auth_master_session.id
37
+
38
+ # TODO: Use config for
39
+ # a) session key;
40
+ # b) default redirect path
41
+ saved_path = session.delete("redirect_to")
42
+ redirect_to(saved_path || "/")
43
+ end
44
+
45
+ def destroy
46
+ auth_master_session_id = session.delete(target_session_key)
47
+ AuthMaster::LogoutOperation.call!(auth_master_session_id)
48
+
49
+ # TODO: Use config for redirect_path
50
+ redirect_to("/")
51
+ end
52
+
23
53
  private
24
54
 
55
+ def session_key
56
+ [ "auth_master", params[:target].to_s, "id" ].join("_")
57
+ end
58
+
25
59
  def prevent_timing_attack
26
60
  start_time = Time.current
27
61
  yield
@@ -36,5 +70,17 @@ module AuthMaster
36
70
  def timing_attack_interval
37
71
  AuthMaster.timing_attack_interval.presence || TIMING_ATTACK_INTERVAL
38
72
  end
73
+
74
+ def check_token_presence
75
+ raise ActionController::RoutingError.new("Not Found Token") if params[:token].blank?
76
+ end
77
+
78
+ def check_pre_session_id
79
+ raise ActionController::RoutingError.new("Not Found Session") if session[session_key].blank?
80
+ end
81
+
82
+ def target_session_key
83
+ [ "current", target_param_name, "id" ].join("_")
84
+ end
39
85
  end
40
86
  end
@@ -0,0 +1,22 @@
1
+ module AuthMaster::CurrentConcern
2
+ extend ActiveSupport::Concern
3
+
4
+ # included do
5
+ # helper_method :current_auth_master
6
+ # end
7
+
8
+ def current_auth_master(target_param_name)
9
+ session_accessor_key = [ "current", target_param_name, "id" ].join("_")
10
+ auth_master_session_id = session[session_accessor_key]
11
+ return nil if auth_master_session_id.blank?
12
+
13
+ auth_master_session = AuthMaster::Session.active.find_by(id: auth_master_session_id)
14
+ return nil if auth_master_session.blank?
15
+
16
+ target = auth_master_session.target
17
+ return nil if target.blank?
18
+ # return nil if !target.is_a?(target_class_by_name(target_param_name))
19
+
20
+ target
21
+ end
22
+ end
@@ -0,0 +1,42 @@
1
+ module AuthMaster
2
+ module Config
3
+ DEFAULT_LOGIN_TIMEOUT_INTERVAL = 5.minutes
4
+ DEFAULT_LOGIN_ATTEMPTS_COUNT = 3
5
+ DEFAULT_TOKEN_PURPOSE = :auth_master_email
6
+
7
+ def login_timeout_interval_config(target)
8
+ config_for(target, :login_timeout_interval) || DEFAULT_LOGIN_TIMEOUT_INTERVAL
9
+ end
10
+
11
+ def login_attempts_count_config(target)
12
+ config_for(target, :login_attempts_count) || DEFAULT_LOGIN_ATTEMPTS_COUNT
13
+ end
14
+
15
+ def token_purpose_config(target)
16
+ config_for(target, :token_purpose) || DEFAULT_TOKEN_PURPOSE
17
+ end
18
+
19
+ def secret_config(target)
20
+ config_for(target, :secret)
21
+ end
22
+
23
+ def target_mailer_config(target)
24
+ config_for(target, :mailer_class).to_s.classify.constantize
25
+ end
26
+
27
+ def target_mailer_login_link_method(target)
28
+ config_for(target, :mailer_login_link_method)
29
+ end
30
+
31
+ def target_name(target)
32
+ return target if target.is_a? Symbol
33
+ return target.to_sym if target.is_a? String
34
+
35
+ target.class.to_s.underscore.to_sym
36
+ end
37
+
38
+ def config_for(target, name)
39
+ AuthMaster.targets[target_name(target)][name.to_sym]
40
+ end
41
+ end
42
+ end
@@ -2,6 +2,6 @@ module AuthMaster
2
2
  class Session < ApplicationRecord
3
3
  belongs_to :target, polymorphic: true
4
4
 
5
- enum :status, [ :inactive, :active ], default: :inactive
5
+ enum :status, [ :inactive, :active, :logout ], default: :inactive
6
6
  end
7
7
  end
@@ -0,0 +1,7 @@
1
+ require "token_guard"
2
+
3
+ module AuthMaster
4
+ class AbstractOperation
5
+ extend AuthMaster::Config
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ module AuthMaster
2
+ class CheckLinkOperation < AuthMaster::AbstractOperation
3
+ def self.call!(encrypted_token, uuid:, target_param_name:)
4
+ purpose = token_purpose_config(target_param_name)
5
+ secret = secret_config(target_param_name)
6
+
7
+ auth_master_session_id = TokenGuard.decrypt(encrypted_token, purpose:, secret:)
8
+ return if auth_master_session_id.blank?
9
+
10
+ # NOTE: Auth from the same device
11
+ return if auth_master_session_id != uuid
12
+
13
+ auth_master_session = AuthMaster::SessionService.inactive_find(auth_master_session_id)
14
+ return if auth_master_session.blank?
15
+
16
+ AuthMaster::SessionService.activate!(auth_master_session)
17
+
18
+ auth_master_session
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ module AuthMaster
2
+ class LogoutOperation < AuthMaster::AbstractOperation
3
+ def self.call!(auth_master_session_id)
4
+ auth_master_session = AuthMaster::Session.active.find_by(id: auth_master_session_id)
5
+ return if auth_master_session.blank?
6
+
7
+ AuthMaster::SessionService.logout!(auth_master_session)
8
+ end
9
+ end
10
+ end
@@ -1,11 +1,28 @@
1
1
  module AuthMaster
2
- class SendLinkOperation
3
- def self.call!(email, target_scoped_class:)
2
+ class SendLinkOperation < AuthMaster::AbstractOperation
3
+ def self.call!(email, target_scoped_class:, uuid:)
4
4
  target = target_scoped_class.find_by(email:)
5
5
  return if target.blank?
6
6
 
7
- auth_master_session = AuthMaster::SessionService.create!(target)
8
- AuthMaster::SessionService.send_link!(auth_master_session) if auth_master_session.present?
7
+ auth_master_session = AuthMaster::SessionService.create!(target, uuid:)
8
+ return if auth_master_session.blank?
9
+
10
+ purpose = token_purpose_config(target)
11
+ secret = secret_config(target)
12
+ token = TokenGuard.encrypt(auth_master_session.id, purpose:, secret:)
13
+
14
+ mailer = target_mailer_config(target)
15
+ mailer_action = target_mailer_login_link_method(target)
16
+
17
+ url = AuthMaster::Engine.routes.url_helpers.auth_master_link_url(
18
+ target: target_name(target),
19
+ token: token,
20
+ host: Rails.application.config.action_mailer.default_url_options[:host]
21
+ )
22
+
23
+ mailer.with(email: target.email, url:).public_send(mailer_action).deliver_later
24
+
25
+ # auth_master_session
9
26
  end
10
27
  end
11
28
  end
@@ -2,21 +2,27 @@ require "token_guard"
2
2
 
3
3
  module AuthMaster
4
4
  class SessionService
5
- LOGIN_TIMEOUT_INTERVAL = 5.minutes
6
- LOGIN_ATTEMPTS_COUNT = 3
5
+ extend AuthMaster::Config
7
6
 
8
7
  class << self
9
- def create!(target)
8
+ def create!(target, uuid:)
10
9
  return if !allow_creation?(target)
11
10
 
12
- AuthMaster::Session.create!(target:)
11
+ AuthMaster::Session.create!(target:, id: uuid)
13
12
  end
14
13
 
15
- def send_link!(auth_master_session)
16
- target = auth_master_session.target
14
+ def inactive_find(id)
15
+ AuthMaster::Session.inactive.find_by(id:)
16
+ end
17
+
18
+ def activate!(auth_master_session)
19
+ # TODO: Save IP Address, User Agent, etc
20
+ auth_master_session.active!
21
+ end
17
22
 
18
- token = TokenGuard.encrypt(auth_master_session.id, purpose: :email, secret: AuthMaster.targets[target_name(target)][:secret])
19
- target_mailer(target).with(email: target.email, token:).send(target_mailer_login_link_method(target)).deliver_later
23
+ def logout!(auth_master_session)
24
+ # TODO: Save IP Address, User Agent, etc
25
+ auth_master_session.logout!
20
26
  end
21
27
 
22
28
  private
@@ -26,31 +32,7 @@ module AuthMaster
26
32
  end
27
33
 
28
34
  def allow_creation?(target)
29
- count(target, time: login_timeout_interval(target)) < login_attempts_count(target)
30
- end
31
-
32
- def login_timeout_interval(target)
33
- AuthMaster.targets[target_name(target)][:login_timeout_interval] || LOGIN_TIMEOUT_INTERVAL
34
- end
35
-
36
- def login_attempts_count(target)
37
- AuthMaster.targets[target_name(target)][:login_attempts_count] || LOGIN_ATTEMPTS_COUNT
38
- end
39
-
40
- def target_name(target)
41
- target.class.to_s.downcase.to_sym
42
- end
43
-
44
- def target_mailer(target)
45
- config_for(target, :mailer_class).to_s.classify.constantize
46
- end
47
-
48
- def target_mailer_login_link_method(target)
49
- config_for(target, :mailer_login_link_method)
50
- end
51
-
52
- def config_for(target, name)
53
- AuthMaster.targets[target_name(target)][name.to_sym]
35
+ count(target, time: login_timeout_interval_config(target)) < login_attempts_count_config(target)
54
36
  end
55
37
  end
56
38
  end
@@ -0,0 +1,6 @@
1
+ <h1>Login:</h1>
2
+
3
+ <%= form_with url: send(target_route).auth_master_link_path(target: params[:target]), method: :post do |form| %>
4
+ <%= form.hidden_field :token, value: params[:token] %>
5
+ <%= form.submit "Login" %>
6
+ <% end %>
@@ -1 +1,7 @@
1
- <h1>Sessions#new</h1>
1
+ <h1>Login:</h1>
2
+
3
+ <%= form_with url: send(target_route).auth_master_login_path(target: params[:target]), method: :post do |form| %>
4
+ <%= form.label :email, "Email:" %>
5
+ <%= form.email_field :email %>
6
+ <%= form.submit "Login" %>
7
+ <% end %>
data/config/routes.rb CHANGED
@@ -1,11 +1,9 @@
1
1
  AuthMaster::Engine.routes.draw do
2
- get "/:target/login", to: "sessions#new", as: :auth_master_login
3
- post "/:target/login", to: "sessions#send_link"
4
-
5
- get "/:target/sent", to: "sessions#sent", as: :auth_master_sent
6
-
7
- get "/:target/link", to: "sessions#link", as: :auth_master_link
8
- post "/:target/link", to: "sessions#create"
9
-
10
- get "/:target/denied", to: "sessions#denied", as: :auth_master_denied
2
+ get "/:target/login", to: "sessions#new", as: :auth_master_login
3
+ post "/:target/login", to: "sessions#create"
4
+ get "/:target/sent", to: "sessions#sent", as: :auth_master_sent
5
+ get "/:target/link", to: "sessions#link", as: :auth_master_link
6
+ post "/:target/link", to: "sessions#activate"
7
+ get "/:target/denied", to: "sessions#denied", as: :auth_master_denied
8
+ delete "/:target/logout", to: "sessions#destroy", as: :auth_master_logout
11
9
  end
@@ -1,3 +1,3 @@
1
1
  module AuthMaster
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.5"
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: auth_master
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - vickodin
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-27 00:00:00.000000000 Z
10
+ date: 2025-03-30 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -56,12 +56,18 @@ files:
56
56
  - app/assets/stylesheets/auth_master/application.css
57
57
  - app/controllers/auth_master/application_controller.rb
58
58
  - app/controllers/auth_master/sessions_controller.rb
59
+ - app/controllers/concerns/auth_master/current_concern.rb
59
60
  - app/helpers/auth_master/application_helper.rb
60
61
  - app/helpers/auth_master/sessions_helper.rb
62
+ - app/lib/auth_master/config.rb
61
63
  - app/models/auth_master/application_record.rb
62
64
  - app/models/auth_master/session.rb
65
+ - app/operations/auth_master/abstract_operation.rb
66
+ - app/operations/auth_master/check_link_operation.rb
67
+ - app/operations/auth_master/logout_operation.rb
63
68
  - app/operations/auth_master/send_link_operation.rb
64
69
  - app/services/auth_master/session_service.rb
70
+ - app/views/auth_master/sessions/link.html.erb
65
71
  - app/views/auth_master/sessions/new.html.erb
66
72
  - app/views/auth_master/sessions/sent.html.erb
67
73
  - config/routes.rb