diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index e35bb81..7d9a64c 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -26,7 +26,7 @@ jobs: "hostname": "${{ secrets.SQ_LITE_CLOUD_HOST }}", "username": "${{ secrets.SQ_LITE_CLOUD_USER }}", "password": "${{ secrets.SQ_LITE_CLOUD_PASS }}", - "apyKey": "${{ secrets.SQ_LITE_CLOUD_APIKEY }}" + "apiKey": "${{ secrets.SQ_LITE_CLOUD_APIKEY }}" } ' >> Tests/SQLiteCloudTests/Secrets/secrets.json diff --git a/Sources/SQLiteCloud/Core/SQLiteCloudConfig+Internal.swift b/Sources/SQLiteCloud/Core/SQLiteCloudConfig+Internal.swift index 8e5c2c5..eda3e2b 100644 --- a/Sources/SQLiteCloud/Core/SQLiteCloudConfig+Internal.swift +++ b/Sources/SQLiteCloud/Core/SQLiteCloudConfig+Internal.swift @@ -32,8 +32,14 @@ extension SQLiteCloudConfig { var sqConfig: SQCloudConfig { var sqConfig = SQCloudConfig() sqConfig.family = family.rawValue - sqConfig.username = (username as NSString).utf8String - sqConfig.password = (password as NSString).utf8String + if let username, + let password { + sqConfig.username = (username as NSString).utf8String + sqConfig.password = (password as NSString).utf8String + } + if let apiKey { + sqConfig.api_key = (apiKey as NSString).utf8String + } sqConfig.password_hashed = passwordHashed sqConfig.non_linearizable = nonlinearizable sqConfig.timeout = Int32(timeout) diff --git a/Sources/SQLiteCloud/Core/SQLiteCloudConfig.swift b/Sources/SQLiteCloud/Core/SQLiteCloudConfig.swift index b49d298..2b8e61f 100644 --- a/Sources/SQLiteCloud/Core/SQLiteCloudConfig.swift +++ b/Sources/SQLiteCloud/Core/SQLiteCloudConfig.swift @@ -28,8 +28,9 @@ import Foundation public struct SQLiteCloudConfig: Sendable { public let hostname: String public let port: Port - public let username: String - public let password: String + public let username: String? + public let password: String? + public let apiKey: String? public let family: Family public let passwordHashed: Bool public let nonlinearizable: Bool @@ -49,6 +50,52 @@ public struct SQLiteCloudConfig: Sendable { public let clientCertificate: String? public let clientCertificateKey: String? + public init(hostname: String, + apiKey: String, + port: Port = .default, + family: Family = .ipv4, + passwordHashed: Bool = false, + nonlinearizable: Bool = false, + timeout: Int = 0, + compression: Bool = false, + zerotext: Bool = false, + memory: Bool = false, + dbCreate: Bool = false, + insecure: Bool = false, + noblob: Bool = false, + isReadonlyConnection: Bool = false, + maxData: Int = 0, + maxRows: Int = 0, + maxRowset: Int = 0, + dbname: String? = nil, + rootCertificate: String? = nil, + clientCertificate: String? = nil, + clientCertificateKey: String? = nil) { + self.init(hostname: hostname, + username: nil, + password: nil, + apiKey: apiKey, + port: port, + family: family, + passwordHashed: passwordHashed, + nonlinearizable: nonlinearizable, + timeout: timeout, + compression: compression, + zerotext: zerotext, + memory: memory, + dbCreate: dbCreate, + insecure: insecure, + noblob: noblob, + isReadonlyConnection: isReadonlyConnection, + maxData: maxData, + maxRows: maxRows, + maxRowset: maxRowset, + dbname: dbname, + rootCertificate: rootCertificate, + clientCertificate: clientCertificate, + clientCertificateKey: clientCertificateKey) + } + public init(hostname: String, username: String, password: String, @@ -71,10 +118,59 @@ public struct SQLiteCloudConfig: Sendable { rootCertificate: String? = nil, clientCertificate: String? = nil, clientCertificateKey: String? = nil) { + self.init(hostname: hostname, + username: username, + password: password, + apiKey: nil, + port: port, + family: family, + passwordHashed: passwordHashed, + nonlinearizable: nonlinearizable, + timeout: timeout, + compression: compression, + zerotext: zerotext, + memory: memory, + dbCreate: dbCreate, + insecure: insecure, + noblob: noblob, + isReadonlyConnection: isReadonlyConnection, + maxData: maxData, + maxRows: maxRows, + maxRowset: maxRowset, + dbname: dbname, + rootCertificate: rootCertificate, + clientCertificate: clientCertificate, + clientCertificateKey: clientCertificateKey) + } + + private init(hostname: String, + username: String?, + password: String?, + apiKey: String?, + port: Port = .default, + family: Family = .ipv4, + passwordHashed: Bool = false, + nonlinearizable: Bool = false, + timeout: Int = 0, + compression: Bool = false, + zerotext: Bool = false, + memory: Bool = false, + dbCreate: Bool = false, + insecure: Bool = false, + noblob: Bool = false, + isReadonlyConnection: Bool = false, + maxData: Int = 0, + maxRows: Int = 0, + maxRowset: Int = 0, + dbname: String? = nil, + rootCertificate: String? = nil, + clientCertificate: String? = nil, + clientCertificateKey: String? = nil) { self.hostname = hostname self.port = port self.username = username self.password = password + self.apiKey = apiKey self.family = family self.passwordHashed = passwordHashed self.nonlinearizable = nonlinearizable @@ -94,7 +190,7 @@ public struct SQLiteCloudConfig: Sendable { self.clientCertificate = clientCertificate self.clientCertificateKey = clientCertificateKey } - + public init?(connectionString: String) { guard let url = URL(string: connectionString) else { return nil } @@ -103,22 +199,38 @@ public struct SQLiteCloudConfig: Sendable { /// sqlitecloud://user:pass@host.com:port/dbname?timeout=10&key2=value2&key3=value3. public init?(connectionURL: URL) { - guard let username = connectionURL.user else { return nil } - guard let password = connectionURL.password else { return nil } guard let hostname = connectionURL.host else { return nil } - let port = connectionURL.port.map { Port.custom(portNumber: $0) } ?? .default - + let urlComponents = URLComponents(string: connectionURL.absoluteString) let queryItems = urlComponents?.queryItems - + + // There are 2 kind of possibile credentials types + // - based on an apikey + // - based on the username and the password combo + // We need to search for this credential info in the connection string. + // First we check for apikey, if fails we fallback on the user/pass combo. + + if let apiKey = UrlParser.parse(items: queryItems, name: "apikey") { + self.username = nil + self.password = nil + self.apiKey = apiKey + } else { + guard let username = connectionURL.user else { return nil } + guard let password = connectionURL.password else { return nil } + + self.username = username + self.password = password + self.apiKey = nil + } + + let port = connectionURL.port.map { Port.custom(portNumber: $0) } ?? .default + // external self.hostname = hostname self.port = port self.isReadonlyConnection = UrlParser.parse(items: queryItems, name: "readonly") // in config - self.username = username - self.password = password self.dbname = urlComponents?.path.replacingOccurrences(of: "/", with: "") self.family = Family(rawValue: UrlParser.parse(items: queryItems, name: "family")) ?? .ipv4 self.passwordHashed = UrlParser.parse(items: queryItems, name: "passwordHashed") @@ -143,7 +255,11 @@ public struct SQLiteCloudConfig: Sendable { extension SQLiteCloudConfig { var connectionString: String { - "sqlitecloud://\(username):****@\(hostname):\(port.number)/\(dbname ?? .empty)" + if let apiKey { + "sqlitecloud://\(hostname):\(port.number)/\(dbname ?? .empty)?apikey=\(apiKey)" + } else { + "sqlitecloud://\(username ?? ""):****@\(hostname):\(port.number)/\(dbname ?? .empty)" + } } } diff --git a/Sources/libsqcloud.xcframework/Info.plist b/Sources/libsqcloud.xcframework/Info.plist index 33539da..a0032c8 100644 --- a/Sources/libsqcloud.xcframework/Info.plist +++ b/Sources/libsqcloud.xcframework/Info.plist @@ -10,18 +10,15 @@ HeadersPath Headers LibraryIdentifier - ios-arm64_x86_64-simulator + ios-arm64 LibraryPath libsqcloud.a SupportedArchitectures arm64 - x86_64 SupportedPlatform ios - SupportedPlatformVariant - simulator BinaryPath @@ -29,15 +26,16 @@ HeadersPath Headers LibraryIdentifier - ios-arm64 + macos-arm64_x86_64 LibraryPath libsqcloud.a SupportedArchitectures arm64 + x86_64 SupportedPlatform - ios + macos BinaryPath @@ -64,7 +62,7 @@ HeadersPath Headers LibraryIdentifier - macos-arm64_x86_64 + ios-arm64_x86_64-simulator LibraryPath libsqcloud.a SupportedArchitectures @@ -73,7 +71,9 @@ x86_64 SupportedPlatform - macos + ios + SupportedPlatformVariant + simulator CFBundlePackageType diff --git a/Sources/libsqcloud.xcframework/ios-arm64/libsqcloud.a b/Sources/libsqcloud.xcframework/ios-arm64/libsqcloud.a index b2ab4f2..7246852 100644 Binary files a/Sources/libsqcloud.xcframework/ios-arm64/libsqcloud.a and b/Sources/libsqcloud.xcframework/ios-arm64/libsqcloud.a differ diff --git a/Sources/libsqcloud.xcframework/ios-arm64_x86_64-maccatalyst/libsqcloud.a b/Sources/libsqcloud.xcframework/ios-arm64_x86_64-maccatalyst/libsqcloud.a index b761393..30cf6d4 100644 Binary files a/Sources/libsqcloud.xcframework/ios-arm64_x86_64-maccatalyst/libsqcloud.a and b/Sources/libsqcloud.xcframework/ios-arm64_x86_64-maccatalyst/libsqcloud.a differ diff --git a/Sources/libsqcloud.xcframework/ios-arm64_x86_64-simulator/libsqcloud.a b/Sources/libsqcloud.xcframework/ios-arm64_x86_64-simulator/libsqcloud.a index 0a61685..5968d13 100644 Binary files a/Sources/libsqcloud.xcframework/ios-arm64_x86_64-simulator/libsqcloud.a and b/Sources/libsqcloud.xcframework/ios-arm64_x86_64-simulator/libsqcloud.a differ diff --git a/Sources/libsqcloud.xcframework/macos-arm64_x86_64/libsqcloud.a b/Sources/libsqcloud.xcframework/macos-arm64_x86_64/libsqcloud.a index a008cc7..7a0f290 100644 Binary files a/Sources/libsqcloud.xcframework/macos-arm64_x86_64/libsqcloud.a and b/Sources/libsqcloud.xcframework/macos-arm64_x86_64/libsqcloud.a differ diff --git a/Sources/libtls.xcframework/Info.plist b/Sources/libtls.xcframework/Info.plist index fd21744..d97cdd3 100644 --- a/Sources/libtls.xcframework/Info.plist +++ b/Sources/libtls.xcframework/Info.plist @@ -29,15 +29,16 @@ HeadersPath Headers LibraryIdentifier - ios-arm64 + macos-arm64_x86_64 LibraryPath libtls.a SupportedArchitectures arm64 + x86_64 SupportedPlatform - ios + macos BinaryPath @@ -45,16 +46,15 @@ HeadersPath Headers LibraryIdentifier - macos-arm64_x86_64 + ios-arm64 LibraryPath libtls.a SupportedArchitectures arm64 - x86_64 SupportedPlatform - macos + ios BinaryPath diff --git a/Sources/libtls.xcframework/ios-arm64/libtls.a b/Sources/libtls.xcframework/ios-arm64/libtls.a index e6653a2..38af8e1 100644 Binary files a/Sources/libtls.xcframework/ios-arm64/libtls.a and b/Sources/libtls.xcframework/ios-arm64/libtls.a differ diff --git a/Sources/libtls.xcframework/ios-arm64_x86_64-maccatalyst/libtls.a b/Sources/libtls.xcframework/ios-arm64_x86_64-maccatalyst/libtls.a index 5baa5ff..05e5498 100644 Binary files a/Sources/libtls.xcframework/ios-arm64_x86_64-maccatalyst/libtls.a and b/Sources/libtls.xcframework/ios-arm64_x86_64-maccatalyst/libtls.a differ diff --git a/Sources/libtls.xcframework/ios-arm64_x86_64-simulator/libtls.a b/Sources/libtls.xcframework/ios-arm64_x86_64-simulator/libtls.a index 5c6b678..67d888b 100644 Binary files a/Sources/libtls.xcframework/ios-arm64_x86_64-simulator/libtls.a and b/Sources/libtls.xcframework/ios-arm64_x86_64-simulator/libtls.a differ diff --git a/Sources/libtls.xcframework/macos-arm64_x86_64/libtls.a b/Sources/libtls.xcframework/macos-arm64_x86_64/libtls.a index 460d5e5..f0e4422 100644 Binary files a/Sources/libtls.xcframework/macos-arm64_x86_64/libtls.a and b/Sources/libtls.xcframework/macos-arm64_x86_64/libtls.a differ diff --git a/Tests/SQLiteCloudTests/Integrations/SQLiteCloudTests+Blob.swift b/Tests/SQLiteCloudTests/Integrations/SQLiteCloudTests+Blob.swift index e0e17ff..a4aec56 100644 --- a/Tests/SQLiteCloudTests/Integrations/SQLiteCloudTests+Blob.swift +++ b/Tests/SQLiteCloudTests/Integrations/SQLiteCloudTests+Blob.swift @@ -40,7 +40,7 @@ final class SQLiteCloudTests_Blob: XCTestCase { hostname = secrets.hostname username = secrets.username password = secrets.password - + let config = SQLiteCloudConfig(hostname: hostname, username: username, password: password) cloud = SQLiteCloud(config: config) try await cloud.connect() diff --git a/Tests/SQLiteCloudTests/Integrations/SQLiteCloudTests+Connection.swift b/Tests/SQLiteCloudTests/Integrations/SQLiteCloudTests+Connection.swift index 4e8464f..7d50fbb 100644 --- a/Tests/SQLiteCloudTests/Integrations/SQLiteCloudTests+Connection.swift +++ b/Tests/SQLiteCloudTests/Integrations/SQLiteCloudTests+Connection.swift @@ -30,12 +30,14 @@ final class SQLiteCloudTests_Connection: XCTestCase { private var hostname: String = .empty private var username: String = .empty private var password: String = .empty + private var apiKey: String = .empty override func setUpWithError() throws { let secrets = try Secrets.load() hostname = secrets.hostname username = secrets.username password = secrets.password + apiKey = secrets.apiKey } func test_connect_withValidCredentials_shouldConnectWithoutError() async throws { @@ -48,7 +50,18 @@ final class SQLiteCloudTests_Connection: XCTestCase { XCTFail("An error was thrown: \(error)") } } - + + func test_connect_withValidCredentialAPIKey_shouldConnectWithoutError() async throws { + let config = SQLiteCloudConfig(hostname: hostname, apiKey: apiKey) + let cloud = SQLiteCloud(config: config) + + do { + try await cloud.connect() + } catch { + XCTFail("An error was thrown: \(error)") + } + } + func test_connect_withInvalidCredentials_shouldThrowError() async throws { let config = SQLiteCloudConfig(hostname: hostname, username: "!!invalid!!", password: "!!credentials!!") let cloud = SQLiteCloud(config: config) @@ -60,7 +73,19 @@ final class SQLiteCloudTests_Connection: XCTestCase { XCTAssert(error is SQLiteCloudError) } } - + + func test_connect_withInvalidCredentialAPIKey_shouldThrowError() async throws { + let config = SQLiteCloudConfig(hostname: hostname, apiKey: "!!invalid!!") + let cloud = SQLiteCloud(config: config) + + do { + try await cloud.connect() + XCTFail("No errors were thrown.") + } catch { + XCTAssert(error is SQLiteCloudError) + } + } + func test_disconnect_withValidConnection_shouldDisconnectWithoutError() async throws { let config = SQLiteCloudConfig(hostname: hostname, username: username, password: password) let cloud = SQLiteCloud(config: config) diff --git a/Tests/SQLiteCloudTests/Secrets/Secrets.swift b/Tests/SQLiteCloudTests/Secrets/Secrets.swift index 4cbce11..77b73bd 100644 --- a/Tests/SQLiteCloudTests/Secrets/Secrets.swift +++ b/Tests/SQLiteCloudTests/Secrets/Secrets.swift @@ -29,6 +29,7 @@ struct Secrets: Decodable { let hostname: String let username: String let password: String + let apiKey: String } extension Secrets { diff --git a/Tests/SQLiteCloudTests/Secrets/secrets.json.sample b/Tests/SQLiteCloudTests/Secrets/secrets.json.sample index e8a3d92..ffdc227 100644 --- a/Tests/SQLiteCloudTests/Secrets/secrets.json.sample +++ b/Tests/SQLiteCloudTests/Secrets/secrets.json.sample @@ -1,5 +1,6 @@ { "hostname": "", "username": "", - "password": "" + "password": "", + "apiKey": "" }