diff --git a/.github/workflows/Tests.yml b/.github/workflows/Tests.yml index 9627414..2692d89 100644 --- a/.github/workflows/Tests.yml +++ b/.github/workflows/Tests.yml @@ -1,13 +1,16 @@ name: Tests -on: - push: +on: pull_request: - types: [opened] + types: [opened, synchronize, reopened] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: Tests: - runs-on: macos-14-xlarge + runs-on: macos-15-xlarge steps: - name: Cancel previous jobs uses: styfle/cancel-workflow-action@0.11.0 @@ -21,8 +24,8 @@ jobs: xcode-version: latest-stable - name: Build project - run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild build-for-testing -destination 'name=iPhone 14 Pro' -scheme 'PovioKitAuth-Package' | xcpretty + run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild build-for-testing -destination 'name=iPhone 16 Pro,OS=18.0' -scheme 'PovioKitAuth-Package' | xcpretty - name: Run tests - run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild test-without-building -destination 'name=iPhone 14 Pro' -scheme 'PovioKitAuth-Package' | xcpretty - + run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild test-without-building -destination 'name=iPhone 16 Pro,OS=18.0' -scheme 'PovioKitAuth-Package' | xcpretty + diff --git a/Package.swift b/Package.swift index 5411593..be4ce65 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( .library(name: "PovioKitAuthLinkedIn", targets: ["PovioKitAuthLinkedIn"]) ], dependencies: [ - .package(url: "https://github.com/google/GoogleSignIn-iOS", .upToNextMajor(from: "7.0.0")), + .package(url: "https://github.com/google/GoogleSignIn-iOS", .upToNextMajor(from: "8.0.0")), .package(url: "https://github.com/facebook/facebook-ios-sdk", .upToNextMajor(from: "17.0.0")), ], targets: [ diff --git a/Sources/Apple/AppleAuthenticator+Models.swift b/Sources/Apple/AppleAuthenticator+Models.swift index 935eb49..d09335d 100644 --- a/Sources/Apple/AppleAuthenticator+Models.swift +++ b/Sources/Apple/AppleAuthenticator+Models.swift @@ -25,13 +25,7 @@ public extension AppleAuthenticator { /// User full name represented by `givenName` and `familyName` public var name: String? { - guard let givenName = nameComponents?.givenName else { - return nameComponents?.familyName - } - guard let familyName = nameComponents?.familyName else { - return givenName - } - return "\(givenName) \(familyName)" + nameComponents?.name } } diff --git a/Sources/Apple/AppleAuthenticator.swift b/Sources/Apple/AppleAuthenticator.swift index 37901de..20dd83a 100644 --- a/Sources/Apple/AppleAuthenticator.swift +++ b/Sources/Apple/AppleAuthenticator.swift @@ -147,7 +147,7 @@ private extension AppleAuthenticator { } request.nonce = generateNonceString(length: length).sha256 case .custom(let value): - request.nonce = value + request.nonce = value.sha256 case .none: break } diff --git a/Sources/Core/PersonNameComponents+Extension.swift b/Sources/Core/PersonNameComponents+Extension.swift new file mode 100644 index 0000000..46c70b7 --- /dev/null +++ b/Sources/Core/PersonNameComponents+Extension.swift @@ -0,0 +1,42 @@ +// +// PersonNameComponents+Extension.swift +// PovioKitAuth +// +// Created by Egzon Arifi on 09/11/2024. +// + +import AuthenticationServices +import Foundation + +public extension PersonNameComponents { + var name: String? { + guard let givenName else { + return familyName + } + guard let familyName else { + return givenName + } + return "\(givenName) \(familyName)" + } +} + +public extension PersonNameComponents { + init( + namePrefix: String? = .none, + middleName: String? = .none, + givenName: String? = .none, + familyName: String? = .none, + nameSuffix: String? = .none, + nickname: String? = .none, + phoneticRepresentation: PersonNameComponents? = .none + ) { + self.init() + self.namePrefix = namePrefix + self.familyName = familyName + self.middleName = middleName + self.givenName = givenName + self.nameSuffix = nameSuffix + self.nickname = nickname + self.phoneticRepresentation = phoneticRepresentation + } +} diff --git a/Sources/Facebook/FacebookAuthenticator+Models.swift b/Sources/Facebook/FacebookAuthenticator+Models.swift index ca060d2..9996584 100644 --- a/Sources/Facebook/FacebookAuthenticator+Models.swift +++ b/Sources/Facebook/FacebookAuthenticator+Models.swift @@ -12,9 +12,14 @@ public extension FacebookAuthenticator { struct Response { public let userId: String public let token: String - public let name: String? + public let nameComponents: PersonNameComponents? public let email: String? public let expiresAt: Date + + /// User full name represented by `givenName` and `familyName` + public var name: String? { + nameComponents?.name + } } struct GraphResponse: Decodable { @@ -36,11 +41,3 @@ public extension FacebookAuthenticator { } } } - -public extension FacebookAuthenticator.GraphResponse { - var displayName: String { - [firstName, lastName] - .compactMap { $0 } - .joined(separator: " ") - } -} diff --git a/Sources/Facebook/FacebookAuthenticator.swift b/Sources/Facebook/FacebookAuthenticator.swift index 90bd25a..f133815 100644 --- a/Sources/Facebook/FacebookAuthenticator.swift +++ b/Sources/Facebook/FacebookAuthenticator.swift @@ -110,7 +110,10 @@ private extension FacebookAuthenticator { let authResponse = Response( userId: object.id, token: token.tokenString, - name: object.displayName, + nameComponents: PersonNameComponents( + givenName: object.firstName, + familyName: object.lastName + ), email: object.email, expiresAt: token.expirationDate ) diff --git a/Sources/Google/GoogleAuthenticator+Models.swift b/Sources/Google/GoogleAuthenticator+Models.swift index a0906f6..7f08a41 100644 --- a/Sources/Google/GoogleAuthenticator+Models.swift +++ b/Sources/Google/GoogleAuthenticator+Models.swift @@ -7,6 +7,7 @@ // import Foundation +import PovioKitAuthCore public extension GoogleAuthenticator { struct Response { @@ -14,8 +15,13 @@ public extension GoogleAuthenticator { public let idToken: String? public let accessToken: String public let refreshToken: String - public let name: String? + public let nameComponents: PersonNameComponents? public let email: String? public let expiresAt: Date? + + /// User full name represented by `givenName` and `familyName` + public var name: String? { + nameComponents?.name + } } } diff --git a/Sources/Google/GoogleAuthenticator.swift b/Sources/Google/GoogleAuthenticator.swift index 6e7f88d..1242a19 100644 --- a/Sources/Google/GoogleAuthenticator.swift +++ b/Sources/Google/GoogleAuthenticator.swift @@ -23,14 +23,24 @@ extension GoogleAuthenticator: Authenticator { /// SignIn user. /// /// Will asynchronously return the `Response` object on success or `Error` on error. - public func signIn(from presentingViewController: UIViewController, - hint: String? = .none, - additionalScopes: [String]? = .none) async throws -> Response { + public func signIn( + from presentingViewController: UIViewController, + clientId: String? = nil, + hint: String? = .none, + additionalScopes: [String]? = .none + ) async throws -> Response { guard !provider.hasPreviousSignIn() else { return try await restorePreviousSignIn() } - return try await signInUser(from: presentingViewController, hint: hint, additionalScopes: additionalScopes) + // set clientId if provided (clientId is needed when doing auth via firebase) + clientId.map { provider.configuration = .init(clientID: $0) } + + return try await signInUser( + from: presentingViewController, + hint: hint, + additionalScopes: additionalScopes + ) } /// Clears the signIn footprint and logs out the user immediatelly. @@ -46,7 +56,11 @@ extension GoogleAuthenticator: Authenticator { /// Boolean if given `url` should be handled. /// /// Call this from UIApplicationDelegate’s `application:openURL:options:` method. - public func canOpenUrl(_ url: URL, application: UIApplication, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool { + public func canOpenUrl( + _ url: URL, + application: UIApplication, + options: [UIApplication.OpenURLOptionsKey : Any] + ) -> Bool { GIDSignIn.sharedInstance.handle(url) } } @@ -66,11 +80,19 @@ private extension GoogleAuthenticator { } } } - - func signInUser(from presentingViewController: UIViewController, hint: String?, additionalScopes: [String]?) async throws -> Response { + + func signInUser( + from presentingViewController: UIViewController, + hint: String?, + additionalScopes: [String]? + ) async throws -> Response { try await withCheckedThrowingContinuation { continuation in provider - .signIn(withPresenting: presentingViewController, hint: hint, additionalScopes: additionalScopes) { result, error in + .signIn( + withPresenting: presentingViewController, + hint: hint, + additionalScopes: additionalScopes + ) { result, error in switch (result, error) { case (let signInResult?, _): continuation.resume(returning: signInResult.user.authResponse) @@ -107,7 +129,10 @@ private extension GIDGoogleUser { idToken: idToken?.tokenString, accessToken: accessToken.tokenString, refreshToken: refreshToken.tokenString, - name: profile?.name, + nameComponents: PersonNameComponents( + givenName: profile?.givenName, + familyName: profile?.familyName + ), email: profile?.email, expiresAt: accessToken.expirationDate ) diff --git a/Sources/LinkedIn/LinkedInAuthenticator+Models.swift b/Sources/LinkedIn/LinkedInAuthenticator+Models.swift index 73441cf..e835783 100644 --- a/Sources/LinkedIn/LinkedInAuthenticator+Models.swift +++ b/Sources/LinkedIn/LinkedInAuthenticator+Models.swift @@ -86,8 +86,13 @@ public extension LinkedInAuthenticator { struct Response { public let userId: String public let token: String - public let name: String + public let nameComponents: PersonNameComponents public let email: String public let expiresAt: Date + + /// User full name represented by `givenName` and `familyName` + public var name: String? { + nameComponents.name + } } } diff --git a/Sources/LinkedIn/LinkedInAuthenticator.swift b/Sources/LinkedIn/LinkedInAuthenticator.swift index bc91c4d..e2a8aa1 100644 --- a/Sources/LinkedIn/LinkedInAuthenticator.swift +++ b/Sources/LinkedIn/LinkedInAuthenticator.swift @@ -39,11 +39,13 @@ extension LinkedInAuthenticator: Authenticator { storage.set(true, forKey: storageIsAuthenticatedKey) - let name = "\(profileResponse.localizedFirstName) \(profileResponse.localizedLastName)" return Response( userId: profileResponse.id, token: authResponse.accessToken, - name: name, + nameComponents: PersonNameComponents( + givenName: profileResponse.localizedFirstName, + familyName: profileResponse.localizedLastName + ), email: emailResponse.emailAddress, expiresAt: authResponse.expiresIn )