Repository navigation
feat: Phising resistant MFA #39760
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
feat: Phising resistant MFA #39760
Changes from 3 commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
2243b8a
add deprecation warning
yash-rajpal b0cd754
chore: Passport login UI (#39848)
yash-rajpal 6889133
feat: Configure Passport (#39604)
yash-rajpal a85ee03
merge develop and fix conflicts
yash-rajpal 9313bce
Merge branch 'develop' into feat/phishing-resistant-mfa
yash-rajpal b1b3d94
fix express user typing
yash-rajpal ae49afb
use site url instead of meteor absolute url
yash-rajpal ed170a8
fix async login token creation
yash-rajpal c558ae5
oops
yash-rajpal 5628302
merge develop and fix conflicts
yash-rajpal a01dcdb
failure debugging
yash-rajpal 3ad63c7
use flash for error debugging
yash-rajpal 55d5a48
merge develop
yash-rajpal be66f93
lint
yash-rajpal 4e185c8
chore: Passport Google OAuth (#40222)
yash-rajpal c874372
trust proxy
yash-rajpal 7294f3c
merge and conflicts
yash-rajpal a0b3c2c
merge develop and fix conflicts
yash-rajpal 500e008
feat: Custom OAuth using Passport (#40203)
yash-rajpal aa3602b
custom OAuth auto publish fields
yash-rajpal 6e42e87
chore: LinkedIn custom OAuth (#40594)
yash-rajpal 9a2d2b0
feat: Deeplink login for Mobile and Desktop clients (#40601)
yash-rajpal 9cda3cf
feat: Secret setting for express-sessions secret (#40602)
yash-rajpal 93af0a8
chore: Passport Nextcloud OAuth (#40591)
yash-rajpal 33e32b9
merge develop and fix conflict
yash-rajpal c7f7306
chore: Passport Meteor OAuth (#40595)
yash-rajpal 80a082b
me stupid
yash-rajpal afda09b
chore: Passport Github Enterprise OAuth (#40608)
yash-rajpal dc44d7c
chore: Passport Gitlab OAuth (#40609)
yash-rajpal f052ca8
chore: Passport Dolphin (#40611)
yash-rajpal 02fa86b
chore: Passport Drupal OAuth (#40610)
yash-rajpal f709064
chore: Passport Wordpress OAuth (#40593)
yash-rajpal 9509e9b
add rate limiter
yash-rajpal d394e16
exclude saml, cas, ldap from login flow
yash-rajpal df761f0
express sessions mongo store
yash-rajpal 30c5ee1
rate limit only oauth req
yash-rajpal 161212e
chore: Passport Twitter OAuth (#40563)
yash-rajpal 77de0c2
feat: New 2FA flow for passport OAuth (#40106)
yash-rajpal de91e8f
Merge branch 'develop' into feat/phishing-resistant-mfa
yash-rajpal 732fc12
chore: Passport apple oauth (#40481)
yash-rajpal 20db627
Merge branch 'develop' into feat/phishing-resistant-mfa
yash-rajpal bdb7a2e
handle undefined client secrets
yash-rajpal fb20370
Merge branch 'develop' into feat/phishing-resistant-mfa
yash-rajpal 540c2b6
fix oauth e2e tests
yash-rajpal 88a8099
isolate oauth middlewares
yash-rajpal 1bb9ad4
Merge branch 'develop' into feat/phishing-resistant-mfa
yash-rajpal 5f2b140
spread profile for all fields
yash-rajpal 8a9a965
restore /_oauth/provider callback structure
yash-rajpal c1525a9
fix review
yash-rajpal 873daad
add changeset
yash-rajpal d360cd2
fix unused tokenObject
yash-rajpal 0b99664
remove unused deps
yash-rajpal 396cf66
improve login redirect url handling
yash-rajpal 2700a46
check for email method before sending email code
yash-rajpal File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import { Users } from '@rocket.chat/models'; | ||
| import { Random } from '@rocket.chat/random'; | ||
| import express from 'express'; | ||
| import session from 'express-session'; | ||
| import { WebApp } from 'meteor/webapp'; | ||
| import passport from 'passport'; | ||
|
|
||
| import type { ICachedSettings } from '../../app/settings/server/CachedSettings'; | ||
| import { configureOAuthServices } from '../lib/oauth/configureOAuthServices'; | ||
| import { createOAuthServiceConfig } from '../lib/oauth/createOAuthServiceConfig'; | ||
| import { getOAuthServices } from '../lib/oauth/getOAuthServices'; | ||
|
|
||
| export const oAuthRouter = express(); | ||
|
|
||
| oAuthRouter.use( | ||
| session({ | ||
| name: 'oauth', | ||
| secret: Random.secret(), | ||
| resave: false, | ||
| saveUninitialized: false, | ||
| cookie: { | ||
| httpOnly: true, | ||
| secure: process.env.NODE_ENV === 'production', | ||
| maxAge: 5 * 60 * 1000, // 5 minutes | ||
| }, | ||
| }), | ||
| ); | ||
|
|
||
| oAuthRouter.use(passport.initialize()); | ||
| oAuthRouter.use(passport.session()); | ||
|
|
||
| export const configurePassport = (settings: ICachedSettings) => { | ||
| passport.serializeUser((user: any, done) => { | ||
| done(null, user._id); | ||
| }); | ||
|
|
||
| passport.deserializeUser(async (id, done) => { | ||
| const user = await Users.findOneById(id as string); | ||
| // we don’t actually use this user later | ||
| done(null, user); | ||
| }); | ||
|
|
||
| settings.watchByRegex(/^(Accounts_OAuth_)[a-z0-9_]+$/i, () => { | ||
|
yash-rajpal marked this conversation as resolved.
|
||
| const services = getOAuthServices(settings); | ||
| const oauthServiceConfigs = createOAuthServiceConfig(settings, services); | ||
| configureOAuthServices(oauthServiceConfigs); | ||
| }); | ||
|
|
||
| WebApp.rawConnectHandlers.use(oAuthRouter); | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| import { type IUser } from '@rocket.chat/core-typings'; | ||
| import { Users } from '@rocket.chat/models'; | ||
| import { Accounts } from 'meteor/accounts-base'; | ||
| import { Meteor } from 'meteor/meteor'; | ||
| import passport from 'passport'; | ||
| import type { Profile, DoneCallback } from 'passport'; | ||
|
|
||
| import type { OAuthServiceConfig } from './createOAuthServiceConfig'; | ||
| import { oAuthRouter } from '../../configuration/configurePassport'; | ||
|
|
||
| export const configureOAuthServices = (oauthServiceConfig: OAuthServiceConfig[]) => { | ||
| oauthServiceConfig.forEach((config) => { | ||
| const Strategy = config.strategy; | ||
|
|
||
| passport.unuse(config.provider); | ||
|
|
||
| passport.use( | ||
| config.provider, | ||
| new Strategy( | ||
| { | ||
| clientID: config.clientId, | ||
| clientSecret: config.clientSecret, | ||
| callbackURL: `${Meteor.absoluteUrl()}oauth/${config.provider}/callback`, | ||
| state: true, | ||
| pkce: true, | ||
| scope: config.scope, | ||
| profileFields: ['id', 'displayName', 'emails'], | ||
| }, | ||
| async (accessToken: string, refreshToken: string, profile: Profile, done: DoneCallback) => { | ||
|
yash-rajpal marked this conversation as resolved.
|
||
| const profileWithRaw = profile as Profile & { _json?: Record<string, unknown>; _raw?: string }; | ||
| const { _json, _raw, ...restProfile } = profileWithRaw; | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/await-thenable | ||
| const user = await Accounts.updateOrCreateUserFromExternalService( | ||
| config.provider, | ||
| { | ||
| accessToken, | ||
| refreshToken, | ||
| name: profile.displayName, | ||
| email: profile?.emails?.[0]?.value, | ||
| ...restProfile, | ||
| ..._json, | ||
| }, | ||
| {}, | ||
| ); | ||
|
|
||
| if (!user?.userId || typeof user?.userId !== 'string') { | ||
| return done(new Error('User not found')); | ||
| } | ||
|
|
||
| const userFromDB = await Users.findOneById(user.userId); | ||
|
|
||
| if (!userFromDB) { | ||
| return done(new Error('User not found')); | ||
| } | ||
|
|
||
| return done(null, userFromDB); | ||
| }, | ||
|
yash-rajpal marked this conversation as resolved.
|
||
| ), | ||
| ); | ||
|
|
||
| oAuthRouter.get( | ||
| `/oauth/${config.provider}`, | ||
| passport.authenticate(config.provider, { scope: config.scope, prompt: 'consent', failureRedirect: '/login' }), | ||
| ); | ||
| oAuthRouter.get( | ||
| `/oauth/${config.provider}/callback`, | ||
| passport.authenticate(config.provider, { failureRedirect: '/login' }), | ||
| async (req, res) => { | ||
| const oAuthUser = req.user as IUser; | ||
|
|
||
| if (!oAuthUser) { | ||
| return res.redirect('/login'); | ||
| } | ||
|
|
||
| const stampedToken = Accounts._generateStampedLoginToken(); | ||
| Accounts._insertLoginToken(oAuthUser._id, stampedToken); | ||
|
|
||
| res.redirect(`/home?resumeToken=${stampedToken.token}`); | ||
|
|
||
| req.session.destroy((err) => { | ||
| if (err) { | ||
| console.error('Error destroying session', err); | ||
| } | ||
| }); | ||
| }, | ||
|
|
||
| ); | ||
|
yash-rajpal marked this conversation as resolved.
|
||
| }); | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { capitalize } from '@rocket.chat/string-helpers'; | ||
| import type { Strategy } from 'passport'; | ||
|
|
||
| import { OAuthConfigs } from './oauthConfigs'; | ||
| import { type ICachedSettings } from '../../../app/settings/server/CachedSettings'; | ||
|
|
||
| export type OAuthServiceConfig = { | ||
| provider: string; | ||
| strategy: new (...args: any[]) => Strategy; | ||
| clientId: string; | ||
| clientSecret: string; | ||
| scope: string[]; | ||
| }; | ||
|
|
||
| export const createOAuthServiceConfig = (settings: ICachedSettings, services: string[]): OAuthServiceConfig[] => { | ||
| return services.map((service) => { | ||
| return { | ||
| provider: service, | ||
| strategy: OAuthConfigs[service].strategy, | ||
| clientId: settings.get<string>(`Accounts_OAuth_${capitalize(service)}_id`), | ||
| clientSecret: settings.get<string>(`Accounts_OAuth_${capitalize(service)}_secret`), | ||
| scope: OAuthConfigs[service].scope, | ||
| }; | ||
| }); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import { isTruthy } from '@rocket.chat/tools'; | ||
|
|
||
| import { OAuthConfigs } from './oauthConfigs'; | ||
| import { type ICachedSettings } from '../../../app/settings/server/CachedSettings'; | ||
|
|
||
| export const getOAuthServices = (settings: ICachedSettings) => { | ||
| const services = settings.getByRegexp(/^(Accounts_OAuth_|Accounts_OAuth_Custom-)[a-z0-9_]+$/i); | ||
| const filteredServices = services.filter(([, value]) => typeof value === 'boolean' && value === true); | ||
| return filteredServices | ||
| .map(([key, value]) => { | ||
| if (!value) { | ||
| return; | ||
| } | ||
|
|
||
| let serviceName = key.replace('Accounts_OAuth_', ''); | ||
| if (serviceName === 'Meteor') { | ||
| serviceName = 'meteor-developer'; | ||
| } | ||
| if (/Accounts_OAuth_Custom-/.test(key)) { | ||
| return; | ||
| } | ||
|
|
||
| const serviceKey = serviceName.toLowerCase(); | ||
|
|
||
| const oauthConfig = OAuthConfigs[serviceKey]; | ||
| if (!oauthConfig) { | ||
| return; | ||
| } | ||
|
|
||
| return serviceKey; | ||
| }) | ||
| .filter(isTruthy); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import type { Strategy } from 'passport'; | ||
| import { Strategy as FacebookStrategy } from 'passport-facebook'; | ||
| import { Strategy as GitHubStrategy } from 'passport-github2'; | ||
|
|
||
| export type OAuthConfig = { | ||
| strategy: new (...args: any[]) => Strategy; | ||
| scope: string[]; | ||
| }; | ||
|
|
||
| export const OAuthConfigs: Record<string, OAuthConfig> = { | ||
| github: { | ||
| strategy: GitHubStrategy, | ||
| scope: ['user:email'], | ||
| }, | ||
| facebook: { | ||
| strategy: FacebookStrategy, | ||
| scope: ['email'], | ||
| }, | ||
| } as const; | ||
|
|
||
| export type Provider = keyof typeof OAuthConfigs; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.