Skip to content
This repository was archived by the owner on Feb 5, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions WordPressAuthenticator.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,22 @@
3F550D4E23DA429B007E5897 /* AppSelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F550D4D23DA429B007E5897 /* AppSelectorTests.swift */; };
3F550D5123DA4A9C007E5897 /* LinkMailPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F550D5023DA4A9C007E5897 /* LinkMailPresenter.swift */; };
3F550D5323DA4AC6007E5897 /* URLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F550D5223DA4AC6007E5897 /* URLHandler.swift */; };
3F879FD5293A3AB6005C2B48 /* OAuthTokenRequestBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FD4293A3AB6005C2B48 /* OAuthTokenRequestBody.swift */; };
3F879FD7293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FD6293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift */; };
3F879FD9293A48B2005C2B48 /* OAuthTokenResponseBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FD8293A48B2005C2B48 /* OAuthTokenResponseBody.swift */; };
3F879FDD293A500D005C2B48 /* URLRequest+OAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FDC293A500D005C2B48 /* URLRequest+OAuth.swift */; };
3F879FDF293A501D005C2B48 /* URLRequest+OAuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FDE293A501D005C2B48 /* URLRequest+OAuthTests.swift */; };
3F879FE2293A53F5005C2B48 /* OAuthRequestBody+GoogleSignIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FE0293A53CB005C2B48 /* OAuthRequestBody+GoogleSignIn.swift */; };
3F879FE4293A545C005C2B48 /* OAuthRequestBody+GoogleSignInTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FE3293A545C005C2B48 /* OAuthRequestBody+GoogleSignInTests.swift */; };
3F9439BE27D6F9B60067183A /* LoginPrologueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9439BD27D6F9B60067183A /* LoginPrologueViewController.swift */; };
3FE8071529364C410088420C /* Result+ConvenienceInitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE8071429364C410088420C /* Result+ConvenienceInitTests.swift */; };
3FE80717293650190088420C /* Result+ConvenienceInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE80716293650190088420C /* Result+ConvenienceInit.swift */; };
3FE8071B2936515F0088420C /* ASWebAuthenticationSession+Utils.swift .swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE8071A2936515F0088420C /* ASWebAuthenticationSession+Utils.swift .swift */; };
3FE8071D293652BB0088420C /* OAuthError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE8071C293652BB0088420C /* OAuthError.swift */; };
3FE8071F2936558F0088420C /* URL+GoogleSignInTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE8071E2936558F0088420C /* URL+GoogleSignInTests.swift */; };
3FE8072129365F6D0088420C /* URL+GoogleSignIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE8072029365F6D0088420C /* URL+GoogleSignIn.swift */; };
3FEC44F7293A0E4600EBDECF /* ProofKeyForCodeExchange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEC44F6293A0E4600EBDECF /* ProofKeyForCodeExchange.swift */; };
3FEC44F9293A0F2900EBDECF /* ProofKeyForCodeExchangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEC44F8293A0F2900EBDECF /* ProofKeyForCodeExchangeTests.swift */; };
3FFF2FC123D7ED7C00D38C77 /* EmailClients.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3FFF2FC023D7ED7C00D38C77 /* EmailClients.plist */; };
3FFF2FC323D7F53200D38C77 /* AppSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFF2FC223D7F53200D38C77 /* AppSelector.swift */; };
4A1DEF4A29341B1F00322608 /* LoggingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A1DEF4829341B1F00322608 /* LoggingTests.m */; };
Expand Down Expand Up @@ -236,13 +245,22 @@
3F550D4D23DA429B007E5897 /* AppSelectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSelectorTests.swift; sourceTree = "<group>"; };
3F550D5023DA4A9C007E5897 /* LinkMailPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkMailPresenter.swift; sourceTree = "<group>"; };
3F550D5223DA4AC6007E5897 /* URLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLHandler.swift; sourceTree = "<group>"; };
3F879FD4293A3AB6005C2B48 /* OAuthTokenRequestBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTokenRequestBody.swift; sourceTree = "<group>"; };
3F879FD6293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTokenRequestBodyTests.swift; sourceTree = "<group>"; };
3F879FD8293A48B2005C2B48 /* OAuthTokenResponseBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTokenResponseBody.swift; sourceTree = "<group>"; };
3F879FDC293A500D005C2B48 /* URLRequest+OAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+OAuth.swift"; sourceTree = "<group>"; };
3F879FDE293A501D005C2B48 /* URLRequest+OAuthTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+OAuthTests.swift"; sourceTree = "<group>"; };
3F879FE0293A53CB005C2B48 /* OAuthRequestBody+GoogleSignIn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OAuthRequestBody+GoogleSignIn.swift"; sourceTree = "<group>"; };
3F879FE3293A545C005C2B48 /* OAuthRequestBody+GoogleSignInTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OAuthRequestBody+GoogleSignInTests.swift"; sourceTree = "<group>"; };
3F9439BD27D6F9B60067183A /* LoginPrologueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPrologueViewController.swift; sourceTree = "<group>"; };
3FE8071429364C410088420C /* Result+ConvenienceInitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+ConvenienceInitTests.swift"; sourceTree = "<group>"; };
3FE80716293650190088420C /* Result+ConvenienceInit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+ConvenienceInit.swift"; sourceTree = "<group>"; };
3FE8071A2936515F0088420C /* ASWebAuthenticationSession+Utils.swift .swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ASWebAuthenticationSession+Utils.swift .swift"; sourceTree = "<group>"; };
3FE8071C293652BB0088420C /* OAuthError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthError.swift; sourceTree = "<group>"; };
3FE8071E2936558F0088420C /* URL+GoogleSignInTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+GoogleSignInTests.swift"; sourceTree = "<group>"; };
3FE8072029365F6D0088420C /* URL+GoogleSignIn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+GoogleSignIn.swift"; sourceTree = "<group>"; };
3FEC44F6293A0E4600EBDECF /* ProofKeyForCodeExchange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProofKeyForCodeExchange.swift; sourceTree = "<group>"; };
3FEC44F8293A0F2900EBDECF /* ProofKeyForCodeExchangeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProofKeyForCodeExchangeTests.swift; sourceTree = "<group>"; };
3FFF2FC023D7ED7C00D38C77 /* EmailClients.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = EmailClients.plist; sourceTree = "<group>"; };
3FFF2FC223D7F53200D38C77 /* AppSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSelector.swift; sourceTree = "<group>"; };
4A1DEF4829341B1F00322608 /* LoggingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LoggingTests.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -480,22 +498,30 @@
children = (
3FE8071A2936515F0088420C /* ASWebAuthenticationSession+Utils.swift .swift */,
3FE8071C293652BB0088420C /* OAuthError.swift */,
3F879FD4293A3AB6005C2B48 /* OAuthTokenRequestBody.swift */,
3F879FD8293A48B2005C2B48 /* OAuthTokenResponseBody.swift */,
3FEC44F6293A0E4600EBDECF /* ProofKeyForCodeExchange.swift */,
3FE80716293650190088420C /* Result+ConvenienceInit.swift */,
3F879FDC293A500D005C2B48 /* URLRequest+OAuth.swift */,
);
path = OAuth;
sourceTree = "<group>";
};
3FE807192936504F0088420C /* OAuth */ = {
isa = PBXGroup;
children = (
3F879FD6293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift */,
3FEC44F8293A0F2900EBDECF /* ProofKeyForCodeExchangeTests.swift */,
3FE8071429364C410088420C /* Result+ConvenienceInitTests.swift */,
3F879FDE293A501D005C2B48 /* URLRequest+OAuthTests.swift */,
);
path = OAuth;
sourceTree = "<group>";
};
3FE8072229365F740088420C /* GoogleSignIn */ = {
isa = PBXGroup;
children = (
3F879FE0293A53CB005C2B48 /* OAuthRequestBody+GoogleSignIn.swift */,
3FE8072029365F6D0088420C /* URL+GoogleSignIn.swift */,
);
path = GoogleSignIn;
Expand All @@ -504,6 +530,7 @@
3FE8072329365FC20088420C /* GoogleSignIn */ = {
isa = PBXGroup;
children = (
3F879FE3293A545C005C2B48 /* OAuthRequestBody+GoogleSignInTests.swift */,
3FE8071E2936558F0088420C /* URL+GoogleSignInTests.swift */,
);
path = GoogleSignIn;
Expand Down Expand Up @@ -1295,6 +1322,7 @@
files = (
CE73475624B77A3800A22660 /* SiteCredentialsViewController.swift in Sources */,
EE633D02287560E50002DE03 /* UITableView+Helpers.swift in Sources */,
3F879FD5293A3AB6005C2B48 /* OAuthTokenRequestBody.swift in Sources */,
982C8E7923021C20003F1BA0 /* LoginPrologueLoginMethodViewController.swift in Sources */,
B5609144208A563800399AE4 /* LoginPrologueSignupMethodViewController.swift in Sources */,
B56090D1208A4F5400399AE4 /* NUXViewController.swift in Sources */,
Expand All @@ -1304,6 +1332,7 @@
CE1B18C920EEC2C200BECC3F /* SocialService.swift in Sources */,
F12F9FB424D8A68E00771BCE /* AuthenticatorAnalyticsTracker.swift in Sources */,
988AD8A324CB839900BD045E /* TwoFAViewController.swift in Sources */,
3F879FD9293A48B2005C2B48 /* OAuthTokenResponseBody.swift in Sources */,
CE6BCD2E24A3A235001BCDC5 /* TextLabelTableViewCell.swift in Sources */,
B56090D3208A4F5400399AE4 /* NUXLinkAuthViewController.swift in Sources */,
B5609120208A555E00399AE4 /* SignupNavigationController.swift in Sources */,
Expand All @@ -1313,6 +1342,7 @@
02A526CF28A3A35D00FD1812 /* PasswordCoordinator.swift in Sources */,
98ED483624802F8F00992B2D /* GoogleAuthViewController.swift in Sources */,
F5C817E72582B2F300BD5A3B /* UIPasteboard+Detect.swift in Sources */,
3FEC44F7293A0E4600EBDECF /* ProofKeyForCodeExchange.swift in Sources */,
B56090EA208A51D000399AE4 /* LoginFields+Validation.swift in Sources */,
F1DE08CC24F4266A007AE6B3 /* StoredCredentialsAuthenticator.swift in Sources */,
CE1B18CC20EEC32400BECC3F /* WordPressComCredentials.swift in Sources */,
Expand All @@ -1335,6 +1365,7 @@
3FE8071D293652BB0088420C /* OAuthError.swift in Sources */,
B5609119208A555600399AE4 /* SiteInfoHeaderView.swift in Sources */,
B560913E208A563800399AE4 /* SigninEditingState.swift in Sources */,
3F879FDD293A500D005C2B48 /* URLRequest+OAuth.swift in Sources */,
CE2D03E024E5DD4500D18942 /* UnifiedSignupViewController.swift in Sources */,
98CF18F7248725370047B66C /* GoogleSignupConfirmationViewController.swift in Sources */,
1A21EE9822832BC300C940C6 /* WordPressComOAuthClientFacade+Swift.swift in Sources */,
Expand All @@ -1355,6 +1386,7 @@
B56090F9208A533200399AE4 /* WordPressAuthenticator+Events.swift in Sources */,
CEDE0D93242011E000CB3345 /* NSObject+Helpers.swift in Sources */,
020DEF6428AA091100C85D51 /* MagicLinkRequester.swift in Sources */,
3F879FE2293A53F5005C2B48 /* OAuthRequestBody+GoogleSignIn.swift in Sources */,
020BE74A23B0BD2E007FE54C /* WordPressAuthenticatorDisplayImages.swift in Sources */,
B560913A208A563800399AE4 /* LoginLinkRequestViewController.swift in Sources */,
B560910C208A54F800399AE4 /* WordPressComOAuthClientFacade.m in Sources */,
Expand Down Expand Up @@ -1423,21 +1455,25 @@
B501C045208FC68700D1E58F /* LoginFieldsValidationTests.swift in Sources */,
BA53D64824DFDF97001F1ABF /* WordPressSourceTagTests.swift in Sources */,
4A1DEF4B29341B1F00322608 /* LoggingTests.swift in Sources */,
3F879FDF293A501D005C2B48 /* URLRequest+OAuthTests.swift in Sources */,
D8610CEC2570A60C00A5DF27 /* NavigationToRootTests.swift in Sources */,
BA53D64D24DFE4E6001F1ABF /* ModalViewControllerPresentingSpy.swift in Sources */,
BA53D64624DFDE1D001F1ABF /* CredentialsTests.swift in Sources */,
D85C36EC256E10EA00D56E34 /* MockNavigationController.swift in Sources */,
3F550D4E23DA429B007E5897 /* AppSelectorTests.swift in Sources */,
BA53D64B24DFE07D001F1ABF /* WordpressAuthenticatorProvider.swift in Sources */,
4A1DEF4A29341B1F00322608 /* LoggingTests.m in Sources */,
3FEC44F9293A0F2900EBDECF /* ProofKeyForCodeExchangeTests.swift in Sources */,
CE16177821B70C1A00B82A47 /* WordPressAuthenticatorDisplayTextTests.swift in Sources */,
B501C048208FC79C00D1E58F /* LoginFacadeTests.m in Sources */,
3F879FD7293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift in Sources */,
3108613125AFA4830022F75E /* PasteboardTests.swift in Sources */,
3FE8071F2936558F0088420C /* URL+GoogleSignInTests.swift in Sources */,
D85C36F0256E118D00D56E34 /* NavigationToEnterAccountTests.swift in Sources */,
D85C36E6256E0DDE00D56E34 /* NavigationToEnterSiteTests.swift in Sources */,
D85C3882256E3FEC00D56E34 /* WordPressComSiteInfoTests.swift in Sources */,
D8611A672576236800A5DF27 /* NavigateBackTests.swift in Sources */,
3F879FE4293A545C005C2B48 /* OAuthRequestBody+GoogleSignInTests.swift in Sources */,
F12F9FB824D8A7FC00771BCE /* AnalyticsTrackerTests.swift in Sources */,
B501C046208FC6A700D1E58F /* WordPressAuthenticatorTests.swift in Sources */,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
extension OAuthTokenRequestBody {

static func googleSignInRequestBody(
clientId: String,
authCode: String,
pkce: ProofKeyForCodeExchange
) -> Self {
.init(
clientId: clientId,
// "The client secret obtained from the API Console Credentials page."
// - https://developers.google.com/identity/protocols/oauth2/native-app#step-2:-send-a-request-to-googles-oauth-2.0-server
//
// There doesn't seem to be any secret for iOS app credentials.
// The process works with an empty string...
clientSecret: "",
code: authCode,
codeVerifier: pkce.codeVerifier,
// As defined in the OAuth 2.0 specification, this field's value must be set to authorization_code.
// – https://developers.google.com/identity/protocols/oauth2/native-app#exchange-authorization-code
grantType: "authorization_code",
redirectURI: URL.redirectURI(from: clientId)
)
}
}
36 changes: 28 additions & 8 deletions WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,46 @@ import Foundation

extension URL {

// TODO: This is incomplete
static func googleSignInAuthURL(clientId: String) throws -> URL {
let baseURL = "https://accounts.google.com/o/oauth2/v2/auth"
// It's acceptable to force-unwrap here because, for this call to fail we'd need a developer
// error, which we would catch because the unit tests would crash.
static var googleSignInBaseURL = URL(string: "https://accounts.google.com/o/oauth2/v2/auth")!

static func googleSignInAuthURL(clientId: String, pkce: ProofKeyForCodeExchange) throws -> URL {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are essentially implement OAuth 2, I think we can replace "Google Sign In" with a more generic term? Like, this function becomes oauth2AuthenticationURL, what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm making assumption here, but I imagine the whole Google Sign In workflow can be used in "Sign In via Facebook", or even the WordPress OAuth2.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I was thinking about this before going AFK and realized I may be using unclear boundaries here.

You might have noticed I made an OAuth folder and a GoogleSignIn one. My intent was to separate the components that are GoogleSignIn specific from those that are part of the OAuth spec. One issue with how I executed this is that I haven't been cross checking between the OAuth and Google documentations so there might be stuff in the OAuth folder that's still Google specific, and vice versa.

Thinking about it since, I came to the conclusion that I'd prefer to separate OAuth- from Google- specific code only once we'll need to add a new OAuth client (likely WordPress OAuth2). As such, I'm tempted to follow up this PR with one that moves all the OAuth/ code into GoogleSignIn/, renaming some of the types if necessary.

My rationale for the suggestion above is that I'm more interested in having a working version of the SDK-less authentication than a general purpose OAuth client, even though building a client first might make it easier to add further authentication implementation in the future.

What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me. We probably don't need to "pre-abstract" stuff if there is no immediate plan to support another OAuth provider (service? server?). I trust you'll strike the right balance 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I trust you'll strike the right balance 👍

Thanks for the confidence.

let queryItems = [
("client_id", clientId),
("code_challenge", pkce.codeCallenge),
("code_challenge_method", pkce.method.urlQueryParameterValue),
("redirect_uri", redirectURI(from: clientId)),
("response_type", "code")
("response_type", "code"),
// TODO: We might want to add some of these or them configurable
//
// The request we make with the SDK asks for:
//
// - email
// - profile
// - https://www.googleapis.com/auth/userinfo.email
// - https://www.googleapis.com/auth/userinfo.profile
// - openid
//
// See https://developers.google.com/identity/protocols/oauth2/scopes
("scope", "https://www.googleapis.com/auth/userinfo.email")
Comment on lines +16 to +27
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoiler: I updated this in my WIP branch but haven't seen any difference in the behavior, but that's likely due to my implementation being quite minimal at this point. Or, it's possible that only the backend would notice the difference, when completing the authentication using the OAuth token.

].map { URLQueryItem(name: $0.0, value: $0.1) }

if #available(iOS 16.0, *) {
return URL(string: baseURL)!.appending(queryItems: queryItems)
return googleSignInBaseURL.appending(queryItems: queryItems)
} else {
var components = URLComponents(string: baseURL)!
// Given `googleSignInBaseURL` is assumed as a valid URL, a `URLComponents` instance
// should always be available.
var components = URLComponents(url: googleSignInBaseURL, resolvingAgainstBaseURL: false)!
components.queryItems = queryItems
return try components.asURL()
// Likewise, we can as long as the given `queryItems` are valid, we can assume `url` to
// not be nil. If `queryItems` are invalid, a developer error has been committed, and
// crashing is appropriate.
return components.url!
}
}

private static func redirectURI(from clientId: String) -> String {
static func redirectURI(from clientId: String) -> String {
// Google's client id is in the form: 123-abc245def.apps.googleusercontent.com
// The redirect URI uses the reverse-DNS notation.
let reverseDNSClientId = clientId.split(separator: ".").reversed().joined(separator: ".")
Expand Down
1 change: 1 addition & 0 deletions WordPressAuthenticator/OAuth/OAuthError.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
enum OAuthError: LocalizedError {

// ASWebAuthenticationSession
case inconsistentWebAuthenticationSessionCompletion

var errorDescription: String {
Expand Down
39 changes: 39 additions & 0 deletions WordPressAuthenticator/OAuth/OAuthTokenRequestBody.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/// Models the request to send for an OAuth token
///
/// - Note: See documentation at https://developers.google.com/identity/protocols/oauth2/native-app#exchange-authorization-code
struct OAuthTokenRequestBody: Encodable {
let clientId: String
let clientSecret: String
let code: String
let codeVerifier: String
let grantType: String
let redirectURI: String

enum CodingKeys: String, CodingKey {
case clientId = "client_id"
case clientSecret = "client_secret"
case code
case codeVerifier = "code_verifier"
case grantType = "grant_type"
case redirectURI = "redirect_uri"
}

func asURLEncodedData() throws -> Data {
let params = [
(CodingKeys.clientId.rawValue, clientId),
(CodingKeys.clientSecret.rawValue, clientSecret),
(CodingKeys.code.rawValue, code),
(CodingKeys.codeVerifier.rawValue, codeVerifier),
(CodingKeys.grantType.rawValue, grantType),
(CodingKeys.redirectURI.rawValue, redirectURI),
]

let items = params.map { URLQueryItem(name: $0.0, value: $0.1) }

var components = URLComponents()
components.queryItems = items

// We can assume `query` to never be nil because we set `queryItems` in the line above.
return Data(components.query!.utf8)
}
}
Loading