RocketChat / Rocket.Chat

The communications platform that puts data protection first.
https://rocket.chat/
Other
40.16k stars 10.37k forks source link

2fa prevents oauth custom login #21533

Open robincafolla opened 3 years ago

robincafolla commented 3 years ago

Description:

If both custom oauth and 2fa (email) are enabled a user logging in will go through the oauth flow and be emailed an authentication code, but no input screen will be presented to use the 2fa code. They will not be able to login.

Steps to reproduce:

  1. Enable and configure custom oauth in Settings / OAuth / Add Custom OAuth
  2. Enable 2fa in Settings / Accounts / Two Factor Authentication
  3. Open a new browser window and authenticate via the custom oauth path

Expected behavior:

User authenticates via OAuth and is then shown a 2fa screen to input the code they are emailed

Actual behavior:

The user authenticates via OAuth, and the 2fa code is emailed to them, but no screen is presented to input the code.

rocketchat-custom-oauth-2fa

oauth-code

Server Setup Information:

Client Setup Information

Relevant logs:

The following exception is generated in the rocketchat logs:

I20210412-12:25:34.124(1) Failed login detected - Username[unknown] ClientAddress[192.168.122.1] ForwardedFor[-] XRealIp[-] UserAgent[Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0] 
I20210412-12:25:34.125(1) Exception while invoking method login Error: TOTP Required [totp-required]
    at checkCodeForUser (app/2fa/server/code/index.ts:186:9)
    at app/2fa/server/loginHandler.js:30:2
    at callbacks.runItem (app/callbacks/lib/callbacks.js:111:70)
    at Object.callbacks.runItem (app/metrics/server/callbacksMetrics.js:24:20)
    at app/callbacks/lib/callbacks.js:39:35
    at app/callbacks/lib/callbacks.js:45:45
    at callbacks.run (app/callbacks/lib/callbacks.js:127:9)
    at Object.callbacks.run (app/metrics/server/callbacksMetrics.js:14:17)
    at app/authentication/server/startup/index.js:346:20
    at packages/callback-hook/hook.js:131:22
    at packages/accounts-base/accounts_server.js:191:15
    at Hook.each (packages/callback-hook/hook.js:109:15)
    at AccountsServer._validateLogin (packages/accounts-base/accounts_server.js:188:29)
    at AccountsServer._attemptLogin (packages/accounts-base/accounts_server.js:377:10)
    at MethodInvocation.methods.login (packages/accounts-base/accounts_server.js:559:23)
    at maybeAuditArgumentChecks (packages/ddp-server/livedata_server.js:1771:12)
    at packages/ddp-server/livedata_server.js:1689:15
    at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)
    at packages/ddp-server/livedata_server.js:1687:36
    at new Promise (<anonymous>)
    at Server.applyAsync (packages/ddp-server/livedata_server.js:1686:12)
    at Server.apply (packages/ddp-server/livedata_server.js:1625:26)
    at Server.call (packages/ddp-server/livedata_server.js:1607:17)
    at Object.post (app/api/server/v1/misc.js:263:26)
    at app/api/server/api.js:394:82
    at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)
    at Object._internalRouteActionHandler [as action] (app/api/server/api.js:394:39)
    at Route.share.Route.Route._callEndpoint (packages/nimble_restivus/lib/route.coffee:150:32)
    at packages/nimble_restivus/lib/route.coffee:59:33
    at packages/simple_json-routes.js:98:9  => awaited here:
    at Promise.await (/var/lib/rocket.chat/bundle/programs/server/npm/node_modules/meteor/promise/node_modules/meteor-promise/promise_server.js:60:12)
    at Server.apply (packages/ddp-server/livedata_server.js:1638:22)
    at Server.call (packages/ddp-server/livedata_server.js:1607:17)
    at Object.post (app/api/server/v1/misc.js:263:26)
    at app/api/server/api.js:394:82
    at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1234:12)
    at Object._internalRouteActionHandler [as action] (app/api/server/api.js:394:39)
    at Route.share.Route.Route._callEndpoint (packages/nimble_restivus/lib/route.coffee:150:32)
    at packages/nimble_restivus/lib/route.coffee:59:33
    at packages/simple_json-routes.js:98:9 

Workarounds

johncrisp commented 3 years ago

I'm not 100% sure here, but don't you still have to put in your username/email + password first?

Hence you get the unknown user?

Failed login detected - Username[unknown] ClientAddress[192.168.122.1]

johncrisp commented 3 years ago

Sorry - close by mistake - I'll re-open and wait for your repsonse.

robincafolla commented 3 years ago

In this instance I was pre-authenticated with the external OAuth provider.

When I clicked the Login with OAuth button the system redirected me to the oauth provider, which checked my session, saw I was logged in and redirected me back to the Rocket Chat login end point with an auth token. At that point Rocket Chat fails as it is configured to require 2fa but isn't redirecting the user to a screen where they can input the 2fa code that's been emailed to them (or showing the user a message to say the code has been sent).

mrtndwrd commented 3 years ago

I get the exact same error with Rocket.Chat 3.15.0 using a custom oauth provider. Please let me know what information I can provide so we can get to the bottom of this, because it blocks all our users from being able to use Rocket.Chat.

mrtndwrd commented 3 years ago

Oh, I'm sorry, I don't have the exact same problem. I do get the screen to enter the code, but I do not get the email... I'll investigate a bit if it's maybe just our email settings that are wrong.

But the logs do show the same exception as in this issue's description.

mrtndwrd commented 3 years ago

I configured SMTP correctly. The test email worked, but I still didn't get any 2fa TOTP token emails for my user.

To circumvent the problem I disabled Accounts_TwoFactorAuthentication_By_Email_Auto_Opt_In (available in admin settings, under Accounts -> Two Factor Authentication) and now at least logging in with OIDC works. I think 2FA responsibility should lie with the OIDC provider rather than Rocket.Chat in our case, so I'm OK with that solution.

ghost commented 2 years ago

Hey, we are experiencing exactly the same problems like OP described. Were on v4.1.1 (Docker) and MongoDB 4.2.17 (wiredTiger).

I am also interested in helping investigating that, just tell me what should/must be tested.

Gummikavalier commented 2 years ago

@debdutdeb We are also experiencing this on the RC 4.3.3 Enterprise version.

Email2fa option has the issue as depicted by the video of the original poster of this issue. The user logs in via custom Oauth provider and gets redirected back to login screen normally after successfully doing so. At this point the user should be shown the email2fa dialog to type in the code from the email. (This email they do receive in our case as usual.)

The dialog is not shown and the only option for the user is to try login again, and the result is exactly what we see in the video.

Normal Two-Factor authentication via TOTP dialog works fine with Custom Oauth provider.

Two-Factor authentication via Email dialog does not.

wolbernd commented 2 years ago

I can confirm that the problem also persists in RC 4.4.2 . This seems to happen to SAML-Logins as well. I did some digging in a development environment and found out the following:

In https://github.com/RocketChat/Rocket.Chat/blob/a523503195f843f20ee6784039d250d9ad239dee/client/lib/2fa/process2faReturn.ts#L36 the client-side 2fa process is handled (a modal is opened to let the user enter the 2fa code). However when that call comes from an Oauth or SAML Login emailOrUsername is undefined and since Meteor.user() is also undefined before the login (which makes sense) the variable props looks something like this:

props = {
        'email',
        undefined
    };

Thus the method assertModalProp() throws the above described Error Invalid Two Factor method. A quick and dirty fix is to disable the call to assertModalProp(). I tested this in a dev environment and 2fa worked with Oauth after that. However clicking on "Resend Mail" leads to an error. My guess is that there is a reason this method exists and we shouldn't just skip it.

I did check the Oauth login code if there is a way to provide emailOrUsername but I'm not at all proficient in JS/TS and got lost somewhere in the rabbit hole of overwritten functions.