verify_it 0.2.0 → 0.4.0.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,156 +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:)
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)
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 code
45
88
  storage.store_code(
46
89
  identifier: to,
47
90
  record: record,
48
- code: code,
91
+ code: hashed_code,
49
92
  expires_at: expires_at
50
93
  )
51
-
52
- # Reset verification attempts for new code
53
94
  storage.reset_attempts(identifier: to, record: record)
54
95
 
55
- # Record send attempt
56
- rate_limiter.record_send(identifier: to, record: record)
57
-
58
- # Track identifier change if needed
59
- rate_limiter.record_identifier_change(record: record, identifier: to)
96
+ { code: code, expires_at: expires_at }
97
+ end
60
98
 
61
- # Deliver code
62
- begin
63
- delivery = get_delivery_adapter(channel)
64
- delivery.deliver(to: to, code: code, context: context)
65
- rescue StandardError => e
66
- return Result.new(
67
- success: false,
68
- error: :delivery_failed,
69
- message: "Failed to deliver verification code: #{e.message}"
70
- )
71
- 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
72
106
 
73
- # Call on_send callback if configured
74
- callback = VerifyIt.configuration.on_send
75
- 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)
76
110
 
77
- # Build result
78
- result_data = {
79
- success: true,
80
- message: "Verification code sent successfully",
81
- expires_at: expires_at
82
- }
111
+ invoke_callback(config.on_send, record: record, identifier: to, channel: channel)
83
112
 
84
- # Include code in test mode
85
- result_data[:code] = code if VerifyIt.configuration.test_mode
113
+ build_send_success_result(code_data)
114
+ end
86
115
 
87
- 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
+ )
88
121
  end
89
122
 
90
- def verify_code(to:, code:, record:)
91
- # Check verification rate limit
92
- limit_check = rate_limiter.check_verification_limit(identifier: to, record: record)
93
- unless limit_check[:allowed]
94
- return Result.new(
95
- success: false,
96
- error: :locked,
97
- message: limit_check[:reason],
98
- locked: true,
99
- attempts: storage.attempts(identifier: to, record: record)
100
- )
101
- end
123
+ def handle_verification_success(to:, record:, attempts:)
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)
102
127
 
103
- # Fetch stored code
104
- stored_code = storage.fetch_code(identifier: to, record: record)
128
+ invoke_callback(config.on_verify_success, record: record, identifier: to)
105
129
 
106
- unless stored_code
107
- return Result.new(
108
- success: false,
109
- error: :code_not_found,
110
- message: "No verification code found or code has expired"
111
- )
112
- end
130
+ success_result("Verification successful", verified: true, attempts: attempts)
131
+ end
113
132
 
114
- # Increment attempts
115
- 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
116
136
 
117
- # Verify code
118
- if stored_code == code.to_s
119
- # Success - cleanup
120
- storage.delete_code(identifier: to, record: record)
121
- storage.reset_attempts(identifier: to, record: record)
122
- storage.reset_send_count(identifier: to, record: record)
123
-
124
- # Call success callback if configured
125
- callback = VerifyIt.configuration.on_verify_success
126
- callback&.call(record: record, identifier: to)
127
-
128
- Result.new(
129
- success: true,
130
- message: "Verification successful",
131
- verified: true,
132
- attempts: current_attempts
133
- )
134
- else
135
- # Failure
136
- max_attempts = VerifyIt.configuration.max_verification_attempts
137
- locked = current_attempts >= max_attempts
138
-
139
- # Call failure callback if configured
140
- callback = VerifyIt.configuration.on_verify_failure
141
- callback&.call(record: record, identifier: to, attempts: current_attempts)
142
-
143
- Result.new(
144
- success: false,
145
- error: :invalid_code,
146
- message: locked ? "Maximum verification attempts exceeded" : "Invalid verification code",
147
- attempts: current_attempts,
148
- locked: locked
149
- )
150
- end
151
- end
137
+ invoke_callback(
138
+ config.on_verify_failure,
139
+ record: record,
140
+ identifier: to,
141
+ attempts: attempts
142
+ )
152
143
 
153
- def cleanup(to:, record:)
154
- storage.cleanup(identifier: to, record: record)
155
- Result.new(
156
- success: true,
157
- 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
158
148
  )
159
149
  end
160
150
 
161
- private
162
-
163
151
  def get_delivery_adapter(channel)
164
152
  case channel
165
153
  when :sms
@@ -170,5 +158,67 @@ module VerifyIt
170
158
  raise ArgumentError, "Invalid delivery channel: #{channel}"
171
159
  end
172
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
173
223
  end
174
224
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module VerifyIt
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0.beta"
5
5
  end
data/lib/verify_it.rb CHANGED
@@ -4,6 +4,7 @@ require_relative "verify_it/version"
4
4
  require_relative "verify_it/configuration"
5
5
  require_relative "verify_it/result"
6
6
  require_relative "verify_it/code_generator"
7
+ require_relative "verify_it/code_hasher"
7
8
  require_relative "verify_it/rate_limiter"
8
9
  require_relative "verify_it/storage/base"
9
10
  require_relative "verify_it/storage/memory_storage"
@@ -61,6 +62,8 @@ module VerifyIt
61
62
  end
62
63
 
63
64
  def build_storage
65
+ configuration.validate!
66
+
64
67
  case configuration.storage
65
68
  when :memory
66
69
  Storage::MemoryStorage.new
@@ -80,4 +83,4 @@ module VerifyIt
80
83
  end
81
84
 
82
85
  # Load Rails integration if Rails is present
83
- require_relative "verify_it/railtie" if defined?(Rails)
86
+ require_relative "verify_it/engine" if defined?(Rails::Engine)
Binary file
Binary file
Binary file
Binary file
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.2.0
4
+ version: 0.4.0.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,15 +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
178
+ - lib/verify_it/code_hasher.rb
160
179
  - lib/verify_it/configuration.rb
161
180
  - lib/verify_it/delivery/base.rb
162
181
  - lib/verify_it/delivery/email_delivery.rb
163
182
  - lib/verify_it/delivery/sms_delivery.rb
164
- - lib/verify_it/railtie.rb
183
+ - lib/verify_it/engine.rb
165
184
  - lib/verify_it/rate_limiter.rb
166
185
  - lib/verify_it/result.rb
167
186
  - lib/verify_it/storage/base.rb
@@ -171,13 +190,15 @@ files:
171
190
  - lib/verify_it/storage/models/code.rb
172
191
  - lib/verify_it/storage/models/identifier_change.rb
173
192
  - lib/verify_it/storage/redis_storage.rb
174
- - lib/verify_it/templates/initializer.rb.erb
175
- - lib/verify_it/templates/migration.rb.erb
176
193
  - lib/verify_it/verifiable.rb
177
194
  - lib/verify_it/verifier.rb
178
195
  - lib/verify_it/version.rb
179
196
  - pkg/verify_it-0.1.0.gem
180
197
  - sig/verify_it.rbs
198
+ - verify_it-0.1.0.gem
199
+ - verify_it-0.1.1.gem
200
+ - verify_it-0.2.0.gem
201
+ - verify_it-0.3.0.gem
181
202
  homepage: https://github.com/JeremasPosta/verify_it
182
203
  licenses:
183
204
  - MIT
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)