用户自行找回密码流程走通,测试通过。客户端弱密码检测功能实现了,服务端的弱密码检测功能还需要实现。

pull/105/head
Apex Liu 2017-11-16 19:54:08 +08:00
parent 04cc466ea2
commit 05297ec5e5
14 changed files with 438 additions and 141 deletions

File diff suppressed because one or more lines are too long

View File

@ -72,7 +72,6 @@ $app.on_init = function (cb_stack) {
$app.dom.area_captcha.slideUp(100); $app.dom.area_captcha.slideUp(100);
}); });
$app.dom.btn_login.click($app.login_account); $app.dom.btn_login.click($app.login_account);
$app.dom.captcha_image.click(function () { $app.dom.captcha_image.click(function () {
@ -173,13 +172,12 @@ $app.login_account = function () {
$app.do_account_login(str_username, str_password, str_captcha, str_oath, is_remember); $app.do_account_login(str_username, str_password, str_captcha, str_oath, is_remember);
} }
else { else {
$app.dom.btn_login.removeAttr('disabled');
$app.hide_op_box(); $app.hide_op_box();
$app.show_op_box('error', tp_error_msg(ret.code, ret.message)); $app.show_op_box('error', tp_error_msg(ret.code, ret.message));
$app.dom.captcha_image.attr('src', '/auth/captcha?h=36&rnd=' + Math.random()); $app.dom.captcha_image.attr('src', '/auth/captcha?h=36&rnd=' + Math.random());
$app.dom.input_captcha.focus().select().val(''); $app.dom.input_captcha.focus().select().val('');
} }
$app.dom.btn_login.removeAttr('disabled');
}, },
function () { function () {
$app.hide_op_box(); $app.hide_op_box();
@ -232,9 +230,9 @@ $app.init_blur_bg = function () {
}; };
$app._update_blur_bg = function () { $app._update_blur_bg = function () {
for(;;) { for (; ;) {
var img_id = Math.floor(Math.random() * (BLUR_BG_IMG.length)); var img_id = Math.floor(Math.random() * (BLUR_BG_IMG.length));
if(img_id !== $app.last_img_idx) { if (img_id !== $app.last_img_idx) {
$app.last_img_idx = img_id; $app.last_img_idx = img_id;
break; break;
} }
@ -251,9 +249,9 @@ $app.init_slogan = function () {
}; };
$app._update_slogan = function () { $app._update_slogan = function () {
for(;;) { for (; ;) {
var msg_id = Math.floor(Math.random() * (SLOGAN.length)); var msg_id = Math.floor(Math.random() * (SLOGAN.length));
if(msg_id !== $app.last_slogan_idx) { if (msg_id !== $app.last_slogan_idx) {
$app.last_slogan_idx = msg_id; $app.last_slogan_idx = msg_id;
break; break;
} }

View File

@ -4,12 +4,13 @@
"use strict"; "use strict";
$tp.notify_error = function (message_, title_) { $tp.notify_error = function (message_, title_, timeout_) {
var _title = title_ || ''; var _title = title_ || '';
var _t = timeout_ || 15000;
$.gritter.add({ $.gritter.add({
//sticky:true, //sticky:true,
class_name: 'gritter-error', class_name: 'gritter-error',
time: 10000, time: _t,
title: '<i class="fa fa-warning fa-fw"></i> 错误:' + _title, title: '<i class="fa fa-warning fa-fw"></i> 错误:' + _title,
text: message_ text: message_
}); });

View File

@ -169,6 +169,7 @@ function tp_format_datetime(timestamp, format) {
} }
var base64KeyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; var base64KeyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
function tp_base64_encode(input) { function tp_base64_encode(input) {
var output = ""; var output = "";
var chr1, chr2, chr3 = ""; var chr1, chr2, chr3 = "";
@ -236,6 +237,7 @@ function tp_get_file_name(path) {
} }
var g_unique_id = (new Date()).valueOf(); var g_unique_id = (new Date()).valueOf();
function tp_generate_id() { function tp_generate_id() {
return g_unique_id++; return g_unique_id++;
} }
@ -251,6 +253,7 @@ function htmlEncode(_s) {
s = s.replace(/\"/g, "&quot;"); s = s.replace(/\"/g, "&quot;");
return s; return s;
} }
// //
///*2.用正则表达式实现html解码*/ ///*2.用正则表达式实现html解码*/
//function htmlDecode(_s) { //function htmlDecode(_s) {
@ -274,11 +277,35 @@ function tp_sleep4debug(duration) {
// 获取长度为len的随机字符串 // 获取长度为len的随机字符串
function tp_gen_random_string(len) { function tp_gen_random_string(len) {
len = len || 32; len = len || 32;
var _chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; // 默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1 var _chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; // 默认去掉了容易混淆的字符oO,Ll,9gq,Vv,Uu,I1
var max_pos = _chars.length; var max_pos = _chars.length;
var ret = ''; var ret = '';
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
ret += _chars.charAt(Math.floor(Math.random() * max_pos)); ret += _chars.charAt(Math.floor(Math.random() * max_pos));
} }
return ret; return ret;
} }
// 弱密码检测
function tp_check_strong_password(p) {
var s = 0;
if (p.length < 8)
return false;
for (var i = 0; i < p.length; ++i) {
var c = p.charCodeAt(i);
if (c >= 48 && c <= 57) // 数字
s |= 1;
else if (c >= 65 && c <= 90) // 大写字母
s |= 2;
else if (c >= 97 && c <= 122) // 小写字母
s |= 4;
else
s |= 8;
}
if((s&1) && (s&2) && (s&4))
return true;
else
return false;
}

View File

@ -5,62 +5,262 @@ $app.on_init = function (cb_stack) {
title: $('#title'), title: $('#title'),
icon_bg: $('#icon-bg'), icon_bg: $('#icon-bg'),
err_area: $('#error-area'), op_message: null,
err_message: $('#err-message'),
err_actions: $('#err-actions'),
find_password_area: $('#find-my-password'), error: {
username: $('#username'), area: $('#area-error'),
email: $('#email'), message: $('#area-error [data-field="message"]')
captcha_image: $('#captcha-image'), },
captcha: $('#captcha'),
btn_send_email: $('#btn-send-email'),
send_result:$('#send-result'),
password_area: $('#password-area') find: {
area: $('#area-find-password'),
captcha_image: $('#area-find-password [data-field="captcha-image"]'),
input_username: $('#area-find-password [data-field="input-username"]'),
input_email: $('#area-find-password [data-field="input-email"]'),
input_captcha: $('#area-find-password [data-field="input-captcha"]'),
btn_submit: $('#area-find-password [data-field="btn-submit"]'),
message: $('#area-find-password [data-field="message"]')
},
set_password: {
area: $('#area-set-password'),
info: $('#area-set-password [data-field="info"]'),
input_password: $('#area-set-password [data-field="input-password"]'),
btn_switch_password: $('#area-set-password [data-field="btn-switch-password"]'),
btn_switch_password_icon: $('#area-set-password [data-field="btn-switch-password"] i'),
btn_submit: $('#area-set-password [data-field="btn-submit"]'),
message: $('#area-set-password [data-field="message"]')
}
}; };
$app.dom.captcha_image.click(function () { $app.dom.find.captcha_image.click(function () {
$(this).attr('src', '/auth/captcha?h=28&rnd=' + Math.random()); $(this).attr('src', '/auth/captcha?h=28&rnd=' + Math.random());
$app.dom.captcha.focus().val(''); $app.dom.find.input_captcha.focus().val('');
}); });
$app.dom.btn_send_email.click(function() { $app.dom.find.btn_submit.click(function () {
$app.on_send_email(); $app.on_send_find_password_email();
}); });
$app.options.mode = 3; $app.dom.find.input_username.keydown(function (event) {
if (event.which === 13) {
$app.dom.find.input_email.focus();
} else {
$('[data-toggle="popover"]').popover('hide');
}
});
$app.dom.find.input_email.keydown(function (event) {
if (event.which === 13) {
$app.dom.find.input_captcha.focus();
} else {
$('[data-toggle="popover"]').popover('hide');
}
});
$app.dom.find.input_captcha.keydown(function (event) {
if (event.which === 13) {
$app.on_send_find_password_email();
} else {
$('[data-toggle="popover"]').popover('hide');
}
});
$app.dom.set_password.btn_submit.click(function () {
$app.on_set_new_password();
});
$app.dom.set_password.input_password.keydown(function (event) {
if (event.which === 13) {
$app.on_set_new_password();
} else {
$('[data-toggle="popover"]').popover('hide');
}
});
$app.dom.set_password.btn_switch_password.click(function () {
if ('password' === $app.dom.set_password.input_password.attr('type')) {
$app.dom.set_password.input_password.attr('type', 'text');
$app.dom.set_password.btn_switch_password_icon.removeClass('fa-eye').addClass('fa-eye-slash')
} else {
$app.dom.set_password.input_password.attr('type', 'password');
$app.dom.set_password.btn_switch_password_icon.removeClass('fa-eye-slash').addClass('fa-eye')
}
});
if ($app.options.mode === 1) { if ($app.options.mode === 1) {
// show 'find-my-password' page // show 'find-my-password' page
$app.dom.op_message = $app.dom.find.message;
$app.dom.title.text('重置密码'); $app.dom.title.text('重置密码');
$app.dom.icon_bg.addClass('fa fa-search-plus').css('color', '#4366de'); $app.dom.icon_bg.addClass('fa fa-search-plus').css('color', '#286090');
$app.dom.captcha_image.attr('src', '/auth/captcha?h=28&rnd=' + Math.random()); $app.dom.find.captcha_image.attr('src', '/auth/captcha?h=28&rnd=' + Math.random());
$app.dom.find_password_area.show(); $app.dom.find.area.show();
$app.dom.username.focus(); $app.dom.find.input_username.focus();
} else if ($app.options.mode === 2) { } else if ($app.options.mode === 2) {
// show 'password-expired' page // show 'error' page
$app.dom.icon_bg.addClass('fa fa-warning').css('color', '#ff6242'); $app.dom.icon_bg.addClass('fa fa-warning').css('color', '#ff6242');
if ($app.options.code === TPE_NOT_EXISTS) { if ($app.options.code === TPE_NOT_EXISTS) {
$app.dom.title.text('链接无效'); $app.dom.title.text('链接无效');
$app.dom.err_message.html('当前使用的密码重置链接无效可能已经因过期而被清除密码重置链接仅在24小时内有效请及时设置您的新密码'); $app.dom.error.message.html('当前使用的密码重置链接无效可能已经因过期而被清除密码重置链接仅在24小时内有效请及时设置您的新密码');
} else if ($app.options.code === TPE_EXPIRED) { } else if ($app.options.code === TPE_EXPIRED) {
$app.dom.title.text('链接已过期'); $app.dom.title.text('链接已过期');
$app.dom.err_message.html('当前使用的密码重置链接已经过期密码重置链接仅在24小时内有效请及时设置您的新密码'); $app.dom.error.message.html('当前使用的密码重置链接已经过期密码重置链接仅在24小时内有效请及时设置您的新密码');
} else if ($app.options.code === TPE_NETWORK) {
$app.dom.title.text('功能未启用');
$app.dom.error.message.html('系统尚未配置邮件发送功能,无法进行密码找回操作!');
} else if ($app.options.code === TPE_PRIVILEGE) {
$app.dom.title.text('功能已禁用');
$app.dom.error.message.html('根据系统配置,禁止进行密码找回操作!');
} else { } else {
$app.dom.title.text('更新密码'); $app.dom.title.text('发生错误!');
$app.dom.error.message.html('很抱歉发生了错误:' + tp_error_msg($app.options.code));
} }
$app.dom.err_area.show(); $app.dom.error.area.show();
} else if ($app.options.mode === 3) { } else if ($app.options.mode === 3) {
// show 'set-new-password' page // show 'set-new-password' page
$app.dom.op_message = $app.dom.set_password.message;
$app.dom.title.text('重置密码'); $app.dom.title.text('重置密码');
$app.dom.icon_bg.addClass('fa fa-info-circle').css('color', '#357a3c'); $app.dom.icon_bg.addClass('fa fa-info-circle').css('color', '#357a3c');
$app.dom.password_area.show(); if ($app.options.force_strong)
$app.dom.set_password.info.show();
$app.dom.set_password.area.show();
} }
cb_stack.exec(); cb_stack.exec();
}; };
$app.on_send_email = function() { $app.hide_op_box = function () {
$app.dom.op_message.hide();
};
$app.show_op_box = function (op_type, op_msg) {
$app.dom.op_message.html(op_msg);
$app.dom.op_message.removeClass().addClass('op_box op_' + op_type);
$app.dom.op_message.show();
};
$app.on_send_find_password_email = function () {
$app.hide_op_box();
var str_username = $app.dom.find.input_username.val();
var str_email = $app.dom.find.input_email.val();
var str_captcha = $app.dom.find.input_captcha.val();
if (str_username.length === 0) {
$app.show_op_box('error', '账号未填写!');
$app.dom.find.input_username.attr('data-content', "请填写您的账号!").focus().popover('show');
// $app.dom.find.input_username.focus();
return;
}
if (str_email.length === 0) {
$app.show_op_box('error', '邮箱未填写!');
$app.dom.find.input_email.attr('data-content', "请填写您的邮箱!").focus().popover('show');
// $app.dom.find.input_email.focus();
return;
}
if (str_captcha.length !== 4) {
$app.show_op_box('error', '验证码错误!');
$app.dom.find.input_captcha.attr('data-content', "验证码为4位数字和字母的组合请重新填写").focus().select().popover('show');
return;
}
$app.dom.find.btn_submit.attr('disabled', 'disabled');
$tp.ajax_post_json('/auth/verify-captcha', {captcha: str_captcha},
function (ret) {
if (ret.code === TPE_OK) {
// 验证成功
$app.hide_op_box();
$app.show_op_box('wait', '<i class="fa fa-circle-o-notch fa-spin"></i> 正在发送密码重置确认函,请稍候...');
$app.do_send_reset_email(str_username, str_email, str_captcha);
}
else {
$app.dom.find.btn_submit.removeAttr('disabled');
$app.hide_op_box();
$app.show_op_box('error', tp_error_msg(ret.code, ret.message));
$app.dom.captcha_image.attr('src', '/auth/captcha?h=28&rnd=' + Math.random());
$app.dom.input_captcha.focus().select().val('');
}
},
function () {
$app.hide_op_box();
$app.show_op_box('error', '很抱歉,无法连接服务器!请稍后再试一次!');
$app.dom.find.btn_submit.removeAttr('disabled');
}
);
};
$app.do_send_reset_email = function (str_username, str_email, str_captcha) {
$tp.ajax_post_json('/user/do-reset-password', {
mode: 3,
username: str_username,
email: str_email,
captcha: str_captcha
},
function (ret) {
$app.dom.find.btn_submit.removeAttr('disabled');
if (ret.code === TPE_OK) {
$app.show_op_box('success', '密码重置确认函已发送!');
} else {
$app.hide_op_box();
var msg = '';
if (ret.code === TPE_NOT_EXISTS)
msg = tp_error_msg(ret.code, '没有此用户');
else
msg = tp_error_msg(ret.code, ret.message);
$app.show_op_box('error', msg);
}
},
function () {
$app.dom.find.btn_submit.removeAttr('disabled');
$app.hide_op_box();
$app.show_op_box('error', '网络故障,密码重置确认函发送失败!');
},
15000
);
};
$app.on_set_new_password = function () {
$app.hide_op_box();
var str_password = $app.dom.set_password.input_password.val();
if (str_password.length === 0) {
$app.show_op_box('error', '密码未填写!');
$app.dom.set_password.input_password.attr('data-content', "请设置您的新密码!").focus().popover('show');
return;
}
if ($app.options.force_strong) {
if (!tp_check_strong_password(str_password)) {
$app.show_op_box('error', '抱歉,不能使用弱密码!');
$app.dom.set_password.input_password.attr('data-content', "请设置强密码至少8位必须包含大写字母、小写字母以及数字").focus().popover('show');
return;
}
}
$tp.ajax_post_json('/user/do-reset-password', {
mode: 4,
token: $app.options.token,
password: str_password
},
function (ret) {
$app.dom.find.btn_submit.removeAttr('disabled');
if (ret.code === TPE_OK) {
$app.show_op_box('success', '密码已重置,正在转到登录界面!');
setTimeout(function () {
window.location.href = '/';
}, 2000);
} else {
var msg = '';
if (ret.code === TPE_NOT_EXISTS)
msg = tp_error_msg(ret.code, '无效的密码重置链接!');
else
msg = tp_error_msg(ret.code, ret.message);
$app.show_op_box('error', msg);
}
},
function () {
$app.dom.find.btn_submit.removeAttr('disabled');
$app.hide_op_box();
$app.show_op_box('error', '网络故障,密码重置失败!');
}
);
}; };

View File

@ -989,10 +989,8 @@ $app.create_dlg_reset_password = function () {
dlg.do_send_reset_email = function () { dlg.do_send_reset_email = function () {
dlg.dom.btn_send_reset_email.attr('disabled', 'disabled'); dlg.dom.btn_send_reset_email.attr('disabled', 'disabled');
$tp.ajax_post_json('/user/do-reset-password', { $tp.ajax_post_json('/user/do-reset-password', {
id: dlg.field_id,
mode: 1, mode: 1,
// email: dlg.field_email, id: dlg.field_id
password: ''
}, },
function (ret) { function (ret) {
dlg.dom.btn_send_reset_email.removeAttr('disabled'); dlg.dom.btn_send_reset_email.removeAttr('disabled');
@ -1020,9 +1018,8 @@ $app.create_dlg_reset_password = function () {
} }
$tp.ajax_post_json('/user/do-reset-password', { $tp.ajax_post_json('/user/do-reset-password', {
id: dlg.field_id,
mode: 2, mode: 2,
// email: '', id: dlg.field_id,
password: dlg.field_password password: dlg.field_password
}, },
function (ret) { function (ret) {

View File

@ -40,13 +40,6 @@ body {
} }
} }
//@header-height: @top-navbar-height; // + @top-toolbar-height;
// 顶部状态栏
//.page-header-fixed {
//padding-top: @header-height;
//}
.page-header { .page-header {
//border: none; //border: none;
box-shadow: 0 0 3px rgba(0, 0, 0, .5); box-shadow: 0 0 3px rgba(0, 0, 0, .5);
@ -166,6 +159,37 @@ body {
} }
} }
.op_box {
display: block;
padding: 5px;
//border-radius: 3px;
//text-align: center;
//margin: 5px 20px 10px 20px;
//margin: 5px 0;
margin: 0;
&.op_error {
//background: rgba(255, 5, 0, 0.5);
background-color: #ffb8b5;
border: 1px solid #d47e7b;
color: #333;
}
&.op_wait {
//background: rgba(255, 255, 255, 0.3);
background: #e5e5e5;
border: 1px solid #a8a8a8;
color: #333;
}
&.op_success {
//background: rgba(255, 255, 255, 0.3);
background: #acf1b2;
border: 1px solid #82df82;
color: #333;
}
}
// for error/reset-password page // for error/reset-password page
.info-box { .info-box {
@error-icon-size: 164px; @error-icon-size: 164px;
@ -203,11 +227,16 @@ body {
hr { hr {
//border-top: 1px solid #d3d3d3; //border-top: 1px solid #d3d3d3;
//border-bottom: 1px solid #fff; //border-bottom: 1px solid #fff;
margin:10px 0 20px; margin: 10px 0 20px;
border-top: none; border-top: none;
border-bottom: 1px dashed #d3d3d3; border-bottom: 1px dashed #d3d3d3;
} }
} }
.op_box {
margin: 5px 0;
text-align: center;
}
} }
// for maintenance page // for maintenance page
@ -293,25 +322,9 @@ body {
} }
.op_box { .op_box {
display: block;
padding: 5px;
border-radius: 3px;
//text-align: center;
//margin: 5px 20px 10px 20px;
margin: 5px 0; margin: 5px 0;
} }
.op_error {
//background: rgba(255, 5, 0, 0.5);
background-color: #cc3632;
border: 1px solid #9c2a26;
color: #fff;
}
.op_wait {
background: rgba(255, 255, 255, 0.3);
}
.steps-detail { .steps-detail {
display: none; display: none;
margin: 10px; margin: 10px;

View File

@ -18,11 +18,14 @@
<meta content="black-translucent" name="apple-mobile-web-app-status-bar-style"> <meta content="black-translucent" name="apple-mobile-web-app-status-bar-style">
<title>${self.attr.page_title_[-1]}::TELEPORT</title> <title>${self.attr.page_title_[-1]}::TELEPORT</title>
<link rel="shortcut icon" href="${ static_url('favicon.png') }"> <link rel="shortcut icon" href="${ static_url('favicon.png') }">
<link href="${ static_url('plugins/bootstrap/css/bootstrap.min.css') }" rel="stylesheet" type="text/css"/> <link href="${ static_url('plugins/bootstrap/css/bootstrap.min.css') }" rel="stylesheet" type="text/css"/>
<link href="${ static_url('plugins/font-awesome/css/font-awesome.min.css') }" rel="stylesheet"> <link href="${ static_url('plugins/font-awesome/css/font-awesome.min.css') }" rel="stylesheet">
<link href="${ static_url('plugins/gritter/css/jquery.gritter.css') }" rel="stylesheet"> <link href="${ static_url('plugins/gritter/css/jquery.gritter.css') }" rel="stylesheet">
<link href="${ static_url('plugins/jquery/jquery.mCustomScrollbar.min.css') }" rel="stylesheet"> <link href="${ static_url('plugins/jquery/jquery.mCustomScrollbar.min.css') }" rel="stylesheet">
<link href="${ static_url('css/style.css') }" rel="stylesheet" type="text/css"/> <link href="${ static_url('css/style.css') }" rel="stylesheet" type="text/css"/>
<%block name="extend_css_file"/> <%block name="extend_css_file"/>
<%block name="embed_css"/> <%block name="embed_css"/>
</head> </head>

View File

@ -18,8 +18,7 @@
<link href="${ static_url('plugins/bootstrap/css/bootstrap.min.css') }" rel="stylesheet" type="text/css"/> <link href="${ static_url('plugins/bootstrap/css/bootstrap.min.css') }" rel="stylesheet" type="text/css"/>
<link href="${ static_url('plugins/font-awesome/css/font-awesome.min.css') }" rel="stylesheet"> <link href="${ static_url('plugins/font-awesome/css/font-awesome.min.css') }" rel="stylesheet">
<link href="${ static_url('plugins/gritter/css/jquery.gritter.css') }" rel="stylesheet">
## <link href="${ static_url('css/error.css') }" rel="stylesheet" type="text/css"/>
<link href="${ static_url('css/single.css') }" rel="stylesheet" type="text/css"/> <link href="${ static_url('css/single.css') }" rel="stylesheet" type="text/css"/>

View File

@ -11,7 +11,7 @@
<style type="text/css"> <style type="text/css">
.input-addon-desc { .input-addon-desc {
text-align: right; text-align: right;
font-size: 80%; font-size: 90%;
color: #707070; color: #707070;
} }
@ -43,38 +43,42 @@
<hr/> <hr/>
<div id="content" class="content"> <div id="content" class="content">
<div id="error-area" style="display:none;"> <div id="area-error" style="display:none;">
<div id="err-message" class="alert alert-danger"></div> <div data-field="message" class="alert alert-danger"></div>
<div id="err-actions"> <div>
现在您可以: 您可以:
<ul> <ul>
<li><a href="/user/reset-password">尝试重新找回密码</a></li>
<li>联系管理员手工重置密码</li> <li>联系管理员手工重置密码</li>
<li><a href="/user/reset-password">稍后再尝试重置密码</a></li>
</ul> </ul>
</div> </div>
</div> </div>
<div id="find-my-password" style="display: none;"> <div id="area-find-password" style="display: none;">
<div class="row" style="padding:0 20px;"> <div class="row" style="padding:0 20px;">
<div class="col-md-4"> <div class="col-md-5">
<div class="input-group"> <div class="input-group">
<span class="input-group-addon"><i class="fa fa-user-circle-o fa-fw"></i></span> <span class="input-group-addon"><i class="fa fa-user-circle-o fa-fw"></i></span>
<input id="username" type="text" class="form-control mono" placeholder="teleport系统用户名"> <input data-field="input-username" type="text" class="form-control mono" placeholder="teleport系统用户名" data-toggle="popover" data-trigger="manual" data-placement="top">
</div> </div>
<div class="input-group" style="margin-top:10px;"> <div class="input-group" style="margin-top:10px;">
<span class="input-group-addon"><i class="fa fa-envelope-o fa-fw"></i></span> <span class="input-group-addon"><i class="fa fa-envelope-o fa-fw"></i></span>
<input id="email" type="text" class="form-control mono" placeholder="用户绑定的电子邮箱"> <input data-field="input-email" type="text" class="form-control mono" placeholder="用户绑定的电子邮箱" data-toggle="popover" data-trigger="manual" data-placement="top">
</div> </div>
<div class="input-group" style="margin-top:10px;"> <div class="input-group" style="margin-top:10px;">
<span class="input-group-addon"><i class="fa fa-check-square-o fa-fw"></i></span> <span class="input-group-addon"><i class="fa fa-check-square-o fa-fw"></i></span>
<input id="captcha" type="text" class="form-control" placeholder="验证码" <input data-field="input-captcha" type="text" class="form-control" placeholder="验证码" data-toggle="popover" data-trigger="manual" data-placement="top">
data-toggle="popover" data-trigger="manual" data-placement="top"> <span class="input-group-addon captcha-box"><a href="javascript:;"><img data-field="captcha-image" src=""></a></span>
<span class="input-group-addon captcha-box"><a href="javascript:;"><img id="captcha-image" src=""></a></span>
</div> </div>
<p class="input-addon-desc">验证码,点击图片可更换</p> <p class="input-addon-desc">验证码,点击图片可更换</p>
<div style="margin:20px 0;">
<button type="button" class="btn btn-primary" data-field="btn-submit" style="width:100%;"><i class="fa fa-send fa-fw"></i> 发送密码重置确认函</button>
<div data-field="message" class="alert alert-danger" style="display: none;"></div>
</div>
</div> </div>
<div class="col-md-8"> <div class="col-md-7">
<div class="alert alert-info"> <div class="alert alert-info">
<p>请填写用户信息,随后一封密码重置确认函将发送到您的邮箱。</p> <p>请填写用户信息,随后一封密码重置确认函将发送到您的邮箱。</p>
<p>请注意密码重置确认函在24小时内有效</p> <p>请注意密码重置确认函在24小时内有效</p>
@ -83,43 +87,29 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row" style="padding:0 20px;margin-top:10px;">
<div class="col-md-4">
<button type="button" class="btn btn-primary" id="btn-send-email" style="width:100%;"><i class="fa fa-send fa-fw"></i> 发送密码重置确认函</button>
</div>
<div class="col-md-8">
<div id="send-result" class="alert alert-danger" style="display: none;"></div>
</div>
</div>
</div> </div>
<div id="password-area" style="display:none;"> <div id="area-set-password" style="display:none;">
<div class="row" style="padding:0 20px;"> <div class="row">
<div class="col-md-4"> <div class="col-md-5">
<div class="input-group"> <div class="input-group">
<span class="input-group-addon"><i class="fa fa-edit"></i></span> <span class="input-group-addon"><i class="fa fa-edit"></i></span>
<input data-field="password" type="password" class="form-control mono" placeholder="设置新密码"> <input data-field="input-password" type="password" class="form-control mono" placeholder="设置新密码" data-toggle="popover" data-trigger="manual" data-placement="top">
<span class="input-group-btn"><button class="btn btn-default" type="button" id="btn-switch-password"><i class="fa fa-eye fa-fw"></i></button></span> <span class="input-group-btn"><button class="btn btn-default" type="button" data-field="btn-switch-password"><i class="fa fa-eye fa-fw"></i></button></span>
</div> </div>
<div style="margin:20px 0;">
<button type="button" class="btn btn-primary" data-field="btn-submit" style="width:100%;"><i class="fa fa-check fa-fw"></i> 重置密码</button>
<div data-field="message" style="display: none;"></div>
</div>
</div> </div>
<div class="col-md-8"> <div class="col-md-7">
<div class="alert alert-warning"> <div data-field="info" class="alert alert-warning" style="display:none;">
<p>注意系统启用强密码策略要求密码至少8位必须包含大写字母、小写字母以及数字。</p> <p>注意系统启用强密码策略要求密码至少8位必须包含大写字母、小写字母以及数字。</p>
</div> </div>
</div> </div>
</div> </div>
<div class="row" style="padding:0 20px;margin-top:10px;">
<div class="col-md-4">
<button type="button" class="btn btn-primary" id="btn-reset-password" style="width:100%;"><i class="fa fa-check fa-fw"></i> 重置密码</button>
</div>
<div class="col-md-8">
<div id="reset-result" class="alert alert-danger" style="display: none;"></div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -460,11 +460,10 @@ class AppConfig(BaseAppConfig):
# ===================================== # =====================================
# 密码策略相关 # 密码策略相关
# ===================================== # =====================================
# conf_data['password'] = '{"find":false,"strong":true}'
try: try:
_password = json.loads(conf_data['password']) _password = json.loads(conf_data['password'])
except: except:
log.w('invalid password config, use default.\n') log.w('password config not set or invalid, use default.\n')
_password = {} _password = {}
self.sys.password = tp_convert_to_attr_dict(_password) self.sys.password = tp_convert_to_attr_dict(_password)
@ -481,7 +480,7 @@ class AppConfig(BaseAppConfig):
try: try:
_login = json.loads(conf_data['login']) _login = json.loads(conf_data['login'])
except: except:
log.w('invalid login config, use default.\n') log.w('login config not set or invalid, use default.\n')
_login = {} _login = {}
self.sys.login = tp_convert_to_attr_dict(_login) self.sys.login = tp_convert_to_attr_dict(_login)
@ -493,7 +492,6 @@ class AppConfig(BaseAppConfig):
self.sys.login.lock_timeout = 30 # 30 min self.sys.login.lock_timeout = 30 # 30 min
if not self.sys.login.is_exists('auth'): if not self.sys.login.is_exists('auth'):
self.sys.login.auth = TP_LOGIN_AUTH_USERNAME_PASSWORD_CAPTCHA | TP_LOGIN_AUTH_USERNAME_OATH | TP_LOGIN_AUTH_USERNAME_PASSWORD_OATH self.sys.login.auth = TP_LOGIN_AUTH_USERNAME_PASSWORD_CAPTCHA | TP_LOGIN_AUTH_USERNAME_OATH | TP_LOGIN_AUTH_USERNAME_PASSWORD_OATH
# print('==login==', json.dumps(self.sys.login, separators=(',', ':')))
# ===================================== # =====================================
# SMTP相关 # SMTP相关
@ -502,7 +500,7 @@ class AppConfig(BaseAppConfig):
try: try:
_smtp = json.loads(conf_data['smtp']) _smtp = json.loads(conf_data['smtp'])
except: except:
log.w('invalid smtp config, use default.\n') log.w('smtp config not set or invalid, use default.\n')
_smtp = {} _smtp = {}
self.sys.smtp = tp_convert_to_attr_dict(_smtp) self.sys.smtp = tp_convert_to_attr_dict(_smtp)
@ -517,7 +515,6 @@ class AppConfig(BaseAppConfig):
if self.sys.smtp.is_exists('password'): if self.sys.smtp.is_exists('password'):
self.sys_smtp_password = self.sys.smtp.password self.sys_smtp_password = self.sys.smtp.password
self.sys.smtp.password = '********' self.sys.smtp.password = '********'
# del self.sys.smtp.password
# ===================================== # =====================================
# 存储相关 # 存储相关
@ -525,7 +522,7 @@ class AppConfig(BaseAppConfig):
try: try:
_storage = json.loads(conf_data['storage']) _storage = json.loads(conf_data['storage'])
except: except:
log.w('invalid storage config, use default.\n') log.w('storage config not set or invalid, use default.\n')
_storage = {} _storage = {}
self.sys.storage = tp_convert_to_attr_dict(_storage) self.sys.storage = tp_convert_to_attr_dict(_storage)

View File

@ -104,6 +104,10 @@ class DoLoginHandler(TPBaseJsonHandler):
syslog.sys_log({'username': username, 'surname': username}, self.request.remote_ip, TPE_NOT_EXISTS, '登录失败,用户`{}`不存在'.format(username)) syslog.sys_log({'username': username, 'surname': username}, self.request.remote_ip, TPE_NOT_EXISTS, '登录失败,用户`{}`不存在'.format(username))
return self.write_json(err) return self.write_json(err)
if user_info.privilege == 0:
# 尚未为此用户设置角色
return self.write_json(TPE_PRIVILEGE, '用户尚未分配角色')
if user_info['state'] == TP_STATE_LOCKED: if user_info['state'] == TP_STATE_LOCKED:
# 用户已经被锁定,如果系统配置为一定时间后自动解锁,则更新一下用户信息 # 用户已经被锁定,如果系统配置为一定时间后自动解锁,则更新一下用户信息
if sys_cfg.login.lock_timeout != 0: if sys_cfg.login.lock_timeout != 0:

View File

@ -85,9 +85,17 @@ class ResetPasswordHandler(TPBaseHandler):
_token = self.get_argument('token', None) _token = self.get_argument('token', None)
if _token is None: if _token is None:
param['mode'] = 1 # mode=1, show 'find-my-password' page. # 如果尚未设置SMTP或者系统限制不允许发送密码重置邮件
if len(get_cfg().sys.smtp.server) == 0:
param['mode'] = 2 # mode=2, show 'error' page
param['code'] = TPE_NETWORK
elif not get_cfg().sys.password.allow_reset:
param['mode'] = 2 # mode=2, show 'error' page
param['code'] = TPE_PRIVILEGE
else:
param['mode'] = 1 # mode=1, show 'find-my-password' page.
else: else:
err = user.check_reset_token(_token) err, _ = user.check_reset_token(_token)
param['code'] = err param['code'] = err
param['token'] = _token param['token'] = _token
@ -96,6 +104,7 @@ class ResetPasswordHandler(TPBaseHandler):
param['mode'] = 2 # mode=2, show 'error' page param['mode'] = 2 # mode=2, show 'error' page
else: else:
param['mode'] = 3 # mode=3, show 'set-new-password' page param['mode'] = 3 # mode=3, show 'set-new-password' page
param['force_strong'] = get_cfg().sys.password.force_strong
self.render('user/reset-password.mako', page_param=json.dumps(param)) self.render('user/reset-password.mako', page_param=json.dumps(param))
@ -457,9 +466,6 @@ class DoSetRoleForUsersHandler(TPBaseJsonHandler):
class DoResetPasswordHandler(TPBaseJsonHandler): class DoResetPasswordHandler(TPBaseJsonHandler):
@tornado.gen.coroutine @tornado.gen.coroutine
def post(self): def post(self):
ret = self.check_privilege(TP_PRIVILEGE_USER_CREATE)
if ret != TPE_OK:
return
args = self.get_argument('args', None) args = self.get_argument('args', None)
if args is None: if args is None:
@ -470,23 +476,80 @@ class DoResetPasswordHandler(TPBaseJsonHandler):
return self.write_json(TPE_JSON_FORMAT) return self.write_json(TPE_JSON_FORMAT)
try: try:
user_id = int(args['id'])
mode = int(args['mode']) mode = int(args['mode'])
password = args['password']
except: except:
return self.write_json(TPE_PARAM) return self.write_json(TPE_PARAM)
password = ''
if mode == 1:
# 管理员直接在后台给用户发送密码重置邮件
err = self.check_privilege(TP_PRIVILEGE_USER_CREATE)
if err != TPE_OK:
return self.write_json(err)
try:
user_id = int(args['id'])
except:
return self.write_json(TPE_PARAM)
elif mode == 2:
# 管理员直接在后台为用户重置密码
err = self.check_privilege(TP_PRIVILEGE_USER_CREATE)
if err != TPE_OK:
return self.write_json(err)
try:
user_id = int(args['id'])
password = args['password']
except:
return self.write_json(TPE_PARAM)
elif mode == 3:
# 用户自行找回密码,需要填写用户名、邮箱、验证码
try:
username = args['username']
email = args['email']
captcha = args['captcha']
except:
return self.write_json(TPE_PARAM)
code = self.get_session('captcha')
if code is None:
return self.write_json(TPE_CAPTCHA_EXPIRED, '验证码已失效')
if code.lower() != captcha.lower():
return self.write_json(TPE_CAPTCHA_MISMATCH, '验证码错误')
self.del_session('captcha')
err, user_info = user.get_by_username(username)
if err != TPE_OK:
return self.write_json(err)
if user_info.email != email:
return self.write_json(TPE_NOT_EXISTS)
user_id = user_info.id
elif mode == 4:
# 用户通过密码重置邮件中的链接有token验证在页面上设置新密码需要提供token、新密码
try:
token = args['token']
password = args['password']
except:
return self.write_json(TPE_PARAM)
err, user_id = user.check_reset_token(token)
if err != TPE_OK:
return self.write_json(err)
else:
return self.write_json(TPE_PARAM)
if user_id == 0: if user_id == 0:
return self.write_json(TPE_PARAM) return self.write_json(TPE_PARAM)
if mode == 1: if mode == 1 or mode == 3:
# if len(email) == 0:
# return self.write_json(TPE_PARAM)
err, email, token = user.generate_reset_password_token(self, user_id) err, email, token = user.generate_reset_password_token(self, user_id)
print(err, email, token)
# 生成一个密码重置链接24小时有效 # 生成一个密码重置链接24小时有效
# token = tp_generate_random(16) # token = tp_generate_random(16)
reset_url = '{}://{}/user/reset-password?token={}'.format(self.request.protocol, self.request.host, token) reset_url = '{}://{}/user/reset-password?token={}'.format(self.request.protocol, self.request.host, token)
@ -494,17 +557,19 @@ class DoResetPasswordHandler(TPBaseJsonHandler):
err, msg = yield mail.tp_send_mail( err, msg = yield mail.tp_send_mail(
email, email,
'您好!\n\n请访问以下链接以重设您的TELEPORT登录密码。此链接将于本邮件寄出24小时之后失效。访问此链接将会为您打开密码重置页面然后您可以设定新密码。\n\n' 'Teleport用户您好\n\n请访问以下链接以重设您的teleport登录密码。此链接将于本邮件寄出24小时之后失效。\n'
'如果您并没有做重设密码的操作,请及时联系系统管理员!\n\n' '访问此链接,将会为您打开密码重置页面,然后您可以设定新密码。\n\n'
'{reset_url}\n\n' '如果您并没有做重设密码的操作,请忽略本邮件,请及时联系您的系统管理员!\n\n'
'{reset_url}\n\n\n\n'
'[本邮件由teleport系统自动发出请勿回复]'
'\n\n' '\n\n'
''.format(reset_url=reset_url), ''.format(reset_url=reset_url),
subject='密码重置邮件' subject='密码重置确认函'
) )
return self.write_json(err, msg) return self.write_json(err, msg)
elif mode == 2: elif mode == 2 or mode == 4:
if len(password) == 0: if len(password) == 0:
return self.write_json(TPE_PARAM) return self.write_json(TPE_PARAM)

View File

@ -40,6 +40,9 @@ def get_by_username(username):
if len(s.recorder) == 0: if len(s.recorder) == 0:
return TPE_NOT_EXISTS, {} return TPE_NOT_EXISTS, {}
if s.recorder[0]['privilege'] is None:
s.recorder[0]['privilege'] = 0
return TPE_OK, s.recorder[0] return TPE_OK, s.recorder[0]
@ -300,13 +303,13 @@ def check_reset_token(token):
db.query(sql, (_time_now - 3 * 24 * 60 * 60,)) db.query(sql, (_time_now - 3 * 24 * 60 * 60,))
# 1. query user's id # 1. query user's id
sql = 'SELECT create_time FROM `{dbtp}user_rpt` WHERE token={dbph};'.format(dbtp=db.table_prefix, dbph=db.place_holder) sql = 'SELECT user_id, create_time FROM `{dbtp}user_rpt` WHERE token={dbph};'.format(dbtp=db.table_prefix, dbph=db.place_holder)
db_ret = db.query(sql, (token,)) db_ret = db.query(sql, (token,))
if db_ret is None or len(db_ret) == 0: if db_ret is None or len(db_ret) == 0:
return TPE_NOT_EXISTS return TPE_NOT_EXISTS, 0
# user_id = db_ret[0][0] user_id = db_ret[0][0]
create_time = db_ret[0][0] create_time = db_ret[0][1]
# err = s.select_from('user', ['email'], alt_name='u').where('u.id="{user_id}"'.format(user_id=user_id)).query() # err = s.select_from('user', ['email'], alt_name='u').where('u.id="{user_id}"'.format(user_id=user_id)).query()
# if err != TPE_OK: # if err != TPE_OK:
@ -316,9 +319,9 @@ def check_reset_token(token):
# email = s.recorder[0].email # email = s.recorder[0].email
if _time_now - create_time > 24 * 60 * 60: if _time_now - create_time > 24 * 60 * 60:
return TPE_EXPIRED return TPE_EXPIRED, user_id
else: else:
return TPE_OK return TPE_OK, user_id
def update_login_info(handler, user_id): def update_login_info(handler, user_id):