Skip to content

Commit

Permalink
Make sending the Device Token configurable, and bump the upstream dep…
Browse files Browse the repository at this point in the history
…endency version (#122)
  • Loading branch information
mikenachbaur-okta authored Dec 23, 2022
1 parent b12eadc commit 7fab46e
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 21 deletions.
4 changes: 2 additions & 2 deletions OktaIdx.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'OktaIdx'
spec.version = '3.0.4'
spec.version = '3.0.5'
spec.summary = 'SDK to easily integrate the Okta Identity Engine'
spec.description = <<-DESC
Integrate your native app with Okta using the Okta Identity Engine library.
Expand All @@ -24,5 +24,5 @@ Integrate your native app with Okta using the Okta Identity Engine library.
spec.source_files = 'Sources/OktaIdx/**/*.swift'
spec.swift_version = "5.5"

spec.dependency "OktaAuthFoundation", "1.1.4"
spec.dependency "OktaAuthFoundation", "~> 1.1"
end
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var package = Package(
dependencies: [
.package(name: "AuthFoundation",
url: "https://github.com/okta/okta-mobile-swift",
from: "1.1.4")
from: "1.1.5")
],
targets: [
.target(name: "OktaIdx",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ This library uses semantic versioning and follows Okta's [Library Version Policy
| ------- | ---------------------------------- |
| 1.0.0 | |
| 2.0.1 | |
| 3.0.4 | ✔️ Stable |
| 3.0.5 | ✔️ Stable |

The latest release can always be found on the [releases page][github-releases].

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,21 @@ class ProfileTableViewController: UITableViewController {
var credential: Credential? {
didSet {
if let credential = credential {
credential.refreshIfNeeded { _ in
credential.userInfo { _ in
DispatchQueue.main.async {
self.configure(credential)
DispatchQueue.main.async {
self.configure(credential)
}

credential.refreshIfNeeded { result in
switch result {
case .success():
credential.userInfo { _ in
DispatchQueue.main.async {
self.configure(credential)
}
}

case .failure(let error):
self.show(error: error)
}
}
}
Expand Down Expand Up @@ -78,6 +88,12 @@ class ProfileTableViewController: UITableViewController {
dateFormatter.timeStyle = .long

guard let info = credential.userInfo else {
tableContent = [
.actions: [
.init(kind: .destructive, id: "signout", title: "Sign Out")
]
]
tableView.reloadData()
return
}

Expand Down Expand Up @@ -117,7 +133,7 @@ class ProfileTableViewController: UITableViewController {
DispatchQueue.main.async {
let alert = UIAlertController(title: nil, message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(.init(title: "OK", style: .default))
self.show(alert, sender: nil)
self.present(alert, animated: true)
}
}

Expand Down Expand Up @@ -151,7 +167,7 @@ class ProfileTableViewController: UITableViewController {

func refresh() {
guard let credential = Credential.default else { return }
credential.refreshIfNeeded { result in
credential.refresh { result in
if case let .failure(error) = result {
self.show(error: error)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,12 @@ extension InteractionCodeFlow {
return deviceToken
}
}

extension InteractionCodeFlow.Option {
var includeInInteractRequest: Bool {
switch self {
case .omitDeviceToken: return false
default: return true
}
}
}
20 changes: 16 additions & 4 deletions Sources/OktaIdx/InteractionCodeFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public final class InteractionCodeFlow: AuthenticationFlow {

/// Option used when a user is authenticating using a recovery token.
case recoveryToken = "recovery_token"

/// Option indicating whether or not a device identifier should be sent on API requests, which enables the device to be remembered.
case omitDeviceToken
}

/// The type used for the completion handler result from any method that returns an ``Response``.
Expand Down Expand Up @@ -69,6 +72,11 @@ public final class InteractionCodeFlow: AuthenticationFlow {
/// This value is used when resuming authentication at a later date or after app launch, and to ensure the final token exchange can be completed.
public internal(set) var context: Context?

/// The options used when starting an authentication flow.
///
/// This is updated when the ``start(options:completion:)`` (or ``start(options:)``) method is invoked, and is cleared when ``reset()`` is called.
public internal(set) var options: [Option: Any]?

/// Convenience initializer to construct an authentication flow from variables.
/// - Parameters:
/// - issuer: The issuer URL.
Expand Down Expand Up @@ -133,15 +141,15 @@ public final class InteractionCodeFlow: AuthenticationFlow {
/// - Parameters:
/// - options: Options to include within the OAuth2 transaction.
/// - completion: Completion block to be invoked when the session is started.
public func start(options: [Option: String]? = nil,
public func start(options: [Option: Any]? = nil,
completion: @escaping ResponseResult)
{
if isAuthenticating {
cancel()
}

// Ensure we have, at minimum, a state value
let state = options?[.state] ?? UUID().uuidString
let state: String = options?[.state] as? String ?? UUID().uuidString
var options = options ?? [:]
options[.state] = state

Expand All @@ -151,6 +159,8 @@ public final class InteractionCodeFlow: AuthenticationFlow {
}

self.isAuthenticating = true
self.options = options

let request = InteractRequest(baseURL: client.baseURL,
clientId: client.configuration.clientId,
scope: client.configuration.scopes,
Expand Down Expand Up @@ -309,6 +319,7 @@ public final class InteractionCodeFlow: AuthenticationFlow {
public func reset() {
context = nil
isAuthenticating = false
options = nil

// Remove any previous `idx` cookies so it won't leak into other sessions.
let storage = client.session.configuration.httpCookieStorage ?? HTTPCookieStorage.shared
Expand Down Expand Up @@ -346,7 +357,7 @@ extension InteractionCodeFlow {
/// - configuration: Configuration describing the app settings to contact.
/// - options: Options to include within the OAuth2 transaction.
/// - Returns: A ``Response``.
public func start(options: [Option: String]? = nil) async throws -> Response {
public func start(options: [Option: Any]? = nil) async throws -> Response {
try await withCheckedThrowingContinuation { continuation in
start(options: options) { result in
continuation.resume(with: result)
Expand Down Expand Up @@ -387,7 +398,8 @@ extension InteractionCodeFlow: UsesDelegateCollection {

extension InteractionCodeFlow: OAuth2ClientDelegate {
public func api(client: APIClient, willSend request: inout URLRequest) {
guard let url = request.url,
guard options?[.omitDeviceToken] as? Bool ?? false == false,
let url = request.url,
let deviceTokenCookie = deviceTokenCookie
else {
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ extension InteractionCodeFlow {
let clientId: String
let scope: String
let redirectUri: URL
let options: [InteractionCodeFlow.Option: String]?
let options: [InteractionCodeFlow.Option: Any]?
let pkce: PKCE

init(baseURL: URL,
clientId: String,
scope: String,
redirectUri: URL,
options: [InteractionCodeFlow.Option: String]?,
options: [InteractionCodeFlow.Option: Any]?,
pkce: PKCE)
{
url = baseURL.appendingPathComponent("v1/interact")
Expand Down Expand Up @@ -59,10 +59,12 @@ extension InteractionCodeFlow.InteractRequest: APIRequest, APIRequestBody {
"code_challenge_method": pkce.method.rawValue
]

options?.forEach { (key: InteractionCodeFlow.Option, value: String) in
result[key.rawValue] = value
}

options?.filter { $0.key.includeInInteractRequest }
.compactMapValues { $0 as? String }
.forEach { (key: InteractionCodeFlow.Option, value: String) in
result[key.rawValue] = value
}

return result
}
}
2 changes: 1 addition & 1 deletion Sources/OktaIdx/Version.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@

import Foundation

public let Version = SDKVersion(sdk: "okta-idx-swift", version: "3.0.4")
public let Version = SDKVersion(sdk: "okta-idx-swift", version: "3.0.5")
34 changes: 34 additions & 0 deletions Tests/OktaIdxTests/InteractionCodeFlowTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,40 @@ class InteractionCodeFlowTests: XCTestCase {

XCTAssertEqual(delegate.calls.count, 1)
XCTAssertEqual(delegate.calls.first?.type, .response)

if InteractionCodeFlow.deviceIdentifier != nil {
let deviceToken = try XCTUnwrap(flow.deviceTokenCookie?.value)
XCTAssertEqual(urlSession.requests.first?.allHTTPHeaderFields?["Cookie"],
"DT=\(deviceToken)")
}
}

func testStartWithOptions() throws {
urlSession.expect("https://example.com/oauth2/default/v1/interact",
data: try data(from: .module,
for: "interact-response"))
urlSession.expect("https://example.com/idp/idx/introspect",
data: try data(from: .module,
for: "introspect-response"))

let wait = expectation(description: "start")
flow.start(options: [
.omitDeviceToken: true,
.state: "CustomState"
]) { result in
defer { wait.fulfill() }

guard case let Result.success(response) = result else {
XCTFail("Received a failure when a success was expected")
return
}

XCTAssertNotNil(response.remediations[.identify])
}
waitForExpectations(timeout: 1.0)

XCTAssertNil(urlSession.requests.first?.allHTTPHeaderFields?["Cookie"])
XCTAssertEqual(flow.context?.state, "CustomState")
}

func testStartFailedInteract() throws {
Expand Down

0 comments on commit 7fab46e

Please sign in to comment.