sequel_auth 0.0.7 → 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: e2175f04cd46399643c847326b0e40133804ea62264315f2634968b9c72aa990
4
- data.tar.gz: cc44e957dae778c2e37b0f2d7be209e14d295598598f62951e264e50a6f8ebe8
3
+ metadata.gz: 0b90735774160726580b1d8bbf0b4897b1ae02e5cccebacb41b18c526fed90c4
4
+ data.tar.gz: e23bc0c9be9ae661ae2f6e36b838322c328fe67118bcb617de5f1631f3a3a2e5
5
5
  SHA512:
6
- metadata.gz: 526016215de744b925031bab53a12930737b541312e74353550e8305c8f629b94528d20e5e8178e2a7afd5075a999e3830308a705a1879bf0fcccc5ea117cfab
7
- data.tar.gz: 6f2ae03ded8e22c43cb14c538ee2fade2f5c5e3ece2a6c65ebd0099486fe9d3d3cf6f68d2f80b42738f5e675c5fda551e8ec2ec8c4c15fd966eeaed7b3cf5555
6
+ metadata.gz: 408aab64849e9286363b6a99621a0a738f517794100b6c31f19e47bdde2ec822113cb4bcfaede000ee6e7894017e18e20c74d54d55fec77d602280acc94cbe9e
7
+ data.tar.gz: 9b70e674fe932228d544d7293d849bf2e5d0b158bec008d043518b1a27cdfd388b4219aabb9909b997bddb55746d71e435da578168546470b3ccefbce97c0d44
@@ -3,7 +3,7 @@
3
3
  module Sequel
4
4
  module Plugins
5
5
  module SequelAuth
6
- VERSION = '0.0.7'
6
+ VERSION = '0.1.0'
7
7
  end
8
8
  end
9
9
  end
data/lib/sequel_auth.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "sequel"
2
4
  require "securerandom"
3
5
  require_relative "sequel_auth/providers/bcrypt"
@@ -7,86 +9,206 @@ require_relative "sequel_auth/providers/crypt"
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.7
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-06 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,48 +66,89 @@ 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
75
92
  - !ruby/object:Gem::Dependency
76
93
  name: simplecov
77
94
  requirement: !ruby/object:Gem::Requirement
78
95
  requirements:
79
96
  - - "~>"
80
97
  - !ruby/object:Gem::Version
81
- version: '0.21'
98
+ version: '0.22'
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: 0.22.0
82
102
  type: :development
83
103
  prerelease: false
84
104
  version_requirements: !ruby/object:Gem::Requirement
85
105
  requirements:
86
106
  - - "~>"
87
107
  - !ruby/object:Gem::Version
88
- version: '0.21'
108
+ version: '0.22'
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 0.22.0
89
112
  - !ruby/object:Gem::Dependency
90
113
  name: sqlite3
91
114
  requirement: !ruby/object:Gem::Requirement
92
115
  requirements:
93
116
  - - "~>"
94
117
  - !ruby/object:Gem::Version
95
- 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
96
142
  type: :development
97
143
  prerelease: false
98
144
  version_requirements: !ruby/object:Gem::Requirement
99
145
  requirements:
100
146
  - - "~>"
101
147
  - !ruby/object:Gem::Version
102
- version: '1.4'
148
+ version: '13.0'
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: 13.0.0
103
152
  description: Plugin to add authentication methods to Sequel Model
104
153
  email: fatihgnc@gmail.com
105
154
  executables: []
@@ -115,7 +164,6 @@ homepage: https://gitlab.com/ikilifikir/sequel_auth
115
164
  licenses:
116
165
  - MIT
117
166
  metadata: {}
118
- post_install_message:
119
167
  rdoc_options: []
120
168
  require_paths:
121
169
  - lib
@@ -123,15 +171,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
123
171
  requirements:
124
172
  - - ">="
125
173
  - !ruby/object:Gem::Version
126
- version: '0'
174
+ version: 2.7.0
127
175
  required_rubygems_version: !ruby/object:Gem::Requirement
128
176
  requirements:
129
177
  - - ">="
130
178
  - !ruby/object:Gem::Version
131
179
  version: '0'
132
180
  requirements: []
133
- rubygems_version: 3.2.15
134
- signing_key:
181
+ rubygems_version: 4.0.10
135
182
  specification_version: 4
136
183
  summary: Plugin to add authentication methods to Sequel Model Select one of crypt,
137
184
  bcrypt, scrypt