Skip to content

Commit 2e91eb7

Browse files
feat: add TwoFactor base Warden strategy
Provide a base strategy that handles shared 2FA boilerplate: finding the pending resource from session, calling verify_two_factor!, restoring remember_me, and cleaning up session state on success. Extensions subclass and implement valid? + verify_two_factor!. The base strategy returns valid? false to prevent accidental use.
1 parent fb218ce commit 2e91eb7

2 files changed

Lines changed: 50 additions & 0 deletions

File tree

config/locales/en.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ en:
1616
timeout: "Your session expired. Please sign in again to continue."
1717
unauthenticated: "You need to sign in or sign up before continuing."
1818
unconfirmed: "You have to confirm your email address before continuing."
19+
two_factor_session_expired: "Your two-factor authentication session has expired. Please sign in again."
1920
mailer:
2021
confirmation_instructions:
2122
subject: "Confirmation instructions"
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# frozen_string_literal: true
2+
3+
require 'devise/strategies/base'
4+
5+
module Devise
6+
module Strategies
7+
class TwoFactor < Base
8+
def valid?
9+
session[:devise_two_factor_resource_id].present?
10+
end
11+
12+
def authenticate!
13+
resource = find_pending_resource
14+
return fail!(:two_factor_session_expired) unless resource
15+
16+
verify_two_factor!(resource)
17+
18+
unless halted?
19+
restore_remember_me(resource)
20+
resource.after_database_authentication
21+
cleanup_two_factor_session!
22+
success!(resource)
23+
end
24+
end
25+
26+
# Extensions must override. Should call fail! with a specific
27+
# message on failure — this halts execution and triggers recall.
28+
def verify_two_factor!(resource)
29+
raise NotImplementedError
30+
end
31+
32+
private
33+
34+
def find_pending_resource
35+
return unless session[:devise_two_factor_resource_id]
36+
mapping.to.where(id: session[:devise_two_factor_resource_id]).first
37+
end
38+
39+
def restore_remember_me(resource)
40+
resource.remember_me = session[:devise_two_factor_remember_me] if resource.respond_to?(:remember_me=)
41+
end
42+
43+
def cleanup_two_factor_session!
44+
session.delete(:devise_two_factor_resource_id)
45+
session.delete(:devise_two_factor_remember_me)
46+
end
47+
end
48+
end
49+
end

0 commit comments

Comments
 (0)