Repository: edgedb/edgedb

Issue:
JWT authentication fails when databases (`edb.d`) specified
- EdgeDB Version: 3.2+bb17a05

```python
>>> from edb.common import secretkey
>>> secretkey.generate_secret_key(key, databases=['edgedb'])
'edbt1_ey...'
```

```
edgedb> CONFIGURE INSTANCE INSERT Auth {
.......     priority := 0,
.......     method := (INSERT JWT {
.......         transports := cfg::ConnectionTransport.TCP
.......     }),
....... };
OK: CONFIGURE INSTANCE
```

```
$ edgedb --host localhost --port 10721 --user edgedb --tls-security insecure --secret-key edbt1_ey...
edgedb error: AuthenticationError: authentication failed: secret key does not authorize access to database "None"
  Server traceback:
      Traceback (most recent call last):
        File "edb/server/protocol/frontend.pyx", line 294, in edb.server.protocol.frontend.FrontendConnection.main
        File "edb/server/protocol/binary.pyx", line 1022, in authenticate
        File "edb/server/protocol/binary.pyx", line 282, in auth
        File "edb/server/protocol/frontend.pyx", line 536, in _authenticate
        File "edb/server/protocol/binary.pyx", line 468, in edb.server.protocol.binary.EdgeConnection._auth_jwt
        File "edb/server/protocol/binary.pyx", line 504, in edb.server.protocol.binary.EdgeConnection._check_jwt_authz
      edb.errors.AuthenticationError: authentication failed: secret key does not authorize access to database "None"
```

The test `test_server_auth_jwt_1()` is mistakingly skipped in CI.

Refs #5744


Discussion:

Resolving patch:
diff --git a/edb/server/cluster.py b/edb/server/cluster.py
--- a/edb/server/cluster.py
+++ b/edb/server/cluster.py
@@ -499,6 +499,12 @@ def __init__(
 class RunningCluster(BaseCluster):
     def __init__(self, **conn_args: Any) -> None:
         self.conn_args = conn_args
+        if path := os.environ.get("EDGEDB_SERVER_JWS_KEY_FILE"):
+            try:
+                jws_key = secretkey.load_secret_key(pathlib.Path(path))
+            except secretkey.SecretKeyReadError as e:
+                raise ClusterError(e.args[0]) from e
+            self.get_jws_key = lambda: jws_key
 
     def is_managed(self) -> bool:
         return False
diff --git a/edb/server/compiler/compiler.py b/edb/server/compiler/compiler.py
--- a/edb/server/compiler/compiler.py
+++ b/edb/server/compiler/compiler.py
@@ -2204,6 +2204,8 @@ def _try_compile(
             if comp.cached_reflection is not None:
                 unit.cached_reflection = \
                     pickle.dumps(comp.cached_reflection, -1)
+            if comp.global_schema is not None:
+                unit.global_schema = pickle.dumps(comp.global_schema, -1)
 
             if comp.modaliases is not None:
                 unit.modaliases = comp.modaliases


Test patch:
diff --git a/tests/test_server_auth.py b/tests/test_server_auth.py
--- a/tests/test_server_auth.py
+++ b/tests/test_server_auth.py
@@ -29,7 +29,6 @@
 
 from edb.common import secretkey
 from edb.schema import defines as s_def
-from edb.server import cluster as edb_cluster
 from edb.testbase import server as tb
 
 
@@ -237,7 +236,7 @@ async def test_server_auth_jwt_1(self):
         if not self.has_create_role:
             self.skipTest("create role is not supported by the backend")
 
-        if not isinstance(self.cluster, edb_cluster.Cluster):
+        if not hasattr(self.cluster, "get_jws_key"):
             raise unittest.SkipTest("test not supported on remote cluster")
 
         jwk = self.cluster.get_jws_key()
@@ -382,3 +381,25 @@ async def test_server_auth_jwt_2(self):
                 'authentication failed: revoked key',
             ):
                 await sd.connect(secret_key=sk)
+
+    async def test_server_auth_in_transaction(self):
+        if not self.has_create_role:
+            self.skipTest('create role is not supported by the backend')
+
+        async with self.con.transaction():
+            await self.con.query('''
+                CREATE SUPERUSER ROLE foo {
+                    SET password := 'foo-pass';
+                };
+            ''')
+
+        try:
+            conn = await self.connect(
+                user='foo',
+                password='foo-pass',
+            )
+            await conn.aclose()
+        finally:
+            await self.con.query('''
+                DROP ROLE foo;
+            ''')