File: two_factor_base.rb

package info (click to toggle)
ruby-rodauth 2.42.0-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 812 kB
  • sloc: ruby: 7,524; javascript: 100; makefile: 4
file content (267 lines) | stat: -rw-r--r-- 8,496 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# frozen-string-literal: true

module Rodauth
  Feature.define(:two_factor_base, :TwoFactorBase) do
    loaded_templates %w'two-factor-manage two-factor-auth two-factor-disable'

    view 'two-factor-manage', 'Manage Multifactor Authentication', 'two_factor_manage'
    view 'two-factor-auth', 'Authenticate Using Additional Factor', 'two_factor_auth'
    view 'two-factor-disable', 'Remove All Multifactor Authentication Methods', 'two_factor_disable'

    before :two_factor_disable

    after :two_factor_authentication
    after :two_factor_disable

    additional_form_tags :two_factor_disable

    button "Remove All Multifactor Authentication Methods", :two_factor_disable

    redirect(:two_factor_auth)
    redirect(:two_factor_already_authenticated)
    redirect(:two_factor_disable)
    redirect(:two_factor_need_setup){two_factor_manage_path}
    redirect(:two_factor_auth_required){two_factor_auth_path}

    response :two_factor_disable

    notice_flash "You have been multifactor authenticated", "two_factor_auth"
    notice_flash "All multifactor authentication methods have been disabled", "two_factor_disable"

    error_flash "This account has not been setup for multifactor authentication", 'two_factor_not_setup'
    error_flash "You have already been multifactor authenticated", 'two_factor_already_authenticated'
    error_flash "You need to authenticate via an additional factor before continuing", 'two_factor_need_authentication'
    error_flash "Unable to remove all multifactor authentication methods", "two_factor_disable"

    auth_value_method :two_factor_already_authenticated_error_status, 403
    auth_value_method :two_factor_need_authentication_error_status, 401
    auth_value_method :two_factor_not_setup_error_status, 403

    session_key :two_factor_setup_session_key, :two_factor_auth_setup
    session_key :two_factor_auth_redirect_session_key, :two_factor_auth_redirect

    translatable_method :two_factor_setup_heading, "<h2>Setup Multifactor Authentication</h2>"
    translatable_method :two_factor_remove_heading, "<h2>Remove Multifactor Authentication</h2>"
    translatable_method :two_factor_disable_link_text, "Remove All Multifactor Authentication Methods"
    auth_value_method :two_factor_auth_return_to_requested_location?, false

    auth_value_methods :two_factor_modifications_require_password?

    auth_methods(
      :two_factor_authenticated?,
      :two_factor_remove,
      :two_factor_remove_auth_failures,
      :two_factor_remove_session,
      :two_factor_update_session
    )

    auth_private_methods(
      :two_factor_auth_links,
      :two_factor_auth_response,
      :two_factor_setup_links,
      :two_factor_remove_links
    )

    internal_request_method :two_factor_disable

    route(:two_factor_manage, 'multifactor-manage') do |r|
      require_account
      before_two_factor_manage_route

      r.get do
        all_links = two_factor_setup_links + two_factor_remove_links
        if all_links.length == 1
          redirect all_links[0][1]
        end
        two_factor_manage_view
      end
    end

    route(:two_factor_auth, 'multifactor-auth') do |r|
      require_login
      require_account_session
      require_two_factor_setup
      require_two_factor_not_authenticated
      before_two_factor_auth_route

      r.get do
        if two_factor_auth_links.length == 1
          redirect two_factor_auth_links[0][1]
        end
        two_factor_auth_view
      end
    end

    route(:two_factor_disable, 'multifactor-disable') do |r|
      require_account
      require_two_factor_setup
      before_two_factor_disable_route

      r.get do
        two_factor_disable_view
      end

      r.post do
        if two_factor_password_match?(param(password_param))
          transaction do
            before_two_factor_disable
            two_factor_remove
            _two_factor_remove_all_from_session
            after_two_factor_disable
          end
          two_factor_disable_response
        end

        set_response_error_reason_status(:invalid_password, invalid_password_error_status)
        set_field_error(password_param, invalid_password_message)
        set_error_flash two_factor_disable_error_flash
        two_factor_disable_view
      end
    end

    def two_factor_modifications_require_password?
      modifications_require_password?
    end

    def authenticated?
      super && !two_factor_partially_authenticated?
    end

    def require_authentication
      super
      require_two_factor_authenticated if two_factor_partially_authenticated?
    end

    def require_two_factor_setup
      # Avoid database query if already authenticated via 2nd factor
      return if two_factor_authenticated?

      return if uses_two_factor_authentication?

      set_redirect_error_status(two_factor_not_setup_error_status)
      set_error_reason :two_factor_not_setup
      set_redirect_error_flash two_factor_not_setup_error_flash
      redirect two_factor_need_setup_redirect
    end
    
    def require_two_factor_not_authenticated(auth_type = nil)
      if two_factor_authenticated? || (auth_type && two_factor_login_type_match?(auth_type))
        set_redirect_error_status(two_factor_already_authenticated_error_status)
        set_error_reason :two_factor_already_authenticated
        set_redirect_error_flash two_factor_already_authenticated_error_flash
        redirect two_factor_already_authenticated_redirect
      end
    end

    def require_two_factor_authenticated
      unless two_factor_authenticated?
        if two_factor_auth_return_to_requested_location?
          set_session_value(two_factor_auth_redirect_session_key, request.fullpath)
        end
        set_redirect_error_status(two_factor_need_authentication_error_status)
        set_error_reason :two_factor_need_authentication
        set_redirect_error_flash two_factor_need_authentication_error_flash
        redirect two_factor_auth_required_redirect
      end
    end

    def two_factor_remove_auth_failures
      nil
    end

    def two_factor_password_match?(password)
      if two_factor_modifications_require_password?
        password_match?(password)
      else
        true
      end
    end

    def two_factor_partially_authenticated?
      logged_in? && !two_factor_authenticated? && uses_two_factor_authentication?
    end

    def two_factor_authenticated?
      authenticated_by && authenticated_by.length >= 2
    end

    def two_factor_authentication_setup?
      possible_authentication_methods.length >= 2
    end

    def uses_two_factor_authentication?
      return false unless logged_in?
      set_session_value(two_factor_setup_session_key, two_factor_authentication_setup?) unless session.has_key?(two_factor_setup_session_key)
      session[two_factor_setup_session_key]
    end

    def two_factor_login_type_match?(type)
      authenticated_by && authenticated_by.include?(type)
    end

    def two_factor_remove
      nil
    end

    def two_factor_auth_links
      @two_factor_auth_links ||= _filter_links(_two_factor_auth_links)
    end

    def two_factor_setup_links
      @two_factor_setup_links ||= _filter_links(_two_factor_setup_links)
    end

    def two_factor_remove_links
      @two_factor_remove_links ||= _filter_links(_two_factor_remove_links)
    end

    private

    def _two_factor_auth_links
      (super if defined?(super)) || []
    end

    def _two_factor_setup_links
      []
    end

    def _two_factor_remove_links
      []
    end

    def _two_factor_remove_all_from_session
      nil
    end

    def after_close_account
      super if defined?(super)
      two_factor_remove
    end

    def two_factor_authenticate(type)
      two_factor_update_session(type)
      two_factor_remove_auth_failures
      after_two_factor_authentication
      require_response(:_two_factor_auth_response)
    end

    def _two_factor_auth_response
      saved_two_factor_auth_redirect = remove_session_value(two_factor_auth_redirect_session_key)
      set_notice_flash two_factor_auth_notice_flash
      redirect(saved_two_factor_auth_redirect || two_factor_auth_redirect)
    end

    def two_factor_remove_session(type)
      authenticated_by.delete(type)
      remove_session_value(two_factor_setup_session_key)
      if authenticated_by.empty?
        clear_session
      end
    end

    def two_factor_update_session(auth_type)
      authenticated_by << auth_type
      set_session_value(two_factor_setup_session_key, true)
    end
  end
end