Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,174 @@
import Foundation
import Postbox
import TelegramApi
import SwiftSignalKit
public struct AuthTransferExportedToken {
public let value: Data
public let validUntil: Int32
}
public enum ExportAuthTransferTokenError {
case generic
case limitExceeded
}
public enum ExportAuthTransferTokenResult {
case displayToken(AuthTransferExportedToken)
case changeAccountAndRetry(UnauthorizedAccount)
case loggedIn
case passwordRequested(UnauthorizedAccount)
}
func _internal_exportAuthTransferToken(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, otherAccountUserIds: [PeerId.Id], syncContacts: Bool) -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> {
return account.network.request(Api.functions.auth.exportLoginToken(apiId: account.networkArguments.apiId, apiHash: account.networkArguments.apiHash, exceptIds: otherAccountUserIds.map({ $0._internalGetInt64Value() })))
|> map(Optional.init)
|> `catch` { error -> Signal<Api.auth.LoginToken?, ExportAuthTransferTokenError> in
if error.errorDescription == "SESSION_PASSWORD_NEEDED" {
return account.network.request(Api.functions.account.getPassword(), automaticFloodWait: false)
|> mapError { error -> ExportAuthTransferTokenError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<Api.auth.LoginToken?, ExportAuthTransferTokenError> in
switch result {
case let .password(_, _, _, _, hint, _, _, _, _, _, _):
return account.postbox.transaction { transaction -> Api.auth.LoginToken? in
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .passwordEntry(hint: hint ?? "", number: nil, code: nil, suggestReset: false, syncContacts: syncContacts)))
return nil
}
|> castError(ExportAuthTransferTokenError.self)
}
}
} else {
return .fail(.generic)
}
}
|> mapToSignal { result -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> in
guard let result = result else {
return .single(.passwordRequested(account))
}
switch result {
case let .loginToken(expires, token):
return .single(.displayToken(AuthTransferExportedToken(value: token.makeData(), validUntil: expires)))
case let .loginTokenMigrateTo(dcId, token):
let updatedAccount = account.changedMasterDatacenterId(accountManager: accountManager, masterDatacenterId: dcId)
return updatedAccount
|> castError(ExportAuthTransferTokenError.self)
|> mapToSignal { updatedAccount -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> in
return updatedAccount.network.request(Api.functions.auth.importLoginToken(token: token))
|> map(Optional.init)
|> `catch` { error -> Signal<Api.auth.LoginToken?, ExportAuthTransferTokenError> in
if error.errorDescription == "SESSION_PASSWORD_NEEDED" {
return updatedAccount.network.request(Api.functions.account.getPassword(), automaticFloodWait: false)
|> mapError { error -> ExportAuthTransferTokenError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<Api.auth.LoginToken?, ExportAuthTransferTokenError> in
switch result {
case let .password(_, _, _, _, hint, _, _, _, _, _, _):
return updatedAccount.postbox.transaction { transaction -> Api.auth.LoginToken? in
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: updatedAccount.testingEnvironment, masterDatacenterId: updatedAccount.masterDatacenterId, contents: .passwordEntry(hint: hint ?? "", number: nil, code: nil, suggestReset: false, syncContacts: syncContacts)))
return nil
}
|> castError(ExportAuthTransferTokenError.self)
}
}
} else {
return .fail(.generic)
}
}
|> mapToSignal { result -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> in
guard let result = result else {
return .single(.passwordRequested(updatedAccount))
}
switch result {
case let .loginTokenSuccess(authorization):
switch authorization {
case let .authorization(_, _, _, futureAuthToken, user):
if let futureAuthToken = futureAuthToken {
storeFutureLoginToken(accountManager: accountManager, token: futureAuthToken.makeData())
}
return updatedAccount.postbox.transaction { transaction -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> in
let user = TelegramUser(user: user)
let state = AuthorizedAccountState(isTestingEnvironment: updatedAccount.testingEnvironment, masterDatacenterId: updatedAccount.masterDatacenterId, peerId: user.id, state: nil, invalidatedChannels: [])
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: updatedAccount.networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
return accountManager.transaction { transaction -> ExportAuthTransferTokenResult in
switchToAuthorizedAccount(transaction: transaction, account: updatedAccount, isSupportUser: false)
return .loggedIn
}
|> castError(ExportAuthTransferTokenError.self)
}
|> castError(ExportAuthTransferTokenError.self)
|> switchToLatest
default:
return .fail(.generic)
}
default:
return .single(.changeAccountAndRetry(updatedAccount))
}
}
}
case let .loginTokenSuccess(authorization):
switch authorization {
case let .authorization(_, _, _, futureAuthToken, user):
if let futureAuthToken = futureAuthToken {
storeFutureLoginToken(accountManager: accountManager, token: futureAuthToken.makeData())
}
return account.postbox.transaction { transaction -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> in
let user = TelegramUser(user: user)
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil, invalidatedChannels: [])
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
return accountManager.transaction { transaction -> ExportAuthTransferTokenResult in
switchToAuthorizedAccount(transaction: transaction, account: account, isSupportUser: false)
return .loggedIn
}
|> castError(ExportAuthTransferTokenError.self)
}
|> castError(ExportAuthTransferTokenError.self)
|> switchToLatest
case .authorizationSignUpRequired:
return .fail(.generic)
}
}
}
}
public enum ApproveAuthTransferTokenError {
case generic
case invalid
case expired
case alreadyAccepted
}
public func approveAuthTransferToken(account: Account, token: Data, activeSessionsContext: ActiveSessionsContext) -> Signal<RecentAccountSession, ApproveAuthTransferTokenError> {
return account.network.request(Api.functions.auth.acceptLoginToken(token: Buffer(data: token)))
|> mapError { error -> ApproveAuthTransferTokenError in
switch error.errorDescription {
case "AUTH_TOKEN_INVALID":
return .invalid
case "AUTH_TOKEN_EXPIRED":
return .expired
case "AUTH_TOKEN_ALREADY_ACCEPTED":
return .alreadyAccepted
default:
return .generic
}
}
|> mapToSignal { authorization -> Signal<RecentAccountSession, ApproveAuthTransferTokenError> in
let session = RecentAccountSession(apiAuthorization: authorization)
activeSessionsContext.addSession(session)
return .single(session)
}
}
@@ -0,0 +1,87 @@
import Foundation
import TelegramApi
import Postbox
import SwiftSignalKit
import MtProtoKit
public struct CancelAccountResetData: Equatable {
public let type: SentAuthorizationCodeType
public let hash: String
public let timeout: Int32?
public let nextType: AuthorizationCodeNextType?
}
public enum RequestCancelAccountResetDataError {
case limitExceeded
case generic
}
func _internal_requestCancelAccountResetData(network: Network, hash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
return network.request(Api.functions.account.sendConfirmPhoneCode(hash: hash, settings: .codeSettings(flags: 0, logoutTokens: nil, token: nil, appSandbox: nil)), automaticFloodWait: false)
|> mapError { error -> RequestCancelAccountResetDataError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else {
return .generic
}
}
|> mapToSignal { sentCode -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> in
switch sentCode {
case let .sentCode(_, type, phoneCodeHash, nextType, timeout):
var parsedNextType: AuthorizationCodeNextType?
if let nextType = nextType {
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
return .single(CancelAccountResetData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType))
case .sentCodeSuccess, .sentCodePaymentRequired:
return .never()
}
}
}
func _internal_requestNextCancelAccountResetOption(network: Network, phoneNumber: String, phoneCodeHash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
return network.request(Api.functions.auth.resendCode(flags: 0, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, reason: nil), automaticFloodWait: false)
|> mapError { error -> RequestCancelAccountResetDataError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else {
return .generic
}
}
|> mapToSignal { sentCode -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> in
switch sentCode {
case let .sentCode(_, type, phoneCodeHash, nextType, timeout):
var parsedNextType: AuthorizationCodeNextType?
if let nextType = nextType {
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
return .single(CancelAccountResetData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType))
case .sentCodeSuccess, .sentCodePaymentRequired:
return .never()
}
}
}
public enum CancelAccountResetError {
case generic
case invalidCode
case codeExpired
case limitExceeded
}
func _internal_requestCancelAccountReset(network: Network, phoneCodeHash: String, phoneCode: String) -> Signal<Never, CancelAccountResetError> {
return network.request(Api.functions.account.confirmPhone(phoneCodeHash: phoneCodeHash, phoneCode: phoneCode))
|> mapError { error -> CancelAccountResetError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription == "PHONE_CODE_INVALID" {
return .invalidCode
} else if error.errorDescription == "PHONE_CODE_EXPIRED" {
return .codeExpired
} else {
return .generic
}
}
|> ignoreValues
}
@@ -0,0 +1,58 @@
import Foundation
import SwiftSignalKit
import MtProtoKit
import TelegramApi
public enum ConfirmTwoStepRecoveryEmailError {
case invalidEmail
case invalidCode
case flood
case expired
case generic
}
func _internal_confirmTwoStepRecoveryEmail(network: Network, code: String) -> Signal<Never, ConfirmTwoStepRecoveryEmailError> {
return network.request(Api.functions.account.confirmPasswordEmail(code: code), automaticFloodWait: false)
|> mapError { error -> ConfirmTwoStepRecoveryEmailError in
if error.errorDescription == "EMAIL_INVALID" {
return .invalidEmail
} else if error.errorDescription == "CODE_INVALID" {
return .invalidCode
} else if error.errorDescription == "EMAIL_HASH_EXPIRED" {
return .expired
} else if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .flood
}
return .generic
}
|> ignoreValues
}
public enum ResendTwoStepRecoveryEmailError {
case flood
case generic
}
func _internal_resendTwoStepRecoveryEmail(network: Network) -> Signal<Never, ResendTwoStepRecoveryEmailError> {
return network.request(Api.functions.account.resendPasswordEmail(), automaticFloodWait: false)
|> mapError { error -> ResendTwoStepRecoveryEmailError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .flood
}
return .generic
}
|> ignoreValues
}
public enum CancelTwoStepRecoveryEmailError {
case generic
}
func _internal_cancelTwoStepRecoveryEmail(network: Network) -> Signal<Never, CancelTwoStepRecoveryEmailError> {
return network.request(Api.functions.account.cancelPasswordEmail(), automaticFloodWait: false)
|> mapError { _ -> CancelTwoStepRecoveryEmailError in
return .generic
}
|> ignoreValues
}
@@ -0,0 +1,284 @@
import SwiftSignalKit
import Postbox
import TelegramApi
import MtProtoKit
public enum TelegramEngineAuthorizationState {
case unauthorized(UnauthorizedAccountState)
case authorized
}
public extension TelegramEngineUnauthorized {
final class Auth {
private let account: UnauthorizedAccount
init(account: UnauthorizedAccount) {
self.account = account
}
public func exportAuthTransferToken(accountManager: AccountManager<TelegramAccountManagerTypes>, otherAccountUserIds: [PeerId.Id], syncContacts: Bool) -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> {
return _internal_exportAuthTransferToken(accountManager: accountManager, account: self.account, otherAccountUserIds: otherAccountUserIds, syncContacts: syncContacts)
}
public func twoStepAuthData() -> Signal<TwoStepAuthData, MTRpcError> {
return _internal_twoStepAuthData(self.account.network)
}
public func test() -> Signal<Bool, String> {
return _internal_test(self.account.network)
}
public func updateTwoStepVerificationPassword(currentPassword: String?, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<UpdateTwoStepVerificationPasswordResult, UpdateTwoStepVerificationPasswordError> {
return _internal_updateTwoStepVerificationPassword(network: self.account.network, currentPassword: currentPassword, updatedPassword: updatedPassword)
}
public func requestTwoStepVerificationPasswordRecoveryCode() -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> {
return _internal_requestTwoStepVerificationPasswordRecoveryCode(network: self.account.network)
}
public func checkPasswordRecoveryCode(code: String) -> Signal<Never, PasswordRecoveryError> {
return _internal_checkPasswordRecoveryCode(network: self.account.network, code: code)
}
public func performPasswordRecovery(code: String, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<RecoveredAccountData, PasswordRecoveryError> {
return _internal_performPasswordRecovery(network: self.account.network, code: code, updatedPassword: updatedPassword)
}
public func resendTwoStepRecoveryEmail() -> Signal<Never, ResendTwoStepRecoveryEmailError> {
return _internal_resendTwoStepRecoveryEmail(network: self.account.network)
}
public func uploadedPeerVideo(resource: MediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: nil, resource: resource)
}
public func reportMissingCode(phoneNumber: String, phoneCodeHash: String, mnc: String) -> Signal<Never, ReportMissingCodeError> {
return _internal_reportMissingCode(network: self.account.network, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, mnc: mnc)
}
public func requestPasskeyLoginData(apiId: Int32, apiHash: String) -> Signal<String?, NoError> {
return _internal_requestPasskeyLoginData(network: self.account.network, apiId: apiId, apiHash: apiHash)
}
public func state() -> Signal<TelegramEngineAuthorizationState?, NoError> {
return self.account.postbox.stateView()
|> map { view -> TelegramEngineAuthorizationState? in
if let state = view.state as? UnauthorizedAccountState {
return .unauthorized(state)
} else if let _ = view.state as? AuthorizedAccountState {
return .authorized
} else {
return nil
}
}
}
public func setState(state: UnauthorizedAccountState) -> Signal<Never, NoError> {
return self.account.postbox.transaction { transaction -> Void in
transaction.setState(state)
}
|> ignoreValues
}
}
}
public enum DeleteAccountError {
case generic
}
public extension TelegramEngine {
final class Auth {
private let account: Account
init(account: Account) {
self.account = account
}
public func twoStepAuthData() -> Signal<TwoStepAuthData, MTRpcError> {
return _internal_twoStepAuthData(self.account.network)
}
public func updateTwoStepVerificationPassword(currentPassword: String?, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<UpdateTwoStepVerificationPasswordResult, UpdateTwoStepVerificationPasswordError> {
return _internal_updateTwoStepVerificationPassword(network: self.account.network, currentPassword: currentPassword, updatedPassword: updatedPassword)
}
public func deleteAccount(reason: String, password: String?) -> Signal<Never, DeleteAccountError> {
let network = self.account.network
let passwordSignal: Signal<Api.InputCheckPasswordSRP?, DeleteAccountError>
if let password = password {
passwordSignal = _internal_twoStepAuthData(network)
|> mapError { _ -> DeleteAccountError in
return .generic
}
|> mapToSignal { authData -> Signal<Api.InputCheckPasswordSRP?, DeleteAccountError> in
if let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData {
guard let kdfResult = passwordKDF(encryptionProvider: network.encryptionProvider, password: password, derivation: currentPasswordDerivation, srpSessionData: srpSessionData) else {
return .fail(.generic)
}
return .single(.inputCheckPasswordSRP(srpId: kdfResult.id, A: Buffer(data: kdfResult.A), M1: Buffer(data: kdfResult.M1)))
} else {
return .single(nil)
}
}
} else {
passwordSignal = .single(nil)
}
return passwordSignal
|> mapToSignal { password -> Signal<Never, DeleteAccountError> in
var flags: Int32 = 0
if let _ = password {
flags |= (1 << 0)
}
return self.account.network.request(Api.functions.account.deleteAccount(flags: flags, reason: reason, password: password))
|> mapError { _ -> DeleteAccountError in
return .generic
}
|> ignoreValues
}
}
public func updateTwoStepVerificationEmail(currentPassword: String, updatedEmail: String) -> Signal<UpdateTwoStepVerificationPasswordResult, UpdateTwoStepVerificationPasswordError> {
return _internal_updateTwoStepVerificationEmail(network: self.account.network, currentPassword: currentPassword, updatedEmail: updatedEmail)
}
public func confirmTwoStepRecoveryEmail(code: String) -> Signal<Never, ConfirmTwoStepRecoveryEmailError> {
return _internal_confirmTwoStepRecoveryEmail(network: self.account.network, code: code)
}
public func resendTwoStepRecoveryEmail() -> Signal<Never, ResendTwoStepRecoveryEmailError> {
return _internal_resendTwoStepRecoveryEmail(network: self.account.network)
}
public func cancelTwoStepRecoveryEmail() -> Signal<Never, CancelTwoStepRecoveryEmailError> {
return _internal_cancelTwoStepRecoveryEmail(network: self.account.network)
}
public func twoStepVerificationConfiguration() -> Signal<TwoStepVerificationConfiguration, NoError> {
return _internal_twoStepVerificationConfiguration(account: self.account)
}
public func requestTwoStepVerifiationSettings(password: String) -> Signal<TwoStepVerificationSettings, AuthorizationPasswordVerificationError> {
return _internal_requestTwoStepVerifiationSettings(network: self.account.network, password: password)
}
public func requestTwoStepVerificationPasswordRecoveryCode() -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> {
return _internal_requestTwoStepVerificationPasswordRecoveryCode(network: self.account.network)
}
public func performPasswordRecovery(code: String, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<RecoveredAccountData, PasswordRecoveryError> {
return _internal_performPasswordRecovery(network: self.account.network, code: code, updatedPassword: updatedPassword)
}
public func cachedTwoStepPasswordToken() -> Signal<TemporaryTwoStepPasswordToken?, NoError> {
return _internal_cachedTwoStepPasswordToken(postbox: self.account.postbox)
}
public func cacheTwoStepPasswordToken(token: TemporaryTwoStepPasswordToken?) -> Signal<Void, NoError> {
return _internal_cacheTwoStepPasswordToken(postbox: self.account.postbox, token: token)
}
public func requestTemporaryTwoStepPasswordToken(password: String, period: Int32, requiresBiometrics: Bool) -> Signal<TemporaryTwoStepPasswordToken, AuthorizationPasswordVerificationError> {
return _internal_requestTemporaryTwoStepPasswordToken(account: self.account, password: password, period: period, requiresBiometrics: requiresBiometrics)
}
public func checkPasswordRecoveryCode(code: String) -> Signal<Never, PasswordRecoveryError> {
return _internal_checkPasswordRecoveryCode(network: self.account.network, code: code)
}
public func requestTwoStepPasswordReset() -> Signal<RequestTwoStepPasswordResetResult, NoError> {
return _internal_requestTwoStepPasswordReset(network: self.account.network)
}
public func declineTwoStepPasswordReset() -> Signal<Never, NoError> {
return _internal_declineTwoStepPasswordReset(network: self.account.network)
}
public func requestCancelAccountResetData(hash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
return _internal_requestCancelAccountResetData(network: self.account.network, hash: hash)
}
public func requestNextCancelAccountResetOption(phoneNumber: String, phoneCodeHash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
return _internal_requestNextCancelAccountResetOption(network: self.account.network, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash)
}
public func requestCancelAccountReset(phoneCodeHash: String, phoneCode: String) -> Signal<Never, CancelAccountResetError> {
return _internal_requestCancelAccountReset(network: self.account.network, phoneCodeHash: phoneCodeHash, phoneCode: phoneCode)
}
public func invalidateLoginCodes(codes: [String]) -> Signal<Never, NoError> {
return _internal_invalidateLoginCodes(network: self.account.network, codes: codes)
}
public func reportMissingCode(phoneNumber: String, phoneCodeHash: String, mnc: String) -> Signal<Never, ReportMissingCodeError> {
return _internal_reportMissingCode(network: self.account.network, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, mnc: mnc)
}
public func passkeysData() -> Signal<[TelegramPasskey], NoError> {
return _internal_passkeysData(network: self.account.network)
}
public func requestPasskeyRegistration() -> Signal<String?, NoError> {
return _internal_requestPasskeyRegistration(network: self.account.network)
}
public func requestCreatePasskey(id: String, clientData: String, attestationObject: Data) -> Signal<TelegramPasskey?, NoError> {
return _internal_requestCreatePasskey(network: self.account.network, id: id, clientData: clientData, attestationObject: attestationObject)
}
public func deletePasskey(id: String) -> Signal<Never, NoError> {
return _internal_deletePasskey(network: self.account.network, id: id)
}
}
}
public extension SomeTelegramEngine {
final class Auth {
private let engine: SomeTelegramEngine
init(engine: SomeTelegramEngine) {
self.engine = engine
}
public func twoStepAuthData() -> Signal<TwoStepAuthData, MTRpcError> {
switch self.engine {
case let .authorized(engine):
return engine.auth.twoStepAuthData()
case let .unauthorized(engine):
return engine.auth.twoStepAuthData()
}
}
public func updateTwoStepVerificationPassword(currentPassword: String?, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<UpdateTwoStepVerificationPasswordResult, UpdateTwoStepVerificationPasswordError> {
switch self.engine {
case let .authorized(engine):
return engine.auth.updateTwoStepVerificationPassword(currentPassword: currentPassword, updatedPassword: updatedPassword)
case let .unauthorized(engine):
return engine.auth.updateTwoStepVerificationPassword(currentPassword: currentPassword, updatedPassword: updatedPassword)
}
}
public func requestTwoStepVerificationPasswordRecoveryCode() -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> {
switch self.engine {
case let .authorized(engine):
return engine.auth.requestTwoStepVerificationPasswordRecoveryCode()
case let .unauthorized(engine):
return engine.auth.requestTwoStepVerificationPasswordRecoveryCode()
}
}
public func checkPasswordRecoveryCode(code: String) -> Signal<Never, PasswordRecoveryError> {
switch self.engine {
case let .authorized(engine):
return engine.auth.checkPasswordRecoveryCode(code: code)
case let .unauthorized(engine):
return engine.auth.checkPasswordRecoveryCode(code: code)
}
}
}
var auth: Auth {
return Auth(engine: self)
}
}
@@ -0,0 +1,449 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum TwoStepVerificationConfiguration {
case notSet(pendingEmail: TwoStepVerificationPendingEmail?)
case set(hint: String, hasRecoveryEmail: Bool, pendingEmail: TwoStepVerificationPendingEmail?, hasSecureValues: Bool, pendingResetTimestamp: Int32?)
}
func _internal_twoStepVerificationConfiguration(account: Account) -> Signal<TwoStepVerificationConfiguration, NoError> {
return account.network.request(Api.functions.account.getPassword())
|> retryRequest
|> map { result -> TwoStepVerificationConfiguration in
switch result {
case let .password(flags, currentAlgo, _, _, hint, emailUnconfirmedPattern, _, _, _, pendingResetDate, _):
if currentAlgo != nil {
return .set(hint: hint ?? "", hasRecoveryEmail: (flags & (1 << 0)) != 0, pendingEmail: emailUnconfirmedPattern.flatMap({ TwoStepVerificationPendingEmail(pattern: $0, codeLength: nil) }), hasSecureValues: (flags & (1 << 1)) != 0, pendingResetTimestamp: pendingResetDate)
} else {
return .notSet(pendingEmail: emailUnconfirmedPattern.flatMap({ TwoStepVerificationPendingEmail(pattern: $0, codeLength: nil) }))
}
}
}
}
public struct TwoStepVerificationSecureSecret {
public let data: Data
public let derivation: TwoStepSecurePasswordDerivation
public let id: Int64
}
public struct TwoStepVerificationSettings {
public let email: String
public let secureSecret: TwoStepVerificationSecureSecret?
}
func _internal_requestTwoStepVerifiationSettings(network: Network, password: String) -> Signal<TwoStepVerificationSettings, AuthorizationPasswordVerificationError> {
return _internal_twoStepAuthData(network)
|> mapError { error -> AuthorizationPasswordVerificationError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription == "PASSWORD_HASH_INVALID" {
return .invalidPassword
} else {
return .generic
}
}
|> mapToSignal { authData -> Signal<TwoStepVerificationSettings, AuthorizationPasswordVerificationError> in
guard let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData else {
return .fail(.generic)
}
guard let kdfResult = passwordKDF(encryptionProvider: network.encryptionProvider, password: password, derivation: currentPasswordDerivation, srpSessionData: srpSessionData) else {
return .fail(.generic)
}
return network.request(Api.functions.account.getPasswordSettings(password: .inputCheckPasswordSRP(srpId: kdfResult.id, A: Buffer(data: kdfResult.A), M1: Buffer(data: kdfResult.M1))), automaticFloodWait: false)
|> mapError { error -> AuthorizationPasswordVerificationError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription == "PASSWORD_HASH_INVALID" {
return .invalidPassword
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<TwoStepVerificationSettings, AuthorizationPasswordVerificationError> in
switch result {
case let .passwordSettings(_, email, secureSettings):
var parsedSecureSecret: TwoStepVerificationSecureSecret?
if let secureSettings = secureSettings {
switch secureSettings {
case let .secureSecretSettings(secureAlgo, secureSecret, secureSecretId):
if secureSecret.size != 32 {
return .fail(.generic)
}
parsedSecureSecret = TwoStepVerificationSecureSecret(data: secureSecret.makeData(), derivation: TwoStepSecurePasswordDerivation(secureAlgo), id: secureSecretId)
}
}
return .single(TwoStepVerificationSettings(email: email ?? "", secureSecret: parsedSecureSecret))
}
}
}
}
public enum UpdateTwoStepVerificationPasswordError {
case generic
case invalidEmail
}
public struct TwoStepVerificationPendingEmail: Equatable {
public let pattern: String
public let codeLength: Int32?
public init(pattern: String, codeLength: Int32?) {
self.pattern = pattern
self.codeLength = codeLength
}
}
public enum UpdateTwoStepVerificationPasswordResult {
case none
case password(password: String, pendingEmail: TwoStepVerificationPendingEmail?)
}
public enum UpdatedTwoStepVerificationPassword {
case none
case password(password: String, hint: String, email: String?)
}
func _internal_updateTwoStepVerificationPassword(network: Network, currentPassword: String?, updatedPassword: UpdatedTwoStepVerificationPassword) -> Signal<UpdateTwoStepVerificationPasswordResult, UpdateTwoStepVerificationPasswordError> {
return _internal_twoStepAuthData(network)
|> mapError { _ -> UpdateTwoStepVerificationPasswordError in
return .generic
}
|> mapToSignal { authData -> Signal<TwoStepVerificationSecureSecret?, UpdateTwoStepVerificationPasswordError> in
if let _ = authData.currentPasswordDerivation {
return _internal_requestTwoStepVerifiationSettings(network: network, password: currentPassword ?? "")
|> mapError { _ -> UpdateTwoStepVerificationPasswordError in
return .generic
}
|> map { settings in
return settings.secureSecret
}
} else {
return .single(nil)
}
}
|> mapToSignal { secureSecret -> Signal<(TwoStepAuthData, TwoStepVerificationSecureSecret?), UpdateTwoStepVerificationPasswordError> in
return _internal_twoStepAuthData(network)
|> mapError { _ -> UpdateTwoStepVerificationPasswordError in
return .generic
}
|> map { authData -> (TwoStepAuthData, TwoStepVerificationSecureSecret?) in
return (authData, secureSecret)
}
}
|> mapToSignal { authData, secureSecret -> Signal<UpdateTwoStepVerificationPasswordResult, UpdateTwoStepVerificationPasswordError> in
let checkPassword: Api.InputCheckPasswordSRP
if let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData {
if let kdfResult = passwordKDF(encryptionProvider: network.encryptionProvider, password: currentPassword ?? "", derivation: currentPasswordDerivation, srpSessionData: srpSessionData) {
checkPassword = .inputCheckPasswordSRP(srpId: kdfResult.id, A: Buffer(data: kdfResult.A), M1: Buffer(data: kdfResult.M1))
} else {
return .fail(.generic)
}
} else {
checkPassword = .inputCheckPasswordEmpty
}
switch updatedPassword {
case .none:
var flags: Int32 = (1 << 1)
if authData.currentPasswordDerivation != nil {
flags |= (1 << 0)
}
return network.request(Api.functions.account.updatePasswordSettings(password: checkPassword, newSettings: .passwordInputSettings(flags: flags, newAlgo: .passwordKdfAlgoUnknown, newPasswordHash: Buffer(data: Data()), hint: "", email: "", newSecureSettings: nil)), automaticFloodWait: true)
|> mapError { _ -> UpdateTwoStepVerificationPasswordError in
return .generic
}
|> map { _ -> UpdateTwoStepVerificationPasswordResult in
return .none
}
case let .password(password, hint, email):
var flags: Int32 = 1 << 0
if email != nil {
flags |= (1 << 1)
}
guard let (updatedPasswordHash, updatedPasswordDerivation) = passwordUpdateKDF(encryptionProvider: network.encryptionProvider, password: password, derivation: authData.nextPasswordDerivation) else {
return .fail(.generic)
}
var updatedSecureSecret: TwoStepVerificationSecureSecret?
if let encryptedSecret = secureSecret {
if let decryptedSecret = decryptedSecureSecret(encryptedSecretData: encryptedSecret.data, password: currentPassword ?? "", derivation: encryptedSecret.derivation, id: encryptedSecret.id) {
if let (data, derivation, id) = encryptedSecureSecret(secretData: decryptedSecret, password: password, inputDerivation: authData.nextSecurePasswordDerivation) {
updatedSecureSecret = TwoStepVerificationSecureSecret(data: data, derivation: derivation, id: id)
} else {
return .fail(.generic)
}
} else {
return .fail(.generic)
}
}
var updatedSecureSettings: Api.SecureSecretSettings?
if let updatedSecureSecret = updatedSecureSecret {
flags |= 1 << 2
updatedSecureSettings = .secureSecretSettings(secureAlgo: updatedSecureSecret.derivation.apiAlgo, secureSecret: Buffer(data: updatedSecureSecret.data), secureSecretId: updatedSecureSecret.id)
}
return network.request(Api.functions.account.updatePasswordSettings(password: checkPassword, newSettings: Api.account.PasswordInputSettings.passwordInputSettings(flags: flags, newAlgo: updatedPasswordDerivation.apiAlgo, newPasswordHash: Buffer(data: updatedPasswordHash), hint: hint, email: email, newSecureSettings: updatedSecureSettings)), automaticFloodWait: false)
|> map { _ -> UpdateTwoStepVerificationPasswordResult in
return .password(password: password, pendingEmail: nil)
}
|> `catch` { error -> Signal<UpdateTwoStepVerificationPasswordResult, MTRpcError> in
if error.errorDescription.hasPrefix("EMAIL_UNCONFIRMED") {
var codeLength: Int32?
if error.errorDescription.hasPrefix("EMAIL_UNCONFIRMED_") {
if let value = Int32(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "EMAIL_UNCONFIRMED_".count)...]) {
codeLength = value
}
}
return _internal_twoStepAuthData(network)
|> map { result -> UpdateTwoStepVerificationPasswordResult in
return .password(password: password, pendingEmail: result.unconfirmedEmailPattern.flatMap({ TwoStepVerificationPendingEmail(pattern: $0, codeLength: codeLength) }))
}
} else {
return .fail(error)
}
}
|> mapError { error -> UpdateTwoStepVerificationPasswordError in
if error.errorDescription == "EMAIL_INVALID" {
return .invalidEmail
} else {
return .generic
}
}
}
}
}
enum UpdateTwoStepVerificationSecureSecretResult {
case success
}
enum UpdateTwoStepVerificationSecureSecretError {
case generic
}
func updateTwoStepVerificationSecureSecret(network: Network, password: String, secret: Data) -> Signal<UpdateTwoStepVerificationSecureSecretResult, UpdateTwoStepVerificationSecureSecretError> {
return _internal_twoStepAuthData(network)
|> mapError { _ -> UpdateTwoStepVerificationSecureSecretError in
return .generic
}
|> mapToSignal { authData -> Signal<UpdateTwoStepVerificationSecureSecretResult, UpdateTwoStepVerificationSecureSecretError> in
guard let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData else {
return .fail(.generic)
}
guard let kdfResult = passwordKDF(encryptionProvider: network.encryptionProvider, password: password, derivation: currentPasswordDerivation, srpSessionData: srpSessionData) else {
return .fail(.generic)
}
let checkPassword: Api.InputCheckPasswordSRP = .inputCheckPasswordSRP(srpId: kdfResult.id, A: Buffer(data: kdfResult.A), M1: Buffer(data: kdfResult.M1))
guard let (encryptedSecret, secretDerivation, secretId) = encryptedSecureSecret(secretData: secret, password: password, inputDerivation: authData.nextSecurePasswordDerivation) else {
return .fail(.generic)
}
let flags: Int32 = (1 << 2)
return network.request(Api.functions.account.updatePasswordSettings(password: checkPassword, newSettings: .passwordInputSettings(flags: flags, newAlgo: nil, newPasswordHash: nil, hint: "", email: "", newSecureSettings: .secureSecretSettings(secureAlgo: secretDerivation.apiAlgo, secureSecret: Buffer(data: encryptedSecret), secureSecretId: secretId))), automaticFloodWait: true)
|> mapError { _ -> UpdateTwoStepVerificationSecureSecretError in
return .generic
}
|> map { _ -> UpdateTwoStepVerificationSecureSecretResult in
return .success
}
}
}
func _internal_updateTwoStepVerificationEmail(network: Network, currentPassword: String, updatedEmail: String) -> Signal<UpdateTwoStepVerificationPasswordResult, UpdateTwoStepVerificationPasswordError> {
return _internal_twoStepAuthData(network)
|> mapError { _ -> UpdateTwoStepVerificationPasswordError in
return .generic
}
|> mapToSignal { authData -> Signal<UpdateTwoStepVerificationPasswordResult, UpdateTwoStepVerificationPasswordError> in
let checkPassword: Api.InputCheckPasswordSRP
if let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData {
guard let kdfResult = passwordKDF(encryptionProvider: network.encryptionProvider, password: currentPassword, derivation: currentPasswordDerivation, srpSessionData: srpSessionData) else {
return .fail(.generic)
}
checkPassword = .inputCheckPasswordSRP(srpId: kdfResult.id, A: Buffer(data: kdfResult.A), M1: Buffer(data: kdfResult.M1))
} else {
checkPassword = .inputCheckPasswordEmpty
}
let flags: Int32 = 1 << 1
return network.request(Api.functions.account.updatePasswordSettings(password: checkPassword, newSettings: Api.account.PasswordInputSettings.passwordInputSettings(flags: flags, newAlgo: nil, newPasswordHash: nil, hint: nil, email: updatedEmail, newSecureSettings: nil)), automaticFloodWait: false)
|> map { _ -> UpdateTwoStepVerificationPasswordResult in
return .password(password: currentPassword, pendingEmail: nil)
}
|> `catch` { error -> Signal<UpdateTwoStepVerificationPasswordResult, MTRpcError> in
if error.errorDescription.hasPrefix("EMAIL_UNCONFIRMED") {
return _internal_twoStepAuthData(network)
|> map { result -> UpdateTwoStepVerificationPasswordResult in
var codeLength: Int32?
if error.errorDescription.hasPrefix("EMAIL_UNCONFIRMED_") {
if let value = Int32(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "EMAIL_UNCONFIRMED_".count)...]) {
codeLength = value
}
}
return .password(password: currentPassword, pendingEmail: result.unconfirmedEmailPattern.flatMap({ TwoStepVerificationPendingEmail(pattern: $0, codeLength: codeLength) }))
}
} else {
return .fail(error)
}
}
|> mapError { error -> UpdateTwoStepVerificationPasswordError in
if error.errorDescription == "EMAIL_INVALID" {
return .invalidEmail
} else {
return .generic
}
}
}
}
public enum RequestTwoStepVerificationPasswordRecoveryCodeError {
case generic
case limitExceeded
}
func _internal_requestTwoStepVerificationPasswordRecoveryCode(network: Network) -> Signal<String, RequestTwoStepVerificationPasswordRecoveryCodeError> {
return network.request(Api.functions.auth.requestPasswordRecovery(), automaticFloodWait: false)
|> mapError { error -> RequestTwoStepVerificationPasswordRecoveryCodeError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription.hasPrefix("PASSWORD_RECOVERY_NA") {
return .generic
} else {
return .generic
}
}
|> map { result -> String in
switch result {
case let .passwordRecovery(emailPattern):
return emailPattern
}
}
}
public enum RecoverTwoStepVerificationPasswordError {
case generic
case codeExpired
case limitExceeded
case invalidCode
}
func _internal_cachedTwoStepPasswordToken(postbox: Postbox) -> Signal<TemporaryTwoStepPasswordToken?, NoError> {
return postbox.transaction { transaction -> TemporaryTwoStepPasswordToken? in
let key = ValueBoxKey(length: 1)
key.setUInt8(0, value: 0)
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedTwoStepToken, key: key))?.get(TemporaryTwoStepPasswordToken.self)
}
}
func _internal_cacheTwoStepPasswordToken(postbox: Postbox, token: TemporaryTwoStepPasswordToken?) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Void in
let key = ValueBoxKey(length: 1)
key.setUInt8(0, value: 0)
if let token = token.flatMap(CodableEntry.init) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedTwoStepToken, key: key), entry: token)
} else {
transaction.removeItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedTwoStepToken, key: key))
}
}
}
func _internal_requestTemporaryTwoStepPasswordToken(account: Account, password: String, period: Int32, requiresBiometrics: Bool) -> Signal<TemporaryTwoStepPasswordToken, AuthorizationPasswordVerificationError> {
return _internal_twoStepAuthData(account.network)
|> mapToSignal { authData -> Signal<TemporaryTwoStepPasswordToken, MTRpcError> in
guard let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData else {
return .fail(MTRpcError(errorCode: 400, errorDescription: "NO_PASSWORD"))
}
guard let kdfResult = passwordKDF(encryptionProvider: account.network.encryptionProvider, password: password, derivation: currentPasswordDerivation, srpSessionData: srpSessionData) else {
return .fail(MTRpcError(errorCode: 400, errorDescription: "KDF_ERROR"))
}
let checkPassword: Api.InputCheckPasswordSRP = .inputCheckPasswordSRP(srpId: kdfResult.id, A: Buffer(data: kdfResult.A), M1: Buffer(data: kdfResult.M1))
return account.network.request(Api.functions.account.getTmpPassword(password: checkPassword, period: period), automaticFloodWait: false)
|> map { result -> TemporaryTwoStepPasswordToken in
switch result {
case let .tmpPassword(tmpPassword, validUntil):
return TemporaryTwoStepPasswordToken(token: tmpPassword.makeData(), validUntilDate: validUntil, requiresBiometrics: requiresBiometrics)
}
}
}
|> `catch` { error -> Signal<TemporaryTwoStepPasswordToken, AuthorizationPasswordVerificationError> in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .fail(.limitExceeded)
} else if error.errorDescription == "PASSWORD_HASH_INVALID" {
return .fail(.invalidPassword)
} else {
return .fail(.generic)
}
}
}
public enum RequestTwoStepPasswordResetResult {
public enum ErrorReason {
case generic
case limitExceeded(retryAtTimestamp: Int32?)
}
case done
case waitingForReset(resetAtTimestamp: Int32)
case declined
case error(reason: ErrorReason)
}
func _internal_requestTwoStepPasswordReset(network: Network) -> Signal<RequestTwoStepPasswordResetResult, NoError> {
return network.request(Api.functions.account.resetPassword(), automaticFloodWait: false)
|> map { result -> RequestTwoStepPasswordResetResult in
switch result {
case let .resetPasswordFailedWait(retryDate):
return .error(reason: .limitExceeded(retryAtTimestamp: retryDate))
case .resetPasswordOk:
return .done
case let .resetPasswordRequestedWait(untilDate):
return .waitingForReset(resetAtTimestamp: untilDate)
}
}
|> `catch` { error -> Signal<RequestTwoStepPasswordResetResult, NoError> in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .single(.error(reason: .limitExceeded(retryAtTimestamp: nil)))
} else if error.errorDescription.hasPrefix("RESET_WAIT_") {
if let remainingSeconds = Int32(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "RESET_WAIT_".count)...]) {
let timestamp = Int32(network.globalTime)
return .single(.waitingForReset(resetAtTimestamp: timestamp + remainingSeconds))
} else {
return .single(.error(reason: .generic))
}
} else if error.errorDescription.hasPrefix("RESET_PREVIOUS_WAIT_") {
if let remainingSeconds = Int32(error.errorDescription[error.errorDescription.index(error.errorDescription.startIndex, offsetBy: "RESET_PREVIOUS_WAIT_".count)...]) {
let timestamp = Int32(network.globalTime)
return .single(.waitingForReset(resetAtTimestamp: timestamp + remainingSeconds))
} else {
return .single(.error(reason: .generic))
}
} else if error.errorDescription == "RESET_PREVIOUS_DECLINE" {
return .single(.declined)
} else {
return .single(.error(reason: .generic))
}
}
}
func _internal_declineTwoStepPasswordReset(network: Network) -> Signal<Never, NoError> {
return network.request(Api.functions.account.declinePasswordReset())
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> ignoreValues
}