login code via email

This commit is contained in:
Your Name 2024-04-12 20:42:36 -04:00 committed by Henri Dickson
parent c5e0dd5432
commit 6f21b5ad7d
7 changed files with 114 additions and 7 deletions

View file

@ -358,6 +358,7 @@ TEMPLATES = [
WSGI_APPLICATION = "boofilsic.wsgi.application"
SESSION_COOKIE_NAME = "neodbsid"
SESSION_COOKIE_AGE = 90 * 24 * 60 * 60 # 90 days
AUTHENTICATION_BACKENDS = [
"mastodon.auth.OAuth2Backend",

View file

@ -0,0 +1,52 @@
{% load static %}
{% load i18n %}
{% load l10n %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
{% load thumb %}
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ site_name }}</title>
{% include "common_libs.html" %}
</head>
<body>
{% include "_header.html" %}
<main class="container">
<article>
<header>
<h3>验证邮件已发送</h3>
</header>
请点击邮件中的登录链接,或输入收到的验证码:
<style type="text/css">
.otp input {
font-family: monospace;
font-weight: bolder;
letter-spacing: 2em;
text-align: center;
}
</style>
<form action="{% url 'users:verify_code' %}" method="post">
<div class="otp">
<input name="code"
maxlength="5"
value=""
autocomplete="one-time-code"
autocapitalize="none"
autocorrect="off"
required
pattern="^[a-zA-Z0-9]{5}$" />
<small>{{ error }}</small>
</div>
<input type="submit" value="提交" />
{% csrf_token %}
</form>
</article>
</main>
{% include "_footer.html" %}
</body>
</html>

View file

@ -4,7 +4,7 @@ dateparser
discord.py
django~=4.2.11
django-anymail
django-auditlog>=3.0.0-beta.4
django-auditlog>=3.0.0
django-bleach
django-compressor
django-cors-headers

View file

@ -16,6 +16,7 @@ from django.db.models import Count, Q
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils import timezone
from django.utils.baseconv import base62
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from loguru import logger
@ -86,18 +87,24 @@ def connect(request):
{"msg": _("无效的电子邮件地址")},
)
user = User.objects.filter(email__iexact=login_email).first()
code = base62.encode(random.randint(pow(62, 4), pow(62, 5) - 1))
cache.set(f"login_{code}", login_email, timeout=60 * 15)
request.session["login_email"] = login_email
action = "login" if user else "register"
django_rq.get_queue("mastodon").enqueue(
send_verification_link,
user.pk if user else 0,
"login" if user else "register",
action,
login_email,
code,
)
return render(
request,
"common/info.html",
"common/verify.html",
{
"msg": _("验证邮件已发送"),
"secondary_msg": _("请查阅收件箱"),
"action": action,
},
)
login_domain = (
@ -284,7 +291,7 @@ class RegistrationForm(forms.ModelForm):
return email
def send_verification_link(user_id, action, email):
def send_verification_link(user_id, action, email, code=""):
s = {"i": user_id, "e": email, "a": action}
v = TimestampSigner().sign_object(s)
if action == "verify":
@ -292,9 +299,13 @@ def send_verification_link(user_id, action, email):
url = settings.SITE_INFO["site_url"] + "/account/verify_email?c=" + v
msg = f"你好,\n请点击以下链接验证你的电子邮件地址 {email}\n{url}\n\n如果你没有注册过本站,请忽略此邮件。"
elif action == "login":
subject = f'{settings.SITE_INFO["site_name"]} - {_("登录")}'
subject = f'{settings.SITE_INFO["site_name"]} - {_("登录")} {code}'
url = settings.SITE_INFO["site_url"] + "/account/login/email?c=" + v
msg = f"你好,\n请点击以下链接登录{email}账号\n{url}\n\n如果你没有请求登录本站,请忽略此邮件;如果你确信账号存在安全风险,请更改注册邮件地址或与我们联系。"
msg = (
"你好,\n"
+ f"在登录界面输入如下验证码:\n\n{code}\n\n"
+ f"点击以下链接登录{email}账号\n{url}\n\n如果你没有请求登录本站,请忽略此邮件;如果你确信账号存在安全风险,请更改注册邮件地址或与我们联系。"
)
elif action == "register":
subject = f'{settings.SITE_INFO["site_name"]} - {_("注册新账号")}'
url = settings.SITE_INFO["site_url"] + "/account/register_email?c=" + v
@ -320,6 +331,36 @@ def send_verification_link(user_id, action, email):
logger.error(e)
@require_http_methods(["POST"])
def verify_code(request):
code = request.POST.get("code")
if not code:
return render(
request,
"common/verify.html",
{
"error": _("无效的验证码"),
},
)
login_email = cache.get(f"login_{code}")
if not login_email or request.session.get("login_email") != login_email:
return render(
request,
"common/verify.html",
{
"error": _("无效的验证码"),
},
)
cache.delete(f"login_{code}")
user = User.objects.filter(email__iexact=login_email).first()
if user:
resp = login_existing_user(request, user)
else:
resp = register_new_user(request, username=None, email=login_email)
resp.set_cookie("mastodon_domain", "@")
return resp
def verify_email(request):
error = ""
try:

View file

@ -31,6 +31,10 @@ _RESERVED_USERNAMES = [
"oauth2_login",
"__",
"admin",
"administrator",
"system",
"user",
"users",
"api",
"me",
]

View file

@ -85,7 +85,6 @@
id="loginButton"
disabled />
</div>
<script type="text/javascript">if (Cookies.get('mastodon_domain')) $('#domain').val(Cookies.get('mastodon_domain'));</script>
{{ sites|json_script:"sites-data" }}
<script>
function switch_login(){
@ -129,6 +128,15 @@
}
}
});
var d = Cookies.get('mastodon_domain');
if (d) {
if (d == "@") {
$('select').val('email');
switch_login()
} else {
$('#domain').val(Cookies.get('mastodon_domain'));
}
}
</script>
{% else %}
<select name="domain">

View file

@ -8,6 +8,7 @@ urlpatterns = [
path("login/oauth", connect_redirect_back, name="login_oauth"),
path("login/email", verify_email, name="login_email"),
path("verify_email", verify_email, name="verify_email"),
path("verify_code", verify_code, name="verify_code"),
path("register_email", verify_email, name="register_email"),
path("register", register, name="register"),
path("connect", connect, name="connect"),