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 +4 -4
- data/app/controllers/auth_master/application_controller.rb +13 -1
- data/app/controllers/auth_master/sessions_controller.rb +49 -3
- data/app/controllers/concerns/auth_master/current_concern.rb +22 -0
- data/app/lib/auth_master/config.rb +42 -0
- data/app/models/auth_master/session.rb +1 -1
- data/app/operations/auth_master/abstract_operation.rb +7 -0
- data/app/operations/auth_master/check_link_operation.rb +21 -0
- data/app/operations/auth_master/logout_operation.rb +10 -0
- data/app/operations/auth_master/send_link_operation.rb +21 -4
- data/app/services/auth_master/session_service.rb +15 -33
- data/app/views/auth_master/sessions/link.html.erb +6 -0
- data/app/views/auth_master/sessions/new.html.erb +7 -1
- data/config/routes.rb +7 -9
- data/lib/auth_master/version.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af811daf24f09f98a0a9c93aa4eba5f7fbebafe0a08f1fc68f3ce11491de2c14
|
4
|
+
data.tar.gz: 90418adf94d7341733371256330cb504c956aac0b358d4b1b323a5a220220056
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
13
|
-
|
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
|
@@ -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
|
-
|
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
|
-
|
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
|
16
|
-
|
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
|
-
|
19
|
-
|
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:
|
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
|
@@ -1 +1,7 @@
|
|
1
|
-
<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
|
3
|
-
post
|
4
|
-
|
5
|
-
get
|
6
|
-
|
7
|
-
get
|
8
|
-
|
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
|
data/lib/auth_master/version.rb
CHANGED
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.
|
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-
|
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
|