Skip to content
Snippets Groups Projects
Unverified Commit 6bd06278 authored by Diego Sampaio's avatar Diego Sampaio Committed by GitHub
Browse files

Regression: Add support for 2FA errors to `Meteor.callAsync` (#27767)

parent 9fa9661f
No related branches found
No related tags found
No related merge requests found
import { Meteor } from 'meteor/meteor';
import { t } from '../../utils/client';
import { process2faReturn } from '../../../client/lib/2fa/process2faReturn';
import { process2faReturn, process2faAsyncReturn } from '../../../client/lib/2fa/process2faReturn';
import { isTotpInvalidError } from '../../../client/lib/2fa/utils';
const { call } = Meteor;
const { call, callAsync } = Meteor;
type Callback = {
(error: unknown): void;
......@@ -34,8 +34,34 @@ const callWithoutTotp = (methodName: string, args: unknown[], callback: Callback
});
});
const callAsyncWithTotp =
(methodName: string, args: unknown[]) =>
async (twoFactorCode: string, twoFactorMethod: string): Promise<unknown> => {
try {
const result = await callAsync(methodName, ...args, { twoFactorCode, twoFactorMethod });
return result;
} catch (error: unknown) {
if (isTotpInvalidError(error)) {
throw new Error(twoFactorMethod === 'password' ? t('Invalid_password') : t('Invalid_two_factor_code'));
}
throw error;
}
};
Meteor.call = function (methodName: string, ...args: unknown[]): unknown {
const callback = args.length > 0 && typeof args[args.length - 1] === 'function' ? (args.pop() as Callback) : (): void => undefined;
return callWithoutTotp(methodName, args, callback)();
};
Meteor.callAsync = async function _callAsyncWithTotp(methodName: string, ...args: unknown[]): Promise<unknown> {
const promise = callAsync(methodName, ...args);
return process2faAsyncReturn({
promise,
onCode: callAsyncWithTotp(methodName, args),
emailOrUsername: undefined,
});
};
......@@ -76,3 +76,47 @@ export function process2faReturn({
},
});
}
export async function process2faAsyncReturn({
promise,
onCode,
emailOrUsername,
}: {
promise: Promise<unknown>;
onCode: (code: string, method: string) => void;
emailOrUsername: string | null | undefined;
}): Promise<unknown> {
// if the promise is rejected, we need to check if it's a 2fa error
return promise.catch((error) => {
// if it's not a 2fa error, we reject the promise
if (!isTotpRequiredError(error) || !hasRequiredTwoFactorMethod(error)) {
throw error;
}
const props = {
method: error.details.method,
emailOrUsername: emailOrUsername || error.details.emailOrUsername || Meteor.user()?.username,
};
assertModalProps(props);
return new Promise<unknown>((resolve, reject) => {
imperativeModal.open({
component: TwoFactorModal,
props: {
...props,
onConfirm: (code: string, method: string): void => {
imperativeModal.close();
// once we have the code, we resolve the promise with the result of the `onCode` callback
resolve(onCode(method === 'password' ? SHA256(code) : code, method));
},
onClose: (): void => {
imperativeModal.close();
reject(new Meteor.Error('totp-canceled'));
},
},
});
});
});
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment