Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,19 @@ import SwiftUI
@main
struct OAuthApp: App {

/// The observable oauth object
let oauth: OAuth = .init(.main)

/// Build the scene body
var body: some Scene {

WindowGroup {
ContentView()
}
.environment(\.oauth, oauth)

WindowGroup(id: "oauth") {
OAWebView()
OAWebView(oauth: oauth)
}
}
}
Expand Down
10 changes: 6 additions & 4 deletions Sources/OAuthKit/Views/OAWebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import WebKit
@MainActor
public struct OAWebView {

@Environment(\.oauth)
var oauth: OAuth
let oauth: OAuth
let view = WKWebView()

/// Public Initializer.
public init() { }
/// Initializer with the speciifed oauth object,
/// - Parameter oauth: the oauth object to use
public init(oauth: OAuth) {
self.oauth = oauth
}

public func makeWebView(context: Context) -> WKWebView {
view.navigationDelegate = context.coordinator
Expand Down
6 changes: 1 addition & 5 deletions Sources/OAuthKit/Views/OAWebViewCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ public class OAWebViewCoordinator: NSObject {
}
// Exchange the code for a token
oauth.token(provider: provider, code: code)
case .clientCredentials:
break
case .deviceCode:
break
case .pkce(let pkce):
// Verify the state
guard state == pkce.state else {
Expand All @@ -70,7 +66,7 @@ public class OAWebViewCoordinator: NSObject {
}
// Exchange the code for a token along with the pkce validation data
oauth.token(provider: provider, code: code, pkce: pkce)
case .refreshToken:
case .clientCredentials, .deviceCode, .refreshToken:
break
}
}
Expand Down
76 changes: 69 additions & 7 deletions Tests/OAuthKitTests/OAWebViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ final class OAWebViewTests {
}()

let oauth: OAuth
let webView: OAWebView

/// Initializer.
init() async throws {
oauth = .init(.module)
webView = .init(oauth: oauth)
oauth.urlSession = urlSession
}

Expand All @@ -42,23 +44,83 @@ final class OAWebViewTests {
#expect(environmentOAuth == oauth)
}

/// Tests the OAWebViewCoordinator coordinator policy decision.
/// TODO: This is fairly limited at the moment because we will receive errors
/// about accessing the `oauth` environment outside of being installed on a view.
/// Needs more investigation for testing SwiftUI views.
/// Tests the OAWebViewCoordinator coordinator policy decisions.
@Test("Coordinator Policy Decisons")
func whenCoordinatorDecidingPolicy() async throws {

let webView: OAWebView = .init()
// 1) Bad Request Expectations
let coordinator: OAWebViewCoordinator = webView.makeCoordinator()
#expect(coordinator.oauth == oauth)
let wkWebView = webView.view

var urlRequest: URLRequest = .init(url: URL(string: "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/codefiesta/OAuthKit")!)
urlRequest.url = nil

let navigationAction: WKNavigationAction = OAuthTestWKNavigationAction(urlRequest: urlRequest)
let policy = await coordinator.webView(wkWebView, decidePolicyFor: navigationAction)
var navigationAction: WKNavigationAction = OAuthTestWKNavigationAction(urlRequest: urlRequest)
var policy = await coordinator.webView(wkWebView, decidePolicyFor: navigationAction)
#expect(policy == .cancel)

let provider = oauth.providers[0]

// 2) Authorization Code Expectations
let state: String = .secureRandom()
let code: String = .secureRandom()

oauth.authorize(provider: provider, grantType: .authorizationCode(state))
coordinator.update(state: oauth.state)
var urlString = provider.redirectURI! + "?code=\(code)&state=\(state)"
urlRequest = .init(url: URL(string: urlString)!)

navigationAction = OAuthTestWKNavigationAction(urlRequest: urlRequest)
policy = await coordinator.webView(wkWebView, decidePolicyFor: navigationAction)
#expect(policy == .allow)

// 3) PKCE Expectations
let pkce: OAuth.PKCE = .init()
oauth.authorize(provider: provider, grantType: .pkce(pkce))
coordinator.update(state: oauth.state)
urlString = provider.redirectURI! + "?code=\(code)&state=\(pkce.state)"
urlRequest = .init(url: URL(string: urlString)!)

navigationAction = OAuthTestWKNavigationAction(urlRequest: urlRequest)
policy = await coordinator.webView(wkWebView, decidePolicyFor: navigationAction)
#expect(policy == .allow)
}

/// Tests to make sure the coordinator doesn't being requesting access tokens when we've detected state mismatches.
@Test("Coordinator Detects Mismatched States")
func whenCoordinatorDetectsMismatchedStates() async throws {

// 1) Bad Request Expectations
let coordinator: OAWebViewCoordinator = webView.makeCoordinator()
#expect(coordinator.oauth == oauth)
let wkWebView = webView.view

var urlRequest: URLRequest = .init(url: URL(string: "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/codefiesta/OAuthKit")!)
let provider = oauth.providers[0]

// 2) Authorization Code Expectations
let state: String = .secureRandom()
let code: String = .secureRandom()

oauth.authorize(provider: provider, grantType: .authorizationCode(state))
let urlString = provider.redirectURI! + "?code=\(code)&state=ABC-123"
urlRequest = .init(url: URL(string: urlString)!)
var navigationAction: WKNavigationAction = OAuthTestWKNavigationAction(urlRequest: urlRequest)
var policy = await coordinator.webView(wkWebView, decidePolicyFor: navigationAction)
#expect(policy == .allow)
#expect(oauth.state != .requestingAccessToken(provider))

// 3) PKCE Expectations
let pkce: OAuth.PKCE = .init()
oauth.authorize(provider: provider, grantType: .pkce(pkce))
coordinator.update(state: oauth.state)

navigationAction = OAuthTestWKNavigationAction(urlRequest: urlRequest)
policy = await coordinator.webView(wkWebView, decidePolicyFor: navigationAction)
#expect(policy == .allow)
#expect(oauth.state != .requestingAccessToken(provider))

}
}
#endif