Skip to content

Commit

Permalink
Merge pull request #80 from okta/dev
Browse files Browse the repository at this point in the history
OKTA-234954-AuthN iOS SDK: Push factor polling doesn't stop if user taps on back button
  • Loading branch information
IldarAbdullin-okta authored Jul 2, 2019
2 parents 7b7b9d6 + 4522aa9 commit 3feb584
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 20 deletions.
46 changes: 28 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,34 @@ public func activate(onStatusChange: @escaping (_ newStatus: OktaAuthStatus) ->
```
Sample app [example](https://github.com/okta/samples-ios/blob/master/custom-sign-in/OktaNativeLogin/MFA/Enrollment/MFActivatePushTotpViewController.swift#L48-L62)

#### checkFactorResult

After the push notification is sent to user's device we need to know when the user completes the activation/challenge.

```swift
public func checkFactorResult(onStatusChange: @escaping (_ newStatus: OktaAuthStatus) -> Void,
onError: @escaping (_ error: OktaError) -> Void)
```

Keep calling `checkFactorResult` function while `status.factorResult` value equals `.waiting`.
**NOTE** Don't call `checkFactorResult` function too often, keep polling interval within 3-5 seconds to not cause extra load on the server
Example of polling logic:
```swift
func handlePushChallenge(factor: OktaFactorPush) {
factor.checkFactorResult(onStatusChange: { (status) in
if status.factorResult == .waiting {
DispatchQueue.main.asyncAfter(deadline:.now() + 5.0) {
self.handlePushChallenge(factor: factor)
}
} else {
self.handleStatus(status: status)
}
}, onError: { (error) in
self.handleError(error)
})
}
```

#### [sendActivationLinkViaSms](https://developer.okta.com/docs/api/resources/authn/#activate-push-factor)

Sends an activation SMS when the user is unable to scan the QR code provided as part of an Okta Verify transaction. If for any reason the user can't scan the QR code, they can use the link provided in SMS to complete the transaction.
Expand Down Expand Up @@ -802,24 +830,6 @@ Sends an asynchronous push notification (challenge) to the device for the user t
public func verify(onStatusChange: @escaping (_ newStatus: OktaAuthStatus) -> Void,
onError: @escaping (_ error: OktaError) -> Void)
```
Implement polling logic in order to get updates for the push factor result.
**NOTE** Don't call `verify` function too often, keep polling interval within 3-5 seconds to not cause extra load on the server
Example of polling logic:
```swift
func handlePushChallenge(factor: OktaFactorPush) {
factor.verify(onStatusChange: { (status) in
if status.factorResult == .waiting {
DispatchQueue.main.asyncAfter(deadline:.now() + 5.0) {
self.handlePushChallenge(factor: factor)
}
} else {
self.handleStatus(status: status)
}
}, onError: { (error) in
self.handleError(error)
})
}
```

### OktaFactorTotp

Expand Down
21 changes: 21 additions & 0 deletions Source/Factors/OktaFactorPush.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,27 @@ open class OktaFactorPush : OktaFactor {
onError: onError)
}

public func checkFactorResult(onStatusChange: @escaping (_ newStatus: OktaAuthStatus) -> Void,
onError: @escaping (_ error: OktaError) -> Void) {
guard canActivate() || canVerify() else {
onError(OktaError.wrongStatus("Can't find 'poll' link in response"))
return
}

let pollLink: LinksResponse.Link?
if activationLink != nil {
pollLink = activationLink
} else {
pollLink = verifyLink
}

self.verifyFactor(with: pollLink!,
answer: nil,
passCode: nil,
onStatusChange: onStatusChange,
onError: onError)
}

// MARK: - Internal
override init(factor: EmbeddedResponse.Factor,
stateToken:String,
Expand Down
4 changes: 4 additions & 0 deletions Source/Statuses/OktaAuthStatus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ open class OktaAuthStatus {
return true
}

open func canPoll() -> Bool {
return false
}

open func returnToPreviousStatus(onStatusChange: @escaping (_ newStatus: OktaAuthStatus) -> Void,
onError: @escaping (_ error: OktaError) -> Void) {
guard canReturn() else {
Expand Down
2 changes: 1 addition & 1 deletion Source/Statuses/OktaAuthStatusFactorChallenge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ open class OktaAuthStatusFactorChallenge : OktaAuthStatus {
return true
}

open func canPoll() -> Bool {
override open func canPoll() -> Bool {
return model.links?.next?.name == "poll" || factor.type == .push
}

Expand Down
2 changes: 1 addition & 1 deletion Source/Statuses/OktaAuthStatusFactorEnrollActivate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ open class OktaAuthStatusFactorEnrollActivate : OktaAuthStatus {
return true
}

open func canPoll() -> Bool {
override open func canPoll() -> Bool {
return model.links?.next?.name == "poll" || factor.type == .push
}

Expand Down
62 changes: 62 additions & 0 deletions Tests/Factors/OktaFactorPushTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,68 @@ class OktaFactorPushTests: OktaFactorTestCase {
XCTAssertTrue(factor.apiMock.verifyFactorCalled)
XCTAssertEqual(factor.verifyLink?.href, factor.apiMock.factorVerificationLink?.href)
}

// MARK: - checkFactorResult

func testCheckFactorResult() {
guard let factor: OktaFactorPush = createFactor(from: .MFA_REQUIRED, type: .push) else {
XCTFail()
return
}

factor.setupApiMockResponse(.MFA_REQUIRED)
let delegate = factor.setupMockDelegate(with: OktaAuthStatusUnauthenticated(oktaDomain: URL(string: "http://mock.url")!))

let ex = expectation(description: "Operation should succeed!")

factor.checkFactorResult(
onStatusChange: { status in
XCTAssertEqual(delegate.changedStatus?.statusType, status.statusType)
ex.fulfill()
},
onError: { error in
XCTFail(error.localizedDescription)
ex.fulfill()
}
)

waitForExpectations(timeout: 5.0)

verifyDelegateSucceeded(delegate, with: .MFA_REQUIRED)

XCTAssertTrue(factor.apiMock.verifyFactorCalled)
XCTAssertEqual(factor.verifyLink?.href, factor.apiMock.factorVerificationLink?.href)
}

func testCheckFactorResult_ApiFailed() {
guard let factor: OktaFactorPush = createFactor(from: .MFA_REQUIRED, type: .push) else {
XCTFail()
return
}

factor.setupApiMockFailure()
let delegate = factor.setupMockDelegate(with: OktaError.internalError("Test"))

let ex = expectation(description: "Operation should fail!")

factor.checkFactorResult(
onStatusChange: { status in
XCTFail("Operation should fail!")
ex.fulfill()
},
onError: { error in
XCTAssertEqual(delegate.error?.localizedDescription, error.localizedDescription)
ex.fulfill()
}
)

waitForExpectations(timeout: 5.0)

verifyDelegateFailed(delegate)

XCTAssertTrue(factor.apiMock.verifyFactorCalled)
XCTAssertEqual(factor.verifyLink?.href, factor.apiMock.factorVerificationLink?.href)
}

// MARK: - select

Expand Down

0 comments on commit 3feb584

Please sign in to comment.