Add refresh tokens.
This commit is contained in:
parent
f9ffe542a5
commit
f780d78a48
|
@ -43,7 +43,7 @@ public extension MastodonClient {
|
|||
self?.oAuthContinuation = continuation
|
||||
|
||||
oauthClient?.renewAccessToken(
|
||||
withRefreshToken: "refrestoken",
|
||||
withRefreshToken: refreshToken,
|
||||
completionHandler: { result in
|
||||
switch result {
|
||||
case let .success((credentials, _, _)):
|
||||
|
|
|
@ -234,6 +234,7 @@
|
|||
F89D6C4329718092001DA3D4 /* AccentsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccentsSection.swift; sourceTree = "<group>"; };
|
||||
F89D6C4529718193001DA3D4 /* ThemeSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSection.swift; sourceTree = "<group>"; };
|
||||
F89D6C49297196FF001DA3D4 /* ImagesViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesViewer.swift; sourceTree = "<group>"; };
|
||||
F89F0605299139F6003DC875 /* Vernissage-002.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-002.xcdatamodel"; sourceTree = "<group>"; };
|
||||
F8A93D7D2965FD89001D8331 /* UserProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileView.swift; sourceTree = "<group>"; };
|
||||
F8B1E6502973FB7E00EE0D10 /* ToastrService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastrService.swift; sourceTree = "<group>"; };
|
||||
F8B9B344298D1FCB009CC69C /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = "<group>"; };
|
||||
|
@ -1071,10 +1072,11 @@
|
|||
F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
F89F0605299139F6003DC875 /* Vernissage-002.xcdatamodel */,
|
||||
F8C937A929882CA90004D782 /* Vernissage-001.xcdatamodel */,
|
||||
F88C2477295C37BB0006098B /* Vernissage.xcdatamodel */,
|
||||
);
|
||||
currentVersion = F8C937A929882CA90004D782 /* Vernissage-001.xcdatamodel */;
|
||||
currentVersion = F89F0605299139F6003DC875 /* Vernissage-002.xcdatamodel */;
|
||||
path = Vernissage.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
|
|
|
@ -17,7 +17,7 @@ extension ApplicationSettings {
|
|||
@NSManaged public var theme: Int32
|
||||
@NSManaged public var tintColor: Int32
|
||||
@NSManaged public var avatarShape: Int32
|
||||
|
||||
@NSManaged public var lastRefreshTokens: Date
|
||||
}
|
||||
|
||||
extension ApplicationSettings : Identifiable {
|
||||
|
|
|
@ -56,7 +56,7 @@ public class AuthorizationService {
|
|||
// Verify address.
|
||||
_ = try await client.readInstanceInformation()
|
||||
|
||||
// Create application (we will get clientId amd clientSecret).
|
||||
// Create application (we will get clientId and clientSecret).
|
||||
let oAuthApp = try await client.createApp(
|
||||
named: AppConstants.oauthApplicationName,
|
||||
redirectUri: AppConstants.oauthRedirectUri,
|
||||
|
@ -76,8 +76,8 @@ public class AuthorizationService {
|
|||
// Get account information from server.
|
||||
let account = try await authenticatedClient.verifyCredentials()
|
||||
|
||||
// Create account object in database.
|
||||
let accountData = AccountDataHandler.shared.createAccountDataEntity()
|
||||
// Get/create account object in database.
|
||||
let accountData = self.getAccountData(account: account)
|
||||
|
||||
accountData.id = account.id
|
||||
accountData.username = account.username
|
||||
|
@ -125,6 +125,44 @@ public class AuthorizationService {
|
|||
result(accountData)
|
||||
}
|
||||
|
||||
public func refreshAccessTokens() async {
|
||||
let accounts = AccountDataHandler.shared.getAccountsData()
|
||||
|
||||
await withTaskGroup(of: Void.self) { group in
|
||||
for account in accounts {
|
||||
group.addTask {
|
||||
do {
|
||||
try await self.refreshAccessToken(accountData: account)
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "Error during refreshing access token for account '\(account.acct)'.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func refreshAccessToken(accountData: AccountData) async throws {
|
||||
let client = MastodonClient(baseURL: accountData.serverUrl)
|
||||
|
||||
guard let refreshToken = accountData.refreshToken else {
|
||||
return
|
||||
}
|
||||
|
||||
let oAuthSwiftCredential = try await client.refreshToken(clientId: accountData.clientId,
|
||||
clientSecret: accountData.clientSecret,
|
||||
refreshToken: refreshToken)
|
||||
|
||||
// Get authenticated client.
|
||||
let authenticatedClient = client.getAuthenticated(token: oAuthSwiftCredential.oauthToken)
|
||||
|
||||
// Get account information from server.
|
||||
let account = try await authenticatedClient.verifyCredentials()
|
||||
try await self.update(account: accountData,
|
||||
basedOn: account,
|
||||
accessToken: oAuthSwiftCredential.oauthToken,
|
||||
refreshToken: oAuthSwiftCredential.oauthRefreshToken)
|
||||
}
|
||||
|
||||
private func refreshCredentials(for accountData: AccountData,
|
||||
presentationContextProvider: ASWebAuthenticationPresentationContextProviding
|
||||
) async throws {
|
||||
|
@ -191,4 +229,12 @@ public class AuthorizationService {
|
|||
// Save account data in database and in application state.
|
||||
CoreDataHandler.shared.save()
|
||||
}
|
||||
|
||||
private func getAccountData(account: Account) -> AccountData {
|
||||
if let accountFromDb = AccountDataHandler.shared.getAccountData(accountId: account.id) {
|
||||
return accountFromDb
|
||||
}
|
||||
|
||||
return AccountDataHandler.shared.createAccountDataEntity()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>Vernissage-001.xcdatamodel</string>
|
||||
<string>Vernissage-002.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22C65" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="AccountData" representedClassName="AccountData" syncable="YES">
|
||||
<attribute name="accessToken" optional="YES" attributeType="String"/>
|
||||
<attribute name="acct" attributeType="String"/>
|
||||
<attribute name="avatar" optional="YES" attributeType="URI"/>
|
||||
<attribute name="avatarData" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="clientId" attributeType="String"/>
|
||||
<attribute name="clientSecret" attributeType="String"/>
|
||||
<attribute name="clientVapidKey" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="String"/>
|
||||
<attribute name="displayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="followersCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="followingCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="header" optional="YES" attributeType="URI"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="lastSeenStatusId" optional="YES" attributeType="String"/>
|
||||
<attribute name="locked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="note" optional="YES" attributeType="String"/>
|
||||
<attribute name="refreshToken" optional="YES" attributeType="String"/>
|
||||
<attribute name="serverUrl" attributeType="URI"/>
|
||||
<attribute name="statusesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="url" optional="YES" attributeType="URI"/>
|
||||
<attribute name="username" attributeType="String"/>
|
||||
<relationship name="statuses" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="StatusData" inverseName="pixelfedAccount" inverseEntity="StatusData"/>
|
||||
</entity>
|
||||
<entity name="ApplicationSettings" representedClassName="ApplicationSettings" syncable="YES">
|
||||
<attribute name="avatarShape" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="YES"/>
|
||||
<attribute name="currentAccount" optional="YES" attributeType="String"/>
|
||||
<attribute name="lastRefreshTokens" attributeType="Date" defaultDateTimeInterval="694256400" usesScalarValueType="NO"/>
|
||||
<attribute name="theme" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="tintColor" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="YES"/>
|
||||
</entity>
|
||||
<entity name="AttachmentData" representedClassName="AttachmentData" syncable="YES">
|
||||
<attribute name="blurhash" optional="YES" attributeType="String"/>
|
||||
<attribute name="data" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES"/>
|
||||
<attribute name="exifCamera" optional="YES" attributeType="String"/>
|
||||
<attribute name="exifCreatedDate" optional="YES" attributeType="String"/>
|
||||
<attribute name="exifExposure" optional="YES" attributeType="String"/>
|
||||
<attribute name="exifLens" optional="YES" attributeType="String"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="metaImageHeight" optional="YES" attributeType="Integer 32" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="metaImageWidth" optional="YES" attributeType="Integer 32" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="previewUrl" optional="YES" attributeType="URI"/>
|
||||
<attribute name="remoteUrl" optional="YES" attributeType="URI"/>
|
||||
<attribute name="statusId" attributeType="String"/>
|
||||
<attribute name="text" optional="YES" attributeType="String"/>
|
||||
<attribute name="type" attributeType="String"/>
|
||||
<attribute name="url" attributeType="URI"/>
|
||||
<relationship name="statusRelation" maxCount="1" deletionRule="Nullify" destinationEntity="StatusData" inverseName="attachmentsRelation" inverseEntity="StatusData"/>
|
||||
</entity>
|
||||
<entity name="StatusData" representedClassName="StatusData" syncable="YES">
|
||||
<attribute name="accountAvatar" optional="YES" attributeType="URI"/>
|
||||
<attribute name="accountDisplayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="accountId" attributeType="String"/>
|
||||
<attribute name="accountUsername" optional="YES" attributeType="String"/>
|
||||
<attribute name="applicationName" optional="YES" attributeType="String"/>
|
||||
<attribute name="applicationWebsite" optional="YES" attributeType="URI"/>
|
||||
<attribute name="bookmarked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="content" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="String"/>
|
||||
<attribute name="favourited" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="favouritesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="inReplyToAccount" optional="YES" attributeType="String"/>
|
||||
<attribute name="inReplyToId" optional="YES" attributeType="String"/>
|
||||
<attribute name="muted" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="pinned" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="reblogged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="rebloggedAccountAvatar" optional="YES" attributeType="URI"/>
|
||||
<attribute name="rebloggedAccountDisplayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebloggedAccountId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebloggedAccountUsername" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebloggedStatusId" optional="YES" attributeType="String"/>
|
||||
<attribute name="reblogsCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="repliesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sensitive" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="spoilerText" optional="YES" attributeType="String"/>
|
||||
<attribute name="uri" attributeType="String"/>
|
||||
<attribute name="url" optional="YES" attributeType="URI"/>
|
||||
<attribute name="visibility" attributeType="String"/>
|
||||
<relationship name="attachmentsRelation" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="AttachmentData" inverseName="statusRelation" inverseEntity="AttachmentData"/>
|
||||
<relationship name="pixelfedAccount" maxCount="1" deletionRule="Nullify" destinationEntity="AccountData" inverseName="statuses" inverseEntity="AccountData"/>
|
||||
</entity>
|
||||
</model>
|
|
@ -58,6 +58,9 @@ struct VernissageApp: App {
|
|||
// Load user preferences from database.
|
||||
self.loadUserPreferences()
|
||||
|
||||
// Refresh other access tokens.
|
||||
await self.refreshAccessTokens()
|
||||
|
||||
// Verify access token correctness.
|
||||
let authorizationSession = AuthorizationSession()
|
||||
await AuthorizationService.shared.verifyAccount(session: authorizationSession) { accountData in
|
||||
|
@ -124,6 +127,23 @@ struct VernissageApp: App {
|
|||
|
||||
ImagePipeline.shared = pipeline
|
||||
}
|
||||
|
||||
private func refreshAccessTokens() async {
|
||||
let defaultSettings = ApplicationSettingsHandler.shared.getDefaultSettings()
|
||||
print(defaultSettings.lastRefreshTokens)
|
||||
|
||||
// Run refreshing access tokens once per day.
|
||||
guard let refreshTokenDate = Calendar.current.date(byAdding: .day, value: 1, to: defaultSettings.lastRefreshTokens), refreshTokenDate < Date.now else {
|
||||
return
|
||||
}
|
||||
|
||||
// Refresh access tokens.
|
||||
await AuthorizationService.shared.refreshAccessTokens()
|
||||
|
||||
// Update time when refresh tokens has been updated.
|
||||
defaultSettings.lastRefreshTokens = Date.now
|
||||
CoreDataHandler.shared.save()
|
||||
}
|
||||
}
|
||||
|
||||
class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
|
|
Loading…
Reference in New Issue