verify_it 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 +7 -0
- data/.DS_Store +0 -0
- data/AGENTS.md +140 -0
- data/CHANGELOG.md +32 -0
- data/CLAUDE.md +132 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +414 -0
- data/Rakefile +12 -0
- data/exe/verify_it +7 -0
- data/lib/verify_it/cli.rb +135 -0
- data/lib/verify_it/code_generator.rb +32 -0
- data/lib/verify_it/configuration.rb +46 -0
- data/lib/verify_it/delivery/base.rb +11 -0
- data/lib/verify_it/delivery/email_delivery.rb +16 -0
- data/lib/verify_it/delivery/sms_delivery.rb +16 -0
- data/lib/verify_it/railtie.rb +14 -0
- data/lib/verify_it/rate_limiter.rb +68 -0
- data/lib/verify_it/result.rb +36 -0
- data/lib/verify_it/storage/base.rb +71 -0
- data/lib/verify_it/storage/database_storage.rb +225 -0
- data/lib/verify_it/storage/memory_storage.rb +136 -0
- data/lib/verify_it/storage/models/attempt.rb +33 -0
- data/lib/verify_it/storage/models/code.rb +30 -0
- data/lib/verify_it/storage/models/identifier_change.rb +23 -0
- data/lib/verify_it/storage/redis_storage.rb +85 -0
- data/lib/verify_it/templates/initializer.rb.erb +79 -0
- data/lib/verify_it/templates/migration.rb.erb +41 -0
- data/lib/verify_it/verifiable.rb +38 -0
- data/lib/verify_it/verifier.rb +174 -0
- data/lib/verify_it/version.rb +5 -0
- data/lib/verify_it.rb +82 -0
- data/pkg/verify_it-0.1.0.gem +0 -0
- data/sig/verify_it.rbs +4 -0
- metadata +207 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VerifyIt
|
|
4
|
+
module Storage
|
|
5
|
+
class RedisStorage < Base
|
|
6
|
+
def initialize(redis_client)
|
|
7
|
+
@redis = redis_client
|
|
8
|
+
raise ArgumentError, "Redis client is required for RedisStorage" unless @redis
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def store_code(identifier:, record:, code:, expires_at:)
|
|
12
|
+
key = build_key(identifier: identifier, record: record, suffix: "code")
|
|
13
|
+
ttl = (expires_at - Time.now).to_i
|
|
14
|
+
@redis.setex(key, ttl, code)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def fetch_code(identifier:, record:)
|
|
18
|
+
key = build_key(identifier: identifier, record: record, suffix: "code")
|
|
19
|
+
@redis.get(key)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def delete_code(identifier:, record:)
|
|
23
|
+
key = build_key(identifier: identifier, record: record, suffix: "code")
|
|
24
|
+
@redis.del(key)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def increment_attempts(identifier:, record:)
|
|
28
|
+
key = build_key(identifier: identifier, record: record, suffix: "attempts")
|
|
29
|
+
count = @redis.incr(key)
|
|
30
|
+
@redis.expire(key, VerifyIt.configuration.rate_limit_window) if count == 1
|
|
31
|
+
count
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def attempts(identifier:, record:)
|
|
35
|
+
key = build_key(identifier: identifier, record: record, suffix: "attempts")
|
|
36
|
+
@redis.get(key).to_i
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def reset_attempts(identifier:, record:)
|
|
40
|
+
key = build_key(identifier: identifier, record: record, suffix: "attempts")
|
|
41
|
+
@redis.del(key)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def increment_send_count(identifier:, record:)
|
|
45
|
+
key = build_key(identifier: identifier, record: record, suffix: "send_count")
|
|
46
|
+
count = @redis.incr(key)
|
|
47
|
+
@redis.expire(key, VerifyIt.configuration.rate_limit_window) if count == 1
|
|
48
|
+
count
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def send_count(identifier:, record:)
|
|
52
|
+
key = build_key(identifier: identifier, record: record, suffix: "send_count")
|
|
53
|
+
@redis.get(key).to_i
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def reset_send_count(identifier:, record:)
|
|
57
|
+
key = build_key(identifier: identifier, record: record, suffix: "send_count")
|
|
58
|
+
@redis.del(key)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def track_identifier_change(record:, identifier:)
|
|
62
|
+
key = build_key(identifier: "", record: record, suffix: "identifier_changes")
|
|
63
|
+
score = Time.now.to_f
|
|
64
|
+
@redis.zadd(key, score, "#{identifier}:#{score}")
|
|
65
|
+
@redis.expire(key, VerifyIt.configuration.rate_limit_window)
|
|
66
|
+
|
|
67
|
+
# Remove old entries
|
|
68
|
+
cutoff = Time.now.to_f - VerifyIt.configuration.rate_limit_window
|
|
69
|
+
@redis.zremrangebyscore(key, "-inf", cutoff)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def identifier_changes(record:)
|
|
73
|
+
key = build_key(identifier: "", record: record, suffix: "identifier_changes")
|
|
74
|
+
cutoff = Time.now.to_f - VerifyIt.configuration.rate_limit_window
|
|
75
|
+
@redis.zcount(key, cutoff, "+inf")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def cleanup(identifier:, record:)
|
|
79
|
+
pattern = build_key(identifier: identifier, record: record, suffix: "*")
|
|
80
|
+
keys = @redis.keys(pattern)
|
|
81
|
+
@redis.del(*keys) if keys.any?
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
VerifyIt.configure do |config|
|
|
4
|
+
<% if storage_type == "memory" %>
|
|
5
|
+
# Memory storage (for development/testing only)
|
|
6
|
+
config.storage = :memory
|
|
7
|
+
<% elsif storage_type == "redis" %>
|
|
8
|
+
# Redis storage (recommended for production)
|
|
9
|
+
config.storage = :redis
|
|
10
|
+
config.redis_client = Redis.new(
|
|
11
|
+
url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0")
|
|
12
|
+
)
|
|
13
|
+
<% elsif storage_type == "database" %>
|
|
14
|
+
# Database storage (uses ActiveRecord)
|
|
15
|
+
config.storage = :database
|
|
16
|
+
<% end %>
|
|
17
|
+
|
|
18
|
+
# Code settings
|
|
19
|
+
config.code_length = 6
|
|
20
|
+
config.code_ttl = 300 # 5 minutes
|
|
21
|
+
config.code_format = :numeric # :numeric, :alphanumeric, or :alpha
|
|
22
|
+
|
|
23
|
+
# Rate limiting
|
|
24
|
+
config.max_send_attempts = 3
|
|
25
|
+
config.max_verification_attempts = 5
|
|
26
|
+
config.max_identifier_changes = 5
|
|
27
|
+
config.rate_limit_window = 3600 # 1 hour
|
|
28
|
+
|
|
29
|
+
# Delivery channels
|
|
30
|
+
config.delivery_channel = :sms # :sms or :email
|
|
31
|
+
|
|
32
|
+
# SMS delivery (configure with your provider)
|
|
33
|
+
config.sms_sender = ->(to:, code:, context:) {
|
|
34
|
+
# Example with Twilio:
|
|
35
|
+
# twilio_client = Twilio::REST::Client.new(
|
|
36
|
+
# ENV['TWILIO_ACCOUNT_SID'],
|
|
37
|
+
# ENV['TWILIO_AUTH_TOKEN']
|
|
38
|
+
# )
|
|
39
|
+
# twilio_client.messages.create(
|
|
40
|
+
# to: to,
|
|
41
|
+
# from: ENV['TWILIO_PHONE_NUMBER'],
|
|
42
|
+
# body: "Your verification code is: #{code}"
|
|
43
|
+
# )
|
|
44
|
+
|
|
45
|
+
# For development, just log it:
|
|
46
|
+
Rails.logger.info("SMS to #{to}: Your verification code is #{code}")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Email delivery (configure with your mailer)
|
|
50
|
+
config.email_sender = ->(to:, code:, context:) {
|
|
51
|
+
# Example with ActionMailer:
|
|
52
|
+
# VerificationMailer.send_code(to, code, context).deliver_now
|
|
53
|
+
|
|
54
|
+
# For development, just log it:
|
|
55
|
+
Rails.logger.info("Email to #{to}: Your verification code is #{code}")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Lifecycle callbacks (optional)
|
|
59
|
+
# config.on_send = ->(record:, identifier:, channel:) {
|
|
60
|
+
# Rails.logger.info("Verification sent to #{identifier} via #{channel}")
|
|
61
|
+
# }
|
|
62
|
+
|
|
63
|
+
# config.on_verify_success = ->(record:, identifier:) {
|
|
64
|
+
# Rails.logger.info("Verification successful for #{identifier}")
|
|
65
|
+
# }
|
|
66
|
+
|
|
67
|
+
# config.on_verify_failure = ->(record:, identifier:, attempts:) {
|
|
68
|
+
# Rails.logger.warn("Verification failed for #{identifier} (#{attempts} attempts)")
|
|
69
|
+
# }
|
|
70
|
+
|
|
71
|
+
# Namespace (for multi-tenant apps)
|
|
72
|
+
# config.namespace = ->(record) {
|
|
73
|
+
# record.respond_to?(:organization_id) ? "org:#{record.organization_id}" : nil
|
|
74
|
+
# }
|
|
75
|
+
|
|
76
|
+
# Test mode (expose codes for testing)
|
|
77
|
+
config.test_mode = Rails.env.test?
|
|
78
|
+
config.bypass_delivery = Rails.env.test?
|
|
79
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateVerifyItTables < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :verify_it_codes do |t|
|
|
6
|
+
t.string :identifier, null: false
|
|
7
|
+
t.string :record_type
|
|
8
|
+
t.integer :record_id
|
|
9
|
+
t.string :code, null: false
|
|
10
|
+
t.datetime :expires_at, null: false
|
|
11
|
+
t.timestamps
|
|
12
|
+
|
|
13
|
+
t.index [:identifier, :record_type, :record_id], name: "index_verify_it_codes_on_record"
|
|
14
|
+
t.index :expires_at
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
create_table :verify_it_attempts do |t|
|
|
18
|
+
t.string :identifier, null: false
|
|
19
|
+
t.string :record_type
|
|
20
|
+
t.integer :record_id
|
|
21
|
+
t.string :attempt_type, null: false # 'verification' or 'send'
|
|
22
|
+
t.integer :count, default: 0, null: false
|
|
23
|
+
t.datetime :expires_at, null: false
|
|
24
|
+
t.timestamps
|
|
25
|
+
|
|
26
|
+
t.index [:identifier, :record_type, :record_id, :attempt_type],
|
|
27
|
+
name: "index_verify_it_attempts_on_record_and_type"
|
|
28
|
+
t.index :expires_at
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
create_table :verify_it_identifier_changes do |t|
|
|
32
|
+
t.string :record_type
|
|
33
|
+
t.integer :record_id
|
|
34
|
+
t.string :identifier, null: false
|
|
35
|
+
t.datetime :created_at, null: false
|
|
36
|
+
|
|
37
|
+
t.index [:record_type, :record_id], name: "index_verify_it_identifier_changes_on_record"
|
|
38
|
+
t.index :created_at
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VerifyIt
|
|
4
|
+
module Verifiable
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
module ClassMethods
|
|
8
|
+
def verifies(attribute, channel: :sms)
|
|
9
|
+
define_method("send_verification_code") do |context: {}|
|
|
10
|
+
identifier = send(attribute)
|
|
11
|
+
VerifyIt.send_code(
|
|
12
|
+
to: identifier,
|
|
13
|
+
record: self,
|
|
14
|
+
channel: channel,
|
|
15
|
+
context: context
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
define_method("verify_code") do |code|
|
|
20
|
+
identifier = send(attribute)
|
|
21
|
+
VerifyIt.verify_code(
|
|
22
|
+
to: identifier,
|
|
23
|
+
code: code,
|
|
24
|
+
record: self
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
define_method("cleanup_verification") do
|
|
29
|
+
identifier = send(attribute)
|
|
30
|
+
VerifyIt.cleanup(
|
|
31
|
+
to: identifier,
|
|
32
|
+
record: self
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VerifyIt
|
|
4
|
+
class Verifier
|
|
5
|
+
attr_reader :storage, :rate_limiter
|
|
6
|
+
|
|
7
|
+
def initialize(storage:)
|
|
8
|
+
@storage = storage
|
|
9
|
+
@rate_limiter = RateLimiter.new(storage)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
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
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Check identifier change limit
|
|
27
|
+
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
|
|
36
|
+
|
|
37
|
+
# Generate code
|
|
38
|
+
code = CodeGenerator.generate(
|
|
39
|
+
length: VerifyIt.configuration.code_length,
|
|
40
|
+
format: VerifyIt.configuration.code_format
|
|
41
|
+
)
|
|
42
|
+
expires_at = Time.now + VerifyIt.configuration.code_ttl
|
|
43
|
+
|
|
44
|
+
# Store code
|
|
45
|
+
storage.store_code(
|
|
46
|
+
identifier: to,
|
|
47
|
+
record: record,
|
|
48
|
+
code: code,
|
|
49
|
+
expires_at: expires_at
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Reset verification attempts for new code
|
|
53
|
+
storage.reset_attempts(identifier: to, record: record)
|
|
54
|
+
|
|
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)
|
|
60
|
+
|
|
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
|
|
72
|
+
|
|
73
|
+
# Call on_send callback if configured
|
|
74
|
+
callback = VerifyIt.configuration.on_send
|
|
75
|
+
callback&.call(record: record, identifier: to, channel: channel)
|
|
76
|
+
|
|
77
|
+
# Build result
|
|
78
|
+
result_data = {
|
|
79
|
+
success: true,
|
|
80
|
+
message: "Verification code sent successfully",
|
|
81
|
+
expires_at: expires_at
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Include code in test mode
|
|
85
|
+
result_data[:code] = code if VerifyIt.configuration.test_mode
|
|
86
|
+
|
|
87
|
+
Result.new(**result_data)
|
|
88
|
+
end
|
|
89
|
+
|
|
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
|
|
102
|
+
|
|
103
|
+
# Fetch stored code
|
|
104
|
+
stored_code = storage.fetch_code(identifier: to, record: record)
|
|
105
|
+
|
|
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
|
|
113
|
+
|
|
114
|
+
# Increment attempts
|
|
115
|
+
current_attempts = rate_limiter.record_verification_attempt(identifier: to, record: record)
|
|
116
|
+
|
|
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
|
|
152
|
+
|
|
153
|
+
def cleanup(to:, record:)
|
|
154
|
+
storage.cleanup(identifier: to, record: record)
|
|
155
|
+
Result.new(
|
|
156
|
+
success: true,
|
|
157
|
+
message: "Verification data cleaned up"
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
def get_delivery_adapter(channel)
|
|
164
|
+
case channel
|
|
165
|
+
when :sms
|
|
166
|
+
Delivery::SmsDelivery.new
|
|
167
|
+
when :email
|
|
168
|
+
Delivery::EmailDelivery.new
|
|
169
|
+
else
|
|
170
|
+
raise ArgumentError, "Invalid delivery channel: #{channel}"
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
data/lib/verify_it.rb
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "verify_it/version"
|
|
4
|
+
require_relative "verify_it/configuration"
|
|
5
|
+
require_relative "verify_it/result"
|
|
6
|
+
require_relative "verify_it/code_generator"
|
|
7
|
+
require_relative "verify_it/rate_limiter"
|
|
8
|
+
require_relative "verify_it/storage/base"
|
|
9
|
+
require_relative "verify_it/storage/memory_storage"
|
|
10
|
+
require_relative "verify_it/storage/redis_storage"
|
|
11
|
+
require_relative "verify_it/storage/database_storage"
|
|
12
|
+
require_relative "verify_it/delivery/base"
|
|
13
|
+
require_relative "verify_it/delivery/sms_delivery"
|
|
14
|
+
require_relative "verify_it/delivery/email_delivery"
|
|
15
|
+
require_relative "verify_it/verifier"
|
|
16
|
+
|
|
17
|
+
module VerifyIt
|
|
18
|
+
class Error < StandardError; end
|
|
19
|
+
class ConfigurationError < Error; end
|
|
20
|
+
class StorageError < Error; end
|
|
21
|
+
class DeliveryError < Error; end
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
attr_writer :configuration
|
|
25
|
+
|
|
26
|
+
def configuration
|
|
27
|
+
@configuration ||= Configuration.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def configure
|
|
31
|
+
yield(configuration)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def send_code(to:, record:, channel: nil, context: {})
|
|
35
|
+
verifier.send_code(to: to, record: record, channel: channel, context: context)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def verify_code(to:, code:, record:)
|
|
39
|
+
verifier.verify_code(to: to, code: code, record: record)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def cleanup(to:, record:)
|
|
43
|
+
verifier.cleanup(to: to, record: record)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def reset!
|
|
47
|
+
@configuration = Configuration.new
|
|
48
|
+
@verifier = nil
|
|
49
|
+
@storage = nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def verifier
|
|
55
|
+
@verifier ||= Verifier.new(storage: storage)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def storage
|
|
59
|
+
@storage ||= build_storage
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def build_storage
|
|
63
|
+
case configuration.storage
|
|
64
|
+
when :memory
|
|
65
|
+
Storage::MemoryStorage.new
|
|
66
|
+
when :redis
|
|
67
|
+
unless configuration.redis_client
|
|
68
|
+
raise ConfigurationError, "Redis client must be configured when using :redis storage"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
Storage::RedisStorage.new(configuration.redis_client)
|
|
72
|
+
when :database
|
|
73
|
+
Storage::DatabaseStorage.new
|
|
74
|
+
else
|
|
75
|
+
raise ConfigurationError, "Invalid storage type: #{configuration.storage}"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Load Rails integration if Rails is present
|
|
82
|
+
require_relative "verify_it/railtie" if defined?(Rails)
|
|
Binary file
|
data/sig/verify_it.rbs
ADDED