Files
SideStore/SideStoreApp/Sources/SideStoreAppKit/Operations/AuthenticationOperation.swift

597 lines
28 KiB
Swift
Raw Normal View History

//
// AuthenticationOperation.swift
// AltStore
//
// Created by Riley Testut on 6/5/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
import Network
2023-03-01 14:36:52 -05:00
import RoxasUIKit
2023-03-02 00:40:11 -05:00
import os.log
import AltSign
2023-03-01 00:48:36 -05:00
import SideStoreCore
2023-03-01 00:48:36 -05:00
enum AuthenticationError: LocalizedError {
case noTeam
case noCertificate
case teamSelectorError
2023-03-01 00:48:36 -05:00
case missingPrivateKey
case missingCertificate
2023-03-01 00:48:36 -05:00
var errorDescription: String? {
switch self {
case .noTeam: return NSLocalizedString("Developer team could not be found.", comment: "")
case .teamSelectorError: return NSLocalizedString("Error presenting team selector view.", comment: "")
case .noCertificate: return NSLocalizedString("Developer certificate could not be found.", comment: "")
case .missingPrivateKey: return NSLocalizedString("The certificate's private key could not be found.", comment: "")
case .missingCertificate: return NSLocalizedString("The certificate could not be found.", comment: "")
}
}
}
@objc(AuthenticationOperation)
2023-03-01 19:09:33 -05:00
public final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppleAPISession)> {
public let context: AuthenticatedOperationContext
2023-03-01 00:48:36 -05:00
private weak var presentingViewController: UIViewController?
2023-03-01 00:48:36 -05:00
2019-09-07 15:29:19 -07:00
private lazy var navigationController: UINavigationController = {
let navigationController = self.storyboard.instantiateViewController(withIdentifier: "navigationController") as! UINavigationController
2023-03-01 00:48:36 -05:00
if #available(iOS 13.0, *) {
navigationController.isModalInPresentation = true
}
2019-09-07 15:29:19 -07:00
return navigationController
}()
2023-03-01 00:48:36 -05:00
2023-03-02 01:09:10 -05:00
private lazy var storyboard = UIStoryboard(name: "Authentication", bundle: Bundle.init(for: AuthenticationViewController.self))
2023-03-01 00:48:36 -05:00
private var appleIDEmailAddress: String?
private var appleIDPassword: String?
2019-09-07 15:29:19 -07:00
private var shouldShowInstructions = false
2023-03-01 00:48:36 -05:00
private let operationQueue = OperationQueue()
2023-03-01 00:48:36 -05:00
private var submitCodeAction: UIAlertAction?
2023-03-01 00:48:36 -05:00
2023-03-01 19:09:33 -05:00
public init(context: AuthenticatedOperationContext, presentingViewController: UIViewController?) {
self.context = context
self.presentingViewController = presentingViewController
2023-03-01 00:48:36 -05:00
super.init()
2023-03-01 00:48:36 -05:00
self.context.authenticationOperation = self
2023-03-01 00:48:36 -05:00
operationQueue.name = "com.altstore.AuthenticationOperation"
progress.totalUnitCount = 4
}
2023-03-01 00:48:36 -05:00
2023-03-01 19:09:33 -05:00
public override func main() {
super.main()
2023-03-01 00:48:36 -05:00
if let error = context.error {
finish(.failure(error))
return
}
2023-03-01 00:48:36 -05:00
// Sign In
2023-03-01 00:48:36 -05:00
signIn { result in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
2023-03-01 00:48:36 -05:00
switch result {
case let .failure(error): self.finish(.failure(error))
case let .success((account, session)):
self.context.session = session
self.progress.completedUnitCount += 1
2023-03-01 00:48:36 -05:00
// Fetch Team
2023-03-01 00:48:36 -05:00
self.fetchTeam(for: account, session: session) { result in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
2023-03-01 00:48:36 -05:00
switch result {
case let .failure(error): self.finish(.failure(error))
case let .success(team):
self.context.team = team
self.progress.completedUnitCount += 1
2023-03-01 00:48:36 -05:00
// Fetch Certificate
2023-03-01 00:48:36 -05:00
self.fetchCertificate(for: team, session: session) { result in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
2023-03-01 00:48:36 -05:00
switch result {
case let .failure(error): self.finish(.failure(error))
case let .success(certificate):
self.context.certificate = certificate
self.progress.completedUnitCount += 1
2023-03-01 00:48:36 -05:00
// Register Device
2023-03-01 00:48:36 -05:00
self.registerCurrentDevice(for: team, session: session) { result in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
2023-03-01 00:48:36 -05:00
switch result {
case let .failure(error): self.finish(.failure(error))
case .success:
self.progress.completedUnitCount += 1
2023-03-01 00:48:36 -05:00
// Save account/team to disk.
2023-03-01 00:48:36 -05:00
self.save(team) { result in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
2023-03-01 00:48:36 -05:00
switch result {
case let .failure(error): self.finish(.failure(error))
case .success:
// Must cache App IDs _after_ saving account/team to disk.
2023-03-01 00:48:36 -05:00
self.cacheAppIDs(team: team, session: session) { result in
let result = result.map { _ in (team, certificate, session) }
self.finish(result)
}
}
}
}
}
}
}
}
}
}
}
}
2023-03-01 00:48:36 -05:00
func save(_ altTeam: ALTTeam, completionHandler: @escaping (Result<Void, Error>) -> Void) {
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
context.performAndWait {
2023-03-01 00:48:36 -05:00
do {
let account: Account
let team: Team
2023-03-01 00:48:36 -05:00
if let tempAccount = Account.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Account.identifier), altTeam.account.identifier), in: context) {
account = tempAccount
2023-03-01 00:48:36 -05:00
} else {
account = Account(altTeam.account, context: context)
}
2023-03-01 00:48:36 -05:00
if let tempTeam = Team.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Team.identifier), altTeam.identifier), in: context) {
team = tempTeam
2023-03-01 00:48:36 -05:00
} else {
team = Team(altTeam, account: account, context: context)
}
2023-03-01 00:48:36 -05:00
account.update(account: altTeam.account)
2023-03-01 00:48:36 -05:00
if let providedEmailAddress = self.appleIDEmailAddress {
// Save the user's provided email address instead of the one associated with their account (which may be outdated).
account.appleID = providedEmailAddress
}
2023-03-01 00:48:36 -05:00
team.update(team: altTeam)
2023-03-01 00:48:36 -05:00
try context.save()
2023-03-01 00:48:36 -05:00
completionHandler(.success(()))
2023-03-01 00:48:36 -05:00
} catch {
completionHandler(.failure(error))
}
}
}
2023-03-01 00:48:36 -05:00
override func finish(_ result: Result<(ALTTeam, ALTCertificate, ALTAppleAPISession), Error>) {
guard !isFinished else { return }
2023-03-02 00:40:11 -05:00
if let error = result.error {
os_log("Failed to finish authenticating wirth error: %@", type: .error, error.localizedDescription)
} else {
os_log("Successfully authenticating", type: .info)
}
2023-03-01 00:48:36 -05:00
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
context.perform {
2023-03-01 00:48:36 -05:00
do {
let (altTeam, altCertificate, session) = try result.get()
2023-03-01 00:48:36 -05:00
guard
let account = Account.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Account.identifier), altTeam.account.identifier), in: context),
let team = Team.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Team.identifier), altTeam.identifier), in: context)
else { throw AuthenticationError.noTeam }
2023-03-01 00:48:36 -05:00
// Account
account.isActiveAccount = true
2023-03-01 00:48:36 -05:00
let otherAccountsFetchRequest = Account.fetchRequest() as NSFetchRequest<Account>
otherAccountsFetchRequest.predicate = NSPredicate(format: "%K != %@", #keyPath(Account.identifier), account.identifier)
2023-03-01 00:48:36 -05:00
let otherAccounts = try context.fetch(otherAccountsFetchRequest)
2023-03-01 00:48:36 -05:00
for account in otherAccounts {
account.isActiveAccount = false
}
2023-03-01 00:48:36 -05:00
// Team
team.isActiveTeam = true
2023-03-01 00:48:36 -05:00
let otherTeamsFetchRequest = Team.fetchRequest() as NSFetchRequest<Team>
otherTeamsFetchRequest.predicate = NSPredicate(format: "%K != %@", #keyPath(Team.identifier), team.identifier)
2023-03-01 00:48:36 -05:00
let otherTeams = try context.fetch(otherTeamsFetchRequest)
2023-03-01 00:48:36 -05:00
for team in otherTeams {
team.isActiveTeam = false
}
2023-03-01 00:48:36 -05:00
let activeAppsMinimumVersion = OperatingSystemVersion(majorVersion: 13, minorVersion: 3, patchVersion: 1)
2023-03-01 00:48:36 -05:00
if team.type == .free, ProcessInfo.processInfo.isOperatingSystemAtLeast(activeAppsMinimumVersion) {
UserDefaults.standard.activeAppsLimit = ALTActiveAppsLimit
2023-03-01 00:48:36 -05:00
} else {
UserDefaults.standard.activeAppsLimit = nil
}
2023-03-01 00:48:36 -05:00
// Save
try context.save()
2023-03-01 00:48:36 -05:00
// Update keychain
Keychain.shared.appleIDEmailAddress = self.appleIDEmailAddress ?? altTeam.account.appleID // Prefer the user's provided email address over the one associated with their account (which may be outdated).
Keychain.shared.appleIDPassword = self.appleIDPassword
2023-03-01 00:48:36 -05:00
Keychain.shared.signingCertificate = altCertificate.p12Data()
Keychain.shared.signingCertificatePassword = altCertificate.machineIdentifier
2023-03-01 00:48:36 -05:00
self.showInstructionsIfNecessary { _ in
let signer = ALTSigner(team: altTeam, certificate: altCertificate)
// Refresh screen must go last since a successful refresh will cause the app to quit.
2023-03-01 00:48:36 -05:00
self.showRefreshScreenIfNecessary(signer: signer, session: session) { _ in
super.finish(result)
2023-03-01 00:48:36 -05:00
DispatchQueue.main.async {
self.navigationController.dismiss(animated: true, completion: nil)
}
}
}
2023-03-01 00:48:36 -05:00
} catch {
super.finish(result)
2023-03-01 00:48:36 -05:00
DispatchQueue.main.async {
self.navigationController.dismiss(animated: true, completion: nil)
}
}
}
}
}
2023-03-01 00:48:36 -05:00
private extension AuthenticationOperation {
func present(_ viewController: UIViewController) -> Bool {
guard let presentingViewController = presentingViewController else { return false }
navigationController.view.tintColor = .white
if navigationController.viewControllers.isEmpty {
guard presentingViewController.presentedViewController == nil else { return false }
2023-03-01 00:48:36 -05:00
navigationController.setViewControllers([viewController], animated: false)
presentingViewController.present(navigationController, animated: true, completion: nil)
} else {
viewController.navigationItem.leftBarButtonItem = nil
2023-03-01 00:48:36 -05:00
navigationController.pushViewController(viewController, animated: true)
}
2023-03-01 00:48:36 -05:00
return true
}
}
2023-03-01 00:48:36 -05:00
private extension AuthenticationOperation {
func signIn(completionHandler: @escaping (Result<(ALTAccount, ALTAppleAPISession), Swift.Error>) -> Void) {
func authenticate() {
DispatchQueue.main.async {
let authenticationViewController = self.storyboard.instantiateViewController(withIdentifier: "authenticationViewController") as! AuthenticationViewController
2023-03-01 00:48:36 -05:00
authenticationViewController.authenticationHandler = { appleID, password, completionHandler in
self.authenticate(appleID: appleID, password: password) { result in
completionHandler(result)
}
}
2023-03-01 00:48:36 -05:00
authenticationViewController.completionHandler = { result in
if let (account, session, password) = result {
2019-09-07 15:29:19 -07:00
// We presented the Auth UI and the user signed in.
// In this case, we'll assume we should show the instructions again.
self.shouldShowInstructions = true
2023-03-01 00:48:36 -05:00
2019-09-07 15:29:19 -07:00
self.appleIDPassword = password
completionHandler(.success((account, session)))
2023-03-01 00:48:36 -05:00
} else {
completionHandler(.failure(OperationError.cancelled))
}
}
2023-03-01 00:48:36 -05:00
if !self.present(authenticationViewController) {
completionHandler(.failure(OperationError.notAuthenticated))
}
}
}
2023-03-01 00:48:36 -05:00
if let appleID = Keychain.shared.appleIDEmailAddress, let password = Keychain.shared.appleIDPassword {
self.authenticate(appleID: appleID, password: password) { result in
switch result {
case let .success((account, session)):
self.appleIDPassword = password
completionHandler(.success((account, session)))
2023-03-01 00:48:36 -05:00
case .failure(ALTAppleAPIError.incorrectCredentials), .failure(ALTAppleAPIError.appSpecificPasswordRequired):
authenticate()
2023-03-01 00:48:36 -05:00
case let .failure(error):
completionHandler(.failure(error))
}
}
2023-03-01 00:48:36 -05:00
} else {
authenticate()
}
}
2023-03-01 00:48:36 -05:00
func authenticate(appleID: String, password: String, completionHandler: @escaping (Result<(ALTAccount, ALTAppleAPISession), Swift.Error>) -> Void) {
appleIDEmailAddress = appleID
let fetchAnisetteDataOperation = FetchAnisetteDataOperation(context: context)
fetchAnisetteDataOperation.resultHandler = { result in
switch result {
case let .failure(error): completionHandler(.failure(error))
case let .success(anisetteData):
let verificationHandler: ((@escaping (String?) -> Void) -> Void)?
2023-03-01 00:48:36 -05:00
if let presentingViewController = self.presentingViewController {
verificationHandler = { completionHandler in
DispatchQueue.main.async {
let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""), message: nil, preferredStyle: .alert)
2023-03-01 00:48:36 -05:00
alertController.addTextField { textField in
textField.autocorrectionType = .no
textField.autocapitalizationType = .none
textField.keyboardType = .numberPad
2023-03-01 00:48:36 -05:00
NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationOperation.textFieldTextDidChange(_:)), name: UITextField.textDidChangeNotification, object: textField)
}
2023-03-01 00:48:36 -05:00
let submitAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default) { _ in
let textField = alertController.textFields?.first
2023-03-01 00:48:36 -05:00
let code = textField?.text ?? ""
completionHandler(code)
}
submitAction.isEnabled = false
alertController.addAction(submitAction)
self.submitCodeAction = submitAction
2023-03-01 00:48:36 -05:00
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("Cancel"), style: .cancel) { _ in
completionHandler(nil)
})
2023-03-01 00:48:36 -05:00
if self.navigationController.presentingViewController != nil {
self.navigationController.present(alertController, animated: true, completion: nil)
2023-03-01 00:48:36 -05:00
} else {
presentingViewController.present(alertController, animated: true, completion: nil)
}
}
}
2023-03-01 00:48:36 -05:00
} else {
// No view controller to present security code alert, so don't provide verificationHandler.
verificationHandler = nil
}
2023-03-01 00:48:36 -05:00
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData,
2023-03-01 00:48:36 -05:00
verificationHandler: verificationHandler) { account, session, error in
if let account = account, let session = session {
completionHandler(.success((account, session)))
2023-03-01 00:48:36 -05:00
} else {
completionHandler(.failure(error ?? OperationError.unknown))
}
}
}
}
2023-03-01 00:48:36 -05:00
operationQueue.addOperation(fetchAnisetteDataOperation)
}
2023-03-01 00:48:36 -05:00
func fetchTeam(for account: ALTAccount, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTTeam, Swift.Error>) -> Void) {
func selectTeam(from teams: [ALTTeam]) {
if teams.count <= 1 {
if let team = teams.first {
return completionHandler(.success(team))
} else {
return completionHandler(.failure(AuthenticationError.noTeam))
}
} else {
DispatchQueue.main.async {
let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController
selectTeamViewController.teams = teams
selectTeamViewController.completionHandler = completionHandler
if !self.present(selectTeamViewController) {
return completionHandler(.failure(AuthenticationError.noTeam))
}
2023-03-01 00:48:36 -05:00
}
}
}
ALTAppleAPI.shared.fetchTeams(for: account, session: session) { teams, error in
switch Result(teams, error) {
case let .failure(error): completionHandler(.failure(error))
case let .success(teams):
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
if let activeTeam = DatabaseManager.shared.activeTeam(in: context), let altTeam = teams.first(where: { $0.identifier == activeTeam.identifier }) {
completionHandler(.success(altTeam))
} else {
selectTeam(from: teams)
}
}
}
}
}
2023-03-01 00:48:36 -05:00
func fetchCertificate(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTCertificate, Swift.Error>) -> Void) {
func requestCertificate() {
let machineName = "AltStore - " + UIDevice.current.name
2023-03-01 00:48:36 -05:00
ALTAppleAPI.shared.addCertificate(machineName: machineName, to: team, session: session) { certificate, error in
do {
let certificate = try Result(certificate, error).get()
guard let privateKey = certificate.privateKey else { throw AuthenticationError.missingPrivateKey }
2023-03-01 00:48:36 -05:00
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { certificates, error in
do {
let certificates = try Result(certificates, error).get()
2023-03-01 00:48:36 -05:00
guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber }) else {
throw AuthenticationError.missingCertificate
}
2023-03-01 00:48:36 -05:00
certificate.privateKey = privateKey
completionHandler(.success(certificate))
2023-03-01 00:48:36 -05:00
} catch {
completionHandler(.failure(error))
}
}
2023-03-01 00:48:36 -05:00
} catch {
completionHandler(.failure(error))
}
}
}
2023-03-01 00:48:36 -05:00
func replaceCertificate(from certificates: [ALTCertificate]) {
guard let certificate = certificates.first(where: { $0.machineName?.starts(with: "AltStore") == true }) ?? certificates.first else { return completionHandler(.failure(AuthenticationError.noCertificate)) }
2023-03-01 00:48:36 -05:00
ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { success, error in
if let error = error, !success {
2019-09-07 15:29:19 -07:00
completionHandler(.failure(error))
2023-03-01 00:48:36 -05:00
} else {
2019-09-07 15:29:19 -07:00
requestCertificate()
}
}
}
2023-03-01 00:48:36 -05:00
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { certificates, error in
do {
let certificates = try Result(certificates, error).get()
2023-03-01 00:48:36 -05:00
if
let data = Keychain.shared.signingCertificate,
let localCertificate = ALTCertificate(p12Data: data, password: nil),
2023-03-01 00:48:36 -05:00
let certificate = certificates.first(where: { $0.serialNumber == localCertificate.serialNumber }) {
// We have a certificate stored in the keychain and it hasn't been revoked.
localCertificate.machineIdentifier = certificate.machineIdentifier
completionHandler(.success(localCertificate))
2023-03-01 00:48:36 -05:00
} else if
let serialNumber = Keychain.shared.signingCertificateSerialNumber,
let privateKey = Keychain.shared.signingCertificatePrivateKey,
2023-03-01 00:48:36 -05:00
let certificate = certificates.first(where: { $0.serialNumber == serialNumber }) {
// LEGACY
// We have the private key for one of the certificates, so add it to certificate and use it.
certificate.privateKey = privateKey
completionHandler(.success(certificate))
2023-03-01 00:48:36 -05:00
} else if
let serialNumber = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.certificateID) as? String,
let certificate = certificates.first(where: { $0.serialNumber == serialNumber }),
let machineIdentifier = certificate.machineIdentifier,
FileManager.default.fileExists(atPath: Bundle.main.certificateURL.path),
let data = try? Data(contentsOf: Bundle.main.certificateURL),
2023-03-01 00:48:36 -05:00
let localCertificate = ALTCertificate(p12Data: data, password: machineIdentifier) {
// We have an embedded certificate that hasn't been revoked.
localCertificate.machineIdentifier = machineIdentifier
completionHandler(.success(localCertificate))
2023-03-01 00:48:36 -05:00
} else if certificates.isEmpty {
// No certificates, so request a new one.
requestCertificate()
2023-03-01 00:48:36 -05:00
} else {
// We don't have private keys for any of the certificates,
// so we need to revoke one and create a new one.
replaceCertificate(from: certificates)
}
2023-03-01 00:48:36 -05:00
} catch {
completionHandler(.failure(error))
}
}
}
2023-03-01 00:48:36 -05:00
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void) {
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else {
return completionHandler(.failure(OperationError.unknownUDID))
}
2023-03-01 00:48:36 -05:00
ALTAppleAPI.shared.fetchDevices(for: team, types: [.iphone, .ipad], session: session) { devices, error in
do {
let devices = try Result(devices, error).get()
2023-03-01 00:48:36 -05:00
if let device = devices.first(where: { $0.identifier == udid }) {
completionHandler(.success(device))
2023-03-01 00:48:36 -05:00
} else {
ALTAppleAPI.shared.registerDevice(name: UIDevice.current.name, identifier: udid, type: .iphone, team: team, session: session) { device, error in
completionHandler(Result(device, error))
}
}
2023-03-01 00:48:36 -05:00
} catch {
completionHandler(.failure(error))
}
}
}
2023-03-01 00:48:36 -05:00
func cacheAppIDs(team _: ALTTeam, session _: ALTAppleAPISession, completionHandler: @escaping (Result<Void, Error>) -> Void) {
let fetchAppIDsOperation = FetchAppIDsOperation(context: context)
fetchAppIDsOperation.resultHandler = { result in
do {
let (_, context) = try result.get()
try context.save()
2023-03-01 00:48:36 -05:00
completionHandler(.success(()))
2023-03-01 00:48:36 -05:00
} catch {
completionHandler(.failure(error))
}
}
2023-03-01 00:48:36 -05:00
operationQueue.addOperation(fetchAppIDsOperation)
}
2023-03-01 00:48:36 -05:00
func showInstructionsIfNecessary(completionHandler: @escaping (Bool) -> Void) {
guard shouldShowInstructions else { return completionHandler(false) }
2019-09-07 15:29:19 -07:00
DispatchQueue.main.async {
let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController
instructionsViewController.showsBottomButton = true
instructionsViewController.completionHandler = {
completionHandler(true)
}
2023-03-01 00:48:36 -05:00
if !self.present(instructionsViewController) {
2019-09-07 15:29:19 -07:00
completionHandler(false)
}
}
}
2023-03-01 00:48:36 -05:00
func showRefreshScreenIfNecessary(signer: ALTSigner, session _: ALTAppleAPISession, completionHandler: @escaping (Bool) -> Void) {
guard let application = ALTApplication(fileURL: Bundle.main.bundleURL), let provisioningProfile = application.provisioningProfile else { return completionHandler(false) }
2023-03-01 00:48:36 -05:00
// If we're not using the same certificate used to install AltStore, warn user that they need to refresh.
guard !provisioningProfile.certificates.contains(signer.certificate) else { return completionHandler(false) }
2023-03-01 00:48:36 -05:00
#if DEBUG
completionHandler(false)
#else
DispatchQueue.main.async {
let context = AuthenticatedOperationContext(context: self.context)
context.operations.removeAllObjects() // Prevent deadlock due to endless waiting on previous operations to finish.
let refreshViewController = self.storyboard.instantiateViewController(withIdentifier: "refreshAltStoreViewController") as! RefreshAltStoreViewController
refreshViewController.context = context
refreshViewController.completionHandler = { _ in
completionHandler(true)
}
if !self.present(refreshViewController) {
completionHandler(false)
}
}
2023-03-01 00:48:36 -05:00
#endif
}
}
2023-03-01 00:48:36 -05:00
extension AuthenticationOperation {
@objc func textFieldTextDidChange(_ notification: Notification) {
guard let textField = notification.object as? UITextField else { return }
2023-03-01 00:48:36 -05:00
submitCodeAction?.isEnabled = (textField.text ?? "").count == 6
}
}