Repository: nats-io/nats.py

Issue:
Nats client not respecting the username and password provided in server string if server has "no_auth_user" configured
### What version were you using?

nats-server: v2.9.21
Using commit `caaf42818eb80e180e40ae2225b49b9c47fd8b1d` which shows `2.3.1`

### What environment was the server running in?

Mac M1

### Is this defect reproducible?

Yes, I wrote a test that shows the failure (and I'm working on an pull request for this):
```
class NoAuthUserClientTest(NoAuthUserServerTestCase):
    @async_test
    async def test_connect_user_from_server(self):
        fut = asyncio.Future()

        async def err_cb(e):
            if not fut.done():
                fut.set_result(e)

        nc = await nats.connect(
            "nats://bar:bar@127.0.0.1:4555",
            error_cb=err_cb,
        )
        sub = await nc.subscribe("foo")
        await nc.flush()
        await asyncio.sleep(0)
        await nc.publish("foo", b'hello')
        await asyncio.wait_for(fut, 2)
        err = fut.result()
        assert str(
            err
        ) == 'nats: permissions violation for subscription to "foo"'

        nc2 = await nats.connect("nats://127.0.0.1:4555", )

        async def cb(msg):
            await msg.respond(b'pong')

        sub2 = await nc2.subscribe("foo", cb=cb)
        await nc2.flush()
        resp = await nc2.request("foo", b'ping')
        assert resp.data == b'pong'

        await nc2.close()
        await nc.close()
```

### Given the capability you are leveraging, describe your expectation?

I expect that if I provide a server with the username and password in it (such as `nats://bar:bar@127.0.0.1:4555`), that the nats client will connect using username `bar` and password `bar`

### Given the expectation, what is the defect you are observing?

The nats client is not connecting using the passed in username and password.

Discussion:

Patch:
diff --git a/nats/aio/client.py b/nats/aio/client.py
index a58fd2f..7db96f6 100644
--- a/nats/aio/client.py
+++ b/nats/aio/client.py
@@ -451,7 +451,14 @@ class Client:
         if tls_hostname:
             self.options['tls_hostname'] = tls_hostname
 
-        if user or password or token:
+        # Check if the username or password was set in the server URI
+        server_auth_configured = False
+        if len(self._server_pool) > 0:
+            for server in self._server_pool:
+                if server.uri.username or server.uri.password:
+                    server_auth_configured = True
+                    break
+        if user or password or token or server_auth_configured:
             self._auth_configured = True
 
         if self._user_credentials is not None or self._nkeys_seed is not None:


Test patch:
diff --git a/tests/test_client.py b/tests/test_client.py
index 4957fc1..9739663 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -2724,6 +2724,41 @@ class NoAuthUserClientTest(NoAuthUserServerTestCase):
         await nc2.close()
         await nc.close()
 
+    @async_test
+    async def test_connect_user_from_server(self):
+        fut = asyncio.Future()
+
+        async def err_cb(e):
+            if not fut.done():
+                fut.set_result(e)
+
+        nc = await nats.connect(
+            "nats://bar:bar@127.0.0.1:4555",
+            error_cb=err_cb,
+        )
+        sub = await nc.subscribe("foo")
+        await nc.flush()
+        await asyncio.sleep(0)
+        await nc.publish("foo", b'hello')
+        await asyncio.wait_for(fut, 2)
+        err = fut.result()
+        assert str(
+            err
+        ) == 'nats: permissions violation for subscription to "foo"'
+
+        nc2 = await nats.connect("nats://127.0.0.1:4555", )
+
+        async def cb(msg):
+            await msg.respond(b'pong')
+
+        sub2 = await nc2.subscribe("foo", cb=cb)
+        await nc2.flush()
+        resp = await nc2.request("foo", b'ping')
+        assert resp.data == b'pong'
+
+        await nc2.close()
+        await nc.close()
+
 
 if __name__ == '__main__':
     import sys