We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
bug: Cloud CLI logs auth token at info level
### Meltano Version

cloud branch

### Python Version

NA

### Bug scope

CLI (options, error messages, logging, etc.)

### Operating System

N/A

### Description

Running `meltano cloud login` results in the input shown below. Running `meltano-cloud login` does not.

A good resolution to this would probably be to figure out what is logging these HTTP requests, and silence those log messages. My guess is that it has to do with the recent change to use `aiohttp` instead of Flask for the local auth server.

### Code

```python
Logging in to Meltano Cloud.
You will be directed to a web browser to complete login.
If a web browser does not open, open the following link:
https://auth.meltano.cloud/oauth2/authorize?client_id=45rpn5ep3g4qjut8jd3s4iq872&response_type=token&scope=email+openid+profile&redirect_uri=http%3A%2F%2Flocalhost%3A9999
2023-04-04T16:09:25.658362Z [info     ] 127.0.0.1 [04/Apr/2023:16:09:25 +0000] "GET / HTTP/1.1" 200 236 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
2023-04-04T16:09:25.780667Z [info     ] 127.0.0.1 [04/Apr/2023:16:09:25 +0000] "GET /tokens?access_token=<redacted>&token_type=Bearer&expires_in=28800 HTTP/1.1" 204 99 "http://localhost:9999/" "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/cloud-cli/meltano/cloud/api/auth/auth.py`
Content:
```
1 """Authentication for Meltano Cloud."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 import sys
7 import tempfile
8 import typing as t
9 import webbrowser
10 from contextlib import asynccontextmanager
11 from http import HTTPStatus
12 from pathlib import Path
13 from urllib.parse import urlencode, urljoin
14 
15 import aiohttp
16 import click
17 import jinja2
18 from aiohttp import web
19 
20 from meltano.cloud.api.config import MeltanoCloudConfig
21 
22 if sys.version_info <= (3, 8):
23     from cached_property import cached_property
24 else:
25     from functools import cached_property
26 
27 if sys.version_info < (3, 9):
28     import importlib_resources
29 else:
30     from importlib import resources as importlib_resources
31 
32 LOGIN_STATUS_CHECK_DELAY_SECONDS = 0.2
33 
34 
35 class MeltanoCloudAuthError(Exception):
36     """Raised when an API call returns a 403."""
37 
38 
39 class MeltanoCloudAuth:  # noqa: WPS214
40     """Authentication methods for Meltano Cloud."""
41 
42     def __init__(self, config: MeltanoCloudConfig | None = None):
43         """Initialize a MeltanoCloudAuth instance.
44 
45         Args:
46             config: the MeltanoCloudConfig to use
47         """
48         self.config = config or MeltanoCloudConfig.find()
49         self.base_url = self.config.base_auth_url
50         self.client_id = self.config.app_client_id
51 
52     @cached_property
53     def login_url(self) -> str:
54         """Get the oauth2 authorization URL.
55 
56         Returns:
57             the oauth2 authorization URL.
58         """
59         query_params = urlencode(
60             {
61                 "client_id": self.client_id,
62                 "response_type": "token",
63                 "scope": "email openid profile",
64                 "redirect_uri": f"http://localhost:{self.config.auth_callback_port}",
65             },
66         )
67         return f"{self.base_url}/oauth2/authorize?{query_params}"
68 
69     @cached_property
70     def logout_url(self) -> str:
71         """Get the Meltano Cloud logout URL.
72 
73         Returns:
74             the Meltano Cloud logout URL.
75         """
76         params = urlencode(
77             {
78                 "client_id": self.client_id,
79                 "logout_uri": f"http://localhost:{self.config.auth_callback_port}/logout",  # noqa: E501)
80             },
81         )
82         return urljoin(self.base_url, f"logout?{params}")
83 
84     @asynccontextmanager
85     async def _callback_server(
86         self,
87         rendered_template_dir: Path,
88     ) -> t.AsyncIterator[web.Application]:
89         app = web.Application()
90         resource_root = importlib_resources.files(__package__)
91 
92         async def callback_page(_):
93             with importlib_resources.as_file(
94                 resource_root / "callback.jinja2",
95             ) as template_file, (rendered_template_dir / "callback.html").open(
96                 "w",
97             ) as rendered_template_file:
98                 rendered_template_file.write(
99                     jinja2.Template(template_file.read_text()).render(
100                         port=self.config.auth_callback_port,
101                     ),
102                 )
103             return web.FileResponse(rendered_template_file.name)
104 
105         async def handle_tokens(request: web.Request):
106             self.config.id_token = request.query["id_token"]
107             self.config.access_token = request.query["access_token"]
108             self.config.write_to_file()
109             return web.Response(status=HTTPStatus.NO_CONTENT)
110 
111         async def handle_logout(_):
112             self.config.id_token = None
113             self.config.access_token = None
114             self.config.write_to_file()
115             with importlib_resources.as_file(
116                 resource_root / "logout.html",
117             ) as html_file:
118                 return web.FileResponse(html_file)
119 
120         app.add_routes(
121             (
122                 web.get("/", callback_page),
123                 web.get("/tokens", handle_tokens),
124                 web.get("/logout", handle_logout),
125             ),
126         )
127         runner = web.AppRunner(app)
128         await runner.setup()
129         site = web.TCPSite(runner, "localhost", self.config.auth_callback_port)
130         await site.start()
131         try:
132             yield app
133         finally:
134             await runner.cleanup()
135 
136     @asynccontextmanager
137     async def callback_server(self) -> t.AsyncIterator[web.Application]:
138         """Context manager to run callback server locally.
139 
140         Yields:
141             The aiohttp web application.
142         """
143         with tempfile.TemporaryDirectory(prefix="meltano-cloud-") as tmpdir:
144             async with self._callback_server(Path(tmpdir)) as app:
145                 yield app
146 
147     async def login(self) -> None:
148         """Take user through login flow and get auth and id tokens."""
149         if await self.logged_in():
150             return
151         async with self.callback_server():
152             click.echo("Logging in to Meltano Cloud.")
153             click.echo("You will be directed to a web browser to complete login.")
154             click.echo("If a web browser does not open, open the following link:")
155             click.secho(self.login_url, fg="green")
156             webbrowser.open_new_tab(self.login_url)
157             while not await self.logged_in():
158                 self.config.refresh()
159                 await asyncio.sleep(LOGIN_STATUS_CHECK_DELAY_SECONDS)
160 
161     async def logout(self) -> None:  # noqa: WPS213
162         """Log out."""
163         if not await self.logged_in():
164             click.secho("Not logged in.", fg="green")
165             return
166         async with self.callback_server():
167             click.echo("Logging out of Meltano Cloud.")
168             click.echo("You will be directed to a web browser to complete logout.")
169             click.echo("If a web browser does not open, open the following link:")
170             click.secho(self.logout_url, fg="green")
171             webbrowser.open_new_tab(self.logout_url)
172             while await self.logged_in():
173                 self.config.refresh()
174                 await asyncio.sleep(LOGIN_STATUS_CHECK_DELAY_SECONDS)
175         click.secho("Successfully logged out.", fg="green")
176 
177     def get_auth_header(self) -> dict[str, str]:
178         """Get the authorization header.
179 
180         Used for authenticating to cloud API endpoints.
181 
182         Returns:
183             Authorization header using ID token as bearer token.
184 
185         """
186         return {"Authorization": f"Bearer {self.config.id_token}"}
187 
188     def get_access_token_header(self) -> dict[str, str]:
189         """Get the access token header.
190 
191         Used for authenticating to auth endpoints.
192 
193         Returns:
194             Authorization header using access token as bearer token.
195         """
196         return {"Authorization": f"Bearer {self.config.access_token}"}
197 
198     @asynccontextmanager
199     async def _get_user_info_response(self) -> t.AsyncIterator[aiohttp.ClientResponse]:
200         async with aiohttp.ClientSession() as session:
201             async with session.get(
202                 urljoin(self.base_url, "oauth2/userInfo"),
203                 headers=self.get_access_token_header(),
204             ) as response:
205                 yield response
206 
207     async def get_user_info_response(self) -> aiohttp.ClientResponse:
208         """Get user info.
209 
210         Returns:
211             User info response
212         """
213         async with self._get_user_info_response() as response:
214             return response
215 
216     async def get_user_info_json(self) -> dict:
217         """Get user info as dict.
218 
219         Returns:
220             User info json
221         """
222         async with self._get_user_info_response() as response:
223             return await response.json()
224 
225     async def logged_in(self) -> bool:
226         """Check if this instance is currently logged in.
227 
228         Returns:
229             True if logged in, else False
230         """
231         return bool(
232             self.config.access_token
233             and self.config.id_token
234             # Perform this check at the end to avoid
235             # spamming our servers if logout fails
236             and (await self.get_user_info_response()).ok,
237         )
238
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/cloud-cli/meltano/cloud/api/auth/auth.py b/src/cloud-cli/meltano/cloud/api/auth/auth.py
--- a/src/cloud-cli/meltano/cloud/api/auth/auth.py
+++ b/src/cloud-cli/meltano/cloud/api/auth/auth.py
@@ -124,7 +124,7 @@
                 web.get("/logout", handle_logout),
             ),
         )
-        runner = web.AppRunner(app)
+        runner = web.AppRunner(app, access_log=None)
         await runner.setup()
         site = web.TCPSite(runner, "localhost", self.config.auth_callback_port)
         await site.start()

{"golden_diff": "diff --git a/src/cloud-cli/meltano/cloud/api/auth/auth.py b/src/cloud-cli/meltano/cloud/api/auth/auth.py\n--- a/src/cloud-cli/meltano/cloud/api/auth/auth.py\n+++ b/src/cloud-cli/meltano/cloud/api/auth/auth.py\n@@ -124,7 +124,7 @@\n                 web.get(\"/logout\", handle_logout),\n             ),\n         )\n-        runner = web.AppRunner(app)\n+        runner = web.AppRunner(app, access_log=None)\n         await runner.setup()\n         site = web.TCPSite(runner, \"localhost\", self.config.auth_callback_port)\n         await site.start()\n", "issue": "bug: Cloud CLI logs auth token at info level\n### Meltano Version\n\ncloud branch\n\n### Python Version\n\nNA\n\n### Bug scope\n\nCLI (options, error messages, logging, etc.)\n\n### Operating System\n\nN/A\n\n### Description\n\nRunning `meltano cloud login` results in the input shown below. Running `meltano-cloud login` does not.\r\n\r\nA good resolution to this would probably be to figure out what is logging these HTTP requests, and silence those log messages. My guess is that it has to do with the recent change to use `aiohttp` instead of Flask for the local auth server.\n\n### Code\n\n```python\nLogging in to Meltano Cloud.\r\nYou will be directed to a web browser to complete login.\r\nIf a web browser does not open, open the following link:\r\nhttps://auth.meltano.cloud/oauth2/authorize?client_id=45rpn5ep3g4qjut8jd3s4iq872&response_type=token&scope=email+openid+profile&redirect_uri=http%3A%2F%2Flocalhost%3A9999\r\n2023-04-04T16:09:25.658362Z [info     ] 127.0.0.1 [04/Apr/2023:16:09:25 +0000] \"GET / HTTP/1.1\" 200 236 \"-\" \"Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0\"\r\n2023-04-04T16:09:25.780667Z [info     ] 127.0.0.1 [04/Apr/2023:16:09:25 +0000] \"GET /tokens?access_token=<redacted>&token_type=Bearer&expires_in=28800 HTTP/1.1\" 204 99 \"http://localhost:9999/\" \"Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0\"\n```\n\n", "before_files": [{"content": "\"\"\"Authentication for Meltano Cloud.\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nimport sys\nimport tempfile\nimport typing as t\nimport webbrowser\nfrom contextlib import asynccontextmanager\nfrom http import HTTPStatus\nfrom pathlib import Path\nfrom urllib.parse import urlencode, urljoin\n\nimport aiohttp\nimport click\nimport jinja2\nfrom aiohttp import web\n\nfrom meltano.cloud.api.config import MeltanoCloudConfig\n\nif sys.version_info <= (3, 8):\n    from cached_property import cached_property\nelse:\n    from functools import cached_property\n\nif sys.version_info < (3, 9):\n    import importlib_resources\nelse:\n    from importlib import resources as importlib_resources\n\nLOGIN_STATUS_CHECK_DELAY_SECONDS = 0.2\n\n\nclass MeltanoCloudAuthError(Exception):\n    \"\"\"Raised when an API call returns a 403.\"\"\"\n\n\nclass MeltanoCloudAuth:  # noqa: WPS214\n    \"\"\"Authentication methods for Meltano Cloud.\"\"\"\n\n    def __init__(self, config: MeltanoCloudConfig | None = None):\n        \"\"\"Initialize a MeltanoCloudAuth instance.\n\n        Args:\n            config: the MeltanoCloudConfig to use\n        \"\"\"\n        self.config = config or MeltanoCloudConfig.find()\n        self.base_url = self.config.base_auth_url\n        self.client_id = self.config.app_client_id\n\n    @cached_property\n    def login_url(self) -> str:\n        \"\"\"Get the oauth2 authorization URL.\n\n        Returns:\n            the oauth2 authorization URL.\n        \"\"\"\n        query_params = urlencode(\n            {\n                \"client_id\": self.client_id,\n                \"response_type\": \"token\",\n                \"scope\": \"email openid profile\",\n                \"redirect_uri\": f\"http://localhost:{self.config.auth_callback_port}\",\n            },\n        )\n        return f\"{self.base_url}/oauth2/authorize?{query_params}\"\n\n    @cached_property\n    def logout_url(self) -> str:\n        \"\"\"Get the Meltano Cloud logout URL.\n\n        Returns:\n            the Meltano Cloud logout URL.\n        \"\"\"\n        params = urlencode(\n            {\n                \"client_id\": self.client_id,\n                \"logout_uri\": f\"http://localhost:{self.config.auth_callback_port}/logout\",  # noqa: E501)\n            },\n        )\n        return urljoin(self.base_url, f\"logout?{params}\")\n\n    @asynccontextmanager\n    async def _callback_server(\n        self,\n        rendered_template_dir: Path,\n    ) -> t.AsyncIterator[web.Application]:\n        app = web.Application()\n        resource_root = importlib_resources.files(__package__)\n\n        async def callback_page(_):\n            with importlib_resources.as_file(\n                resource_root / \"callback.jinja2\",\n            ) as template_file, (rendered_template_dir / \"callback.html\").open(\n                \"w\",\n            ) as rendered_template_file:\n                rendered_template_file.write(\n                    jinja2.Template(template_file.read_text()).render(\n                        port=self.config.auth_callback_port,\n                    ),\n                )\n            return web.FileResponse(rendered_template_file.name)\n\n        async def handle_tokens(request: web.Request):\n            self.config.id_token = request.query[\"id_token\"]\n            self.config.access_token = request.query[\"access_token\"]\n            self.config.write_to_file()\n            return web.Response(status=HTTPStatus.NO_CONTENT)\n\n        async def handle_logout(_):\n            self.config.id_token = None\n            self.config.access_token = None\n            self.config.write_to_file()\n            with importlib_resources.as_file(\n                resource_root / \"logout.html\",\n            ) as html_file:\n                return web.FileResponse(html_file)\n\n        app.add_routes(\n            (\n                web.get(\"/\", callback_page),\n                web.get(\"/tokens\", handle_tokens),\n                web.get(\"/logout\", handle_logout),\n            ),\n        )\n        runner = web.AppRunner(app)\n        await runner.setup()\n        site = web.TCPSite(runner, \"localhost\", self.config.auth_callback_port)\n        await site.start()\n        try:\n            yield app\n        finally:\n            await runner.cleanup()\n\n    @asynccontextmanager\n    async def callback_server(self) -> t.AsyncIterator[web.Application]:\n        \"\"\"Context manager to run callback server locally.\n\n        Yields:\n            The aiohttp web application.\n        \"\"\"\n        with tempfile.TemporaryDirectory(prefix=\"meltano-cloud-\") as tmpdir:\n            async with self._callback_server(Path(tmpdir)) as app:\n                yield app\n\n    async def login(self) -> None:\n        \"\"\"Take user through login flow and get auth and id tokens.\"\"\"\n        if await self.logged_in():\n            return\n        async with self.callback_server():\n            click.echo(\"Logging in to Meltano Cloud.\")\n            click.echo(\"You will be directed to a web browser to complete login.\")\n            click.echo(\"If a web browser does not open, open the following link:\")\n            click.secho(self.login_url, fg=\"green\")\n            webbrowser.open_new_tab(self.login_url)\n            while not await self.logged_in():\n                self.config.refresh()\n                await asyncio.sleep(LOGIN_STATUS_CHECK_DELAY_SECONDS)\n\n    async def logout(self) -> None:  # noqa: WPS213\n        \"\"\"Log out.\"\"\"\n        if not await self.logged_in():\n            click.secho(\"Not logged in.\", fg=\"green\")\n            return\n        async with self.callback_server():\n            click.echo(\"Logging out of Meltano Cloud.\")\n            click.echo(\"You will be directed to a web browser to complete logout.\")\n            click.echo(\"If a web browser does not open, open the following link:\")\n            click.secho(self.logout_url, fg=\"green\")\n            webbrowser.open_new_tab(self.logout_url)\n            while await self.logged_in():\n                self.config.refresh()\n                await asyncio.sleep(LOGIN_STATUS_CHECK_DELAY_SECONDS)\n        click.secho(\"Successfully logged out.\", fg=\"green\")\n\n    def get_auth_header(self) -> dict[str, str]:\n        \"\"\"Get the authorization header.\n\n        Used for authenticating to cloud API endpoints.\n\n        Returns:\n            Authorization header using ID token as bearer token.\n\n        \"\"\"\n        return {\"Authorization\": f\"Bearer {self.config.id_token}\"}\n\n    def get_access_token_header(self) -> dict[str, str]:\n        \"\"\"Get the access token header.\n\n        Used for authenticating to auth endpoints.\n\n        Returns:\n            Authorization header using access token as bearer token.\n        \"\"\"\n        return {\"Authorization\": f\"Bearer {self.config.access_token}\"}\n\n    @asynccontextmanager\n    async def _get_user_info_response(self) -> t.AsyncIterator[aiohttp.ClientResponse]:\n        async with aiohttp.ClientSession() as session:\n            async with session.get(\n                urljoin(self.base_url, \"oauth2/userInfo\"),\n                headers=self.get_access_token_header(),\n            ) as response:\n                yield response\n\n    async def get_user_info_response(self) -> aiohttp.ClientResponse:\n        \"\"\"Get user info.\n\n        Returns:\n            User info response\n        \"\"\"\n        async with self._get_user_info_response() as response:\n            return response\n\n    async def get_user_info_json(self) -> dict:\n        \"\"\"Get user info as dict.\n\n        Returns:\n            User info json\n        \"\"\"\n        async with self._get_user_info_response() as response:\n            return await response.json()\n\n    async def logged_in(self) -> bool:\n        \"\"\"Check if this instance is currently logged in.\n\n        Returns:\n            True if logged in, else False\n        \"\"\"\n        return bool(\n            self.config.access_token\n            and self.config.id_token\n            # Perform this check at the end to avoid\n            # spamming our servers if logout fails\n            and (await self.get_user_info_response()).ok,\n        )\n", "path": "src/cloud-cli/meltano/cloud/api/auth/auth.py"}], "after_files": [{"content": "\"\"\"Authentication for Meltano Cloud.\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nimport sys\nimport tempfile\nimport typing as t\nimport webbrowser\nfrom contextlib import asynccontextmanager\nfrom http import HTTPStatus\nfrom pathlib import Path\nfrom urllib.parse import urlencode, urljoin\n\nimport aiohttp\nimport click\nimport jinja2\nfrom aiohttp import web\n\nfrom meltano.cloud.api.config import MeltanoCloudConfig\n\nif sys.version_info <= (3, 8):\n    from cached_property import cached_property\nelse:\n    from functools import cached_property\n\nif sys.version_info < (3, 9):\n    import importlib_resources\nelse:\n    from importlib import resources as importlib_resources\n\nLOGIN_STATUS_CHECK_DELAY_SECONDS = 0.2\n\n\nclass MeltanoCloudAuthError(Exception):\n    \"\"\"Raised when an API call returns a 403.\"\"\"\n\n\nclass MeltanoCloudAuth:  # noqa: WPS214\n    \"\"\"Authentication methods for Meltano Cloud.\"\"\"\n\n    def __init__(self, config: MeltanoCloudConfig | None = None):\n        \"\"\"Initialize a MeltanoCloudAuth instance.\n\n        Args:\n            config: the MeltanoCloudConfig to use\n        \"\"\"\n        self.config = config or MeltanoCloudConfig.find()\n        self.base_url = self.config.base_auth_url\n        self.client_id = self.config.app_client_id\n\n    @cached_property\n    def login_url(self) -> str:\n        \"\"\"Get the oauth2 authorization URL.\n\n        Returns:\n            the oauth2 authorization URL.\n        \"\"\"\n        query_params = urlencode(\n            {\n                \"client_id\": self.client_id,\n                \"response_type\": \"token\",\n                \"scope\": \"email openid profile\",\n                \"redirect_uri\": f\"http://localhost:{self.config.auth_callback_port}\",\n            },\n        )\n        return f\"{self.base_url}/oauth2/authorize?{query_params}\"\n\n    @cached_property\n    def logout_url(self) -> str:\n        \"\"\"Get the Meltano Cloud logout URL.\n\n        Returns:\n            the Meltano Cloud logout URL.\n        \"\"\"\n        params = urlencode(\n            {\n                \"client_id\": self.client_id,\n                \"logout_uri\": f\"http://localhost:{self.config.auth_callback_port}/logout\",  # noqa: E501)\n            },\n        )\n        return urljoin(self.base_url, f\"logout?{params}\")\n\n    @asynccontextmanager\n    async def _callback_server(\n        self,\n        rendered_template_dir: Path,\n    ) -> t.AsyncIterator[web.Application]:\n        app = web.Application()\n        resource_root = importlib_resources.files(__package__)\n\n        async def callback_page(_):\n            with importlib_resources.as_file(\n                resource_root / \"callback.jinja2\",\n            ) as template_file, (rendered_template_dir / \"callback.html\").open(\n                \"w\",\n            ) as rendered_template_file:\n                rendered_template_file.write(\n                    jinja2.Template(template_file.read_text()).render(\n                        port=self.config.auth_callback_port,\n                    ),\n                )\n            return web.FileResponse(rendered_template_file.name)\n\n        async def handle_tokens(request: web.Request):\n            self.config.id_token = request.query[\"id_token\"]\n            self.config.access_token = request.query[\"access_token\"]\n            self.config.write_to_file()\n            return web.Response(status=HTTPStatus.NO_CONTENT)\n\n        async def handle_logout(_):\n            self.config.id_token = None\n            self.config.access_token = None\n            self.config.write_to_file()\n            with importlib_resources.as_file(\n                resource_root / \"logout.html\",\n            ) as html_file:\n                return web.FileResponse(html_file)\n\n        app.add_routes(\n            (\n                web.get(\"/\", callback_page),\n                web.get(\"/tokens\", handle_tokens),\n                web.get(\"/logout\", handle_logout),\n            ),\n        )\n        runner = web.AppRunner(app, access_log=None)\n        await runner.setup()\n        site = web.TCPSite(runner, \"localhost\", self.config.auth_callback_port)\n        await site.start()\n        try:\n            yield app\n        finally:\n            await runner.cleanup()\n\n    @asynccontextmanager\n    async def callback_server(self) -> t.AsyncIterator[web.Application]:\n        \"\"\"Context manager to run callback server locally.\n\n        Yields:\n            The aiohttp web application.\n        \"\"\"\n        with tempfile.TemporaryDirectory(prefix=\"meltano-cloud-\") as tmpdir:\n            async with self._callback_server(Path(tmpdir)) as app:\n                yield app\n\n    async def login(self) -> None:\n        \"\"\"Take user through login flow and get auth and id tokens.\"\"\"\n        if await self.logged_in():\n            return\n        async with self.callback_server():\n            click.echo(\"Logging in to Meltano Cloud.\")\n            click.echo(\"You will be directed to a web browser to complete login.\")\n            click.echo(\"If a web browser does not open, open the following link:\")\n            click.secho(self.login_url, fg=\"green\")\n            webbrowser.open_new_tab(self.login_url)\n            while not await self.logged_in():\n                self.config.refresh()\n                await asyncio.sleep(LOGIN_STATUS_CHECK_DELAY_SECONDS)\n\n    async def logout(self) -> None:  # noqa: WPS213\n        \"\"\"Log out.\"\"\"\n        if not await self.logged_in():\n            click.secho(\"Not logged in.\", fg=\"green\")\n            return\n        async with self.callback_server():\n            click.echo(\"Logging out of Meltano Cloud.\")\n            click.echo(\"You will be directed to a web browser to complete logout.\")\n            click.echo(\"If a web browser does not open, open the following link:\")\n            click.secho(self.logout_url, fg=\"green\")\n            webbrowser.open_new_tab(self.logout_url)\n            while await self.logged_in():\n                self.config.refresh()\n                await asyncio.sleep(LOGIN_STATUS_CHECK_DELAY_SECONDS)\n        click.secho(\"Successfully logged out.\", fg=\"green\")\n\n    def get_auth_header(self) -> dict[str, str]:\n        \"\"\"Get the authorization header.\n\n        Used for authenticating to cloud API endpoints.\n\n        Returns:\n            Authorization header using ID token as bearer token.\n\n        \"\"\"\n        return {\"Authorization\": f\"Bearer {self.config.id_token}\"}\n\n    def get_access_token_header(self) -> dict[str, str]:\n        \"\"\"Get the access token header.\n\n        Used for authenticating to auth endpoints.\n\n        Returns:\n            Authorization header using access token as bearer token.\n        \"\"\"\n        return {\"Authorization\": f\"Bearer {self.config.access_token}\"}\n\n    @asynccontextmanager\n    async def _get_user_info_response(self) -> t.AsyncIterator[aiohttp.ClientResponse]:\n        async with aiohttp.ClientSession() as session:\n            async with session.get(\n                urljoin(self.base_url, \"oauth2/userInfo\"),\n                headers=self.get_access_token_header(),\n            ) as response:\n                yield response\n\n    async def get_user_info_response(self) -> aiohttp.ClientResponse:\n        \"\"\"Get user info.\n\n        Returns:\n            User info response\n        \"\"\"\n        async with self._get_user_info_response() as response:\n            return response\n\n    async def get_user_info_json(self) -> dict:\n        \"\"\"Get user info as dict.\n\n        Returns:\n            User info json\n        \"\"\"\n        async with self._get_user_info_response() as response:\n            return await response.json()\n\n    async def logged_in(self) -> bool:\n        \"\"\"Check if this instance is currently logged in.\n\n        Returns:\n            True if logged in, else False\n        \"\"\"\n        return bool(\n            self.config.access_token\n            and self.config.id_token\n            # Perform this check at the end to avoid\n            # spamming our servers if logout fails\n            and (await self.get_user_info_response()).ok,\n        )\n", "path": "src/cloud-cli/meltano/cloud/api/auth/auth.py"}]}