Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2FA支持短信验证 & 同时开启多种验证方式 #1625

Merged
merged 6 commits into from
Jun 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions common/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,27 +109,22 @@ def authenticate_entry(request):
if SysConfig().get('enforce_2fa'):
# 用户是否配置过2fa
if twofa_enabled:
auth_type = twofa_enabled[0].auth_type
verify_mode = 'verify_only'
else:
auth_type = 'totp'
verify_mode = 'verify_config'
# 设置无登录状态cookie
# 设置无登录状态session
s = SessionStore()
s['user'] = authenticated_user.username
s['auth_type'] = auth_type
s['verify_mode'] = verify_mode
s.set_expiry(300)
s.create()
result = {'status': 0, 'msg': 'ok', 'data': s.session_key}
else:
# 用户是否配置过2fa
if twofa_enabled:
auth_type = twofa_enabled[0].auth_type
# 设置无登录状态cookie
# 设置无登录状态session
s = SessionStore()
s['user'] = authenticated_user.username
s['auth_type'] = auth_type
s['verify_mode'] = 'verify_only'
s.set_expiry(300)
s.create()
Expand Down
24 changes: 22 additions & 2 deletions common/static/dist/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ var jsonHighLight = function(json) {
});
};

// 这个函数存在报错,因此不应该把任何模块放在这个模块之后
// 实例配置页面根据db_type选择显示或隐藏mode字段,mode字段只适用于redis实例
(function($) {
$(function() {
Expand All @@ -62,4 +61,25 @@ var jsonHighLight = function(json) {
toggleMode($(this).val());
});
});
})(django && django.jQuery || jQuery);
})(jQuery);

// 短信验证码倒计时
let countdown = 60, clock, btn_captcha;
function init_countdown(obj) {
btn_captcha = obj;
btn_captcha.disabled = true;
btn_captcha.innerText = countdown + ' 秒后重试';
clock = setInterval(countdown_loop, 1000);
}

function countdown_loop() {
countdown--;
if(countdown > 0){
btn_captcha.innerText = countdown + ' 秒后重试';
} else {
clearInterval(clock);
btn_captcha.disabled = false;
btn_captcha.innerText = '获取验证码';
countdown = 60;
}
}
148 changes: 103 additions & 45 deletions common/templates/2fa.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,51 +22,56 @@
<form class="login-form fade-in-effect" id="auth" method="post" role="form">
{% csrf_token %}
{% if verify_mode == 'verify_config' %}
<div class="form-group">
<h4 style="font-weight: bold">启用两步验证</h4>
</div>
<div class="form-group">
<label for="auth_type">验证方式:</label>
<select id="auth_type" class="form-control show-tick selectpicker" name="instances"
title="选择额外验证方式:"
data-live-search="true">
<option value="totp" selected="selected">Google身份验证器</option>
</select>
</div>
<div class="form-group" style="display: grid">
<label class="control-label" for="qrcode-img">1. 使用Google身份验证器扫码:</label>
<img id="qrcode-img" key="" src="" style="width: 100%;height: auto;">
</div>
<div class="form-group">
<label class="control-label" for="otpCode">2. 输入6位验证码完成验证:</label>
<input class="form-control ng-valid ng-dirty ng-touched" id="otpCode" name="otpCode" type="text"
oninput="value=value.replace(/[^\d]/g,'')" autocomplete="off" required>
</div>
<div class="form-group">
<button id="btnAuth" type="button" class="btn btn-success btn-block"><i class="fa-lock"></i>验证</button>
</div>
{% else %}
{% if auth_type == 'totp' %}
<div class="form-group is-focused">
<label class="control-label" for="otpCode">OTP验证码</label>
<input class="form-control ng-valid ng-dirty ng-touched" id="otpCode" name="otpCode" type="text"
oninput="value=value.replace(/[^\d]/g,'')" autocomplete="off" required>
<div class="form-group">
<h4 style="font-weight: bold">启用两步验证</h4>
</div>
<div class="form-group">
<label for="auth_type">验证方式:</label>
<select id="auth_type" class="form-control show-tick selectpicker" name="auth_type"
title="选择额外验证方式:"
data-live-search="true">
<option value="totp" selected="selected">Google身份验证器</option>
<option value="sms">短信验证码</option>
</select>
</div>

<div id="totp-form" class="form-group" style="display: grid">
<label class="control-label" for="qrcode-img">1. 使用Google身份验证器扫码:</label>
<img id="qrcode-img" key="" src="" style="width: 100%;height: auto;">
</div>
<div id="sms-form" class="form-group" style="display:none;">
<label class="control-label" for="phone">1. 输入要绑定的手机号码:</label>
<input class="form-control ng-valid ng-dirty ng-touched" id="phone" name="phone" type="text"
oninput="value=value.replace(/[^\d]/g,'')" autocomplete="off" required>
</div>
<div class="form-group">
<label class="control-label" for="otpCode">2. 输入6位验证码完成验证:</label>
<input class="form-control ng-valid ng-dirty ng-touched" id="otpCode" name="otpCode" type="text"
oninput="value=value.replace(/[^\d]/g,'')" autocomplete="off" required>
</div>
<div class="form-group" style="text-align: center">
<button id="btnCaptcha" type="button" class="btn btn-primary btn-block" style="display: none">获取验证码</button>
<button id="btnAuth" type="button" class="btn btn-success btn-block"><i class="fa-lock"></i>验证</button>
</div>
{% else %}
<div class="form-group is-focused">
<label class="control-label" for="otpCode">验证码</label>
{% else %}
<div class="form-group">
<label for="auth_type">选择验证方式:</label>
<select id="auth_type" class="form-control show-tick selectpicker" name="auth_type"
title="选择额外验证方式:"
data-live-search="true">
</select>
</div>
<div class="form-group">
<label id="totp-form" class="control-label" for="otpCode">OTP验证码</label>
<label id="sms-form" class="control-label" for="otpCode">短信验证码</label>
<input class="form-control ng-valid ng-dirty ng-touched" id="otpCode" name="otpCode" type="text"
oninput="value=value.replace(/[^\d]/g,'')" autocomplete="off" required>
</div>
<input id="phone" value="{{ phone }}" style="display: none">
<div class="form-group">
<button id="btnCaptcha" type="button" class="btn btn-default btn-block" >获取验证码</button>
<button id="btnAuth" type="button" class="btn btn-success btn-block" style="display: none"><i class="fa-lock"></i>验证</button>
<button id="btnCaptcha" type="button" class="btn btn-primary btn-block" >获取验证码</button>
<button id="btnAuth" type="button" class="btn btn-success btn-block"><i class="fa-lock"></i>验证</button>
</div>
{% endif %}
{% endif %}
<input type="text" style="display:none">
</form>
Expand All @@ -81,6 +86,7 @@ <h4 style="font-weight: bold">启用两步验证</h4>
<script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
<script src="{% static 'bootstrap-select/js/bootstrap-select.min.js' %}"></script>
<script src="{% static 'bootstrap-select/js/i18n/defaults-zh_CN.min.js' %}"></script>
<script src="{% static 'dist/js/utils.js' %}"></script>
</body>
<!-- 解决CSRF-->
<script>
Expand Down Expand Up @@ -114,6 +120,10 @@ <h4 style="font-weight: bold">启用两步验证</h4>
//keycode==13为回车键
if (event.keyCode === 13) {
let otp = $('#otpCode').val();
if (!otp) {
alert('请输入验证码!')
return
}
authOTP(otp);
}
});
Expand All @@ -124,14 +134,59 @@ <h4 style="font-weight: bold">启用两步验证</h4>
let data = config_2fa();
$("#qrcode-img").attr("key", data.data.key)
$("#qrcode-img").attr("src", "/user/qrcode/" + data.data.key)
} else if ('{{ verify_mode }}' === 'verify_only') {
let auth_types = {{ auth_types|safe }};
for (i=0;i < auth_types.length;i++) {
let auth_type;
if (i === 0) {
auth_type = '<option value="' + auth_types[i].code + '" selected="selected">' + auth_types[i].display + '</option>'
} else {
auth_type = '<option value="' + auth_types[i].code + '">' + auth_types[i].display + '</option>'
}
$("#auth_type").append(auth_type)
$("#auth_type").trigger('change')
}
}
})

$('#btnAuth').click(function () {
let otp = $('#otpCode').val();
if (!otp) {
alert('请输入验证码!')
return
}
authOTP(otp);
});

$("#btnCaptcha").click(function () {
if ('{{ verify_mode }}' === 'verify_config') {
let phone = $("#phone").val();
if (!phone) {
alert('请输入手机号!')
return
}
}
let data = config_2fa();
if (data.status === 0) {
init_countdown(this);
alert('验证码已发送,5分钟内有效')
}
})

$("#auth_type").change(function () {
$("#otpCode").val('');
let auth_type = $("#auth_type").val();
if (auth_type === 'totp') {
$("#totp-form").show();
$("#sms-form").hide();
$("#btnCaptcha").hide()
} else if (auth_type === 'sms') {
$("#totp-form").hide();
$("#sms-form").show();
$("#btnCaptcha").show()
}
})

function config_2fa() {
// 配置2fa
let result;
Expand All @@ -141,15 +196,16 @@ <h4 style="font-weight: bold">启用两步验证</h4>
dataType: "json",
data: {
engineer: '{{ username }}',
auth_type: $("#auth_type").val()
enable: 'true',
auth_type: $("#auth_type").val(),
phone: $("#phone").val()
},
async: false,
complete: function () {
},
success: function (data) {
if (data.status === 0) {
result = data
} else {
result = data
if (data.status !== 0) {
alert(data.msg);
}
},
Expand All @@ -160,15 +216,17 @@ <h4 style="font-weight: bold">启用两步验证</h4>
return result
}

function save(key) {
function save() {
$.ajax({
type: "post",
url: "/api/v1/user/2fa/save/",
dataType: "json",
headers: {"X-CSRFToken": getCookie("csrftoken")},
data: {
engineer: '{{ username }}',
key: key,
auth_type: $("#auth_type").val(),
key: $("#qrcode-img").attr('key'),
phone: $("#phone").val()
},
complete: function () {
},
Expand All @@ -186,7 +244,6 @@ <h4 style="font-weight: bold">启用两步验证</h4>
}

function authOTP(otp) {
let key = $("#qrcode-img").attr('key');
$.ajax({
type: "post",
url: "/api/v1/user/2fa/verify/",
Expand All @@ -195,14 +252,15 @@ <h4 style="font-weight: bold">启用两步验证</h4>
engineer: '{{ username }}',
auth_type: $("#auth_type").val(),
otp: otp,
key: key
key: $("#qrcode-img").attr('key'),
phone: $("#phone").val()
},
complete: function () {
},
success: function (data) {
if (data.status === 0) {
if ('{{ verify_mode }}' === 'verify_config') {
save(key);
save();
}
$(location).attr('href', '/index/');
} else {
Expand All @@ -213,6 +271,6 @@ <h4 style="font-weight: bold">启用两步验证</h4>
alert(errorThrown + ' : ' + XMLHttpRequest.responseText)
}
});
};
}
</script>
</html>
Loading