Browse Source

Maintenance: Improve 2FA endpoints.

Co-authored-by: Mantas Masalskis <mm@zammad.com>
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Dusan Vuckovic 3 weeks ago
parent
commit
f554b7e928

+ 5 - 4
app/assets/javascripts/app/controllers/_profile/password.coffee

@@ -34,9 +34,9 @@ class ProfilePassword extends App.ControllerSubContent
     @startLoading()
 
     @ajax(
-      id:   'profile_two_factor'
+      id:   'profile_two_factor_personal_configuration'
       type: 'GET'
-      url:  @apiPath + '/users/two_factor_personal_configuration'
+      url:  @apiPath + '/users/two_factor/personal_configuration'
       processData: true
       success: (data, status, xhr) =>
         @stopLoading()
@@ -175,14 +175,15 @@ class ProfilePassword extends App.ControllerSubContent
     new App.TwoFactorConfigurationModalPasswordCheck(
       headPrefix: __('Remove two-factor authentication')
       buttonSubmit: 'Remove'
-      successCallback: =>
+      successCallback: (data) =>
         @ajax(
           id:   'profile_two_factor_removal'
           type: 'DELETE'
-          url:  @apiPath + "/users/#{App.User.current().id}/two_factor_remove_authentication_method"
+          url:  @apiPath + '/users/two_factor/remove_authentication_method'
           processData: true
           data: JSON.stringify(
             method: key
+            token: data.token
           )
           success: (data, status, xhr) =>
             @notify

+ 38 - 4
app/assets/javascripts/app/controllers/after_auth/two_factor_configuration.coffee

@@ -12,6 +12,10 @@ class App.AfterAuthTwoFactorConfiguration extends App.ControllerAfterAuthModal
     if params.noFadeTransition
       params.className = 'modal'
 
+    if not params.data?.token
+      @invalidPasswordToken()
+      return
+
     super(params)
 
   content: ->
@@ -27,10 +31,12 @@ class App.AfterAuthTwoFactorConfiguration extends App.ControllerAfterAuthModal
     return if !App.User.current()
 
     @ajax(
-      id:      'two_factor_enabled_authentication_methods'
-      type:    'GET'
-      url:     "#{@apiPath}/users/#{App.User.current().id}/two_factor_enabled_authentication_methods"
-      success: @renderAvailableMethods
+      id:          'two_factor_enabled_authentication_methods'
+      type:        'POST'
+      url:         "#{@apiPath}/users/two_factor/enabled_authentication_methods"
+      data:        JSON.stringify(token: @data?.token)
+      processData: true
+      success:     @renderAvailableMethods
       error: (xhr, status, error) =>
         return if xhr.status != 403
 
@@ -39,6 +45,11 @@ class App.AfterAuthTwoFactorConfiguration extends App.ControllerAfterAuthModal
       )
 
   renderAvailableMethods: (data, status, xhr) =>
+    if data?.invalid_password_token
+      @closeWithoutFade()
+      @invalidPasswordToken(true)
+      return
+
     methodButtons = $(App.view('after_auth/two_factor_configuration/method_buttons')(
       enabledMethods: @transformTwoFactorMethods(data)
     ))
@@ -78,7 +89,30 @@ class App.AfterAuthTwoFactorConfiguration extends App.ControllerAfterAuthModal
 
     new App['TwoFactorConfigurationMethod' + configurationMethod](
       mode: 'after_auth'
+      token: @data?.token
       successCallback: =>
         @fetchAfterAuth()
         App.User.current().trigger('two_factor_changed')
     )
+
+  invalidPasswordToken: (notify = false) =>
+    if notify
+      @notify(
+        type:      'error'
+        msg:       __('Invalid password revalidation token, please confirm your password again.')
+        removeAll: true
+      )
+
+    new App.TwoFactorConfigurationModalPasswordCheck(
+      logoutOnCancel: @logoutOnCancel
+      backdrop: @backdrop
+      keyboard: @keyboard
+      buttonClose: @buttonClose
+      buttonCancel: @buttonCancel
+      onCancel: @onCancel
+      successCallback: (data) ->
+        new App.AfterAuthTwoFactorConfiguration(
+          data: data
+          className: 'modal' # no automatic fade transitions
+        )
+    )

+ 3 - 3
app/assets/javascripts/app/controllers/user/manage_two_factor.coffee

@@ -17,7 +17,7 @@ class App.ControllerManageTwoFactor extends App.ControllerModal
 
     @ajax(
       type: 'GET'
-      url: "#{@apiPath}/users/#{@user.id}/two_factor_enabled_authentication_methods"
+      url: "#{@apiPath}/users/#{@user.id}/admin_two_factor/enabled_authentication_methods"
       success: (data, status, xhr) =>
         @stopLoading()
 
@@ -79,7 +79,7 @@ class App.ControllerManageTwoFactor extends App.ControllerModal
 
     @ajax(
       type: 'DELETE'
-      url: "#{@apiPath}/users/#{@user.id}/two_factor_remove_authentication_method"
+      url: "#{@apiPath}/users/#{@user.id}/admin_two_factor/remove_authentication_method"
       data: JSON.stringify(
         method: params.method
       )
@@ -119,7 +119,7 @@ class App.ControllerManageTwoFactor extends App.ControllerModal
       callback: =>
         @ajax(
           type: 'DELETE'
-          url: "#{@apiPath}/users/#{@user.id}/two_factor_remove_all_authentication_methods"
+          url: "#{@apiPath}/users/#{@user.id}/admin_two_factor/remove_all_authentication_methods"
           success: (data, status, xhr) =>
             @user.trigger('two_factor_changed')
 

+ 8 - 0
app/assets/javascripts/app/controllers/widget/two_factor_configuration/method.coffee

@@ -13,15 +13,23 @@ class App.TwoFactorConfigurationMethod extends App.Controller
     #   and bind the cancel handler to return back to after auth modal.
     if params.mode is 'after_auth'
       @passwordCheck = false
+
+      data = _.extend(
+        {},
+        token: params.token
+      )
+
       modalOptions = _.extend(
         {},
         modalOptions,
+        data,
         backdrop: 'static'
         buttonClose: false
         buttonCancel: __('Go Back')
         keyboard: false
         onCancel: ->
           new App.AfterAuthTwoFactorConfiguration(
+            data: data
             noFadeTransition: true
           )
       )

+ 14 - 0
app/assets/javascripts/app/controllers/widget/two_factor_configuration/modal.coffee

@@ -60,3 +60,17 @@ class App.TwoFactorConfigurationModal extends App.ControllerModal
       removeAll: true
 
     @successCallback() if @successCallback
+
+  invalidPasswordToken: =>
+    @close()
+
+    @notify(
+      type:      'error'
+      msg:       __('Invalid password revalidation token, please confirm your password again.')
+      removeAll: true
+    )
+
+    new App["TwoFactorConfigurationMethod#{@method.identifier}"](
+      container: @container
+      successCallback: @successCallback
+    )

+ 16 - 5
app/assets/javascripts/app/controllers/widget/two_factor_configuration/modal/authenticator_app.coffee

@@ -18,6 +18,10 @@ class App.TwoFactorConfigurationModalAuthenticatorApp extends App.TwoFactorConfi
     $('.modal .js-submit').prop('disabled', true)
 
     callback = (data) =>
+      if data?.invalid_password_token
+        @invalidPasswordToken()
+        return
+
       @config = data.configuration
 
       content = $(App.view('widget/two_factor_configuration/authenticator_app')(
@@ -56,10 +60,12 @@ class App.TwoFactorConfigurationModalAuthenticatorApp extends App.TwoFactorConfi
 
   fetchInitialConfiguration: (callback) =>
     @ajax(
-      id:      'two_factor_authentication_method_initiate_configuration'
-      type:    'GET'
-      url:     "#{@apiPath}/users/two_factor_authentication_method_initiate_configuration/#{@method.key}"
-      success: callback
+      id:          'two_factor_authentication_method_initiate_configuration'
+      type:        'POST'
+      url:         "#{@apiPath}/users/two_factor/authentication_method_initiate_configuration/#{@method.key}"
+      data:        JSON.stringify(token: @token)
+      processData: true
+      success:     callback
     )
 
   onSubmit: (e) =>
@@ -72,6 +78,7 @@ class App.TwoFactorConfigurationModalAuthenticatorApp extends App.TwoFactorConfi
 
     data = JSON.stringify(
       method: @method.key
+      token: @token
       payload: params.payload
       configuration: @config
     )
@@ -81,10 +88,14 @@ class App.TwoFactorConfigurationModalAuthenticatorApp extends App.TwoFactorConfi
     @ajax
       id: 'two_factor_verify_configuration'
       type: 'POST'
-      url: "#{@apiPath}/users/two_factor_verify_configuration"
+      url: "#{@apiPath}/users/two_factor/verify_configuration"
       data: data
       processData: true
       success: (data, status, xhr) =>
+        if data?.invalid_password_token
+          @invalidPasswordToken()
+          return
+
         if data?.verified
           @finalizeConfigurationWizard(data)
           return

+ 2 - 2
app/assets/javascripts/app/controllers/widget/two_factor_configuration/modal/password_check.coffee

@@ -35,9 +35,9 @@ class App.TwoFactorConfigurationModalPasswordCheck extends App.TwoFactorConfigur
         if data?.success
           if @successCallback && !@container
             @close()
-            @successCallback()
+            @successCallback(token: data.token)
           else
-            @next()
+            @next(token: data.token)
 
           return
 

+ 10 - 4
app/assets/javascripts/app/controllers/widget/two_factor_configuration/modal/recovery_codes.coffee

@@ -54,13 +54,19 @@ class App.TwoFactorConfigurationModalRecoveryCodes extends App.TwoFactorConfigur
 
   fetchRecoveryCodes: =>
     @ajax(
-      id:      'two_factor_authentication_method_configuration'
-      type:    'POST'
-      url:     "#{@apiPath}/users/two_factor_recovery_codes_generate"
+      id: 'two_factor_recovery_codes_generate'
+      type: 'POST'
+      url: "#{@apiPath}/users/two_factor/recovery_codes_generate"
+      data: JSON.stringify(token: @token)
+      processData: true
       success: @didFetch
     )
 
-  didFetch: (recovery_codes) ->
+  didFetch: (recovery_codes) =>
+    if recovery_codes?.invalid_password_token
+      @invalidPasswordToken()
+      return
+
     content = $(App.view('widget/two_factor_configuration/recovery_codes')(
       recovery_codes: recovery_codes
     ))

+ 40 - 14
app/assets/javascripts/app/controllers/widget/two_factor_configuration/modal/security_keys.coffee

@@ -13,6 +13,10 @@ class App.TwoFactorConfigurationModalSecurityKeys extends App.TwoFactorConfigura
     $('.modal .js-submit').prop('disabled', true)
 
     callback = (data) =>
+      if data?.invalid_password_token
+        @invalidPasswordToken()
+        return
+
       @config      = data?.configuration or {}
       @credentials = @config?.credentials or []
 
@@ -31,11 +35,13 @@ class App.TwoFactorConfigurationModalSecurityKeys extends App.TwoFactorConfigura
 
   fetchExistingSecurityKeys: (callback) =>
     @ajax(
-      id:      'two_factor_authentication_method_configuration'
-      type:    'GET'
-      url:     "#{@apiPath}/users/two_factor_authentication_method_configuration/security_keys"
-      success: callback
-      error:   callback
+      id:          'two_factor_authentication_method_configuration'
+      type:        'POST'
+      url:         "#{@apiPath}/users/two_factor/authentication_method_configuration/security_keys"
+      data:        JSON.stringify(token: @token)
+      processData: true
+      success:     callback
+      error:       callback
     )
 
   renderTable: =>
@@ -74,15 +80,22 @@ class App.TwoFactorConfigurationModalSecurityKeys extends App.TwoFactorConfigura
     )
 
   removeSecurityKey: (id) =>
-    data = { credential_id: id }
+    data = {
+      credential_id: id
+      token: @token
+    }
 
     @ajax(
-      id:          'two_factor_authentication_method_configuration'
+      id:          'two_factor_authentication_remove_credentials'
       type:        'DELETE'
-      url:         "#{@apiPath}/users/two_factor_authentication_remove_credentials/security_keys"
+      url:         "#{@apiPath}/users/two_factor/authentication_remove_credentials/security_keys"
       data:        JSON.stringify(data)
       processData: true
-      success: =>
+      success: (data) =>
+        if data?.invalid_password_token
+          @invalidPasswordToken()
+          return
+
         # Refresh the table in the password profile screen.
         @successCallback()
 
@@ -98,6 +111,7 @@ class App.TwoFactorConfigurationModalSecurityKeys extends App.TwoFactorConfigura
     @next(
       container: @container
       successCallback: @successCallback
+      token: @token
     )
 
     # We are not calling `super`, since we do not want to call success callback yet.
@@ -138,6 +152,7 @@ class App.TwoFactorConfigurationModalSecurityKeyConfig extends App.TwoFactorConf
       container: @container
       nickname: params.nickname
       successCallback: @successCallback
+      token: @token
     )
 
     # We are not calling `super`, since we do not want to call success callback yet.
@@ -161,6 +176,10 @@ class App.TwoFactorConfigurationModalSecurityKeyRegister extends App.TwoFactorCo
     $('.modal .js-loading').removeClass('hide')
 
     callback = (data) =>
+      if data?.invalid_password_token
+        @invalidPasswordToken()
+        return
+
       @config = data.configuration
 
       content = $(App.view('widget/two_factor_configuration/security_keys/register')())
@@ -174,10 +193,12 @@ class App.TwoFactorConfigurationModalSecurityKeyRegister extends App.TwoFactorCo
 
   fetchInitialConfiguration: (callback) =>
     @ajax(
-      id:      'two_factor_authentication_method_initiate_configuration'
-      type:    'GET'
-      url:     "#{@apiPath}/users/two_factor_authentication_method_initiate_configuration/#{@method.key}"
-      success: callback
+      id:          'two_factor_authentication_method_initiate_configuration'
+      type:        'POST'
+      url:         "#{@apiPath}/users/two_factor/authentication_method_initiate_configuration/#{@method.key}"
+      data:        JSON.stringify(token: @token)
+      processData: true
+      success:     callback
     )
 
   showError: (message = __('Security key setup failed.')) =>
@@ -202,6 +223,7 @@ class App.TwoFactorConfigurationModalSecurityKeyRegister extends App.TwoFactorCo
       .then((publicKeyCredential) =>
         data = JSON.stringify(
           method: @method.key
+          token: @token
           payload:
             credential: publicKeyCredential
             challenge:  @config.challenge
@@ -211,10 +233,14 @@ class App.TwoFactorConfigurationModalSecurityKeyRegister extends App.TwoFactorCo
         @ajax
           id: 'two_factor_verify_configuration'
           type: 'POST'
-          url: "#{@apiPath}/users/two_factor_verify_configuration"
+          url: "#{@apiPath}/users/two_factor/verify_configuration"
           data: data
           processData: true
           success: (data, status, xhr) =>
+            if data?.invalid_password_token
+              @invalidPasswordToken()
+              return
+
             if data?.verified
               @finalizeConfigurationWizard(data)
               return

+ 1 - 1
app/assets/javascripts/app/views/profile/token_access.jst.eco

@@ -31,7 +31,7 @@
         <td><%= token.name %></td>
         <td><% if token.preferences && token.preferences.permission: %><%= token.preferences.permission.join(', ') %><% end %></td>
         <td><%- @humanTime(token.created_at) %></td>
-        <td><%- @Tdate(token.expires_at) %></td>
+        <td><%- @humanTime(token.expires_at) %></td>
         <td><%- @humanTime(token.last_used_at) %></td>
         <td class="settings-list-controls">
           <div class="settings-list-control js-delete" data-token-id="<%- token.id %>" title="<%- @Ti('Delete') %>"><%- @Icon('trash') %></div>

Some files were not shown because too many files changed in this diff