rails_mfa 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4022b6c7e142e946603768e2ff0066f3a2698c3b20a663fe29305ac670b8fcfc
4
+ data.tar.gz: 077046b6cbb8a8261bfc4f9942df3779b6a1dd9d062853d61abc43224abb8c35
5
+ SHA512:
6
+ metadata.gz: 9d9dd6854a9209dc5cb45390148f7f965d8106b29ad34203c528f277bb84ccb2c5a8d4393811b0579092cf7bdae52803671119c8c90415f5449ac666e401fb22
7
+ data.tar.gz: 751deab61328d9e39d12bccbbd057ab719db5afca3bc8973993bd225574b9a9abd686dd1abed19b650f3fe8e80365992c84909c0831f888922229783cab837a5
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+
4
+ Style/StringLiterals:
5
+ EnforcedStyle: double_quotes
6
+
7
+ Style/StringLiteralsInInterpolation:
8
+ EnforcedStyle: double_quotes
data/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+ - Rails generators for easy installation and setup
12
+ - `rails generate rails_mfa:install` - Creates initializer with configuration examples
13
+ - `rails generate rails_mfa:migration User` - Generates migration for MFA columns
14
+ - Enhanced documentation with provider-agnostic examples
15
+ - Multiple SMS provider examples (Twilio, AWS SNS, Vonage, MessageBird, Plivo)
16
+ - Multiple email provider examples (SendGrid, ActionMailer, Postmark)
17
+ - Complete authenticator app (TOTP) setup guide with QR code generation
18
+ - Dedicated controller examples for authenticator app setup flow
19
+ - Improved README emphasizing provider-agnostic nature
20
+
21
+ ## [0.1.0] - 2025-11-06
22
+
23
+ ### Added
24
+ - Initial release of RailsMFA gem
25
+ - Support for SMS-based multi-factor authentication
26
+ - Support for email-based multi-factor authentication
27
+ - Support for TOTP authenticator apps (Google Authenticator, Authy, 1Password, Microsoft Authenticator)
28
+ - `RailsMFA::Model` concern for easy integration with any user model
29
+ - `RailsMFA::TokenManager` for secure token generation and verification
30
+ - `RailsMFA::Configuration` for flexible gem configuration
31
+ - Pluggable SMS and email delivery providers via lambdas
32
+ - Built-in QR code generation support via `rqrcode` gem
33
+ - Timing-safe token comparison using ActiveSupport::SecurityUtils
34
+ - One-time use tokens with automatic deletion after verification
35
+ - Configurable token expiration (default: 5 minutes)
36
+ - Configurable token length (default: 6 digits)
37
+ - SimpleStore fallback for applications without Rails.cache
38
+ - Provider-agnostic design compatible with Devise, Authlogic, and custom auth systems
39
+ - Comprehensive RSpec test suite (52 examples, 0 failures)
40
+ - Detailed documentation with integration examples
@@ -0,0 +1,132 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ [INSERT CONTACT METHOD].
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series of
86
+ actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or permanent
93
+ ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within the
113
+ community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.1, available at
119
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
+
121
+ Community Impact Guidelines were inspired by
122
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
+
124
+ For answers to common questions about this code of conduct, see the FAQ at
125
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
+ [https://www.contributor-covenant.org/translations][translations].
127
+
128
+ [homepage]: https://www.contributor-covenant.org
129
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
+ [Mozilla CoC]: https://github.com/mozilla/diversity
131
+ [FAQ]: https://www.contributor-covenant.org/faq
132
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Shoaib Malik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,614 @@
1
+ # RailsMFA
2
+
3
+ A pluggable, provider-agnostic multi-factor authentication (MFA/2FA) gem for Ruby on Rails applications. RailsMFA makes it simple to add secure authentication via SMS, email, or authenticator apps (TOTP) to any Rails application, regardless of your authentication system.
4
+
5
+ ## Features
6
+
7
+ - **Multiple Authentication Methods**: Support for SMS, email, and TOTP-based authenticator apps (like Google Authenticator, Authy, 1Password, Microsoft Authenticator)
8
+ - **Fully Provider Agnostic**: Works with ANY SMS provider (Twilio, AWS SNS, Vonage, MessageBird, etc.) and ANY authentication system (Devise, Authlogic, Clearance, or custom)
9
+ - **Pluggable Delivery**: Easy-to-customize SMS and email delivery adapters - bring your own service
10
+ - **Secure by Default**: Uses timing-safe comparison and one-time use tokens
11
+ - **Flexible Storage**: Works with Rails.cache, Redis, or any custom cache store
12
+ - **QR Code Generation**: Built-in support for generating QR codes for authenticator app setup
13
+ - **Rails Generators**: Quick setup with `rails generate` commands
14
+ - **Simple Configuration**: Minimal setup with sensible defaults
15
+
16
+ ## Installation
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ ```ruby
21
+ gem 'rails_mfa'
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ ```bash
27
+ bundle install
28
+ ```
29
+
30
+ Or install it yourself as:
31
+
32
+ ```bash
33
+ gem install rails_mfa
34
+ ```
35
+
36
+ ## Quick Start
37
+
38
+ ### 1. Run the installer
39
+
40
+ ```bash
41
+ rails generate rails_mfa:install
42
+ ```
43
+
44
+ This creates an initializer at `config/initializers/rails_mfa.rb` with configuration options.
45
+
46
+ ### 2. Generate the migration
47
+
48
+ ```bash
49
+ rails generate rails_mfa:migration User
50
+ ```
51
+
52
+ This creates a migration to add MFA columns to your User model (or any model you specify).
53
+
54
+ ### 3. Run the migration
55
+
56
+ ```bash
57
+ rails db:migrate
58
+ ```
59
+
60
+ **Security Note**: The `mfa_secret` column should be encrypted in production. Use Rails 7's `encrypts` feature or `attr_encrypted`:
61
+
62
+ ```ruby
63
+ class User < ApplicationRecord
64
+ encrypts :mfa_secret
65
+ end
66
+ ```
67
+
68
+ ### 4. Include the Model concern in your User model
69
+
70
+ ```ruby
71
+ class User < ApplicationRecord
72
+ include RailsMFA::Model
73
+
74
+ # Optional: specify which MFA methods this model supports
75
+ enable_mfa_for :sms, :email, :totp
76
+ end
77
+ ```
78
+
79
+ ### 5. Configure Your Providers
80
+
81
+ Edit `config/initializers/rails_mfa.rb` and configure your preferred SMS and email providers:
82
+
83
+ ```ruby
84
+ RailsMFA.configure do |config|
85
+ # Use ANY SMS provider - Twilio, AWS SNS, Vonage, MessageBird, etc.
86
+ # Just provide a lambda that sends the SMS
87
+ config.sms_provider = lambda do |phone_number, message|
88
+ # Your SMS provider implementation here
89
+ # Example: YourSmsService.send(phone_number, message)
90
+ end
91
+
92
+ # Use ANY email provider - ActionMailer, SendGrid, Postmark, etc.
93
+ # Just provide a lambda that sends the email
94
+ config.email_provider = lambda do |email, subject, body|
95
+ # Your email provider implementation here
96
+ # Example: YourMailer.send_code(email, subject, body).deliver_now
97
+ end
98
+
99
+ # Optional: customize token settings
100
+ config.code_length = 6 # Default: 6 digits
101
+ config.code_expiry_seconds = 300 # Default: 5 minutes
102
+
103
+ # Optional: use custom cache store (Redis, Memcached, etc.)
104
+ # config.token_store = Redis.new
105
+ end
106
+ ```
107
+
108
+ ## Usage
109
+
110
+ ### Email-based MFA
111
+
112
+ ```ruby
113
+ # Send a verification code
114
+ user = User.find(params[:id])
115
+ code = user.send_numeric_code(via: :email)
116
+
117
+ # Verify the code
118
+ if user.verify_numeric_code(params[:code])
119
+ # Code is valid and user is authenticated
120
+ session[:mfa_verified] = true
121
+ redirect_to dashboard_path
122
+ else
123
+ # Code is invalid
124
+ flash[:error] = "Invalid verification code"
125
+ end
126
+ ```
127
+
128
+ ### SMS-based MFA
129
+
130
+ ```ruby
131
+ # Send a verification code
132
+ code = user.send_numeric_code(via: :sms)
133
+
134
+ # Verify the code (same as email)
135
+ if user.verify_numeric_code(params[:code])
136
+ session[:mfa_verified] = true
137
+ redirect_to dashboard_path
138
+ end
139
+ ```
140
+
141
+ ### Authenticator App (TOTP) - Google Authenticator, Authy, 1Password, Microsoft Authenticator
142
+
143
+ Authenticator apps provide the most secure MFA method using time-based one-time passwords (TOTP).
144
+
145
+ #### Setup Flow
146
+
147
+ ```ruby
148
+ # 1. Generate a secret for the user (do this once during setup)
149
+ user.generate_totp_secret!
150
+
151
+ # 2. Get the provisioning URI for QR code generation
152
+ provisioning_uri = user.totp_provisioning_uri(issuer: "MyApp")
153
+
154
+ # 3. Generate QR code for the user to scan
155
+ require 'rqrcode'
156
+ qrcode = RQRCode::QRCode.new(provisioning_uri)
157
+
158
+ # For HTML view:
159
+ @qr_svg = qrcode.as_svg(
160
+ module_size: 4,
161
+ standalone: true,
162
+ use_path: true
163
+ )
164
+
165
+ # Or for PNG:
166
+ @qr_png = qrcode.as_png(size: 300)
167
+ ```
168
+
169
+ #### Verification
170
+
171
+ ```ruby
172
+ # Verify the TOTP code from the authenticator app
173
+ if user.verify_totp(params[:code])
174
+ user.update(mfa_enabled: true, mfa_method: 'totp')
175
+ session[:mfa_verified] = true
176
+ redirect_to dashboard_path
177
+ else
178
+ flash[:error] = "Invalid authenticator code"
179
+ render :verify
180
+ end
181
+ ```
182
+
183
+ #### Example Controller (Complete Setup Flow)
184
+
185
+ ```ruby
186
+ # app/controllers/mfa/authenticator_controller.rb
187
+ class Mfa::AuthenticatorController < ApplicationController
188
+ before_action :authenticate_user!
189
+
190
+ def new
191
+ # Show setup page
192
+ end
193
+
194
+ def create
195
+ # Generate secret and show QR code
196
+ current_user.generate_totp_secret!
197
+ provisioning_uri = current_user.totp_provisioning_uri(issuer: "MyApp")
198
+ @qrcode = RQRCode::QRCode.new(provisioning_uri)
199
+ end
200
+
201
+ def verify
202
+ # Verify the code from authenticator app
203
+ if current_user.verify_totp(params[:code])
204
+ current_user.update!(mfa_enabled: true, mfa_method: 'totp')
205
+ flash[:success] = "Authenticator app configured successfully!"
206
+ redirect_to profile_path
207
+ else
208
+ flash[:error] = "Invalid code. Please try again."
209
+ redirect_to mfa_authenticator_path
210
+ end
211
+ end
212
+ end
213
+ ```
214
+
215
+ #### Example View (QR Code Display)
216
+
217
+ ```erb
218
+ <!-- app/views/mfa/authenticator/create.html.erb -->
219
+ <div class="authenticator-setup">
220
+ <h2>Set Up Authenticator App</h2>
221
+
222
+ <p>Scan this QR code with your authenticator app:</p>
223
+
224
+ <div class="qr-code">
225
+ <%= @qrcode.as_svg(module_size: 4).html_safe %>
226
+ </div>
227
+
228
+ <p>Or enter this secret key manually:</p>
229
+ <code><%= current_user.mfa_secret %></code>
230
+
231
+ <p>After scanning, enter the 6-digit code from your app to verify:</p>
232
+
233
+ <%= form_with url: verify_mfa_authenticator_path, method: :post do |f| %>
234
+ <%= f.text_field :code, placeholder: "000000", maxlength: 6, autofocus: true %>
235
+ <%= f.submit "Verify and Enable" %>
236
+ <% end %>
237
+ </div>
238
+ ```
239
+
240
+ ## Integration Examples
241
+
242
+ ### With Devise
243
+
244
+ ```ruby
245
+ # app/controllers/users/mfa_controller.rb
246
+ class Users::MfaController < ApplicationController
247
+ before_action :authenticate_user!
248
+
249
+ def show
250
+ # Display MFA setup page
251
+ end
252
+
253
+ def create
254
+ if current_user.verify_numeric_code(params[:code])
255
+ sign_in current_user, bypass: true
256
+ redirect_to root_path
257
+ else
258
+ flash[:alert] = "Invalid code"
259
+ redirect_to users_mfa_path
260
+ end
261
+ end
262
+
263
+ def send_code
264
+ current_user.send_numeric_code(via: params[:method].to_sym)
265
+ flash[:notice] = "Verification code sent"
266
+ redirect_to users_mfa_path
267
+ end
268
+ end
269
+ ```
270
+
271
+ Add routes:
272
+
273
+ ```ruby
274
+ # config/routes.rb
275
+ devise_for :users
276
+ namespace :users do
277
+ resource :mfa, only: [:show, :create] do
278
+ post :send_code
279
+ end
280
+ end
281
+ ```
282
+
283
+ ### With Custom Authentication
284
+
285
+ ```ruby
286
+ # app/controllers/sessions_controller.rb
287
+ class SessionsController < ApplicationController
288
+ def create
289
+ user = User.find_by(email: params[:email])
290
+
291
+ if user&.authenticate(params[:password])
292
+ if user.mfa_enabled?
293
+ # Store user ID in session temporarily
294
+ session[:pending_mfa_user_id] = user.id
295
+ user.send_numeric_code(via: :sms)
296
+ redirect_to mfa_verification_path
297
+ else
298
+ # No MFA required, log them in
299
+ session[:user_id] = user.id
300
+ redirect_to dashboard_path
301
+ end
302
+ else
303
+ flash[:error] = "Invalid credentials"
304
+ render :new
305
+ end
306
+ end
307
+ end
308
+
309
+ # app/controllers/mfa_verifications_controller.rb
310
+ class MfaVerificationsController < ApplicationController
311
+ def show
312
+ # Display MFA verification form
313
+ end
314
+
315
+ def create
316
+ user = User.find(session[:pending_mfa_user_id])
317
+
318
+ if user.verify_numeric_code(params[:code])
319
+ session.delete(:pending_mfa_user_id)
320
+ session[:user_id] = user.id
321
+ redirect_to dashboard_path
322
+ else
323
+ flash[:error] = "Invalid verification code"
324
+ render :show
325
+ end
326
+ end
327
+ end
328
+ ```
329
+
330
+ ## Provider Configuration Examples
331
+
332
+ RailsMFA is **fully provider-agnostic**. You can use any SMS or email service by providing a simple lambda function. Here are examples for popular providers:
333
+
334
+ ### SMS Provider Examples
335
+
336
+ #### Twilio
337
+
338
+ ```ruby
339
+ # config/initializers/rails_mfa.rb
340
+ RailsMFA.configure do |config|
341
+ config.sms_provider = lambda do |to, message|
342
+ require 'twilio-ruby'
343
+
344
+ client = Twilio::REST::Client.new(
345
+ ENV['TWILIO_ACCOUNT_SID'],
346
+ ENV['TWILIO_AUTH_TOKEN']
347
+ )
348
+
349
+ client.messages.create(
350
+ from: ENV['TWILIO_PHONE_NUMBER'],
351
+ to: to,
352
+ body: message
353
+ )
354
+ end
355
+ end
356
+ ```
357
+
358
+ #### AWS SNS
359
+
360
+ ```ruby
361
+ # config/initializers/rails_mfa.rb
362
+ RailsMFA.configure do |config|
363
+ config.sms_provider = lambda do |to, message|
364
+ require 'aws-sdk-sns'
365
+
366
+ sns = Aws::SNS::Client.new(
367
+ region: ENV['AWS_REGION'],
368
+ access_key_id: ENV['AWS_ACCESS_KEY_ID'],
369
+ secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']
370
+ )
371
+
372
+ sns.publish(
373
+ phone_number: to,
374
+ message: message
375
+ )
376
+ end
377
+ end
378
+ ```
379
+
380
+ #### Vonage (Nexmo)
381
+
382
+ ```ruby
383
+ RailsMFA.configure do |config|
384
+ config.sms_provider = lambda do |to, message|
385
+ require 'vonage'
386
+
387
+ client = Vonage::Client.new(
388
+ api_key: ENV['VONAGE_API_KEY'],
389
+ api_secret: ENV['VONAGE_API_SECRET']
390
+ )
391
+
392
+ client.sms.send(
393
+ from: ENV['VONAGE_PHONE_NUMBER'],
394
+ to: to,
395
+ text: message
396
+ )
397
+ end
398
+ end
399
+ ```
400
+
401
+ #### MessageBird
402
+
403
+ ```ruby
404
+ RailsMFA.configure do |config|
405
+ config.sms_provider = lambda do |to, message|
406
+ require 'messagebird'
407
+
408
+ client = MessageBird::Client.new(ENV['MESSAGEBIRD_API_KEY'])
409
+
410
+ client.message_create(
411
+ ENV['MESSAGEBIRD_PHONE_NUMBER'],
412
+ to,
413
+ message
414
+ )
415
+ end
416
+ end
417
+ ```
418
+
419
+ #### Plivo
420
+
421
+ ```ruby
422
+ RailsMFA.configure do |config|
423
+ config.sms_provider = lambda do |to, message|
424
+ require 'plivo'
425
+
426
+ client = Plivo::RestClient.new(
427
+ ENV['PLIVO_AUTH_ID'],
428
+ ENV['PLIVO_AUTH_TOKEN']
429
+ )
430
+
431
+ client.messages.create(
432
+ src: ENV['PLIVO_PHONE_NUMBER'],
433
+ dst: to,
434
+ text: message
435
+ )
436
+ end
437
+ end
438
+ ```
439
+
440
+ ### Email Provider Examples
441
+
442
+ #### SendGrid
443
+
444
+ ```ruby
445
+ # config/initializers/rails_mfa.rb
446
+ RailsMFA.configure do |config|
447
+ config.email_provider = lambda do |to, subject, body|
448
+ require 'sendgrid-ruby'
449
+ include SendGrid
450
+
451
+ from = Email.new(email: 'noreply@example.com')
452
+ to = Email.new(email: to)
453
+ content = Content.new(type: 'text/plain', value: body)
454
+ mail = Mail.new(from, subject, to, content)
455
+
456
+ sg = SendGrid::API.new(api_key: ENV['SENDGRID_API_KEY'])
457
+ sg.client.mail._('send').post(request_body: mail.to_json)
458
+ end
459
+ end
460
+ ```
461
+
462
+ ### Custom ActionMailer Example
463
+
464
+ ```ruby
465
+ # app/mailers/mfa_mailer.rb
466
+ class MfaMailer < ApplicationMailer
467
+ def send_code(to, subject, body)
468
+ @code = body
469
+ mail(to: to, subject: subject)
470
+ end
471
+ end
472
+
473
+ # config/initializers/rails_mfa.rb
474
+ RailsMFA.configure do |config|
475
+ config.email_provider = lambda do |to, subject, body|
476
+ MfaMailer.send_code(to, subject, body).deliver_now
477
+ end
478
+ end
479
+ ```
480
+
481
+ ## Configuration Options
482
+
483
+ ```ruby
484
+ RailsMFA.configure do |config|
485
+ # SMS provider (required for SMS-based MFA)
486
+ # Lambda that takes (phone_number, message) as arguments
487
+ config.sms_provider = ->(to, message) { ... }
488
+
489
+ # Email provider (required for email-based MFA)
490
+ # Lambda that takes (email, subject, body) as arguments
491
+ config.email_provider = ->(to, subject, body) { ... }
492
+
493
+ # Length of numeric codes (default: 6)
494
+ config.code_length = 6
495
+
496
+ # Code expiration time in seconds (default: 300 = 5 minutes)
497
+ config.code_expiry_seconds = 300
498
+
499
+ # Token storage backend (default: Rails.cache or SimpleStore)
500
+ config.token_store = Redis.new
501
+ end
502
+ ```
503
+
504
+ ## Testing
505
+
506
+ RailsMFA uses RSpec for testing. To run the test suite:
507
+
508
+ ```bash
509
+ bundle exec rspec
510
+ ```
511
+
512
+ ### Testing in Your Application
513
+
514
+ You can stub the providers in your tests:
515
+
516
+ ```ruby
517
+ RSpec.describe "MFA", type: :request do
518
+ before do
519
+ RailsMFA.configure do |config|
520
+ config.sms_provider = ->(to, message) { "SMS sent" }
521
+ config.email_provider = ->(to, subject, body) { "Email sent" }
522
+ end
523
+ end
524
+
525
+ it "sends verification code" do
526
+ user = create(:user)
527
+ post send_code_path, params: { method: 'sms' }
528
+
529
+ expect(response).to have_http_status(:success)
530
+ end
531
+ end
532
+ ```
533
+
534
+ ## Security Considerations
535
+
536
+ 1. **Encrypt MFA Secrets**: Always encrypt the `mfa_secret` column in your database using Rails' built-in encryption or a gem like `attr_encrypted`.
537
+
538
+ 2. **HTTPS Only**: Always use HTTPS in production to prevent code interception.
539
+
540
+ 3. **Rate Limiting**: Implement rate limiting on MFA endpoints to prevent brute-force attacks:
541
+
542
+ ```ruby
543
+ # Use rack-attack or similar
544
+ throttle('mfa/verify', limit: 5, period: 5.minutes) do |req|
545
+ req.ip if req.path == '/mfa/verify' && req.post?
546
+ end
547
+ ```
548
+
549
+ 4. **Secure Storage**: Use secure session storage (encrypted cookies or server-side sessions).
550
+
551
+ 5. **Backup Codes**: Consider implementing backup codes for account recovery.
552
+
553
+ ## Advanced Usage
554
+
555
+ ### Using with Redis
556
+
557
+ ```ruby
558
+ # config/initializers/rails_mfa.rb
559
+ RailsMFA.configure do |config|
560
+ config.token_store = Redis.new(
561
+ host: ENV['REDIS_HOST'],
562
+ port: ENV['REDIS_PORT'],
563
+ db: 1
564
+ )
565
+ end
566
+ ```
567
+
568
+ ### Custom Token Length and Expiration
569
+
570
+ ```ruby
571
+ # Generate an 8-digit code that expires in 10 minutes
572
+ user.send_numeric_code(via: :email)
573
+
574
+ # Configure globally
575
+ RailsMFA.configure do |config|
576
+ config.code_length = 8
577
+ config.code_expiry_seconds = 600
578
+ end
579
+ ```
580
+
581
+ ### Checking TOTP Status
582
+
583
+ ```ruby
584
+ # Check if user has TOTP set up
585
+ if user.mfa_secret.present? && user.mfa_enabled?
586
+ # User has TOTP configured
587
+ end
588
+ ```
589
+
590
+ ## Contributing
591
+
592
+ Bug reports and pull requests are welcome on GitHub at https://github.com/shoaibmalik786/rails_mfa.
593
+
594
+ 1. Fork it
595
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
596
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
597
+ 4. Push to the branch (`git push origin my-new-feature`)
598
+ 5. Create new Pull Request
599
+
600
+ ## Development
601
+
602
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
603
+
604
+ ## License
605
+
606
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
607
+
608
+ ## Credits
609
+
610
+ Created by [Shoaib Malik](https://github.com/shoaibmalik786)
611
+
612
+ ## Support
613
+
614
+ If you have any questions or need help integrating RailsMFA, please open an issue on GitHub.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMFA
4
+ VERSION = "0.1.0"
5
+ end
data/lib/rails_mfa.rb ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rails_mfa/version"
4
+ require_relative "rails_mfa/configuration"
5
+ require_relative "rails_mfa/token_manager"
6
+ require_relative "rails_mfa/model"
7
+ require_relative "rails_mfa/providers/base"
8
+ require_relative "rails_mfa/providers/sms_provider"
9
+ require_relative "rails_mfa/providers/email_provider"
10
+
11
+ module RailsMFA
12
+ class Error < StandardError; end
13
+
14
+ class << self
15
+ attr_accessor :configuration
16
+
17
+ def configure
18
+ self.configuration ||= Configuration.new
19
+ yield(configuration) if block_given?
20
+ end
21
+ end
22
+ end
data/sig/rails_mfa.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module RailsMfa
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_mfa
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Shoaib Malik
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-11-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rotp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '6.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '6.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rqrcode
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.2'
55
+ description: RailsMFA is a provider-agnostic, plug-and-play gem that adds secure multi-factor
56
+ authentication (MFA/2FA) to any Rails app. Supports SMS, email, and authenticator
57
+ apps (TOTP) with customizable providers.
58
+ email:
59
+ - shoaib2109@gmail.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ".rspec"
65
+ - ".rubocop.yml"
66
+ - CHANGELOG.md
67
+ - CODE_OF_CONDUCT.md
68
+ - LICENSE.txt
69
+ - README.md
70
+ - Rakefile
71
+ - lib/rails_mfa.rb
72
+ - lib/rails_mfa/version.rb
73
+ - sig/rails_mfa.rbs
74
+ homepage: https://github.com/shoaibmalik786/rails_mfa
75
+ licenses:
76
+ - MIT
77
+ metadata:
78
+ allowed_push_host: https://rubygems.org
79
+ homepage_uri: https://github.com/shoaibmalik786/rails_mfa
80
+ source_code_uri: https://github.com/shoaibmalik786/rails_mfa
81
+ changelog_uri: https://github.com/shoaibmalik786/rails_mfa/blob/main/CHANGELOG.md
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 3.1.0
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubygems_version: 3.4.10
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Add multi-factor authentication (2FA/MFA) to any Rails app with pluggable
101
+ SMS, Email, and TOTP support.
102
+ test_files: []