mirror of
https://github.com/umap-project/umap.git
synced 2025-05-04 05:31:50 +02:00
wip(sync): do not send token on query string
It's not safe, it's logged in and can be easily intercepted. A bit of documentation on websocket auth: https://websockets.readthedocs.io/en/stable/topics/authentication.html
This commit is contained in:
parent
e865c31d69
commit
8cf2451142
4 changed files with 16 additions and 26 deletions
|
@ -17,8 +17,6 @@ urlpatterns = (re_path(r"ws/sync/(?P<map_id>\w+)/$", consumers.SyncConsumer.as_a
|
||||||
application = ProtocolTypeRouter(
|
application = ProtocolTypeRouter(
|
||||||
{
|
{
|
||||||
"http": django_asgi_app,
|
"http": django_asgi_app,
|
||||||
"websocket": consumers.TokenMiddleware(
|
"websocket": AllowedHostsOriginValidator(URLRouter(urlpatterns)),
|
||||||
AllowedHostsOriginValidator(URLRouter(urlpatterns))
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,7 +7,7 @@ export class WebSocketTransport {
|
||||||
this.receiver = messagesReceiver
|
this.receiver = messagesReceiver
|
||||||
this.closeRequested = false
|
this.closeRequested = false
|
||||||
|
|
||||||
this.websocket = new WebSocket(`${webSocketURI}?${authToken}`)
|
this.websocket = new WebSocket(`${webSocketURI}`)
|
||||||
|
|
||||||
this.websocket.onopen = () => {
|
this.websocket.onopen = () => {
|
||||||
this.send('JoinRequest', { token: authToken })
|
this.send('JoinRequest', { token: authToken })
|
||||||
|
|
|
@ -14,21 +14,6 @@ from .payloads import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TokenMiddleware:
|
|
||||||
def __init__(self, app):
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
async def __call__(self, scope, receive, send):
|
|
||||||
signed = TimestampSigner().unsign_object(
|
|
||||||
scope["query_string"].decode(), max_age=30
|
|
||||||
)
|
|
||||||
user, map_id, permissions = signed.values()
|
|
||||||
if "edit" not in permissions:
|
|
||||||
raise ValueError("Invalid Token")
|
|
||||||
scope["user"] = user
|
|
||||||
return await self.app(scope, receive, send)
|
|
||||||
|
|
||||||
|
|
||||||
class SyncConsumer(AsyncWebsocketConsumer):
|
class SyncConsumer(AsyncWebsocketConsumer):
|
||||||
@property
|
@property
|
||||||
def peers(self):
|
def peers(self):
|
||||||
|
@ -41,7 +26,7 @@ class SyncConsumer(AsyncWebsocketConsumer):
|
||||||
await self.channel_layer.group_add(self.map_id, self.channel_name)
|
await self.channel_layer.group_add(self.map_id, self.channel_name)
|
||||||
|
|
||||||
await self.accept()
|
await self.accept()
|
||||||
await self.send_peers_list()
|
self.is_authenticated = False
|
||||||
|
|
||||||
async def disconnect(self, close_code):
|
async def disconnect(self, close_code):
|
||||||
await self.channel_layer.group_discard(self.map_id, self.channel_name)
|
await self.channel_layer.group_discard(self.map_id, self.channel_name)
|
||||||
|
@ -68,6 +53,18 @@ class SyncConsumer(AsyncWebsocketConsumer):
|
||||||
await self.send(event["message"])
|
await self.send(event["message"])
|
||||||
|
|
||||||
async def receive(self, text_data):
|
async def receive(self, text_data):
|
||||||
|
if not self.is_authenticated:
|
||||||
|
message = JoinRequest.model_validate_json(text_data)
|
||||||
|
signed = TimestampSigner().unsign_object(message.token, max_age=30)
|
||||||
|
user, map_id, permissions = signed.values()
|
||||||
|
if "edit" not in permissions:
|
||||||
|
return await self.disconnect()
|
||||||
|
response = JoinResponse(uuid=self.channel_name, peers=self.peers)
|
||||||
|
await self.send(response.model_dump_json())
|
||||||
|
await self.send_peers_list()
|
||||||
|
self.is_authenticated = True
|
||||||
|
return
|
||||||
|
|
||||||
if text_data == "ping":
|
if text_data == "ping":
|
||||||
return await self.send("pong")
|
return await self.send("pong")
|
||||||
|
|
||||||
|
@ -81,9 +78,6 @@ class SyncConsumer(AsyncWebsocketConsumer):
|
||||||
else:
|
else:
|
||||||
match incoming.root:
|
match incoming.root:
|
||||||
# Broadcast all operation messages to connected peers
|
# Broadcast all operation messages to connected peers
|
||||||
case JoinRequest():
|
|
||||||
response = JoinResponse(uuid=self.channel_name, peers=self.peers)
|
|
||||||
await self.send(response.model_dump_json())
|
|
||||||
case OperationMessage():
|
case OperationMessage():
|
||||||
await self.broadcast(text_data)
|
await self.broadcast(text_data)
|
||||||
|
|
||||||
|
|
|
@ -31,9 +31,7 @@ class PeerMessage(BaseModel):
|
||||||
class Request(RootModel):
|
class Request(RootModel):
|
||||||
"""Any message coming from the websocket should be one of these, and will be rejected otherwise."""
|
"""Any message coming from the websocket should be one of these, and will be rejected otherwise."""
|
||||||
|
|
||||||
root: Union[PeerMessage, OperationMessage, JoinRequest] = Field(
|
root: Union[PeerMessage, OperationMessage] = Field(discriminator="kind")
|
||||||
discriminator="kind"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class JoinResponse(BaseModel):
|
class JoinResponse(BaseModel):
|
||||||
|
|
Loading…
Reference in a new issue