Skip to content

Commit 3897113

Browse files
authored
Merge branch 'master' into dependabot/github_actions/dot-github/actions/setup/ruby/setup-ruby-1.269.0
2 parents 90899b2 + aa4dcd8 commit 3897113

File tree

10 files changed

+630
-39
lines changed

10 files changed

+630
-39
lines changed

Auth0.xcodeproj/project.pbxproj

Lines changed: 48 additions & 4 deletions
Large diffs are not rendered by default.

Auth0/Auth0.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,13 +219,13 @@ public func webAuth(clientId: String, domain: String, session: URLSession = .sha
219219
func plistValues(bundle: Bundle) -> (clientId: String, domain: String)? {
220220
guard let path = bundle.path(forResource: "Auth0", ofType: "plist"),
221221
let values = NSDictionary(contentsOfFile: path) as? [String: Any] else {
222-
print("Missing Auth0.plist file with 'ClientId' and 'Domain' entries in main bundle!")
222+
Auth0Log.error(.configuration, "Missing Auth0.plist file with 'ClientId' and 'Domain' entries in main bundle!")
223223
return nil
224224
}
225225

226226
guard let clientId = values["ClientId"] as? String, let domain = values["Domain"] as? String else {
227-
print("Auth0.plist file at \(path) is missing 'ClientId' and/or 'Domain' entries!")
228-
print("File currently has the following entries: \(values)")
227+
Auth0Log.error(.configuration, "Auth0.plist file at \(path) is missing 'ClientId' and/or 'Domain' entries!")
228+
Auth0Log.debug(.configuration, "File currently has the following entries: \(String(describing: values))")
229229
return nil
230230
}
231231
return (clientId: clientId, domain: domain)

Auth0/Auth0Log.swift

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import Foundation
2+
import os.log
3+
4+
// MARK: - Unified Logging System
5+
6+
/// Represents different categories of logs within the SDK.
7+
enum LogCategory: String {
8+
/// networkTracing category is used for the network request and response traces
9+
case networkTracing = "NetworkTracing"
10+
/// configuration category is used for SDK configuration related logs
11+
case configuration = "Configuration"
12+
}
13+
14+
/// Log levels supported by the unified logging system.
15+
enum LogLevel {
16+
/// Detailed information for debugging
17+
case debug
18+
/// General informational messages
19+
case info
20+
/// Warning messages for potentially problematic situations
21+
case warning
22+
/// Error messages for failures
23+
case error
24+
/// Critical system errors
25+
case fault
26+
}
27+
28+
/// Unified logging interface for the SDK.
29+
/// Provides a consistent, type-safe API for logging across all SDK components.
30+
enum Auth0Log {
31+
32+
/// Shared logging service instance (can be replaced for testing).
33+
static var loggingService: UnifiedLogging = OSUnifiedLoggingService()
34+
35+
/// Centralized subsystem identifier for all Auth0 logs.
36+
static var subsystem: String { OSUnifiedLoggingService.subsystem }
37+
38+
/// Log a message with the specified category and level.
39+
///
40+
/// Use this method for fine-grained control over logging. For common use cases,
41+
/// prefer the convenience methods: `debug()`, `info()`, `warning()`, `error()`, `fault()`.
42+
///
43+
/// - Parameters:
44+
/// - category: The log category for filtering and organization. See `LogCategory` for available categories.
45+
/// - level: The severity level of the log. Defaults to `.debug`. See `LogLevel` for available levels.
46+
/// - message: The message to log. Evaluated lazily via autoclosure for performance.
47+
static func log(
48+
_ category: LogCategory,
49+
level: LogLevel = .debug,
50+
message: String
51+
) {
52+
loggingService.log(category, level: level, message: message)
53+
}
54+
55+
/// Log a debug message.
56+
///
57+
/// Use for detailed information useful during development and debugging.
58+
///
59+
/// - Parameters:
60+
/// - category: The log category for filtering
61+
/// - message: The message to log
62+
static func debug(
63+
_ category: LogCategory,
64+
_ message: String
65+
) {
66+
log(category, level: .debug, message: message)
67+
}
68+
69+
/// Log an informational message.
70+
///
71+
/// Use for general informational messages about application state.
72+
///
73+
/// - Parameters:
74+
/// - category: The log category for filtering
75+
/// - message: The message to log
76+
static func info(
77+
_ category: LogCategory,
78+
_ message: String
79+
) {
80+
log(category, level: .info, message: message)
81+
}
82+
83+
/// Log a warning message.
84+
///
85+
/// Use for potentially problematic situations that aren't errors.
86+
///
87+
/// - Parameters:
88+
/// - category: The log category for filtering
89+
/// - message: The message to log
90+
static func warning(
91+
_ category: LogCategory,
92+
_ message: String
93+
) {
94+
log(category, level: .warning, message: message)
95+
}
96+
97+
/// Log an error message.
98+
///
99+
/// Use for error conditions and failures that need attention.
100+
///
101+
/// - Parameters:
102+
/// - category: The log category for filtering
103+
/// - message: The message to log
104+
static func error(
105+
_ category: LogCategory,
106+
_ message: String
107+
) {
108+
log(category, level: .error, message: message)
109+
}
110+
111+
/// Log a fault message for critical system errors.
112+
///
113+
/// Use for serious errors that may cause data loss or application instability.
114+
///
115+
/// - Parameters:
116+
/// - category: The log category for filtering
117+
/// - message: The message to log
118+
static func fault(
119+
_ category: LogCategory,
120+
_ message: @autoclosure () -> String
121+
) {
122+
log(category, level: .fault, message: message())
123+
}
124+
}

Auth0/Logger.swift

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,21 @@ public protocol Logger {
1414

1515
}
1616

17+
private let networkTraceQueue = DispatchQueue(label: "com.auth0.networkTrace", qos: .utility)
18+
1719
protocol LoggerOutput {
1820
func log(message: String)
1921
func newLine()
2022
}
2123

2224
struct DefaultOutput: LoggerOutput {
23-
func log(message: String) { print(message) }
24-
func newLine() { print() }
25+
func log(message: String) {
26+
Auth0Log.debug(.networkTracing, message)
27+
}
28+
29+
func newLine() {
30+
Auth0Log.debug(.networkTracing, "")
31+
}
2532
}
2633

2734
struct DefaultLogger: Logger {
@@ -33,31 +40,37 @@ struct DefaultLogger: Logger {
3340
}
3441

3542
func trace(request: URLRequest, session: URLSession) {
36-
guard let method = request.httpMethod, let url = request.url?.absoluteString else { return }
37-
output.log(message: "\(method) \(url) HTTP/1.1")
38-
session.configuration.httpAdditionalHeaders?.forEach { key, value in output.log(message: "\(key): \(value)") }
39-
request.allHTTPHeaderFields?.forEach { key, value in output.log(message: "\(key): \(value)") }
40-
if let data = request.httpBody, let string = String(data: data, encoding: .utf8) {
43+
networkTraceQueue.async { [output] in
44+
guard let method = request.httpMethod, let url = request.url?.absoluteString else { return }
45+
output.log(message: "\(method) \(url) HTTP/1.1")
46+
session.configuration.httpAdditionalHeaders?.forEach { key, value in output.log(message: "\(key): \(value)") }
47+
request.allHTTPHeaderFields?.forEach { key, value in output.log(message: "\(key): \(value)") }
48+
if let data = request.httpBody, let string = String(data: data, encoding: .utf8) {
49+
output.newLine()
50+
output.log(message: string)
51+
}
4152
output.newLine()
42-
output.log(message: string)
4353
}
44-
output.newLine()
4554
}
4655

4756
func trace(response: URLResponse, data: Data?) {
48-
if let http = response as? HTTPURLResponse {
49-
output.log(message: "HTTP/1.1 \(http.statusCode)")
50-
http.allHeaderFields.forEach { key, value in output.log(message: "\(key): \(value)") }
51-
if let data = data, let string = String(data: data, encoding: .utf8) {
57+
networkTraceQueue.async { [output] in
58+
if let http = response as? HTTPURLResponse {
59+
output.log(message: "HTTP/1.1 \(http.statusCode)")
60+
http.allHeaderFields.forEach { key, value in output.log(message: "\(key): \(value)") }
61+
if let data = data, let string = SensitiveDataRedactor.redact(data) {
62+
output.newLine()
63+
output.log(message: "API Response: \(string)")
64+
}
5265
output.newLine()
53-
output.log(message: string)
5466
}
55-
output.newLine()
5667
}
5768
}
5869

5970
func trace(url: URL, source: String?) {
60-
output.log(message: "\(source ?? "URL"): \(url.absoluteString)")
71+
networkTraceQueue.async { [output] in
72+
output.log(message: "\(source ?? "URL"): \(url.absoluteString)")
73+
}
6174
}
6275

6376
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import Foundation
2+
import os.log
3+
4+
// Avoid naming conflict with Auth0Logger protocol
5+
typealias OSLogger = os.Logger
6+
7+
/// Protocol for unified logging operations.
8+
protocol UnifiedLogging {
9+
/// Log a message with the specified parameters.
10+
/// - Parameters:
11+
/// - category: The log category for filtering (e.g., `.networkTracing`, `.configuration`)
12+
/// - level: The severity level (`.debug`, `.info`, `.warning`, `.error`, `.fault`)
13+
/// - message: The message to log (lazy-evaluated via autoclosure)
14+
func log(
15+
_ category: LogCategory,
16+
level: LogLevel,
17+
message: @autoclosure () -> String
18+
)
19+
}
20+
21+
/// Production OSLog-based logging implementation.
22+
struct OSUnifiedLoggingService: UnifiedLogging {
23+
24+
/// Internal marker class used to identify the SDK bundle.
25+
private final class BundleMarker {}
26+
27+
/// Centralized subsystem identifier for all Auth0 logs.
28+
static let subsystem = Bundle(for: BundleMarker.self).bundleIdentifier ?? "com.auth0.Auth0"
29+
30+
/// Cached loggers for each category to avoid repeated allocations.
31+
private static let loggers: [LogCategory: OSLogger] = {
32+
var loggers: [LogCategory: OSLogger] = [:]
33+
loggers[.networkTracing] = OSLogger(subsystem: subsystem, category: LogCategory.networkTracing.rawValue)
34+
loggers[.configuration] = OSLogger(subsystem: subsystem, category: LogCategory.configuration.rawValue)
35+
return loggers
36+
}()
37+
38+
/// Logs a message with privacy redaction.
39+
///
40+
/// All messages use `.private` privacy level, which means:
41+
/// - **Debug/Info**: Never persisted to disk. Only visible during active debugging in Xcode Console.
42+
/// - **Warning/Error/Fault**: Persisted to system logs but entire message is redacted as `<private>`
43+
/// when exported or viewed without debugger attached. Only visible unredacted during active debugging.
44+
45+
func log(
46+
_ category: LogCategory,
47+
level: LogLevel,
48+
message: @autoclosure () -> String
49+
) {
50+
guard let logger = Self.loggers[category] else { return }
51+
52+
let messageString = message()
53+
54+
switch level {
55+
case .debug:
56+
logger.debug("\(messageString, privacy: .private)")
57+
case .info:
58+
logger.info("\(messageString, privacy: .private)")
59+
case .warning:
60+
logger.warning("\(messageString, privacy: .private)")
61+
case .error:
62+
logger.error("\(messageString, privacy: .private)")
63+
case .fault:
64+
logger.fault("\(messageString, privacy: .private)")
65+
}
66+
}
67+
}

Auth0/Request.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,7 @@ public struct Request<T, E: Auth0APIError>: Requestable {
121121
} else {
122122
handle(.failure(error), callback)
123123
}
124-
print(error)
125124
} catch {
126-
print(error)
127125
handle(.failure(E(cause: error)), callback)
128126
}
129127
})

Auth0/SensitiveDataRedactor.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import Foundation
2+
3+
/// Utility for redacting sensitive data from JSON responses.
4+
struct SensitiveDataRedactor {
5+
6+
/// List of sensitive keys that should be redacted.
7+
private static let sensitiveKeys: Set<String> = ["access_token", "id_token", "refresh_token"]
8+
9+
/// Redacts sensitive fields from a JSON string in the response body.
10+
///
11+
/// This is an additional safety check - even when debugger is connected, we don't log
12+
/// access tokens, id tokens, and refresh tokens on Xcode console and live Console.app.
13+
///
14+
/// Note: These logs are only for debugging purposes and never persisted in production.
15+
///
16+
/// - Parameter data: The response data to redact.
17+
/// - Returns: A JSON string with sensitive fields replaced by `<REDACTED>` if valid JSON, otherwise returns the data decoded as a UTF-8 string, or `nil` if decoding fails.
18+
static func redact(_ data: Data) -> String? {
19+
do {
20+
// Attempt to parse as JSON
21+
var jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
22+
23+
// Redact any sensitive keys present
24+
sensitiveKeys.forEach { key in
25+
if jsonObject?[key] != nil {
26+
jsonObject?[key] = "<REDACTED>"
27+
}
28+
}
29+
30+
// Convert back to pretty-printed JSON string
31+
let redactedData = try JSONSerialization.data(withJSONObject: jsonObject ?? [:], options: [.prettyPrinted])
32+
return String(data: redactedData, encoding: .utf8) ?? "<REDACTED>"
33+
34+
} catch {
35+
// If not JSON, return try converting data to string
36+
return String(data: data, encoding: .utf8)
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)