auth_master 0.0.2 → 0.0.4

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: ac57c91a103b1ea33d4f895eb59938c605e09ec4d0e82668fdce9bfbf48ffdb4
4
- data.tar.gz: 2e13917724efec1f13880dbcea2d449cdfece27ce5c5da44dc475af70e00fa6f
3
+ metadata.gz: c266d6e5222227d0f002f1ed663a9420d599d9f3456d082f3de21473f2c6af25
4
+ data.tar.gz: 68ef46005182ad74059391ad7df9f869099ae381b5b7913224f6fcdfe17cbcdd
5
5
  SHA512:
6
- metadata.gz: f75e3ff10432e47d5790167b2a213dc4a458aa93669da69a0491948987d8d2535bed00bc554abb61140923c923986a1a1d730e890a48e4bdf753cf47d9c54727
7
- data.tar.gz: 5f1cf4148fb7ea0c831d914d03122bd5b0e879552a529f1b14df80842f914f61c04e83d82b718247858fbc8f390df1a943e00d107e39c6015fcd38ab95733d72
6
+ metadata.gz: c16626eee3bf0fbbe5b2e6db7beea09a9a27d2785450ef50c4a572bd60b4fd1be9cf313100308b3cd1cc483555fd48fed06e936f4bda5aaf5f1400cf863cc41d
7
+ data.tar.gz: 735d46ca1a99308be75b5891df5203c9b63c1b8459312d63d5de623b77322204526b49958842f6983ae35744f785a803e9e5cd4b8de40a65f30e2ad459bdf898
data/README.md CHANGED
@@ -21,8 +21,27 @@ Or install it yourself as:
21
21
  $ gem install auth_master
22
22
  ```
23
23
 
24
+ Install database migrations:
25
+ ```bash
26
+ $ bin/rails auth_master:install:migrations
27
+ ```
28
+
29
+ Run migrations:
30
+ ```bash
31
+ $ bin/rails db:migrate
32
+ ```
33
+
24
34
  ## Contributing
25
- Contribution directions go here.
35
+
36
+ Build gem:
37
+ ```bash
38
+ $ rake build
39
+ ```
40
+
41
+ Push to RubyGems.org:
42
+ ```bash
43
+ $ gem push auth_master-x.y.z.gem
44
+ ```
26
45
 
27
46
  ## License
28
47
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -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,23 @@ 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
+ redirect_to("/")
39
+ end
40
+
23
41
  private
24
42
 
43
+ def session_key
44
+ [ "auth_master", params[:target].to_s, "id" ].join("_")
45
+ end
46
+
25
47
  def prevent_timing_attack
26
48
  start_time = Time.current
27
49
  yield
@@ -36,5 +58,17 @@ module AuthMaster
36
58
  def timing_attack_interval
37
59
  AuthMaster.timing_attack_interval.presence || TIMING_ATTACK_INTERVAL
38
60
  end
61
+
62
+ def check_token_presence
63
+ raise ActionController::RoutingError.new("Not Found Token") if params[:token].blank?
64
+ end
65
+
66
+ def check_pre_session_id
67
+ raise ActionController::RoutingError.new("Not Found Session") if session[session_key].blank?
68
+ end
69
+
70
+ def target_session_key
71
+ [ "current", target_param_name, "id" ].join("_")
72
+ end
39
73
  end
40
74
  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
@@ -1,5 +1,7 @@
1
1
  module AuthMaster
2
2
  class Session < ApplicationRecord
3
3
  belongs_to :target, polymorphic: true
4
+
5
+ enum :status, [ :inactive, :active ], default: :inactive
4
6
  end
5
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
@@ -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,22 @@ 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
17
 
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
18
+ def activate!(auth_master_session)
19
+ auth_master_session.active!
20
+ # TODO: Save IP Address, User Agent, etc
20
21
  end
21
22
 
22
23
  private
@@ -26,31 +27,7 @@ module AuthMaster
26
27
  end
27
28
 
28
29
  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]
30
+ count(target, time: login_timeout_interval_config(target)) < login_attempts_count_config(target)
54
31
  end
55
32
  end
56
33
  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,11 @@
1
1
  AuthMaster::Engine.routes.draw do
2
2
  get "/:target/login", to: "sessions#new", as: :auth_master_login
3
- post "/:target/login", to: "sessions#send_link"
3
+ post "/:target/login", to: "sessions#create"
4
4
 
5
5
  get "/:target/sent", to: "sessions#sent", as: :auth_master_sent
6
6
 
7
7
  get "/:target/link", to: "sessions#link", as: :auth_master_link
8
- post "/:target/link", to: "sessions#create"
8
+ post "/:target/link", to: "sessions#activate"
9
9
 
10
10
  get "/:target/denied", to: "sessions#denied", as: :auth_master_denied
11
11
  end
@@ -1,7 +1,8 @@
1
1
  class CreateAuthMasterSessions < ActiveRecord::Migration[8.0]
2
2
  def change
3
3
  create_table :auth_master_sessions, id: :uuid do |t|
4
- t.references :target, polymorphic: true, null: false, type: :uuid
4
+ t.references :target, polymorphic: true, null: false, type: :uuid
5
+ t.integer :status, limit: 2, null: false
5
6
 
6
7
  t.timestamps
7
8
  end
@@ -1,3 +1,3 @@
1
1
  module AuthMaster
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.4"
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.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - vickodin
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-26 00:00:00.000000000 Z
10
+ date: 2025-03-29 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -56,12 +56,17 @@ 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
63
67
  - app/operations/auth_master/send_link_operation.rb
64
68
  - app/services/auth_master/session_service.rb
69
+ - app/views/auth_master/sessions/link.html.erb
65
70
  - app/views/auth_master/sessions/new.html.erb
66
71
  - app/views/auth_master/sessions/sent.html.erb
67
72
  - config/routes.rb