diff --git a/Sources/NIOSSH/Child Channels/ChildChannelOptions.swift b/Sources/NIOSSH/Child Channels/ChildChannelOptions.swift index 5be8382..2be2e80 100644 --- a/Sources/NIOSSH/Child Channels/ChildChannelOptions.swift +++ b/Sources/NIOSSH/Child Channels/ChildChannelOptions.swift @@ -29,6 +29,9 @@ public struct SSHChildChannelOptions { /// See: ``SSHChildChannelOptions/Types/PeerMaximumMessageLengthOption``. public static let peerMaximumMessageLength: SSHChildChannelOptions.Types.PeerMaximumMessageLengthOption = .init() + + /// - seealso: `UsernameOption`. + public static let username: SSHChildChannelOptions.Types.UsernameOption = .init() } extension SSHChildChannelOptions { @@ -61,7 +64,14 @@ extension SSHChildChannelOptions.Types { /// ``SSHChildChannelOptions/Types/PeerMaximumMessageLengthOption`` allows users to query the maximum packet size value reported by the remote peer for a given channel. public struct PeerMaximumMessageLengthOption: ChannelOption, Sendable { public typealias Value = UInt32 - + + public init() {} + } + + /// `UsernameOption` allows users to query the authenticated username of the channel. + public struct UsernameOption: ChannelOption { + public typealias Value = String? + public init() {} } } diff --git a/Sources/NIOSSH/Child Channels/SSHChannelMultiplexer.swift b/Sources/NIOSSH/Child Channels/SSHChannelMultiplexer.swift index ff03346..480fca3 100644 --- a/Sources/NIOSSH/Child Channels/SSHChannelMultiplexer.swift +++ b/Sources/NIOSSH/Child Channels/SSHChannelMultiplexer.swift @@ -93,6 +93,9 @@ extension SSHChannelMultiplexer { self.erroredChannels.append(channelID) } } + + // The username which the server accepted in authorization + var username: String? { delegate?.username } } // MARK: Calls from SSH handlers. @@ -218,6 +221,8 @@ extension SSHChannelMultiplexer { protocol SSHMultiplexerDelegate { var channel: Channel? { get } + var username: String? { get } + func writeFromChildChannel(_: SSHMessage, _: EventLoopPromise?) func flushFromChildChannel() diff --git a/Sources/NIOSSH/Child Channels/SSHChildChannel.swift b/Sources/NIOSSH/Child Channels/SSHChildChannel.swift index e2188e8..6928991 100644 --- a/Sources/NIOSSH/Child Channels/SSHChildChannel.swift +++ b/Sources/NIOSSH/Child Channels/SSHChildChannel.swift @@ -228,6 +228,8 @@ extension SSHChildChannel: Channel, ChannelCore { return self.type! as! Option.Value case _ as SSHChildChannelOptions.Types.PeerMaximumMessageLengthOption: return self.peerMaxMessageSize as! Option.Value + case _ as SSHChildChannelOptions.Types.UsernameOption: + return multiplexer.username as! Option.Value case _ as ChannelOptions.Types.AutoReadOption: return self.autoRead as! Option.Value case _ as ChannelOptions.Types.AllowRemoteHalfClosureOption: diff --git a/Sources/NIOSSH/Connection State Machine/Operations/AcceptsUserAuthMessages.swift b/Sources/NIOSSH/Connection State Machine/Operations/AcceptsUserAuthMessages.swift index 70344dc..8ff29ce 100644 --- a/Sources/NIOSSH/Connection State Machine/Operations/AcceptsUserAuthMessages.swift +++ b/Sources/NIOSSH/Connection State Machine/Operations/AcceptsUserAuthMessages.swift @@ -15,6 +15,8 @@ protocol AcceptsUserAuthMessages { var userAuthStateMachine: UserAuthenticationStateMachine { get set } + var connectionAttributes: SSHConnectionStateMachine.Attributes { get } + var role: SSHConnectionRole { get } } @@ -60,14 +62,15 @@ extension AcceptsUserAuthMessages { mutating func receiveUserAuthRequest(_ message: SSHMessage.UserAuthRequestMessage) throws -> SSHConnectionStateMachine.StateMachineInboundProcessResult { let result = try self.userAuthStateMachine.receiveUserAuthRequest(message) - + if let future = result { var banner: SSHServerConfiguration.UserAuthBanner? if case .server(let config) = role { banner = config.banner } - return .possibleFutureMessage(future.map { Self.transform($0, banner: banner) }) + let connectionAttributes = self.connectionAttributes + return .possibleFutureMessage(future.map { Self.transform($0, connectionAttributes: connectionAttributes, username: message.username, banner: banner) }) } else { return .noMessage } @@ -96,9 +99,11 @@ extension AcceptsUserAuthMessages { return .event(NIOUserAuthBannerEvent(message: message.message, languageTag: message.languageTag)) } - private static func transform(_ result: NIOSSHUserAuthenticationResponseMessage, banner: SSHServerConfiguration.UserAuthBanner? = nil) -> SSHMultiMessage { + private static func transform(_ result: NIOSSHUserAuthenticationResponseMessage, connectionAttributes: SSHConnectionStateMachine.Attributes?, username: String, banner: SSHServerConfiguration.UserAuthBanner? = nil) -> SSHMultiMessage { switch result { case .success: + connectionAttributes?.username = username + if let banner = banner { // Send banner bundled with auth success to avoid leaking any information to unauthenticated clients. // Note that this is by no means the only option according to RFC 4252 diff --git a/Sources/NIOSSH/Connection State Machine/SSHConnectionStateMachine.swift b/Sources/NIOSSH/Connection State Machine/SSHConnectionStateMachine.swift index 3fb84b5..e808376 100644 --- a/Sources/NIOSSH/Connection State Machine/SSHConnectionStateMachine.swift +++ b/Sources/NIOSSH/Connection State Machine/SSHConnectionStateMachine.swift @@ -57,11 +57,21 @@ struct SSHConnectionStateMachine { case sentDisconnect(SSHConnectionRole) } + class Attributes { + var username: String? = nil + } + /// The state of this state machine. private var state: State + + /// Attributes of the connection which can be changed by messages handlers + private let attributes: Attributes + + var username: String? { attributes.username } init(role: SSHConnectionRole, protectionSchemes: [NIOSSHTransportProtection.Type] = Constants.bundledTransportProtectionSchemes) { - self.state = .idle(IdleState(role: role, protectionSchemes: protectionSchemes)) + self.attributes = Attributes() + self.state = .idle(IdleState(role: role, protectionSchemes: protectionSchemes, attributes: attributes)) } func start() -> SSHMultiMessage? { diff --git a/Sources/NIOSSH/Connection State Machine/States/ActiveState.swift b/Sources/NIOSSH/Connection State Machine/States/ActiveState.swift index 70ee709..b03e335 100644 --- a/Sources/NIOSSH/Connection State Machine/States/ActiveState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/ActiveState.swift @@ -31,6 +31,8 @@ extension SSHConnectionStateMachine { internal var sessionIdentifier: ByteBuffer + internal let connectionAttributes: SSHConnectionStateMachine.Attributes + init(_ previous: UserAuthenticationState) { self.role = previous.role self.serializer = previous.serializer @@ -38,6 +40,7 @@ extension SSHConnectionStateMachine { self.remoteVersion = previous.remoteVersion self.protectionSchemes = previous.protectionSchemes self.sessionIdentifier = previous.sessionIdentifier + self.connectionAttributes = previous.connectionAttributes } init(_ previous: RekeyingReceivedNewKeysState) { @@ -47,6 +50,7 @@ extension SSHConnectionStateMachine { self.remoteVersion = previous.remoteVersion self.protectionSchemes = previous.protectionSchemes self.sessionIdentifier = previous.sessionIdentifier + self.connectionAttributes = previous.connectionAttributes } init(_ previous: RekeyingSentNewKeysState) { @@ -56,6 +60,7 @@ extension SSHConnectionStateMachine { self.remoteVersion = previous.remoteVersion self.protectionSchemes = previous.protectionSchemes self.sessionIdentifier = previous.sessionIdentifier + self.connectionAttributes = previous.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/IdleState.swift b/Sources/NIOSSH/Connection State Machine/States/IdleState.swift index 0ea83d3..9fb185f 100644 --- a/Sources/NIOSSH/Connection State Machine/States/IdleState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/IdleState.swift @@ -23,10 +23,13 @@ extension SSHConnectionStateMachine { internal var protectionSchemes: [NIOSSHTransportProtection.Type] - init(role: SSHConnectionRole, protectionSchemes: [NIOSSHTransportProtection.Type]) { + internal let connectionAttributes: SSHConnectionStateMachine.Attributes + + init(role: SSHConnectionRole, protectionSchemes: [NIOSSHTransportProtection.Type], attributes: SSHConnectionStateMachine.Attributes) { self.role = role self.serializer = SSHPacketSerializer() self.protectionSchemes = protectionSchemes + self.connectionAttributes = attributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/KeyExchangeState.swift b/Sources/NIOSSH/Connection State Machine/States/KeyExchangeState.swift index 50f13ae..0e77b4e 100644 --- a/Sources/NIOSSH/Connection State Machine/States/KeyExchangeState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/KeyExchangeState.swift @@ -33,6 +33,8 @@ extension SSHConnectionStateMachine { /// The backing state machine. var keyExchangeStateMachine: SSHKeyExchangeStateMachine + let connectionAttributes: SSHConnectionStateMachine.Attributes + init(sentVersionState state: SentVersionState, allocator: ByteBufferAllocator, loop: EventLoop, remoteVersion: String) { self.role = state.role self.parser = state.parser @@ -40,6 +42,7 @@ extension SSHConnectionStateMachine { self.remoteVersion = remoteVersion self.protectionSchemes = state.protectionSchemes self.keyExchangeStateMachine = SSHKeyExchangeStateMachine(allocator: allocator, loop: loop, role: state.role, remoteVersion: remoteVersion, protectionSchemes: state.protectionSchemes, previousSessionIdentifier: nil) + self.connectionAttributes = state.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/ReceivedKexInitWhenActiveState.swift b/Sources/NIOSSH/Connection State Machine/States/ReceivedKexInitWhenActiveState.swift index c8f53e4..1a7c87e 100644 --- a/Sources/NIOSSH/Connection State Machine/States/ReceivedKexInitWhenActiveState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/ReceivedKexInitWhenActiveState.swift @@ -34,6 +34,8 @@ extension SSHConnectionStateMachine { internal var sessionIdentifier: ByteBuffer + internal let connectionAttributes: SSHConnectionStateMachine.Attributes + init(_ previous: ActiveState, allocator: ByteBufferAllocator, loop: EventLoop) { self.role = previous.role self.serializer = previous.serializer @@ -42,6 +44,7 @@ extension SSHConnectionStateMachine { self.protectionSchemes = previous.protectionSchemes self.sessionIdentifier = previous.sessionIdentifier self.keyExchangeStateMachine = SSHKeyExchangeStateMachine(allocator: allocator, loop: loop, role: previous.role, remoteVersion: previous.remoteVersion, protectionSchemes: previous.protectionSchemes, previousSessionIdentifier: self.sessionIdentifier) + self.connectionAttributes = previous.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/ReceivedNewKeysState.swift b/Sources/NIOSSH/Connection State Machine/States/ReceivedNewKeysState.swift index f183d3d..8df5648 100644 --- a/Sources/NIOSSH/Connection State Machine/States/ReceivedNewKeysState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/ReceivedNewKeysState.swift @@ -39,6 +39,8 @@ extension SSHConnectionStateMachine { /// The user auth state machine that drives user authentication. var userAuthStateMachine: UserAuthenticationStateMachine + let connectionAttributes: SSHConnectionStateMachine.Attributes + init(keyExchangeState state: KeyExchangeState, loop: EventLoop) { self.role = state.role @@ -53,6 +55,7 @@ extension SSHConnectionStateMachine { self.userAuthStateMachine = UserAuthenticationStateMachine(role: self.role, loop: loop, sessionID: self.sessionIdentifier) + self.connectionAttributes = state.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/RekeyingReceivedNewKeysState.swift b/Sources/NIOSSH/Connection State Machine/States/RekeyingReceivedNewKeysState.swift index 7caa796..23d7e17 100644 --- a/Sources/NIOSSH/Connection State Machine/States/RekeyingReceivedNewKeysState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/RekeyingReceivedNewKeysState.swift @@ -36,6 +36,8 @@ extension SSHConnectionStateMachine { /// The backing state machine. var keyExchangeStateMachine: SSHKeyExchangeStateMachine + let connectionAttributes: SSHConnectionStateMachine.Attributes + init(_ previousState: RekeyingState) { self.role = previousState.role self.parser = previousState.parser @@ -44,6 +46,7 @@ extension SSHConnectionStateMachine { self.protectionSchemes = previousState.protectionSchemes self.sessionIdentifier = previousState.sessionIdentifier self.keyExchangeStateMachine = previousState.keyExchangeStateMachine + self.connectionAttributes = previousState.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/RekeyingSentNewKeysState.swift b/Sources/NIOSSH/Connection State Machine/States/RekeyingSentNewKeysState.swift index 95c0865..954ff3c 100644 --- a/Sources/NIOSSH/Connection State Machine/States/RekeyingSentNewKeysState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/RekeyingSentNewKeysState.swift @@ -36,6 +36,8 @@ extension SSHConnectionStateMachine { /// The backing state machine. var keyExchangeStateMachine: SSHKeyExchangeStateMachine + let connectionAttributes: SSHConnectionStateMachine.Attributes + init(_ previousState: RekeyingState) { self.role = previousState.role self.parser = previousState.parser @@ -44,6 +46,7 @@ extension SSHConnectionStateMachine { self.protectionSchemes = previousState.protectionSchemes self.sessionIdentifier = previousState.sessionIdentifier self.keyExchangeStateMachine = previousState.keyExchangeStateMachine + self.connectionAttributes = previousState.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/RekeyingState.swift b/Sources/NIOSSH/Connection State Machine/States/RekeyingState.swift index cbeea56..e7f58c8 100644 --- a/Sources/NIOSSH/Connection State Machine/States/RekeyingState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/RekeyingState.swift @@ -35,6 +35,8 @@ extension SSHConnectionStateMachine { /// The backing state machine. var keyExchangeStateMachine: SSHKeyExchangeStateMachine + let connectionAttributes: SSHConnectionStateMachine.Attributes + init(_ previousState: ReceivedKexInitWhenActiveState) { self.role = previousState.role self.parser = previousState.parser @@ -43,6 +45,7 @@ extension SSHConnectionStateMachine { self.protectionSchemes = previousState.protectionSchemes self.sessionIdentifier = previousState.sessionIdentifier self.keyExchangeStateMachine = previousState.keyExchangeStateMachine + self.connectionAttributes = previousState.connectionAttributes } init(_ previousState: SentKexInitWhenActiveState) { @@ -53,6 +56,7 @@ extension SSHConnectionStateMachine { self.protectionSchemes = previousState.protectionSchemes self.sessionIdentifier = previousState.sessionIdentitifier self.keyExchangeStateMachine = previousState.keyExchangeStateMachine + self.connectionAttributes = previousState.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/SentKexInitWhenActiveState.swift b/Sources/NIOSSH/Connection State Machine/States/SentKexInitWhenActiveState.swift index 5af0890..bba530f 100644 --- a/Sources/NIOSSH/Connection State Machine/States/SentKexInitWhenActiveState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/SentKexInitWhenActiveState.swift @@ -34,6 +34,8 @@ extension SSHConnectionStateMachine { internal var keyExchangeStateMachine: SSHKeyExchangeStateMachine + internal let connectionAttributes: SSHConnectionStateMachine.Attributes + init(_ previous: ActiveState, allocator: ByteBufferAllocator, loop: EventLoop) { self.role = previous.role self.serializer = previous.serializer @@ -42,6 +44,7 @@ extension SSHConnectionStateMachine { self.protectionSchemes = previous.protectionSchemes self.sessionIdentitifier = previous.sessionIdentifier self.keyExchangeStateMachine = SSHKeyExchangeStateMachine(allocator: allocator, loop: loop, role: self.role, remoteVersion: self.remoteVersion, protectionSchemes: self.protectionSchemes, previousSessionIdentifier: previous.sessionIdentifier) + self.connectionAttributes = previous.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/SentNewKeysState.swift b/Sources/NIOSSH/Connection State Machine/States/SentNewKeysState.swift index 53b637f..80118ea 100644 --- a/Sources/NIOSSH/Connection State Machine/States/SentNewKeysState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/SentNewKeysState.swift @@ -39,6 +39,8 @@ extension SSHConnectionStateMachine { /// The user auth state machine that drives user authentication. var userAuthStateMachine: UserAuthenticationStateMachine + let connectionAttributes: SSHConnectionStateMachine.Attributes + init(keyExchangeState state: KeyExchangeState, loop: EventLoop) { self.role = state.role @@ -53,6 +55,7 @@ extension SSHConnectionStateMachine { self.userAuthStateMachine = UserAuthenticationStateMachine(role: self.role, loop: loop, sessionID: self.sessionIdentifier) + self.connectionAttributes = state.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/SentVersionState.swift b/Sources/NIOSSH/Connection State Machine/States/SentVersionState.swift index 4c85b83..d34f69c 100644 --- a/Sources/NIOSSH/Connection State Machine/States/SentVersionState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/SentVersionState.swift @@ -28,6 +28,8 @@ extension SSHConnectionStateMachine { var protectionSchemes: [NIOSSHTransportProtection.Type] + let connectionAttributes: SSHConnectionStateMachine.Attributes + private let allocator: ByteBufferAllocator init(idleState state: IdleState, allocator: ByteBufferAllocator) { @@ -37,6 +39,7 @@ extension SSHConnectionStateMachine { self.parser = SSHPacketParser(allocator: allocator) self.allocator = allocator + self.connectionAttributes = state.connectionAttributes } } } diff --git a/Sources/NIOSSH/Connection State Machine/States/UserAuthenticationState.swift b/Sources/NIOSSH/Connection State Machine/States/UserAuthenticationState.swift index 847e53d..c1e2bd9 100644 --- a/Sources/NIOSSH/Connection State Machine/States/UserAuthenticationState.swift +++ b/Sources/NIOSSH/Connection State Machine/States/UserAuthenticationState.swift @@ -35,6 +35,8 @@ extension SSHConnectionStateMachine { /// The backing state machine. var userAuthStateMachine: UserAuthenticationStateMachine + let connectionAttributes: SSHConnectionStateMachine.Attributes + init(sentNewKeysState state: SentNewKeysState) { self.role = state.role self.parser = state.parser @@ -43,6 +45,7 @@ extension SSHConnectionStateMachine { self.remoteVersion = state.remoteVersion self.protectionSchemes = state.protectionSchemes self.sessionIdentifier = state.sessionIdentifier + self.connectionAttributes = state.connectionAttributes } init(receivedNewKeysState state: ReceivedNewKeysState) { @@ -53,6 +56,7 @@ extension SSHConnectionStateMachine { self.remoteVersion = state.remoteVersion self.protectionSchemes = state.protectionSchemes self.sessionIdentifier = state.sessionIdentifier + self.connectionAttributes = state.connectionAttributes } } } diff --git a/Sources/NIOSSH/NIOSSHHandler.swift b/Sources/NIOSSH/NIOSSHHandler.swift index c6d64c9..54a2535 100644 --- a/Sources/NIOSSH/NIOSSHHandler.swift +++ b/Sources/NIOSSH/NIOSSHHandler.swift @@ -57,6 +57,9 @@ public final class NIOSSHHandler { private var pendingGlobalRequestResponses: CircularBuffer + // The authenticated username, if there was one. + var username: String? { stateMachine.username } + /// Construct a new ``NIOSSHHandler``. /// /// - parameters: diff --git a/Tests/NIOSSHTests/ChildChannelMultiplexerTests.swift b/Tests/NIOSSHTests/ChildChannelMultiplexerTests.swift index 284bba9..41ee235 100644 --- a/Tests/NIOSSHTests/ChildChannelMultiplexerTests.swift +++ b/Tests/NIOSSHTests/ChildChannelMultiplexerTests.swift @@ -22,6 +22,8 @@ import XCTest /// This reduces the testing surface area somewhat, which greatly helps us to test the /// implementation of the multiplexer and child channels. final class DummyDelegate: SSHMultiplexerDelegate { + var username : String? = "dummy" + var _channel: EmbeddedChannel = EmbeddedChannel() var writes: MarkedCircularBuffer<(SSHMessage, EventLoopPromise?)> = MarkedCircularBuffer(initialCapacity: 8) diff --git a/Tests/NIOSSHTests/EndToEndTests.swift b/Tests/NIOSSHTests/EndToEndTests.swift index 3e5c3a8..6f3318c 100644 --- a/Tests/NIOSSHTests/EndToEndTests.swift +++ b/Tests/NIOSSHTests/EndToEndTests.swift @@ -34,7 +34,7 @@ class BackToBackEmbeddedChannel { } var serverSSHHandler: NIOSSHHandler? { - try? self.client.pipeline.handler(type: NIOSSHHandler.self).wait() + try? self.server.pipeline.handler(type: NIOSSHHandler.self).wait() } init() { @@ -264,6 +264,43 @@ class EndToEndTests: XCTestCase { XCTAssertEqual((error as? NIOSSHError)?.type, .globalRequestRefused) } } + + func testServerRecordsAuthenticatedUsername() throws { + class ClientHandshakeHandler: ChannelInboundHandler { + typealias InboundIn = Any + + let promise: EventLoopPromise + + init(promise: EventLoopPromise) { + self.promise = promise + } + + func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) { + if event is UserAuthSuccessEvent { + self.promise.succeed(()) + } + } + } + + let promise = self.channel.client.eventLoop.makePromise(of: Void.self) + let handshaker = ClientHandshakeHandler(promise: promise) + + let clientAuthDelegate = InfinitePasswordDelegate() + let username = UUID().uuidString + clientAuthDelegate.username = username + var harness = TestHarness() + harness.clientAuthDelegate = clientAuthDelegate + + // Set up the connection, validate all is well. + XCTAssertNoThrow(try self.channel.configureWithHarness(harness)) + XCTAssertNoThrow(try self.channel.client.pipeline.addHandler(handshaker).wait()) + XCTAssertNoThrow(try self.channel.activate()) + XCTAssertNoThrow(try self.channel.interactInMemory()) + + XCTAssertNoThrow(try promise.futureResult.wait()) + + XCTAssertEqual(self.channel.serverSSHHandler?.username, username) + } func testGlobalRequestWithCustomDelegate() throws { class CustomGlobalRequestDelegate: GlobalRequestDelegate { diff --git a/Tests/NIOSSHTests/UserAuthenticationStateMachineTests.swift b/Tests/NIOSSHTests/UserAuthenticationStateMachineTests.swift index 00d3079..4eadd71 100644 --- a/Tests/NIOSSHTests/UserAuthenticationStateMachineTests.swift +++ b/Tests/NIOSSHTests/UserAuthenticationStateMachineTests.swift @@ -40,8 +40,9 @@ private enum Fixtures { /// An authentication delegate that yields passwords forever. final class InfinitePasswordDelegate: NIOSSHClientUserAuthenticationDelegate { + var username = "foo" func nextAuthenticationType(availableMethods: NIOSSHAvailableUserAuthenticationMethods, nextChallengePromise: EventLoopPromise) { - let request = NIOSSHUserAuthenticationOffer(username: "foo", serviceName: "", offer: .password(.init(password: "bar"))) + let request = NIOSSHUserAuthenticationOffer(username: username, serviceName: "", offer: .password(.init(password: "bar"))) nextChallengePromise.succeed(request) } }