diff --git a/account/serializers.py b/account/serializers.py index 4d1077ac..a2d27683 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -7,7 +7,7 @@ from .models import User, UserProfile class UserLoginSerializer(serializers.Serializer): username = serializers.CharField(max_length=30) password = serializers.CharField(max_length=30) - captcha = serializers.CharField(min_length=4, max_length=4) + tfa_code = serializers.CharField(min_length=6, max_length=6, required=False) class UsernameCheckSerializer(serializers.Serializer): diff --git a/account/views.py b/account/views.py index 86e28dd8..f964b052 100644 --- a/account/views.py +++ b/account/views.py @@ -42,14 +42,23 @@ class UserLoginAPIView(APIView): serializer = UserLoginSerializer(data=request.data) if serializer.is_valid(): data = serializer.data - captcha = Captcha(request) - if not captcha.check(data["captcha"]): - return error_response(u"验证码错误") + print data user = auth.authenticate(username=data["username"], password=data["password"]) # 用户名或密码错误的话 返回None if user: - auth.login(request, user) - return success_response(u"登录成功") + if not user.two_factor_auth: + auth.login(request, user) + return success_response(u"登录成功") + + # 没有输入两步验证的验证码 + if user.two_factor_auth and "tfa_code" not in data: + return success_response("tfa_required") + + if OtpAuth(user.tfa_token).valid_totp(data["tfa_code"]): + auth.login(request, user) + return success_response(u"登录成功") + else: + return error_response(u"验证码错误") else: return error_response(u"用户名或密码错误") else: diff --git a/oj/urls.py b/oj/urls.py index 94e9d00f..fe86966f 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -135,6 +135,7 @@ urlpatterns = [ url(r'^reset_password/$', TemplateView.as_view(template_name="oj/account/apply_reset_password.html"), name="apply_reset_password_page"), url(r'^reset_password/t/(?P\w+)/$', "account.views.reset_password_page", name="reset_password_page"), url(r'^api/two_factor_auth/$', TwoFactorAuthAPIView.as_view(), name="two_factor_auth_api"), + url(r'^two_factor_auth/$', TemplateView.as_view(template_name="oj/account/two_factor_auth.html"), name="two_factor_auth_page"), ] diff --git a/static/src/css/oj.css b/static/src/css/oj.css index 52701b3d..a04f0a0e 100644 --- a/static/src/css/oj.css +++ b/static/src/css/oj.css @@ -118,3 +118,13 @@ li.problem-tag { padding-top: 7.5px; padding-bottom: 7.5px; } + + +#tfa-qrcode{ + height: 40%; + width: 40%; +} + +#tfa-area{ + display: none; +} \ No newline at end of file diff --git a/static/src/js/app/oj/account/login.js b/static/src/js/app/oj/account/login.js index 8e1351d9..3e35fd05 100644 --- a/static/src/js/app/oj/account/login.js +++ b/static/src/js/app/oj/account/login.js @@ -4,23 +4,31 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c if (!e.isDefaultPrevented()) { var username = $("#username").val(); var password = $("#password").val(); - var captcha = $("#captcha").val(); + var tfaCode = $("#tfa-code").val(); + console.log(tfaCode); + if(tfaCode.length && tfaCode.length != 6){ + bsAlert("验证码为六位数字"); + return false; + } $.ajax({ beforeSend: csrfTokenHeader, url: "/api/login/", - data: {username: username, password: password, captcha: captcha}, + data: {username: username, password: password, tfa_code: tfaCode}, dataType: "json", method: "post", success: function (data) { if (!data.code) { + if(data.data == "tfa_required"){ + $("#tfa-area").show(); + return false; + } function getLocationVal(id){ var temp = unescape(location.search).split(id+"=")[1] || ""; return temp.indexOf("&")>=0 ? temp.split("&")[0] : temp; } var from = getLocationVal("__from"); if(from != ""){ - console.log(from); window.location.href = from; } else{ @@ -28,7 +36,6 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c } } else { - refresh_captcha(); bsAlert(data.data); } }, @@ -40,11 +47,5 @@ require(["jquery", "bsAlert", "csrfToken", "validator"], function ($, bsAlert, c return false; } }); - function refresh_captcha(){ - $("#captcha-img")[0].src = "/captcha/?" + Math.random(); - $("#captcha")[0].value = ""; - } - $("#captcha-img").click(function(){ - refresh_captcha(); - }); + }); \ No newline at end of file diff --git a/static/src/js/app/oj/account/twoFactorAuth.js b/static/src/js/app/oj/account/twoFactorAuth.js new file mode 100644 index 00000000..7c89e5b5 --- /dev/null +++ b/static/src/js/app/oj/account/twoFactorAuth.js @@ -0,0 +1,27 @@ +require(["jquery", "bsAlert", "csrfToken"], function ($, bsAlert, csrfTokenHeader) { + $("#tfa_submit").click(function(){ + var code = $("#tfa_code").val(); + if (code.length != 6){ + bsAlert("验证码是6位数字"); + return; + } + $.ajax({ + beforeSend: csrfTokenHeader, + url: "/api/two_factor_auth/", + data: {code: code}, + dataType: "json", + method: "post", + success: function(data){ + if(data.code){ + bsAlert(data.data); + } + else{ + bsAlert("两步验证开启成功"); + location.reload(); + } + } + }) + + }) +}); + diff --git a/static/src/js/build.js b/static/src/js/build.js index 8c8de627..3688d1f7 100644 --- a/static/src/js/build.js +++ b/static/src/js/build.js @@ -54,31 +54,32 @@ //以下都是页面 script 标签引用的js announcement_0_pack: "app/admin/announcement/announcement", userList_1_pack: "app/admin/user/userList", - problem_2_pack: "app/oj/problem/problem", - submissionList_3_pack: "app/admin/problem/submissionList", - contestCountdown_4_pack: "app/oj/contest/contestCountdown", - avatar_5_pack: "app/oj/account/avatar", - addProblem_6_pack: "app/admin/problem/addProblem", - problem_7_pack: "app/admin/problem/problem", - contestList_8_pack: "app/admin/contest/contestList", - admin_9_pack: "app/admin/admin", - login_10_pack: "app/oj/account/login", - applyResetPassword_11_pack: "app/oj/account/applyResetPassword", - addContest_12_pack: "app/admin/contest/addContest", - contestPassword_13_pack: "app/oj/contest/contestPassword", - changePassword_14_pack: "app/oj/account/changePassword", - monitor_15_pack: "app/admin/monitor/monitor", - editProblem_16_pack: "app/admin/contest/editProblem", - joinGroupRequestList_17_pack: "app/admin/group/joinGroupRequestList", - group_18_pack: "app/oj/group/group", - contestProblemList_19_pack: "app/admin/contest/contestProblemList", - editProblem_20_pack: "app/admin/problem/editProblem", - register_21_pack: "app/oj/account/register", - groupDetail_22_pack: "app/admin/group/groupDetail", - editContest_23_pack: "app/admin/contest/editContest", - resetPassword_24_pack: "app/oj/account/resetPassword", - group_25_pack: "app/admin/group/group", - settings_26_pack: "app/oj/account/settings" + twoFactorAuth_2_pack: "app/oj/account/twoFactorAuth", + problem_3_pack: "app/oj/problem/problem", + submissionList_4_pack: "app/admin/problem/submissionList", + contestCountdown_5_pack: "app/oj/contest/contestCountdown", + avatar_6_pack: "app/oj/account/avatar", + addProblem_7_pack: "app/admin/problem/addProblem", + problem_8_pack: "app/admin/problem/problem", + contestList_9_pack: "app/admin/contest/contestList", + admin_10_pack: "app/admin/admin", + login_11_pack: "app/oj/account/login", + applyResetPassword_12_pack: "app/oj/account/applyResetPassword", + addContest_13_pack: "app/admin/contest/addContest", + contestPassword_14_pack: "app/oj/contest/contestPassword", + changePassword_15_pack: "app/oj/account/changePassword", + monitor_16_pack: "app/admin/monitor/monitor", + editProblem_17_pack: "app/admin/contest/editProblem", + joinGroupRequestList_18_pack: "app/admin/group/joinGroupRequestList", + group_19_pack: "app/oj/group/group", + contestProblemList_20_pack: "app/admin/contest/contestProblemList", + editProblem_21_pack: "app/admin/problem/editProblem", + register_22_pack: "app/oj/account/register", + groupDetail_23_pack: "app/admin/group/groupDetail", + editContest_24_pack: "app/admin/contest/editContest", + resetPassword_25_pack: "app/oj/account/resetPassword", + group_26_pack: "app/admin/group/group", + settings_27_pack: "app/oj/account/settings" }, shim: { avalon: { @@ -96,79 +97,82 @@ name: "userList_1_pack" }, { - name: "problem_2_pack" + name: "twoFactorAuth_2_pack" }, { - name: "submissionList_3_pack" + name: "problem_3_pack" }, { - name: "contestCountdown_4_pack" + name: "submissionList_4_pack" }, { - name: "avatar_5_pack" + name: "contestCountdown_5_pack" }, { - name: "addProblem_6_pack" + name: "avatar_6_pack" }, { - name: "problem_7_pack" + name: "addProblem_7_pack" }, { - name: "contestList_8_pack" + name: "problem_8_pack" }, { - name: "admin_9_pack" + name: "contestList_9_pack" }, { - name: "login_10_pack" + name: "admin_10_pack" }, { - name: "applyResetPassword_11_pack" + name: "login_11_pack" }, { - name: "addContest_12_pack" + name: "applyResetPassword_12_pack" }, { - name: "contestPassword_13_pack" + name: "addContest_13_pack" }, { - name: "changePassword_14_pack" + name: "contestPassword_14_pack" }, { - name: "monitor_15_pack" + name: "changePassword_15_pack" }, { - name: "editProblem_16_pack" + name: "monitor_16_pack" }, { - name: "joinGroupRequestList_17_pack" + name: "editProblem_17_pack" }, { - name: "group_18_pack" + name: "joinGroupRequestList_18_pack" }, { - name: "contestProblemList_19_pack" + name: "group_19_pack" }, { - name: "editProblem_20_pack" + name: "contestProblemList_20_pack" }, { - name: "register_21_pack" + name: "editProblem_21_pack" }, { - name: "groupDetail_22_pack" + name: "register_22_pack" }, { - name: "editContest_23_pack" + name: "groupDetail_23_pack" }, { - name: "resetPassword_24_pack" + name: "editContest_24_pack" }, { - name: "group_25_pack" + name: "resetPassword_25_pack" }, { - name: "settings_26_pack" + name: "group_26_pack" + }, + { + name: "settings_27_pack" } ], optimizeCss: "standard", diff --git a/static/src/js/config.js b/static/src/js/config.js index ee6f52b2..d860d68b 100644 --- a/static/src/js/config.js +++ b/static/src/js/config.js @@ -56,31 +56,32 @@ var require = { //以下都是页面 script 标签引用的js announcement_0_pack: "app/admin/announcement/announcement", userList_1_pack: "app/admin/user/userList", - problem_2_pack: "app/oj/problem/problem", - submissionList_3_pack: "app/admin/problem/submissionList", - contestCountdown_4_pack: "app/oj/contest/contestCountdown", - avatar_5_pack: "app/oj/account/avatar", - addProblem_6_pack: "app/admin/problem/addProblem", - problem_7_pack: "app/admin/problem/problem", - contestList_8_pack: "app/admin/contest/contestList", - admin_9_pack: "app/admin/admin", - login_10_pack: "app/oj/account/login", - applyResetPassword_11_pack: "app/oj/account/applyResetPassword", - addContest_12_pack: "app/admin/contest/addContest", - contestPassword_13_pack: "app/oj/contest/contestPassword", - changePassword_14_pack: "app/oj/account/changePassword", - monitor_15_pack: "app/admin/monitor/monitor", - editProblem_16_pack: "app/admin/contest/editProblem", - joinGroupRequestList_17_pack: "app/admin/group/joinGroupRequestList", - group_18_pack: "app/oj/group/group", - contestProblemList_19_pack: "app/admin/contest/contestProblemList", - editProblem_20_pack: "app/admin/problem/editProblem", - register_21_pack: "app/oj/account/register", - groupDetail_22_pack: "app/admin/group/groupDetail", - editContest_23_pack: "app/admin/contest/editContest", - resetPassword_24_pack: "app/oj/account/resetPassword", - group_25_pack: "app/admin/group/group", - settings_26_pack: "app/oj/account/settings", + twoFactorAuth_2_pack: "app/oj/account/twoFactorAuth", + problem_3_pack: "app/oj/problem/problem", + submissionList_4_pack: "app/admin/problem/submissionList", + contestCountdown_5_pack: "app/oj/contest/contestCountdown", + avatar_6_pack: "app/oj/account/avatar", + addProblem_7_pack: "app/admin/problem/addProblem", + problem_8_pack: "app/admin/problem/problem", + contestList_9_pack: "app/admin/contest/contestList", + admin_10_pack: "app/admin/admin", + login_11_pack: "app/oj/account/login", + applyResetPassword_12_pack: "app/oj/account/applyResetPassword", + addContest_13_pack: "app/admin/contest/addContest", + contestPassword_14_pack: "app/oj/contest/contestPassword", + changePassword_15_pack: "app/oj/account/changePassword", + monitor_16_pack: "app/admin/monitor/monitor", + editProblem_17_pack: "app/admin/contest/editProblem", + joinGroupRequestList_18_pack: "app/admin/group/joinGroupRequestList", + group_19_pack: "app/oj/group/group", + contestProblemList_20_pack: "app/admin/contest/contestProblemList", + editProblem_21_pack: "app/admin/problem/editProblem", + register_22_pack: "app/oj/account/register", + groupDetail_23_pack: "app/admin/group/groupDetail", + editContest_24_pack: "app/admin/contest/editContest", + resetPassword_25_pack: "app/oj/account/resetPassword", + group_26_pack: "app/admin/group/group", + settings_27_pack: "app/oj/account/settings" }, shim: { avalon: { diff --git a/template/src/oj/account/avatar.html b/template/src/oj/account/avatar.html index d551c4a9..25a6d17f 100644 --- a/template/src/oj/account/avatar.html +++ b/template/src/oj/account/avatar.html @@ -10,6 +10,7 @@
  • 通用设置
  • 个人信息
  • 更换头像
  • +
  • 两步验证
  • 修改密码
  • diff --git a/template/src/oj/account/change_password.html b/template/src/oj/account/change_password.html index 9fd13340..25faf1d8 100644 --- a/template/src/oj/account/change_password.html +++ b/template/src/oj/account/change_password.html @@ -10,6 +10,7 @@
  • 通用设置
  • 个人信息
  • 更换头像
  • +
  • 两步验证
  • 修改密码
  • diff --git a/template/src/oj/account/login.html b/template/src/oj/account/login.html index eadb5cc0..04302f17 100644 --- a/template/src/oj/account/login.html +++ b/template/src/oj/account/login.html @@ -11,22 +11,21 @@
    + data-error="请填写用户名" placeholder="用户名" autofocus required autocomplete="off">
    + data-error="请填写密码" placeholder="密码" required autocomplete="off">
    -
    -    -

    - +
    + +
    diff --git a/template/src/oj/account/settings.html b/template/src/oj/account/settings.html index 53560240..e6436bb5 100644 --- a/template/src/oj/account/settings.html +++ b/template/src/oj/account/settings.html @@ -10,6 +10,7 @@
  • 通用设置
  • 个人信息
  • 更换头像
  • +
  • 两步验证
  • 修改密码
  • diff --git a/template/src/oj/account/two_factor_auth.html b/template/src/oj/account/two_factor_auth.html new file mode 100644 index 00000000..fe35d74d --- /dev/null +++ b/template/src/oj/account/two_factor_auth.html @@ -0,0 +1,41 @@ +{% extends "oj_base.html" %} +{% block title %} + 两步验证 +{% endblock %} +{% block body %} +
    + +
    + +
    + +
    + {% if not request.user.two_factor_auth %} +

    扫描二维码开启两步验证

    + + +
    +
    + + +
    + +
    + + {% else %} + + {% endif %} +
    +
    +{% endblock %} + +{% block js_block %} + +{% endblock %}