diff --git a/Gigya.podspec b/Gigya.podspec index 7ab3efc0..3eb85bfc 100644 --- a/Gigya.podspec +++ b/Gigya.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'Gigya' - spec.version = '1.3.2' + spec.version = '1.4.0' spec.license = 'Apache 2.0' spec.homepage = 'https://developers.gigya.com/display/GD/Swift+SDK' spec.author = 'Gigya SAP' @@ -10,7 +10,7 @@ Pod::Spec.new do |spec| your Swift application DESC - spec.source = { :git => 'https://github.com/SAP/gigya-swift-sdk.git', :tag => 'core/v1.3.2' } + spec.source = { :git => 'https://github.com/SAP/gigya-swift-sdk.git', :tag => 'core/v1.4.0' } spec.module_name = 'Gigya' spec.swift_version = '5.3' diff --git a/GigyaAuth/GigyaAuth.xcodeproj/project.pbxproj b/GigyaAuth/GigyaAuth.xcodeproj/project.pbxproj index 58b2d147..9aa4263f 100644 --- a/GigyaAuth/GigyaAuth.xcodeproj/project.pbxproj +++ b/GigyaAuth/GigyaAuth.xcodeproj/project.pbxproj @@ -395,6 +395,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = YES; EXCLUDED_ARCHS = i386; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -432,6 +433,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = YES; EXCLUDED_ARCHS = i386; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/GigyaNss/GigyaNss.xcodeproj/project.pbxproj b/GigyaNss/GigyaNss.xcodeproj/project.pbxproj index 335cbd09..5c200250 100644 --- a/GigyaNss/GigyaNss.xcodeproj/project.pbxproj +++ b/GigyaNss/GigyaNss.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ E70F8BED2405215C00D52208 /* ActionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E70F8BEC2405215C00D52208 /* ActionFactory.swift */; }; E70F8BEF24053ABA00D52208 /* RegisterAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = E70F8BEE24053AB900D52208 /* RegisterAction.swift */; }; E70F8BF124053B5800D52208 /* LoginAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = E70F8BF024053B5800D52208 /* LoginAction.swift */; }; + E710952E288EC2E70062DCAD /* Flutter.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7E119B2266CD5C3006EE19C /* Flutter.xcframework */; }; E719C6AF24225216002F8C36 /* LogChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E719C6AE24225216002F8C36 /* LogChannel.swift */; }; E719C6B124226139002F8C36 /* SetAccountAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = E719C6B024226139002F8C36 /* SetAccountAction.swift */; }; E71CB3B22554029000D63726 /* JsEvaluatorHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E71CB3B12554029000D63726 /* JsEvaluatorHelper.swift */; }; @@ -153,7 +154,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E732A81828AA2CE00035DDEA /* Flutter.xcframework in Frameworks */, + E710952E288EC2E70062DCAD /* Flutter.xcframework in Frameworks */, E76421A325D51FFD00CDED4C /* GigyaAuth.framework in Frameworks */, E7288CA32455CFD800AD99CA /* Gigya.framework in Frameworks */, ); diff --git a/GigyaSwift.xcodeproj/project.pbxproj b/GigyaSwift.xcodeproj/project.pbxproj index 2611c346..1457be24 100644 --- a/GigyaSwift.xcodeproj/project.pbxproj +++ b/GigyaSwift.xcodeproj/project.pbxproj @@ -49,8 +49,11 @@ E70519F822C255F7008ECB25 /* TFAVerificationpTotpResolverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7BE5799229D284A00C17D93 /* TFAVerificationpTotpResolverTests.swift */; }; E70519FA22C27267008ECB25 /* ProvidersLoginWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E70519F922C27267008ECB25 /* ProvidersLoginWrapperTests.swift */; }; E70DC25722AFEF7300D7346D /* ProvidersLoginWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E70DC25622AFEF7300D7346D /* ProvidersLoginWrapper.swift */; }; - E71095332897D2200062DCAD /* WebBridgeInterruptionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E71095322897D2200062DCAD /* WebBridgeInterruptionManager.swift */; }; - E71095352897D22F0062DCAD /* WebBridgeFroceLoginResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = E71095342897D22F0062DCAD /* WebBridgeFroceLoginResolver.swift */; }; + E710951F286D89630062DCAD /* WebAuthnService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E710951E286D89630062DCAD /* WebAuthnService.swift */; }; + E7109521286DC44C0062DCAD /* WebAuthnDeviceIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7109520286DC44C0062DCAD /* WebAuthnDeviceIntegration.swift */; }; + E7109523287473340062DCAD /* OauthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7109522287473340062DCAD /* OauthService.swift */; }; + E7109526288446540062DCAD /* WebAuthnModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7109525288446540062DCAD /* WebAuthnModels.swift */; }; + E7109528288D4D400062DCAD /* WebAuthnAttestationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7109527288D4D400062DCAD /* WebAuthnAttestationUtils.swift */; }; E711A8B522E9A12500ADA304 /* URLSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E711A8B422E9A12500ADA304 /* URLSessionTests.swift */; }; E712E90822A662D6005397BA /* PluginViewWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E712E90722A662D6005397BA /* PluginViewWrapperTests.swift */; }; E712E90C22A664DB005397BA /* FakeUIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E712E90B22A664DB005397BA /* FakeUIViewController.swift */; }; @@ -82,18 +85,10 @@ E72FEC53234F19850028EF28 /* Lang.strings in Resources */ = {isa = PBXBuildFile; fileRef = E72FEC55234F19850028EF28 /* Lang.strings */; }; E731B72D232A53AD0032E111 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E731B72C232A53AD0032E111 /* AuthenticationServices.framework */; }; E731B72F232A53E70032E111 /* AppleSignInWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E731B72E232A53E70032E111 /* AppleSignInWrapper.swift */; }; - E732A80828AA2C200035DDEA /* Gigya.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7BE36732224219200A108D9 /* Gigya.framework */; platformFilter = ios; }; - E732A80928AA2C200035DDEA /* Gigya.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E7BE36732224219200A108D9 /* Gigya.framework */; platformFilter = ios; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - E732A80D28AA2C270035DDEA /* GigyaAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E764214525D51D2200CDED4C /* GigyaAuth.framework */; }; - E732A80E28AA2C270035DDEA /* GigyaAuth.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E764214525D51D2200CDED4C /* GigyaAuth.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - E732A80F28AA2C2D0035DDEA /* GigyaTfa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7FF6BB02691A84000FF8E8A /* GigyaTfa.framework */; }; - E732A81028AA2C2D0035DDEA /* GigyaTfa.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E7FF6BB02691A84000FF8E8A /* GigyaTfa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - E732A81128AA2C330035DDEA /* GigyaNss.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E775E8ED23C6170D0059151D /* GigyaNss.framework */; }; - E732A81228AA2C330035DDEA /* GigyaNss.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E775E8ED23C6170D0059151D /* GigyaNss.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - E732A81F28AA2D4F0035DDEA /* Flutter.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = E732A81D28AA2D4F0035DDEA /* Flutter.xcframework */; }; - E732A82028AA2D4F0035DDEA /* Flutter.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E732A81D28AA2D4F0035DDEA /* Flutter.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - E732A82128AA2D4F0035DDEA /* App.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = E732A81E28AA2D4F0035DDEA /* App.xcframework */; }; - E732A82228AA2D4F0035DDEA /* App.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E732A81E28AA2D4F0035DDEA /* App.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + E732A82F28BB62720035DDEA /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + E732A83028BB62720035DDEA /* (null) in Embed Frameworks */ = {isa = PBXBuildFile; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + E732A83128BB62720035DDEA /* Flutter.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = E710952B288EC1FF0062DCAD /* Flutter.xcframework */; }; + E732A83228BB62720035DDEA /* Flutter.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E710952B288EC1FF0062DCAD /* Flutter.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E73373AC2254D79200ADEDBB /* GigyaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E73373AB2254D79200ADEDBB /* GigyaDefinitions.swift */; }; E73373B02254FD4800ADEDBB /* Int.swift in Sources */ = {isa = PBXBuildFile; fileRef = E73373AF2254FD4800ADEDBB /* Int.swift */; }; E73373D5225E326100ADEDBB /* GigyaLoggerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E73373D4225E326100ADEDBB /* GigyaLoggerTest.swift */; }; @@ -116,7 +111,6 @@ E741DCE52771DDF600E46223 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E741DCE42771DDF600E46223 /* Assets.xcassets */; }; E741DCE82771DDF600E46223 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E741DCE72771DDF600E46223 /* Preview Assets.xcassets */; }; E741DCED2771DE1D00E46223 /* Gigya.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7BE36732224219200A108D9 /* Gigya.framework */; platformFilter = ios; }; - E741DCEE2771DE1D00E46223 /* Gigya.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E7BE36732224219200A108D9 /* Gigya.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E74C9275272A8CD300934C73 /* PKCEHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E74C9274272A8CD300934C73 /* PKCEHelper.swift */; }; E74E26692383E9B1002E361B /* GeneralUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = E74E26682383E9B1002E361B /* GeneralUtils.swift */; }; E756A2992275CB5C007FA801 /* SocialProviderLoginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E756A2982275CB5C007FA801 /* SocialProviderLoginTests.swift */; }; @@ -132,6 +126,14 @@ E760668D2278998E005E4CFD /* GigyaWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E760668A2278998E005E4CFD /* GigyaWebViewController.swift */; }; E76421DF25E2BFEB00CDED4C /* ApiRequestModel+GlobalConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = E76421DE25E2BFEB00CDED4C /* ApiRequestModel+GlobalConfig.swift */; }; E76AF6EB24C88BA8008B9114 /* ApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E76AF6EA24C88BA8008B9114 /* ApiService.swift */; }; + E76B94F7285089D100E63E3B /* GigyaAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E779232526A04F1E00446A7F /* GigyaAuth.framework */; }; + E76B94F8285089D100E63E3B /* GigyaAuth.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E779232526A04F1E00446A7F /* GigyaAuth.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + E76B94F9285089EF00E63E3B /* GigyaNss.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E775E8ED23C6170D0059151D /* GigyaNss.framework */; }; + E76B94FA285089EF00E63E3B /* GigyaNss.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E775E8ED23C6170D0059151D /* GigyaNss.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + E76B94FD28508A5E00E63E3B /* GigyaTfa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7FF6BB02691A84000FF8E8A /* GigyaTfa.framework */; }; + E76B94FE28508A5E00E63E3B /* GigyaTfa.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E7FF6BB02691A84000FF8E8A /* GigyaTfa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + E76B94FF28508A7600E63E3B /* Gigya.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7BE36732224219200A108D9 /* Gigya.framework */; }; + E76B950028508A7600E63E3B /* Gigya.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E7BE36732224219200A108D9 /* Gigya.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E76F2E942345E78800056D19 /* Gigya.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7BE36732224219200A108D9 /* Gigya.framework */; }; E76F2E952345E78800056D19 /* Gigya.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E7BE36732224219200A108D9 /* Gigya.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E774A54B224283DF00BC67D1 /* ApiRequestModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E774A54A224283DF00BC67D1 /* ApiRequestModel.swift */; }; @@ -236,6 +238,8 @@ E7E80DA22252041D00D3B663 /* ResolverProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7E80DA12252041D00D3B663 /* ResolverProtocol.swift */; }; E7E80DA522535A5700D3B663 /* ResponseDataGlobal.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7E80DA422535A5700D3B663 /* ResponseDataGlobal.swift */; }; E7E90CAD25875B7000576186 /* ReportingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7E90CAC25875B7000576186 /* ReportingService.swift */; }; + E7EDECE928D739B400801807 /* WebBridgeFroceLoginResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7EDECE728D739B400801807 /* WebBridgeFroceLoginResolver.swift */; }; + E7EDECEA28D739B400801807 /* WebBridgeInterruptionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7EDECE828D739B400801807 /* WebBridgeInterruptionManager.swift */; }; E7F63B1322A7E88200ABA24F /* PluginViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7F63B1222A7E88200ABA24F /* PluginViewControllerTests.swift */; }; E7F63B1722A7E9FC00ABA24F /* FakeWKScriptMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7F63B1622A7E9FC00ABA24F /* FakeWKScriptMessage.swift */; }; E7F63B1922A7ED5E00ABA24F /* LogRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7F63B1822A7ED5E00ABA24F /* LogRecord.swift */; }; @@ -302,29 +306,18 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - E732A80C28AA2C200035DDEA /* Embed Frameworks */ = { + E76B94F6285089BD00E63E3B /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( - E732A82028AA2D4F0035DDEA /* Flutter.xcframework in Embed Frameworks */, - E732A80E28AA2C270035DDEA /* GigyaAuth.framework in Embed Frameworks */, - E732A80928AA2C200035DDEA /* Gigya.framework in Embed Frameworks */, - E732A81028AA2C2D0035DDEA /* GigyaTfa.framework in Embed Frameworks */, - E732A81228AA2C330035DDEA /* GigyaNss.framework in Embed Frameworks */, - E732A82228AA2D4F0035DDEA /* App.xcframework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; - E741DCF12771DE1D00E46223 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - E741DCEE2771DE1D00E46223 /* Gigya.framework in Embed Frameworks */, + E732A83228BB62720035DDEA /* Flutter.xcframework in Embed Frameworks */, + E76B950028508A7600E63E3B /* Gigya.framework in Embed Frameworks */, + E76B94F8285089D100E63E3B /* GigyaAuth.framework in Embed Frameworks */, + E732A83028BB62720035DDEA /* (null) in Embed Frameworks */, + E76B94FE28508A5E00E63E3B /* GigyaTfa.framework in Embed Frameworks */, + E76B94FA285089EF00E63E3B /* GigyaNss.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -366,8 +359,12 @@ E70519D422C0C8B3008ECB25 /* TfaViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TfaViewController.swift; sourceTree = ""; }; E70519F922C27267008ECB25 /* ProvidersLoginWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProvidersLoginWrapperTests.swift; sourceTree = ""; }; E70DC25622AFEF7300D7346D /* ProvidersLoginWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProvidersLoginWrapper.swift; sourceTree = ""; }; - E71095322897D2200062DCAD /* WebBridgeInterruptionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebBridgeInterruptionManager.swift; sourceTree = ""; }; - E71095342897D22F0062DCAD /* WebBridgeFroceLoginResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebBridgeFroceLoginResolver.swift; sourceTree = ""; }; + E710951E286D89630062DCAD /* WebAuthnService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebAuthnService.swift; sourceTree = ""; }; + E7109520286DC44C0062DCAD /* WebAuthnDeviceIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebAuthnDeviceIntegration.swift; sourceTree = ""; }; + E7109522287473340062DCAD /* OauthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OauthService.swift; sourceTree = ""; }; + E7109525288446540062DCAD /* WebAuthnModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebAuthnModels.swift; sourceTree = ""; }; + E7109527288D4D400062DCAD /* WebAuthnAttestationUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebAuthnAttestationUtils.swift; sourceTree = ""; }; + E710952B288EC1FF0062DCAD /* Flutter.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Flutter.xcframework; path = GigyaNss/Flutter/Release/Flutter.xcframework; sourceTree = ""; }; E711A8B422E9A12500ADA304 /* URLSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTests.swift; sourceTree = ""; }; E712E90722A662D6005397BA /* PluginViewWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginViewWrapperTests.swift; sourceTree = ""; }; E712E90B22A664DB005397BA /* FakeUIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeUIViewController.swift; sourceTree = ""; }; @@ -409,8 +406,8 @@ E72FEC56234F19A50028EF28 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Lang.strings; sourceTree = ""; }; E731B72C232A53AD0032E111 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; E731B72E232A53E70032E111 /* AppleSignInWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleSignInWrapper.swift; sourceTree = ""; }; - E732A81D28AA2D4F0035DDEA /* Flutter.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Flutter.xcframework; path = GigyaNss/Flutter/Release/Flutter.xcframework; sourceTree = ""; }; - E732A81E28AA2D4F0035DDEA /* App.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = App.xcframework; path = GigyaNss/Flutter/Release/App.xcframework; sourceTree = ""; }; + E732A82328BB55240035DDEA /* App.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = App.xcframework; path = GigyaNss/Flutter/Debug/App.xcframework; sourceTree = ""; }; + E732A82428BB55240035DDEA /* Flutter.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Flutter.xcframework; path = GigyaNss/Flutter/Debug/Flutter.xcframework; sourceTree = ""; }; E73373AB2254D79200ADEDBB /* GigyaDefinitions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GigyaDefinitions.swift; sourceTree = ""; }; E73373AF2254FD4800ADEDBB /* Int.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Int.swift; sourceTree = ""; }; E73373D4225E326100ADEDBB /* GigyaLoggerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GigyaLoggerTest.swift; sourceTree = ""; }; @@ -597,6 +594,8 @@ E7E80DA12252041D00D3B663 /* ResolverProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolverProtocol.swift; sourceTree = ""; }; E7E80DA422535A5700D3B663 /* ResponseDataGlobal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseDataGlobal.swift; sourceTree = ""; }; E7E90CAC25875B7000576186 /* ReportingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportingService.swift; sourceTree = ""; }; + E7EDECE728D739B400801807 /* WebBridgeFroceLoginResolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebBridgeFroceLoginResolver.swift; sourceTree = ""; }; + E7EDECE828D739B400801807 /* WebBridgeInterruptionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebBridgeInterruptionManager.swift; sourceTree = ""; }; E7F63B1222A7E88200ABA24F /* PluginViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginViewControllerTests.swift; sourceTree = ""; }; E7F63B1622A7E9FC00ABA24F /* FakeWKScriptMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeWKScriptMessage.swift; sourceTree = ""; }; E7F63B1822A7ED5E00ABA24F /* LogRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogRecord.swift; sourceTree = ""; }; @@ -672,20 +671,20 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E732A81F28AA2D4F0035DDEA /* Flutter.xcframework in Frameworks */, E7DC2C6D2280778000A13426 /* libc++.tbd in Frameworks */, - E732A82128AA2D4F0035DDEA /* App.xcframework in Frameworks */, E7DC2C6C2280771700A13426 /* libz.tbd in Frameworks */, + E76B94F7285089D100E63E3B /* GigyaAuth.framework in Frameworks */, + E732A83128BB62720035DDEA /* Flutter.xcframework in Frameworks */, + E76B94F9285089EF00E63E3B /* GigyaNss.framework in Frameworks */, E7DC2C6B228076E600A13426 /* libsqlite3.tbd in Frameworks */, E73373F522633BDA00ADEDBB /* SafariServices.framework in Frameworks */, E731B72D232A53AD0032E111 /* AuthenticationServices.framework in Frameworks */, - E732A80D28AA2C270035DDEA /* GigyaAuth.framework in Frameworks */, + E732A82F28BB62720035DDEA /* (null) in Frameworks */, E77F7A2F2407EC7900FF90FB /* CoreFoundation.framework in Frameworks */, E73373F422633BD300ADEDBB /* SystemConfiguration.framework in Frameworks */, - E732A81128AA2C330035DDEA /* GigyaNss.framework in Frameworks */, - E732A80F28AA2C2D0035DDEA /* GigyaTfa.framework in Frameworks */, + E76B94FD28508A5E00E63E3B /* GigyaTfa.framework in Frameworks */, E7A71CAD232F7F3000993A61 /* CoreTelephony.framework in Frameworks */, - E732A80828AA2C200035DDEA /* Gigya.framework in Frameworks */, + E76B94FF28508A7600E63E3B /* Gigya.framework in Frameworks */, E73373F322633BCC00ADEDBB /* LocalAuthentication.framework in Frameworks */, E75C69E2276A342400FFA610 /* CryptoKit.framework in Frameworks */, 08C1D6EB23BFCFF79B9AAC08 /* Pods_TestApp.framework in Frameworks */, @@ -707,7 +706,7 @@ D52F88E422795793007DE20F /* Plugins */ = { isa = PBXGroup; children = ( - E71095312897D1DA0062DCAD /* WebBridgeManager */, + E7EDECE628D739B400801807 /* WebBridgeManager */, D52F88E922795F39007DE20F /* PluginViewController.swift */, D58EF4422279948B003DB878 /* PluginViewWrapper.swift */, E77F719F230AD95600E8738A /* GigyaWebBridge.swift */, @@ -773,13 +772,23 @@ path = ProvidersLogin; sourceTree = ""; }; - E71095312897D1DA0062DCAD /* WebBridgeManager */ = { + E710951D286D89450062DCAD /* WebAuthn */ = { isa = PBXGroup; children = ( - E71095322897D2200062DCAD /* WebBridgeInterruptionManager.swift */, - E71095342897D22F0062DCAD /* WebBridgeFroceLoginResolver.swift */, + E7109522287473340062DCAD /* OauthService.swift */, + E710951E286D89630062DCAD /* WebAuthnService.swift */, + E7109527288D4D400062DCAD /* WebAuthnAttestationUtils.swift */, + E7109520286DC44C0062DCAD /* WebAuthnDeviceIntegration.swift */, ); - path = WebBridgeManager; + path = WebAuthn; + sourceTree = ""; + }; + E71095242884461A0062DCAD /* WebAuthn */ = { + isa = PBXGroup; + children = ( + E7109525288446540062DCAD /* WebAuthnModels.swift */, + ); + path = WebAuthn; sourceTree = ""; }; E713E0FF2248C84000181328 /* Config */ = { @@ -1137,8 +1146,9 @@ E7BE368C2224225900A108D9 /* Frameworks */ = { isa = PBXGroup; children = ( - E732A81E28AA2D4F0035DDEA /* App.xcframework */, - E732A81D28AA2D4F0035DDEA /* Flutter.xcframework */, + E710952B288EC1FF0062DCAD /* Flutter.xcframework */, + E732A82328BB55240035DDEA /* App.xcframework */, + E732A82428BB55240035DDEA /* Flutter.xcframework */, E741DCBF2770F79B00E46223 /* GigyaTfa.framework */, E741DCBC2770F72F00E46223 /* GigyaTfa.framework */, E75C69E0276A33E400FFA610 /* CryptoKit.framework */, @@ -1321,6 +1331,7 @@ children = ( E7E90CAC25875B7000576186 /* ReportingService.swift */, E7D412F322E8A38F0082A3E3 /* PersistenceService.swift */, + E710951D286D89450062DCAD /* WebAuthn */, E720D34A2382D2FC00446EA4 /* PushNotifications */, E719373422D32FA100CD149E /* Biometric */, E73373DE2263312F00ADEDBB /* Providers */, @@ -1353,6 +1364,7 @@ E7CBB03C22356086000B6C11 /* GigyaInstanceProtocol.swift */, E720D34F2382D49400446EA4 /* PushNotificationModes.swift */, E7CBB0592237DD2C000B6C11 /* GigyaSocialProviders.swift */, + E71095242884461A0062DCAD /* WebAuthn */, E719372222CA4F9F00CD149E /* Session */, E719372122CA4F5A00CD149E /* Interruption */, E71D3C9A22BF906300A413D7 /* Tfa */, @@ -1483,6 +1495,16 @@ path = IOContainer; sourceTree = ""; }; + E7EDECE628D739B400801807 /* WebBridgeManager */ = { + isa = PBXGroup; + children = ( + E7EDECE728D739B400801807 /* WebBridgeFroceLoginResolver.swift */, + E7EDECE828D739B400801807 /* WebBridgeInterruptionManager.swift */, + ); + name = WebBridgeManager; + path = GigyaSwift/Global/Plugins/WebBridgeManager; + sourceTree = SOURCE_ROOT; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -1505,7 +1527,6 @@ E741DCDA2771DDF400E46223 /* Sources */, E741DCDB2771DDF400E46223 /* Frameworks */, E741DCDC2771DDF400E46223 /* Resources */, - E741DCF12771DE1D00E46223 /* Embed Frameworks */, ); buildRules = ( ); @@ -1620,7 +1641,7 @@ E7BE36972224260400A108D9 /* Frameworks */, E77F7A2124069AC800FF90FB /* ShellScript */, EB7918E424CC7E8B9A7E4C27 /* [CP] Embed Pods Frameworks */, - E732A80C28AA2C200035DDEA /* Embed Frameworks */, + E76B94F6285089BD00E63E3B /* Embed Frameworks */, ); buildRules = ( ); @@ -1978,6 +1999,7 @@ E74E26692383E9B1002E361B /* GeneralUtils.swift in Sources */, E7E80DA22252041D00D3B663 /* ResolverProtocol.swift in Sources */, E73A930222F974120004F93E /* Notification.Name.swift in Sources */, + E710951F286D89630062DCAD /* WebAuthnService.swift in Sources */, D58EF441227993C6003DB878 /* Dictionary.swift in Sources */, E774A54B224283DF00BC67D1 /* ApiRequestModel.swift in Sources */, E7E80D9D22510EC600D3B663 /* IOCContainer.swift in Sources */, @@ -1989,9 +2011,10 @@ E7CBB041223561F4000B6C11 /* GigyaAccount.swift in Sources */, E7E80DA0225203D400D3B663 /* ServiceMaker.swift in Sources */, E7B4D69C238329F50090E57D /* PushNotificationsService.swift in Sources */, + E7109521286DC44C0062DCAD /* WebAuthnDeviceIntegration.swift in Sources */, E7E80D8A224BC17000D3B663 /* FatalErrorUtils.swift in Sources */, E713E0FC2247C6E700181328 /* SignatureUtils.swift in Sources */, - E71095332897D2200062DCAD /* WebBridgeInterruptionManager.swift in Sources */, + E7EDECE928D739B400801807 /* WebBridgeFroceLoginResolver.swift in Sources */, E7CBB03D22356086000B6C11 /* GigyaInstanceProtocol.swift in Sources */, E76AF6EB24C88BA8008B9114 /* ApiService.swift in Sources */, E720D3502382D49400446EA4 /* PushNotificationModes.swift in Sources */, @@ -2001,7 +2024,6 @@ E756A2AD22771B6F007FA801 /* Url.swift in Sources */, E719374322D5E6C600CD149E /* PlistConfigFactory.swift in Sources */, E7CBAFC5222E7314000B6C11 /* GigyaResult.swift in Sources */, - E71095352897D22F0062DCAD /* WebBridgeFroceLoginResolver.swift in Sources */, E74C9275272A8CD300934C73 /* PKCEHelper.swift in Sources */, E7CBAFCE222EA268000B6C11 /* GigyaSession.swift in Sources */, E7CBAFB8222D17C9000B6C11 /* NetworkProvider.swift in Sources */, @@ -2020,6 +2042,7 @@ E71D3C9E22BF906300A413D7 /* TFAModels.swift in Sources */, E71D3C8C22BF7C2E00A413D7 /* Resolver.swift in Sources */, E741B4CD271420AB00BF693F /* SsoLoginWrapper.swift in Sources */, + E7109523287473340062DCAD /* OauthService.swift in Sources */, E7DC2C3B228019AC00A13426 /* SocialLoginProvider.swift in Sources */, E7CBAFD42230232D000B6C11 /* AccountService.swift in Sources */, D5C9DD3D227B0C9200E47095 /* GigyaPluginEvent.swift in Sources */, @@ -2040,6 +2063,7 @@ E7B5408A23952E2200C0C8D8 /* UserNotificationCenterProtocol.swift in Sources */, E7257C5F23B4BBC300692528 /* SessionVerificationService.swift in Sources */, E71D3CA222BFA81F00A413D7 /* InterruptionResolverFactory.swift in Sources */, + E7109528288D4D400062DCAD /* WebAuthnAttestationUtils.swift in Sources */, E733743D22671FDD00ADEDBB /* BusinessApiDelegate.swift in Sources */, E7BE368B2224225100A108D9 /* Gigya.swift in Sources */, E7E80D7A224B6A3900D3B663 /* GigyaLogger.swift in Sources */, @@ -2048,6 +2072,7 @@ E7C859E7228C52A0005C1FBE /* ConflictingAccount.swift in Sources */, E7D412F022E87B140082A3E3 /* URLSession.swift in Sources */, E7221D7922B8FC8700C296A9 /* PendingRegistrationResolver.swift in Sources */, + E7EDECEA28D739B400801807 /* WebBridgeInterruptionManager.swift in Sources */, E7CBAFA4222BC881000B6C11 /* InternalConfig.swift in Sources */, E7CAE1C9238C14DE0073440C /* UserNotificationCenterHelper.swift in Sources */, E76421DF25E2BFEB00CDED4C /* ApiRequestModel+GlobalConfig.swift in Sources */, @@ -2056,6 +2081,7 @@ E7D3E22822CA01760033BAF1 /* SessionInfoModel.swift in Sources */, E713E1022248E41400181328 /* GigyaRequestSignature.swift in Sources */, E7D9C5B02368398500714807 /* NetworkRetryDispacher.swift in Sources */, + E7109526288446540062DCAD /* WebAuthnModels.swift in Sources */, D58EF4452279CB38003DB878 /* String.swift in Sources */, E7CBAFBA222D1840000B6C11 /* NetworkError.swift in Sources */, E7CAE1D2238D61D40073440C /* BasePushManagerProtocol.swift in Sources */, @@ -2558,7 +2584,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.3.2; + MARKETING_VERSION = 1.4.0; MODULEMAP_FILE = ""; MODULEMAP_PRIVATE_FILE = ""; ONLY_ACTIVE_ARCH = NO; @@ -2619,7 +2645,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.3.2; + MARKETING_VERSION = 1.4.0; MODULEMAP_FILE = ""; MODULEMAP_PRIVATE_FILE = ""; ONLY_ACTIVE_ARCH = NO; @@ -2656,6 +2682,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 52P2295V75; ENABLE_BITCODE = NO; + EXCLUDED_ARCHS = ""; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/GigyaSwift", @@ -2706,6 +2733,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 52P2295V75; ENABLE_BITCODE = NO; + EXCLUDED_ARCHS = ""; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/GigyaSwift", @@ -2714,7 +2742,7 @@ "$(PROJECT_DIR)/TestApp", "$(PROJECT_DIR)/TestApp/facebook", "$(PROJECT_DIR)/GigyaTfa", - "$(PROJECT_DIR)/GigyaNss/Flutter/Debug", + "$(PROJECT_DIR)/GigyaNss/Flutter/Release", ); INFOPLIST_FILE = TestApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; diff --git a/GigyaSwift/Gigya/GigyaCore.swift b/GigyaSwift/Gigya/GigyaCore.swift index 6a6c019b..fa3211d9 100644 --- a/GigyaSwift/Gigya/GigyaCore.swift +++ b/GigyaSwift/Gigya/GigyaCore.swift @@ -51,9 +51,16 @@ public final class GigyaCore: GigyaInstanceProtocol { */ public let biometric: BiometricServiceProtocol + /** + WebAuthn (FIDO2). + + - returns: `WebAuthnService` service + */ + public let webAuthn: WebAuthnService + // MARK: - Initialize - internal init(config: GigyaConfig, persistenceService: PersistenceService, businessApiService: BusinessApiServiceProtocol, sessionService: SessionServiceProtocol, interruptionResolver: InterruptionResolverFactoryProtocol, biometric: BiometricServiceProtocol, plistFactory: PlistConfigFactory, sessionVerificationService: SessionVerificationServiceProtocol, container: IOCContainer) { + internal init(config: GigyaConfig, persistenceService: PersistenceService, businessApiService: BusinessApiServiceProtocol, sessionService: SessionServiceProtocol, interruptionResolver: InterruptionResolverFactoryProtocol, biometric: BiometricServiceProtocol, plistFactory: PlistConfigFactory, sessionVerificationService: SessionVerificationServiceProtocol, webAuthn: WebAuthnService, container: IOCContainer) { self.config = config self.persistenceService = persistenceService self.businessApiService = businessApiService @@ -61,6 +68,7 @@ public final class GigyaCore: GigyaInstanceProtocol { self.interruptionResolver = interruptionResolver self.biometric = biometric self.container = container + self.webAuthn = webAuthn self.sessionVerificationService = sessionVerificationService // load plist and make init diff --git a/GigyaSwift/Global/Biometric/BiometricService.swift b/GigyaSwift/Global/Biometric/BiometricService.swift index fae4fb66..a2604d83 100644 --- a/GigyaSwift/Global/Biometric/BiometricService.swift +++ b/GigyaSwift/Global/Biometric/BiometricService.swift @@ -29,7 +29,7 @@ final class BiometricService: BiometricServiceProtocol, BiometricServiceInternal */ var isLocked: Bool { - return persistenceService.biometricLocked ?? false + return (isOptIn && !sessionService.isValidSession()) } init(config: GigyaConfig, persistenceService: PersistenceService, sessionService: SessionServiceProtocol) { diff --git a/GigyaSwift/Global/PersistenceService.swift b/GigyaSwift/Global/PersistenceService.swift index b3df4a92..3dbf2cb8 100644 --- a/GigyaSwift/Global/PersistenceService.swift +++ b/GigyaSwift/Global/PersistenceService.swift @@ -55,6 +55,20 @@ public final class PersistenceService { return UserDefaults.standard.string(forKey: InternalConfig.Storage.pushKey) } } + + public var webAuthnlist: [GigyaWebAuthnCredential] { + get { + if let data = UserDefaults.standard.object(forKey: InternalConfig.Storage.webAuthn) as? Data { + do { + return try PropertyListDecoder().decode([GigyaWebAuthnCredential].self, from: data) + } catch { + return [] + } + } + + return [] + } + } // save gmid, ucid to userDefaults internal func save(ids: InitSdkIdsModel) { @@ -77,4 +91,14 @@ public final class PersistenceService { internal func setPushKey(to string: String) { UserDefaults.standard.setValue(string, forKey: InternalConfig.Storage.pushKey) } + + func addWebAuthnKey(model: GigyaWebAuthnCredential) { + var list = webAuthnlist + list.append(model) + UserDefaults.standard.set(try? PropertyListEncoder().encode(list), forKey: InternalConfig.Storage.webAuthn) + } + + internal func removeAllWebAuthnKeys() { + UserDefaults.standard.removeObject(forKey: InternalConfig.Storage.webAuthn) + } } diff --git a/GigyaSwift/Global/Plugins/GigyaWebBridge.swift b/GigyaSwift/Global/Plugins/GigyaWebBridge.swift index 068f20af..cac12856 100644 --- a/GigyaSwift/Global/Plugins/GigyaWebBridge.swift +++ b/GigyaSwift/Global/Plugins/GigyaWebBridge.swift @@ -284,7 +284,7 @@ public class GigyaWebBridge: NSObject, WKScriptMessageH guard let self = self else { return } switch result { case .success(let data): - GigyaLogger.log(with: self, message: "sendRequest: success") + GigyaLogger.log(with: self, message: "sendRequest: success = \(apiMethod)") // Mapping AnyCodable values. Otherwise we will crash in the JSON dictionary conversion. let mapped: [String: Any] = data.mapValues { value in return value.value } diff --git a/GigyaSwift/Global/Resolvers/LinkAccountsResolver.swift b/GigyaSwift/Global/Resolvers/LinkAccountsResolver.swift index 1b64a47d..b66338aa 100644 --- a/GigyaSwift/Global/Resolvers/LinkAccountsResolver.swift +++ b/GigyaSwift/Global/Resolvers/LinkAccountsResolver.swift @@ -73,6 +73,5 @@ final public class LinkAccountsResolver: BaseResolver { businessDelegate?.callSociallogin(provider: provider, viewController: viewController, params: params, dataType: T.self, completion: self.completion) GigyaLogger.log(with: self, message: "[linkToSocial] - data: \(params), provider: \(provider.rawValue)") - } } diff --git a/GigyaSwift/Global/Utils/Decode+EncodeUtils.swift b/GigyaSwift/Global/Utils/Decode+EncodeUtils.swift index f0d2f309..673fe70f 100644 --- a/GigyaSwift/Global/Utils/Decode+EncodeUtils.swift +++ b/GigyaSwift/Global/Utils/Decode+EncodeUtils.swift @@ -8,9 +8,9 @@ import Foundation -final class DecodeEncodeUtils { +public final class DecodeEncodeUtils { - static func decode(fromType: T.Type, data: Data) throws -> T where T: Codable { + public static func decode(fromType: T.Type, data: Data) throws -> T where T: Codable { do { let decodedObject = try JSONDecoder().decode(fromType, from: data) return decodedObject diff --git a/GigyaSwift/Global/Utils/GeneralUtils.swift b/GigyaSwift/Global/Utils/GeneralUtils.swift index c8a4d99b..13dc68fd 100644 --- a/GigyaSwift/Global/Utils/GeneralUtils.swift +++ b/GigyaSwift/Global/Utils/GeneralUtils.swift @@ -69,3 +69,21 @@ extension Dictionary { return self.reduce("") { "\($0)\($1.0)=\("\($1.1)".addingPercentEncoding(withAllowedCharacters: urlAllowed) ?? "")&" } } } + +extension String { + func decodeBase64Url() -> Data? { + var base64 = self + .replacingOccurrences(of: "-", with: "+") + .replacingOccurrences(of: "_", with: "/") + if base64.count % 4 != 0 { + base64.append(String(repeating: "=", count: 4 - base64.count % 4)) + } + return Data(base64Encoded: base64) + } +} + +extension Data { + func toBase64Url() -> String { + return self.base64EncodedString().replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "") + } +} diff --git a/GigyaSwift/Global/Utils/GigyaIOCContainer.swift b/GigyaSwift/Global/Utils/GigyaIOCContainer.swift index c438899a..0493cf20 100644 --- a/GigyaSwift/Global/Utils/GigyaIOCContainer.swift +++ b/GigyaSwift/Global/Utils/GigyaIOCContainer.swift @@ -199,6 +199,7 @@ final class GigyaIOCContainer: GigyaContainerProtocol { let persistenceService = resolver.resolve(PersistenceService.self) let container = resolver.resolve(IOCContainer.self) let sessionVerificationService = resolver.resolve(SessionVerificationServiceProtocol.self) + let webAuthn = resolver.resolve(WebAuthnService.self) return GigyaCore(config: config!, persistenceService: persistenceService!, @@ -208,6 +209,7 @@ final class GigyaIOCContainer: GigyaContainerProtocol { biometric: biometricService!, plistFactory: plistFactory!, sessionVerificationService: sessionVerificationService!, + webAuthn: webAuthn!, container: container!) } @@ -220,5 +222,29 @@ final class GigyaIOCContainer: GigyaContainerProtocol { return businessService as! BusinessApiDelegate } + + container.register(service: WebAuthnService.self) { resolver in + let busnessApi = resolver.resolve(BusinessApiServiceProtocol.self) + let webAuthnDeviceIntegration = resolver.resolve(WebAuthnDeviceIntegration.self) + let oauthService = resolver.resolve(OauthService.self) + let attestationUtils = resolver.resolve(WebAuthnAttestationUtils.self) + let persistenceService = resolver.resolve(PersistenceService.self) + + return WebAuthnService(businessApiService: busnessApi!, webAuthnDeviceIntegration: webAuthnDeviceIntegration!, oauthService: oauthService!, attestationUtils: attestationUtils!, persistenceService: persistenceService!) + } + + container.register(service: WebAuthnAttestationUtils.self) { resolver in + return WebAuthnAttestationUtils() + } + + container.register(service: WebAuthnDeviceIntegration.self) { resolver in + return WebAuthnDeviceIntegration() + } + + container.register(service: OauthService.self) { resolver in + let busnessApi = resolver.resolve(BusinessApiServiceProtocol.self) + + return OauthService(businessApiService: busnessApi!) + } } } diff --git a/GigyaSwift/Global/WebAuthn/OauthService.swift b/GigyaSwift/Global/WebAuthn/OauthService.swift new file mode 100644 index 00000000..639ecc72 --- /dev/null +++ b/GigyaSwift/Global/WebAuthn/OauthService.swift @@ -0,0 +1,68 @@ +// +// OauthService.swift +// Gigya +// +// Created by Sagi Shmuel on 05/07/2022. +// Copyright © 2022 Gigya. All rights reserved. +// + +import Foundation + +class OauthService { + let businessApiService: BusinessApiServiceProtocol + + init(businessApiService: BusinessApiServiceProtocol) { + self.businessApiService = businessApiService + } + + func connect(token: String, completion: @escaping (GigyaApiResult) -> Void) { + var model = ApiRequestModel(method: GigyaDefinitions.Oauth.connect) + model.headers = ["Authorization": "Bearer \(token)"] + businessApiService.apiService.send(model: model, responseType: GigyaDictionary.self) { result in + completion(result) + } + } + + @available(iOS 13.0.0, *) + func authorize(token: String) async -> GigyaLoginResult { + return await withCheckedContinuation { [weak self] continuation in + guard let self = self else { return } + + var model = ApiRequestModel(method: GigyaDefinitions.Oauth.authorize, params: ["response_type": "code"]) + model.headers = ["Authorization": "Bearer \(token)"] + + self.businessApiService.apiService.send(model: model, responseType: GigyaDictionary.self) { result in + switch result { + case .success(data: let data): + self.token(continuation: continuation, code: data["code"]?.value as! String) + case .failure(let error): + continuation.resume(returning: GigyaLoginResult.failure(.init(error: error))) + } + } + } + + } + + @available(iOS 13.0.0, *) + private func token(continuation: CheckedContinuation, Never>, code: String) { + self.businessApiService.send(dataType: T.self, api: GigyaDefinitions.Oauth.token, params: ["grant_type": "authorization_code", "code": code]) { [weak self] res in + guard let self = self else { return } + + switch res { + case .success(data: _): + + self.businessApiService.getAccount(params: [:], clearAccount: true, dataType: T.self) { result in + switch result { + case .success(data: let data): + continuation.resume(returning: .success(data: data)) + case .failure(let error): + continuation.resume(returning: .failure(LoginApiError.init(error: error))) + } + } + case .failure(let error): + continuation.resume(returning: .failure(LoginApiError.init(error: error))) + } + } + } +} + diff --git a/GigyaSwift/Global/WebAuthn/WebAuthnAttestationUtils.swift b/GigyaSwift/Global/WebAuthn/WebAuthnAttestationUtils.swift new file mode 100644 index 00000000..8b3fee03 --- /dev/null +++ b/GigyaSwift/Global/WebAuthn/WebAuthnAttestationUtils.swift @@ -0,0 +1,84 @@ +// +// WebAuthnAttestationUtils.swift +// Gigya +// +// Created by Sagi Shmuel on 24/07/2022. +// Copyright © 2022 Gigya. All rights reserved. +// + +import AuthenticationServices + +struct WebAuthnAttestationUtils { + @available(iOS 16.0, *) + func makeRegisterData(object: ASAuthorizationPlatformPublicKeyCredentialRegistration) -> [String: Any] { + let response: [String: String] = [ + "attestationObject": object.rawAttestationObject!.toBase64Url(), + "clientDataJSON": object.rawClientDataJSON.toBase64Url() + ] + + let attestation: [String: Any] = [ + "id": object.credentialID.toBase64Url(), + "rawId": object.credentialID.toBase64Url(), + "type": "public-key", + "response": response + ] + + return attestation + } + + @available(iOS 16.0, *) + func makeRegisterData(object: ASAuthorizationSecurityKeyPublicKeyCredentialRegistration) -> [String: Any] { + let response: [String: String] = [ + "attestationObject": object.rawAttestationObject!.toBase64Url(), + "clientDataJSON": object.rawClientDataJSON.toBase64Url() + ] + + let attestation: [String: Any] = [ + "id": object.credentialID.toBase64Url(), + "rawId": object.credentialID.toBase64Url(), + "type": "public-key", + "response": response + ] + + return attestation + } + + + @available(iOS 16.0, *) + func makeLoginData(object: ASAuthorizationPlatformPublicKeyCredentialAssertion) -> [String: Any] { + let response: [String: Any?] = [ + "authenticatorData": object.rawAuthenticatorData.toBase64Url(), + "clientDataJSON": object.rawClientDataJSON.toBase64Url(), + "signature": object.signature.toBase64Url(), + "userHandle": object.userID.toBase64Url() + ] + + let attestation: [String: Any] = [ + "id": object.credentialID.toBase64Url(), + "rawId": object.credentialID.toBase64Url(), + "type": "public-key", + "response": response + ] + + return attestation + } + + @available(iOS 16.0, *) + func makeSecurityLoginData(object: ASAuthorizationSecurityKeyPublicKeyCredentialAssertion) -> [String: Any] { + let response: [String: Any] = [ + "authenticatorData": object.rawAuthenticatorData.toBase64Url(), + "clientDataJSON": object.rawClientDataJSON.toBase64Url(), + "signature": object.signature.toBase64Url(), + "userHandle": NSNull() + ] + + let attestation: [String: Any] = [ + "id": object.credentialID.toBase64Url(), + "rawId": object.credentialID.toBase64Url(), + "type": "public-key", + "response": response + ] + + return attestation + } +} diff --git a/GigyaSwift/Global/WebAuthn/WebAuthnDeviceIntegration.swift b/GigyaSwift/Global/WebAuthn/WebAuthnDeviceIntegration.swift new file mode 100644 index 00000000..433cf3ca --- /dev/null +++ b/GigyaSwift/Global/WebAuthn/WebAuthnDeviceIntegration.swift @@ -0,0 +1,170 @@ +// +// WebAuthnDeviceIntegration.swift +// Gigya +// +// Created by Sagi Shmuel on 30/06/2022. +// Copyright © 2022 Gigya. All rights reserved. +// + +import Foundation +import AuthenticationServices + +class WebAuthnDeviceIntegration: NSObject { + @available(iOS 16.0, *) + typealias WebAuthnIntegrationHandler = (ResponseType) -> Void + + @available(iOS 16.0, *) + enum ResponseType { + case register(ASAuthorizationPlatformPublicKeyCredentialRegistration) + case securityRegister(ASAuthorizationSecurityKeyPublicKeyCredentialRegistration) + case login(ASAuthorizationPlatformPublicKeyCredentialAssertion) + case securityLogin(ASAuthorizationSecurityKeyPublicKeyCredentialAssertion) + case canceled + case error + } + + var vc: UIViewController? + + var handler: Any = { } + + @available(iOS 16.0, *) + func register(viewController: UIViewController, options: WebAuthnInitRegisterResponseModel, handler: @escaping WebAuthnIntegrationHandler) { + self.vc = viewController + self.handler = handler + + let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: options.options.rp.id) + + let securityKeyProvider = ASAuthorizationSecurityKeyPublicKeyCredentialProvider(relyingPartyIdentifier: options.options.rp.id) + + let challenge = options.options.challenge.decodeBase64Url()! + + let userID = options.options.user.id.decodeBase64Url()! + + let securityRequest = securityKeyProvider.createCredentialRegistrationRequest(challenge: challenge, displayName: options.options.user.displayName, name: options.options.user.name, userID: userID) + + securityRequest.credentialParameters = [ ASAuthorizationPublicKeyCredentialParameters(algorithm: ASCOSEAlgorithmIdentifier.ES256) ] + + let assertionRequest = publicKeyCredentialProvider.createCredentialRegistrationRequest(challenge: challenge, name: options.options.user.name, userID: userID) + + if let userVerification = options.options.authenticatorSelection.userVerification { + securityRequest.userVerificationPreference = ASAuthorizationPublicKeyCredentialUserVerificationPreference.init(rawValue: userVerification) + assertionRequest.userVerificationPreference = ASAuthorizationPublicKeyCredentialUserVerificationPreference.init(rawValue: userVerification) + + } + // platform = device, cross-platform = external, unspecified = both (device & external) + var authorizationRequests: [ASAuthorizationRequest] = [] + let authenticatorAttachment = options.options.authenticatorSelection.authenticatorAttachment ?? .unspecified + + switch authenticatorAttachment { + case .platform: + authorizationRequests.append(assertionRequest) + case .crossPlatform: + authorizationRequests.append(securityRequest) + case .unspecified: + authorizationRequests.append(assertionRequest) + authorizationRequests.append(securityRequest) + } + + let authController = ASAuthorizationController(authorizationRequests: authorizationRequests ) + authController.delegate = self + authController.presentationContextProvider = self + authController.performRequests() + } + + @available(iOS 16.0, *) + func login(viewController: UIViewController, options: WebAuthnGetOptionsResponseModel, allowedKeys: [GigyaWebAuthnCredential], handler: @escaping WebAuthnIntegrationHandler) { + self.vc = viewController + self.handler = handler + + let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: options.options.rpId) + + let securityKeyProvider = ASAuthorizationSecurityKeyPublicKeyCredentialProvider(relyingPartyIdentifier: options.options.rpId) + + let challenge = options.options.challenge.decodeBase64Url()! + + let securityRequest = securityKeyProvider.createCredentialAssertionRequest(challenge: challenge) + + let securityKeys = allowedKeys.filter { $0.type == .crossPlatform } + .map { + ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor(credentialID: Data(base64Encoded: $0.key) ?? Data(), transports: ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor.Transport.allSupported) + } + + + securityRequest.allowedCredentials = securityKeys + + let assertionRequest = publicKeyCredentialProvider.createCredentialAssertionRequest(challenge: challenge) + + let publicKeys = allowedKeys.filter { $0.type == .platform } + .map { + ASAuthorizationPlatformPublicKeyCredentialDescriptor(credentialID: Data(base64Encoded: $0.key) ?? Data()) + } + + assertionRequest.allowedCredentials = publicKeys + + if let userVerification = options.options.userVerification { + securityRequest.userVerificationPreference = ASAuthorizationPublicKeyCredentialUserVerificationPreference.init(rawValue: userVerification) + assertionRequest.userVerificationPreference = ASAuthorizationPublicKeyCredentialUserVerificationPreference.init(rawValue: userVerification) + } + + let authorizationRequests: [ASAuthorizationRequest] = [assertionRequest, securityRequest] + + let authController = ASAuthorizationController(authorizationRequests: authorizationRequests ) + authController.delegate = self + authController.presentationContextProvider = self + authController.performRequests() + } + + + deinit { + GigyaLogger.log(with: self, message: "deinit") + } +} + +@available(iOS 16.0, *) +extension WebAuthnDeviceIntegration: ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate { + func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { + return vc!.view.window! + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { + guard let authorizationError = ASAuthorizationError.Code(rawValue: (error as NSError).code) else { + GigyaLogger.log(with: self, message: "Unexpected authorization error: \(error.localizedDescription)") + return + } + + if authorizationError == .canceled { + // Either no credentials were found and the request silently ended, or the user canceled the request. + // Consider asking the user to create an account. + GigyaLogger.log(with: self, message: "Request canceled.") + (handler as! WebAuthnIntegrationHandler)(.canceled) + } else { + // Other ASAuthorization error. + // The userInfo dictionary should contain useful information. + GigyaLogger.log(with: self, message: "Error: \((error as NSError).userInfo)") + (handler as! WebAuthnIntegrationHandler)(.error) + } + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { + switch authorization.credential { + case let secCredentialRegistrations as ASAuthorizationSecurityKeyPublicKeyCredentialRegistration: + GigyaLogger.log(with: self, message: "A new cross-platform credential was registered: \(secCredentialRegistrations)") + (handler as! WebAuthnIntegrationHandler)(.securityRegister(secCredentialRegistrations)) + case let credentialRegistration as ASAuthorizationPlatformPublicKeyCredentialRegistration: + GigyaLogger.log(with: self, message: "A new platform credential was registered: \(credentialRegistration)") + + (handler as! WebAuthnIntegrationHandler)(.register(credentialRegistration)) + case let credentialAssertion as ASAuthorizationPlatformPublicKeyCredentialAssertion: + GigyaLogger.log(with: self, message: "A platform credential was used to authenticate: \(credentialAssertion)") + + (handler as! WebAuthnIntegrationHandler)(.login(credentialAssertion)) + case let credentialAssertion as ASAuthorizationSecurityKeyPublicKeyCredentialAssertion: + GigyaLogger.log(with: self, message: "A cross-platform credential was used to authenticate: \(credentialAssertion)") + + (handler as! WebAuthnIntegrationHandler)(.securityLogin(credentialAssertion)) + default: + (handler as! WebAuthnIntegrationHandler)(.error) + GigyaLogger.log(with: self, message: "Received unknown authorization type") + } + } +} diff --git a/GigyaSwift/Global/WebAuthn/WebAuthnService.swift b/GigyaSwift/Global/WebAuthn/WebAuthnService.swift new file mode 100644 index 00000000..18168e0a --- /dev/null +++ b/GigyaSwift/Global/WebAuthn/WebAuthnService.swift @@ -0,0 +1,275 @@ +// +// WebAuthnService.swift +// Gigya +// +// Created by Sagi Shmuel on 30/06/2022. +// Copyright © 2022 Gigya. All rights reserved. +// + +import Foundation +import UIKit + +public class WebAuthnService { + let businessApiService: BusinessApiServiceProtocol + let webAuthnDeviceIntegration: WebAuthnDeviceIntegration + let oauthService: OauthService + let attestationUtils: WebAuthnAttestationUtils + let persistenceService: PersistenceService + + private var isActiveContinuation: Bool = false + + init(businessApiService: BusinessApiServiceProtocol, webAuthnDeviceIntegration: WebAuthnDeviceIntegration, oauthService: OauthService, attestationUtils: WebAuthnAttestationUtils, persistenceService: PersistenceService) { + self.businessApiService = businessApiService + self.webAuthnDeviceIntegration = webAuthnDeviceIntegration + self.oauthService = oauthService + self.attestationUtils = attestationUtils + self.persistenceService = persistenceService + } + + // MARK: Registration flow + + public var isSupported: Bool { + get { + if #available(iOS 16.0.0, *) { + return true + } else { + return false + } + } + } + + @available(iOS 16.0.0, *) + public func register(viewController: UIViewController) async -> GigyaApiResult { + if isActiveContinuation { + return .failure(.providerError(data: "cancelled")) + } + + isActiveContinuation.toggle() + + let initResult = await initRegistration() + switch initResult { + case .success(let options): + return await withCheckedContinuation({ continuation in + webAuthnDeviceIntegration.register(viewController: viewController, options: options) { [weak self] result in + guard let self = self else { + return + } + + switch result { + case .securityRegister(let token): + let attestation: [String: Any] = self.attestationUtils.makeRegisterData(object: token) + + Task { + let result = await self.registerCredentials(params: ["attestation": attestation, "token": options.token]) + switch result { + case .success(data: let data): + self.oauthService.connect(token: data["idToken"]!.value as! String) { result in + Task { + await self.addKey(token: token.credentialID.base64EncodedString(), user: options.options.user, type: .crossPlatform) + + continuation.resume(returning: result) + self.isActiveContinuation.toggle() + } + } // TODO: idToken - save for delete device? + case .failure(let error): + continuation.resume(returning: GigyaApiResult.failure(error)) + self.isActiveContinuation.toggle() + } + } + case .register(let token): + + let attestation: [String: Any] = self.attestationUtils.makeRegisterData(object: token) + + Task { + let result = await self.registerCredentials(params: ["attestation": attestation, "token": options.token]) + switch result { + case .success(data: let data): + self.oauthService.connect(token: data["idToken"]!.value as! String) { result in + Task { + await self.addKey(token: token.credentialID.base64EncodedString(), user: options.options.user, type: .platform) + + continuation.resume(returning: result) + self.isActiveContinuation.toggle() + } + } // TODO: idToken - save for delete device? + case .failure(let error): + continuation.resume(returning: GigyaApiResult.failure(error)) + self.isActiveContinuation.toggle() + } + } + case .canceled: + continuation + .resume(returning: .failure(NetworkError.providerError(data: "cancelled"))) + self.isActiveContinuation.toggle() + default: + let error = GigyaResponseModel(statusCode: .unknown, errorCode: 400301, callId: "", errorMessage: "Operation failed", sessionInfo: nil) + continuation + .resume(returning: .failure(NetworkError.gigyaError(data: error))) + self.isActiveContinuation.toggle() + } + } + }) + case .failure(let error): + return .failure(error) + } + } + + @available(iOS 16.0.0, *) + private func initRegistration() async -> GigyaApiResult { + return await withCheckedContinuation() { continuation in + businessApiService.send(dataType: WebAuthnInitRegisterResponseModel.self, api: GigyaDefinitions.WenAuthn.initRegister, params: [:]) { result in + continuation.resume(returning: result) + } + } + } + + @available(iOS 16.0.0, *) + private func registerCredentials(params: [String: Any]) async -> GigyaApiResult { + return await withCheckedContinuation({ + continuation in + businessApiService.send(dataType: GigyaDictionary.self, api: GigyaDefinitions.WenAuthn.registerCredentials, params: params) { result in + continuation.resume(returning: result) + } + }) + } + + // MARK: Login flow + + @available(iOS 16.0.0, *) + public func login(viewController: UIViewController) async -> GigyaLoginResult { + if isActiveContinuation { + return .failure(.init(error: .providerError(data: "cancelled"))) + } + isActiveContinuation.toggle() + + let assertionOptions = await getAssertionOptions() + + switch assertionOptions { + case .success(let options): + return await withCheckedContinuation() { continuation in + + let allowedKeys = persistenceService.webAuthnlist + + webAuthnDeviceIntegration.login(viewController: viewController, options: options, allowedKeys: allowedKeys) { [weak self] result in + guard let self = self else { + return + } + switch result { + case .login(let token): + let attestation: [String: Any] = self.attestationUtils.makeLoginData(object: token) + + Task { + let result = await self.verifyAssertion(params: ["authenticatorAssertion": attestation, "token": options.token]) + switch result { + case .success(data: let data): + let user: GigyaLoginResult = await self.oauthService.authorize(token: data["idToken"]!.value as! String) // idToken for login + continuation.resume(returning: user) + self.isActiveContinuation.toggle() + case .failure(let error): + continuation.resume(returning: .failure(LoginApiError(error: error))) + self.isActiveContinuation.toggle() + } + } + case .securityLogin(let token): + let attestation: [String: Any] = self.attestationUtils.makeSecurityLoginData(object: token) + + Task { + let result = await self.verifyAssertion(params: ["authenticatorAssertion": attestation, "token": options.token]) + switch result { + case .success(data: let data): + let user: GigyaLoginResult = await self.oauthService.authorize(token: data["idToken"]!.value as! String) // idToken for login + continuation.resume(returning: user) + self.isActiveContinuation.toggle() + case .failure(let error): + continuation.resume(returning: .failure(LoginApiError(error: error))) + self.isActiveContinuation.toggle() + } + } + case .canceled: + continuation + .resume(returning: .failure(LoginApiError(error: NetworkError.providerError(data: "cancelled")))) + self.isActiveContinuation.toggle() + default: + let error = GigyaResponseModel(statusCode: .unknown, errorCode: 400301, callId: "", errorMessage: "Operation failed", sessionInfo: nil) + continuation + .resume(returning: .failure(LoginApiError(error: NetworkError.gigyaError(data: error)))) + self.isActiveContinuation.toggle() + } + } + } + case .failure(let error): + return .failure(LoginApiError(error: error)) + } + } + + @available(iOS 16.0.0, *) + private func getAssertionOptions() async -> GigyaApiResult { + return await withCheckedContinuation({ + continuation in + businessApiService.send(dataType: WebAuthnGetOptionsResponseModel.self, api: GigyaDefinitions.WenAuthn.getAssertionOptions, params: [:]) { result in + continuation.resume(returning: result) + } + }) + } + + @available(iOS 16.0.0, *) + private func verifyAssertion(params: [String: Any]) async -> GigyaApiResult { + return await withCheckedContinuation({ + continuation in + businessApiService.send(dataType: GigyaDictionary.self, api: GigyaDefinitions.WenAuthn.verifyAssertion, params: params) { result in + continuation.resume(returning: result) + } + }) + } + + @available(iOS 16.0.0, *) + private func revoke(key: String) async -> GigyaApiResult { + return await withCheckedContinuation({ + continuation in + businessApiService.send(dataType: GigyaDictionary.self, api: GigyaDefinitions.WenAuthn.removeCredential, params: ["credentialId": key]) { result in + continuation.resume(returning: result) + } + }) + } + + @available(iOS 16.0.0, *) + @discardableResult + public func revoke() async -> GigyaApiResult { + if let lastKey = self.persistenceService.webAuthnlist.last { + let result = await self.revoke(key: lastKey.key) + switch result { + case .success(data: _): + self.persistenceService.removeAllWebAuthnKeys() + case .failure(_): + break + } + return result + } else { + let error = GigyaResponseModel(statusCode: .unknown, errorCode: 400301, callId: "", errorMessage: "Operation failed", sessionInfo: nil) + + return GigyaApiResult.failure(.gigyaError(data: error)) + } + } + + @available(iOS 16.0, *) + @discardableResult + private func addKey(token: String, user: WebAuthnUserModel, type: GigyaWebAuthnCredentialType) async -> Bool { + return await withCheckedContinuation({ continuation in + Task { + if let lastKey = self.persistenceService.webAuthnlist.last { + let result = await self.revoke(key: lastKey.key) + switch result { + case .success(data: _): + self.persistenceService.removeAllWebAuthnKeys() + case .failure(_): + continuation.resume(returning: false) + } + } + + let credential = GigyaWebAuthnCredential(name: user.name, displayName: user.displayName, type: type, key: token) + self.persistenceService.addWebAuthnKey(model: credential) + continuation.resume(returning: true) + } + }) + } +} diff --git a/GigyaSwift/Models/Config/GigyaDefinitions.swift b/GigyaSwift/Models/Config/GigyaDefinitions.swift index 3b013844..20d8bd91 100644 --- a/GigyaSwift/Models/Config/GigyaDefinitions.swift +++ b/GigyaSwift/Models/Config/GigyaDefinitions.swift @@ -72,6 +72,20 @@ public struct GigyaDefinitions { public static let invalidJwt = 400006 public static let requestExpired = 403002 } + + public struct WenAuthn { + static let initRegister = "accounts.auth.fido.initRegisterCredentials" + static let getAssertionOptions = "accounts.auth.fido.getAssertionOptions" + static let registerCredentials = "accounts.auth.fido.registerCredentials" + static let verifyAssertion = "accounts.auth.fido.verifyAssertion" + static let removeCredential = "accounts.auth.fido.removeCredential" + } + + public struct Oauth { + static let connect = "oauth.connect" + static let authorize = "oauth.authorize" + static let token = "oauth.token" + } public static var charactersAllowed = "!*'|();/:-_.@&=^+$,?%#[]{}\" " public static var charactersAllowedInSig = "!*'|();/:@&=^+$,?%#\\[]{}\" " diff --git a/GigyaSwift/Models/Config/InternalConfig.swift b/GigyaSwift/Models/Config/InternalConfig.swift index 9be121de..d1e3142c 100644 --- a/GigyaSwift/Models/Config/InternalConfig.swift +++ b/GigyaSwift/Models/Config/InternalConfig.swift @@ -30,7 +30,7 @@ struct InternalConfig { internal static let hasRunBefore = "com.gigya.GigyaSDK:hasRunBefore" internal static let expirationSession = "com.gigya.GigyaSDK:expirationSession" internal static let pushKey = "com.gigya.GigyaTfa:pushKey" - + internal static let webAuthn = "com.gigya.GigyaSDK:webauthn" } struct Network { diff --git a/GigyaSwift/Models/WebAuthn/WebAuthnModels.swift b/GigyaSwift/Models/WebAuthn/WebAuthnModels.swift new file mode 100644 index 00000000..fce196d1 --- /dev/null +++ b/GigyaSwift/Models/WebAuthn/WebAuthnModels.swift @@ -0,0 +1,113 @@ +// +// WebAuthnModels.swift +// Gigya +// +// Created by Sagi Shmuel on 17/07/2022. +// Copyright © 2022 Gigya. All rights reserved. +// + +import Foundation + +struct WebAuthnGetOptionsResponseModel { + let options: WebAuthnGetOptionsModel + + let token: String + + enum CodingKeys: String, CodingKey { + case options + case token + } + +} + +extension WebAuthnGetOptionsResponseModel: Decodable { + init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + + let optionsString = try values.decode(String.self, forKey: .options) + + let decodedOptions = try DecodeEncodeUtils.decode(fromType: WebAuthnGetOptionsModel.self, data: Data((optionsString).utf8)) + + options = decodedOptions + + token = try values.decode(String.self, forKey: .token) + } +} + +extension WebAuthnGetOptionsResponseModel: Encodable {} + +struct WebAuthnGetOptionsModel: Codable { + let challenge: String + let rpId: String + let userVerification: String? +} + +struct WebAuthnInitRegisterResponseModel { + let options: WebAuthnOptionsModel + + let token: String + + enum CodingKeys: String, CodingKey { + case options + case token + } + +} + +extension WebAuthnInitRegisterResponseModel: Decodable { + init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + + let optionsString = try values.decode(String.self, forKey: .options) + + let decodedOptions = try DecodeEncodeUtils.decode(fromType: WebAuthnOptionsModel.self, data: Data((optionsString).utf8)) + + options = decodedOptions + + token = try values.decode(String.self, forKey: .token) + } +} + +extension WebAuthnInitRegisterResponseModel: Encodable { } + +struct WebAuthnOptionsModel: Codable { + let challenge: String + let rp: WebAuthnRpModel + let user: WebAuthnUserModel + let authenticatorSelection: WebAuthnAuthenticatorSelectionModel +} + +struct WebAuthnRpModel: Codable { + let id: String + let name: String +} + +struct WebAuthnUserModel: Codable { + let id: String + let name: String + let displayName: String +} + +struct WebAuthnAuthenticatorSelectionModel: Codable { + let requireResidentKey: Bool? + let userVerification: String? + let authenticatorAttachment: WebAuthnAuthenticatorSelectionType? +} + +enum WebAuthnAuthenticatorSelectionType: String, Codable { + case platform = "platform" + case crossPlatform = "cross-platform" + case unspecified = "unspecified" +} + +public struct GigyaWebAuthnCredential: Codable { + let name: String + let displayName: String + let type: GigyaWebAuthnCredentialType + let key: String +} + +public enum GigyaWebAuthnCredentialType: String, Codable { + case crossPlatform = "cross-platform" + case platform +} diff --git a/GigyaSwift/Network/Models/ApiRequestModel.swift b/GigyaSwift/Network/Models/ApiRequestModel.swift index 0b6bf5e5..10824f27 100644 --- a/GigyaSwift/Network/Models/ApiRequestModel.swift +++ b/GigyaSwift/Network/Models/ApiRequestModel.swift @@ -11,6 +11,7 @@ import Foundation public struct ApiRequestModel { public let method: String public var params: [String: Any]? + public var headers: [String: String]? public let isAnonymous: Bool var config: GigyaConfig? diff --git a/GigyaSwift/Network/Service/NetworkProvider.swift b/GigyaSwift/Network/Service/NetworkProvider.swift index ed441a65..b6a40355 100644 --- a/GigyaSwift/Network/Service/NetworkProvider.swift +++ b/GigyaSwift/Network/Service/NetworkProvider.swift @@ -16,7 +16,7 @@ final class NetworkProvider { var sessionService: SessionServiceProtocol - let urlSession = URLSession.sharedInternal + var urlSession = URLSession.sharedInternal init(config: GigyaConfig, persistenceService: PersistenceService, sessionService: SessionServiceProtocol) { @@ -50,6 +50,13 @@ final class NetworkProvider { dataURL.appendPathComponent(model.method) var request: URLRequest = URLRequest(url: dataURL) + + if let headers = model.headers { + headers.forEach { row in + request.setValue(row.value, forHTTPHeaderField: row.key) + } + } + request.timeoutInterval = TimeInterval(config?.requestTimeout ?? InternalConfig.Network.requestTimeoutDefult) // Encode body request to params diff --git a/GigyaSwift/README.md b/GigyaSwift/README.md index eecebf65..bbe9cd25 100644 --- a/GigyaSwift/README.md +++ b/GigyaSwift/README.md @@ -448,6 +448,65 @@ Gigya.sharedInstance().sso(viewController: viewController) { result in } ``` +## FIDO/WebAuthn Authentication +FIDO is a passwordless authentication method that enables password-only logins to be replaced with secure and fast login experiences across websites and apps. +Our SDK provides an interface to register a passkey, login, and revoke passkeys created using Fido/Passkeys, backed by our WebAuthn service. +​ +### SDK limitations: +Only one passkey is supported at a time. Once registering a new key, the client's previous key will be automatically revoked. +​ +### SDK prerequisites: +iOS 16+ +​ +To use Fido authentication on mobile, make sure you have correctly set up your **Fido Configuration** section under the **Identity -> Security -> Authentication** tab of your SAP Customer Data Cloud console. +​ +**Interoperability with your website** +You must have an associated domain with the webcredentials service type when making a registration or assertion request; otherwise, the request returns an error. See [Supporting associated domains](https://developer.apple.com/documentation/xcode/supporting-associated-domains) for more information. +​ +### Implementation. +The Fido interface contains 3 methods: +​ +**Registration:** +Registering a new passkey can be performed only when a valid session is available. +``` +``` +``` +``` +``` +let result = await gigya.webAuthn.register(viewController: self) + switch result { + case .success(let data): + <#code#> + case .failure(let error): + <#code#> + } +``` +​ +**Login:** +Logging in using a valid passkey. +``` + let result = await gigya.webAuthn.login(viewController: self) + switch result { + case .success(let data): + <#code#> + case .failure(let error): + <#code#> + } +``` +​ +​ +**Revoke:** +Revoking the current passkey. Logging in will not be available until registering a new one. +``` +let result = await gigya.webAuthn.revoke() + switch result { + case .success(let data): + <#code#> + case .failure(let error): + <#code#> + } +``` + ## Logout diff --git a/Podfile b/Podfile index ed3d04fb..31bf84df 100644 --- a/Podfile +++ b/Podfile @@ -13,8 +13,8 @@ target 'TestApp' do pod 'LineSDKSwift', '~> 5.0' pod 'GoogleSignIn', '6.0.1', :modular_headers => false - pod 'FBSDKCoreKit', '7.0.1' - pod 'FBSDKLoginKit', '7.0.1' + pod 'FBSDKCoreKit', '9.0.1' + pod 'FBSDKLoginKit', '9.0.1' pod 'Firebase/Analytics' pod 'Firebase/Messaging'