diff --git a/Makefile b/Makefile index 3dfa0f6..8baf5c3 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,3 @@ make: - python3 -m venv ./.venv - ./.venv/bin/python3 -m pip install -r requirements.txt + python3 -m venv venv + ./venv/bin/python3 -m pip install -r requirements.txt diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index d37b63e..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,50 +0,0 @@ -# Security Policy - - - - -| | | -| ------------------- | ------------------------------------- | -| :white_check_mark: | Full support | -| :warning: | Support to be dropped in next version | -| :x: | No support | - ---- - -## Supported Versions - - -| Version | Supported | -| ------- | ------------------ | -| 1.0.2 | :white_check_mark: | -| 1.0.1 | :white_check_mark: | -| 1.0.0 | :warning: | -| < 0.0 | :x: | - -## Reporting a Vulnerability - -If you find a vulnerability please contact the maintainer at - -`me@mirandaniel.com`, `F42DFEFE9040255FAE1ADC8CC96567DFE63D4202` - -```pgp ------BEGIN PGP PUBLIC KEY BLOCK----- -Comment: User-ID: MiranDaniel -Comment: Valid from: 25 Aug 2023 14:36:23 -Comment: Valid until: 25 Aug 2025 12:00:00 -Comment: Type: 255-bit EdDSA (secret key available) -Comment: Usage: Signing, Encryption, Certifying User-IDs, SSH Authentication -Comment: Fingerprint: F42DFEFE9040255FAE1ADC8CC96567DFE63D4202 - -mDMEZOigRxYJKwYBBAHaRw8BAQdA6nlfxM3PXd6CQKw2j3EuB0l9z9s/vYlW78Ib -1GisZeG0IE1pcmFuRGFuaWVsIDxtZUBtaXJhbmRhbmllbC5jb20+iJkEExYKAEEW -IQT0Lf7+kEAlX64a3IzJZWff5j1CAgUCZOigRwIbIwUJA8OT2QULCQgHAgIiAgYV -CgkICwIEFgIDAQIeBwIXgAAKCRDJZWff5j1CAuh9AP0VYTe9YY9flGNNUdaFANtd -klxR2Uip2MPS/kmKjsEtMQEAxRb5ehP7NjdsD8FCUHA86ABTciSzvgekfignoFih -jQW4OARk6KBHEgorBgEEAZdVAQUBAQdAed8AKjqp0TtGTc4ywW4p5gIXAU+edl2h -GKgg5x3Yu34DAQgHiH4EGBYKACYWIQT0Lf7+kEAlX64a3IzJZWff5j1CAgUCZOig -RwIbDAUJA8OT2QAKCRDJZWff5j1CAukqAP0fcO+cWxwPP+EpYzaQTt76GbnmOhJX -xXCP27i1Cl3AbgD/cwRMBR3egPR1Zs+UVFvgiPhRGNZGijGA442mXjtllwc= -=4Ke1 ------END PGP PUBLIC KEY BLOCK----- -``` diff --git a/app.py b/app.py index e42e163..13a8a1c 100644 --- a/app.py +++ b/app.py @@ -40,72 +40,17 @@ import yaml import requests -with open("config.yaml","r") as stream: +import exceptions +from utils import verifyRecaptcha, generateInvite, verifyConfig + +with open("config.yaml", "r") as f: try: - config = yaml.safe_load(stream) + config = yaml.safe_load(f) except yaml.YAMLError as exc: print(exc) quit(1) - - -if "dark_theme" not in config: - print("!! Theme not defined") -if "recaptcha" in config: - if config["recaptcha"]["public"] == None: - print("!! Recaptcha public key is not defined, exiting") - quit(1) - if config["recaptcha"]["private"] == None: - print("!! Recaptcha private key is not defined, exiting") - quit(1) -else: - print("!! Recaptcha config doesnt exist, exiting") - quit(1) - -if "discord" in config: - if config["discord"]["welcome_room"] == None: - print("!! Discord welcome room not defined, exiting") - quit(1) - if config["discord"]["private"] == None: - print("!! Discord private key is not defined, exiting") - quit(1) -else: - print("!! Discord config doesnt exist, exiting") - quit(1) - -if "server" in config: - if config["server"]["port"] == None: - print("!! Server port not defined, exiting") - quit(1) -else: - print("!! Sever config not defined, exiting") - quit(1) - -def recaptcha(token): - print(f"Verifying recaptcha {token[:15]}") - recaptcha_url = 'https://www.google.com/recaptcha/api/siteverify' - payload = { - 'secret': config["recaptcha"]["private"], - 'response': token, - 'remoteip': request.remote_addr, - } - response = requests.post(recaptcha_url, data = payload) - result = response.json() - return result - -def invite(): - print("Generating new invite!") - resp = requests.post( - 'https://discordapp.com/api/channels/{}/invites'.format(config["discord"]["welcome_room"]), - headers={'Authorization': 'Bot {}'.format(config["discord"]["private"])}, - json={'max_uses': 1, 'unique': True, 'max_age': 300} - ) - i = resp.json() - # error handling for invite creation - if (i.get('code')): - print("Generated new invite!") else: - print(i) - return i["code"] + verifyConfig(config) app = Flask(__name__) @@ -114,17 +59,56 @@ def invite(): catpcha_theme = "dark" if config["dark_theme"] else "light" -@app.route("/") # main function +@app.route("/") def index(): - key = request.args.get('key') # get key parameter from URL - if key: # if key set - r = recaptcha(key) # confirm captcha - if r.get("success"): # if ok + key = request.args.get("key") + if key: # User has submitted a captcha + r = verifyRecaptcha(key, request, config) + if r.get("success"): # Captcha is OK print(f"Recaptcha {key[:30]} verified!") - i = invite() # generate new invite - return redirect(f"https://discord.gg/{i}") # redirect user to new invite - else: # if captcha invalid + inviteCode = generateInvite(config) + return redirect(f"https://discord.gg/{inviteCode}") + else: # Captcha failed print(f"Recaptcha {key[:30]} failed!") - return render_template("index.html", public=config["recaptcha"]["public"], failed=True, theme=theme, border=border, catpcha_theme=catpcha_theme) # return error page - # if not key - return render_template("index.html", public=config["recaptcha"]["public"], failed=False, theme=theme, border=border, catpcha_theme=catpcha_theme) # return normal page + # Return error page + return render_template( + "index.html", + public=config["recaptcha"]["public"], + failed="Invalid captcha, try again", + theme=theme, + border=border, + catpcha_theme=catpcha_theme, + ) + + return render_template( + "index.html", + public=config["recaptcha"]["public"], + failed=None, + theme=theme, + border=border, + catpcha_theme=catpcha_theme, + ) # Return normal page + + +@app.errorhandler(500) +def internalError(error): + return render_template( + "index.html", + public=config["recaptcha"]["public"], + failed="Internal server error, please try again later", + theme=theme, + border=border, + catpcha_theme=catpcha_theme, + ) + + +@app.errorhandler(404) +def notFound(error): + return render_template( + "index.html", + public=config["recaptcha"]["public"], + failed=None, + theme=theme, + border=border, + catpcha_theme=catpcha_theme, + ) diff --git a/config.example.yaml b/config.example.yaml index 80352fe..7b6ecdf 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -28,4 +28,4 @@ discord: server: # the script will host the gateway on this port # defaults to 80 - port: 5000 \ No newline at end of file + port: 5000 diff --git a/config.yaml b/config.yaml index a3c209d..78650b7 100644 --- a/config.yaml +++ b/config.yaml @@ -1,7 +1,7 @@ # # Hello there, user # this configuration file contains your credentials, make sure not to share it with ANYONE. -# anyone with your Discord private key can controll your bot! +# anyone with your Discord private key can control your bot! # @@ -10,21 +10,21 @@ dark_theme: false recaptcha: - # put your public recaptcha key here! - public: + # put your public (site key) recaptcha key here! + public: # DO NOT LEAK THIS - # put your private recapthca key here! - private: + # put your private (secret) recapthca key here! + private: discord: # users will be invited to this room, it should be public # put your welcome room ID here - welcome_room: + welcome_room: # DO NOT LEAK THIS # put your Discord bot token here - private: + private: server: # the script will host the gateway on this port diff --git a/exceptions.py b/exceptions.py new file mode 100644 index 0000000..f95dc00 --- /dev/null +++ b/exceptions.py @@ -0,0 +1,3 @@ +class InviteGenerationError(Exception): + def __init__(self, message): + super().__init__(message) diff --git a/readme.md b/readme.md index 58651d2..fc71a47 100644 --- a/readme.md +++ b/readme.md @@ -1,3 +1,8 @@ +> [!CAUTION] +> UPGRADE TO VERSION >= 1.1.0 DUE TO SECURITY VULNERABILITY IN OLDER VERSIONS + +--- + # f1rewall *The sleek, simple and scalable invite gateway for your Discord community* @@ -90,7 +95,7 @@ Congrats! Your recaptcha is now ready! 1. Run `apt-get update -y && apt-get upgrade -y` to update your packages 1. Run `apt-get install python3-dev -y && apt-get install python3-venv -y` to install the required dependencies for Python 1. Run `sudo make` to install all dependencies -2. Run `sh run.sh` to start the server +2. Run `./venv/bin/python3 server.py` to start the server 3. The script will now host your gateway on the port specified in config.yaml #### Debugging diff --git a/requirements.txt b/requirements.txt index ef74d3d..b0fa77e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ flask requests gevent pyyaml +black diff --git a/run.sh b/run.sh deleted file mode 100644 index 2f3e6d9..0000000 --- a/run.sh +++ /dev/null @@ -1 +0,0 @@ -./.venv/bin/python3 server.py \ No newline at end of file diff --git a/server.py b/server.py index 0b2c1c3..0d8a546 100644 --- a/server.py +++ b/server.py @@ -2,7 +2,7 @@ from app import app import yaml -with open("config.yaml","r") as stream: +with open("config.yaml", "r") as stream: try: config = yaml.safe_load(stream) except yaml.YAMLError as exc: @@ -11,5 +11,5 @@ print(f"Serving on port {config['server']['port']}") -http_server = WSGIServer(('', config["server"]["port"]), app) +http_server = WSGIServer(("", config["server"]["port"]), app) http_server.serve_forever() diff --git a/static/abackground.png b/static/abackground.png new file mode 100644 index 0000000..df9cc92 Binary files /dev/null and b/static/abackground.png differ diff --git a/static/background.png b/static/background.png index df9cc92..f5e66b2 100644 Binary files a/static/background.png and b/static/background.png differ diff --git a/templates/index.html b/templates/index.html index 445e3ac..f435b56 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,119 +1,137 @@ - - - - - + + + + Invite gateway - - + + - + - +
-
-
-
- -
-
-
-
-
-
-
-
-

Join our Discord

-

Solve the captcha below to join

-
-
-
-
-
-
-
-
-

{{"Invalid captcha, try again" if failed else ""}}

-
+
+
+
+ +
+
+
+
+
+
+
+
+

Join our Discord

+

Solve the captcha below to join

+
+
+
+
+
+
+
+

{{failed if failed else ""}}

+
+
- - - + + params.set("key", grecaptcha.getResponse()); + window.location.href = url + "?" + params.toString(); + } + diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..75ea3fa --- /dev/null +++ b/utils.py @@ -0,0 +1,73 @@ +import yaml +import requests + +import exceptions + + +def verifyRecaptcha(token, request, config): + print(f"Verifying recaptcha {token[:15]}") + recaptcha_url = "https://www.google.com/recaptcha/api/siteverify" + payload = { + "secret": config["recaptcha"]["private"], + "response": token, + "remoteip": request.remote_addr, + } + response = requests.post(recaptcha_url, data=payload) + result = response.json() + return result + + +def generateInvite(config): + print("Generating new invite!") + resp = requests.post( + f"https://discordapp.com/api/channels/{config['discord']['welcome_room']}/invites", + headers={"Authorization": f"Bot {config['discord']['private']}"}, + json={"max_uses": 1, "unique": True, "max_age": 300}, + ) + i = resp.json() + if resp.status_code != 200: + raise exceptions.InviteGenerationError(i) + if i.get("code"): + print("Generated new invite!") + return i["code"] + + raise exceptions.InviteGenerationError(i) + + +def verifyConfig(config): + ok = True + + if "dark_theme" not in config: + print("!! Theme not defined") + if "recaptcha" in config: + if config["recaptcha"]["public"] == None: + print("!! Recaptcha public key is not defined, exiting") + ok = False + if config["recaptcha"]["private"] == None: + print("!! Recaptcha private key is not defined, exiting") + ok = False + else: + print("!! Recaptcha config doesnt exist, exiting") + ok = False + + if "discord" in config: + if config["discord"]["welcome_room"] == None: + print("!! Discord welcome room not defined, exiting") + ok = False + if config["discord"]["private"] == None: + print("!! Discord private key is not defined, exiting") + ok = False + else: + print("!! Discord config doesnt exist, exiting") + ok = False + + if "server" in config: + if config["server"]["port"] == None: + print("!! Server port not defined, exiting") + ok = False + else: + print("!! Sever config not defined, exiting") + ok = False + + if not ok: + quit(1)