rom-auth 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0ce8711f63f7f47e34d33f9eb7d06cf50c786dec
4
+ data.tar.gz: f348776bc063992f9e86e8205c61a244d07b8ed8
5
+ SHA512:
6
+ metadata.gz: f26618867fd3c1580bd258a7c1c5c14ec62bd9a357ad93fa4222f827191965a7fc03c22031aadd9d419da9e2487b82adc2846cd08a044555080bebc785525842
7
+ data.tar.gz: dfc67314e9c244cf45e09480985bce757f46bf29f17ee0458b763419a10de6ecb8078ecec5d7ffa3fd82467fd27a546a0e0c61dbaf6fbe2d77221fef14f9617f
@@ -0,0 +1,63 @@
1
+ # rom-auth
2
+
3
+ * https://github.com/Ragmaanir/rom-auth
4
+
5
+ ## DESCRIPTION:
6
+
7
+ Low level Authentication solution based on [ROM](http://rom-rb.org). It is based on plugins and only supposed to do database-oriented authentication of users. It is completely independent of HTTP/Rails.
8
+
9
+ ## FEATURES/PROBLEMS:
10
+
11
+ * authentication of users via passwords
12
+ * Plugins
13
+ * A plugin for storing authentication attempts and their success/failure
14
+ * A lockdown plugin for locking down user accounts on authentication failure
15
+
16
+ ## SYNOPSIS:
17
+
18
+ * TODO
19
+
20
+ ## REQUIREMENTS:
21
+
22
+ * activesupport
23
+ * rom
24
+ * pbkdf2
25
+ * virtus
26
+
27
+ ## INSTALL:
28
+
29
+ * gem install rom-auth
30
+
31
+ ## DEVELOPERS:
32
+
33
+ After checking out the source, run:
34
+
35
+ $ rake newb
36
+
37
+ This task will install any missing dependencies, run the tests/specs,
38
+ and generate the RDoc.
39
+
40
+ ## LICENSE:
41
+
42
+ (The MIT License)
43
+
44
+ Copyright (c) 2015 Ragmaanir
45
+
46
+ Permission is hereby granted, free of charge, to any person obtaining
47
+ a copy of this software and associated documentation files (the
48
+ 'Software'), to deal in the Software without restriction, including
49
+ without limitation the rights to use, copy, modify, merge, publish,
50
+ distribute, sublicense, and/or sell copies of the Software, and to
51
+ permit persons to whom the Software is furnished to do so, subject to
52
+ the following conditions:
53
+
54
+ The above copyright notice and this permission notice shall be
55
+ included in all copies or substantial portions of the Software.
56
+
57
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
58
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
59
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
60
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
61
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
62
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
63
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,6 @@
1
+ require 'rom/auth'
2
+
3
+ module ROM
4
+ module Auth
5
+ end
6
+ end
@@ -0,0 +1,34 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+
4
+ require 'rom'
5
+ require 'rom-sql'
6
+ require 'virtus'
7
+ require 'active_support'
8
+ require 'active_support/core_ext'
9
+
10
+ # require 'rom/auth/support/shorthand_symbol'
11
+
12
+ require 'rom/auth/version'
13
+ require 'rom/auth/configuration'
14
+ require 'rom/auth/system'
15
+
16
+ # require 'rom/auth/authenticators/authenticator'
17
+ # require 'rom/auth/authenticators/password_authenticator'
18
+
19
+ require 'rom/auth/migration'
20
+
21
+ require 'rom/auth/plugins/plugin'
22
+ require 'rom/auth/plugins/authentication_events_plugin'
23
+ require 'rom/auth/plugins/authentication_credentials_plugin'
24
+ require 'rom/auth/plugins/lockdown_plugin'
25
+
26
+ require 'rom/auth/digest'
27
+ require 'rom/auth/password_verifiers/password_verifier'
28
+ require 'rom/auth/password_verifiers/pbkdf2_verifier'
29
+
30
+ module ROM
31
+ module Auth
32
+
33
+ end
34
+ end
@@ -0,0 +1,11 @@
1
+ module ROM::Auth
2
+ module Authenticators
3
+ class Authenticator
4
+ include Support::ShorthandSymbol.strip(/Authenticator/)
5
+
6
+ def authenticate(*args)
7
+ raise NotImplementedError
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module ROM::Auth
2
+ module Authenticators
3
+ class PasswordAuthenticator < Authenticator
4
+
5
+ def authenticate(user, identifier, password)
6
+ raise ArgumentError unless password.is_a?(String)
7
+
8
+ #user.password_verifier.verifies?(password)
9
+
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,45 @@
1
+ module ROM
2
+ module Auth
3
+ class Configuration
4
+ include Virtus.model
5
+
6
+ attribute :users_table_name, Symbol, default: :users
7
+ attribute :email_confirmation_token_length, Range
8
+ attribute :cookie_authentication_token_length, Range
9
+ attribute :instrumentation, Object, default: ->(c,_){ c.find_default_instrumentation }
10
+ attribute :plugins, Hash, default: {}
11
+ attribute :logger, Object, default: ->(_,_){
12
+ Logger.new(STDOUT).tap do |l|
13
+ l.level = Logger::WARN
14
+ l.progname = 'ROM::Auth'
15
+ end
16
+ }
17
+
18
+ def initialize(*args, &block)
19
+ super(*args)
20
+ block.call(self) if block
21
+ self
22
+ end
23
+
24
+ def plugin(cls, options={}, &block)
25
+ raise(ArgumentError, "Expected a class but was #{cls.inspect}") unless cls.is_a?(Class)
26
+ config = cls.const_get(:Configuration).new(options)
27
+ block.call(config) if block
28
+ self.plugins = plugins.merge(cls => config)
29
+ end
30
+
31
+ def find_default_instrumentation
32
+ ActiveSupport::Notifications
33
+ end
34
+
35
+ def singular_users_table_name
36
+ users_table_name.to_s.singularize
37
+ end
38
+
39
+ def user_fk_name
40
+ [singular_users_table_name, :id].join('_').to_sym
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,34 @@
1
+ module ROM
2
+ module Auth
3
+
4
+ class Digest
5
+ def initialize(data)
6
+ raise(ArgumentError, "String required got #{data.class}") if !data.is_a?(String)
7
+ @data = data
8
+ end
9
+
10
+ def length
11
+ @data.length
12
+ end
13
+
14
+ def ==(other)
15
+ raise ArgumentError if !other.is_a?(Digest)
16
+ raise ArgumentError if length != other.length
17
+
18
+ # SECURITY timing attack
19
+ # TODO because of short-circuiting this is actually not 100% constant time. fix this.
20
+ result = @data.chars.zip(other.to_s.chars).inject(true) do |res, (char, other_char)|
21
+ eq = (char == other_char)
22
+ res && eq
23
+ end
24
+
25
+ result
26
+ end
27
+
28
+ def to_s
29
+ @data
30
+ end
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ module ROM::Auth
2
+ class Migration
3
+ attr_reader :system, :setup, :config
4
+
5
+ def initialize(system, setup, config)
6
+ @system = system
7
+ @setup = setup
8
+ @config = config
9
+ end
10
+
11
+ def column_exists?(table, column)
12
+ database.schema(table).find{ |col| col.first == column }
13
+ end
14
+
15
+ def run
16
+ raise NotImplementedError
17
+ end
18
+
19
+ private
20
+
21
+ def database
22
+ @setup.default.connection
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,69 @@
1
+ module ROM
2
+ module Auth
3
+ module PasswordVerifiers
4
+ class PasswordVerifier
5
+
6
+ DEFAULT_OPTIONS = { :iterations => 15000, :hash_function => :sha256 }.freeze
7
+ DEFAULT_SALT_LENGTH = 16
8
+ SEPARATOR = ","
9
+ VERIFIERS = {}
10
+
11
+ attr_reader :salt, :digest, :options
12
+
13
+ def verifies?(plaintext_password)
14
+ tested_digest = compute_digest(plaintext_password, salt, options)
15
+
16
+ digest == tested_digest
17
+ end
18
+
19
+ def to_s
20
+ [type, salt, digest.to_s, options[:iterations]].join(SEPARATOR)
21
+ end
22
+
23
+ def ==(other)
24
+ case other
25
+ when PasswordVerifier then to_s == other.to_s
26
+ when String then to_s == other
27
+ else false
28
+ end
29
+ end
30
+
31
+ def self.for_password(plaintext_password, options={})
32
+ raise ArgumentError unless plaintext_password.is_a?(String)
33
+ new(options.merge(:password => plaintext_password))
34
+ end
35
+
36
+ def self.from_s(string)
37
+ kind, salt, digest, iterations = string.split(SEPARATOR)
38
+
39
+ raise(ArgumentError, "Invalid password verifier kind: #{kind.inspect}") unless VERIFIERS.keys.map(&:to_s).include?(kind)
40
+
41
+ VERIFIERS[kind.to_sym].new(DEFAULT_OPTIONS.merge(:digest => digest, :salt => salt, :iterations => iterations.to_i))
42
+ end
43
+
44
+ protected
45
+
46
+ def initialize(options={})
47
+ raise(ArgumentError, ":password or :digest required but got #{options.inspect}") unless options.values_at(:password, :digest).compact.one?
48
+
49
+ @options = DEFAULT_OPTIONS.merge(options)
50
+ @salt = @options.delete(:salt) || generate_random_salt(@options.delete(:salt_length))
51
+ @digest = if digest_str = @options.delete(:digest)
52
+ Digest.new(digest_str)
53
+ else
54
+ compute_digest(@options[:password], @salt, @options)
55
+ end
56
+ end
57
+
58
+ def compute_digest(plaintext_password, salt, options={})
59
+ raise NotImplementedError
60
+ end
61
+
62
+ def generate_random_salt(length=nil)
63
+ SecureRandom.hex(length || DEFAULT_SALT_LENGTH)
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,24 @@
1
+ require 'pbkdf2'
2
+
3
+ module ROM
4
+ module Auth
5
+ module PasswordVerifiers
6
+ class PBKDF2Verifier < PasswordVerifier
7
+
8
+ PasswordVerifier::VERIFIERS.merge!(:PBKDF2 => PBKDF2Verifier)
9
+
10
+ def type
11
+ :PBKDF2
12
+ end
13
+
14
+ protected
15
+
16
+ def compute_digest(plaintext_password, salt, options={})
17
+ hex = PBKDF2.new(options.merge(:password => plaintext_password, :salt => salt)).hex_string
18
+ Digest.new(hex)
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,115 @@
1
+ module ROM::Auth
2
+ module Plugins
3
+ class AuthenticationCredentialsPlugin < Plugin
4
+
5
+ class Configuration
6
+ include Virtus.model
7
+
8
+ attribute :table_name, Symbol, default: :authentication_credentials
9
+ end
10
+
11
+ def install
12
+ system.extend(CallbackOverrides)
13
+
14
+ config = configuration
15
+
16
+ @mapper_cls = Class.new(ROM::Mapper) do
17
+ relation(config.table_name)
18
+ model(AuthenticationCredential)
19
+ register_as :rom_auth_credential
20
+ end
21
+
22
+ @relation_cls = Class.new(ROM::Relation[:sql]) do
23
+ dataset(config.table_name)
24
+
25
+ def find_record(credentials)
26
+ raise if !credentials.respond_to?(:type) || !credentials.respond_to?(:identifier)
27
+ where(
28
+ type: credentials.type,
29
+ identifier: credentials.identifier
30
+ )
31
+ end
32
+ end
33
+ end
34
+
35
+ def migrate(setup)
36
+ AuthenticationCredentialsMigration.new(system, setup, configuration).run
37
+ end
38
+
39
+ def find_credential_entry(credentials)
40
+ ROM.env.relation(configuration.table_name).find_record(credentials).as(:rom_auth_credential).first
41
+ end
42
+
43
+ def identify_user(credentials)
44
+ cred = find_credential_entry(credentials)
45
+ ROM.env.relation(system.configuration.users_table_name).by_id(cred.user_id).first if cred
46
+ end
47
+
48
+ def authenticate(credentials)
49
+ cred = find_credential_entry(credentials)
50
+
51
+ cred.verifier.verifies?(credentials.password)
52
+ end
53
+
54
+ class AuthenticationCredential
55
+ include Virtus.value_object(coerce: false)
56
+
57
+ values do
58
+ attribute :user_id, Integer # FIXME this could be dynamic and could be account_id
59
+ attribute :created_at, DateTime
60
+ attribute :updated_at, DateTime
61
+ attribute :identifier, String
62
+ attribute :type, String
63
+ attribute :verifier_data, String
64
+ #attribute :verifier_type, String
65
+ attribute :active, Boolean
66
+ end
67
+
68
+ def verifier
69
+ PasswordVerifiers::PasswordVerifier.from_s(verifier_data)
70
+ end
71
+ end
72
+
73
+ class AuthenticationCredentialsMigration < Migration
74
+ def run
75
+ config = self.config
76
+ auth_config = system.configuration
77
+
78
+ user_fk_name = auth_config.user_fk_name
79
+
80
+ database.create_table(config.table_name) do
81
+ foreign_key(user_fk_name, auth_config.users_table_name.to_sym)
82
+
83
+ String :identifier
84
+ String :type
85
+ Boolean :active
86
+ #add_constraint(:password_verifier_length, Sequel.function(:char_length, :password_verifier)=>64..255)
87
+
88
+ String :verifier_data
89
+
90
+ DateTime :confirmed_at, null: true
91
+ String :confirmation_token, null: true
92
+ #add_constraint(:email_confirmation_token_length) { char_length(email_confirmation_token) == config.email_confirmation_token_length }
93
+ #add_constraint(:email_confirmation_token_length, Sequel.function(:char_length, :email_confirmation_token) => config.email_confirmation_token_length)
94
+
95
+ #String :cookie_authentication_token, null: true
96
+ #add_constraint(:cookie_authentication_token_length) { char_length(:cookie_authentication_token) == config.cookie_authentication_token_length }
97
+ #add_constraint(:cookie_authentication_token_length, Sequel.function(:char_length, :cookie_authentication_token) => config.cookie_authentication_token_length)
98
+ index [:identifier, :type], unique: true
99
+ end
100
+ end
101
+ end
102
+
103
+ module CallbackOverrides
104
+ def identify_user(credentials)
105
+ plugins[ROM::Auth::Plugins::AuthenticationCredentialsPlugin].identify_user(credentials)
106
+ end
107
+
108
+ def run_authentication_check(credentials)
109
+ plugins[ROM::Auth::Plugins::AuthenticationCredentialsPlugin].authenticate(credentials)
110
+ end
111
+ end
112
+
113
+ end
114
+ end
115
+ end