diff --git a/Sources/ImperialCore/Routing/FederatedServiceRouter.swift b/Sources/ImperialCore/Routing/FederatedServiceRouter.swift index 623a460a..de05090b 100644 --- a/Sources/ImperialCore/Routing/FederatedServiceRouter.swift +++ b/Sources/ImperialCore/Routing/FederatedServiceRouter.swift @@ -108,6 +108,9 @@ extension FederatedServiceRouter { .flatMap { buffer in return request.client.post(url, headers: self.callbackHeaders) { $0.body = buffer } }.flatMapThrowing { response in + if let refreshToken = try? response.content.get(String.self, at: ["refresh_token"]), !refreshToken.isEmpty { + request.session.setRefreshToken(refreshToken) + } return try response.content.get(String.self, at: ["access_token"]) } } diff --git a/Sources/ImperialGoogle/Standard/Google.swift b/Sources/ImperialGoogle/Standard/Google.swift index 1589f3a5..ac5b3811 100644 --- a/Sources/ImperialGoogle/Standard/Google.swift +++ b/Sources/ImperialGoogle/Standard/Google.swift @@ -14,7 +14,7 @@ public class Google: FederatedService { scope: [String] = [], completion: @escaping (Request, String) throws -> (EventLoopFuture) ) throws { - self.router = try GoogleRouter(callback: callback, completion: completion) + self.router = try GoogleRouter(callback: callback, completion: completion, accessType: .online) self.tokens = self.router.tokens self.router.scope = scope diff --git a/Sources/ImperialGoogle/Standard/GoogleAccessType.swift b/Sources/ImperialGoogle/Standard/GoogleAccessType.swift new file mode 100644 index 00000000..03d808c0 --- /dev/null +++ b/Sources/ImperialGoogle/Standard/GoogleAccessType.swift @@ -0,0 +1,4 @@ +public enum GoogleAccessType: String { + case offline = "offline" + case online = "online" +} diff --git a/Sources/ImperialGoogle/Standard/GoogleOffline.swift b/Sources/ImperialGoogle/Standard/GoogleOffline.swift new file mode 100644 index 00000000..21d43333 --- /dev/null +++ b/Sources/ImperialGoogle/Standard/GoogleOffline.swift @@ -0,0 +1,25 @@ +@_exported import ImperialCore +import Vapor + +public class GoogleOffline: FederatedService { + public var tokens: FederatedServiceTokens + public var router: FederatedServiceRouter + + @discardableResult + public required init( + routes: RoutesBuilder, + authenticate: String, + authenticateCallback: ((Request) throws -> (EventLoopFuture))?, + callback: String, + scope: [String] = [], + completion: @escaping (Request, String) throws -> (EventLoopFuture) + ) throws { + self.router = try GoogleRouter(callback: callback, completion: completion, accessType: .offline) + self.tokens = self.router.tokens + + self.router.scope = scope + try self.router.configureRoutes(withAuthURL: authenticate, authenticateCallback: authenticateCallback, on: routes) + + OAuthService.register(.google) + } +} diff --git a/Sources/ImperialGoogle/Standard/GoogleRouter.swift b/Sources/ImperialGoogle/Standard/GoogleRouter.swift index 56b70519..e84fe48c 100644 --- a/Sources/ImperialGoogle/Standard/GoogleRouter.swift +++ b/Sources/ImperialGoogle/Standard/GoogleRouter.swift @@ -8,6 +8,7 @@ public class GoogleRouter: FederatedServiceRouter { public let callbackURL: String public let accessTokenURL: String = "https://www.googleapis.com/oauth2/v4/token" public let service: OAuthService = .google + public var accessType: GoogleAccessType = .online public let callbackHeaders: HTTPHeaders = { var headers = HTTPHeaders() headers.contentType = .urlEncodedForm @@ -19,18 +20,32 @@ public class GoogleRouter: FederatedServiceRouter { self.callbackURL = callback self.callbackCompletion = completion } + + public convenience init( + callback: String, + completion: @escaping (Request, String) throws -> (EventLoopFuture), + accessType: GoogleAccessType + ) throws { + try self.init(callback: callback, completion: completion) + self.accessType = accessType + } public func authURL(_ request: Request) throws -> String { var components = URLComponents() components.scheme = "https" components.host = "accounts.google.com" - components.path = "/o/oauth2/auth" + components.path = "/o/oauth2/v2/auth" components.queryItems = [ clientIDItem, redirectURIItem, scopeItem, - codeResponseTypeItem + codeResponseTypeItem, + accessTypeItem ] + + if accessType == .offline { + components.queryItems?.append(promptItem) + } guard let url = components.url else { throw Abort(.internalServerError) @@ -46,4 +61,12 @@ public class GoogleRouter: FederatedServiceRouter { redirectURI: callbackURL) } + public var accessTypeItem: URLQueryItem { + .init(name: "access_type", value: accessType.rawValue) + } + + public var promptItem: URLQueryItem { + .init(name: "prompt", value: "consent") + } + }