Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gravatar OAuth in SwiftUI #359

Merged
merged 13 commits into from
Aug 23, 2024
Merged

Gravatar OAuth in SwiftUI #359

merged 13 commits into from
Aug 23, 2024

Conversation

etoledom
Copy link
Contributor

@etoledom etoledom commented Aug 21, 2024

Description

This PR implements the Gravatar OAuth flow.

CleanShot 2024-08-22 at 14 51 23

Testing Steps

  • Build and run the SwiftUI demo app
  • Go to Profile Editor with oauth demo screen.
  • insert an email and open the editor.
    • Check that OAuth flow works as expected.

Comment on lines 96 to 103
enum OAuthError: Error {
case notConfigured
case couldNotCreateOAuthURLWithGivenSecrets
case couldNotGetCodeFromURL(String)
case oauthResponseError(String)
case unknown(Error)
case couldNotStoreToken(Error)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This became a bit messy and I don't like it:

  • notConfigured: Secrets are not set up. This maybe could be a assertionFailure instead of throwing? The idea is to warn the developer.
  • couldNotCreateOAuthURLWithGivenSecrets this comes from using Encodable to add the query items to the URL. Probably if we add them manually we wouldn't need this error. And if this error happens, it's probably a developer mistake which should be catch on dev time (maybe assertion also?)
  • case couldNotGetCodeFromURL(String) error on parsing the callback URL to get the code. This could be a runtime error but not sure how could this be handled 🤔 .
  • oauthResponseError(String) Something went wrong at the backend. Needs to be handled in the UI.
  • unknown ¯_(ツ)_/¯ ... Probably on the UI also.
  • couldNotStoreToken(Error) Keychain error. I got errors here because the keychan was wrongly set up. But it was a dev issue. Probably there are instances that this could be a user issue, but again, not sure how this should be handled. Technically we have the token and the user should be able to edit the profile, so I don't think that throwing is a good option. The problem is that they will need to be authenticated again next time.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would add the assertionFailures for notConfigured and couldNotCreateOAuthURLWithGivenSecrets but not necessarily replace the "throw" statements. We still need the throws for prod. Throws also allow us return non-optional values.

Copy link
Contributor

Choose a reason for hiding this comment

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

couldNotStoreToken(Error) Keychain error. I got errors here because the keychan was wrongly set up. But it was a dev issue. Probably there are instances that this could be a user issue, but again, not sure how this should be handled. Technically we have the token and the user should be able to edit the profile, so I don't think that throwing is a good option. The problem is that they will need to be authenticated again next time

As you said, since the user is able to continue I think we don't need to re-throw this one. This can be something we swallow inside a separate do { ... } catch {...} and handle just with an assertionFailure.

Copy link
Contributor

@pinarol pinarol left a comment

Choose a reason for hiding this comment

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

Looks pretty good overall. Just dropping some non-critical comments and a question.

We need UI for this 2 case but it doesn't have to be in this PR.

  • we don’t have the token yet but we’ll try (we need an activity indicating UI)
  • we tried to get the token but we failed (we can reuse the ContentLoadingErrorView here as well)

Sources/GravatarUI/SwiftUI/OAuthSession/Keychain.swift Outdated Show resolved Hide resolved
Sources/GravatarUI/SwiftUI/OAuthSession/OAuthSession.swift Outdated Show resolved Hide resolved
Sources/GravatarUI/SwiftUI/OAuthSession/OAuthSession.swift Outdated Show resolved Hide resolved
Comment on lines 96 to 103
enum OAuthError: Error {
case notConfigured
case couldNotCreateOAuthURLWithGivenSecrets
case couldNotGetCodeFromURL(String)
case oauthResponseError(String)
case unknown(Error)
case couldNotStoreToken(Error)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I would add the assertionFailures for notConfigured and couldNotCreateOAuthURLWithGivenSecrets but not necessarily replace the "throw" statements. We still need the throws for prod. Throws also allow us return non-optional values.

Sources/GravatarUI/SwiftUI/OAuthSession/OAuthSession.swift Outdated Show resolved Hide resolved
case let error as Keychain.KeychainError:
return .couldNotStoreToken(error)
default:
print("🛑 Error: \(type(of: error)) - \(error.localizedDescription)")
Copy link
Contributor

Choose a reason for hiding this comment

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

let's remove the "print" call. We can probably use the Apple’s os.log framework for logging, doesn't have to be in this PR though. 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, this was for debugging and see the types of the error thrown to be catch 👍
os.log is a good idea. It might help developers debug issues on their users.


extension OAuthError {
static func from(error: Error) -> OAuthError {
switch error {
Copy link
Contributor

Choose a reason for hiding this comment

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

We can catch "DecodingError"s and re-throw them after adding an assertionFailure maybe? This means our decoding keys or types are not matching the response, so the developer should fix it.

case let error as DecodingError:
   assertionFailure("Unable to decode the response. Error: \(error.description)")
   throw .decodingError(error)

case .avatarPicker:
AvatarPickerView(model: .init(email: email, authToken: token))
}
Button("Log out (for testing only)") {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is meant to be a temp Button I guess but I feel like it can be useful during development in the long run as well. So how about we wrap it with some kind of compiler flag and keep it?

#if DEMO

#endif

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@pinarol - I tried this and the Build Conditions from the demo app won't be passed to the package, so there's no access to it from within the SDK.

I decided to move this button to the demo screen, previous to present the picker.

Now we will have a Log out button when we detect a session:

CleanShot 2024-08-22 at 14 28 18@2x

Copy link
Contributor

Choose a reason for hiding this comment

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

I see. That works too. 👍

@etoledom etoledom marked this pull request as ready for review August 22, 2024 12:48
@wpmobilebot
Copy link

wpmobilebot commented Aug 22, 2024

Gravatar UIKit Prototype Build📲 You can test the changes from this Pull Request in Gravatar UIKit Prototype Build by scanning the QR code below to install the corresponding build.
App NameGravatar UIKit Prototype Build Gravatar UIKit Prototype Build
Build Number1126
Version1.0
Bundle IDcom.automattic.gravatar-sdk-demo-uikit.prototype-build
Commite3164d5
App Center BuildGravatar SDK Demo - UIKit #57
Automatticians: You can use our internal self-serve MC tool to give yourself access to App Center if needed.

@wpmobilebot
Copy link

wpmobilebot commented Aug 22, 2024

Gravatar SwiftUI Prototype Build📲 You can test the changes from this Pull Request in Gravatar SwiftUI Prototype Build by scanning the QR code below to install the corresponding build.
App NameGravatar SwiftUI Prototype Build Gravatar SwiftUI Prototype Build
Build Number1126
Version1.0
Bundle IDcom.automattic.gravatar-sdk-demo-swiftui.prototype-build
Commite3164d5
App Center BuildGravatar SDK Demo - SwiftUI #56
Automatticians: You can use our internal self-serve MC tool to give yourself access to App Center if needed.

@etoledom etoledom requested a review from pinarol August 22, 2024 12:57
Task {
isAuthenticating = true
if !oauthSession.hasSession(with: email) {
_ = try? await oauthSession.retrieveAccessToken(with: email)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The possible errors are still not yet properly handled here, but we can do this alongside building the error screens to present these errors.

@pinarol
Copy link
Contributor

pinarol commented Aug 22, 2024

Something caught my attention, I see the diff for my secrets file:

Screenshot 2024-08-22 at 15 59 07

Seems like the path doesn't match the paths in the .gitignore

@etoledom
Copy link
Contributor Author

@pinarol - I believe this is ready for another look 👀

@etoledom
Copy link
Contributor Author

etoledom commented Aug 22, 2024

Something caught my attention, I see the diff for my secrets file:

Screenshot 2024-08-22 at 15 59 07 Seems like the path doesn't match the paths in the `.gitignore`

You can delete this file.

This was an error when we renamed the demo apps. We left this file which is not supposed to be in source control inside the old folder name. Now it shows because I removed its entry from .gitignore.

You get this since this file still exist in your machine, but it's safe to be deleted 👍

Copy link
Contributor

@pinarol pinarol left a comment

Choose a reason for hiding this comment

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

Looks good, just leaving some nits. I don't need to check again.

Sources/GravatarUI/SwiftUI/OAuthSession/Keychain.swift Outdated Show resolved Hide resolved
Sources/GravatarUI/SwiftUI/View+Additions.swift Outdated Show resolved Hide resolved
@pinarol
Copy link
Contributor

pinarol commented Aug 22, 2024

You get this since this file still exist in your machine, but it's safe to be deleted 👍

Oh I see. Cool.

@etoledom etoledom merged commit 33d1283 into trunk Aug 23, 2024
9 checks passed
@etoledom etoledom deleted the etoledom/swiftui-oauth branch August 23, 2024 08:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants