diff --git a/CHANGELOG.md b/CHANGELOG.md index af7543a..e635a39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,14 @@ + +## Version 1.11.0 - 2025-12-19 + +Adds the ability to configure whether user registration is open or closed via system settings. Administrators can now toggle registration openness to control access to the registration form. + +**Changes:** 5 files, 262 lines +**Languages:** HTML (185 lines), Python (77 lines) + ## Version 1.10.0 - 2025-12-19 Users must now receive an invitation to register for an account, as open registration is disabled. diff --git a/pyproject.toml b/pyproject.toml index 5da6a0d..458c82e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "Snek" -version = "1.10.0" +version = "1.11.0" readme = "README.md" #license = { file = "LICENSE", content-type="text/markdown" } description = "Snek Chat Application by Molodetz" diff --git a/src/snek/app.py b/src/snek/app.py index 254adec..9b93842 100644 --- a/src/snek/app.py +++ b/src/snek/app.py @@ -31,6 +31,7 @@ from snek.system import http from snek.system.cache import Cache from snek.system.markdown import MarkdownExtension from snek.system.middleware import auth_middleware, cors_middleware, csp_middleware +from snek.system.config import config from snek.system.profiler import profiler_handler from snek.system.template import ( EmojiExtension, @@ -178,6 +179,7 @@ class Application(BaseApplication): self.sync_service = None self.executor = None self.cache = Cache(self) + self.config = config self.services = get_services(app=self) self.mappers = get_mappers(app=self) self.broadcast_service = None @@ -381,6 +383,7 @@ class Application(BaseApplication): if not context: context = {} context["rid"] = str(uuid.uuid4()) + context["config"] = self.config if request.session.get("uid"): async for subscribed_channel in self.services.channel_member.find( user_uid=request.session.get("uid"), deleted_at=None, is_banned=False diff --git a/src/snek/system/config.py b/src/snek/system/config.py new file mode 100644 index 0000000..4f22911 --- /dev/null +++ b/src/snek/system/config.py @@ -0,0 +1,34 @@ +# retoor + +import os + + +def get_bool(key, default=False): + value = os.environ.get(key, str(default)).lower() + return value in ('true', '1', 'yes', 'on') + + +def get_str(key, default=''): + return os.environ.get(key, default) + + +def get_int(key, default=0): + try: + return int(os.environ.get(key, default)) + except (ValueError, TypeError): + return default + + +class Config: + def __init__(self): + self.registration_open = get_bool('SNEK_REGISTRATION_OPEN', False) + self.debug = get_bool('SNEK_DEBUG', False) + self.session_key = get_str('SNEK_SESSION_KEY', 'c79a0c5fda4b424189c427d28c9f7c34') + self.db_path = get_str('SNEK_DB_PATH', 'sqlite:///snek.db') + self.host = get_str('SNEK_HOST', '0.0.0.0') + self.port = get_int('SNEK_PORT', 8081) + self.ssh_host = get_str('SNEK_SSH_HOST', '0.0.0.0') + self.ssh_port = get_int('SNEK_SSH_PORT', 2242) + + +config = Config() diff --git a/src/snek/system/util.py b/src/snek/system/util.py new file mode 100644 index 0000000..5ca572d --- /dev/null +++ b/src/snek/system/util.py @@ -0,0 +1,24 @@ +# retoor + + +def safe_get(obj, key, default=None): + if obj is None: + return default + try: + if isinstance(obj, dict): + return obj.get(key, default) + if hasattr(obj, "fields") and hasattr(obj, "__getitem__"): + val = obj[key] + return val if val is not None else default + return getattr(obj, key, default) + except (KeyError, TypeError, AttributeError): + return default + + +def safe_str(obj): + if obj is None: + return "" + try: + return str(obj) + except Exception: + return "" diff --git a/src/snek/templates/register.html b/src/snek/templates/register.html index b73324d..afb5e97 100644 --- a/src/snek/templates/register.html +++ b/src/snek/templates/register.html @@ -1,17 +1,190 @@ {% extends "base.html" %} {% block title %} - Register - Snek chat by Molodetz + {% if config.registration_open %} + Register - Snek chat by Molodetz + {% else %} + Join Snek - Invitation Only + {% endif %} {% endblock %} {% block head %} + {% if not config.registration_open %} + + {% endif %} {% endblock %} {% block main %} -
- - Sorry, Snek became a closed community. You can only join by receiving an invitation by any member. This ensures that Snek is a safe and secure place for developers, testers, and AI professionals. - {# -
+ {% if config.registration_open %} +
+ + +
+ {% else %} +
+ +
+
+
🔒
+

Invitation Only

+

Snek is a private community

+

+ Snek operates as a closed community to maintain quality and security. + Membership is available through invitation from existing members. + This approach ensures a trusted environment for developers, testers, and AI professionals. +

+
+
+
👥
+
Trusted Network
+
+
+
🔒
+
Secure Environment
+
+
+
+
Quality Members
+
+
+ +
+ {% endif %} {% endblock %} diff --git a/src/snek/view/register.py b/src/snek/view/register.py index d561454..5d2c6e3 100644 --- a/src/snek/view/register.py +++ b/src/snek/view/register.py @@ -1,13 +1,5 @@ # retoor -# Written by retoor@molodetz.nl - -# This module defines a web view for user registration. It handles GET requests and form submissions for the registration process. - -# The code makes use of 'RegisterForm' from 'snek.form.register' for handling registration forms and 'BaseFormView' from 'snek.system.view' for basic view functionalities. - -# MIT License - from aiohttp import web from snek.form.register import RegisterForm @@ -16,19 +8,25 @@ from snek.system.view import BaseFormView class RegisterView(BaseFormView): form = RegisterForm - login_required = False async def get(self): if self.session.get("logged_in"): return web.HTTPFound("/web.html") if self.request.path.endswith(".json"): + if not self.app.config.registration_open: + return web.json_response( + {"error": "Registration is currently closed"}, + status=403 + ) return await super().get() return await self.render_template( "register.html", {"form": await self.form(app=self.app).to_json()} ) async def submit(self, form): + if not self.app.config.registration_open: + return {"error": "Registration is currently closed"} result = await self.app.services.user.register( form.email.value, form.username.value, form.password.value )