Skip to content

Commit 95ecb88

Browse files
author
Isaac
committed
Passkeys
1 parent 0371407 commit 95ecb88

File tree

22 files changed

+1980
-98
lines changed

22 files changed

+1980
-98
lines changed

Telegram/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ associated_domains_fragment = "" if telegram_bundle_id not in official_bundle_id
495495
<string>applinks:telegram.me</string>
496496
<string>applinks:t.me</string>
497497
<string>applinks:*.t.me</string>
498+
<string>webcredentials:t.me</string>
498499
</array>
499500
"""
500501

submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
170170
if let currentController = currentController {
171171
controller = currentController
172172
} else {
173-
controller = AuthorizationSequencePhoneEntryController(sharedContext: self.sharedContext, account: self.account, isTestingEnvironment: self.account.testingEnvironment, otherAccountPhoneNumbers: self.otherAccountPhoneNumbers, network: self.account.network, presentationData: self.presentationData, openUrl: { [weak self] url in
173+
controller = AuthorizationSequencePhoneEntryController(sharedContext: self.sharedContext, account: self.account, apiId: self.apiId, apiHash: self.apiHash, isTestingEnvironment: self.account.testingEnvironment, otherAccountPhoneNumbers: self.otherAccountPhoneNumbers, network: self.account.network, presentationData: self.presentationData, openUrl: { [weak self] url in
174174
self?.openUrl(url)
175175
}, back: { [weak self] in
176176
guard let strongSelf = self else {
@@ -315,6 +315,40 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
315315
}
316316
})
317317
}
318+
controller.loginWithPasskey = { [weak self, weak controller] passkey, syncContacts in
319+
guard let self else {
320+
return
321+
}
322+
323+
self.actionDisposable.set((authorizeWithPasskey(
324+
accountManager: self.sharedContext.accountManager,
325+
account: self.account,
326+
passkey: passkey,
327+
forcedPasswordSetupNotice: { value in
328+
guard let entry = CodableEntry(ApplicationSpecificCounterNotice(value: value)) else {
329+
return nil
330+
}
331+
return (ApplicationSpecificNotice.forcedPasswordSetupKey(), entry)
332+
},
333+
syncContacts: syncContacts
334+
)
335+
|> deliverOnMainQueue).startStrict(next: { _ in
336+
}, error: { [weak self, weak controller] error in
337+
Queue.mainQueue().async {
338+
if let strongSelf = self, let controller {
339+
let text: String
340+
switch error {
341+
case .limitExceeded:
342+
text = strongSelf.presentationData.strings.Login_CodeFloodError
343+
case .generic, .invalidEmailAddress, .codeExpired, .invalidEmailToken, .invalidCode:
344+
text = strongSelf.presentationData.strings.Login_UnknownError
345+
}
346+
347+
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
348+
}
349+
}
350+
}))
351+
}
318352
}
319353
controller.updateData(countryCode: countryCode, countryName: nil, number: number)
320354
return controller

submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import CountrySelectionUI
1212
import PhoneNumberFormat
1313
import DebugSettingsUI
1414
import MessageUI
15+
import AuthenticationServices
1516

16-
public final class AuthorizationSequencePhoneEntryController: ViewController, MFMailComposeViewControllerDelegate {
17+
public final class AuthorizationSequencePhoneEntryController: ViewController, MFMailComposeViewControllerDelegate, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
1718
private var controllerNode: AuthorizationSequencePhoneEntryControllerNode {
1819
return self.displayNode as! AuthorizationSequencePhoneEntryControllerNode
1920
}
@@ -22,6 +23,8 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF
2223

2324
private let sharedContext: SharedAccountContext
2425
private var account: UnauthorizedAccount?
26+
private let apiId: Int32
27+
private let apiHash: String
2528
private let isTestingEnvironment: Bool
2629
private let otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)])
2730
private let network: Network
@@ -52,6 +55,7 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF
5255
}
5356
}
5457
public var loginWithNumber: ((String, Bool) -> Void)?
58+
public var loginWithPasskey: ((AuthorizationPasskeyData, Bool) -> Void)?
5559
var accountUpdated: ((UnauthorizedAccount) -> Void)?
5660

5761
weak var confirmationController: PhoneConfirmationController?
@@ -60,9 +64,11 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF
6064

6165
private let hapticFeedback = HapticFeedback()
6266

63-
public init(sharedContext: SharedAccountContext, account: UnauthorizedAccount?, countriesConfiguration: CountriesConfiguration? = nil, isTestingEnvironment: Bool, otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]), network: Network, presentationData: PresentationData, openUrl: @escaping (String) -> Void, back: @escaping () -> Void) {
67+
public init(sharedContext: SharedAccountContext, account: UnauthorizedAccount?, countriesConfiguration: CountriesConfiguration? = nil, apiId: Int32, apiHash: String, isTestingEnvironment: Bool, otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]), network: Network, presentationData: PresentationData, openUrl: @escaping (String) -> Void, back: @escaping () -> Void) {
6468
self.sharedContext = sharedContext
6569
self.account = account
70+
self.apiId = apiId
71+
self.apiHash = apiHash
6672
self.isTestingEnvironment = isTestingEnvironment
6773
self.otherAccountPhoneNumbers = otherAccountPhoneNumbers
6874
self.network = network
@@ -187,6 +193,110 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF
187193
} else {
188194
self.controllerNode.updateCountryCode()
189195
}
196+
197+
if #available(iOS 15.0, *) {
198+
Task { @MainActor [weak self] in
199+
guard let self, let account = self.account else {
200+
return
201+
}
202+
203+
let decodeBase64: (String) -> Data? = { string in
204+
var string = string.replacingOccurrences(of: "-", with: "+")
205+
.replacingOccurrences(of: "_", with: "/")
206+
while string.count % 4 != 0 {
207+
string.append("=")
208+
}
209+
return Data(base64Encoded: string)
210+
}
211+
212+
let engine = TelegramEngineUnauthorized(account: account)
213+
let passkeyDataString = await engine.auth.requestPasskeyLoginData(apiId: self.apiId, apiHash: self.apiHash).get()
214+
guard let passkeyDataString, let passkeyData = passkeyDataString.data(using: .utf8) else {
215+
return
216+
}
217+
guard let params = try? JSONSerialization.jsonObject(with: passkeyData) as? [String: Any] else {
218+
return
219+
}
220+
guard let pkDict = params["publicKey"] as? [String: Any] else {
221+
return
222+
}
223+
guard let relyingPartyIdentifier = pkDict["rpId"] as? String else {
224+
return
225+
}
226+
guard let challengeBase64 = pkDict["challenge"] as? String else {
227+
return
228+
}
229+
guard let challengeData = decodeBase64(challengeBase64) else {
230+
return
231+
}
232+
233+
let platformProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: relyingPartyIdentifier)
234+
let platformKeyRequest = platformProvider.createCredentialAssertionRequest(challenge: challengeData)
235+
let authController = ASAuthorizationController(authorizationRequests: [platformKeyRequest])
236+
authController.delegate = self
237+
authController.presentationContextProvider = self
238+
authController.performRequests()
239+
}
240+
}
241+
}
242+
243+
public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
244+
Task { @MainActor [weak self] in
245+
guard let self, let account = self.account else {
246+
return
247+
}
248+
249+
let encodeBase64URL: (Data) -> String = { data in
250+
var string = data.base64EncodedString()
251+
string = string
252+
.replacingOccurrences(of: "+", with: "-")
253+
.replacingOccurrences(of: "/", with: "_")
254+
string = string.replacingOccurrences(of: "=", with: "")
255+
return string
256+
}
257+
258+
if #available(iOS 17.0, *) {
259+
if let credential = authorization.credential as? ASAuthorizationPlatformPublicKeyCredentialAssertion {
260+
guard let clientData = String(data: credential.rawClientDataJSON, encoding: .utf8) else {
261+
return
262+
}
263+
guard let userHandle = String(data: credential.userID, encoding: .utf8) else {
264+
return
265+
}
266+
let passkey = AuthorizationPasskeyData(
267+
id: encodeBase64URL(credential.credentialID),
268+
clientData: clientData,
269+
authenticatorData: credential.rawAuthenticatorData,
270+
signature: credential.signature,
271+
userHandle: userHandle
272+
)
273+
self.loginWithPasskey?(passkey, self.controllerNode.syncContacts)
274+
275+
/*if let clientData = String(data: credential.rawClientDataJSON, encoding: .utf8), let attestationObject = credential.rawAttestationObject {
276+
let passkey = await component.context.engine.auth.requestCreatePasskey(id: encodeBase64URL(credential.credentialID), clientData: clientData, attestationObject: attestationObject).get()
277+
if let passkey {
278+
if self.passkeysData == nil {
279+
self.passkeysData = []
280+
self.passkeysData?.insert(passkey, at: 0)
281+
}
282+
self.state?.updated(transition: .immediate)
283+
}
284+
}*/
285+
let _ = account
286+
let _ = credential
287+
}
288+
}
289+
}
290+
}
291+
292+
public func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: any Error) {
293+
}
294+
295+
public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
296+
guard let windowScene = self.view.window?.windowScene else {
297+
preconditionFailure()
298+
}
299+
return ASPresentationAnchor(windowScene: windowScene)
190300
}
191301

192302
public func updateCountryCode() {

submodules/SettingsUI/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ swift_library(
126126
"//submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen",
127127
"//submodules/TelegramUI/Components/Settings/GenerateThemeName",
128128
"//submodules/TelegramUI/Components/Settings/PeerNameColorItem",
129+
"//submodules/TelegramUI/Components/Settings/PasskeysScreen",
129130
"//submodules/TelegramUI/Components/FaceScanScreen",
130131
"//submodules/ComponentFlow",
131132
"//submodules/Components/BundleIconComponent",

submodules/SettingsUI/Sources/ChangePhoneNumberController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public func ChangePhoneNumberController(context: AccountContext) -> ViewControll
2222
let requestDisposable = MetaDisposable()
2323
let changePhoneDisposable = MetaDisposable()
2424

25-
let controller = AuthorizationSequencePhoneEntryController(sharedContext: context.sharedContext, account: nil, countriesConfiguration: context.currentCountriesConfiguration.with { $0 }, isTestingEnvironment: false, otherAccountPhoneNumbers: (nil, []), network: context.account.network, presentationData: presentationData, openUrl: { _ in }, back: {
25+
let controller = AuthorizationSequencePhoneEntryController(sharedContext: context.sharedContext, account: nil, countriesConfiguration: context.currentCountriesConfiguration.with { $0 }, apiId: 0, apiHash: "", isTestingEnvironment: false, otherAccountPhoneNumbers: (nil, []), network: context.account.network, presentationData: presentationData, openUrl: { _ in }, back: {
2626
dismissImpl?()
2727
})
2828
controller.loginWithNumber = { [weak controller] phoneNumber, _ in

0 commit comments

Comments
 (0)