Skip to content

Commit

Permalink
feat(auth): add linkIdentity method (#392)
Browse files Browse the repository at this point in the history
  • Loading branch information
grdsdev committed May 18, 2024
1 parent 76233bb commit 7dfaa46
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 5 deletions.
4 changes: 1 addition & 3 deletions Examples/Examples/Profile/UserIdentityList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ struct UserIdentityList: View {
Button(provider.rawValue) {
Task {
do {
let response = try await supabase.auth.getLinkIdentityURL(provider: provider)
openURL(response.url)
debug("getLinkIdentityURL: \(response.url) opened for provider \(response.provider)")
try await supabase.auth.linkIdentity(provider: provider)
} catch {
self.error = error
}
Expand Down
57 changes: 55 additions & 2 deletions Sources/Auth/AuthClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -955,14 +955,67 @@ public final class AuthClient: Sendable {
try await user().identities ?? []
}

/// Links an OAuth identity to an existing user.
///
/// This method supports the PKCE flow.
///
/// - Parameters:
/// - provider: The provider you want to link the user with.
/// - scopes: A space-separated list of scopes granted to the OAuth application.
/// - redirectTo: A URL to send the user to after they are confirmed.
/// - queryParams: Additional query parameters to use.
/// - launchURL: Custom launch URL logic.
public func linkIdentity(
provider: Provider,
scopes: String? = nil,
redirectTo: URL? = nil,
queryParams: [(name: String, value: String?)] = [],
launchURL: @MainActor (_ url: URL) -> Void
) async throws {
let response = try await getLinkIdentityURL(
provider: provider,
scopes: scopes,
redirectTo: redirectTo,
queryParams: queryParams
)

await launchURL(response.url)
}

/// Links an OAuth identity to an existing user.
///
/// This method supports the PKCE flow.
///
/// - Parameters:
/// - provider: The provider you want to link the user with.
/// - scopes: A space-separated list of scopes granted to the OAuth application.
/// - redirectTo: A URL to send the user to after they are confirmed.
/// - queryParams: Additional query parameters to use.
///
/// - Note: This method opens the URL using the default URL opening mechanism for the platform, if you with to provide your own URL opening logic use ``linkIdentity(provider:scopes:redirectTo:queryParams:launchURL:)``.
public func linkIdentity(
provider: Provider,
scopes: String? = nil,
redirectTo: URL? = nil,
queryParams: [(name: String, value: String?)] = []
) async throws {
try await linkIdentity(
provider: provider,
scopes: scopes,
redirectTo: redirectTo,
queryParams: queryParams,
launchURL: { Current.urlOpener.open($0) }
)
}

/// Returns the URL to link the user's identity with an OAuth provider.
///
/// This method supports the PKCE flow.
///
/// - Parameters:
/// - provider: The provider you want to link the user with.
/// - scopes: The scopes to request from the OAuth provider.
/// - redirectTo: The redirect URL to use, specify a configured deep link.
/// - scopes: A space-separated list of scopes granted to the OAuth application.
/// - redirectTo: A URL to send the user to after they are confirmed.
/// - queryParams: Additional query parameters to use.
public func getLinkIdentityURL(
provider: Provider,
Expand Down
1 change: 1 addition & 0 deletions Sources/Auth/Internal/Dependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ struct Dependencies: Sendable {
var eventEmitter: AuthStateChangeEventEmitter = .shared
var date: @Sendable () -> Date = { Date() }
var codeVerifierStorage = CodeVerifierStorage.live
var urlOpener: URLOpener = .live

var encoder: JSONEncoder { configuration.encoder }
var decoder: JSONDecoder { configuration.decoder }
Expand Down
38 changes: 38 additions & 0 deletions Sources/Auth/Internal/URLOpener.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// URLOpener.swift
//
//
// Created by Guilherme Souza on 17/05/24.
//

import Foundation

#if canImport(WatchKit)
import WatchKit
#endif

#if canImport(UIKit)
import UIKit
#endif

#if canImport(AppKit)
import AppKit
#endif

struct URLOpener {
var open: @MainActor @Sendable (_ url: URL) -> Void
}

extension URLOpener {
static var live: Self {
URLOpener { url in
#if os(macOS)
NSWorkspace.shared.open(url)
#elseif os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst)
UIApplication.shared.open(url)
#elseif os(watchOS)
WKExtension.shared().openSystemURL(url)
#endif
}
}
}
24 changes: 24 additions & 0 deletions Tests/AuthTests/AuthClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,30 @@ final class AuthClientTests: XCTestCase {
)
}

func testLinkIdentity() async throws {
let url = "https://github.com/login/oauth/authorize?client_id=1234&redirect_to=com.supabase.swift-examples://&redirect_uri=http://127.0.0.1:54321/auth/v1/callback&response_type=code&scope=user:email&skip_http_redirect=true&state=jwt"
let sut = makeSUT { _ in
.stub(
"""
{
"url" : "\(url)"
}
"""
)
}

try storage.storeSession(.init(session: .validSession))

let receivedURL = LockIsolated<URL?>(nil)
Current.urlOpener.open = { url in
receivedURL.setValue(url)
}

try await sut.linkIdentity(provider: .github)

XCTAssertEqual(receivedURL.value?.absoluteString, url)
}

private func makeSUT(
fetch: ((URLRequest) async throws -> HTTPResponse)? = nil
) -> AuthClient {
Expand Down
8 changes: 8 additions & 0 deletions Tests/IntegrationTests/AuthClientIntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,14 @@ final class AuthClientIntegrationTests: XCTestCase {
}
}

func testLinkIdentity() async throws {
try await signUpIfNeededOrSignIn(email: mockEmail(), password: mockPassword())

try await authClient.linkIdentity(provider: .github) { url in
XCTAssertTrue(url.absoluteString.contains("github.com"))
}
}

@discardableResult
private func signUpIfNeededOrSignIn(
email: String,
Expand Down

0 comments on commit 7dfaa46

Please sign in to comment.