mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Bot auth
This commit is contained in:
parent
4cef442e92
commit
af6f1da0ab
@ -706,6 +706,8 @@ private final class NotificationServiceHandler {
|
||||
|
||||
setupSharedLogger(rootPath: logsPath, path: logsPath)
|
||||
|
||||
Logger.shared.log("NotificationService \(episode)", "Started handling notification")
|
||||
|
||||
initializeAccountManagement()
|
||||
|
||||
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import LocalAuthentication
|
||||
import SwiftSignalKit
|
||||
import Security
|
||||
|
||||
public enum LocalAuthBiometricAuthentication {
|
||||
case touchId
|
||||
@ -8,10 +9,65 @@ public enum LocalAuthBiometricAuthentication {
|
||||
}
|
||||
|
||||
public struct LocalAuth {
|
||||
public static let biometricAuthentication: LocalAuthBiometricAuthentication? = {
|
||||
private static let customKeyIdPrefix = "$#_".data(using: .utf8)!
|
||||
|
||||
public enum DecryptionResult {
|
||||
public enum Error {
|
||||
case cancelled
|
||||
case generic
|
||||
}
|
||||
|
||||
case result(Data)
|
||||
case error(Error)
|
||||
}
|
||||
|
||||
public final class PrivateKey {
|
||||
private let privateKey: SecKey
|
||||
private let publicKey: SecKey
|
||||
public let publicKeyRepresentation: Data
|
||||
|
||||
fileprivate init(privateKey: SecKey, publicKey: SecKey, publicKeyRepresentation: Data) {
|
||||
self.privateKey = privateKey
|
||||
self.publicKey = publicKey
|
||||
self.publicKeyRepresentation = publicKeyRepresentation
|
||||
}
|
||||
|
||||
public func encrypt(data: Data) -> Data? {
|
||||
var error: Unmanaged<CFError>?
|
||||
let cipherText = SecKeyCreateEncryptedData(self.publicKey, .eciesEncryptionCofactorVariableIVX963SHA512AESGCM, data as CFData, &error)
|
||||
if let error {
|
||||
error.release()
|
||||
}
|
||||
guard let cipherText else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let result = cipherText as Data
|
||||
return result
|
||||
}
|
||||
|
||||
public func decrypt(data: Data) -> DecryptionResult {
|
||||
var maybeError: Unmanaged<CFError>?
|
||||
let plainText = SecKeyCreateDecryptedData(self.privateKey, .eciesEncryptionCofactorVariableIVX963SHA512AESGCM, data as CFData, &maybeError)
|
||||
let error = maybeError?.takeRetainedValue()
|
||||
|
||||
guard let plainText else {
|
||||
if let error {
|
||||
if CFErrorGetCode(error) == -2 {
|
||||
return .error(.cancelled)
|
||||
}
|
||||
}
|
||||
return .error(.generic)
|
||||
}
|
||||
|
||||
let result = plainText as Data
|
||||
return .result(result)
|
||||
}
|
||||
}
|
||||
|
||||
public static var biometricAuthentication: LocalAuthBiometricAuthentication? {
|
||||
let context = LAContext()
|
||||
if context.canEvaluatePolicy(LAPolicy(rawValue: Int(kLAPolicyDeviceOwnerAuthenticationWithBiometrics))!, error: nil) {
|
||||
#if swift(>=5.9)
|
||||
switch context.biometryType {
|
||||
case .faceID, .opticID:
|
||||
return .faceId
|
||||
@ -22,22 +78,10 @@ public struct LocalAuth {
|
||||
@unknown default:
|
||||
return nil
|
||||
}
|
||||
#else
|
||||
switch context.biometryType {
|
||||
case .faceID://, .opticID:
|
||||
return .faceId
|
||||
case .touchID:
|
||||
return .touchId
|
||||
case .none:
|
||||
return nil
|
||||
@unknown default:
|
||||
return nil
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
public static let evaluatedPolicyDomainState: Data? = {
|
||||
let context = LAContext()
|
||||
@ -78,4 +122,145 @@ public struct LocalAuth {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func bundleSeedId() -> String? {
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword as String,
|
||||
kSecAttrAccount as String: "bundleSeedID",
|
||||
kSecAttrService as String: "",
|
||||
kSecReturnAttributes as String: true
|
||||
]
|
||||
var result: CFTypeRef?
|
||||
var status = SecItemCopyMatching(query as CFDictionary, &result)
|
||||
if status == errSecItemNotFound {
|
||||
status = SecItemAdd(query as CFDictionary, &result)
|
||||
}
|
||||
if status != errSecSuccess {
|
||||
return nil
|
||||
}
|
||||
guard let result = result else {
|
||||
return nil
|
||||
}
|
||||
if CFGetTypeID(result) != CFDictionaryGetTypeID() {
|
||||
return nil
|
||||
}
|
||||
guard let resultDict = (result as! CFDictionary) as? [String: Any] else {
|
||||
return nil
|
||||
}
|
||||
guard let accessGroup = resultDict[kSecAttrAccessGroup as String] as? String else {
|
||||
return nil
|
||||
}
|
||||
let components = accessGroup.components(separatedBy: ".")
|
||||
guard let seedId = components.first else {
|
||||
return nil
|
||||
}
|
||||
return seedId;
|
||||
}
|
||||
|
||||
public static func getPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? {
|
||||
guard let bundleSeedId = self.bundleSeedId() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let applicationTag = customKeyIdPrefix + keyId
|
||||
let accessGroup = "\(bundleSeedId).\(baseAppBundleId)"
|
||||
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassKey as String,
|
||||
kSecAttrApplicationTag as String: applicationTag,
|
||||
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom as String,
|
||||
kSecAttrAccessGroup as String: accessGroup,
|
||||
kSecReturnRef as String: true
|
||||
]
|
||||
|
||||
var maybePrivateKey: CFTypeRef?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &maybePrivateKey)
|
||||
if status != errSecSuccess {
|
||||
return nil
|
||||
}
|
||||
guard let maybePrivateKey else {
|
||||
return nil
|
||||
}
|
||||
if CFGetTypeID(maybePrivateKey) != SecKeyGetTypeID() {
|
||||
return nil
|
||||
}
|
||||
let privateKey = maybePrivateKey as! SecKey
|
||||
|
||||
guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
|
||||
return nil
|
||||
}
|
||||
guard let publicKeyRepresentation = SecKeyCopyExternalRepresentation(publicKey, nil) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
public static func removePrivateKey(baseAppBundleId: String, keyId: Data) -> Bool {
|
||||
guard let bundleSeedId = self.bundleSeedId() else {
|
||||
return false
|
||||
}
|
||||
|
||||
let applicationTag = customKeyIdPrefix + keyId
|
||||
let accessGroup = "\(bundleSeedId).\(baseAppBundleId)"
|
||||
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassKey as String,
|
||||
kSecAttrApplicationTag as String: applicationTag,
|
||||
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom as String,
|
||||
kSecAttrIsPermanent as String: true,
|
||||
kSecAttrAccessGroup as String: accessGroup
|
||||
]
|
||||
|
||||
let status = SecItemDelete(query as CFDictionary)
|
||||
if status != errSecSuccess {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public static func addPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? {
|
||||
guard let bundleSeedId = self.bundleSeedId() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let applicationTag = customKeyIdPrefix + keyId
|
||||
let accessGroup = "\(bundleSeedId).\(baseAppBundleId)"
|
||||
|
||||
guard let access = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, [.userPresence, .privateKeyUsage], nil) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let attributes: [String: Any] = [
|
||||
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom as String,
|
||||
kSecAttrKeySizeInBits as String: 256 as NSNumber,
|
||||
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave as String,
|
||||
kSecPrivateKeyAttrs as String: [
|
||||
kSecAttrIsPermanent as String: true,
|
||||
kSecAttrApplicationTag as String: applicationTag,
|
||||
kSecAttrAccessControl as String: access,
|
||||
kSecAttrAccessGroup as String: accessGroup,
|
||||
] as [String: Any]
|
||||
]
|
||||
var error: Unmanaged<CFError>?
|
||||
let maybePrivateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error)
|
||||
if let error {
|
||||
error.release()
|
||||
}
|
||||
guard let privateKey = maybePrivateKey else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
|
||||
return nil
|
||||
}
|
||||
guard let publicKeyRepresentation = SecKeyCopyExternalRepresentation(publicKey, nil) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
@ -286,6 +286,7 @@ private enum PreferencesKeyValues: Int32 {
|
||||
case displaySavedChatsAsTopics = 35
|
||||
case shortcutMessages = 37
|
||||
case timezoneList = 38
|
||||
case botBiometricsState = 39
|
||||
}
|
||||
|
||||
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
|
||||
@ -481,6 +482,13 @@ public struct PreferencesKeys {
|
||||
key.setInt32(0, value: PreferencesKeyValues.timezoneList.rawValue)
|
||||
return key
|
||||
}
|
||||
|
||||
public static func botBiometricsState(peerId: PeerId) -> ValueBoxKey {
|
||||
let key = ValueBoxKey(length: 4 + 8)
|
||||
key.setInt32(0, value: PreferencesKeyValues.botBiometricsState.rawValue)
|
||||
key.setInt64(4, value: peerId.toInt64())
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
private enum SharedDataKeyValues: Int32 {
|
||||
|
@ -1584,5 +1584,33 @@ public extension TelegramEngine.EngineData.Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct BotBiometricsState: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = TelegramBotBiometricsState
|
||||
|
||||
fileprivate var id: EnginePeer.Id
|
||||
public var mapKey: EnginePeer.Id {
|
||||
return self.id
|
||||
}
|
||||
|
||||
public init(id: EnginePeer.Id) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
var key: PostboxViewKey {
|
||||
return .preferences(keys: Set([PreferencesKeys.botBiometricsState(peerId: self.id)]))
|
||||
}
|
||||
|
||||
func extract(view: PostboxView) -> Result {
|
||||
guard let view = view as? PreferencesView else {
|
||||
preconditionFailure()
|
||||
}
|
||||
if let state = view.values[PreferencesKeys.botBiometricsState(peerId: self.id)]?.get(TelegramBotBiometricsState.self) {
|
||||
return state
|
||||
} else {
|
||||
return TelegramBotBiometricsState.default
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -319,3 +319,42 @@ func _internal_invokeBotCustomMethod(postbox: Postbox, network: Network, botId:
|
||||
|> castError(InvokeBotCustomMethodError.self)
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public struct TelegramBotBiometricsState: Codable, Equatable {
|
||||
public struct OpaqueToken: Codable, Equatable {
|
||||
public let publicKey: Data
|
||||
public let data: Data
|
||||
|
||||
public init(publicKey: Data, data: Data) {
|
||||
self.publicKey = publicKey
|
||||
self.data = data
|
||||
}
|
||||
}
|
||||
|
||||
public var accessRequested: Bool
|
||||
public var accessGranted: Bool
|
||||
public var opaqueToken: OpaqueToken?
|
||||
|
||||
public static var `default`: TelegramBotBiometricsState {
|
||||
return TelegramBotBiometricsState(
|
||||
accessRequested: false,
|
||||
accessGranted: false,
|
||||
opaqueToken: nil
|
||||
)
|
||||
}
|
||||
|
||||
public init(accessRequested: Bool, accessGranted: Bool, opaqueToken: OpaqueToken?) {
|
||||
self.accessRequested = accessRequested
|
||||
self.accessGranted = accessGranted
|
||||
self.opaqueToken = opaqueToken
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_updateBotBiometricsState(account: Account, peerId: EnginePeer.Id, update: @escaping (TelegramBotBiometricsState) -> TelegramBotBiometricsState) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
let previousState = transaction.getPreferencesEntry(key: PreferencesKeys.botBiometricsState(peerId: peerId))?.get(TelegramBotBiometricsState.self) ?? TelegramBotBiometricsState.default
|
||||
|
||||
transaction.setPreferencesEntry(key: PreferencesKeys.botBiometricsState(peerId: peerId), value: PreferencesEntry(update(previousState)))
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
@ -1490,6 +1490,10 @@ public extension TelegramEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateBotBiometricsState(peerId: EnginePeer.Id, update: @escaping (TelegramBotBiometricsState) -> TelegramBotBiometricsState) {
|
||||
let _ = _internal_updateBotBiometricsState(account: self.account, peerId: peerId, update: update).startStandalone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ swift_library(
|
||||
"//submodules/CheckNode:CheckNode",
|
||||
"//submodules/Markdown:Markdown",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
"//submodules/LocalAuth",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -23,6 +23,7 @@ import PromptUI
|
||||
import PhoneNumberFormat
|
||||
import QrCodeUI
|
||||
import InstantPageUI
|
||||
import LocalAuth
|
||||
|
||||
private let durgerKingBotIds: [Int64] = [5104055776, 2200339955]
|
||||
|
||||
@ -1064,6 +1065,18 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
if let json = json, let isVisible = json["is_visible"] as? Bool {
|
||||
self.controller?.hasSettings = isVisible
|
||||
}
|
||||
case "web_app_biometry_get_info":
|
||||
self.sendBiometryInfoReceivedEvent()
|
||||
case "web_app_biometry_request_access":
|
||||
self.requestBiometryAccess()
|
||||
case "web_app_biometry_request_auth":
|
||||
self.requestBiometryAuth()
|
||||
case "web_app_biometry_update_token":
|
||||
var tokenData: Data?
|
||||
if let json, let tokenDataValue = json["token"] as? Data {
|
||||
tokenData = tokenDataValue
|
||||
}
|
||||
self.requestBiometryUpdateToken(tokenData: tokenData)
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -1392,6 +1405,230 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.webView?.sendEvent(name: "custom_method_invoked", data: paramsString)
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func sendBiometryInfoReceivedEvent() {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.BotBiometricsState(id: controller.botId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
var data: [String: Any] = [:]
|
||||
if let biometricAuthentication = LocalAuth.biometricAuthentication {
|
||||
data["available"] = true
|
||||
switch biometricAuthentication {
|
||||
case .faceId:
|
||||
data["type"] = "face"
|
||||
case .touchId:
|
||||
data["type"] = "finger"
|
||||
}
|
||||
data["access_requested"] = state.accessRequested
|
||||
data["access_granted"] = state.accessGranted
|
||||
data["token_saved"] = state.opaqueToken != nil
|
||||
} else {
|
||||
data["available"] = false
|
||||
}
|
||||
|
||||
guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else {
|
||||
return
|
||||
}
|
||||
guard let jsonDataString = String(data: jsonData, encoding: .utf8) else {
|
||||
return
|
||||
}
|
||||
self.webView?.sendEvent(name: "biometry_info_received", data: jsonDataString)
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func requestBiometryAccess() {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: controller.botId),
|
||||
TelegramEngine.EngineData.Item.Peer.BotBiometricsState(id: controller.botId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] botPeer, state in
|
||||
guard let self, let botPeer, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
||||
if state.accessRequested {
|
||||
self.sendBiometryInfoReceivedEvent()
|
||||
return
|
||||
}
|
||||
|
||||
let updateAccessGranted: (Bool) -> Void = { [weak self] granted in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.context.engine.peers.updateBotBiometricsState(peerId: botPeer.id, update: { state in
|
||||
var state = state
|
||||
state.accessRequested = true
|
||||
state.accessGranted = granted
|
||||
return state
|
||||
})
|
||||
|
||||
self.sendBiometryInfoReceivedEvent()
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let alertText = "Do you want to allow \(botPeer.compactDisplayTitle) to use Face ID?"
|
||||
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: alertText, actions: [
|
||||
TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_No, action: {
|
||||
updateAccessGranted(false)
|
||||
}),
|
||||
TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Yes, action: {
|
||||
updateAccessGranted(true)
|
||||
})
|
||||
], parseMarkdown: false), in: .window(.root))
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func requestBiometryAuth() {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: controller.botId),
|
||||
TelegramEngine.EngineData.Item.Peer.BotBiometricsState(id: controller.botId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] botPeer, state in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if state.accessRequested && state.accessGranted {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
guard let keyId = "A\(UInt64(bitPattern: self.context.account.id.int64))WebBot\(UInt64(bitPattern: controller.botId.toInt64()))".data(using: .utf8) else {
|
||||
return
|
||||
}
|
||||
let appBundleId = self.context.sharedContext.applicationBindings.appBundleId
|
||||
|
||||
Thread { [weak self] in
|
||||
var key = LocalAuth.getPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
if key == nil {
|
||||
key = LocalAuth.addPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
}
|
||||
|
||||
let decryptedData: LocalAuth.DecryptionResult
|
||||
if let key {
|
||||
if let encryptedData = state.opaqueToken {
|
||||
if encryptedData.publicKey == key.publicKeyRepresentation {
|
||||
decryptedData = key.decrypt(data: encryptedData.data)
|
||||
} else {
|
||||
// The local keychain has been reset
|
||||
if let emptyEncryptedData = key.encrypt(data: Data()) {
|
||||
decryptedData = key.decrypt(data: emptyEncryptedData)
|
||||
} else {
|
||||
decryptedData = .error(.generic)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let emptyEncryptedData = key.encrypt(data: Data()) {
|
||||
decryptedData = key.decrypt(data: emptyEncryptedData)
|
||||
} else {
|
||||
decryptedData = .error(.generic)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
decryptedData = .error(.generic)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
switch decryptedData {
|
||||
case let .result(token):
|
||||
self.sendBiometryAuthResult(isAuthorized: true, tokenData: state.opaqueToken != nil ? token : nil)
|
||||
case .error:
|
||||
self.sendBiometryAuthResult(isAuthorized: false, tokenData: nil)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
} else {
|
||||
self.sendBiometryAuthResult(isAuthorized: false, tokenData: nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func sendBiometryAuthResult(isAuthorized: Bool, tokenData: Data?) {
|
||||
var data: [String: Any] = [:]
|
||||
data["status"] = isAuthorized ? "authorized" : "failed"
|
||||
if isAuthorized {
|
||||
if let tokenData {
|
||||
data["token"] = tokenData
|
||||
} else {
|
||||
data["token"] = Data()
|
||||
}
|
||||
}
|
||||
|
||||
guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else {
|
||||
return
|
||||
}
|
||||
guard let jsonDataString = String(data: jsonData, encoding: .utf8) else {
|
||||
return
|
||||
}
|
||||
self.webView?.sendEvent(name: "biometry_auth_requested", data: jsonDataString)
|
||||
}
|
||||
|
||||
fileprivate func requestBiometryUpdateToken(tokenData: Data?) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
guard let keyId = "A\(UInt64(bitPattern: self.context.account.id.int64))WebBot\(UInt64(bitPattern: controller.botId.toInt64()))".data(using: .utf8) else {
|
||||
return
|
||||
}
|
||||
|
||||
if let tokenData {
|
||||
let appBundleId = self.context.sharedContext.applicationBindings.appBundleId
|
||||
Thread { [weak self] in
|
||||
var key = LocalAuth.getPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
if key == nil {
|
||||
key = LocalAuth.addPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
}
|
||||
|
||||
var encryptedData: TelegramBotBiometricsState.OpaqueToken?
|
||||
if let key {
|
||||
if let result = key.encrypt(data: tokenData) {
|
||||
encryptedData = TelegramBotBiometricsState.OpaqueToken(
|
||||
publicKey: key.publicKeyRepresentation,
|
||||
data: result
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if let encryptedData {
|
||||
self.context.engine.peers.updateBotBiometricsState(peerId: controller.botId, update: { state in
|
||||
var state = state
|
||||
state.opaqueToken = encryptedData
|
||||
return state
|
||||
})
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
} else {
|
||||
self.context.engine.peers.updateBotBiometricsState(peerId: controller.botId, update: { state in
|
||||
var state = state
|
||||
state.opaqueToken = nil
|
||||
return state
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var controllerNode: Node {
|
||||
|
Loading…
x
Reference in New Issue
Block a user