sequel_auth 0.0.6 → 0.1.0

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: 7520438387075590996072d0bf3f39a4590313d5cd4ac9ebbd7bfe75b7a4c1c6
4
- data.tar.gz: 9e9619c3be523a75011aa8469b4704f9572df4d6d566f8d7fb2463243e20cdd1
3
+ metadata.gz: 0b90735774160726580b1d8bbf0b4897b1ae02e5cccebacb41b18c526fed90c4
4
+ data.tar.gz: e23bc0c9be9ae661ae2f6e36b838322c328fe67118bcb617de5f1631f3a3a2e5
5
5
  SHA512:
6
- metadata.gz: de9544a0e81aee9437200aa43f7a1dae108394a12e6a21a123fd4b95d450cbc8b878395318e96e608e53786cef005252547283ac386094d282219ec76b0ef781
7
- data.tar.gz: 27b30df7effd4556194675dcd6d5e847a1dab66a7eb1565f37b5ae052dae47076ffd3eab6b2356cb294c58b41e9de63a1a9e59965b0c5f006a57e653aaa24f08
6
+ metadata.gz: 408aab64849e9286363b6a99621a0a738f517794100b6c31f19e47bdde2ec822113cb4bcfaede000ee6e7894017e18e20c74d54d55fec77d602280acc94cbe9e
7
+ data.tar.gz: 9b70e674fe932228d544d7293d849bf2e5d0b158bec008d043518b1a27cdfd388b4219aabb9909b997bddb55746d71e435da578168546470b3ccefbce97c0d44
@@ -5,10 +5,10 @@ module SequelAuth
5
5
  class Crypt
6
6
  class << self
7
7
 
8
- attr_writer :salt_prefix,:salt_size
8
+ attr_writer :method,:salt_size,:salt
9
9
  # Salt prefix - prefix for random salt
10
- def salt_prefix
11
- @salt_prefix ||= defaults[:salt_prefix]
10
+ def method
11
+ @method ||= defaults[:method]
12
12
  end
13
13
 
14
14
  # Salt size - size as number
@@ -16,14 +16,23 @@ module SequelAuth
16
16
  @salt_size ||= defaults[:salt_size]
17
17
  end
18
18
 
19
+ # Salt size
19
20
  def salt
20
- salt_prefix + (salt_size-salt_prefix.length).times.map {
21
+ @salt ||= defaults[:salt]
22
+ end
23
+
24
+ def random_salt
25
+ schemes.fetch(method) + (salt_size-schemes.fetch(method).length).times.map {
21
26
  (('a'..'z').to_a + (1..9).to_a + ('A'..'Z').to_a).sample
22
27
  }.join
23
28
  end
24
29
 
25
30
  def encrypt(password)
26
- password.crypt(salt)
31
+ if salt
32
+ password.crypt(salt)
33
+ else
34
+ password.crypt(random_salt)
35
+ end
27
36
  end
28
37
 
29
38
  def matches?(hash, password)
@@ -32,10 +41,21 @@ module SequelAuth
32
41
 
33
42
  def defaults
34
43
  {
35
- salt_prefix: "$6$",
44
+ salt: nil,
45
+ method: :sha512,
36
46
  salt_size: 16,
37
47
  }.freeze
38
48
  end
49
+ private
50
+ def schemes
51
+ {
52
+ md5: '$1$',
53
+ nthash: '$3$$',
54
+ sha256: '$5$',
55
+ sha512: '$6$',
56
+ sha1: '$sha1$'
57
+ }.freeze
58
+ end
39
59
  end
40
60
  end
41
61
  end
@@ -3,7 +3,7 @@
3
3
  module Sequel
4
4
  module Plugins
5
5
  module SequelAuth
6
- VERSION = '0.0.6'
6
+ VERSION = '0.1.0'
7
7
  end
8
8
  end
9
9
  end
data/lib/sequel_auth.rb CHANGED
@@ -1,92 +1,214 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "sequel"
2
4
  require "securerandom"
3
- require_relative "providers/bcrypt"
4
- require_relative "providers/scrypt"
5
- require_relative "providers/crypt"
5
+ require_relative "sequel_auth/providers/bcrypt"
6
+ require_relative "sequel_auth/providers/scrypt"
7
+ require_relative "sequel_auth/providers/crypt"
6
8
 
7
9
  module Sequel
8
10
  module Plugins
9
11
  module SequelAuth
10
- def self.provider(provider,opts={})
11
- provider = Kernel.const_get("SequelAuth::Providers::#{provider.to_s.capitalize}")
12
- opts.each { |k,v| provider.public_send("#{k}=", v) }
13
- provider
12
+ # Provider factory method
13
+ def self.provider(provider, opts = {})
14
+ provider_class = Kernel.const_get("SequelAuth::Providers::#{provider.to_s.capitalize}")
15
+ opts.each { |k, v| provider_class.public_send("#{k}=", v) }
16
+ provider_class
14
17
  end
15
18
 
19
+ # Default password strength options
20
+ DEFAULT_STRENGTH_OPTIONS = {
21
+ min_length: 8,
22
+ require_lowercase: true,
23
+ require_uppercase: true,
24
+ require_number: true,
25
+ require_special_char: false,
26
+ max_length: 128
27
+ }.freeze
28
+
29
+ # Plugin configuration
16
30
  def self.configure(model, opts = {})
17
31
  model.instance_eval do
32
+ # Core configuration
18
33
  @digest_column = opts.fetch(:digest_column, :password_digest)
19
34
  @include_validations = opts.fetch(:include_validations, true)
20
- @provider = SequelAuth.provider opts.fetch(:provider, :bcrypt),
21
- opts.fetch(:provider_opts, {})
22
- #Optional columns
35
+
36
+ # Provider configuration
37
+ @provider = SequelAuth.provider(
38
+ opts.fetch(:provider, :bcrypt),
39
+ opts.fetch(:provider_opts, {})
40
+ )
41
+
42
+ # Optional columns
23
43
  @access_token_column = opts.fetch(:access_token_column, nil)
24
44
  @login_count_column = opts.fetch(:login_count_column, nil)
25
45
  @failed_login_count_column = opts.fetch(:failed_login_count_column, nil)
26
- @last_login_at_column = opts.fetch(:last_login_at_column,nil)
46
+ @last_login_at_column = opts.fetch(:last_login_at_column, nil)
47
+
48
+ # Password strength configuration
49
+ if opts[:password_strength]
50
+ @password_strength_options = SequelAuth.validate_strength_options(opts[:password_strength])
51
+ end
52
+ end
53
+ end
54
+
55
+ # Validate strength options
56
+ def self.validate_strength_options(options)
57
+ # Merge with defaults
58
+ merged = DEFAULT_STRENGTH_OPTIONS.merge(options)
59
+
60
+ # Validate options
61
+ unless merged[:min_length].is_a?(Integer) && merged[:min_length] > 0
62
+ raise ArgumentError, ':min_length must be a positive integer'
63
+ end
64
+
65
+ unless merged[:max_length].is_a?(Integer) && merged[:max_length] >= merged[:min_length]
66
+ raise ArgumentError, ':max_length must be an integer greater than or equal to min_length'
27
67
  end
68
+
69
+ [ :require_lowercase, :require_uppercase, :require_number, :require_special_char ].each do |key|
70
+ unless [true, false].include?(merged[key])
71
+ raise ArgumentError, ":#{key} must be true or false"
72
+ end
73
+ end
74
+
75
+ merged.freeze
28
76
  end
29
77
 
78
+ # Class methods
30
79
  module ClassMethods
31
- attr_reader :provider,
32
- :digest_column,
33
- :access_token_column,
34
- :login_count_column,
35
- :failed_login_count_column,
36
- :last_login_at_column,
37
- :include_validations
80
+ attr_reader :provider,
81
+ :digest_column,
82
+ :access_token_column,
83
+ :login_count_column,
84
+ :failed_login_count_column,
85
+ :last_login_at_column,
86
+ :include_validations,
87
+ :password_strength_options
38
88
 
39
- # NOTE: nil as a value means that the value of the instance variable
40
- # will be assigned as is in the subclass.
41
- Plugins.inherited_instance_variables(self, :@provider => nil,
42
- :@include_validations => nil,
43
- :@digest_column => nil)
89
+ # Check if password strength validation is enabled
90
+ def password_strength_enabled?
91
+ !@password_strength_options.nil?
92
+ end
44
93
 
94
+ # Inherit instance variables
95
+ Plugins.inherited_instance_variables(
96
+ self,
97
+ :@provider => nil,
98
+ :@include_validations => nil,
99
+ :@digest_column => nil,
100
+ :@password_strength_options => nil
101
+ )
45
102
  end
46
103
 
104
+ # Instance methods
47
105
  module InstanceMethods
48
106
  attr_accessor :password_confirmation
49
107
  attr_reader :password
108
+
109
+ # Password setter with encryption
50
110
  def password=(unencrypted)
51
111
  @password = unencrypted
52
- self.send "#{model.digest_column}=",model.provider.encrypt(unencrypted)
112
+ self.send("#{model.digest_column}=", model.provider.encrypt(unencrypted))
53
113
  end
54
114
 
115
+ # Authentication method
55
116
  def authenticate(unencrypted)
56
- if model.provider.matches?(self.send(model.digest_column),unencrypted)
57
- if model.login_count_column || model.last_login_at_column
58
- #Update login count
59
- self.send("#{model.login_count_column}=",self.send(model.login_count_column)+1 ) if model.login_count_column
60
- self.send("#{model.last_login_at_column}=",Time.now ) if model.last_login_at_column
61
- self.save
62
- end
117
+ if model.provider.matches?(self.send(model.digest_column), unencrypted)
118
+ update_login_success
63
119
  self
64
120
  else
65
- if model.failed_login_count_column
66
- #Update failed login count
67
- self.send("#{model.failed_login_count_column}=",self.send(model.failed_login_count_column)+1 )
68
- self.save
69
- end
121
+ update_login_failure
122
+ nil
70
123
  end
71
124
  end
72
125
 
126
+ # Access token reset
73
127
  def reset_access_token
74
- if model.access_token_column
75
- self.update(model.access_token_column=>SecureRandom.urlsafe_base64(16))
76
- end
128
+ return unless model.access_token_column
129
+
130
+ self.update(model.access_token_column => SecureRandom.urlsafe_base64(16))
77
131
  end
78
132
 
133
+ # Validation method
79
134
  def validate
80
135
  super
81
136
 
82
- if model.include_validations
83
- errors.add :password, 'is not present' if password.nil? && new?
84
- errors.add :password, 'doesn\'t match confirmation' if password != password_confirmation
137
+ run_basic_validations
138
+ run_password_strength_validations if model.password_strength_enabled?
139
+ end
140
+
141
+ private
142
+
143
+ # Basic validations
144
+ def run_basic_validations
145
+ return unless model.include_validations
146
+
147
+ # Password presence validation (only for new records)
148
+ if password.nil? && new?
149
+ errors.add :password, 'is not present'
150
+ end
151
+
152
+ # Password confirmation validation
153
+ if password != password_confirmation
154
+ errors.add :password, "doesn't match confirmation"
85
155
  end
86
156
  end
87
157
 
158
+ # Password strength validations
159
+ def run_password_strength_validations
160
+ return if password.nil? || password.empty?
161
+
162
+ options = model.password_strength_options
163
+
164
+ # Check minimum length
165
+ if password.length < options[:min_length]
166
+ errors.add :password, "must be at least #{options[:min_length]} characters long"
167
+ end
168
+
169
+ # Check maximum length
170
+ if password.length > options[:max_length]
171
+ errors.add :password, "must be at most #{options[:max_length]} characters long"
172
+ end
173
+
174
+ # Check for lowercase letters
175
+ if options[:require_lowercase] && !password.match(/[a-z]/)
176
+ errors.add :password, 'must contain at least one lowercase letter'
177
+ end
178
+
179
+ # Check for uppercase letters
180
+ if options[:require_uppercase] && !password.match(/[A-Z]/)
181
+ errors.add :password, 'must contain at least one uppercase letter'
182
+ end
183
+
184
+ # Check for numbers
185
+ if options[:require_number] && !password.match(/\d/)
186
+ errors.add :password, 'must contain at least one number'
187
+ end
188
+
189
+ # Check for special characters
190
+ if options[:require_special_char] && !password.match(/[^A-Za-z0-9]/)
191
+ errors.add :password, 'must contain at least one special character'
192
+ end
193
+ end
194
+
195
+ # Update login success counters
196
+ def update_login_success
197
+ return unless model.login_count_column || model.last_login_at_column
198
+
199
+ self.send("#{model.login_count_column}=", self.send(model.login_count_column) + 1) if model.login_count_column
200
+ self.send("#{model.last_login_at_column}=", Time.now) if model.last_login_at_column
201
+ self.save
202
+ end
203
+
204
+ # Update login failure counters
205
+ def update_login_failure
206
+ return unless model.failed_login_count_column
207
+
208
+ self.send("#{model.failed_login_count_column}=", self.send(model.failed_login_count_column) + 1)
209
+ self.save
210
+ end
88
211
  end
89
-
90
212
  end
91
213
  end
92
- end
214
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fatih GENÇ
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2021-05-03 00:00:00.000000000 Z
10
+ date: 2026-04-16 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: sequel
@@ -16,20 +15,20 @@ dependencies:
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: '5.0'
18
+ version: '5.60'
20
19
  - - ">="
21
20
  - !ruby/object:Gem::Version
22
- version: 5.0.0
21
+ version: 5.60.0
23
22
  type: :runtime
24
23
  prerelease: false
25
24
  version_requirements: !ruby/object:Gem::Requirement
26
25
  requirements:
27
26
  - - "~>"
28
27
  - !ruby/object:Gem::Version
29
- version: '5.0'
28
+ version: '5.60'
30
29
  - - ">="
31
30
  - !ruby/object:Gem::Version
32
- version: 5.0.0
31
+ version: 5.60.0
33
32
  - !ruby/object:Gem::Dependency
34
33
  name: bcrypt
35
34
  requirement: !ruby/object:Gem::Requirement
@@ -37,6 +36,9 @@ dependencies:
37
36
  - - "~>"
38
37
  - !ruby/object:Gem::Version
39
38
  version: '3.1'
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 3.1.0
40
42
  type: :runtime
41
43
  prerelease: false
42
44
  version_requirements: !ruby/object:Gem::Requirement
@@ -44,6 +46,9 @@ dependencies:
44
46
  - - "~>"
45
47
  - !ruby/object:Gem::Version
46
48
  version: '3.1'
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: 3.1.0
47
52
  - !ruby/object:Gem::Dependency
48
53
  name: scrypt
49
54
  requirement: !ruby/object:Gem::Requirement
@@ -51,6 +56,9 @@ dependencies:
51
56
  - - "~>"
52
57
  - !ruby/object:Gem::Version
53
58
  version: '3.0'
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 3.0.0
54
62
  type: :runtime
55
63
  prerelease: false
56
64
  version_requirements: !ruby/object:Gem::Requirement
@@ -58,50 +66,104 @@ dependencies:
58
66
  - - "~>"
59
67
  - !ruby/object:Gem::Version
60
68
  version: '3.0'
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 3.0.0
61
72
  - !ruby/object:Gem::Dependency
62
73
  name: rspec
63
74
  requirement: !ruby/object:Gem::Requirement
64
75
  requirements:
65
76
  - - "~>"
66
77
  - !ruby/object:Gem::Version
67
- version: '3.0'
78
+ version: '3.12'
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 3.12.0
68
82
  type: :development
69
83
  prerelease: false
70
84
  version_requirements: !ruby/object:Gem::Requirement
71
85
  requirements:
72
86
  - - "~>"
73
87
  - !ruby/object:Gem::Version
74
- version: '3.0'
88
+ version: '3.12'
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 3.12.0
92
+ - !ruby/object:Gem::Dependency
93
+ name: simplecov
94
+ requirement: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - "~>"
97
+ - !ruby/object:Gem::Version
98
+ version: '0.22'
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: 0.22.0
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '0.22'
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 0.22.0
75
112
  - !ruby/object:Gem::Dependency
76
113
  name: sqlite3
77
114
  requirement: !ruby/object:Gem::Requirement
78
115
  requirements:
79
116
  - - "~>"
80
117
  - !ruby/object:Gem::Version
81
- version: '1.4'
118
+ version: '1.7'
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: 1.7.0
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - "~>"
127
+ - !ruby/object:Gem::Version
128
+ version: '1.7'
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: 1.7.0
132
+ - !ruby/object:Gem::Dependency
133
+ name: rake
134
+ requirement: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '13.0'
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: 13.0.0
82
142
  type: :development
83
143
  prerelease: false
84
144
  version_requirements: !ruby/object:Gem::Requirement
85
145
  requirements:
86
146
  - - "~>"
87
147
  - !ruby/object:Gem::Version
88
- version: '1.4'
148
+ version: '13.0'
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: 13.0.0
89
152
  description: Plugin to add authentication methods to Sequel Model
90
153
  email: fatihgnc@gmail.com
91
154
  executables: []
92
155
  extensions: []
93
156
  extra_rdoc_files: []
94
157
  files:
95
- - lib/providers/bcrypt.rb
96
- - lib/providers/crypt.rb
97
- - lib/providers/scrypt.rb
98
158
  - lib/sequel_auth.rb
159
+ - lib/sequel_auth/providers/bcrypt.rb
160
+ - lib/sequel_auth/providers/crypt.rb
161
+ - lib/sequel_auth/providers/scrypt.rb
99
162
  - lib/sequel_auth/version.rb
100
163
  homepage: https://gitlab.com/ikilifikir/sequel_auth
101
164
  licenses:
102
165
  - MIT
103
166
  metadata: {}
104
- post_install_message:
105
167
  rdoc_options: []
106
168
  require_paths:
107
169
  - lib
@@ -109,15 +171,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
109
171
  requirements:
110
172
  - - ">="
111
173
  - !ruby/object:Gem::Version
112
- version: '0'
174
+ version: 2.7.0
113
175
  required_rubygems_version: !ruby/object:Gem::Requirement
114
176
  requirements:
115
177
  - - ">="
116
178
  - !ruby/object:Gem::Version
117
179
  version: '0'
118
180
  requirements: []
119
- rubygems_version: 3.2.15
120
- signing_key:
181
+ rubygems_version: 4.0.10
121
182
  specification_version: 4
122
183
  summary: Plugin to add authentication methods to Sequel Model Select one of crypt,
123
184
  bcrypt, scrypt
File without changes
File without changes