authralia 0.0.1 → 0.0.3

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: c8385e74f10affaebc8489d31883c8cd31a7b73b2984e328fd5df68f4325be3a
4
- data.tar.gz: c50197c0b40fab4121e3648b25ceff8c0dfd9e9c5d168aa0a9a7d7defa3be70e
3
+ metadata.gz: 414a5e20e68e0a941cfee786236b8936b20c6c501abb1c0915eb88c0d2a75ca4
4
+ data.tar.gz: 4a88420c5c8a63f744c0aa8d5a88314fd59060ec73ae717105bf6423d40a078f
5
5
  SHA512:
6
- metadata.gz: dac5585c2d7526e833ecade43d1472f085834ed55a1a35d2a8914c2930b91311b342b740deb8be3a9a91740b1a77aa9265f738ea85b57f3dd84362b04f266cab
7
- data.tar.gz: e3b0bc76ae036c712d8cc79089f109a9bb49e9355a133a71a642c080035caed2da54fcc002fc4a4b675c781dfcdcaab2a09b3a05bd989653965a66f22e3fe0c2
6
+ metadata.gz: 19aaf48d110abd50ec78b293312c5e34ad80b23fa99af8c5c1017037398939395bc1f376af6f3113f7749e35dc90d2f7f438e71927f80bfeb07df0aaaebb27d1
7
+ data.tar.gz: a7f01ccea9d49569e61174d1d34c26885219ee1a91a296906da809dd17b6e67e419320594aa93f56d3109d84dc430a83f741fbcc1d8d25e7b5a46a921e008201
@@ -0,0 +1,6 @@
1
+ module Authralia
2
+ module Authenticate
3
+ class Base < SerpisObjek::Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,27 @@
1
+ module Authralia
2
+ module Authenticate
3
+ class Resource < Base
4
+ def initialize(resource_class_name, auth_data)
5
+ @auth_data = auth_data
6
+ @resource_class = resource_class_name.constantize
7
+ end
8
+
9
+ def call
10
+ if @auth_data.class.name == @resource_class.to_s
11
+ response(status: SUCCESS, payload: @auth_data)
12
+ else
13
+ resource = @resource_class.protect_thcope(@auth_data)
14
+
15
+ if resource&.authenticate(@auth_data[:password])
16
+ response(status: SUCCESS, payload: resource)
17
+ else
18
+ response(
19
+ status: FAIL,
20
+ message: "Incorrect Login Data"
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ module Authralia
2
+ module AuthenticatedSession
3
+ class Acceptence < Base
4
+ def initialize(session_identifier, guid)
5
+ @session_identifier = session_identifier
6
+ @guid = guid
7
+ end
8
+
9
+ def call
10
+ response(status: SUCCESS) if @session_identifier.blank?
11
+
12
+ update_sessions(@session_identifier) do |session|
13
+ if session[:guid] == @guid
14
+ session[:is_accepted] = true
15
+ else
16
+ session[:new_session_guids].delete(@guid)
17
+ end
18
+ end
19
+
20
+ response(status: SUCCESS)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,74 @@
1
+ module Authralia
2
+ module AuthenticatedSession
3
+ class Base < SerpisObjek::Base
4
+ protected
5
+
6
+ def extract_resource_identifier(session_identifier)
7
+ session_identifier.split('#').first(2).join('#')
8
+ end
9
+
10
+ def extract_guid(session_identifier)
11
+ session_identifier.split('#').last
12
+ end
13
+
14
+ def build_resource_identifier(resource)
15
+ "#{resource.class.name}##{resource.id}"
16
+ end
17
+
18
+ def filter_sessions(value)
19
+ return [] unless value.is_a? Array
20
+
21
+ value.select do |session|
22
+ session.is_a?(Hash)
23
+ end.map do |session|
24
+ session.symbolize_keys
25
+ end
26
+ end
27
+
28
+ def add_session(resource, session)
29
+ handler = get_session_handler(resource)
30
+
31
+ handler.value ||= []
32
+ handler.value << session
33
+ handler.save
34
+ end
35
+
36
+ def update_sessions(resource, &block)
37
+ handler = get_session_handler(resource)
38
+ sessions = filter_sessions(handler.value)
39
+
40
+ sessions.each do |session|
41
+ yield(session)
42
+ end
43
+
44
+ handler.value = sessions
45
+ handler.save
46
+ end
47
+
48
+ def get_session_handler(value)
49
+ resource_identifier =
50
+ if is_session_identifier?(value)
51
+ extract_resource_identifier(value)
52
+ elsif is_resource_identifier?(value)
53
+ value
54
+ else
55
+ build_resource_identifier(value)
56
+ end
57
+
58
+ @session_handler ||= Chredis::Json.new resource_identifier
59
+ end
60
+
61
+ # TODO: validate using start_with?
62
+ def is_session_identifier?(value)
63
+ value.is_a?(String) &&
64
+ value.count('#').eql?(2)
65
+ end
66
+
67
+ # TODO: validate using start_with?
68
+ def is_resource_identifier?(value)
69
+ value.is_a?(String) &&
70
+ value.count('#').eql?(1)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,42 @@
1
+ module Authralia
2
+ module AuthenticatedSession
3
+ class Creation < Base
4
+ def initialize(resource, controller)
5
+ @resource = resource
6
+ @controller = controller
7
+ end
8
+
9
+ def call
10
+ session_guid = SecureRandom.uuid
11
+ browser_guid = SecureRandom.uuid
12
+ resource_identifier = build_resource_identifier(@resource)
13
+
14
+ handler = get_session_handler(@resource)
15
+ sessions = filter_sessions(handler.value)
16
+
17
+ add_session(
18
+ @resource,
19
+ {
20
+ guid: session_guid,
21
+ browser_guid: @controller.send(:cookies)[:browser_guid],
22
+ login_at: Time.now.utc,
23
+ login_ip: @controller.request.ip,
24
+ host: @controller.request.host,
25
+ user_agent: @controller.request.user_agent,
26
+ expires_at: Time.now.utc + Authralia.expires_in.hours,
27
+ is_accepted: sessions.size.zero?,
28
+ new_session_guids: []
29
+ }
30
+ )
31
+
32
+ response(
33
+ status: SUCCESS,
34
+ payload: {
35
+ browser_guid: browser_guid,
36
+ session_identifier: "#{resource_identifier}##{session_guid}"
37
+ }
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ module Authralia
2
+ module AuthenticatedSession
3
+ class Rejection < Removal
4
+ def initialize(session_identifier, guid)
5
+ @session_identifier = session_identifier
6
+ @guid = guid
7
+ end
8
+
9
+ def call
10
+ response(status: FAIL) if @session_identifier.blank?
11
+ remove_session_based_on_guid(@session_identifier, @guid)
12
+ response(status: SUCCESS)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,36 @@
1
+ module Authralia
2
+ module AuthenticatedSession
3
+ class Removal < Base
4
+ def initialize(session_identifier)
5
+ @session_identifier = session_identifier
6
+ end
7
+
8
+ def call
9
+ guid = extract_guid(@session_identifier)
10
+
11
+ response(status: FAIL) if @session_identifier.blank?
12
+ remove_session_based_on_guid(@session_identifier, guid)
13
+ response(status: SUCCESS)
14
+ end
15
+
16
+ protected
17
+
18
+ def remove_session_based_on_guid(session_identifier, guid)
19
+ handler = get_session_handler(session_identifier)
20
+ collection = filter_sessions(handler.value)
21
+ collection = collection.reject { |s| s[:guid] == guid }
22
+
23
+ collection.each do |session|
24
+ session[:new_session_guids].delete(guid)
25
+ end
26
+
27
+ if collection.size.zero?
28
+ handler.clear
29
+ else
30
+ handler.value = collection
31
+ handler.save
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,95 @@
1
+ module Authralia
2
+ module AuthenticatedSession
3
+ class Validation < Base
4
+ AMOGUS = :AMOGUS
5
+
6
+ def initialize(session_identifier, controller)
7
+ @controller = controller
8
+ @session_identifier = session_identifier
9
+ end
10
+
11
+ def call
12
+ login_message = 'Please login to continue'
13
+
14
+ unless is_session_identifier?(@session_identifier)
15
+ return response(status: FAIL, message: login_message)
16
+ end
17
+
18
+ resource_name, resource_id, guid = @session_identifier.split('#')
19
+ resource = resource_name.constantize.find_by_id(resource_id)
20
+
21
+ handler = get_session_handler(resource)
22
+ sessions = filter_sessions(handler.value)
23
+ session = sessions.detect { |s| s[:guid].eql? guid }
24
+
25
+ if resource.blank? || session.blank?
26
+ return response(status: FAIL, message: login_message)
27
+ end
28
+
29
+ if is_session_expire?(session)
30
+ response(status: FAIL, message: "Session expire. #{login_message}")
31
+ elsif is_session_sus?(guid, sessions)
32
+ response(status: AMOGUS, message: 'AMOGUS')
33
+ else
34
+ if sessions.size >= 1 && !session[:is_accepted]
35
+ sessions = notify_other_sessions(sessions, guid)
36
+ handler.value = sessions
37
+ handler.save
38
+ end
39
+
40
+ response(
41
+ status: SUCCESS,
42
+ payload: { resource:, session: }
43
+ )
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def notify_other_sessions(sessions, guid)
50
+ sessions.each do |s|
51
+ new_session_guids = s[:new_session_guids]
52
+ is_accepted_session = s[:guid] != guid && s[:is_accepted]
53
+
54
+ if is_accepted_session && !new_session_guids.include?(guid)
55
+ s.update(new_session_guids: new_session_guids << guid)
56
+ end
57
+ end
58
+ end
59
+
60
+ def is_session_expire?(session)
61
+ (Time.now.utc - session[:expires_at].to_time) >= Authralia.expires_in.hours
62
+ end
63
+
64
+ def is_session_sus?(guid, sessions)
65
+ sessions.detect do |session|
66
+ is_guid_valid?(guid, session) &&
67
+ is_browser_valid?(session) &&
68
+ is_ip_valid?(session) &&
69
+ is_host_valid?(session) &&
70
+ is_user_agent_valid?(session)
71
+ end.blank?
72
+ end
73
+
74
+ def is_guid_valid?(guid, session)
75
+ session[:guid] == guid
76
+ end
77
+
78
+ def is_browser_valid?(session)
79
+ session[:browser_guid] == @controller.send(:cookies)[:browser_guid]
80
+ end
81
+
82
+ def is_ip_valid?(session)
83
+ session[:login_ip] == @controller.request.ip
84
+ end
85
+
86
+ def is_host_valid?(session)
87
+ session[:host] == @controller.request.host
88
+ end
89
+
90
+ def is_user_agent_valid?(session)
91
+ session[:user_agent] == @controller.request.user_agent
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,15 @@
1
+ module Authralia
2
+ module AuthenticatedSession
3
+ class Wipeout < Base
4
+ def initialize(identifier)
5
+ @identifier = identifier
6
+ end
7
+
8
+ def call
9
+ get_session_handler(@identifier).clear
10
+
11
+ response(status: SUCCESS)
12
+ end
13
+ end
14
+ end
15
+ end
data/lib/authralia.rb CHANGED
@@ -9,7 +9,7 @@ module Authralia
9
9
  end
10
10
 
11
11
  module AuthenticatedSession
12
- autoload :Base, 'session/base'
12
+ autoload :Base, 'authenticated_session/base'
13
13
  autoload :Acceptence, 'authenticated_session/acceptence'
14
14
  autoload :Creation, 'authenticated_session/creation'
15
15
  autoload :Rejection, 'authenticated_session/rejection'
@@ -26,8 +26,8 @@ module Authralia
26
26
  autoload :ThydneyTheProtector, 'models/thydney_the_protector'
27
27
  end
28
28
 
29
- mattr_accessor :single_active_session
30
- @@single_active_session = false
29
+ mattr_accessor :single_active_session_for
30
+ @@single_active_session_for = []
31
31
 
32
32
  mattr_accessor :expires_in
33
33
  @@expires_in = 1.hour
@@ -41,11 +41,11 @@ module Authralia
41
41
  end
42
42
 
43
43
  ActiveSupport.on_load(:action_controller_base) do
44
- include WowAuthralia::Controllers::Helpers
44
+ include Authralia::Controllers::Helpers
45
45
  end
46
46
 
47
47
  ActiveSupport.on_load(:active_record) do
48
- include WowAuthralia::Models::ThydneyTheProtector
48
+ include Authralia::Models::ThydneyTheProtector
49
49
  end
50
50
 
51
51
  module ActionDispatch::Routing
@@ -0,0 +1,112 @@
1
+ module Authralia
2
+ module Controllers
3
+ module Helpers
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ add_flash_types :error
8
+
9
+ protected
10
+
11
+ Authralia.resource_class_names.each do |resource_class_name|
12
+ resource_name = resource_class_name.underscore
13
+
14
+ attr_reader "current_#{resource_name}".to_sym,
15
+ "current_#{resource_name}_session".to_sym
16
+
17
+ helper_method "current_#{resource_name}".to_sym,
18
+ "current_#{resource_name}_session".to_sym
19
+
20
+ # SECURITY: CSRF countermeasure
21
+ rescue_from ActionController::InvalidAuthenticityToken,
22
+ with: "logout_#{resource_name}!".to_sym
23
+
24
+ define_method "authenticate_#{resource_name}!" do
25
+ validation_response = AuthenticatedSession::Validation.call(
26
+ session[resource_name], self
27
+ )
28
+
29
+ case validation_response.status
30
+ when AuthenticatedSession::Validation::SUCCESS
31
+ instance_variable_set(
32
+ "@current_#{resource_name}",
33
+ validation_response.payload[:resource]
34
+ )
35
+
36
+ instance_variable_set(
37
+ "@current_#{resource_name}_session",
38
+ validation_response.payload[:session]
39
+ )
40
+ when AuthenticatedSession::Validation::AMOGUS
41
+ reset_session
42
+
43
+ when_authtralia_fail(validation_response)
44
+ when AuthenticatedSession::Validation::FAIL
45
+ send("logout_#{resource_name}!")
46
+ when_authtralia_fail(validation_response)
47
+ end
48
+ end
49
+
50
+ define_method "logout_#{resource_name}!" do
51
+ return unless session[resource_name]
52
+
53
+ removal_response = AuthenticatedSession::Removal.call(
54
+ session[resource_name]
55
+ )
56
+
57
+ case removal_response.status
58
+ when Authenticate::Resource::SUCCESS
59
+ session.delete(resource_name)
60
+ end
61
+ end
62
+
63
+ define_method "login_#{resource_name}!" do |auth_data, &block|
64
+ set_browser_guid
65
+
66
+ auth_res_response = Authenticate::Resource.call(
67
+ resource_class_name, auth_data
68
+ )
69
+
70
+ case auth_res_response.status
71
+ when Authenticate::Resource::SUCCESS
72
+ resource = auth_res_response.payload
73
+
74
+ if Authralia.single_active_session_for.include?(resource_class_name) &&
75
+ AuthenticatedSession::Wipeout.call(resource)
76
+ end
77
+
78
+ # SECURITY: multi device and session fixation countermeasure
79
+ authed_sess_response = AuthenticatedSession::Creation.call(
80
+ resource, self
81
+ )
82
+
83
+ # SECURITY: session fixation countermeasure
84
+ session.delete(resource_name)
85
+
86
+ session[resource_name] =
87
+ authed_sess_response.payload[:session_identifier]
88
+
89
+ block.call(true, auth_res_response) if block
90
+ when Authenticate::Resource::FAIL
91
+ block.call(false, auth_res_response) if block
92
+ end
93
+ end
94
+
95
+ define_method "accept_#{resource_name}_session!" do |guid:|
96
+ AuthenticatedSession::Acceptence.call(session[resource_name], guid)
97
+ end
98
+
99
+ define_method "reject_#{resource_name}_session!" do |guid:|
100
+ AuthenticatedSession::Rejection.call(session[resource_name], guid)
101
+ end
102
+ end
103
+ end
104
+
105
+ def set_browser_guid
106
+ if cookies[:browser_guid].blank?
107
+ cookies.permanent.signed.encrypted[:browser_guid] = SecureRandom.uuid
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,21 @@
1
+ module Authralia
2
+ module Models
3
+ module ThydneyTheProtector
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def thydney_protects(*fields)
8
+ has_secure_password
9
+
10
+ define_singleton_method :protect_thcope do |params|
11
+ field = fields.detect { |f| params[f].present? }
12
+
13
+ return nil unless field
14
+
15
+ find_by("#{field}": params[field])
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authralia
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gilang Mugni Respaty
@@ -9,14 +9,54 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
  date: 2022-11-16 00:00:00.000000000 Z
12
- dependencies: []
13
- description: A authentication dedicated to Mike Tyson
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: chredis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: serpis_objek
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: A authentication dedicated to Mike Tyson based on his favorite place,
42
+ Australia
14
43
  email: gilmoregarland@gmail.com
15
44
  executables: []
16
45
  extensions: []
17
46
  extra_rdoc_files: []
18
47
  files:
48
+ - lib/authenticate/base.rb
49
+ - lib/authenticate/resource.rb
50
+ - lib/authenticated_session/acceptence.rb
51
+ - lib/authenticated_session/base.rb
52
+ - lib/authenticated_session/creation.rb
53
+ - lib/authenticated_session/rejection.rb
54
+ - lib/authenticated_session/removal.rb
55
+ - lib/authenticated_session/validation.rb
56
+ - lib/authenticated_session/wipeout.rb
19
57
  - lib/authralia.rb
58
+ - lib/controllers/helpers.rb
59
+ - lib/models/thydney_the_protector.rb
20
60
  homepage: https://rubygems.org/gems/hola
21
61
  licenses:
22
62
  - MIT