122 lines
4.4 KiB
TypeScript
122 lines
4.4 KiB
TypeScript
import TelegramClient from './TelegramClient';
|
|
import { default as Api } from '../tl/api';
|
|
import { generateRandomBytes } from '../Helpers';
|
|
import { computeCheck, computeDigest } from '../Password';
|
|
import errors from '../errors';
|
|
|
|
export interface TwoFaParams {
|
|
isCheckPassword?: boolean;
|
|
currentPassword?: string;
|
|
newPassword?: string;
|
|
hint?: string;
|
|
email?: string;
|
|
emailCodeCallback?: (length: number) => Promise<string>;
|
|
onEmailCodeError?: (err: Error) => void;
|
|
}
|
|
|
|
/**
|
|
* Changes the 2FA settings of the logged in user.
|
|
Note that this method may be *incredibly* slow depending on the
|
|
prime numbers that must be used during the process to make sure
|
|
that everything is safe.
|
|
|
|
Has no effect if both current and new password are omitted.
|
|
|
|
* @param client: The telegram client instance
|
|
* @param isCheckPassword: Must be ``true`` if you want to check the current password
|
|
* @param currentPassword: The current password, to authorize changing to ``new_password``.
|
|
Must be set if changing existing 2FA settings.
|
|
Must **not** be set if 2FA is currently disabled.
|
|
Passing this by itself will remove 2FA (if correct).
|
|
* @param newPassword: The password to set as 2FA.
|
|
If 2FA was already enabled, ``currentPassword`` **must** be set.
|
|
Leaving this blank or `undefined` will remove the password.
|
|
* @param hint: Hint to be displayed by Telegram when it asks for 2FA.
|
|
Must be set when changing or creating a new password.
|
|
Has no effect if ``newPassword`` is not set.
|
|
* @param email: Recovery and verification email. If present, you must also
|
|
set `emailCodeCallback`, else it raises an Error.
|
|
* @param emailCodeCallback: If an email is provided, a callback that returns the code sent
|
|
to it must also be set. This callback may be asynchronous.
|
|
It should return a string with the code. The length of the
|
|
code will be passed to the callback as an input parameter.
|
|
|
|
If the callback returns an invalid code, it will raise an rpc error with the message
|
|
``CODE_INVALID``
|
|
|
|
* @returns Promise<void>
|
|
* @throws this method can throw:
|
|
"PASSWORD_HASH_INVALID" if you entered a wrong password (or set it to undefined).
|
|
"EMAIL_INVALID" if the entered email is wrong
|
|
"EMAIL_HASH_EXPIRED" if the user took too long to verify their email
|
|
*/
|
|
export async function updateTwoFaSettings(
|
|
client: TelegramClient,
|
|
{
|
|
isCheckPassword,
|
|
currentPassword,
|
|
newPassword,
|
|
hint = '',
|
|
email,
|
|
emailCodeCallback,
|
|
onEmailCodeError,
|
|
}: TwoFaParams,
|
|
) {
|
|
if (!newPassword && !currentPassword) {
|
|
throw new Error('Neither `currentPassword` nor `newPassword` is present');
|
|
}
|
|
|
|
if (email && !(emailCodeCallback && onEmailCodeError)) {
|
|
throw new Error('`email` present without `emailCodeCallback` and `onEmailCodeError`');
|
|
}
|
|
|
|
const pwd = await client.invoke(new Api.account.GetPassword());
|
|
|
|
if (!(pwd.newAlgo instanceof Api.PasswordKdfAlgoUnknown)) {
|
|
pwd.newAlgo.salt1 = Buffer.concat([pwd.newAlgo.salt1, generateRandomBytes(32)]);
|
|
}
|
|
if (!pwd.hasPassword && currentPassword) {
|
|
currentPassword = undefined;
|
|
}
|
|
|
|
const password = currentPassword ? await computeCheck(pwd, currentPassword!) : new Api.InputCheckPasswordEmpty();
|
|
|
|
if (isCheckPassword) {
|
|
await client.invoke(new Api.auth.CheckPassword({ password }));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await client.invoke(new Api.account.UpdatePasswordSettings({
|
|
password,
|
|
newSettings: new Api.account.PasswordInputSettings({
|
|
newAlgo: pwd.newAlgo,
|
|
newPasswordHash: newPassword ? await computeDigest(pwd.newAlgo, newPassword) : Buffer.alloc(0),
|
|
hint,
|
|
email,
|
|
// not explained what it does and it seems to always be set to empty in tdesktop
|
|
newSecureSettings: undefined,
|
|
}),
|
|
}));
|
|
} catch (e) {
|
|
if (e instanceof errors.EmailUnconfirmedError) {
|
|
while (1) {
|
|
try {
|
|
const code = await emailCodeCallback!(e.codeLength);
|
|
|
|
if (!code) {
|
|
throw new Error('Code is empty');
|
|
}
|
|
|
|
await client.invoke(new Api.account.ConfirmPasswordEmail({ code }));
|
|
break;
|
|
} catch (err) {
|
|
onEmailCodeError!(err);
|
|
}
|
|
}
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|