Skip to content

Commit 4cdbe9b

Browse files
committed
fixes run script for Auth0.plist file
(cherry picked from commit 3274a1d)
1 parent 560b4ae commit 4cdbe9b

File tree

7 files changed

+353
-11
lines changed

7 files changed

+353
-11
lines changed

App/ContentView.swift

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import SwiftUI
2+
import Auth0
3+
import Combine
4+
struct ContentView: View {
5+
@ObservedObject var viewModel: ContentViewModel
6+
@State var email: String = ""
7+
@State var password: String = ""
8+
init(viewModel: ContentViewModel) {
9+
self.viewModel = viewModel
10+
}
11+
12+
var body: some View {
13+
if viewModel.showOTPTextField {
14+
TextField(text: $viewModel.otpCode) {
15+
Text("Enter otp")
16+
}.keyboardType(.numberPad)
17+
.padding()
18+
.cornerRadius(4)
19+
.overlay {
20+
RoundedRectangle(cornerRadius: 4)
21+
.stroke(Color.blue, lineWidth: 1)
22+
}
23+
24+
Button {
25+
viewModel.confirmEnrollment()
26+
} label: {
27+
Text("Continue")
28+
}
29+
.frame(height: 48)
30+
.background(Color.black)
31+
} else if let qrCodeImage = viewModel.qrCodeImage {
32+
VStack {
33+
Spacer()
34+
qrCodeImage
35+
.resizable()
36+
.interpolation(.none)
37+
.aspectRatio(1.0, contentMode: .fit)
38+
.padding()
39+
.overlay {
40+
RoundedRectangle(cornerRadius: 4)
41+
.stroke(Color.black, lineWidth: 0.5)
42+
}
43+
44+
TextField(text: $viewModel.otpCode) {
45+
Text("Enter otp")
46+
}.keyboardType(.numberPad)
47+
.overlay {
48+
RoundedRectangle(cornerRadius: 4)
49+
.stroke(Color.blue, lineWidth: 1)
50+
}
51+
52+
Button {
53+
viewModel.confirmEnrollment()
54+
} label: {
55+
Text("Continue")
56+
}
57+
.frame(height: 48)
58+
.background(Color.black)
59+
Spacer()
60+
}
61+
} else {
62+
VStack(spacing: 10) {
63+
TextField("email", text: $email)
64+
.keyboardType(.emailAddress)
65+
.padding()
66+
.cornerRadius(4)
67+
.overlay {
68+
RoundedRectangle(cornerRadius: 4)
69+
.stroke(Color.blue, lineWidth: 1)
70+
}
71+
72+
SecureField("password", text: $password)
73+
.padding()
74+
.cornerRadius(4)
75+
.overlay {
76+
RoundedRectangle(cornerRadius: 4)
77+
.stroke(Color.blue, lineWidth: 1)
78+
}
79+
Button {
80+
viewModel.login(email: email, password: password)
81+
} label: {
82+
Text("Login")
83+
.foregroundStyle(Color.white)
84+
.padding()
85+
.frame(maxWidth: .infinity)
86+
}
87+
.background(Color.black)
88+
89+
Button {
90+
_ = viewModel.credentialsManager.clear()
91+
_ = viewModel.credentialsManager.clear(forAudience: "")
92+
} label: {
93+
Text("Logout")
94+
.foregroundStyle(Color.white)
95+
.padding()
96+
.frame(maxWidth: .infinity)
97+
}
98+
.background(Color.black)
99+
100+
Button {
101+
viewModel.fetchAPICredentials()
102+
} label: {
103+
Text("Get credentials")
104+
.foregroundStyle(Color.white)
105+
.padding()
106+
.frame(maxWidth: .infinity)
107+
}
108+
.background(Color.black)
109+
Spacer()
110+
111+
if let credentials = viewModel.credentials {
112+
Text("\(credentials.accessToken)")
113+
.foregroundStyle(Color.white)
114+
.background(Color.black)
115+
.frame(maxWidth: .infinity)
116+
}
117+
}.padding()
118+
}
119+
}
120+
121+
}

App/ContentViewModel.swift

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import Combine
2+
import SwiftUI
3+
import Foundation
4+
import Auth0
5+
import CoreImage.CIFilterBuiltins
6+
@MainActor
7+
final class ContentViewModel: ObservableObject {
8+
let credentialsManager: CredentialsManager
9+
@Published var otpCode: String = ""
10+
@Published var qrCodeImage: Image?
11+
@Published var showOTPTextField = false
12+
var mfaToken: String = ""
13+
@Published var credentials: Credentials? = nil
14+
15+
init(credentialsManager: CredentialsManager) {
16+
self.credentialsManager = credentialsManager
17+
}
18+
19+
func store(credentials: Credentials) {
20+
_ = self.credentialsManager.store(credentials: credentials)
21+
_ = self.credentialsManager.store(apiCredentials: APICredentials(from: credentials), forAudience: "")
22+
}
23+
24+
func fetchAPICredentials() {
25+
Task {
26+
do {
27+
let credentials = try await credentialsManager.apiCredentials(forAudience: "")
28+
print(credentials.accessToken)
29+
} catch {
30+
print(error)
31+
}
32+
}
33+
}
34+
35+
func login(email: String, password: String) {
36+
Task {
37+
do {
38+
let credentials = try await Auth0.authentication()
39+
.login(usernameOrEmail: email, password: password, realmOrConnection: "Username-Password-Authentication", audience: "https://test-nandan.com", scope: "openid email profile offline_access")
40+
.start()
41+
await MainActor.run {
42+
self.credentials = credentials
43+
}
44+
self.store(credentials: credentials)
45+
} catch {
46+
if let error = error as? AuthenticationError,
47+
error.isMultifactorRequired,
48+
let mfaToken = error.info["mfa_token"] as? String {
49+
self.mfaToken = mfaToken
50+
await self.fetchAuthenticators(mfaToken: mfaToken)
51+
}
52+
}
53+
}
54+
}
55+
56+
func confirmEnrollment() {
57+
qrCodeImage = nil
58+
showOTPTextField = false
59+
Task {
60+
do {
61+
let credentials = try await Auth0.authentication()
62+
.login(withOTP: otpCode, mfaToken: mfaToken)
63+
.start()
64+
await MainActor.run {
65+
self.credentials = credentials
66+
}
67+
self.store(credentials: credentials)
68+
} catch {
69+
print(error)
70+
}
71+
}
72+
}
73+
func fetchAuthenticators(mfaToken: String) async {
74+
do {
75+
let authenticators = try await Auth0.authentication()
76+
.listMFAAuthenticators(mfaToken: mfaToken)
77+
.start()
78+
if authenticators.filter { $0.type == "otp" && $0.active == true }.isEmpty {
79+
let challenge = try await Auth0.authentication()
80+
.enrollOTPMFA(mfaToken: mfaToken)
81+
.start()
82+
await self.setAuthQRCodeImage(challenge: challenge)
83+
} else {
84+
showOTPTextField = true
85+
}
86+
} catch {
87+
print(error)
88+
}
89+
}
90+
91+
@MainActor func setAuthQRCodeImage(challenge: OTPMFAEnrollmentChallenge) async {
92+
let context = CIContext()
93+
let filter = CIFilter.qrCodeGenerator()
94+
filter.correctionLevel = "H"
95+
filter.message = Data(challenge.barCodeURI.utf8)
96+
97+
if let outputImage = filter.outputImage {
98+
if let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
99+
await MainActor.run {
100+
qrCodeImage = Image(decorative: cgImage, scale: 1.0) }
101+
}
102+
}
103+
}
104+
}

Auth0.xcodeproj/project.pbxproj

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2352,7 +2352,7 @@
23522352
);
23532353
runOnlyForDeploymentPostprocessing = 0;
23542354
shellPath = /bin/sh;
2355-
shellScript = "AUTH0_PLIST=\"${SRCROOT}/Auth0.plist\"\n\nif [ -f $AUTH0_PLIST ]; then\n cp \"$AUTH0_PLIST\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app\"\nfi\n";
2355+
shellScript = "PLIST_PATH=\"${SRCROOT}/App/Auth0.plist\"\n\n# If missing, create a placeholder Auth0.plist so Xcode doesn't fail\nif [ ! -f \"$PLIST_PATH\" ]; then\n echo \"Auth0.plist not found. Creating placeholder.\"\n cat > \"$PLIST_PATH\" <<EOF\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>ClientId</key>\n <string>YOUR_CLIENT_ID</string>\n <key>Domain</key>\n <string>YOUR_DOMAIN</string>\n</dict>\n</plist>\nEOF\nfi\n\n# Copy to .app bundle\ncp \"$PLIST_PATH\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app\"\n\n\n";
23562356
};
23572357
5B7EE48C20FCA0D900367724 /* Auth0 */ = {
23582358
isa = PBXShellScriptBuildPhase;
@@ -2367,7 +2367,7 @@
23672367
);
23682368
runOnlyForDeploymentPostprocessing = 0;
23692369
shellPath = /bin/sh;
2370-
shellScript = "AUTH0_PLIST=\"${SRCROOT}/Auth0.plist\"\n\nif [ -f $AUTH0_PLIST ]; then\n cp \"$AUTH0_PLIST\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Contents/Resources\"\nfi\n";
2370+
shellScript = "PLIST_PATH=\"${SRCROOT}/App/Auth0.plist\"\n\n# If missing, create a placeholder Auth0.plist so Xcode doesn't fail\nif [ ! -f \"$PLIST_PATH\" ]; then\n echo \"Auth0.plist not found. Creating placeholder.\"\n cat > \"$PLIST_PATH\" <<EOF\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>ClientId</key>\n <string>YOUR_CLIENT_ID</string>\n <key>Domain</key>\n <string>YOUR_DOMAIN</string>\n</dict>\n</plist>\nEOF\nfi\n\n# Copy to .app bundle\ncp \"$PLIST_PATH\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app\"\n\n";
23712371
};
23722372
5F53F5CB1CFCDC2500476A46 /* Auth0 */ = {
23732373
isa = PBXShellScriptBuildPhase;
@@ -2382,7 +2382,7 @@
23822382
);
23832383
runOnlyForDeploymentPostprocessing = 0;
23842384
shellPath = /bin/sh;
2385-
shellScript = "AUTH0_PLIST=\"${SRCROOT}/Auth0.plist\"\n\nif [ -f $AUTH0_PLIST ]; then\n cp \"$AUTH0_PLIST\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app\"\nfi\n";
2385+
shellScript = "PLIST_PATH=\"${SRCROOT}/App/Auth0.plist\"\n\n# If missing, create a placeholder Auth0.plist so Xcode doesn't fail\nif [ ! -f \"$PLIST_PATH\" ]; then\n echo \"Auth0.plist not found. Creating placeholder.\"\n cat > \"$PLIST_PATH\" <<EOF\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>ClientId</key>\n <string>YOUR_CLIENT_ID</string>\n <key>Domain</key>\n <string>YOUR_DOMAIN</string>\n</dict>\n</plist>\nEOF\nfi\n\n# Copy to .app bundle\ncp \"$PLIST_PATH\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app\"\n\n";
23862386
};
23872387
C1B3B9ED2C24B699004A32A4 /* SwiftLint */ = {
23882388
isa = PBXShellScriptBuildPhase;
@@ -2420,7 +2420,7 @@
24202420
);
24212421
runOnlyForDeploymentPostprocessing = 0;
24222422
shellPath = /bin/sh;
2423-
shellScript = "AUTH0_PLIST=\"${SRCROOT}/Auth0.plist\"\n\nif [ -f $AUTH0_PLIST ]; then\n cp \"$AUTH0_PLIST\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app\"\nfi\n";
2423+
shellScript = "PLIST_PATH=\"${SRCROOT}/App/Auth0.plist\"\n\n# If missing, create a placeholder Auth0.plist so Xcode doesn't fail\nif [ ! -f \"$PLIST_PATH\" ]; then\n echo \"Auth0.plist not found. Creating placeholder.\"\n cat > \"$PLIST_PATH\" <<EOF\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>ClientId</key>\n <string>YOUR_CLIENT_ID</string>\n <key>Domain</key>\n <string>YOUR_DOMAIN</string>\n</dict>\n</plist>\nEOF\nfi\n\n# Copy to .app bundle\ncp \"$PLIST_PATH\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app\"\n\n\n";
24242424
};
24252425
/* End PBXShellScriptBuildPhase section */
24262426

@@ -3496,7 +3496,7 @@
34963496
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build";
34973497
INFOPLIST_FILE = Auth0/Info.plist;
34983498
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
3499-
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
3499+
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
35003500
LD_RUNPATH_SEARCH_PATHS = (
35013501
"$(inherited)",
35023502
"@executable_path/Frameworks",
@@ -3527,7 +3527,7 @@
35273527
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build";
35283528
INFOPLIST_FILE = Auth0/Info.plist;
35293529
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
3530-
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
3530+
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
35313531
LD_RUNPATH_SEARCH_PATHS = (
35323532
"$(inherited)",
35333533
"@executable_path/Frameworks",
@@ -3894,12 +3894,12 @@
38943894
CURRENT_PROJECT_VERSION = 1;
38953895
DEVELOPMENT_TEAM = 86WQXF56BC;
38963896
INFOPLIST_FILE = App/Info.plist;
3897-
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
3897+
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
38983898
LD_RUNPATH_SEARCH_PATHS = (
38993899
"$(inherited)",
39003900
"@executable_path/Frameworks",
39013901
);
3902-
PRODUCT_BUNDLE_IDENTIFIER = com.auth0.OAuth2;
3902+
PRODUCT_BUNDLE_IDENTIFIER = com.auth0.OAuth2.dev;
39033903
PRODUCT_NAME = "$(TARGET_NAME)";
39043904
PROVISIONING_PROFILE = "";
39053905
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -3917,12 +3917,12 @@
39173917
CURRENT_PROJECT_VERSION = 1;
39183918
DEVELOPMENT_TEAM = 86WQXF56BC;
39193919
INFOPLIST_FILE = App/Info.plist;
3920-
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
3920+
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
39213921
LD_RUNPATH_SEARCH_PATHS = (
39223922
"$(inherited)",
39233923
"@executable_path/Frameworks",
39243924
);
3925-
PRODUCT_BUNDLE_IDENTIFIER = com.auth0.OAuth2;
3925+
PRODUCT_BUNDLE_IDENTIFIER = com.auth0.OAuth2.dev;
39263926
PRODUCT_NAME = "$(TARGET_NAME)";
39273927
PROVISIONING_PROFILE_SPECIFIER = "";
39283928
SWIFT_VERSION = 5.0;

Auth0/Auth0Authentication.swift

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,34 @@ struct Auth0Authentication: Authentication {
7474
dpop: self.dpop)
7575
}
7676

77+
func enrollOTPMFA(mfaToken: String) -> Request<OTPMFAEnrollmentChallenge, AuthenticationError> {
78+
let url = URL(string: "mfa/associate", relativeTo: self.url)!
79+
80+
let payload: [String: Any] = [
81+
"authenticator_types": ["otp"]
82+
]
83+
84+
return Request(session: session, url: url,
85+
method: "POST",
86+
handle: authenticationDecodable,
87+
parameters: payload,
88+
headers: baseHeaders(accessToken: mfaToken, tokenType: "Bearer"),
89+
logger: logger,
90+
telemetry: telemetry)
91+
}
92+
93+
func listMFAAuthenticators(mfaToken: String) -> Request<[Authenticator], AuthenticationError> {
94+
let url = URL(string: "mfa/authenticators", relativeTo: self.url)!
95+
96+
return Request(session: session,
97+
url: url,
98+
method: "GET",
99+
handle: authenticationDecodable,
100+
headers: baseHeaders(accessToken: mfaToken, tokenType: "Bearer"),
101+
logger: self.logger,
102+
telemetry: self.telemetry)
103+
}
104+
77105
func login(withOTP otp: String, mfaToken: String) -> Request<Credentials, AuthenticationError> {
78106
let url = URL(string: "oauth/token", relativeTo: self.url)!
79107

@@ -588,3 +616,49 @@ private extension Auth0Authentication {
588616
}
589617

590618
}
619+
620+
621+
622+
public struct Authenticator: Decodable {
623+
624+
public let type: String
625+
public let oobChannel: String?
626+
public let id: String
627+
public let name: String?
628+
public let active: Bool
629+
// "authenticator_type": "oob",
630+
// "oob_channel": "sms", //
631+
// "id": "sms|dev_sEe99pcpN0xp0yOO",
632+
// "name": "+1123XXXXX", //
633+
// "active": true
634+
635+
636+
enum CodingKeys: String, CodingKey {
637+
case type = "authenticator_type"
638+
case oobChannel = "oob_channel"
639+
case name
640+
case active
641+
case id
642+
}
643+
}
644+
645+
646+
public struct OTPMFAEnrollmentChallenge: Decodable {
647+
public let authenticatorType: String
648+
public let secret: String
649+
public let barCodeURI: String
650+
public let recoveryCodes: [String]?
651+
652+
enum CodingKeys: String, CodingKey {
653+
case authenticatorType = "authenticator_type"
654+
case secret
655+
case barCodeURI = "barcode_uri"
656+
case recoveryCodes = "recovery_codes"
657+
}
658+
// {
659+
// "authenticator_type": "otp",
660+
// "secret": "EN...S",
661+
// "barcode_uri": "otpauth://totp/tenant:user?secret=...&issuer=tenant&algorithm=SHA1&digits=6&period=30",
662+
// "recovery_codes": [ "N3B...XC"]
663+
// }
664+
}

0 commit comments

Comments
 (0)