Skip to content

Commit f622ee5

Browse files
committed
Merge branch 'ios-events' of https://github.com/nats-io/nats.swift into ios-events
2 parents c8f08f7 + cea6106 commit f622ee5

File tree

7 files changed

+123
-1
lines changed

7 files changed

+123
-1
lines changed

Package.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ let package = Package(
1414
dependencies: [
1515
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"),
1616
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.2"),
17-
.package(url: "https://github.com/nats-io/nkeys.swift.git", from: "0.1.1"),
17+
.package(url: "https://github.com/nats-io/nkeys.swift.git", from: "0.1.2"),
1818
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.0.0"),
1919
.package(url: "https://github.com/Jarema/swift-nuid.git", from: "0.2.0"),
2020
],

Sources/Nats/NatsClient/NatsClient.swift

+12
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public struct Auth {
3434
var password: String?
3535
var token: String?
3636
var credentialsPath: URL?
37+
var nkeyPath: URL?
38+
var nkey: String?
3739

3840
init() {
3941

@@ -51,6 +53,16 @@ public struct Auth {
5153
auth.credentialsPath = credentials
5254
return auth
5355
}
56+
static func fromNkey(_ nkey: URL) -> Auth {
57+
var auth = Auth()
58+
auth.nkeyPath = nkey
59+
return auth
60+
}
61+
static func fromNkey(_ nkey: String) -> Auth {
62+
var auth = Auth()
63+
auth.nkey = nkey
64+
return auth
65+
}
5466
}
5567

5668
public class NatsClient {

Sources/Nats/NatsClient/NatsClientOptions.swift

+17
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,23 @@ public class NatsClientOptions {
8686
return self
8787
}
8888

89+
public func nkeyFile(_ nkey: URL) -> NatsClientOptions {
90+
if self.auth == nil {
91+
self.auth = Auth.fromNkey(nkey)
92+
} else {
93+
self.auth?.nkeyPath = nkey
94+
}
95+
return self
96+
}
97+
public func nkey(_ nkey: String) -> NatsClientOptions {
98+
if self.auth == nil {
99+
self.auth = Auth.fromNkey(nkey)
100+
} else {
101+
self.auth?.nkey = nkey
102+
}
103+
return self
104+
}
105+
89106
public func requireTls() -> NatsClientOptions {
90107
self.withTls = true
91108
return self

Sources/Nats/NatsConnection.swift

+33
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,9 @@ class ConnectionHandler: ChannelInboundHandler {
333333
user: self.auth?.user ?? "", pass: self.auth?.password ?? "",
334334
authToken: self.auth?.token ?? "", headers: true, noResponders: true)
335335

336+
if self.auth?.nkey != nil && self.auth?.nkeyPath != nil {
337+
throw NatsConfigError("cannot use both nkey and nkeyPath")
338+
}
336339
if let auth = self.auth, let credentialsPath = auth.credentialsPath {
337340
let credentials = try await URLSession.shared.data(from: credentialsPath).0
338341
guard let jwt = JwtUtils.parseDecoratedJWT(contents: credentials) else {
@@ -351,6 +354,35 @@ class ConnectionHandler: ChannelInboundHandler {
351354
initialConnect.signature = base64sig
352355
initialConnect.userJwt = String(data: jwt, encoding: .utf8)!
353356
}
357+
if let nkey = self.auth?.nkeyPath {
358+
let nkeyData = try await URLSession.shared.data(from: nkey).0
359+
360+
guard let nkeyContent = String(data: nkeyData, encoding: .utf8) else {
361+
throw NatsConfigError("failed to read NKEY file")
362+
}
363+
let keypair = try KeyPair(
364+
seed: nkeyContent.trimmingCharacters(in: .whitespacesAndNewlines)
365+
)
366+
367+
guard let nonce = self.serverInfo?.nonce else {
368+
throw NatsConfigError("missing nonce")
369+
}
370+
let sig = try keypair.sign(input: nonce.data(using: .utf8)!)
371+
let base64sig = sig.base64EncodedURLSafeNotPadded()
372+
initialConnect.signature = base64sig
373+
initialConnect.nkey = keypair.publicKeyEncoded
374+
}
375+
if let nkey = self.auth?.nkey {
376+
let keypair = try KeyPair(seed: nkey)
377+
guard let nonce = self.serverInfo?.nonce else {
378+
throw NatsConfigError("missing nonce")
379+
}
380+
let nonceData = nonce.data(using: .utf8)!
381+
let sig = try keypair.sign(input: nonceData)
382+
let base64sig = sig.base64EncodedURLSafeNotPadded()
383+
initialConnect.signature = base64sig
384+
initialConnect.nkey = keypair.publicKeyEncoded
385+
}
354386
let connect = initialConnect
355387
try await withCheckedThrowingContinuation { continuation in
356388
self.connectionEstablishedContinuation = continuation
@@ -447,6 +479,7 @@ class ConnectionHandler: ChannelInboundHandler {
447479
// due to the promise originating on the event loop of the channel.
448480
try channel.pipeline.syncOperations.addHandler(sslHandler)
449481
} catch {
482+
upgradePromise.fail(error)
450483
return channel.eventLoop.makeFailedFuture(error)
451484
}
452485
}

Tests/NatsTests/Integration/ConnectionTests.swift

+54
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ class CoreNatsTests: XCTestCase {
4545
("testRequest", testRequest),
4646
("testRequest_noResponders", testRequest_noResponders),
4747
("testRequest_timeout", testRequest_timeout),
48+
("testNkeyAuth", testNkeyAuth),
49+
("testNkeyAuthFile", testNkeyAuthFile),
4850
]
4951
var natsServer = NatsServer()
5052

@@ -463,6 +465,58 @@ class CoreNatsTests: XCTestCase {
463465
_ = await subscribe.next()
464466
}
465467

468+
func testNkeyAuth() async throws {
469+
logger.logLevel = .debug
470+
let bundle = Bundle.module
471+
natsServer.start(cfg: bundle.url(forResource: "nkey", withExtension: "conf")!.relativePath)
472+
473+
let client = NatsClientOptions()
474+
.url(URL(string: natsServer.clientURL)!)
475+
.nkey("SUACH75SWCM5D2JMJM6EKLR2WDARVGZT4QC6LX3AGHSWOMVAKERABBBRWM")
476+
.build()
477+
try await client.connect()
478+
let subscribe = try await client.subscribe(subject: "foo").makeAsyncIterator()
479+
try await client.publish("data".data(using: .utf8)!, subject: "foo")
480+
_ = await subscribe.next()
481+
}
482+
483+
func testNkeyAuthFile() async throws {
484+
logger.logLevel = .debug
485+
let bundle = Bundle.module
486+
natsServer.start(cfg: bundle.url(forResource: "nkey", withExtension: "conf")!.relativePath)
487+
488+
let client = NatsClientOptions()
489+
.url(URL(string: natsServer.clientURL)!)
490+
.nkeyFile(bundle.url(forResource: "nkey", withExtension: "")!)
491+
.build()
492+
try await client.connect()
493+
let subscribe = try await client.subscribe(subject: "foo").makeAsyncIterator()
494+
try await client.publish("data".data(using: .utf8)!, subject: "foo")
495+
_ = await subscribe.next()
496+
497+
// Test if passing both nkey and nkeyPath throws an error
498+
let badClient = NatsClientOptions()
499+
.url(URL(string: natsServer.clientURL)!)
500+
.nkeyFile(bundle.url(forResource: "nkey", withExtension: "")!)
501+
.nkey("SUACH75SWCM5D2JMJM6EKLR2WDARVGZT4QC6LX3AGHSWOMVAKERABBBRWM")
502+
.build()
503+
504+
var thrownError: Error?
505+
do {
506+
try await badClient.connect()
507+
} catch {
508+
thrownError = error
509+
}
510+
511+
// Now assert that an error was thrown and check its type and properties
512+
XCTAssertNotNil(thrownError, "Expected method to throw an error but it did not.")
513+
if let natsError = thrownError as? NatsConfigError {
514+
XCTAssertEqual(natsError.description, "cannot use both nkey and nkeyPath")
515+
} else {
516+
XCTFail("Unexpected error type: \(String(describing: thrownError))")
517+
}
518+
}
519+
466520
func testMutualTls() async throws {
467521
let bundle = Bundle.module
468522
logger.logLevel = .debug
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SUACH75SWCM5D2JMJM6EKLR2WDARVGZT4QC6LX3AGHSWOMVAKERABBBRWM
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
authorization: {
2+
users: [
3+
{ nkey: UAFHG6FUD2UU4SDFVAFUL5LDFO2XM4WYM76UNXTPJYJK7EEMYRBHT2VE }
4+
]
5+
}

0 commit comments

Comments
 (0)