diff --git a/syng/client.py b/syng/client.py index 5776d70..ad2ceb9 100644 --- a/syng/client.py +++ b/syng/client.py @@ -14,6 +14,7 @@ Excerp from the help:: --room ROOM, -r ROOM --secret SECRET, -s SECRET --config-file CONFIG_FILE, -C CONFIG_FILE + --key KEY, -k KEY The config file should be a json file in the following style:: @@ -82,6 +83,8 @@ class State: a room, this must be identical. Also, if a webclient wants to have admin privileges, this must be included. :type secret: str + :param key: An optional key, if registration on the server is limited. + :type key: Optional[str] :param preview_duration: Amount of seconds the preview before a song be displayed. :type preview_duration: int @@ -98,6 +101,7 @@ class State: room: str = "" server: str = "" secret: str = "" + key: Optional[str] = None preview_duration: int = 3 last_song: Optional[datetime.datetime] = None @@ -187,16 +191,16 @@ async def handle_connect() -> None: :rtype: None """ logging.info("Connected to server") - await sio.emit( - "register-client", - { - "queue": state.queue, - "recent": state.recent, - "room": state.room, - "secret": state.secret, - "config": state.get_config(), - }, - ) + data = { + "queue": state.queue, + "recent": state.recent, + "room": state.room, + "secret": state.secret, + "config": state.get_config(), + } + if state.key: + data["registration-key"] = state.key + await sio.emit("register-client", data) @sio.on("get-meta-info") @@ -390,6 +394,7 @@ async def aiomain() -> None: parser.add_argument("--room", "-r") parser.add_argument("--secret", "-s") parser.add_argument("--config-file", "-C", default="syng-client.json") + parser.add_argument("--key", "-k", default=None) parser.add_argument("server") args = parser.parse_args() @@ -406,6 +411,8 @@ async def aiomain() -> None: if "preview_duration" in config["config"]: state.preview_duration = config["config"]["preview_duration"] + state.key = args.key if args.key else None + if args.room: state.room = args.room diff --git a/syng/server.py b/syng/server.py index baa4d85..9aba59a 100644 --- a/syng/server.py +++ b/syng/server.py @@ -16,6 +16,7 @@ from __future__ import annotations import asyncio import datetime +import hashlib import logging import os import random @@ -350,10 +351,12 @@ async def handle_register_client(sid: str, data: dict[str, Any]) -> None: Handle the "register-client" message. The data dictionary should have the following keys: + - `registration_key` (Optional), a key corresponding to those stored + in `app["registration-keyfile"]` - `room` (Optional), the requested room - `config`, an dictionary of initial configurations - `queue`, a list of initial entries for the queue. The entries are - encoded as a dictionary. + encoded as a dictionary. - `recent`, a list of initial entries for the recent list. The entries are encoded as a dictionary. - `secret`, the secret of the room @@ -363,6 +366,9 @@ async def handle_register_client(sid: str, data: dict[str, Any]) -> None: playback client will be replaced if and only if, the new playback client has the same secret. + If registration is restricted, abort, if the given key is not in the + registration keyfile. + If no room is provided, a fresh room id is generated. If the client provides a new room, or a new room id was generated, the @@ -394,6 +400,25 @@ async def handle_register_client(sid: str, data: dict[str, Any]) -> None: client_id = gen_id(length + 1) return client_id + if not app["public"]: + with open(app["registration-keyfile"]) as f: + raw_keys = f.readlines() + keys = [key[:64] for key in raw_keys] + + if ( + "registration-key" not in data + or hashlib.sha256( + data["registration-key"].encode() + ).hexdigest() + not in keys + ): + await sio.emit( + "client-registered", + {"success": False, "room": None}, + room=sid, + ) + return + room: str = data["room"] if "room" in data and data["room"] else gen_id() async with sio.session(sid) as session: session["room"] = room @@ -791,8 +816,14 @@ def main() -> None: parser.add_argument("--host", "-H", default="localhost") parser.add_argument("--port", "-p", default="8080") parser.add_argument("--root-folder", "-r", default="syng/static/") + parser.add_argument("--registration-keyfile", "-k", default=None) args = parser.parse_args() + app["public"] = True + if args.registration_keyfile: + app["public"] = False + app["registration-keyfile"] = args.registration_keyfile + app["root_folder"] = args.root_folder app.add_routes(