Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Phone SMS and Voice as authentication factors. #189

Merged
merged 3 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/documentation-ghpages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
- master

env:
DEVELOPER_DIR: /Applications/Xcode_14.2.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer
NSUnbufferedIO: YES

# NOTE: The DocC `generate-documentation` plugin does not handle the
Expand All @@ -20,7 +20,7 @@ env:
jobs:
ExportToGHPages:
name: Export to Github Pages
runs-on: macos-12
runs-on: macos-latest-large
steps:
- uses: actions/checkout@master
- name: Build Documentation
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/documentation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ on:
- 'Sources/**/*.md'

env:
DEVELOPER_DIR: /Applications/Xcode_14.2.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer
NSUnbufferedIO: YES

jobs:
BuildDocumentation:
name: Build Documentation Archives
runs-on: macos-12
runs-on: macos-latest-large
steps:
- uses: actions/checkout@master
- name: AuthFoundation
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ on:

jobs:
SwiftLint:
runs-on: macos-latest
runs-on: macos-latest-large
steps:
- uses: actions/checkout@v1
- name: Lint code using SwiftLint
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ on:
- 'Tests/**/*.swift'

env:
DEVELOPER_DIR: /Applications/Xcode_14.2.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer
NSUnbufferedIO: YES
iOS_DESTINATION: "platform=iOS Simulator,OS=16.2,name=iPhone 14 Pro Max"
tvOS_DESTINATION: "platform=tvOS Simulator,OS=16.1,name=Apple TV"
iOS_DESTINATION: "platform=iOS Simulator,OS=17.4,name=iPhone 15 Pro Max"
tvOS_DESTINATION: "platform=tvOS Simulator,OS=17.4,name=Apple TV"

jobs:
SwiftBuild:
name: Swift Unit Tests
runs-on: macos-12
runs-on: macos-latest-large
timeout-minutes: 10
steps:
- name: Get swift version
Expand All @@ -42,7 +42,7 @@ jobs:

Cocoapods:
name: CocoaPods Build
runs-on: macos-12
runs-on: macos-latest-large
timeout-minutes: 10
needs:
- SwiftBuild
Expand All @@ -60,7 +60,7 @@ jobs:

XcodeBuild:
name: Xcode Unit Tests
runs-on: macos-12
runs-on: macos-latest-large
timeout-minutes: 25
steps:
- uses: actions/checkout@master
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
967D0ACE29F89379002A5AD3 /* SignInScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967D0ACC29F89231002A5AD3 /* SignInScreen.swift */; };
967D0AD129FAE193002A5AD3 /* SwiftOTP in Frameworks */ = {isa = PBXBuildFile; productRef = 967D0AD029FAE193002A5AD3 /* SwiftOTP */; };
967D0AD329FAE48E002A5AD3 /* DirectAuth2FASignInTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967D0AD229FAE48E002A5AD3 /* DirectAuth2FASignInTests.swift */; };
E05F32392BE0736A00BB20D1 /* ContinuationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E05F32382BE0736A00BB20D1 /* ContinuationView.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -72,6 +73,7 @@
967D0AC929EF47F3002A5AD3 /* SecondaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondaryView.swift; sourceTree = "<group>"; };
967D0ACC29F89231002A5AD3 /* SignInScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInScreen.swift; sourceTree = "<group>"; };
967D0AD229FAE48E002A5AD3 /* DirectAuth2FASignInTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectAuth2FASignInTests.swift; sourceTree = "<group>"; };
E05F32382BE0736A00BB20D1 /* ContinuationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinuationView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -126,6 +128,7 @@
967D0ABE29EDB274002A5AD3 /* SignInView.swift */,
967D0AC729EF47EB002A5AD3 /* PrimaryView.swift */,
967D0AC929EF47F3002A5AD3 /* SecondaryView.swift */,
E05F32382BE0736A00BB20D1 /* ContinuationView.swift */,
967D0AC229EE1E03002A5AD3 /* UnconfiguredView.swift */,
967D0A3229EA1BE4002A5AD3 /* Main.storyboard */,
967D0A3529EA1BE6002A5AD3 /* Assets.xcassets */,
Expand Down Expand Up @@ -325,6 +328,7 @@
967D0A9A29EA1CAD002A5AD3 /* TokenDetailViewController.swift in Sources */,
967D0ABF29EDB274002A5AD3 /* SignInView.swift in Sources */,
967D0A3129EA1BE4002A5AD3 /* SignInViewController.swift in Sources */,
E05F32392BE0736A00BB20D1 /* ContinuationView.swift in Sources */,
967D0AC829EF47EB002A5AD3 /* PrimaryView.swift in Sources */,
967D0A2D29EA1BE4002A5AD3 /* AppDelegate.swift in Sources */,
967D0A2F29EA1BE4002A5AD3 /* SceneDelegate.swift in Sources */,
Expand Down
84 changes: 84 additions & 0 deletions Samples/DirectAuthSignIn/DirectAuthSignIn/ContinuationView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// Copyright (c) 2024-Present, Okta, Inc. and/or its affiliates. All rights reserved.
// The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
//
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and limitations under the License.
//

import SwiftUI
import OktaDirectAuth

extension SignInView {
struct ContinuationView: View {
let flow: DirectAuthenticationFlow

@State var status: DirectAuthenticationFlow.Status
@State var selectedFactor: SignInView.Factor = .code
@State var verificationCode: String = ""

var factor: DirectAuthenticationFlow.ContinuationFactor? {
switch selectedFactor {
case .code:
return .prompt(code: verificationCode)
default:
return nil
}
}

@Binding var error: Error?
@Binding var hasError: Bool

var body: some View {
VStack {
Text("Please continue authenticating.")
.padding(25)

VStack(alignment: .leading, spacing: 1) {
Picker(selection: $selectedFactor, label: EmptyView()) {
ForEach(SignInView.Factor.continuationFactors, id: \.self) {
Text($0.title)
}
}.pickerStyle(.menu)
.accessibilityIdentifier("factor_type_button")
.padding(.horizontal, -10)
.padding(.vertical, -4)

if selectedFactor == .code {
TextField("123456", text: $verificationCode)
.textContentType(.oneTimeCode)
.accessibilityIdentifier("verification_code_button")
.padding(10)
.overlay {
RoundedRectangle(cornerRadius: 6)
.stroke(.secondary, lineWidth: 1)
}
}

if let factor = factor {
Button("Continue") {
Task {
do {
status = try await flow.resume(status, with: factor)
if case let .success(token) = status {
Credential.default = try Credential.store(token)
}
} catch {
self.error = error
self.hasError = true
}
}
}
.accessibilityIdentifier("signin_button")
.font(.headline)
.buttonStyle(.borderedProminent)
}
}.padding()
}
}
}
}
36 changes: 22 additions & 14 deletions Samples/DirectAuthSignIn/DirectAuthSignIn/PrimaryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,20 @@ extension SignInView {
@State var oneTimeCode: String = ""
@State var selectedFactor: SignInView.Factor = .password

var factor: DirectAuthenticationFlow.PrimaryFactor {
var factor: DirectAuthenticationFlow.PrimaryFactor? {
switch selectedFactor {
case .password:
return .password(password)
case .otp:
return .otp(code: oneTimeCode)
case .oob:
return .oob(channel: .push)
case .sms:
return .oob(channel: .sms)
case .voice:
return .oob(channel: .voice)
default:
return nil
}
}

Expand Down Expand Up @@ -86,26 +92,28 @@ extension SignInView {
RoundedRectangle(cornerRadius: 6)
.stroke(.secondary, lineWidth: 1)
}
case .oob: EmptyView()
case .oob, .sms, .voice, .code: EmptyView()
}
}

Button("Sign In") {
Task {
do {
status = try await flow.start(username, with: factor)
if case let .success(token) = status {
Credential.default = try Credential.store(token)
if let factor = factor {
Button("Sign In") {
Task {
do {
status = try await flow.start(username, with: factor)
if case let .success(token) = status {
Credential.default = try Credential.store(token)
}
} catch {
self.error = error
self.hasError = true
}
} catch {
self.error = error
self.hasError = true
}
}
.accessibilityIdentifier("signin_button")
.font(.headline)
.buttonStyle(.borderedProminent)
}
.accessibilityIdentifier("signin_button")
.font(.headline)
.buttonStyle(.borderedProminent)
}.padding()
}
}
Expand Down
6 changes: 6 additions & 0 deletions Samples/DirectAuthSignIn/DirectAuthSignIn/SecondaryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ extension SignInView {
return .oob(channel: .push)
case .password:
return nil
case .sms:
return .oob(channel: .sms)
case .voice:
return .oob(channel: .voice)
default:
return nil
}
}

Expand Down
32 changes: 23 additions & 9 deletions Samples/DirectAuthSignIn/DirectAuthSignIn/SignInView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,37 +21,50 @@ struct SignInView: View {
@State var hasError: Bool = false

enum Factor {
case password, otp, oob
case password, otp, oob, sms, voice, code

var title: String {
switch self {
case .password: return "Password"
case .otp: return "One-Time Code"
case .oob: return "Push Notification"
case .sms: return "Phone: SMS"
case .voice: return "Phone: Voice"
case .code: return "Verification Code"
}
}

static let primaryFactors: [Factor] = [.password, .otp, .oob]
static let secondaryFactors: [Factor] = [.otp, .oob]
static let primaryFactors: [Factor] = [.password, .otp, .oob, .sms, .voice]
static let secondaryFactors: [Factor] = [.otp, .oob, .sms, .voice]
static let continuationFactors: [Factor] = [.code]
}

// swiftlint:disable force_unwrapping
var body: some View {
VStack {
VStack(alignment: .leading, spacing: 15) {
Text("Direct Authentication Sign In")
.font(.title)

if let flow = flow {
if let status = status {
SecondaryView(flow: flow,
status: status,
error: $error,
hasError: $hasError)
} else {
switch status {
case nil:
PrimaryView(flow: flow,
status: $status,
error: $error,
hasError: $hasError)
case .mfaRequired(_):
SecondaryView(flow: flow,
status: status!,
error: $error,
hasError: $hasError)
case .continuation(_):
ContinuationView(flow: flow,
status: status!,
error: $error,
hasError: $hasError)
case .success(_):
ProgressView()
}
} else {
UnconfiguredView()
Expand Down Expand Up @@ -80,6 +93,7 @@ struct SignInView: View {
}
.navigationTitle("Direct Authentication")
}
// swiftlint:enable force_unwrapping
}

// swiftlint:disable force_unwrapping
Expand Down
Loading
Loading