mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
@@ -496,6 +496,7 @@ associated_domains_fragment = "" if telegram_bundle_id not in official_bundle_id
|
||||
<string>applinks:t.me</string>
|
||||
<string>applinks:*.t.me</string>
|
||||
<string>webcredentials:t.me</string>
|
||||
<string>webcredentials:telegram.org</string>
|
||||
</array>
|
||||
"""
|
||||
|
||||
|
||||
@@ -324,6 +324,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
||||
accountManager: self.sharedContext.accountManager,
|
||||
account: self.account,
|
||||
passkey: passkey,
|
||||
foreignDatacenter: nil,
|
||||
forcedPasswordSetupNotice: { value in
|
||||
guard let entry = CodableEntry(ApplicationSpecificCounterNotice(value: value)) else {
|
||||
return nil
|
||||
@@ -332,7 +333,14 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
||||
},
|
||||
syncContacts: syncContacts
|
||||
)
|
||||
|> deliverOnMainQueue).startStrict(next: { _ in
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if result.updatedAccount !== self.account {
|
||||
self.account = result.updatedAccount
|
||||
self.inAppPurchaseManager = InAppPurchaseManager(engine: .unauthorized(self.engine))
|
||||
}
|
||||
}, error: { [weak self, weak controller] error in
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self, let controller {
|
||||
|
||||
@@ -157,6 +157,12 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF
|
||||
strongSelf.account = account
|
||||
strongSelf.accountUpdated?(account)
|
||||
}
|
||||
self.controllerNode.retryPasskey = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.loadAndPresentPasskey(force: true)
|
||||
}
|
||||
|
||||
if let (code, name, number) = self.currentData {
|
||||
self.controllerNode.codeAndNumber = (code, name, number)
|
||||
@@ -194,7 +200,11 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF
|
||||
self.controllerNode.updateCountryCode()
|
||||
}
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
self.loadAndPresentPasskey(force: false)
|
||||
}
|
||||
|
||||
private func loadAndPresentPasskey(force: Bool) {
|
||||
if #available(iOS 16.0, *) {
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self, let account = self.account else {
|
||||
return
|
||||
@@ -235,7 +245,11 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF
|
||||
let authController = ASAuthorizationController(authorizationRequests: [platformKeyRequest])
|
||||
authController.delegate = self
|
||||
authController.presentationContextProvider = self
|
||||
authController.performRequests()
|
||||
if force {
|
||||
authController.performRequests()
|
||||
} else {
|
||||
authController.performRequests(options: [.preferImmediatelyAvailableCredentials])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,6 +304,12 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF
|
||||
}
|
||||
|
||||
public func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: any Error) {
|
||||
if (error as NSError).domain == "com.apple.AuthenticationServices.AuthorizationError" && (error as NSError).code == 1001 {
|
||||
self.controllerNode.updateDisplayPasskeyLoginOption()
|
||||
if let validLayout = self.validLayout {
|
||||
self.containerLayoutUpdated(validLayout, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
|
||||
|
||||
@@ -15,6 +15,7 @@ import TelegramAnimatedStickerNode
|
||||
import SolidRoundedButtonNode
|
||||
import AuthorizationUtils
|
||||
import ManagedAnimationNode
|
||||
import Markdown
|
||||
|
||||
private final class PhoneAndCountryNode: ASDisplayNode {
|
||||
let strings: PresentationStrings
|
||||
@@ -312,7 +313,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
private let managedAnimationNode: ManagedPhoneAnimationNode
|
||||
private let titleNode: ASTextNode
|
||||
private let titleActivateAreaNode: AccessibilityAreaNode
|
||||
private let noticeNode: ASTextNode
|
||||
private let noticeNode: ImmediateTextNode
|
||||
private let noticeActivateAreaNode: AccessibilityAreaNode
|
||||
private let phoneAndCountryNode: PhoneAndCountryNode
|
||||
private let contactSyncNode: ContactSyncNode
|
||||
@@ -323,6 +324,8 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
private let tokenEventsDisposable = MetaDisposable()
|
||||
var accountUpdated: ((UnauthorizedAccount) -> Void)?
|
||||
|
||||
var retryPasskey: (() -> Void)?
|
||||
|
||||
private let debugAction: () -> Void
|
||||
|
||||
var currentNumber: String {
|
||||
@@ -405,7 +408,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
self.titleActivateAreaNode = AccessibilityAreaNode()
|
||||
self.titleActivateAreaNode.accessibilityTraits = .staticText
|
||||
|
||||
self.noticeNode = ASTextNode()
|
||||
self.noticeNode = ImmediateTextNode()
|
||||
self.noticeNode.maximumNumberOfLines = 0
|
||||
self.noticeNode.isUserInteractionEnabled = true
|
||||
self.noticeNode.displaysAsynchronously = false
|
||||
@@ -443,6 +446,23 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
self.addSubnode(self.managedAnimationNode)
|
||||
self.contactSyncNode.isHidden = true
|
||||
|
||||
self.noticeNode.highlightAttributeAction = { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
|
||||
return NSAttributedString.Key(rawValue: "URL")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
self.noticeNode.tapAttributeAction = { [weak self] attributes, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] as? String {
|
||||
self.retryPasskey?()
|
||||
}
|
||||
}
|
||||
self.noticeNode.linkHighlightColor = theme.list.itemAccentColor.withAlphaComponent(0.2)
|
||||
|
||||
self.phoneAndCountryNode.selectCountryCode = { [weak self] in
|
||||
self?.selectCountryCode?()
|
||||
}
|
||||
@@ -484,7 +504,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
super.didLoad()
|
||||
|
||||
self.titleNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.debugTap(_:))))
|
||||
#if DEBUG
|
||||
#if DEBUG && false
|
||||
self.noticeNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.debugQrTap(_:))))
|
||||
#endif
|
||||
}
|
||||
@@ -555,6 +575,28 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
let _ = self.phoneAndCountryNode.processNumberChange(number: self.phoneAndCountryNode.phoneInputNode.number)
|
||||
}
|
||||
|
||||
func updateDisplayPasskeyLoginOption() {
|
||||
//TODO:localize
|
||||
if self.account == nil {
|
||||
return
|
||||
}
|
||||
let attributedText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("Enter your phone number\nor [log in using Passkey >](passkey)", attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(17.0), textColor: self.theme.list.itemPrimaryTextColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(17.0), textColor: self.theme.list.itemPrimaryTextColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(17.0), textColor: self.theme.list.itemAccentColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}
|
||||
)))
|
||||
let chevronImage = generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: self.theme.list.itemAccentColor)
|
||||
|
||||
if let range = attributedText.string.range(of: ">"), let chevronImage {
|
||||
attributedText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedText.string))
|
||||
}
|
||||
|
||||
self.noticeNode.attributedText = attributedText
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
var insets = layout.insets(options: [])
|
||||
insets.top = layout.statusBarHeight ?? 20.0
|
||||
@@ -576,7 +618,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
|
||||
let noticeInset: CGFloat = self.account == nil ? 32.0 : 0.0
|
||||
|
||||
let noticeSize = self.noticeNode.measure(CGSize(width: min(274.0 + noticeInset, maximumWidth - 28.0), height: CGFloat.greatestFiniteMagnitude))
|
||||
let noticeSize = self.noticeNode.updateLayout(CGSize(width: min(274.0 + noticeInset, maximumWidth - 28.0), height: CGFloat.greatestFiniteMagnitude))
|
||||
let proceedHeight = self.proceedNode.updateLayout(width: maximumWidth - inset * 2.0, transition: transition)
|
||||
let proceedSize = CGSize(width: maximumWidth - inset * 2.0, height: proceedHeight)
|
||||
|
||||
|
||||
@@ -501,7 +501,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||
arguments.openTwoStepVerification(data)
|
||||
})
|
||||
case let .passkeys(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/TwoStepAuth")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Passkeys")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openPasskeys()
|
||||
})
|
||||
case let .messageAutoremoveTimeout(_, text, value):
|
||||
|
||||
@@ -340,7 +340,13 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
|
||||
}
|
||||
}
|
||||
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|
||||
#if DEBUG
|
||||
let initialDatacenterId: Int = 1
|
||||
#else
|
||||
let initialDatacenterId: Int = 2
|
||||
#endif
|
||||
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: initialDatacenterId, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|
||||
|> map { network -> AccountResult in
|
||||
return .unauthorized(UnauthorizedAccount(accountManager: accountManager, networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
|
||||
}
|
||||
|
||||
@@ -1183,91 +1183,117 @@ public final class AuthorizationPasskeyData {
|
||||
}
|
||||
}
|
||||
|
||||
public func authorizeWithPasskey(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, passkey: AuthorizationPasskeyData, forcedPasswordSetupNotice: @escaping (Int32) -> (NoticeEntryKey, CodableEntry)?, syncContacts: Bool) -> Signal<AuthorizeWithCodeResult, AuthorizationCodeVerificationError> {
|
||||
return account.postbox.transaction { transaction -> Signal<AuthorizeWithCodeResult, AuthorizationCodeVerificationError> in
|
||||
return account.network.request(Api.functions.auth.finishPasskeyLogin(flags: 0, credential: .inputPasskeyCredentialPublicKey(id: passkey.id, rawId: passkey.id, response: .inputPasskeyResponseLogin(clientData: .dataJSON(data: passkey.clientData), authenticatorData: Buffer(data: passkey.authenticatorData), signature: Buffer(data: passkey.signature), userHandle: passkey.userHandle)), fromDcId: nil, fromAuthKeyId: nil), automaticFloodWait: false)
|
||||
|> map { authorization in
|
||||
return .authorization(authorization)
|
||||
}
|
||||
|> `catch` { error -> Signal<AuthorizationCodeResult, AuthorizationCodeVerificationError> in
|
||||
switch (error.errorCode, error.errorDescription ?? "") {
|
||||
case (401, "SESSION_PASSWORD_NEEDED"):
|
||||
return account.network.request(Api.functions.account.getPassword(), automaticFloodWait: false)
|
||||
|> mapError { error -> AuthorizationCodeVerificationError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
} else {
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result -> Signal<AuthorizationCodeResult, AuthorizationCodeVerificationError> in
|
||||
switch result {
|
||||
case let .password(_, _, _, _, hint, _, _, _, _, _, _):
|
||||
return .single(.password(hint: hint ?? ""))
|
||||
}
|
||||
}
|
||||
case let (_, errorDescription):
|
||||
if errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .fail(.limitExceeded)
|
||||
} else if errorDescription == "PHONE_CODE_INVALID" || errorDescription == "EMAIL_CODE_INVALID" {
|
||||
return .fail(.invalidCode)
|
||||
} else if errorDescription == "CODE_HASH_EXPIRED" || errorDescription == "PHONE_CODE_EXPIRED" {
|
||||
return .fail(.codeExpired)
|
||||
} else if errorDescription == "PHONE_NUMBER_UNOCCUPIED" {
|
||||
return .single(.signUp)
|
||||
} else if errorDescription == "EMAIL_TOKEN_INVALID" {
|
||||
return .fail(.invalidEmailToken)
|
||||
} else if errorDescription == "EMAIL_ADDRESS_INVALID" {
|
||||
return .fail(.invalidEmailAddress)
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
public final class AuthorizeWithPasskeyResult {
|
||||
public let updatedAccount: UnauthorizedAccount
|
||||
|
||||
init(updatedAccount: UnauthorizedAccount) {
|
||||
self.updatedAccount = updatedAccount
|
||||
}
|
||||
}
|
||||
|
||||
public func authorizeWithPasskey(accountManager: AccountManager<TelegramAccountManagerTypes>, account: UnauthorizedAccount, passkey: AuthorizationPasskeyData, foreignDatacenter: (id: Int, authKeyId: Int64)?, forcedPasswordSetupNotice: @escaping (Int32) -> (NoticeEntryKey, CodableEntry)?, syncContacts: Bool) -> Signal<AuthorizeWithPasskeyResult, AuthorizationCodeVerificationError> {
|
||||
let userHandle = passkey.userHandle.components(separatedBy: ":")
|
||||
var targetDatacenterId: Int?
|
||||
if foreignDatacenter == nil && userHandle.count >= 2 {
|
||||
targetDatacenterId = Int(userHandle[0])
|
||||
}
|
||||
|
||||
if let targetDatacenterId, account.masterDatacenterId != Int32(targetDatacenterId) {
|
||||
let initialDatacenterId = account.masterDatacenterId
|
||||
return account.network.getAuthKeyId()
|
||||
|> castError(AuthorizationCodeVerificationError.self)
|
||||
|> mapToSignal { sourceAuthKeyId -> Signal<AuthorizeWithPasskeyResult, AuthorizationCodeVerificationError> in
|
||||
let updatedAccount = account.changedMasterDatacenterId(accountManager: accountManager, masterDatacenterId: Int32(targetDatacenterId))
|
||||
return updatedAccount
|
||||
|> mapToSignalPromotingError { updatedAccount -> Signal<AuthorizeWithPasskeyResult, AuthorizationCodeVerificationError> in
|
||||
return authorizeWithPasskey(accountManager: accountManager, account: updatedAccount, passkey: passkey, foreignDatacenter: (Int(initialDatacenterId), sourceAuthKeyId), forcedPasswordSetupNotice: forcedPasswordSetupNotice, syncContacts: syncContacts)
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result -> Signal<AuthorizeWithCodeResult, AuthorizationCodeVerificationError> in
|
||||
return account.postbox.transaction { transaction -> Signal<AuthorizeWithCodeResult, AuthorizationCodeVerificationError> in
|
||||
switch result {
|
||||
case .signUp:
|
||||
return .fail(.generic)
|
||||
case let .password(hint):
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .passwordEntry(hint: hint, number: nil, code: nil, suggestReset: false, syncContacts: syncContacts)))
|
||||
return .single(.loggedIn)
|
||||
case let .authorization(authorization):
|
||||
switch authorization {
|
||||
case let .authorization(_, otherwiseReloginDays, _, futureAuthToken, user):
|
||||
if let futureAuthToken = futureAuthToken {
|
||||
storeFutureLoginToken(accountManager: accountManager, token: futureAuthToken.makeData())
|
||||
}
|
||||
|
||||
let user = TelegramUser(user: user)
|
||||
var isSupportUser = false
|
||||
if let phone = user.phone, phone.hasPrefix("42") {
|
||||
isSupportUser = true
|
||||
}
|
||||
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)
|
||||
if let otherwiseReloginDays = otherwiseReloginDays, let value = forcedPasswordSetupNotice(otherwiseReloginDays) {
|
||||
transaction.setNoticeEntry(key: value.0, value: value.1)
|
||||
}
|
||||
return accountManager.transaction { transaction -> AuthorizeWithCodeResult in
|
||||
switchToAuthorizedAccount(transaction: transaction, account: account, isSupportUser: isSupportUser)
|
||||
return .loggedIn
|
||||
}
|
||||
|> castError(AuthorizationCodeVerificationError.self)
|
||||
case .authorizationSignUpRequired:
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
var flags: Int32 = 0
|
||||
if foreignDatacenter != nil {
|
||||
flags |= 1 << 0
|
||||
}
|
||||
return account.network.request(Api.functions.auth.finishPasskeyLogin(flags: flags, credential: .inputPasskeyCredentialPublicKey(id: passkey.id, rawId: passkey.id, response: .inputPasskeyResponseLogin(clientData: .dataJSON(data: passkey.clientData), authenticatorData: Buffer(data: passkey.authenticatorData), signature: Buffer(data: passkey.signature), userHandle: passkey.userHandle)), fromDcId: (foreignDatacenter?.id).flatMap(Int32.init), fromAuthKeyId: foreignDatacenter?.authKeyId), automaticFloodWait: false)
|
||||
|> map { authorization in
|
||||
return .authorization(authorization)
|
||||
}
|
||||
|> `catch` { error -> Signal<AuthorizationCodeResult, AuthorizationCodeVerificationError> in
|
||||
switch (error.errorCode, error.errorDescription ?? "") {
|
||||
case (401, "SESSION_PASSWORD_NEEDED"):
|
||||
return account.network.request(Api.functions.account.getPassword(), automaticFloodWait: false)
|
||||
|> mapError { error -> AuthorizationCodeVerificationError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .limitExceeded
|
||||
} else {
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
}
|
||||
|> mapError { _ -> AuthorizationCodeVerificationError in
|
||||
}
|
||||
|> switchToLatest
|
||||
|> mapToSignal { result -> Signal<AuthorizationCodeResult, AuthorizationCodeVerificationError> in
|
||||
switch result {
|
||||
case let .password(_, _, _, _, hint, _, _, _, _, _, _):
|
||||
return .single(.password(hint: hint ?? ""))
|
||||
}
|
||||
}
|
||||
case let (_, errorDescription):
|
||||
if errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .fail(.limitExceeded)
|
||||
} else if errorDescription == "PHONE_CODE_INVALID" || errorDescription == "EMAIL_CODE_INVALID" {
|
||||
return .fail(.invalidCode)
|
||||
} else if errorDescription == "CODE_HASH_EXPIRED" || errorDescription == "PHONE_CODE_EXPIRED" {
|
||||
return .fail(.codeExpired)
|
||||
} else if errorDescription == "PHONE_NUMBER_UNOCCUPIED" {
|
||||
return .single(.signUp)
|
||||
} else if errorDescription == "EMAIL_TOKEN_INVALID" {
|
||||
return .fail(.invalidEmailToken)
|
||||
} else if errorDescription == "EMAIL_ADDRESS_INVALID" {
|
||||
return .fail(.invalidEmailAddress)
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> mapError { _ -> AuthorizationCodeVerificationError in
|
||||
|> mapToSignal { result -> Signal<AuthorizeWithPasskeyResult, AuthorizationCodeVerificationError> in
|
||||
return account.postbox.transaction { transaction -> Signal<AuthorizeWithPasskeyResult, AuthorizationCodeVerificationError> in
|
||||
switch result {
|
||||
case .signUp:
|
||||
return .fail(.generic)
|
||||
case let .password(hint):
|
||||
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .passwordEntry(hint: hint, number: nil, code: nil, suggestReset: false, syncContacts: syncContacts)))
|
||||
return .single(AuthorizeWithPasskeyResult(updatedAccount: account))
|
||||
case let .authorization(authorization):
|
||||
switch authorization {
|
||||
case let .authorization(_, otherwiseReloginDays, _, futureAuthToken, user):
|
||||
if let futureAuthToken = futureAuthToken {
|
||||
storeFutureLoginToken(accountManager: accountManager, token: futureAuthToken.makeData())
|
||||
}
|
||||
|
||||
let user = TelegramUser(user: user)
|
||||
var isSupportUser = false
|
||||
if let phone = user.phone, phone.hasPrefix("42") {
|
||||
isSupportUser = true
|
||||
}
|
||||
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)
|
||||
if let otherwiseReloginDays = otherwiseReloginDays, let value = forcedPasswordSetupNotice(otherwiseReloginDays) {
|
||||
transaction.setNoticeEntry(key: value.0, value: value.1)
|
||||
}
|
||||
return accountManager.transaction { transaction -> AuthorizeWithPasskeyResult in
|
||||
switchToAuthorizedAccount(transaction: transaction, account: account, isSupportUser: isSupportUser)
|
||||
return AuthorizeWithPasskeyResult(updatedAccount: account)
|
||||
}
|
||||
|> castError(AuthorizationCodeVerificationError.self)
|
||||
case .authorizationSignUpRequired:
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> mapError { _ -> AuthorizationCodeVerificationError in
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public enum PasswordRecoveryRequestError {
|
||||
|
||||
@@ -1065,6 +1065,22 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
public func getAuthKeyId() -> Signal<Int64, NoError> {
|
||||
let mtContext = self.mtProto.context
|
||||
let datacenterId = self.datacenterId
|
||||
return Signal { subscriber in
|
||||
MTContext.contextQueue().dispatch(onQueue: {
|
||||
var result: Int64 = 0
|
||||
if let authInfo = mtContext?.authInfoForDatacenter(withId: datacenterId, selector: .persistent) {
|
||||
result = authInfo.authKeyId
|
||||
}
|
||||
subscriber.putNext(result)
|
||||
})
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public func requestWithAdditionalInfo<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), info: NetworkRequestAdditionalInfo, tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<NetworkRequestResult<T>, MTRpcError> {
|
||||
let requestService = self.requestService
|
||||
return Signal { subscriber in
|
||||
|
||||
@@ -29,6 +29,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||
"//submodules/TelegramUI/Components/ListActionItemComponent",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@@ -169,12 +169,12 @@ final class PasskeysScreenComponent: Component {
|
||||
}
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
|
||||
//TODO:localize
|
||||
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Delete Passkey?", text: "Once deleted, this passkey can't be used to log in.\n\nDon't forget to remove it from your password manager too.", actions: [TextAlertAction(type: .destructiveAction, title: "Delete", action: { [weak self] in
|
||||
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Delete Passkey?", text: "Once deleted, this passkey can't be used to log in.\n\nDon't forget to remove it from your password manager too.", actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
}), TextAlertAction(type: .destructiveAction, title: "Delete", action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.deletePasskey(id: id)
|
||||
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ final class PasskeysScreenComponent: Component {
|
||||
}
|
||||
if let credentialId = decodeBase64(passkey.id) {
|
||||
do {
|
||||
try await updater.reportUnknownPublicKeyCredential(relyingPartyIdentifier: "t.me", credentialID: credentialId)
|
||||
try await updater.reportUnknownPublicKeyCredential(relyingPartyIdentifier: "telegram.org", credentialID: credentialId)
|
||||
} catch let e {
|
||||
Logger.shared.log("Passkeys", "reportUnknownPublicKeyCredential error: \(e)")
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ final class PasskeysScreenIntroComponent: Component {
|
||||
let iconSize = self.icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(name: "TwoFactorSetupIntro"),
|
||||
content: LottieComponent.AppBundleContent(name: "passkey_logo"),
|
||||
loop: false
|
||||
)),
|
||||
environment: {},
|
||||
@@ -182,17 +182,17 @@ final class PasskeysScreenIntroComponent: Component {
|
||||
}
|
||||
let itemDescs: [ItemDesc] = [
|
||||
ItemDesc(
|
||||
icon: "Chat List/Archive/IconArchived",
|
||||
icon: "Settings/Passkeys/Intro1",
|
||||
title: "Create a Passkey",
|
||||
text: "Make a passkey to sign in easily and safely."
|
||||
),
|
||||
ItemDesc(
|
||||
icon: "Chat List/Archive/IconHide",
|
||||
icon: "Settings/Passkeys/Intro2",
|
||||
title: "Log in with Face ID",
|
||||
text: "Use Face ID, Touch ID, or your passcode to sign in."
|
||||
),
|
||||
ItemDesc(
|
||||
icon: "Chat List/Archive/IconStories",
|
||||
icon: "Settings/Passkeys/Intro3",
|
||||
title: "Store Passkey Securely",
|
||||
text: "Your passkey is safely kept in your iCloud Keychain."
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ import BundleIconComponent
|
||||
import ListSectionComponent
|
||||
import ListActionItemComponent
|
||||
import TelegramCore
|
||||
import EmojiStatusComponent
|
||||
|
||||
final class PasskeysScreenListComponent: Component {
|
||||
let context: AccountContext
|
||||
@@ -112,7 +113,7 @@ final class PasskeysScreenListComponent: Component {
|
||||
let iconSize = self.icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(name: "TwoFactorSetupIntro"),
|
||||
content: LottieComponent.AppBundleContent(name: "passkey_logo"),
|
||||
loop: false
|
||||
)),
|
||||
environment: {},
|
||||
@@ -184,6 +185,43 @@ final class PasskeysScreenListComponent: Component {
|
||||
dateFormatter.dateStyle = .medium
|
||||
let dateString = dateFormatter.string(from: Date(timeIntervalSince1970: Double(passkey.date)))
|
||||
|
||||
let iconComponent: AnyComponentWithIdentity<Empty>
|
||||
if let emojiId = passkey.emojiId {
|
||||
iconComponent = AnyComponentWithIdentity(
|
||||
id: "lottie",
|
||||
component: AnyComponent(TransformContents<Empty>(
|
||||
content: AnyComponent(EmojiStatusComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
content: .animation(
|
||||
content: .customEmoji(fileId: emojiId),
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
placeholderColor: component.theme.list.mediaPlaceholderColor,
|
||||
themeColor: nil,
|
||||
loopMode: .count(1)
|
||||
),
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
isVisibleForAnimations: true,
|
||||
action: nil
|
||||
)),
|
||||
translation: CGPoint(x: 0.0, y: 1.0)
|
||||
))
|
||||
)
|
||||
} else {
|
||||
iconComponent = AnyComponentWithIdentity(
|
||||
id: "icon",
|
||||
component: AnyComponent(BundleIconComponent(name: "Settings/Menu/Passkeys", tintColor: nil))
|
||||
)
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
var subtitleString = "created \(dateString)"
|
||||
if let lastUsageDate = passkey.lastUsageDate {
|
||||
let lastUsedDateString = dateFormatter.string(from: Date(timeIntervalSince1970: Double(lastUsageDate)))
|
||||
subtitleString.append(" • used \(lastUsedDateString)")
|
||||
}
|
||||
|
||||
listSectionItems.append(AnyComponentWithIdentity(id: passkey.id, component: AnyComponent(ListActionItemComponent(
|
||||
theme: component.theme,
|
||||
title: AnyComponent(VStack([
|
||||
@@ -197,7 +235,7 @@ final class PasskeysScreenListComponent: Component {
|
||||
))),
|
||||
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "created \(dateString)",
|
||||
string: subtitleString,
|
||||
font: Font.regular(14.0),
|
||||
textColor: component.theme.list.itemSecondaryTextColor
|
||||
)),
|
||||
@@ -205,10 +243,7 @@ final class PasskeysScreenListComponent: Component {
|
||||
)))
|
||||
], alignment: .left, spacing: 2.0)),
|
||||
leftIcon: .custom(
|
||||
AnyComponentWithIdentity(
|
||||
id: "icon",
|
||||
component: AnyComponent(BundleIconComponent(name: "Settings/Menu/TwoStepAuth", tintColor: nil))
|
||||
),
|
||||
iconComponent,
|
||||
false
|
||||
),
|
||||
accessory: nil,
|
||||
|
||||
12
submodules/TelegramUI/Images.xcassets/Settings/Menu/Passkeys.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/Menu/Passkeys.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "key_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Settings/Menu/Passkeys.imageset/key_30.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Settings/Menu/Passkeys.imageset/key_30.pdf
vendored
Normal file
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Icon-5.pdf",
|
||||
"filename" : "lock_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||
1.000000 0.584314 0.000000 scn
|
||||
0.000000 18.799999 m
|
||||
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
|
||||
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
|
||||
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
|
||||
18.799999 30.000000 l
|
||||
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
|
||||
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
|
||||
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
|
||||
30.000000 11.200001 l
|
||||
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
|
||||
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
|
||||
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
|
||||
11.200000 0.000000 l
|
||||
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
|
||||
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
|
||||
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
|
||||
0.000000 18.799999 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
-1.000000 -0.000000 -0.000000 1.000000 25.000000 6.000000 cm
|
||||
1.000000 1.000000 1.000000 scn
|
||||
6.650000 19.000000 m
|
||||
2.945000 19.000000 0.000000 16.055000 0.000000 12.350000 c
|
||||
0.000000 8.645000 2.945000 5.700001 6.650000 5.700001 c
|
||||
7.500165 5.700001 8.310955 5.861581 9.054688 6.145313 c
|
||||
10.450001 4.750000 l
|
||||
12.350000 4.750000 l
|
||||
12.350000 2.850000 l
|
||||
14.250000 2.850000 l
|
||||
14.250000 0.950001 l
|
||||
14.903126 0.296875 l
|
||||
15.093125 0.106874 15.300938 0.000000 15.585938 0.000000 c
|
||||
18.049999 0.000000 l
|
||||
18.619999 0.000000 19.000000 0.380001 19.000000 0.950001 c
|
||||
19.000000 3.414062 l
|
||||
19.000000 3.699062 18.893126 3.906875 18.703125 4.096874 c
|
||||
12.854687 9.945312 l
|
||||
13.138419 10.689045 13.299999 11.499835 13.299999 12.350000 c
|
||||
13.299999 16.055000 10.355000 19.000000 6.650000 19.000000 c
|
||||
h
|
||||
5.225000 16.150000 m
|
||||
6.555000 16.150000 7.600000 15.105000 7.600000 13.775000 c
|
||||
7.600000 12.445000 6.555000 11.400000 5.225000 11.400000 c
|
||||
3.895000 11.400000 2.850000 12.445000 2.850000 13.775000 c
|
||||
2.850000 15.105000 3.895000 16.150000 5.225000 16.150000 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1987
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000002077 00000 n
|
||||
0000002100 00000 n
|
||||
0000002273 00000 n
|
||||
0000002347 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2406
|
||||
%%EOF
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Settings/Menu/TwoStepAuth.imageset/lock_30.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Settings/Menu/TwoStepAuth.imageset/lock_30.pdf
vendored
Normal file
Binary file not shown.
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
12
submodules/TelegramUI/Images.xcassets/Settings/Passkeys/Intro1.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/Passkeys/Intro1.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "key.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Settings/Passkeys/Intro1.imageset/key.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Settings/Passkeys/Intro1.imageset/key.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Settings/Passkeys/Intro2.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/Passkeys/Intro2.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "faceid.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Settings/Passkeys/Intro2.imageset/faceid.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Settings/Passkeys/Intro2.imageset/faceid.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Settings/Passkeys/Intro3.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/Passkeys/Intro3.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "lock.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Settings/Passkeys/Intro3.imageset/lock.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Settings/Passkeys/Intro3.imageset/lock.pdf
vendored
Normal file
Binary file not shown.
BIN
submodules/TelegramUI/Resources/Animations/passkey_logo.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/passkey_logo.tgs
Normal file
Binary file not shown.
Reference in New Issue
Block a user