Skip to content

Commit

Permalink
Merge pull request #1 from darrarski/feature/refresh-token
Browse files Browse the repository at this point in the history
Automatically refresh access token
  • Loading branch information
darrarski authored Jul 3, 2023
2 parents 09e5116 + f369efd commit edec40e
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 6 deletions.
91 changes: 88 additions & 3 deletions Sources/GoogleDriveClient/Auth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public struct Auth: Sendable {
public typealias IsSignedInStream = @Sendable () -> AsyncStream<Bool>
public typealias SignIn = @Sendable () async -> Void
public typealias HandleRedirect = @Sendable (URL) async throws -> Void
public typealias RefreshToken = @Sendable () async throws -> Void
public typealias SignOut = @Sendable () async -> Void

public enum Error: Swift.Error, Sendable, Equatable {
Expand All @@ -20,19 +21,22 @@ public struct Auth: Sendable {
isSignedInStream: @escaping IsSignedInStream,
signIn: @escaping SignIn,
handleRedirect: @escaping HandleRedirect,
refreshToken: @escaping RefreshToken,
signOut: @escaping SignOut
) {
self.isSignedIn = isSignedIn
self.isSignedInStream = isSignedInStream
self.signIn = signIn
self.handleRedirect = handleRedirect
self.refreshToken = refreshToken
self.signOut = signOut
}

public var isSignedIn: IsSignedIn
public var isSignedInStream: IsSignedInStream
public var signIn: SignIn
public var handleRedirect: HandleRedirect
public var refreshToken: RefreshToken
public var signOut: SignOut
}

Expand Down Expand Up @@ -67,6 +71,7 @@ extension Auth: DependencyKey {
handleRedirect: { url in
@Dependency(\.googleDriveClientConfig) var config
@Dependency(\.urlSession) var session
@Dependency(\.date) var date

guard url.absoluteString.starts(with: config.redirectURI) else { return }

Expand Down Expand Up @@ -108,12 +113,87 @@ extension Auth: DependencyKey {
throw Error.response(statusCode: statusCode, data: responseData)
}

struct ResponseBody: Decodable {
var accessToken: String
var expiresIn: Int
var refreshToken: String
var tokenType: String
}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let credentials = try decoder.decode(Credentials.self, from: responseData)
let responseBody = try decoder.decode(ResponseBody.self, from: responseData)
let credentials = Credentials(
accessToken: responseBody.accessToken,
expiresAt: Date(
timeInterval: TimeInterval(responseBody.expiresIn),
since: date.now
),
refreshToken: responseBody.refreshToken,
tokenType: responseBody.tokenType
)

await saveCredentials(credentials)
},
refreshToken: {
@Dependency(\.googleDriveClientConfig) var config
@Dependency(\.urlSession) var session
@Dependency(\.date) var date

guard let credentials = await loadCredentials() else { return }
guard credentials.expiresAt <= date.now else { return }

let request: URLRequest = {
var components = URLComponents()
components.scheme = "https"
components.host = "oauth2.googleapis.com"
components.path = "/token"

var request = URLRequest(url: components.url!)
request.httpMethod = "POST"
request.setValue(
"application/x-www-form-urlencoded",
forHTTPHeaderField: "Content-Type"
)
request.httpBody = [
"client_id": config.clientID,
"grant_type": "refresh_token",
"refresh_token": credentials.refreshToken
].map { key, value in "\(key)=\(value)" }
.joined(separator: "&")
.data(using: .utf8)

return request
}()

let (responseData, response) = try await session.data(for: request)
let statusCode = (response as? HTTPURLResponse)?.statusCode

guard let statusCode, (200..<300).contains(statusCode) else {
throw Error.response(statusCode: statusCode, data: responseData)
}

struct ResponseBody: Decodable {
var accessToken: String
var expiresIn: Int
var tokenType: String
}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let responseBody = try decoder.decode(ResponseBody.self, from: responseData)
let newCredentials = Credentials(
accessToken: responseBody.accessToken,
expiresAt: Date(
timeInterval: TimeInterval(responseBody.expiresIn),
since: date.now
),
refreshToken: credentials.refreshToken,
tokenType: responseBody.tokenType
)

await saveCredentials(newCredentials)
},
signOut: {
await saveCredentials(nil)
}
Expand All @@ -123,16 +203,19 @@ extension Auth: DependencyKey {

private static func checkSignedIn() async {
@Dependency(\.googleDriveClientKeychain) var keychain

let isSignedIn = await keychain.loadCredentials() != nil
if await Self.isSignedIn.value != isSignedIn {
await Self.isSignedIn.setValue(isSignedIn)
}
}

private static func saveCredentials(_ credentials: Credentials?) async {
private static func loadCredentials() async -> Credentials? {
@Dependency(\.googleDriveClientKeychain) var keychain
return await keychain.loadCredentials()
}

private static func saveCredentials(_ credentials: Credentials?) async {
@Dependency(\.googleDriveClientKeychain) var keychain
if let credentials {
await keychain.saveCredentials(credentials)
} else {
Expand All @@ -146,6 +229,7 @@ extension Auth: DependencyKey {
isSignedInStream: unimplemented("\(Self.self).isSignedInStream", placeholder: .finished),
signIn: unimplemented("\(Self.self).signIn"),
handleRedirect: unimplemented("\(Self.self).handleRedirect"),
refreshToken: unimplemented("\(Self.self).refreshToken"),
signOut: unimplemented("\(Self.self).signOut")
)

Expand All @@ -162,6 +246,7 @@ extension Auth: DependencyKey {
await previewIsSignedIn.setValue(true)
},
handleRedirect: { _ in },
refreshToken: {},
signOut: {
await previewIsSignedIn.setValue(false)
}
Expand Down
3 changes: 3 additions & 0 deletions Sources/GoogleDriveClient/CreateFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,13 @@ public struct CreateFile: Sendable {

extension CreateFile: DependencyKey {
public static let liveValue = CreateFile { params in
@Dependency(\.googleDriveClientAuth) var auth
@Dependency(\.googleDriveClientKeychain) var keychain
@Dependency(\.urlSession) var session
@Dependency(\.uuid) var uuid

try await auth.refreshToken()

guard let credentials = await keychain.loadCredentials() else {
throw Error.notAuthorized
}
Expand Down
8 changes: 5 additions & 3 deletions Sources/GoogleDriveClient/Credentials.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import Foundation

public struct Credentials: Sendable, Equatable, Codable {
public init(
accessToken: String,
expiresIn: Int,
expiresAt: Date,
refreshToken: String,
tokenType: String
) {
self.accessToken = accessToken
self.expiresIn = expiresIn
self.expiresAt = expiresAt
self.refreshToken = refreshToken
self.tokenType = tokenType
}

public let accessToken: String
public let expiresIn: Int
public let expiresAt: Date
public let refreshToken: String
public let tokenType: String
}
3 changes: 3 additions & 0 deletions Sources/GoogleDriveClient/DeleteFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ public struct DeleteFile: Sendable {

extension DeleteFile: DependencyKey {
public static let liveValue = DeleteFile { params in
@Dependency(\.googleDriveClientAuth) var auth
@Dependency(\.googleDriveClientKeychain) var keychain
@Dependency(\.urlSession) var session

try await auth.refreshToken()

guard let credentials = await keychain.loadCredentials() else {
throw Error.notAuthorized
}
Expand Down
3 changes: 3 additions & 0 deletions Sources/GoogleDriveClient/GetFileData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ public struct GetFileData: Sendable {

extension GetFileData: DependencyKey {
public static let liveValue = GetFileData { params in
@Dependency(\.googleDriveClientAuth) var auth
@Dependency(\.googleDriveClientKeychain) var keychain
@Dependency(\.urlSession) var session

try await auth.refreshToken()

guard let credentials = await keychain.loadCredentials() else {
throw Error.notAuthorized
}
Expand Down
3 changes: 3 additions & 0 deletions Sources/GoogleDriveClient/ListFiles.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,12 @@ public struct ListFiles: Sendable {
extension ListFiles: DependencyKey {
public static let liveValue = ListFiles(
run: { params in
@Dependency(\.googleDriveClientAuth) var auth
@Dependency(\.googleDriveClientKeychain) var keychain
@Dependency(\.urlSession) var session

try await auth.refreshToken()

guard let credentials = await keychain.loadCredentials() else {
throw Error.notAuthorized
}
Expand Down
3 changes: 3 additions & 0 deletions Sources/GoogleDriveClient/UpdateFIle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,13 @@ public struct UpdateFile: Sendable {

extension UpdateFile: DependencyKey {
public static let liveValue = UpdateFile { params in
@Dependency(\.googleDriveClientAuth) var auth
@Dependency(\.googleDriveClientKeychain) var keychain
@Dependency(\.urlSession) var session
@Dependency(\.uuid) var uuid

try await auth.refreshToken()

guard let credentials = await keychain.loadCredentials() else {
throw Error.notAuthorized
}
Expand Down

0 comments on commit edec40e

Please sign in to comment.