rails_mfa 0.1.0 → 0.1.1
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 +4 -4
- data/.rubocop.yml +47 -0
- data/CHANGELOG.md +10 -2
- data/lib/generators/rails_mfa/install_generator.rb +21 -0
- data/lib/generators/rails_mfa/migration_generator.rb +53 -0
- data/lib/generators/rails_mfa/templates/README +34 -0
- data/lib/generators/rails_mfa/templates/migration.rb.erb +12 -0
- data/lib/generators/rails_mfa/templates/rails_mfa.rb +80 -0
- data/lib/rails_mfa/configuration.rb +42 -0
- data/lib/rails_mfa/model.rb +68 -0
- data/lib/rails_mfa/providers/base.rb +15 -0
- data/lib/rails_mfa/providers/email_provider.rb +15 -0
- data/lib/rails_mfa/providers/sms_provider.rb +16 -0
- data/lib/rails_mfa/token_manager.rb +36 -0
- data/lib/rails_mfa/version.rb +1 -1
- metadata +12 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0763347276b010decd3b0ffcd1bf959b8752dcbdabbbebe09c24298bcd3816f7
|
|
4
|
+
data.tar.gz: 3d2f11cfd6fd166c9ff1ee0f3ed191041548563c1fc121b665132736e01203e8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 50f1097add2c856ca93ac36c59bf02177b103ac3312e277373b4c485ef8f34865a54400f0e117af00ad45bb408cac7708add5d1605ff0990a9d71334ee42886a
|
|
7
|
+
data.tar.gz: 8eb7044c13c90741b4a99447adeea254d549586cb78a74cd4c02880bfbb2db887838de4e422c038701b3c7cc86b8b0c6b046b9a7230f486910afe8915f44899f
|
data/.rubocop.yml
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
AllCops:
|
|
2
|
+
NewCops: enable
|
|
2
3
|
TargetRubyVersion: 3.1
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
Exclude:
|
|
6
|
+
- 'bin/**/*'
|
|
7
|
+
- 'vendor/**/*'
|
|
8
|
+
- 'tmp/**/*'
|
|
9
|
+
|
|
10
|
+
# Spec files naturally have longer blocks for describing test scenarios
|
|
11
|
+
# Gemspec naturally has a long block
|
|
12
|
+
Metrics/BlockLength:
|
|
13
|
+
Exclude:
|
|
14
|
+
- 'spec/**/*_spec.rb'
|
|
15
|
+
- 'spec/spec_helper.rb'
|
|
16
|
+
- '*.gemspec'
|
|
17
|
+
|
|
18
|
+
# Gemspec can have longer lines for descriptions
|
|
19
|
+
Layout/LineLength:
|
|
20
|
+
Max: 120
|
|
21
|
+
Exclude:
|
|
22
|
+
- '*.gemspec'
|
|
23
|
+
- 'Gemfile'
|
|
24
|
+
|
|
25
|
+
# Allow longer method names in specs and model methods that handle multiple cases
|
|
26
|
+
Metrics/MethodLength:
|
|
27
|
+
Exclude:
|
|
28
|
+
- 'spec/**/*'
|
|
29
|
+
- 'lib/rails_mfa/model.rb'
|
|
30
|
+
|
|
31
|
+
# Allow more complex code in specs and model methods
|
|
32
|
+
Metrics/AbcSize:
|
|
33
|
+
Exclude:
|
|
34
|
+
- 'spec/**/*'
|
|
35
|
+
- 'lib/rails_mfa/model.rb'
|
|
36
|
+
|
|
37
|
+
# Documentation can be added later - focus on functionality first
|
|
38
|
+
Style/Documentation:
|
|
39
|
+
Enabled: false
|
|
40
|
+
|
|
41
|
+
# Empty blocks in specs are intentional for testing edge cases
|
|
42
|
+
Lint/EmptyBlock:
|
|
43
|
+
Exclude:
|
|
44
|
+
- 'spec/**/*'
|
|
45
|
+
|
|
46
|
+
# Provider initialize methods don't need super
|
|
47
|
+
Lint/MissingSuper:
|
|
48
|
+
Exclude:
|
|
49
|
+
- 'lib/rails_mfa/providers/*.rb'
|
|
3
50
|
|
|
4
51
|
Style/StringLiterals:
|
|
5
52
|
EnforcedStyle: double_quotes
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.1] - 2025-11-10
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- RuboCop linting offenses resolved
|
|
14
|
+
- Added RubyGems MFA requirement metadata for enhanced security
|
|
15
|
+
- Fixed unused block arguments in test files
|
|
16
|
+
- Improved code formatting and readability
|
|
17
|
+
|
|
18
|
+
## [0.1.0] - 2025-11-06
|
|
19
|
+
|
|
10
20
|
### Added
|
|
11
21
|
- Rails generators for easy installation and setup
|
|
12
22
|
- `rails generate rails_mfa:install` - Creates initializer with configuration examples
|
|
@@ -18,8 +28,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
18
28
|
- Dedicated controller examples for authenticator app setup flow
|
|
19
29
|
- Improved README emphasizing provider-agnostic nature
|
|
20
30
|
|
|
21
|
-
## [0.1.0] - 2025-11-06
|
|
22
|
-
|
|
23
31
|
### Added
|
|
24
32
|
- Initial release of RailsMFA gem
|
|
25
33
|
- Support for SMS-based multi-factor authentication
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
|
|
5
|
+
module RailsMfa
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Creates a RailsMFA initializer in your application."
|
|
11
|
+
|
|
12
|
+
def copy_initializer
|
|
13
|
+
template "rails_mfa.rb", "config/initializers/rails_mfa.rb"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def show_readme
|
|
17
|
+
readme "README" if behavior == :invoke
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
|
|
6
|
+
module RailsMfa
|
|
7
|
+
module Generators
|
|
8
|
+
class MigrationGenerator < Rails::Generators::NamedBase
|
|
9
|
+
include Rails::Generators::Migration
|
|
10
|
+
|
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
|
12
|
+
|
|
13
|
+
desc "Generates a migration to add MFA columns to a model"
|
|
14
|
+
|
|
15
|
+
argument :name, type: :string, default: "User",
|
|
16
|
+
desc: "The name of the model to add MFA columns to (e.g., User, Account)"
|
|
17
|
+
|
|
18
|
+
def self.next_migration_number(dirname)
|
|
19
|
+
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def create_migration_file
|
|
23
|
+
migration_template(
|
|
24
|
+
"migration.rb.erb",
|
|
25
|
+
"db/migrate/add_mfa_to_#{table_name}.rb"
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def show_instructions
|
|
30
|
+
return unless behavior == :invoke
|
|
31
|
+
|
|
32
|
+
say ""
|
|
33
|
+
say "Migration created!", :green
|
|
34
|
+
say ""
|
|
35
|
+
say "Next steps:", :yellow
|
|
36
|
+
say " 1. Review the migration file"
|
|
37
|
+
say " 2. Run: rails db:migrate"
|
|
38
|
+
say " 3. Include RailsMFA::Model in your #{class_name} model"
|
|
39
|
+
say ""
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def table_name
|
|
45
|
+
name.tableize
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def class_name
|
|
49
|
+
name.classify
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
===============================================================================
|
|
2
|
+
|
|
3
|
+
RailsMFA has been installed!
|
|
4
|
+
|
|
5
|
+
Next steps:
|
|
6
|
+
|
|
7
|
+
1. Add MFA columns to your User model:
|
|
8
|
+
|
|
9
|
+
rails generate rails_mfa:migration User
|
|
10
|
+
|
|
11
|
+
2. Run the migration:
|
|
12
|
+
|
|
13
|
+
rails db:migrate
|
|
14
|
+
|
|
15
|
+
3. Configure your SMS and Email providers in:
|
|
16
|
+
|
|
17
|
+
config/initializers/rails_mfa.rb
|
|
18
|
+
|
|
19
|
+
4. Include RailsMFA::Model in your User model:
|
|
20
|
+
|
|
21
|
+
# app/models/user.rb
|
|
22
|
+
class User < ApplicationRecord
|
|
23
|
+
include RailsMFA::Model
|
|
24
|
+
|
|
25
|
+
# Optionally specify which MFA methods are supported
|
|
26
|
+
enable_mfa_for :sms, :email, :totp
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
5. Add routes and controllers for MFA verification (see documentation)
|
|
30
|
+
|
|
31
|
+
For detailed usage instructions, visit:
|
|
32
|
+
https://github.com/shoaibmalik786/rails_mfa
|
|
33
|
+
|
|
34
|
+
===============================================================================
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddMfaTo<%= table_name.camelize %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
4
|
+
def change
|
|
5
|
+
add_column :<%= table_name %>, :mfa_secret, :string
|
|
6
|
+
add_column :<%= table_name %>, :mfa_enabled, :boolean, default: false, null: false
|
|
7
|
+
add_column :<%= table_name %>, :phone, :string
|
|
8
|
+
add_column :<%= table_name %>, :mfa_method, :string
|
|
9
|
+
|
|
10
|
+
add_index :<%= table_name %>, :mfa_enabled
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RailsMFA.configure do |config|
|
|
4
|
+
# ============================================================================
|
|
5
|
+
# SMS Provider Configuration
|
|
6
|
+
# ============================================================================
|
|
7
|
+
# Define your SMS provider here. This is a lambda that receives (phone_number, message)
|
|
8
|
+
# and sends the SMS. You can use any SMS service (Twilio, AWS SNS, Vonage, etc.)
|
|
9
|
+
#
|
|
10
|
+
# Example with Twilio:
|
|
11
|
+
# config.sms_provider = lambda do |to, message|
|
|
12
|
+
# twilio_client = Twilio::REST::Client.new(
|
|
13
|
+
# ENV['TWILIO_ACCOUNT_SID'],
|
|
14
|
+
# ENV['TWILIO_AUTH_TOKEN']
|
|
15
|
+
# )
|
|
16
|
+
# twilio_client.messages.create(
|
|
17
|
+
# from: ENV['TWILIO_PHONE_NUMBER'],
|
|
18
|
+
# to: to,
|
|
19
|
+
# body: message
|
|
20
|
+
# )
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# Example with AWS SNS:
|
|
24
|
+
# config.sms_provider = lambda do |to, message|
|
|
25
|
+
# sns = Aws::SNS::Client.new(region: ENV['AWS_REGION'])
|
|
26
|
+
# sns.publish(phone_number: to, message: message)
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
config.sms_provider = lambda do |to, message|
|
|
30
|
+
# TODO: Implement your SMS provider here
|
|
31
|
+
Rails.logger.info "SMS to #{to}: #{message}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# ============================================================================
|
|
35
|
+
# Email Provider Configuration
|
|
36
|
+
# ============================================================================
|
|
37
|
+
# Define your email provider here. This is a lambda that receives (email, subject, body)
|
|
38
|
+
# and sends the email. You can use ActionMailer or any email service.
|
|
39
|
+
#
|
|
40
|
+
# Example with ActionMailer:
|
|
41
|
+
# config.email_provider = lambda do |to, subject, body|
|
|
42
|
+
# MfaMailer.send_code(to, subject, body).deliver_now
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# Example with SendGrid:
|
|
46
|
+
# config.email_provider = lambda do |to, subject, body|
|
|
47
|
+
# mail = SendGrid::Mail.new(
|
|
48
|
+
# from: SendGrid::Email.new(email: 'noreply@example.com'),
|
|
49
|
+
# subject: subject,
|
|
50
|
+
# to: SendGrid::Email.new(email: to),
|
|
51
|
+
# content: SendGrid::Content.new(type: 'text/plain', value: body)
|
|
52
|
+
# )
|
|
53
|
+
# sg = SendGrid::API.new(api_key: ENV['SENDGRID_API_KEY'])
|
|
54
|
+
# sg.client.mail._('send').post(request_body: mail.to_json)
|
|
55
|
+
# end
|
|
56
|
+
#
|
|
57
|
+
config.email_provider = lambda do |to, subject, body|
|
|
58
|
+
# TODO: Implement your email provider here
|
|
59
|
+
Rails.logger.info "Email to #{to}: #{subject} - #{body}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# ============================================================================
|
|
63
|
+
# Token Configuration
|
|
64
|
+
# ============================================================================
|
|
65
|
+
# Length of the numeric verification code (default: 6)
|
|
66
|
+
config.code_length = 6
|
|
67
|
+
|
|
68
|
+
# How long the verification code is valid in seconds (default: 300 = 5 minutes)
|
|
69
|
+
config.code_expiry_seconds = 300
|
|
70
|
+
|
|
71
|
+
# ============================================================================
|
|
72
|
+
# Cache Store Configuration
|
|
73
|
+
# ============================================================================
|
|
74
|
+
# Token storage backend (default: Rails.cache)
|
|
75
|
+
# You can use Redis for better performance:
|
|
76
|
+
# config.token_store = Redis.new(host: ENV['REDIS_HOST'], port: ENV['REDIS_PORT'], db: 1)
|
|
77
|
+
#
|
|
78
|
+
# Or use the default Rails cache:
|
|
79
|
+
# config.token_store = Rails.cache
|
|
80
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsMFA
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :sms_provider, :email_provider, :token_store, :code_expiry_seconds, :code_length
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
# These lambdas are defined by the host app
|
|
9
|
+
@sms_provider = nil # -> lambda: ->(to, message) { ... }
|
|
10
|
+
@email_provider = nil # -> lambda: ->(to, subject, body) { ... }
|
|
11
|
+
|
|
12
|
+
# Use Rails.cache by default if Rails is loaded
|
|
13
|
+
@token_store = defined?(Rails) && Rails.respond_to?(:cache) ? Rails.cache : SimpleStore.new
|
|
14
|
+
|
|
15
|
+
@code_expiry_seconds = 300 # 5 minutes
|
|
16
|
+
@code_length = 6
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Fallback in case Rails.cache is unavailable (for plain Ruby apps)
|
|
21
|
+
class SimpleStore
|
|
22
|
+
def initialize
|
|
23
|
+
@store = {}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def write(key, value, expires_in: nil)
|
|
27
|
+
@store[key] = { value: value, expires_at: expires_in ? Time.now + expires_in : nil }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def read(key)
|
|
31
|
+
entry = @store[key]
|
|
32
|
+
return nil unless entry
|
|
33
|
+
return nil if entry[:expires_at] && Time.now > entry[:expires_at]
|
|
34
|
+
|
|
35
|
+
entry[:value]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def delete(key)
|
|
39
|
+
@store.delete(key)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
require "rotp"
|
|
5
|
+
require "rqrcode"
|
|
6
|
+
|
|
7
|
+
module RailsMFA
|
|
8
|
+
module Model
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
class_methods do
|
|
12
|
+
def enable_mfa_for(*methods)
|
|
13
|
+
class_attribute :rails_mfa_methods, instance_accessor: false
|
|
14
|
+
self.rails_mfa_methods = methods.map(&:to_sym)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def generate_totp_secret!
|
|
19
|
+
secret = ROTP::Base32.random_base32
|
|
20
|
+
# host app should store secret encrypted in a column like :mfa_secret
|
|
21
|
+
update!(mfa_secret: secret) if respond_to?(:update!)
|
|
22
|
+
secret
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def totp_provisioning_uri(issuer: "RailsMFA")
|
|
26
|
+
raise "No mfa_secret present" unless respond_to?(:mfa_secret) && mfa_secret
|
|
27
|
+
|
|
28
|
+
ROTP::TOTP.new(mfa_secret, issuer: issuer).provisioning_uri(respond_to?(:email) ? email : "user")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def verify_totp(code)
|
|
32
|
+
return false unless respond_to?(:mfa_secret) && mfa_secret
|
|
33
|
+
|
|
34
|
+
totp = ROTP::TOTP.new(mfa_secret)
|
|
35
|
+
totp.verify(code, drift_behind: 30)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def send_numeric_code(via: :sms)
|
|
39
|
+
tm = TokenManager.new
|
|
40
|
+
code = tm.generate_numeric_code(id)
|
|
41
|
+
case via.to_sym
|
|
42
|
+
when :sms
|
|
43
|
+
raise "sms_provider not configured" unless RailsMFA.configuration.sms_provider
|
|
44
|
+
|
|
45
|
+
RailsMFA.configuration.sms_provider.call(phone_number_for_sms, "Your verification code is: #{code}")
|
|
46
|
+
when :email
|
|
47
|
+
raise "email_provider not configured" unless RailsMFA.configuration.email_provider
|
|
48
|
+
|
|
49
|
+
RailsMFA.configuration.email_provider.call(email, "Your verification code", "Code: #{code}")
|
|
50
|
+
else
|
|
51
|
+
raise "Unsupported channel"
|
|
52
|
+
end
|
|
53
|
+
code
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def verify_numeric_code(code)
|
|
57
|
+
tm = TokenManager.new
|
|
58
|
+
tm.verify_numeric_code(id, code)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def phone_number_for_sms
|
|
64
|
+
# host app should implement proper phone number attribute
|
|
65
|
+
respond_to?(:phone) ? phone : raise("Define phone attribute or override phone_number_for_sms")
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsMFA
|
|
4
|
+
module Providers
|
|
5
|
+
class Base
|
|
6
|
+
def send_sms(to, message)
|
|
7
|
+
raise NotImplementedError, "Implement send_sms in provider"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def send_email(to, subject, body)
|
|
11
|
+
raise NotImplementedError, "Implement send_email in provider"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsMFA
|
|
4
|
+
module Providers
|
|
5
|
+
# Example: host app can create a provider class that implements send_sms
|
|
6
|
+
class SmsProvider < Base
|
|
7
|
+
def initialize(&block)
|
|
8
|
+
@block = block
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def send_sms(to, message)
|
|
12
|
+
@block.call(to, message)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "active_support/security_utils"
|
|
5
|
+
|
|
6
|
+
module RailsMFA
|
|
7
|
+
class TokenManager
|
|
8
|
+
def initialize(store: RailsMFA.configuration.token_store)
|
|
9
|
+
@store = store
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def generate_numeric_code(user_id, length: RailsMFA.configuration.code_length,
|
|
13
|
+
expiry: RailsMFA.configuration.code_expiry_seconds)
|
|
14
|
+
min = 10**(length - 1)
|
|
15
|
+
max = (10**length) - 1
|
|
16
|
+
code = rand(min..max).to_s
|
|
17
|
+
@store.write(cache_key(user_id), code, expires_in: expiry)
|
|
18
|
+
code
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def verify_numeric_code(user_id, code)
|
|
22
|
+
stored = @store.read(cache_key(user_id))
|
|
23
|
+
return false unless stored
|
|
24
|
+
|
|
25
|
+
valid = ActiveSupport::SecurityUtils.secure_compare(stored.to_s, code.to_s)
|
|
26
|
+
@store.delete(cache_key(user_id)) if valid # one-time use
|
|
27
|
+
valid
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def cache_key(user_id)
|
|
33
|
+
"rails_mfa:otp:#{user_id}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/rails_mfa/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_mfa
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shoaib Malik
|
|
@@ -68,7 +68,18 @@ files:
|
|
|
68
68
|
- LICENSE.txt
|
|
69
69
|
- README.md
|
|
70
70
|
- Rakefile
|
|
71
|
+
- lib/generators/rails_mfa/install_generator.rb
|
|
72
|
+
- lib/generators/rails_mfa/migration_generator.rb
|
|
73
|
+
- lib/generators/rails_mfa/templates/README
|
|
74
|
+
- lib/generators/rails_mfa/templates/migration.rb.erb
|
|
75
|
+
- lib/generators/rails_mfa/templates/rails_mfa.rb
|
|
71
76
|
- lib/rails_mfa.rb
|
|
77
|
+
- lib/rails_mfa/configuration.rb
|
|
78
|
+
- lib/rails_mfa/model.rb
|
|
79
|
+
- lib/rails_mfa/providers/base.rb
|
|
80
|
+
- lib/rails_mfa/providers/email_provider.rb
|
|
81
|
+
- lib/rails_mfa/providers/sms_provider.rb
|
|
82
|
+
- lib/rails_mfa/token_manager.rb
|
|
72
83
|
- lib/rails_mfa/version.rb
|
|
73
84
|
- sig/rails_mfa.rbs
|
|
74
85
|
homepage: https://github.com/shoaibmalik786/rails_mfa
|