mirror of
https://github.com/chatmail/relay.git
synced 2026-05-10 16:04:37 +00:00
Compare commits
11 Commits
docker-reb
...
invite-onl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a7b425958 | ||
|
|
cb87c85c03 | ||
|
|
5b4eb1701e | ||
|
|
f578704069 | ||
|
|
7319977527 | ||
|
|
7dcd109bec | ||
|
|
6940175b06 | ||
|
|
de139bde18 | ||
|
|
a92c9ff275 | ||
|
|
1afdab7b20 | ||
|
|
56cbd6f35b |
@@ -2,6 +2,10 @@
|
||||
|
||||
## untagged
|
||||
|
||||
- Enable invite-only chatmail relays with invite tokens
|
||||
that can override disabled account creation
|
||||
([#600](https://github.com/chatmail/relay/pull/600))
|
||||
|
||||
- dovecot: keep mailbox index only in memory to avoid unnecessary disc usage
|
||||
([#632](https://github.com/chatmail/relay/pull/632))
|
||||
|
||||
|
||||
17
README.md
17
README.md
@@ -284,8 +284,23 @@ Fresh chatmail addresses have a mailbox directory that contains:
|
||||
will typically be empty unless the user of that address hasn't been online
|
||||
for a while.
|
||||
|
||||
## Restrict address creation
|
||||
|
||||
## Emergency Commands to disable automatic address creation
|
||||
### Only allow new addresses with an invite token
|
||||
|
||||
To restrict address creation for anyone who doesn't have the invite link/QR code:
|
||||
|
||||
1. Use the `invite_token` option to add
|
||||
one or more tokens of your choice to `chatmail.ini`:
|
||||
`invite_token = s3cr3t privil3g3`
|
||||
- (recommendation: choose 9 or more letters, or it will be easily bruteforced)
|
||||
2. Run `scripts/cmdeploy run`
|
||||
3. Distribute a `dcaccount` invite link/QR code
|
||||
(like the one on your web page)
|
||||
with one of your invite tokens added at the end,
|
||||
for example: `dcaccount:https://example.org/new?s3cr3t`
|
||||
|
||||
### Emergency Command to disable automatic address creation
|
||||
|
||||
If you need to stop address creation,
|
||||
e.g. because some script is wildly creating addresses,
|
||||
|
||||
@@ -31,6 +31,7 @@ class Config:
|
||||
self.username_min_length = int(params["username_min_length"])
|
||||
self.username_max_length = int(params["username_max_length"])
|
||||
self.password_min_length = int(params["password_min_length"])
|
||||
self.invite_token = params.get("invite_token", "")
|
||||
self.passthrough_senders = params["passthrough_senders"].split()
|
||||
self.passthrough_recipients = params["passthrough_recipients"].split()
|
||||
self.www_folder = params.get("www_folder", "")
|
||||
|
||||
@@ -26,8 +26,19 @@ def is_allowed_to_create(config: Config, user, cleartext_password) -> bool:
|
||||
if os.path.exists(NOCREATE_FILE):
|
||||
logging.warning(f"blocked account creation because {NOCREATE_FILE!r} exists.")
|
||||
return False
|
||||
password_length = len(cleartext_password)
|
||||
if config.invite_token:
|
||||
for inv_token in config.invite_token.split():
|
||||
if cleartext_password.startswith(inv_token):
|
||||
password_length = len(cleartext_password) - len(inv_token)
|
||||
break
|
||||
else:
|
||||
logging.warning(
|
||||
"blocked account creation because password didn't contain invite token(s)."
|
||||
)
|
||||
return False
|
||||
|
||||
if len(cleartext_password) < config.password_min_length:
|
||||
if password_length < config.password_min_length:
|
||||
logging.warning(
|
||||
"Password needs to be at least %s characters long",
|
||||
config.password_min_length,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"""CGI script for creating new accounts."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import secrets
|
||||
import string
|
||||
@@ -20,7 +21,11 @@ def create_newemail_dict(config: Config):
|
||||
secrets.choice(ALPHANUMERIC_PUNCT)
|
||||
for _ in range(config.password_min_length + 3)
|
||||
)
|
||||
return dict(email=f"{user}@{config.mail_domain}", password=f"{password}")
|
||||
redirect_uri = os.getenv("REQUEST_URI", "/new")
|
||||
invite_token = "" if redirect_uri == "/new" else redirect_uri[5:]
|
||||
return dict(
|
||||
email=f"{user}@{config.mail_domain}", password=f"{invite_token}{password}"
|
||||
)
|
||||
|
||||
|
||||
def print_new_account():
|
||||
|
||||
@@ -64,12 +64,38 @@ def test_dont_overwrite_password_on_wrong_login(dictproxy):
|
||||
assert res["password"] == res2["password"]
|
||||
|
||||
|
||||
def test_nocreate_file(monkeypatch, tmpdir, dictproxy):
|
||||
p = tmpdir.join("nocreate")
|
||||
p.write("")
|
||||
monkeypatch.setattr(chatmaild.doveauth, "NOCREATE_FILE", str(p))
|
||||
dictproxy.lookup_passdb("newuser12@chat.example.org", "zequ0Aimuchoodaechik")
|
||||
assert not dictproxy.lookup_userdb("newuser12@chat.example.org")
|
||||
@pytest.mark.parametrize(
|
||||
["nocreate_file", "account", "invite_token", "password"],
|
||||
[
|
||||
(False, True, "asdf", "asdfasdmaimfelsgwerw"),
|
||||
(False, False, "asdf", "z9873240187420913798"),
|
||||
(False, True, "", "dsaiujfw9fjiwf9w"),
|
||||
(False, False, "asdf", "z987324018742asdf0913798"),
|
||||
(False, True, "as df", "asj0wiefkj0ofkeefok"),
|
||||
(False, True, "as df", "dfj0wiefkj0ofkeefok"),
|
||||
(False, False, "as df", "j0wiefkj0ofas dfkeefok"),
|
||||
(True, False, "asdf", "asdfmosadkdkfwdofkw"),
|
||||
(True, False, "asdf", "z9873240187420913798"),
|
||||
(True, False, "", "dsaiujfw9fjiwf9w"),
|
||||
],
|
||||
)
|
||||
def test_nocreate_file(
|
||||
monkeypatch,
|
||||
tmpdir,
|
||||
dictproxy,
|
||||
example_config,
|
||||
nocreate_file: bool,
|
||||
account: bool,
|
||||
invite_token: str,
|
||||
password: str,
|
||||
):
|
||||
if nocreate_file:
|
||||
p = tmpdir.join("nocreate")
|
||||
p.write("")
|
||||
monkeypatch.setattr(chatmaild.doveauth, "NOCREATE_FILE", str(p))
|
||||
example_config.invite_token = invite_token
|
||||
dictproxy.lookup_passdb("newuser12@chat.example.org", password)
|
||||
assert bool(dictproxy.lookup_userdb("newuser12@chat.example.org")) == account
|
||||
|
||||
|
||||
def test_handle_dovecot_request(dictproxy):
|
||||
|
||||
@@ -84,12 +84,13 @@ http {
|
||||
if ($request_method = GET) {
|
||||
# Redirect to Delta Chat,
|
||||
# which will in turn do a POST request.
|
||||
return 301 dcaccount:https://{{ config.domain_name }}/new;
|
||||
return 301 dcaccount:https://{{ config.domain_name }}$request_uri;
|
||||
}
|
||||
|
||||
fastcgi_pass unix:/run/fcgiwrap.socket;
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME /usr/lib/cgi-bin/newemail.py;
|
||||
fastcgi_param QUERY_STRING $query_string;
|
||||
}
|
||||
|
||||
# Old URL for compatibility with e.g. printed QR codes.
|
||||
@@ -100,7 +101,7 @@ http {
|
||||
# Redirects are only for browsers.
|
||||
location /cgi-bin/newemail.py {
|
||||
if ($request_method = GET) {
|
||||
return 301 dcaccount:https://{{ config.domain_name }}/new;
|
||||
return 301 dcaccount:https://{{ config.domain_name }}$request_uri;
|
||||
}
|
||||
|
||||
fastcgi_pass unix:/run/fcgiwrap.socket;
|
||||
|
||||
@@ -11,6 +11,7 @@ for Delta Chat users. For details how it avoids storing personal information
|
||||
please see our [privacy policy](privacy.html).
|
||||
{% endif %}
|
||||
|
||||
{% if not config.invite_token %}
|
||||
<a class="cta-button" href="DCACCOUNT:https://{{ config.mail_domain }}/new">Get a {{config.mail_domain}} chat profile</a>
|
||||
|
||||
If you are viewing this page on a different device
|
||||
@@ -23,6 +24,10 @@ you can also **scan this QR code** with Delta Chat:
|
||||
🐣 **Choose** your Avatar and Name
|
||||
|
||||
💬 **Start** chatting with any Delta Chat contacts using [QR invite codes](https://delta.chat/en/help#howtoe2ee)
|
||||
{% else %}
|
||||
**To join this instance, you need an invite link or QR code -
|
||||
ask the admin for an invite.**
|
||||
{% endif %}
|
||||
|
||||
{% if config.mail_domain != "nine.testrun.org" %}
|
||||
<div class="experimental">Note: this is only a temporary development chatmail service</div>
|
||||
|
||||
Reference in New Issue
Block a user