verify_it 0.3.0 → 0.4.1.beta

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.
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/security_utils"
4
+
3
5
  module VerifyIt
4
6
  class Verifier
5
7
  attr_reader :storage, :rate_limiter
@@ -10,157 +12,142 @@ module VerifyIt
10
12
  end
11
13
 
12
14
  def send_code(to:, record:, channel: nil, context: {})
13
- channel ||= VerifyIt.configuration.delivery_channel
14
-
15
- # Check send rate limit
16
- limit_check = rate_limiter.check_send_limit(identifier: to, record: record)
17
- unless limit_check[:allowed]
18
- return Result.new(
19
- success: false,
20
- error: :rate_limited,
21
- message: limit_check[:reason],
22
- rate_limited: true
23
- )
15
+ channel ||= config.delivery_channel
16
+
17
+ rate_limit_result = check_send_rate_limits(to: to, record: record)
18
+ return rate_limit_result if rate_limit_result
19
+
20
+ code_data = generate_and_store_code(to: to, record: record)
21
+
22
+ delivery_result = attempt_delivery(
23
+ to: to,
24
+ code: code_data[:code],
25
+ channel: channel,
26
+ context: context
27
+ )
28
+ return delivery_result if delivery_result
29
+
30
+ finalize_send(to: to, record: record, channel: channel, code_data: code_data)
31
+ end
32
+
33
+ def verify_code(to:, code:, record:, request: nil)
34
+ rate_limit_result = check_verification_rate_limit(to: to, record: record)
35
+ return rate_limit_result if rate_limit_result
36
+
37
+ stored_code = storage.fetch_code(identifier: to, record: record)
38
+ return code_not_found_result unless stored_code
39
+
40
+ current_attempts = rate_limiter.record_verification_attempt(identifier: to, record: record)
41
+
42
+ if code_matches?(code, stored_code)
43
+ handle_verification_success(to: to, record: record, attempts: current_attempts, request: request)
44
+ else
45
+ handle_verification_failure(to: to, record: record, attempts: current_attempts)
24
46
  end
47
+ end
48
+
49
+ def cleanup(to:, record:)
50
+ storage.cleanup(identifier: to, record: record)
51
+ success_result("Verification data cleaned up")
52
+ end
53
+
54
+ private
55
+
56
+ def config
57
+ VerifyIt.configuration
58
+ end
59
+
60
+ def check_send_rate_limits(to:, record:)
61
+ send_limit = rate_limiter.check_send_limit(identifier: to, record: record)
62
+ return rate_limited_result(send_limit[:reason]) unless send_limit[:allowed]
25
63
 
26
- # Check identifier change limit
27
64
  change_limit = rate_limiter.check_identifier_change_limit(record: record)
28
- unless change_limit[:allowed]
29
- return Result.new(
30
- success: false,
31
- error: :rate_limited,
32
- message: change_limit[:reason],
33
- rate_limited: true
34
- )
35
- end
65
+ return rate_limited_result(change_limit[:reason]) unless change_limit[:allowed]
36
66
 
37
- # Generate code
67
+ nil
68
+ end
69
+
70
+ def check_verification_rate_limit(to:, record:)
71
+ limit_check = rate_limiter.check_verification_limit(identifier: to, record: record)
72
+ return nil if limit_check[:allowed]
73
+
74
+ locked_result(
75
+ limit_check[:reason],
76
+ attempts: storage.attempts(identifier: to, record: record)
77
+ )
78
+ end
79
+
80
+ def generate_and_store_code(to:, record:)
38
81
  code = CodeGenerator.generate(
39
- length: VerifyIt.configuration.code_length,
40
- format: VerifyIt.configuration.code_format
82
+ length: config.code_length,
83
+ format: config.code_format
41
84
  )
42
- expires_at = Time.now + VerifyIt.configuration.code_ttl
85
+ expires_at = Time.now + config.code_ttl
86
+ hashed_code = CodeHasher.digest(code, secret: config.secret_key_base)
43
87
 
44
- # Store hashed code
45
- hashed_code = CodeHasher.digest(code, secret: VerifyIt.configuration.secret_key_base)
46
88
  storage.store_code(
47
89
  identifier: to,
48
90
  record: record,
49
91
  code: hashed_code,
50
92
  expires_at: expires_at
51
93
  )
52
-
53
- # Reset verification attempts for new code
54
94
  storage.reset_attempts(identifier: to, record: record)
55
95
 
56
- # Record send attempt
57
- rate_limiter.record_send(identifier: to, record: record)
58
-
59
- # Track identifier change if needed
60
- rate_limiter.record_identifier_change(record: record, identifier: to)
96
+ { code: code, expires_at: expires_at }
97
+ end
61
98
 
62
- # Deliver code
63
- begin
64
- delivery = get_delivery_adapter(channel)
65
- delivery.deliver(to: to, code: code, context: context)
66
- rescue StandardError => e
67
- return Result.new(
68
- success: false,
69
- error: :delivery_failed,
70
- message: "Failed to deliver verification code: #{e.message}"
71
- )
72
- end
99
+ def attempt_delivery(to:, code:, channel:, context:)
100
+ delivery = get_delivery_adapter(channel)
101
+ delivery.deliver(to: to, code: code, context: context)
102
+ nil
103
+ rescue StandardError => e
104
+ delivery_failed_result
105
+ end
73
106
 
74
- # Call on_send callback if configured
75
- callback = VerifyIt.configuration.on_send
76
- callback&.call(record: record, identifier: to, channel: channel)
107
+ def finalize_send(to:, record:, channel:, code_data:)
108
+ rate_limiter.record_send(identifier: to, record: record)
109
+ rate_limiter.record_identifier_change(record: record, identifier: to)
77
110
 
78
- # Build result
79
- result_data = {
80
- success: true,
81
- message: "Verification code sent successfully",
82
- expires_at: expires_at
83
- }
111
+ invoke_callback(config.on_send, record: record, identifier: to, channel: channel)
84
112
 
85
- # Include code in test mode
86
- result_data[:code] = code if VerifyIt.configuration.test_mode
113
+ build_send_success_result(code_data)
114
+ end
87
115
 
88
- Result.new(**result_data)
116
+ def code_matches?(code, stored_code)
117
+ ActiveSupport::SecurityUtils.secure_compare(
118
+ stored_code,
119
+ CodeHasher.digest(code, secret: config.secret_key_base)
120
+ )
89
121
  end
90
122
 
91
- def verify_code(to:, code:, record:)
92
- # Check verification rate limit
93
- limit_check = rate_limiter.check_verification_limit(identifier: to, record: record)
94
- unless limit_check[:allowed]
95
- return Result.new(
96
- success: false,
97
- error: :locked,
98
- message: limit_check[:reason],
99
- locked: true,
100
- attempts: storage.attempts(identifier: to, record: record)
101
- )
102
- end
123
+ def handle_verification_success(to:, record:, attempts:, request: nil)
124
+ storage.delete_code(identifier: to, record: record)
125
+ storage.reset_attempts(identifier: to, record: record)
126
+ storage.reset_send_count(identifier: to, record: record)
103
127
 
104
- # Fetch stored code
105
- stored_code = storage.fetch_code(identifier: to, record: record)
128
+ invoke_callback(config.on_verify_success, record: record, identifier: to, request: request)
106
129
 
107
- unless stored_code
108
- return Result.new(
109
- success: false,
110
- error: :code_not_found,
111
- message: "No verification code found or code has expired"
112
- )
113
- end
130
+ success_result("Verification successful", verified: true, attempts: attempts)
131
+ end
114
132
 
115
- # Increment attempts
116
- current_attempts = rate_limiter.record_verification_attempt(identifier: to, record: record)
133
+ def handle_verification_failure(to:, record:, attempts:)
134
+ max_attempts = config.max_verification_attempts
135
+ locked = attempts >= max_attempts
117
136
 
118
- # Verify code
119
- if stored_code == CodeHasher.digest(code, secret: VerifyIt.configuration.secret_key_base)
120
- # Success - cleanup
121
- storage.delete_code(identifier: to, record: record)
122
- storage.reset_attempts(identifier: to, record: record)
123
- storage.reset_send_count(identifier: to, record: record)
124
-
125
- # Call success callback if configured
126
- callback = VerifyIt.configuration.on_verify_success
127
- callback&.call(record: record, identifier: to)
128
-
129
- Result.new(
130
- success: true,
131
- message: "Verification successful",
132
- verified: true,
133
- attempts: current_attempts
134
- )
135
- else
136
- # Failure
137
- max_attempts = VerifyIt.configuration.max_verification_attempts
138
- locked = current_attempts >= max_attempts
139
-
140
- # Call failure callback if configured
141
- callback = VerifyIt.configuration.on_verify_failure
142
- callback&.call(record: record, identifier: to, attempts: current_attempts)
143
-
144
- Result.new(
145
- success: false,
146
- error: :invalid_code,
147
- message: locked ? "Maximum verification attempts exceeded" : "Invalid verification code",
148
- attempts: current_attempts,
149
- locked: locked
150
- )
151
- end
152
- end
137
+ invoke_callback(
138
+ config.on_verify_failure,
139
+ record: record,
140
+ identifier: to,
141
+ attempts: attempts
142
+ )
153
143
 
154
- def cleanup(to:, record:)
155
- storage.cleanup(identifier: to, record: record)
156
- Result.new(
157
- success: true,
158
- message: "Verification data cleaned up"
144
+ verification_failed_result(
145
+ message: locked ? "Maximum verification attempts exceeded" : "Invalid verification code",
146
+ attempts: attempts,
147
+ locked: locked
159
148
  )
160
149
  end
161
150
 
162
- private
163
-
164
151
  def get_delivery_adapter(channel)
165
152
  case channel
166
153
  when :sms
@@ -171,5 +158,67 @@ module VerifyIt
171
158
  raise ArgumentError, "Invalid delivery channel: #{channel}"
172
159
  end
173
160
  end
161
+
162
+ def invoke_callback(callback, **args)
163
+ callback&.call(**args)
164
+ end
165
+
166
+ # Result builders
167
+ def success_result(message, **options)
168
+ Result.new(success: true, message: message, **options)
169
+ end
170
+
171
+ def rate_limited_result(reason)
172
+ Result.new(
173
+ success: false,
174
+ error: :rate_limited,
175
+ message: reason,
176
+ rate_limited: true
177
+ )
178
+ end
179
+
180
+ def locked_result(reason, attempts:)
181
+ Result.new(
182
+ success: false,
183
+ error: :locked,
184
+ message: reason,
185
+ locked: true,
186
+ attempts: attempts
187
+ )
188
+ end
189
+
190
+ def code_not_found_result
191
+ Result.new(
192
+ success: false,
193
+ error: :code_not_found,
194
+ message: "No verification code found or code has expired"
195
+ )
196
+ end
197
+
198
+ def delivery_failed_result
199
+ Result.new(success: false, error: :delivery_failed, message: "Failed to deliver verification code")
200
+ end
201
+
202
+ def verification_failed_result(message:, attempts:, locked:)
203
+ Result.new(
204
+ success: false,
205
+ error: :invalid_code,
206
+ message: message,
207
+ attempts: attempts,
208
+ locked: locked
209
+ )
210
+ end
211
+
212
+ def build_send_success_result(code_data)
213
+ result_data = {
214
+ success: true,
215
+ message: "Verification code sent successfully",
216
+ expires_at: code_data[:expires_at]
217
+ }
218
+
219
+ result_data[:code] = code_data[:code] if config.test_mode
220
+
221
+ Result.new(**result_data)
222
+ end
174
223
  end
175
224
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module VerifyIt
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.1.beta"
5
5
  end
data/lib/verify_it.rb CHANGED
@@ -37,8 +37,8 @@ module VerifyIt
37
37
  verifier.send_code(to: to, record: record, channel: channel, context: context)
38
38
  end
39
39
 
40
- def verify_code(to:, code:, record:)
41
- verifier.verify_code(to: to, code: code, record: record)
40
+ def verify_code(to:, code:, record:, request: nil)
41
+ verifier.verify_code(to: to, code: code, record: record, request: request)
42
42
  end
43
43
 
44
44
  def cleanup(to:, record:)
@@ -62,6 +62,8 @@ module VerifyIt
62
62
  end
63
63
 
64
64
  def build_storage
65
+ configuration.validate!
66
+
65
67
  case configuration.storage
66
68
  when :memory
67
69
  Storage::MemoryStorage.new
@@ -81,4 +83,4 @@ module VerifyIt
81
83
  end
82
84
 
83
85
  # Load Rails integration if Rails is present
84
- require_relative "verify_it/railtie" if defined?(Rails)
86
+ require_relative "verify_it/engine" if defined?(Rails::Engine)
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: verify_it
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.1.beta
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremias Ramirez
8
- bindir: exe
8
+ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '6.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: activerecord
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '6.1'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '6.1'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: bundler
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -37,6 +51,20 @@ dependencies:
37
51
  - - ">="
38
52
  - !ruby/object:Gem::Version
39
53
  version: '2.3'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rails
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '6.1'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '6.1'
40
68
  - !ruby/object:Gem::Dependency
41
69
  name: rake
42
70
  requirement: !ruby/object:Gem::Requirement
@@ -66,82 +94,67 @@ dependencies:
66
94
  - !ruby/object:Gem::Version
67
95
  version: '3.12'
68
96
  - !ruby/object:Gem::Dependency
69
- name: rubocop
97
+ name: rspec-rails
70
98
  requirement: !ruby/object:Gem::Requirement
71
99
  requirements:
72
100
  - - ">="
73
101
  - !ruby/object:Gem::Version
74
- version: '1.50'
102
+ version: '6.0'
75
103
  type: :development
76
104
  prerelease: false
77
105
  version_requirements: !ruby/object:Gem::Requirement
78
106
  requirements:
79
107
  - - ">="
80
108
  - !ruby/object:Gem::Version
81
- version: '1.50'
109
+ version: '6.0'
82
110
  - !ruby/object:Gem::Dependency
83
- name: simplecov
111
+ name: rubocop
84
112
  requirement: !ruby/object:Gem::Requirement
85
113
  requirements:
86
114
  - - ">="
87
115
  - !ruby/object:Gem::Version
88
- version: '0.21'
116
+ version: '1.50'
89
117
  type: :development
90
118
  prerelease: false
91
119
  version_requirements: !ruby/object:Gem::Requirement
92
120
  requirements:
93
121
  - - ">="
94
122
  - !ruby/object:Gem::Version
95
- version: '0.21'
123
+ version: '1.50'
96
124
  - !ruby/object:Gem::Dependency
97
- name: activerecord
125
+ name: simplecov
98
126
  requirement: !ruby/object:Gem::Requirement
99
127
  requirements:
100
- - - "~>"
128
+ - - ">="
101
129
  - !ruby/object:Gem::Version
102
- version: 6.1.0
130
+ version: '0.21'
103
131
  type: :development
104
132
  prerelease: false
105
133
  version_requirements: !ruby/object:Gem::Requirement
106
134
  requirements:
107
- - - "~>"
135
+ - - ">="
108
136
  - !ruby/object:Gem::Version
109
- version: 6.1.0
137
+ version: '0.21'
110
138
  - !ruby/object:Gem::Dependency
111
139
  name: sqlite3
112
140
  requirement: !ruby/object:Gem::Requirement
113
141
  requirements:
114
- - - "~>"
142
+ - - ">="
115
143
  - !ruby/object:Gem::Version
116
144
  version: '1.4'
117
145
  type: :development
118
146
  prerelease: false
119
147
  version_requirements: !ruby/object:Gem::Requirement
120
148
  requirements:
121
- - - "~>"
149
+ - - ">="
122
150
  - !ruby/object:Gem::Version
123
151
  version: '1.4'
124
- - !ruby/object:Gem::Dependency
125
- name: logger
126
- requirement: !ruby/object:Gem::Requirement
127
- requirements:
128
- - - "~>"
129
- - !ruby/object:Gem::Version
130
- version: '1.5'
131
- type: :development
132
- prerelease: false
133
- version_requirements: !ruby/object:Gem::Requirement
134
- requirements:
135
- - - "~>"
136
- - !ruby/object:Gem::Version
137
- version: '1.5'
138
152
  description: A storage-agnostic verification system for Ruby applications. VerifyIt
139
153
  provides a flexible framework for implementing SMS and email verifications with
140
154
  built-in rate limiting and multiple storage backends.
141
155
  email:
142
156
  - iguanadejere4@googlemail.com
143
- executables:
144
- - verify_it
157
+ executables: []
145
158
  extensions: []
146
159
  extra_rdoc_files: []
147
160
  files:
@@ -153,16 +166,21 @@ files:
153
166
  - LICENSE.txt
154
167
  - README.md
155
168
  - Rakefile
156
- - exe/verify_it
169
+ - app/controllers/verify_it/application_controller.rb
170
+ - app/controllers/verify_it/verifications_controller.rb
171
+ - config/locales/en.yml
172
+ - config/routes.rb
173
+ - lib/generators/verify_it/install/install_generator.rb
174
+ - lib/generators/verify_it/install/templates/create_verify_it_tables.rb
175
+ - lib/generators/verify_it/install/templates/initializer.rb.erb
157
176
  - lib/verify_it.rb
158
- - lib/verify_it/cli.rb
159
177
  - lib/verify_it/code_generator.rb
160
178
  - lib/verify_it/code_hasher.rb
161
179
  - lib/verify_it/configuration.rb
162
180
  - lib/verify_it/delivery/base.rb
163
181
  - lib/verify_it/delivery/email_delivery.rb
164
182
  - lib/verify_it/delivery/sms_delivery.rb
165
- - lib/verify_it/railtie.rb
183
+ - lib/verify_it/engine.rb
166
184
  - lib/verify_it/rate_limiter.rb
167
185
  - lib/verify_it/result.rb
168
186
  - lib/verify_it/storage/base.rb
@@ -172,12 +190,9 @@ files:
172
190
  - lib/verify_it/storage/models/code.rb
173
191
  - lib/verify_it/storage/models/identifier_change.rb
174
192
  - lib/verify_it/storage/redis_storage.rb
175
- - lib/verify_it/templates/initializer.rb.erb
176
- - lib/verify_it/templates/migration.rb.erb
177
193
  - lib/verify_it/verifiable.rb
178
194
  - lib/verify_it/verifier.rb
179
195
  - lib/verify_it/version.rb
180
- - pkg/verify_it-0.1.0.gem
181
196
  - sig/verify_it.rbs
182
197
  homepage: https://github.com/JeremasPosta/verify_it
183
198
  licenses:
@@ -195,7 +210,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
195
210
  requirements:
196
211
  - - ">="
197
212
  - !ruby/object:Gem::Version
198
- version: '3.0'
213
+ version: '3.2'
199
214
  required_rubygems_version: !ruby/object:Gem::Requirement
200
215
  requirements:
201
216
  - - ">="
data/exe/verify_it DELETED
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "verify_it"
5
- require "verify_it/cli"
6
-
7
- VerifyIt::CLI.start(ARGV)