add config for invite-only mode
This commit is contained in:
parent
86fc011be7
commit
35b7993e48
10 changed files with 103 additions and 3 deletions
|
@ -91,6 +91,7 @@ SETUP_ADMIN_USERNAMES = [
|
|||
u for u in os.environ.get("NEODB_ADMIN_USERNAMES", "").split(",") if u
|
||||
]
|
||||
|
||||
INVITE_ONLY = os.environ.get("NEODB_INVITE_ONLY", "") != ""
|
||||
|
||||
# Mastodon/Pleroma instance allowed to login, keep empty to allow any instance to login
|
||||
MASTODON_ALLOWED_SITES = []
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.core.paginator import Paginator
|
|||
from django.db.models import Count
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
from django.views.decorators.http import require_http_methods
|
||||
|
@ -272,6 +273,8 @@ def discover(request):
|
|||
# gallery["items"] = Item.objects.filter(id__in=ids)
|
||||
|
||||
if request.user.is_authenticated:
|
||||
if not request.user.registration_complete:
|
||||
return redirect(reverse("users:register"))
|
||||
layout = request.user.preference.discover_layout
|
||||
identity = request.user.identity
|
||||
podcast_ids = [
|
||||
|
|
|
@ -11,11 +11,15 @@ from users.models import User
|
|||
|
||||
@login_required
|
||||
def me(request):
|
||||
if not request.user.registration_complete:
|
||||
return redirect(reverse("users:register"))
|
||||
return redirect(request.user.identity.url)
|
||||
|
||||
|
||||
def home(request):
|
||||
if request.user.is_authenticated:
|
||||
if not request.user.registration_complete:
|
||||
return redirect(reverse("users:register"))
|
||||
home = request.user.preference.classic_homepage
|
||||
if home == 1:
|
||||
return redirect(request.user.url)
|
||||
|
|
|
@ -21,6 +21,7 @@ x-shared:
|
|||
NEODB_DEBUG:
|
||||
NEODB_SECRET_KEY:
|
||||
NEODB_ADMIN_USERNAMES:
|
||||
NEODB_INVITE_ONLY:
|
||||
NEODB_DB_NAME: neodb
|
||||
NEODB_DB_USER: neodb
|
||||
NEODB_DB_PASSWORD: aubergine
|
||||
|
|
|
@ -2,7 +2,8 @@ import logging
|
|||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import BadRequest
|
||||
from django.shortcuts import render
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from catalog.models import *
|
||||
|
@ -19,6 +20,8 @@ PAGE_SIZE = 10
|
|||
def feed(request):
|
||||
if request.method != "GET":
|
||||
raise BadRequest()
|
||||
if not request.user.registration_complete:
|
||||
return redirect(reverse("users:register"))
|
||||
user = request.user
|
||||
podcast_ids = [
|
||||
p.item_id
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import datetime
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import secrets
|
||||
import ssl
|
||||
|
@ -137,6 +138,51 @@ class RsaKeys:
|
|||
return private_key_serialized, public_key_serialized
|
||||
|
||||
|
||||
class Invite(models.Model):
|
||||
"""
|
||||
An invite token, good for one signup.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
# managed = False
|
||||
db_table = "users_invite"
|
||||
|
||||
# Should always be lowercase
|
||||
token = models.CharField(max_length=500, unique=True)
|
||||
|
||||
# Admin note about this code
|
||||
note = models.TextField(null=True, blank=True)
|
||||
|
||||
# Uses remaining (null means "infinite")
|
||||
uses = models.IntegerField(null=True, blank=True)
|
||||
|
||||
# Expiry date
|
||||
expires = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
|
||||
@classmethod
|
||||
def create_random(cls, uses=None, expires=None, note=None):
|
||||
return cls.objects.create(
|
||||
token="".join(
|
||||
random.choice("abcdefghkmnpqrstuvwxyz23456789") for i in range(20)
|
||||
),
|
||||
uses=uses,
|
||||
expires=expires,
|
||||
note=note,
|
||||
)
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
if self.uses is not None:
|
||||
if self.uses <= 0:
|
||||
return False
|
||||
if self.expires is not None:
|
||||
return self.expires >= timezone.now()
|
||||
return True
|
||||
|
||||
|
||||
class User(AbstractBaseUser):
|
||||
identities: "RelatedManager[Identity]"
|
||||
|
||||
|
|
|
@ -592,3 +592,10 @@ class Takahe:
|
|||
)
|
||||
cache.set(cache_key, peers, timeout=1800)
|
||||
return peers
|
||||
|
||||
@staticmethod
|
||||
def verify_invite(token):
|
||||
if not token:
|
||||
return False
|
||||
invite = Invite.objects.filter(token=token).first()
|
||||
return invite and invite.valid
|
||||
|
|
|
@ -25,6 +25,7 @@ from journal.models import remove_data_by_user
|
|||
from mastodon import mastodon_request_included
|
||||
from mastodon.api import *
|
||||
from mastodon.api import verify_account
|
||||
from takahe.utils import Takahe
|
||||
|
||||
from .models import Preference, User
|
||||
from .tasks import *
|
||||
|
@ -49,7 +50,13 @@ def login(request):
|
|||
# store redirect url in the cookie
|
||||
if request.GET.get("next"):
|
||||
request.session["next_url"] = request.GET.get("next")
|
||||
|
||||
invite_status = -1 if settings.INVITE_ONLY else 0
|
||||
if settings.INVITE_ONLY and request.GET.get("invite"):
|
||||
if Takahe.verify_invite(request.GET.get("invite")):
|
||||
invite_status = 1
|
||||
request.session["invite"] = request.GET.get("invite")
|
||||
else:
|
||||
invite_status = -2
|
||||
return render(
|
||||
request,
|
||||
"users/login.html",
|
||||
|
@ -58,6 +65,7 @@ def login(request):
|
|||
"scope": quote(settings.MASTODON_CLIENT_SCOPE),
|
||||
"selected_site": selected_site,
|
||||
"allow_any_site": settings.MASTODON_ALLOW_ANY_SITE,
|
||||
"invite_status": invite_status,
|
||||
},
|
||||
)
|
||||
else:
|
||||
|
@ -188,6 +196,18 @@ def OAuth2_login(request):
|
|||
|
||||
|
||||
def register_new_user(request, **param):
|
||||
if settings.INVITE_ONLY:
|
||||
if not Takahe.verify_invite(request.session.get("invite")):
|
||||
return render(
|
||||
request,
|
||||
"common/error.html",
|
||||
{
|
||||
"msg": _("注册失败😫"),
|
||||
"secondary_msg": _("本站仅限邀请注册"),
|
||||
},
|
||||
)
|
||||
else:
|
||||
del request.session["invite"]
|
||||
new_user = User.register(**param)
|
||||
request.session["new_user"] = True
|
||||
auth_login(request, new_user)
|
||||
|
|
|
@ -203,6 +203,10 @@ class User(AbstractUser):
|
|||
def __str__(self):
|
||||
return f'{self.pk}:{self.username or ""}:{self.mastodon_acct}'
|
||||
|
||||
@property
|
||||
def registration_complete(self):
|
||||
return self.username is not None
|
||||
|
||||
def clear(self):
|
||||
if self.mastodon_site == "removed" and not self.is_active:
|
||||
return
|
||||
|
|
|
@ -132,13 +132,24 @@
|
|||
});
|
||||
</script>
|
||||
{% else %}
|
||||
<select name="domain" placeholder="test">
|
||||
<select name="domain">
|
||||
{% for site in sites %}
|
||||
<option value="{{ site.domain_name }}" data-client-id="{{ site.client_id }}">@{{ site.domain_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type='submit' value="{% trans '授权登录' %}" id="loginButton" />
|
||||
{% endif %}
|
||||
{% if invite_status %}
|
||||
<small>
|
||||
{% if invite_status == 1 %}
|
||||
<i class="fa-solid fa-circle-check"></i> 邀请链接有效,可注册新用户
|
||||
{% elif invite_status == -1 %}
|
||||
<i class="fa-solid fa-person-circle-question"></i> 本站目前为邀请注册,已有账户可直接登入,新用户请使用有效邀请链接注册
|
||||
{% elif invite_status == -2 %}
|
||||
<i class="fa-solid fa-circle-xmark"></i> 邀请链接无效,已有账户可直接登入,新用户请使用有效邀请链接注册
|
||||
{% endif %}
|
||||
</small>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endif %}
|
||||
<div class="delayed">部分模块加载超时,请检查网络(翻墙)设置。</div>
|
||||
|
|
Loading…
Add table
Reference in a new issue