authralia 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c8385e74f10affaebc8489d31883c8cd31a7b73b2984e328fd5df68f4325be3a
4
- data.tar.gz: c50197c0b40fab4121e3648b25ceff8c0dfd9e9c5d168aa0a9a7d7defa3be70e
3
+ metadata.gz: 2992e694d931ea98652a6d624793d46239fcf1af73bb5d72e791eb487c994d62
4
+ data.tar.gz: c0d0706e07e39dea8f666670a57ac3b790d0c48916554389f4f04c9485e13b77
5
5
  SHA512:
6
- metadata.gz: dac5585c2d7526e833ecade43d1472f085834ed55a1a35d2a8914c2930b91311b342b740deb8be3a9a91740b1a77aa9265f738ea85b57f3dd84362b04f266cab
7
- data.tar.gz: e3b0bc76ae036c712d8cc79089f109a9bb49e9355a133a71a642c080035caed2da54fcc002fc4a4b675c781dfcdcaab2a09b3a05bd989653965a66f22e3fe0c2
6
+ metadata.gz: 4aa9d6497e33ebc8999bef5f0387054ed8e72282e4b538d2748a785f6b498aa0480d48aff380b43e40102738950f364edfcdeb312901e9beecea50f331cde9c5
7
+ data.tar.gz: 8d062d8f68e62577d4c6f596396a3d4514ae7333698bdc7328c3771d2d6c7952a088effa35d7fca816a98f02a1b396301f089590780925ab42e3331fae767a34
@@ -0,0 +1,6 @@
1
+ module Authralia
2
+ module Authenticate
3
+ class Base < ServiceObject::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 < ServiceObject::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
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # when Mike Tyson marvel at Australia's beautiful view
4
+
5
+ # TODO: use warden
6
+ # http://railscasts.com/episodes/305-authentication-with-warden?view=asciicast
7
+ module WowAuthralia
8
+ def self.configuration
9
+ @configuration ||= Configuration.new
10
+ end
11
+
12
+ def self.configure
13
+ yield configuration
14
+ end
15
+ end
16
+
17
+
18
+ class WowAuthralia::Configuration
19
+ include ActiveSupport::Configurable
20
+
21
+ config_accessor(:single_active_session) { false }
22
+ config_accessor(:expires_in) { 1.hour }
23
+ config_accessor(:resource_class_names) { [] }
24
+ end
25
+
26
+ ActiveSupport.on_load(:action_controller_base) do
27
+ include WowAuthralia::Controllers::Helpers
28
+ end
29
+
30
+ ActiveSupport.on_load(:active_record) do
31
+ include WowAuthralia::Models::ThydneyTheProtector
32
+ end
33
+
34
+ module ActionDispatch::Routing
35
+ class Mapper
36
+ def when_authticanted(resource_class_name, &block)
37
+ constraint = lambda do |request|
38
+ request.session[resource_class_name.underscore].present?
39
+ end
40
+
41
+ constraints(constraint) do
42
+ yield
43
+ end
44
+ end
45
+
46
+ def when_unauthticanted(resource_class_name, &block)
47
+ constraint = lambda do |request|
48
+ request.session[resource_class_name.underscore].blank?
49
+ end
50
+
51
+ constraints(constraint) do
52
+ yield
53
+ end
54
+ end
55
+ end
56
+ 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.2
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: []
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'
13
41
  description: A authentication dedicated to Mike Tyson
14
42
  email: gilmoregarland@gmail.com
15
43
  executables: []
16
44
  extensions: []
17
45
  extra_rdoc_files: []
18
46
  files:
47
+ - lib/authenticate/base.rb
48
+ - lib/authenticate/resource.rb
49
+ - lib/authenticated_session/acceptence.rb
50
+ - lib/authenticated_session/base.rb
51
+ - lib/authenticated_session/creation.rb
52
+ - lib/authenticated_session/rejection.rb
53
+ - lib/authenticated_session/removal.rb
54
+ - lib/authenticated_session/validation.rb
55
+ - lib/authenticated_session/wipeout.rb
19
56
  - lib/authralia.rb
57
+ - lib/controllers/helpers.rb
58
+ - lib/models/thydney_the_protector.rb
59
+ - lib/wow_authralia.rb
20
60
  homepage: https://rubygems.org/gems/hola
21
61
  licenses:
22
62
  - MIT