Skip to content

Commit 0015102

Browse files
committed
Merge branch 'master' into feat/agents.md
* master: feat: Add configurable biometric authentication policies for CredentialsManager (#1019) Support a new offering from Auth0 #1021 (#1024) Bump actions/checkout from 5 to 6 (#1023) chore: added snyk scan workflow (#1025)
2 parents 7455599 + 4d9fe7b commit 0015102

20 files changed

+721
-56
lines changed

.github/workflows/main.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434

3535
steps:
3636
- name: Checkout
37-
uses: actions/checkout@v5
37+
uses: actions/checkout@v6
3838

3939
- name: Set up environment
4040
uses: ./.github/actions/setup
@@ -70,7 +70,7 @@ jobs:
7070

7171
steps:
7272
- name: Checkout
73-
uses: actions/checkout@v5
73+
uses: actions/checkout@v6
7474

7575
- name: Set up environment
7676
uses: ./.github/actions/setup
@@ -92,7 +92,7 @@ jobs:
9292

9393
steps:
9494
- name: Checkout
95-
uses: actions/checkout@v5
95+
uses: actions/checkout@v6
9696

9797
- name: Set up environment
9898
uses: ./.github/actions/setup
@@ -109,7 +109,7 @@ jobs:
109109

110110
steps:
111111
- name: Checkout
112-
uses: actions/checkout@v5
112+
uses: actions/checkout@v6
113113

114114
- name: Install SwiftLint
115115
run: brew install swiftlint

.github/workflows/rl-scanner.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626

2727
steps:
2828
- name: Checkout code
29-
uses: actions/checkout@v5
29+
uses: actions/checkout@v6
3030
with:
3131
fetch-depth: 0
3232

.github/workflows/sca_scan.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: SCA
2+
3+
on:
4+
push:
5+
branches: ["master", "main", "**"]
6+
7+
jobs:
8+
snyk-cli:
9+
uses: auth0/devsecops-tooling/.github/workflows/sca-scan.yml@main
10+
with:
11+
additional-arguments: "--exclude=README.md,.jfrog"
12+
secrets: inherit

Auth0.xcodeproj/project.pbxproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
5B7EE48D20FCA0F400367724 /* Auth0.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F06DD851CC448C90011842B /* Auth0.framework */; };
3131
5B7EE48E20FCA0F400367724 /* Auth0.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = 5F06DD851CC448C90011842B /* Auth0.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3232
5B9262C01ECF0CA800F4F6D3 /* BioAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9262BF1ECF0CA800F4F6D3 /* BioAuthentication.swift */; };
33+
A4PF2C6UJAB9I6DXDJUTRG3B /* BiometricPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = KDG54I4VDN4TZII38JVUS4AP /* BiometricPolicy.swift */; };
3334
5B9262C31ECF0CC200F4F6D3 /* BioAuthenticationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9262C11ECF0CBA00F4F6D3 /* BioAuthenticationSpec.swift */; };
3435
5BE65DCA1F7270DE00CADD3B /* Auth0.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = 5F06DD781CC448B10011842B /* Auth0.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3536
5BEDE18A1EC21B040007300D /* CredentialsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEDE1891EC21B040007300D /* CredentialsManager.swift */; };
@@ -116,6 +117,7 @@
116117
5C41F6C8244F969600252548 /* ASProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B16D88C1F7141A0009476A5 /* ASProvider.swift */; };
117118
5C41F6CA244F96AE00252548 /* LoginTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C41F6A3244DC94E00252548 /* LoginTransaction.swift */; };
118119
5C41F6CB244F96E300252548 /* BioAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9262BF1ECF0CA800F4F6D3 /* BioAuthentication.swift */; };
120+
V17KI34D4PWP799FLD81GPVP /* BiometricPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = KDG54I4VDN4TZII38JVUS4AP /* BiometricPolicy.swift */; };
119121
5C41F6CC244F96F200252548 /* ClaimValidators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB41D7023D0BED200074024 /* ClaimValidators.swift */; };
120122
5C41F6CD244F96FD00252548 /* IDTokenSignatureValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB41D3D23D0BA2C00074024 /* IDTokenSignatureValidator.swift */; };
121123
5C41F6CE244F970500252548 /* IDTokenValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB41D3E23D0BA2C00074024 /* IDTokenValidator.swift */; };
@@ -502,6 +504,7 @@
502504
C1B3B9FB2C24B6D4004A32A4 /* ClaimValidators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB41D7023D0BED200074024 /* ClaimValidators.swift */; };
503505
C1B3B9FC2C24B6D4004A32A4 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1748731EF2D3A40060E653 /* Shared.swift */; };
504506
C1B3B9FD2C24B6D4004A32A4 /* BioAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9262BF1ECF0CA800F4F6D3 /* BioAuthentication.swift */; };
507+
2Z7FPPVSUUJRZC06V5RA8B2B /* BiometricPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = KDG54I4VDN4TZII38JVUS4AP /* BiometricPolicy.swift */; };
505508
C1B3B9FE2C24B6D4004A32A4 /* CredentialsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEDE1891EC21B040007300D /* CredentialsManager.swift */; };
506509
C1B3B9FF2C24B6D4004A32A4 /* CredentialsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C80980A275A7B8600DC0A76 /* CredentialsStorage.swift */; };
507510
C1B3BA002C24B6D4004A32A4 /* CredentialsManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5E93F81EC45C22002A37F9 /* CredentialsManagerError.swift */; };
@@ -855,6 +858,7 @@
855858
5B7EE45820FC9F3200367724 /* OAuth2TV.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OAuth2TV.app; sourceTree = BUILT_PRODUCTS_DIR; };
856859
5B7EE47920FCA0A100367724 /* OAuth2Mac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OAuth2Mac.app; sourceTree = BUILT_PRODUCTS_DIR; };
857860
5B9262BF1ECF0CA800F4F6D3 /* BioAuthentication.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BioAuthentication.swift; sourceTree = "<group>"; };
861+
KDG54I4VDN4TZII38JVUS4AP /* BiometricPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BiometricPolicy.swift; sourceTree = "<group>"; };
858862
5B9262C11ECF0CBA00F4F6D3 /* BioAuthenticationSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BioAuthenticationSpec.swift; path = Auth0Tests/BioAuthenticationSpec.swift; sourceTree = SOURCE_ROOT; };
859863
5BA58D33209081A700782DD1 /* Cartfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile; sourceTree = "<group>"; };
860864
5BEDE1891EC21B040007300D /* CredentialsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialsManager.swift; sourceTree = "<group>"; };
@@ -1038,6 +1042,7 @@
10381042
D4EDCFBF2E740295008E02F8 /* PreferredAuthenticationMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferredAuthenticationMethod.swift; sourceTree = "<group>"; };
10391043
D581CF762757D773007327D1 /* RequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestSpec.swift; sourceTree = "<group>"; };
10401044
D5E9E316273ACCA5000CDB0A /* ChallengeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChallengeGenerator.swift; sourceTree = "<group>"; };
1045+
C177D6C22C2ADDEB0094C657 /* Auth0.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Auth0.plist; path = ../Auth0.plist; sourceTree = "<group>"; };
10411046
/* End PBXFileReference section */
10421047

10431048
/* Begin PBXFrameworksBuildPhase section */
@@ -1176,6 +1181,7 @@
11761181
5CB41D3B23D0BA0300074024 /* Validators */,
11771182
5B1748731EF2D3A40060E653 /* Shared.swift */,
11781183
5B9262BF1ECF0CA800F4F6D3 /* BioAuthentication.swift */,
1184+
KDG54I4VDN4TZII38JVUS4AP /* BiometricPolicy.swift */,
11791185
5BEDE1891EC21B040007300D /* CredentialsManager.swift */,
11801186
5C80980A275A7B8600DC0A76 /* CredentialsStorage.swift */,
11811187
5B5E93F81EC45C22002A37F9 /* CredentialsManagerError.swift */,
@@ -2435,6 +2441,7 @@
24352441
5FE2F8B21CCEAED8003628F4 /* Requestable.swift in Sources */,
24362442
5C4F551E23C8FB8E00C89615 /* Array+Encode.swift in Sources */,
24372443
5B9262C01ECF0CA800F4F6D3 /* BioAuthentication.swift in Sources */,
2444+
A4PF2C6UJAB9I6DXDJUTRG3B /* BiometricPolicy.swift in Sources */,
24382445
5CB41D7123D0BED200074024 /* ClaimValidators.swift in Sources */,
24392446
5FADB60C1CED7E0800D4BB50 /* UserPatchAttributes.swift in Sources */,
24402447
5FCAB1791D09124D00331C84 /* NSURL+Auth0.swift in Sources */,
@@ -2533,6 +2540,7 @@
25332540
5C505FAF2E216677005D0757 /* DPoPError.swift in Sources */,
25342541
5F74CB411CEFD5E600226823 /* JSONObjectPayload.swift in Sources */,
25352542
5C41F6CB244F96E300252548 /* BioAuthentication.swift in Sources */,
2543+
V17KI34D4PWP799FLD81GPVP /* BiometricPolicy.swift in Sources */,
25362544
5C41F6C8244F969600252548 /* ASProvider.swift in Sources */,
25372545
5C38EA242DA4611B0085AC31 /* MyAccount.swift in Sources */,
25382546
5C41F6DF244FA1EE00252548 /* NSURLComponents+OAuth2.swift in Sources */,
@@ -2923,6 +2931,7 @@
29232931
C1B3B9FB2C24B6D4004A32A4 /* ClaimValidators.swift in Sources */,
29242932
C1B3B9FC2C24B6D4004A32A4 /* Shared.swift in Sources */,
29252933
C1B3B9FD2C24B6D4004A32A4 /* BioAuthentication.swift in Sources */,
2934+
2Z7FPPVSUUJRZC06V5RA8B2B /* BiometricPolicy.swift in Sources */,
29262935
5C505FAE2E216677005D0757 /* DPoPError.swift in Sources */,
29272936
C1B3B9FE2C24B6D4004A32A4 /* CredentialsManager.swift in Sources */,
29282937
C1B3B9FF2C24B6D4004A32A4 /* CredentialsStorage.swift in Sources */,

Auth0/APICredentials.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,7 @@ extension APICredentials: Codable {
8787

8888
}
8989

90-
// MARK: - Internal Initializer
91-
92-
extension APICredentials {
90+
public extension APICredentials {
9391

9492
init(from credentials: Credentials) {
9593
self.accessToken = credentials.accessToken

Auth0/AuthenticationError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ public struct AuthenticationError: Auth0APIError, @unchecked Sendable {
142142

143143
// MARK: - Error Messages
144144

145-
extension AuthenticationError {
145+
public extension AuthenticationError {
146146

147147
var message: String {
148148
if let description = self.info[apiErrorDescription] as? String ?? self.info["error_description"] as? String {

Auth0/BioAuthentication.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ struct BioAuthentication {
88
private let evaluationPolicy: LAPolicy
99

1010
let title: String
11+
let policy: BiometricPolicy
12+
1113
var fallbackTitle: String? {
1214
get { return self.authContext.localizedFallbackTitle }
1315
set { self.authContext.localizedFallbackTitle = newValue }
@@ -22,10 +24,17 @@ struct BioAuthentication {
2224
return self.authContext.canEvaluatePolicy(evaluationPolicy, error: nil)
2325
}
2426

25-
init(authContext: LAContext, evaluationPolicy: LAPolicy, title: String, cancelTitle: String? = nil, fallbackTitle: String? = nil) {
27+
init(authContext: LAContext,
28+
evaluationPolicy: LAPolicy,
29+
title: String,
30+
cancelTitle: String? = nil,
31+
fallbackTitle: String? = nil,
32+
policy: BiometricPolicy = .always
33+
) {
2634
self.authContext = authContext
2735
self.evaluationPolicy = evaluationPolicy
2836
self.title = title
37+
self.policy = policy
2938
self.cancelTitle = cancelTitle
3039
self.fallbackTitle = fallbackTitle
3140
}

Auth0/BiometricPolicy.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#if WEB_AUTH_PLATFORM
2+
import Foundation
3+
4+
/// Defines the policy for when a biometric prompt should be shown when using the Credentials Manager.
5+
public enum BiometricPolicy {
6+
7+
/// Default behavior. A biometric prompt will be shown for every call to `credentials()`.
8+
case always
9+
10+
/// A biometric prompt will be shown only once within the specified timeout period.
11+
/// - Parameter timeoutInSeconds: The duration for which the session remains valid.
12+
case session(timeoutInSeconds: Int)
13+
14+
/// A biometric prompt will be shown only once while the app is in the foreground.
15+
/// The session is invalidated by calling `clearBiometricSession()` or after the default timeout.
16+
/// - Parameter timeoutInSeconds: The duration for which the session remains valid. Defaults to 3600 seconds (1 hour).
17+
case appLifecycle(timeoutInSeconds: Int = 3600)
18+
}
19+
#endif

Auth0/CredentialsManager.swift

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ public struct CredentialsManager {
3232
private let dispatchQueue = DispatchQueue(label: "com.auth0.credentialsmanager.serial")
3333
#if WEB_AUTH_PLATFORM
3434
var bioAuth: BioAuthentication?
35+
// Biometric session management - using a class to allow mutation in non-mutating methods
36+
private final class BiometricSession {
37+
let noSession: TimeInterval = -1
38+
var lastBiometricAuthTime: TimeInterval = -1
39+
let lock = NSLock()
40+
41+
init() {
42+
lastBiometricAuthTime = noSession
43+
}
44+
}
45+
private let biometricSession = BiometricSession()
3546
#endif
3647

3748
/// Creates a new `CredentialsManager` instance.
@@ -82,17 +93,27 @@ public struct CredentialsManager {
8293
/// evaluationPolicy: .deviceOwnerAuthentication)
8394
/// ```
8495
///
96+
/// You can also configure a ``BiometricPolicy`` to control when biometric authentication is required:
97+
///
98+
/// ```swift
99+
/// // Require authentication only once per 5-minute session
100+
/// credentialsManager.enableBiometrics(withTitle: "Unlock with Face ID",
101+
/// policy: .session(timeoutInSeconds: 300))
102+
/// ```
103+
///
85104
/// - Parameters:
86105
/// - title: Main message to display when Face ID or Touch ID is used.
87106
/// - cancelTitle: Cancel message to display when Face ID or Touch ID is used.
88107
/// - fallbackTitle: Fallback message to display when Face ID or Touch ID is used after a failed match.
89108
/// - evaluationPolicy: Policy to be used for authentication policy evaluation.
109+
/// - policy: The ``BiometricPolicy`` that controls when biometric authentication is required. Defaults to `.always`.
90110
/// - Important: Access to the ``user`` property will not be protected by biometric authentication.
91111
public mutating func enableBiometrics(withTitle title: String,
92112
cancelTitle: String? = nil,
93113
fallbackTitle: String? = nil,
94-
evaluationPolicy: LAPolicy = .deviceOwnerAuthenticationWithBiometrics) {
95-
self.bioAuth = BioAuthentication(authContext: LAContext(), evaluationPolicy: evaluationPolicy, title: title, cancelTitle: cancelTitle, fallbackTitle: fallbackTitle)
114+
evaluationPolicy: LAPolicy = .deviceOwnerAuthenticationWithBiometrics,
115+
policy: BiometricPolicy = .always) {
116+
self.bioAuth = BioAuthentication(authContext: LAContext(), evaluationPolicy: evaluationPolicy, title: title, cancelTitle: cancelTitle, fallbackTitle: fallbackTitle, policy: policy)
96117
}
97118
#endif
98119

@@ -125,6 +146,11 @@ public struct CredentialsManager {
125146
///
126147
/// - Returns: If the credentials were removed.
127148
public func clear() -> Bool {
149+
#if WEB_AUTH_PLATFORM
150+
self.biometricSession.lock.lock()
151+
self.biometricSession.lastBiometricAuthTime = self.biometricSession.noSession
152+
self.biometricSession.lock.unlock()
153+
#endif
128154
return self.storage.deleteEntry(forKey: self.storeKey)
129155
}
130156

@@ -142,6 +168,49 @@ public struct CredentialsManager {
142168
return self.storage.deleteEntry(forKey: audience)
143169
}
144170

171+
#if WEB_AUTH_PLATFORM
172+
/// Checks if the current biometric session is valid based on the configured policy.
173+
///
174+
/// ## Usage
175+
///
176+
/// ```swift
177+
/// let isValid = credentialsManager.isBiometricSessionValid()
178+
/// ```
179+
///
180+
/// - Returns: `true` if the session is valid and biometric authentication can be skipped, `false` otherwise.
181+
public func isBiometricSessionValid() -> Bool {
182+
guard let bioAuth = self.bioAuth else { return false }
183+
184+
self.biometricSession.lock.lock()
185+
defer { self.biometricSession.lock.unlock() }
186+
187+
let lastAuth = self.biometricSession.lastBiometricAuthTime
188+
if lastAuth == self.biometricSession.noSession { return false }
189+
190+
switch bioAuth.policy {
191+
case .session(let timeoutInSeconds), .appLifecycle(let timeoutInSeconds):
192+
let timeoutInterval = TimeInterval(timeoutInSeconds)
193+
return Date().timeIntervalSince1970 - lastAuth < timeoutInterval
194+
case .always:
195+
return false
196+
}
197+
}
198+
199+
/// Clears the in-memory biometric session timestamp. This will force biometric authentication on the next
200+
/// credential access.
201+
///
202+
/// ## Usage
203+
///
204+
/// ```swift
205+
/// credentialsManager.clearBiometricSession()
206+
/// ```
207+
public func clearBiometricSession() {
208+
self.biometricSession.lock.lock()
209+
defer { self.biometricSession.lock.unlock() }
210+
self.biometricSession.lastBiometricAuthTime = self.biometricSession.noSession
211+
}
212+
#endif
213+
145214
/// Calls the `/oauth/revoke` endpoint to revoke the refresh token and then clears the credentials if the request
146215
/// was successful. Otherwise, the credentials will not be cleared and the callback will be called with a failure
147216
/// result containing a ``CredentialsManagerError/revokeFailed`` error.
@@ -314,11 +383,26 @@ public struct CredentialsManager {
314383
return callback(.failure(error))
315384
}
316385

386+
// Check if biometric session is valid based on policy
387+
if self.isBiometricSessionValid() {
388+
// Session is valid, bypass biometric prompt
389+
self.retrieveCredentials(scope: scope,
390+
minTTL: minTTL,
391+
parameters: parameters,
392+
headers: headers,
393+
forceRenewal: false,
394+
callback: callback)
395+
return
396+
}
397+
317398
bioAuth.validateBiometric { error in
318399
guard error == nil else {
319400
return callback(.failure(CredentialsManagerError(code: .biometricsFailed, cause: error!)))
320401
}
321402

403+
// Update biometric session after successful authentication (only for session-based policies)
404+
self.updateBiometricSession(for: bioAuth.policy)
405+
322406
self.retrieveCredentials(scope: scope,
323407
minTTL: minTTL,
324408
parameters: parameters,
@@ -610,7 +694,7 @@ public struct CredentialsManager {
610694
callback: callback)
611695
}
612696

613-
func store(apiCredentials: APICredentials, forAudience audience: String) -> Bool {
697+
public func store(apiCredentials: APICredentials, forAudience audience: String) -> Bool {
614698
guard let data = try? apiCredentials.encode() else {
615699
return false
616700
}
@@ -1504,5 +1588,21 @@ public extension CredentialsManager {
15041588
}
15051589
}
15061590

1591+
#if WEB_AUTH_PLATFORM
1592+
/// Updates the biometric session timestamp to the current time.
1593+
/// Only updates for session-based policies (Session and AppLifecycle).
1594+
private func updateBiometricSession(for policy: BiometricPolicy) {
1595+
// Don't update session for "Always" policy
1596+
switch policy {
1597+
case .always:
1598+
return
1599+
case .session, .appLifecycle:
1600+
self.biometricSession.lock.lock()
1601+
defer { self.biometricSession.lock.unlock() }
1602+
self.biometricSession.lastBiometricAuthTime = Date().timeIntervalSince1970
1603+
}
1604+
}
1605+
#endif
1606+
15071607
}
15081608
#endif

Auth0/CredentialsManagerError.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,10 @@ public struct CredentialsManagerError: Auth0Error, Sendable {
7070
/// increase the **Token Expiration** value in the settings page of your [Auth0 API](https://manage.auth0.com/#/apis/).
7171
/// This error does not include a ``Auth0Error/cause-9wuyi``.
7272
public static let largeMinTTL: CredentialsManagerError = .init(code: .largeMinTTL(minTTL: 0, lifetime: 0))
73-
7473
}
7574

7675
// MARK: - Error Messages
77-
78-
extension CredentialsManagerError {
76+
public extension CredentialsManagerError {
7977

8078
var message: String {
8179
switch self.code {

0 commit comments

Comments
 (0)