feat(core): support future auth tokens
This commit is contained in:
parent
237146a4f3
commit
78285d4815
7 changed files with 150 additions and 68 deletions
|
@ -13,7 +13,7 @@ import { BaseTelegramClient, BaseTelegramClientOptions } from './base.js'
|
||||||
import { ITelegramClient } from './client.types.js'
|
import { ITelegramClient } from './client.types.js'
|
||||||
import { checkPassword } from './methods/auth/check-password.js'
|
import { checkPassword } from './methods/auth/check-password.js'
|
||||||
import { getPasswordHint } from './methods/auth/get-password-hint.js'
|
import { getPasswordHint } from './methods/auth/get-password-hint.js'
|
||||||
import { logOut } from './methods/auth/log-out.js'
|
import { logOut, LogOutResult } from './methods/auth/log-out.js'
|
||||||
import { recoverPassword } from './methods/auth/recover-password.js'
|
import { recoverPassword } from './methods/auth/recover-password.js'
|
||||||
import { resendCode } from './methods/auth/resend-code.js'
|
import { resendCode } from './methods/auth/resend-code.js'
|
||||||
import { run } from './methods/auth/run.js'
|
import { run } from './methods/auth/run.js'
|
||||||
|
@ -620,7 +620,7 @@ export interface TelegramClient extends ITelegramClient {
|
||||||
*
|
*
|
||||||
* @returns On success, `true` is returned
|
* @returns On success, `true` is returned
|
||||||
*/
|
*/
|
||||||
logOut(): Promise<true>
|
logOut(): Promise<LogOutResult>
|
||||||
/**
|
/**
|
||||||
* Recover your password with a recovery code and log in.
|
* Recover your password with a recovery code and log in.
|
||||||
*
|
*
|
||||||
|
@ -672,6 +672,12 @@ export interface TelegramClient extends ITelegramClient {
|
||||||
sendCode(params: {
|
sendCode(params: {
|
||||||
/** Phone number in international format */
|
/** Phone number in international format */
|
||||||
phone: string
|
phone: string
|
||||||
|
|
||||||
|
/** Saved future auth tokens, if any */
|
||||||
|
futureAuthTokens?: Uint8Array[]
|
||||||
|
|
||||||
|
/** Additional code settings to pass to the server */
|
||||||
|
codeSettings?: Omit<tl.RawCodeSettings, '_' | 'logoutTokens'>
|
||||||
}): Promise<SentCode>
|
}): Promise<SentCode>
|
||||||
/**
|
/**
|
||||||
* Send a code to email needed to recover your password
|
* Send a code to email needed to recover your password
|
||||||
|
@ -818,6 +824,12 @@ export interface TelegramClient extends ITelegramClient {
|
||||||
* @default `console.log`.
|
* @default `console.log`.
|
||||||
*/
|
*/
|
||||||
codeSentCallback?: (code: SentCode) => MaybePromise<void>
|
codeSentCallback?: (code: SentCode) => MaybePromise<void>
|
||||||
|
|
||||||
|
/** Saved future auth tokens, if any */
|
||||||
|
futureAuthTokens?: Uint8Array[]
|
||||||
|
|
||||||
|
/** Additional code settings to pass to the server */
|
||||||
|
codeSettings?: Omit<tl.RawCodeSettings, '_' | 'logoutTokens'>
|
||||||
}): Promise<User>
|
}): Promise<User>
|
||||||
/**
|
/**
|
||||||
* Check if the given peer/input peer is referring to the current user
|
* Check if the given peer/input peer is referring to the current user
|
||||||
|
@ -1780,6 +1792,11 @@ export interface TelegramClient extends ITelegramClient {
|
||||||
* Some library logic depends on this, for example, the library will
|
* Some library logic depends on this, for example, the library will
|
||||||
* periodically ping the server to keep the updates flowing.
|
* periodically ping the server to keep the updates flowing.
|
||||||
*
|
*
|
||||||
|
* > **Warning**: Opening a chat with `openChat` method will make the library make additional requests
|
||||||
|
* > every so often. Which means that you should **avoid opening more than 5-10 chats at once**,
|
||||||
|
* > as it will probably trigger server-side limits and you might start getting transport errors
|
||||||
|
* > or even get banned.
|
||||||
|
*
|
||||||
* **Available**: ✅ both users and bots
|
* **Available**: ✅ both users and bots
|
||||||
*
|
*
|
||||||
* @param chat Chat to open
|
* @param chat Chat to open
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* THIS FILE WAS AUTO-GENERATED */
|
/* THIS FILE WAS AUTO-GENERATED */
|
||||||
export { checkPassword } from './methods/auth/check-password.js'
|
export { checkPassword } from './methods/auth/check-password.js'
|
||||||
export { getPasswordHint } from './methods/auth/get-password-hint.js'
|
export { getPasswordHint } from './methods/auth/get-password-hint.js'
|
||||||
|
export type { LogOutResult } from './methods/auth/log-out.js'
|
||||||
export { logOut } from './methods/auth/log-out.js'
|
export { logOut } from './methods/auth/log-out.js'
|
||||||
export { recoverPassword } from './methods/auth/recover-password.js'
|
export { recoverPassword } from './methods/auth/recover-password.js'
|
||||||
export { resendCode } from './methods/auth/resend-code.js'
|
export { resendCode } from './methods/auth/resend-code.js'
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
import { ITelegramClient } from '../../client.types.js'
|
import { ITelegramClient } from '../../client.types.js'
|
||||||
|
|
||||||
|
// @exported
|
||||||
|
export interface LogOutResult {
|
||||||
|
/**
|
||||||
|
* Future auth token returned by the server (if any), which can then be passed to
|
||||||
|
* {@link start} and {@link sendCode} methods to avoid sending the code again.
|
||||||
|
*/
|
||||||
|
futureAuthToken?: Uint8Array
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log out from Telegram account and optionally reset the session storage.
|
* Log out from Telegram account and optionally reset the session storage.
|
||||||
*
|
*
|
||||||
|
@ -8,9 +17,11 @@ import { ITelegramClient } from '../../client.types.js'
|
||||||
*
|
*
|
||||||
* @returns On success, `true` is returned
|
* @returns On success, `true` is returned
|
||||||
*/
|
*/
|
||||||
export async function logOut(client: ITelegramClient): Promise<true> {
|
export async function logOut(client: ITelegramClient): Promise<LogOutResult> {
|
||||||
await client.call({ _: 'auth.logOut' })
|
const res = await client.call({ _: 'auth.logOut' })
|
||||||
await client.notifyLoggedOut()
|
await client.notifyLoggedOut()
|
||||||
|
|
||||||
return true
|
return {
|
||||||
|
futureAuthToken: res.futureAuthToken,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
||||||
import { assertTypeIs } from '../../../utils/type-assertions.js'
|
import { assertTypeIs } from '../../../utils/type-assertions.js'
|
||||||
import { ITelegramClient } from '../../client.types.js'
|
import { ITelegramClient } from '../../client.types.js'
|
||||||
import { SentCode } from '../../types/auth/sent-code.js'
|
import { SentCode } from '../../types/auth/sent-code.js'
|
||||||
|
@ -13,6 +15,12 @@ export async function sendCode(
|
||||||
params: {
|
params: {
|
||||||
/** Phone number in international format */
|
/** Phone number in international format */
|
||||||
phone: string
|
phone: string
|
||||||
|
|
||||||
|
/** Saved future auth tokens, if any */
|
||||||
|
futureAuthTokens?: Uint8Array[]
|
||||||
|
|
||||||
|
/** Additional code settings to pass to the server */
|
||||||
|
codeSettings?: Omit<tl.RawCodeSettings, '_' | 'logoutTokens'>
|
||||||
},
|
},
|
||||||
): Promise<SentCode> {
|
): Promise<SentCode> {
|
||||||
const phone = normalizePhoneNumber(params.phone)
|
const phone = normalizePhoneNumber(params.phone)
|
||||||
|
@ -24,7 +32,11 @@ export async function sendCode(
|
||||||
phoneNumber: phone,
|
phoneNumber: phone,
|
||||||
apiId: id,
|
apiId: id,
|
||||||
apiHash: hash,
|
apiHash: hash,
|
||||||
settings: { _: 'codeSettings' },
|
settings: {
|
||||||
|
_: 'codeSettings',
|
||||||
|
logoutTokens: params.futureAuthTokens,
|
||||||
|
...params.codeSettings,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
assertTypeIs('sendCode', res, 'auth.sentCode')
|
assertTypeIs('sendCode', res, 'auth.sentCode')
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
||||||
import { MtArgumentError } from '../../../types/errors.js'
|
import { MtArgumentError, MtcuteError } from '../../../types/errors.js'
|
||||||
import { MaybePromise } from '../../../types/utils.js'
|
import { MaybePromise } from '../../../types/utils.js'
|
||||||
import { ITelegramClient } from '../../client.types.js'
|
import { ITelegramClient } from '../../client.types.js'
|
||||||
import { SentCode } from '../../types/auth/sent-code.js'
|
import { SentCode } from '../../types/auth/sent-code.js'
|
||||||
|
@ -94,12 +94,22 @@ export async function start(
|
||||||
* @default `console.log`.
|
* @default `console.log`.
|
||||||
*/
|
*/
|
||||||
codeSentCallback?: (code: SentCode) => MaybePromise<void>
|
codeSentCallback?: (code: SentCode) => MaybePromise<void>
|
||||||
|
|
||||||
|
/** Saved future auth tokens, if any */
|
||||||
|
futureAuthTokens?: Uint8Array[]
|
||||||
|
|
||||||
|
/** Additional code settings to pass to the server */
|
||||||
|
codeSettings?: Omit<tl.RawCodeSettings, '_' | 'logoutTokens'>
|
||||||
},
|
},
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
if (params.session) {
|
if (params.session) {
|
||||||
await client.importSession(params.session, params.sessionForce)
|
await client.importSession(params.session, params.sessionForce)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let has2fa = false
|
||||||
|
let sentCode: SentCode | undefined
|
||||||
|
let phone: string | null = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const me = await getMe(client)
|
const me = await getMe(client)
|
||||||
|
|
||||||
|
@ -111,14 +121,19 @@ export async function start(
|
||||||
|
|
||||||
return me
|
return me
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!tl.RpcError.is(e, 'AUTH_KEY_UNREGISTERED')) throw e
|
if (tl.RpcError.is(e)) {
|
||||||
|
if (e.text === 'SESSION_PASSWORD_NEEDED') has2fa = true
|
||||||
|
else if (e.text !== 'AUTH_KEY_UNREGISTERED') throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if has2fa == true, then we are half-logged in, but need to enter password
|
||||||
|
if (!has2fa) {
|
||||||
if (!params.phone && !params.botToken) {
|
if (!params.phone && !params.botToken) {
|
||||||
throw new MtArgumentError('Neither phone nor bot token were provided')
|
throw new MtArgumentError('Neither phone nor bot token were provided')
|
||||||
}
|
}
|
||||||
|
|
||||||
let phone = params.phone ? await resolveMaybeDynamic(params.phone) : null
|
phone = params.phone ? await resolveMaybeDynamic(params.phone) : null
|
||||||
|
|
||||||
if (phone) {
|
if (phone) {
|
||||||
phone = normalizePhoneNumber(phone)
|
phone = normalizePhoneNumber(phone)
|
||||||
|
@ -136,26 +151,42 @@ export async function start(
|
||||||
return await signInBot(client, botToken)
|
return await signInBot(client, botToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
let sentCode = await sendCode(client, { phone })
|
try {
|
||||||
|
sentCode = await sendCode(client, {
|
||||||
|
phone,
|
||||||
|
futureAuthTokens: params.futureAuthTokens,
|
||||||
|
codeSettings: params.codeSettings,
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
if (tl.RpcError.is(e, 'SESSION_PASSWORD_NEEDED')) {
|
||||||
|
has2fa = true
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (params.forceSms && sentCode.type === 'app') {
|
if (sentCode) {
|
||||||
sentCode = await resendCode(client, { phone, phoneCodeHash: sentCode.phoneCodeHash })
|
if (params.forceSms && (sentCode.type === 'app' || sentCode.type === 'email')) {
|
||||||
|
sentCode = await resendCode(client, { phone: phone!, phoneCodeHash: sentCode.phoneCodeHash })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.codeSentCallback) {
|
if (params.codeSentCallback) {
|
||||||
await params.codeSentCallback(sentCode)
|
await params.codeSentCallback(sentCode)
|
||||||
} else {
|
} else {
|
||||||
console.log(`The confirmation code has been sent via ${sentCode.type}.`)
|
if (sentCode.type === 'email_required') {
|
||||||
|
throw new MtcuteError('Email login setup is required to sign in')
|
||||||
}
|
}
|
||||||
|
|
||||||
let has2fa = false
|
console.log(`The confirmation code has been sent via ${sentCode.type}.`)
|
||||||
|
}
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const code = await resolveMaybeDynamic(params.code)
|
const code = await resolveMaybeDynamic(params.code)
|
||||||
if (!code) throw new tl.RpcError(400, 'PHONE_CODE_EMPTY')
|
if (!code) throw new tl.RpcError(400, 'PHONE_CODE_EMPTY')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await signIn(client, { phone, phoneCodeHash: sentCode.phoneCodeHash, phoneCode: code })
|
return await signIn(client, { phone: phone!, phoneCodeHash: sentCode.phoneCodeHash, phoneCode: code })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!tl.RpcError.is(e)) throw e
|
if (!tl.RpcError.is(e)) throw e
|
||||||
|
|
||||||
|
@ -185,6 +216,7 @@ export async function start(
|
||||||
// if there was no error, code was valid, so it's either 2fa or signup
|
// if there was no error, code was valid, so it's either 2fa or signup
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (has2fa) {
|
if (has2fa) {
|
||||||
if (!params.password) {
|
if (!params.password) {
|
||||||
|
|
|
@ -10,9 +10,10 @@ import { resolvePeer } from '../users/resolve-peer.js'
|
||||||
* Some library logic depends on this, for example, the library will
|
* Some library logic depends on this, for example, the library will
|
||||||
* periodically ping the server to keep the updates flowing.
|
* periodically ping the server to keep the updates flowing.
|
||||||
*
|
*
|
||||||
* > **Warning**: Opening a chat with `openChat` method will make the library make additional requests every so often.
|
* > **Warning**: Opening a chat with `openChat` method will make the library make additional requests
|
||||||
* > Which means that you should **avoid opening more than 5-10 chats at once**, as it will probably trigger
|
* > every so often. Which means that you should **avoid opening more than 5-10 chats at once**,
|
||||||
* > server-side limits and you might start getting transport errors or even get banned.
|
* > as it will probably trigger server-side limits and you might start getting transport errors
|
||||||
|
* > or even get banned.
|
||||||
*
|
*
|
||||||
* @param chat Chat to open
|
* @param chat Chat to open
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -432,6 +432,14 @@ export class UpdatesManager {
|
||||||
|
|
||||||
log.debug('loaded initial state: pts=%d, qts=%d, date=%d, seq=%d', this.pts, this.qts, this.date, this.seq)
|
log.debug('loaded initial state: pts=%d, qts=%d, date=%d, seq=%d', this.pts, this.qts, this.date, this.seq)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (tl.RpcError.is(e, 'AUTH_KEY_UNREGISTERED')) {
|
||||||
|
// we are logged out, stop updates loop
|
||||||
|
lock.release()
|
||||||
|
this.stopLoop()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (this.client.isConnected) {
|
if (this.client.isConnected) {
|
||||||
log.error('failed to fetch updates state: %s', e)
|
log.error('failed to fetch updates state: %s', e)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue