mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Compare commits
2 Commits
1d642e2ffa
...
639753149f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
639753149f | ||
|
|
cc5aeaf099 |
@@ -85,70 +85,86 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign In
|
Task {
|
||||||
self.signIn() { (result) in
|
// try to use cached session
|
||||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
if
|
||||||
|
let certificate = Keychain.shared.certificate,
|
||||||
switch result
|
let session = Keychain.shared.session,
|
||||||
|
let team = Keychain.shared.team
|
||||||
{
|
{
|
||||||
case .failure(let error): self.finish(.failure(error))
|
if session.anisetteData.date.timeIntervalSinceNow < -40.0 {
|
||||||
case .success((let account, let session)):
|
let anisetteData = try await withUnsafeThrowingContinuation { (c: UnsafeContinuation<ALTAnisetteData, any Error>) in
|
||||||
self.context.session = session
|
let fetchAnisetteDataOperation = FetchAnisetteDataOperation(context: self.context)
|
||||||
self.progress.completedUnitCount += 1
|
fetchAnisetteDataOperation.resultHandler = { (result) in
|
||||||
|
c.resume(with: result)
|
||||||
// Fetch Team
|
|
||||||
self.fetchTeam(for: account, session: session) { (result) in
|
|
||||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
|
||||||
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(let error): self.finish(.failure(error))
|
|
||||||
case .success(let team):
|
|
||||||
self.context.team = team
|
|
||||||
self.progress.completedUnitCount += 1
|
|
||||||
|
|
||||||
// Fetch Certificate
|
|
||||||
self.fetchCertificate(for: team, session: session) { (result) in
|
|
||||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
|
||||||
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(let error): self.finish(.failure(error))
|
|
||||||
case .success(let certificate):
|
|
||||||
self.context.certificate = certificate
|
|
||||||
self.progress.completedUnitCount += 1
|
|
||||||
|
|
||||||
// Register Device
|
|
||||||
self.registerCurrentDevice(for: team, session: session) { (result) in
|
|
||||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
|
||||||
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(let error): self.finish(.failure(error))
|
|
||||||
case .success:
|
|
||||||
self.progress.completedUnitCount += 1
|
|
||||||
|
|
||||||
// Save account/team to disk.
|
|
||||||
self.save(team) { (result) in
|
|
||||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
|
||||||
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(let error): self.finish(.failure(error))
|
|
||||||
case .success:
|
|
||||||
// Must cache App IDs _after_ saving account/team to disk.
|
|
||||||
self.cacheAppIDs(team: team, session: session) { (result) in
|
|
||||||
let result = result.map { _ in (team, certificate, session) }
|
|
||||||
self.finish(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
self.operationQueue.addOperation(fetchAnisetteDataOperation)
|
||||||
|
}
|
||||||
|
session.anisetteData = anisetteData
|
||||||
|
}
|
||||||
|
self.context.team = team
|
||||||
|
self.context.session = session
|
||||||
|
self.context.certificate = certificate
|
||||||
|
self.finish(.success((team, certificate, session)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// new login
|
||||||
|
do {
|
||||||
|
let (account, session) = try await withUnsafeThrowingContinuation { c in
|
||||||
|
self.signIn() { (result) in
|
||||||
|
c.resume(with: result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.context.session = session
|
||||||
|
self.progress.completedUnitCount += 1
|
||||||
|
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||||
|
|
||||||
|
let team = try await withUnsafeThrowingContinuation { c in
|
||||||
|
self.fetchTeam(for: account, session: session) { (result) in
|
||||||
|
c.resume(with: result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.context.team = team
|
||||||
|
self.progress.completedUnitCount += 1
|
||||||
|
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||||
|
|
||||||
|
let certificate = try await withUnsafeThrowingContinuation { c in
|
||||||
|
self.fetchCertificate(for: team, session: session) { (result) in
|
||||||
|
c.resume(with: result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.context.certificate = certificate
|
||||||
|
self.progress.completedUnitCount += 1
|
||||||
|
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||||
|
|
||||||
|
let _ = try await withUnsafeThrowingContinuation { c in
|
||||||
|
self.registerCurrentDevice(for: team, session: session) { (result) in
|
||||||
|
c.resume(with: result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.progress.completedUnitCount += 1
|
||||||
|
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||||
|
|
||||||
|
try await withUnsafeThrowingContinuation { c in
|
||||||
|
self.save(team) { (result) in
|
||||||
|
c.resume(with: result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||||
|
|
||||||
|
try await withUnsafeThrowingContinuation { c in
|
||||||
|
self.cacheAppIDs(team: team, session: session) { (result) in
|
||||||
|
c.resume(with: result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Keychain.shared.team = team
|
||||||
|
Keychain.shared.certificate = certificate
|
||||||
|
Keychain.shared.session = session
|
||||||
|
self.finish(.success((team, certificate, session)))
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
self.finish(.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,6 +375,29 @@ private extension AuthenticationOperation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let adsid = Keychain.shared.appleIDAdsid, let xcodeToken = Keychain.shared.appleIDXcodeToken {
|
||||||
|
Logger.sideload.notice("Authenticating Apple ID with tokens...")
|
||||||
|
let semaphore = DispatchSemaphore(value: 0)
|
||||||
|
var shouldContinue = true
|
||||||
|
Task {
|
||||||
|
defer {
|
||||||
|
semaphore.signal()
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
let (account, session) = try await self.authenticateWithToken(adsid: adsid, xcodeToken: xcodeToken)
|
||||||
|
completionHandler(.success((account, session)))
|
||||||
|
shouldContinue = false
|
||||||
|
} catch {
|
||||||
|
Logger.sideload.notice("Authentication failed with token. Fall back to email and password login: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
semaphore.wait()
|
||||||
|
if !shouldContinue {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let appleID = Keychain.shared.appleIDEmailAddress, let password = Keychain.shared.appleIDPassword
|
if let appleID = Keychain.shared.appleIDEmailAddress, let password = Keychain.shared.appleIDPassword
|
||||||
{
|
{
|
||||||
Logger.sideload.notice("Authenticating Apple ID...")
|
Logger.sideload.notice("Authenticating Apple ID...")
|
||||||
@@ -384,6 +423,25 @@ private extension AuthenticationOperation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func authenticateWithToken(adsid: String, xcodeToken: String) async throws -> (ALTAccount, ALTAppleAPISession) {
|
||||||
|
let anisetteData = try await withUnsafeThrowingContinuation { (c: UnsafeContinuation<ALTAnisetteData, any Error>) in
|
||||||
|
let fetchAnisetteDataOperation = FetchAnisetteDataOperation(context: self.context)
|
||||||
|
fetchAnisetteDataOperation.resultHandler = { (result) in
|
||||||
|
c.resume(with: result)
|
||||||
|
}
|
||||||
|
self.operationQueue.addOperation(fetchAnisetteDataOperation)
|
||||||
|
}
|
||||||
|
|
||||||
|
let session = ALTAppleAPISession(dsid: adsid, authToken: xcodeToken, anisetteData: anisetteData)
|
||||||
|
let account = try await withUnsafeThrowingContinuation { (c: UnsafeContinuation<ALTAccount, any Error>) in
|
||||||
|
ALTAppleAPI.shared.fetchAccount2(session: session) { result in
|
||||||
|
c.resume(with: result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (account, session)
|
||||||
|
}
|
||||||
|
|
||||||
func authenticate(appleID: String, password: String, completionHandler: @escaping (Result<(ALTAccount, ALTAppleAPISession), Swift.Error>) -> Void)
|
func authenticate(appleID: String, password: String, completionHandler: @escaping (Result<(ALTAccount, ALTAppleAPISession), Swift.Error>) -> Void)
|
||||||
{
|
{
|
||||||
self.appleIDEmailAddress = appleID
|
self.appleIDEmailAddress = appleID
|
||||||
@@ -444,6 +502,8 @@ private extension AuthenticationOperation
|
|||||||
verificationHandler: verificationHandler) { (account, session, error) in
|
verificationHandler: verificationHandler) { (account, session, error) in
|
||||||
if let account = account, let session = session
|
if let account = account, let session = session
|
||||||
{
|
{
|
||||||
|
Keychain.shared.appleIDAdsid = session.dsid
|
||||||
|
Keychain.shared.appleIDXcodeToken = session.authToken
|
||||||
completionHandler(.success((account, session)))
|
completionHandler(.success((account, session)))
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -747,3 +807,30 @@ extension AuthenticationOperation
|
|||||||
self.submitCodeAction?.isEnabled = (textField.text ?? "").count == 6
|
self.submitCodeAction?.isEnabled = (textField.text ?? "").count == 6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension ALTAppleAPI {
|
||||||
|
func fetchAccount2(session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAccount, Error>) -> Void)
|
||||||
|
{
|
||||||
|
let url = URL(string: "viewDeveloper.action", relativeTo: self.baseURL)!
|
||||||
|
|
||||||
|
self.sendRequest(with: url, additionalParameters: nil, session: session, team: nil) { (responseDictionary, requestError) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
guard let responseDictionary = responseDictionary else { throw requestError ?? ALTAppleAPIError.unknown() }
|
||||||
|
|
||||||
|
guard let account = try self.processResponse(responseDictionary, parseHandler: { () -> Any? in
|
||||||
|
guard let dictionary = responseDictionary["developer"] as? [String: Any] else { return nil }
|
||||||
|
let account = ALTAccount(responseDictionary: dictionary)
|
||||||
|
return account
|
||||||
|
}, resultCodeHandler: nil) as? ALTAccount else {
|
||||||
|
throw ALTAppleAPIError.unknown()
|
||||||
|
}
|
||||||
|
|
||||||
|
completionHandler(.success(account))
|
||||||
|
} catch {
|
||||||
|
completionHandler(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSoc
|
|||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||||
formatter.calendar = Calendar(identifier: .gregorian)
|
formatter.calendar = Calendar(identifier: .gregorian)
|
||||||
formatter.timeZone = TimeZone.current
|
formatter.timeZone = TimeZone.init(secondsFromGMT: 0)
|
||||||
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
|
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
|
||||||
let dateString = formatter.string(from: Date())
|
let dateString = formatter.string(from: Date())
|
||||||
formattedJSON["date"] = dateString
|
formattedJSON["date"] = dateString
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ public class Keychain
|
|||||||
@KeychainItem(key: "appleIDPassword")
|
@KeychainItem(key: "appleIDPassword")
|
||||||
public var appleIDPassword: String?
|
public var appleIDPassword: String?
|
||||||
|
|
||||||
|
@KeychainItem(key: "appleIDAdsid")
|
||||||
|
public var appleIDAdsid: String?
|
||||||
|
|
||||||
|
@KeychainItem(key: "appleIDXcodeToken")
|
||||||
|
public var appleIDXcodeToken: String?
|
||||||
|
|
||||||
@KeychainItem(key: "signingCertificatePrivateKey")
|
@KeychainItem(key: "signingCertificatePrivateKey")
|
||||||
public var signingCertificatePrivateKey: Data?
|
public var signingCertificatePrivateKey: Data?
|
||||||
|
|
||||||
@@ -83,6 +89,12 @@ public class Keychain
|
|||||||
@KeychainItem(key: "adiPb")
|
@KeychainItem(key: "adiPb")
|
||||||
public var adiPb: String?
|
public var adiPb: String?
|
||||||
|
|
||||||
|
// for some reason authenticated cert/session/team is completely not cached, which result in logging in for every request
|
||||||
|
// we save it here so when user logs out we can clear cached account/session/team
|
||||||
|
public var certificate: ALTCertificate? = nil
|
||||||
|
public var session: ALTAppleAPISession? = nil
|
||||||
|
public var team: ALTTeam? = nil
|
||||||
|
|
||||||
private init()
|
private init()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -91,7 +103,13 @@ public class Keychain
|
|||||||
{
|
{
|
||||||
self.appleIDEmailAddress = nil
|
self.appleIDEmailAddress = nil
|
||||||
self.appleIDPassword = nil
|
self.appleIDPassword = nil
|
||||||
|
self.appleIDAdsid = nil
|
||||||
|
self.appleIDXcodeToken = nil
|
||||||
self.signingCertificatePrivateKey = nil
|
self.signingCertificatePrivateKey = nil
|
||||||
self.signingCertificateSerialNumber = nil
|
self.signingCertificateSerialNumber = nil
|
||||||
|
|
||||||
|
self.certificate = nil
|
||||||
|
self.session = nil
|
||||||
|
self.team = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user