Closed SelfhostedPro closed 3 years ago
Possible solution would be to add a function that just validates the JWT and CSRF (if it's enabled) that can be imported so that I can use it in my websocket routes.
Here's the function I'm currently trying to get this to work on:
@router.websocket("/stats")
async def dashboard(websocket: WebSocket):
auth_success = await websocket_auth(websocket=websocket)
if auth_success:
await websocket.accept()
tasks = []
async with aiodocker.Docker() as docker:
containers = []
_containers = await docker.containers.list()
for _app in _containers:
if _app._container["State"] == "running":
containers.append(_app)
for app in containers:
_name = app._container["Names"][0][1:]
container: DockerContainer = await docker.containers.get(_name)
stats = container.stats(stream=True)
tasks.append(process_container(_name, stats, websocket))
await asyncio.gather(*tasks)
else:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
async def process_container(name, stats, websocket):
cpu_total = 0.0
cpu_system = 0.0
cpu_percent = 0.0
async for line in stats:
if line["memory_stats"]:
mem_current = line["memory_stats"]["usage"]
mem_total = line["memory_stats"]["limit"]
mem_percent = (mem_current / mem_total) * 100.0
else:
mem_current = None
mem_total = None
mem_percent = None
try:
cpu_percent, cpu_system, cpu_total = await calculate_cpu_percent2(
line, cpu_total, cpu_system
)
except KeyError as e:
print("error while getting new CPU stats: %r, falling back")
cpu_percent = await calculate_cpu_percent(line)
full_stats = {
"name": name,
"cpu_percent": cpu_percent,
"mem_current": mem_current,
"mem_total": mem_total,
"mem_percent": mem_percent,
}
try:
await websocket.send_text(json.dumps(full_stats))
except Exception as e:
pass
auth_success = await websocket_auth(websocket=websocket)
was a function that used a function from FastAPI-users to validate the jwt token in the cookie.
async def websocket_auth(websocket: WebSocket):
try:
cookie = websocket._cookies["fastapiusersauth"]
user = await cookie_authentication(cookie, user_db)
if user and user.is_active:
return user
elif settings.DISABLE_AUTH == "True":
return True
except:
if settings.DISABLE_AUTH == "True":
return True
else:
return None
(Just wanted to add an example of what manual validation could look like)
Hi @SelfhostedPro, I know the problem it is, fastapi-jwt-auth extract data which is cookies or headers from request instead from WebSocket, I will fix this in the next version thanks π
Glad to hear it! Super excited to be using this in my project.
Hi @SelfhostedPro you can get a new version for support WebSocket, also you can check the documentation here, if you have any question or suggestion please let me now π , or if this version enough for you, you can close this issue thankyou π
I'll try it out today and let you know how it goes. Thanks!
I'm currently getting the following error in my websocket functions. Seems like it doesn't like me specifying the token argument for some reason.
TypeError: jwt_required() got an unexpected keyword argument 'token'
This is the function in question:
@router.websocket("/{app_name}/livelogs")
async def logs(websocket: WebSocket, app_name: str, Authorize: AuthJWT = Depends()):
try:
csrf = websocket._cookies["csrf_access_token"]
Authorize.jwt_required("websocket", token=csrf)
except AuthJWTException as err:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
await websocket.accept()
async with aiodocker.Docker() as docker:
container: DockerContainer = await docker.containers.get(app_name)
if container._container["State"]["Status"] == "running":
stats = container.stats(stream=True)
logs = container.log(stdout=True, stderr=True, follow=True)
async for line in logs:
try:
await websocket.send_text(line)
except Exception as e:
return e
else:
await websocket.close(code=status.WS_1011_INTERNAL_ERROR)
Here's the full error:
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/home/user/dev/work/venv/lib/python3.8/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 154, in run_asgi
result = await self.app(self.scope, self.asgi_receive, self.asgi_send)
File "/home/user/dev/work/venv/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "/home/user/dev/work/venv/lib/python3.8/site-packages/fastapi/applications.py", line 179, in __call__
await super().__call__(scope, receive, send)
File "/home/user/dev/work/venv/lib/python3.8/site-packages/starlette/applications.py", line 111, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/user/dev/work/venv/lib/python3.8/site-packages/starlette/middleware/errors.py", line 146, in __call__
await self.app(scope, receive, send)
File "/home/user/dev/work/venv/lib/python3.8/site-packages/starlette/exceptions.py", line 58, in __call__
await self.app(scope, receive, send)
File "/home/user/dev/work/venv/lib/python3.8/site-packages/starlette/routing.py", line 566, in __call__
await route.handle(scope, receive, send)
File "/home/user/dev/work/venv/lib/python3.8/site-packages/starlette/routing.py", line 283, in handle
await self.app(scope, receive, send)
File "/home/user/dev/work/venv/lib/python3.8/site-packages/starlette/routing.py", line 57, in app
await func(session)
File "/home/user/dev/work/venv/lib/python3.8/site-packages/fastapi/routing.py", line 228, in app
await dependant.call(**values)
File "./backend/api/routers/apps.py", line 118, in stats
Authorize.jwt_required("websocket", token=csrf)
TypeError: jwt_required() got an unexpected keyword argument 'token'
btw if you want to authorization with cookies you must be passing WebSocket instance to
try:
csrf = websocket._cookies["csrf_access_token"]
Authorize.jwt_required("websocket", websocket=websocket, token=csrf) # like this
except AuthJWTException: # delete as err if you no need that message
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
Changed over to that as well with no luck. Getting the following error:
TypeError: jwt_required() got an unexpected keyword argument 'websocket'
@router.websocket("/{app_name}/livelogs")
async def logs(websocket: WebSocket, app_name: str, Authorize: AuthJWT = Depends()):
try:
csrf = websocket._cookies["csrf_access_token"]
Authorize.jwt_required("websocket",websocket=websocket,csrf_token=csrf)
except AuthJWTException:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
await websocket.accept()
async with aiodocker.Docker() as docker:
container: DockerContainer = await docker.containers.get(app_name)
if container._container["State"]["Status"] == "running":
stats = container.stats(stream=True)
logs = container.log(stdout=True, stderr=True, follow=True)
async for line in logs:
try:
await websocket.send_text(line)
except Exception as e:
return e
else:
await websocket.close(code=status.WS_1011_INTERNAL_ERROR)
This is to show I'm on the latest version:
[user@user-desktop Yacht]$ pip show fastapi-jwt-auth
Name: fastapi-jwt-auth
Version: 0.5.0
Summary: FastAPI extension that provides JWT Auth support (secure, easy to use and lightweight)
Home-page: https://github.com/IndominusByte/fastapi-jwt-auth
Author: Nyoman Pradipta Dewantara
Author-email: nyomanpradipta120@gmail.com
License: UNKNOWN
Location: /home/user/dev/Yacht-work/Yacht/backend/venv/lib/python3.8/site-packages
Requires: fastapi, PyJWT
Required-by:
Here is the source code for that section of my app if you wanted to know that as well:
Can you try a fresh install? its doesn't make sense my package doesn't have websocket or token argument its like your application use my previous version which is doesn't have that all argument
Deleted my project, cloned, setup a new venv, and installed with --no-cache-dir and still getting the same error. Is it possible pypi doesn't have the correct files in the new version?
Checked on pypi and in my venv and it looks like it's the correct version.
Also tried just doing positional arguments with no luck.
TypeError: jwt_required() takes 1 positional argument but 5 were given
@router.websocket("/stats")
async def dashboard(websocket: WebSocket, Authorize: AuthJWT = Depends()):
try:
csrf = websocket._cookies["csrf_access_token"]
Authorize.jwt_required("websocket",None,websocket,csrf)
except AuthJWTException:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
await websocket.accept()
tasks = []
async with aiodocker.Docker() as docker:
containers = []
_containers = await docker.containers.list()
for _app in _containers:
if _app._container["State"] == "running":
containers.append(_app)
for app in containers:
_name = app._container["Names"][0][1:]
container: DockerContainer = await docker.containers.get(_name)
stats = container.stats(stream=True)
tasks.append(process_container(_name, stats, websocket))
await asyncio.gather(*tasks)
If you'd be willing to try out my project and see here are the instructions:
backend:
git clone https://github.com/selfhostedpro/yacht.git
cd Yacht
cd backend
pip install -r requirements.txt
frontend(in another terminal):
cd frontend
npm i
npm run serve
If you use vscode, this is the launch.json I use for debugging:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Module",
"type": "python",
"request": "launch",
"module": "uvicorn",
"args": [
"backend.api.main:app",
"--reload"
]
}
]
}
Try this cat env/lib/python3.8/site-packages/fastapi_jwt_auth/auth_jwt.py then screenshot to me in the function jwt_required() ? Does it look like this?
Yeah it does:
Okay, I will try to run your application
Thank you. I appreciate it. One instruction I forgot is to make a config
directory in the root folder of the project like this:
Default username is admin@yacht.local
default password is pass
. You will also need to have docker installed and have one running container to get to the place where I'm having the error. (or you can use websocket king to simulate a user going to the following websocket: localhost:8080/api/apps/appname/livelogs )
Have a friend who's using the same example as in your documentation and is getting the same error. Nothing is popping out to me as far as where the issue is in fastapi-jwt-auth though.
Testing on your application and its working
Are you using a virtual environment? I've never run into something like this before so I'm not really sure where to go from here as far as troubleshooting. I'll try re-imaging my machine after work today and see if that changes anything.
I testing it on docker because you provide Dockerfile, and I don't get an error message as you get. and I try to different machine and work to
Use the virtual environment in a different machine is working well to
I can confirm it's working within docker. Thanks!
Great! glad to hear that π π
I assume the original issue was resolved. But feel free to add more comments or create new issues π π
Currently it looks as though websockets wont work with the standard require_jwt_auth() even when sent via cookies (which works with flask_jwt_extended). This is the error I'm getting: