mirror of https://github.com/tp4a/teleport
绑定身份认证器功能完成,使用身份认证器动态密码登录功能完成。
parent
e73b7c5f6b
commit
980a684d85
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
|
@ -0,0 +1,399 @@
|
|||
"use strict";
|
||||
|
||||
$app.on_init = function (cb_stack) {
|
||||
$app.dom = {
|
||||
// title: $('#title'),
|
||||
// icon_bg: $('#icon-bg'),
|
||||
tp_time: $('#teleport-time')
|
||||
|
||||
, op_message: $('#area-auth [data-field="message"]')
|
||||
|
||||
, btn_show_oath_app: $('#btn-show-oath-app')
|
||||
, dlg_oath_app: $('#dlg-oath-app')
|
||||
|
||||
, qrcode: {
|
||||
area: $('#area-qrcode')
|
||||
, name: $('#area-qrcode [data-field="name"]')
|
||||
, image: $('#area-qrcode [data-field="qrcode"]')
|
||||
, desc: $('#area-qrcode [data-field="desc"]')
|
||||
}
|
||||
|
||||
, auth: {
|
||||
area: $('#area-auth')
|
||||
, input_username: $('#area-auth [data-field="input-username"]')
|
||||
, input_password: $('#area-auth [data-field="input-password"]')
|
||||
, btn_submit: $('#area-auth [data-field="btn-submit"]')
|
||||
, message: $('#area-auth [data-field="message"]')
|
||||
}
|
||||
|
||||
, bind: {
|
||||
dlg: $('#dlg-bind-oath')
|
||||
, qrcode_img: $('#dlg-bind-oath [data-field="oath-secret-qrcode"]')
|
||||
, tmp_secret: $('#dlg-bind-oath [data-field="tmp-oath-secret"]')
|
||||
, input_oath_code: $('#dlg-bind-oath [data-field="oath-code"]')
|
||||
, btn_submit: $('#dlg-bind-oath [data-field="btn-submit"]')
|
||||
, message: $('#dlg-bind-oath [data-field="message"]')
|
||||
}
|
||||
};
|
||||
|
||||
$app.tp_time = 0;
|
||||
$app.dom.op_message = $app.dom.auth.message;
|
||||
$app.dom.bind.dlg.on('shown.bs.modal', function () {
|
||||
$app.dom.op_message = $app.dom.bind.message;
|
||||
});
|
||||
$app.dom.bind.dlg.on('hidden.bs.modal', function () {
|
||||
$app.dom.op_message = $app.dom.auth.message;
|
||||
});
|
||||
|
||||
// $app.show_bind_dlg();
|
||||
|
||||
// $app.dom.icon_bg.addClass('fa fa-crosshairs').css('color', '#8140f1');
|
||||
$app.dom.btn_show_oath_app.click(function () {
|
||||
$app.dom.dlg_oath_app.modal();
|
||||
});
|
||||
|
||||
$('[data-switch]').click(function () {
|
||||
var n = $(this).attr('data-switch');
|
||||
console.log(n);
|
||||
|
||||
var name, img, desc;
|
||||
if (n === 'g-ios-appstore') {
|
||||
name = '<i class="fa fa-apple"></i> 谷歌身份验证器';
|
||||
img = 'img/qrcode/google-oath-appstore.png';
|
||||
desc = '适用于 iOS,从 Apple Store 安装';
|
||||
} else if (n === 'g-android-baidu') {
|
||||
name = '<i class="fa fa-android"></i> 谷歌身份验证器';
|
||||
img = 'img/qrcode/google-oath-baidu.png';
|
||||
desc = '适用于 Android,从百度手机助手安装';
|
||||
} else if (n === 'g-android-google') {
|
||||
name = '<i class="fa fa-android"></i> 谷歌身份验证器';
|
||||
img = 'img/qrcode/google-oath-googleplay.png';
|
||||
desc = '适用于 Android,从 Google Play 安装';
|
||||
} else if (n === 'mi-ios-appstore') {
|
||||
name = '<i class="fa fa-apple"></i> 小米安全令牌';
|
||||
img = 'img/qrcode/xiaomi-oath-appstore.png';
|
||||
desc = '适用于 iOS,从 Apple Store 安装';
|
||||
} else if (n === 'mi-android-mi') {
|
||||
name = '<i class="fa fa-android"></i> 小米安全令牌';
|
||||
img = 'img/qrcode/xiaomi-oath-xiaomi.png';
|
||||
desc = '适用于 Android,从小米应用商店安装';
|
||||
} else if (n === 'wechat') {
|
||||
name = '<i class="fa fa-wechat"></i> 微信 · 小程序';
|
||||
img = 'img/qrcode/wechat.png';
|
||||
desc = '适用于 iOS/Android,在微信小程序中搜索“二次验证码”即可';
|
||||
}
|
||||
|
||||
$app.dom.qrcode.name.html(name);
|
||||
$app.dom.qrcode.image.attr('src', '/static/' + img);
|
||||
$app.dom.qrcode.desc.html(desc);
|
||||
if (!$app.dom.qrcode.image.hasClass('selected'))
|
||||
$app.dom.qrcode.image.addClass('selected');
|
||||
});
|
||||
|
||||
$app.dom.auth.btn_submit.click(function () {
|
||||
$app.on_auth_user();
|
||||
});
|
||||
|
||||
$app.dom.auth.input_username.keydown(function (event) {
|
||||
if (event.which === 13) {
|
||||
$app.dom.auth.input_password.focus();
|
||||
} else {
|
||||
$app.hide_op_box();
|
||||
$('[data-toggle="popover"]').popover('hide');
|
||||
}
|
||||
});
|
||||
$app.dom.auth.input_password.keydown(function (event) {
|
||||
if (event.which === 13) {
|
||||
$app.on_auth_user();
|
||||
} else {
|
||||
$app.hide_op_box();
|
||||
$('[data-toggle="popover"]').popover('hide');
|
||||
}
|
||||
});
|
||||
|
||||
$app.dom.bind.input_oath_code.keydown(function (event) {
|
||||
if (event.which === 13) {
|
||||
$app.on_save();
|
||||
} else {
|
||||
$app.hide_op_box();
|
||||
$('[data-toggle="popover"]').popover('hide');
|
||||
}
|
||||
});
|
||||
|
||||
$app.dom.bind.btn_submit.click(function(){
|
||||
$app.on_save();
|
||||
});
|
||||
|
||||
// 获取服务器时间
|
||||
$app.sync_tp_time = function () {
|
||||
$tp.ajax_post_json('/system/get-time', {},
|
||||
function (ret) {
|
||||
if (ret.code === TPE_OK) {
|
||||
$app.tp_time = tp_utc2local(ret.data);
|
||||
$app.show_tp_time();
|
||||
}
|
||||
},
|
||||
function () {
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$app.sync_tp_time();
|
||||
|
||||
$app.show_tp_time = function () {
|
||||
if ($app.tp_time === 0)
|
||||
return;
|
||||
$app.dom.tp_time.text(tp_format_datetime($app.tp_time));
|
||||
$app.tp_time += 1;
|
||||
};
|
||||
|
||||
setInterval($app.show_tp_time, 1000);
|
||||
// 每五分钟同步一次服务器时间,避免长时间误差积累导致显示不正确
|
||||
setInterval($app.sync_tp_time, 1000 * 60 * 5);
|
||||
|
||||
cb_stack.exec();
|
||||
};
|
||||
|
||||
$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_auth_user = function () {
|
||||
$app.hide_op_box();
|
||||
var str_username = $app.dom.auth.input_username.val();
|
||||
var str_password = $app.dom.auth.input_password.val();
|
||||
|
||||
if (str_username.length === 0) {
|
||||
$app.show_op_box('error', '用户名未填写!');
|
||||
$app.dom.auth.input_username.attr('data-content', "请输入您的用户名!").focus().popover('show');
|
||||
return;
|
||||
}
|
||||
|
||||
if (str_password.length === 0) {
|
||||
$app.show_op_box('error', '密码未填写!');
|
||||
$app.dom.auth.input_password.attr('data-content', "请输入您的密码!").focus().popover('show');
|
||||
return;
|
||||
}
|
||||
|
||||
$app.dom.auth.btn_submit.attr('disabled', 'disabled');
|
||||
$tp.ajax_post_json('/user/verify-user', {username: str_username, password: str_password},
|
||||
function (ret) {
|
||||
$app.dom.auth.btn_submit.removeAttr('disabled');
|
||||
if (ret.code === TPE_OK) {
|
||||
// 验证成功
|
||||
$app.hide_op_box();
|
||||
|
||||
// 显示绑定对话框
|
||||
$app.show_bind_dlg();
|
||||
}
|
||||
else {
|
||||
$app.hide_op_box();
|
||||
$app.show_op_box('error', tp_error_msg(ret.code, ret.message));
|
||||
}
|
||||
},
|
||||
function () {
|
||||
$app.hide_op_box();
|
||||
$app.show_op_box('error', '很抱歉,无法连接服务器!请稍后再试一次!');
|
||||
$app.dom.auth.btn_submit.removeAttr('disabled');
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$app.show_bind_dlg = function () {
|
||||
var str_username = $app.dom.auth.input_username.val();
|
||||
$tp.ajax_post_json('/user/gen-oath-secret', {},
|
||||
function (ret) {
|
||||
if (ret.code === TPE_OK) {
|
||||
$app.dom.bind.qrcode_img.attr('src', '/user/oath-secret-qrcode?u=' + str_username + '&rnd=' + Math.random());
|
||||
$app.dom.bind.tmp_secret.text(ret.data.tmp_oath_secret);
|
||||
$app.dom.bind.dlg.modal({backdrop: 'static'});
|
||||
} else {
|
||||
$tp.notify_error('无法绑定身份验证器:' + tp_error_msg(ret.code, ret.message));
|
||||
}
|
||||
},
|
||||
function () {
|
||||
$tp.notify_error('网路故障,无法连接到服务器!');
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$app.on_save = function () {
|
||||
var str_username = $app.dom.auth.input_username.val();
|
||||
var str_password = $app.dom.auth.input_password.val();
|
||||
var oath_code = $app.dom.bind.input_oath_code.val();
|
||||
|
||||
if (oath_code.length === 0) {
|
||||
$app.show_op_box('error', '动态验证码未填写!');
|
||||
$app.dom.bind.input_oath_code.attr('data-content', "请输入动态验证码!").focus().popover('show');
|
||||
return;
|
||||
}
|
||||
if (oath_code.length !== 6) {
|
||||
$app.show_op_box('error', '动态验证码错误!');
|
||||
$app.dom.bind.input_oath_code.attr('data-content', "动态验证码为 6 位数字!").focus().popover('show');
|
||||
return;
|
||||
}
|
||||
|
||||
$tp.ajax_post_json('/user/do-bind-oath', {username: str_username, password: str_password, oath_code: oath_code},
|
||||
function (ret) {
|
||||
$app.dom.auth.btn_submit.removeAttr('disabled');
|
||||
if (ret.code === TPE_OK) {
|
||||
// 验证成功
|
||||
$app.hide_op_box();
|
||||
$app.show_op_box('success', '绑定成功,正在转到登录界面!');
|
||||
setTimeout(function(){
|
||||
window.location.href = '/';
|
||||
}, 3000);
|
||||
}
|
||||
else {
|
||||
$app.hide_op_box();
|
||||
$app.show_op_box('error', tp_error_msg(ret.code, ret.message));
|
||||
}
|
||||
},
|
||||
function () {
|
||||
$app.hide_op_box();
|
||||
$app.show_op_box('error', '很抱歉,无法连接服务器!请稍后再试一次!');
|
||||
$app.dom.auth.btn_submit.removeAttr('disabled');
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// $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');
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (str_email.length === 0) {
|
||||
// $app.show_op_box('error', '电子邮件地址未填写!');
|
||||
// $app.dom.find.input_email.attr('data-content', "请填写您的电子邮件地址!").focus().popover('show');
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (!tp_check_email(str_email)) {
|
||||
// $app.show_op_box('error', '无效的电子邮件地址!');
|
||||
// $app.dom.find.input_email.attr('data-content', "请检查输入的电子邮件地址!").focus().popover('show');
|
||||
// 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) {
|
||||
// if (ret.code === TPE_OK) {
|
||||
// $app.dom.find.btn_submit.slideUp('fast');
|
||||
// $app.show_op_box('success', '密码重置确认函已发送,请注意查收!');
|
||||
// } else {
|
||||
// $app.dom.find.btn_submit.removeAttr('disabled');
|
||||
// $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', tp_error_msg(TPE_FAILED, '抱歉,不能使用弱密码!'));
|
||||
// $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', '网络故障,密码重置失败!');
|
||||
// }
|
||||
// );
|
||||
// };
|
||||
|
||||
|
|
@ -114,14 +114,14 @@ $app.on_init = function (cb_stack, cb_args) {
|
|||
});
|
||||
|
||||
$app.dom.btn_reset_oath_code.click(function () {
|
||||
$tp.ajax_post_json('/auth/oath-secret-reset', {},
|
||||
$tp.ajax_post_json('/user/gen-oath-secret', {},
|
||||
function (ret) {
|
||||
if (ret.code === TPE_OK) {
|
||||
$app.dom.oath_secret_image.attr('src', '/auth/oath-secret-qrcode?' + Math.random());
|
||||
$app.dom.oath_secret_image.attr('src', '/user/oath-secret-qrcode?' + Math.random());
|
||||
$app.dom.tmp_oath_secret.text(ret.data.tmp_oath_secret);
|
||||
$app.dom.dlg_reset_oath_code.modal({backdrop: 'static'});
|
||||
} else {
|
||||
$tp.notify_error('发生内部错误!' + tp_error_msg(ret.code, ret.message));
|
||||
$tp.notify_error('无法绑定身份验证器:' + tp_error_msg(ret.code, ret.message));
|
||||
}
|
||||
},
|
||||
function () {
|
||||
|
|
|
@ -46,6 +46,7 @@ $app.on_init = function (cb_stack) {
|
|||
if (event.which === 13) {
|
||||
$app.dom.find.input_email.focus();
|
||||
} else {
|
||||
$app.hide_op_box();
|
||||
$('[data-toggle="popover"]').popover('hide');
|
||||
}
|
||||
});
|
||||
|
@ -53,6 +54,7 @@ $app.on_init = function (cb_stack) {
|
|||
if (event.which === 13) {
|
||||
$app.dom.find.input_captcha.focus();
|
||||
} else {
|
||||
$app.hide_op_box();
|
||||
$('[data-toggle="popover"]').popover('hide');
|
||||
}
|
||||
});
|
||||
|
@ -60,6 +62,7 @@ $app.on_init = function (cb_stack) {
|
|||
if (event.which === 13) {
|
||||
$app.on_send_find_password_email();
|
||||
} else {
|
||||
$app.hide_op_box();
|
||||
$('[data-toggle="popover"]').popover('hide');
|
||||
}
|
||||
});
|
||||
|
@ -145,16 +148,20 @@ $app.on_send_find_password_email = function () {
|
|||
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();
|
||||
$app.show_op_box('error', '用户名未填写!');
|
||||
$app.dom.find.input_username.attr('data-content', "请填写您的用户名!").focus().popover('show');
|
||||
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();
|
||||
$app.show_op_box('error', '电子邮件地址未填写!');
|
||||
$app.dom.find.input_email.attr('data-content', "请填写您的电子邮件地址!").focus().popover('show');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tp_check_email(str_email)) {
|
||||
$app.show_op_box('error', '无效的电子邮件地址!');
|
||||
$app.dom.find.input_email.attr('data-content', "请检查输入的电子邮件地址!").focus().popover('show');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -205,7 +212,7 @@ $app.do_send_reset_email = function (str_username, str_email, str_captcha) {
|
|||
$app.hide_op_box();
|
||||
var msg = '';
|
||||
if (ret.code === TPE_NOT_EXISTS)
|
||||
msg = tp_error_msg(ret.code, '没有此用户');
|
||||
msg = tp_error_msg(ret.code, '用户不存在,请检查输入的用户和电子邮件地址是否匹配!');
|
||||
else
|
||||
msg = tp_error_msg(ret.code, ret.message);
|
||||
$app.show_op_box('error', msg);
|
||||
|
|
|
@ -914,8 +914,6 @@ $app.create_dlg_reset_password = function () {
|
|||
var dlg = {};
|
||||
dlg.dom_id = 'dlg-reset-password';
|
||||
dlg.field_id = -1;
|
||||
// dlg.field_username = '';
|
||||
// dlg.field_surname = '';
|
||||
dlg.field_email = '';
|
||||
dlg.field_password = '';
|
||||
|
||||
|
@ -1025,7 +1023,6 @@ $app.create_dlg_reset_password = function () {
|
|||
function (ret) {
|
||||
if (ret.code === TPE_OK) {
|
||||
$tp.notify_success('用户密码重置成功!');
|
||||
// $app.table_users.load_data();
|
||||
dlg.dom.dialog.modal('hide');
|
||||
} else {
|
||||
$tp.notify_error('用户密码重置失败:' + tp_error_msg(ret.code, ret.message));
|
||||
|
|
|
@ -77,14 +77,29 @@ label {
|
|||
font-family: @font-family-mono;
|
||||
}
|
||||
|
||||
.important {
|
||||
//font-weight:bold;
|
||||
color: #d45f1c;
|
||||
//font-style: italic;
|
||||
}
|
||||
|
||||
hr.hr-sm {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
//==============================================
|
||||
// 重载bootstrap的样式
|
||||
//==============================================
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
ul.list {
|
||||
//list-style: none;
|
||||
margin: 0;
|
||||
padding: 3px 0 5px 0;
|
||||
li {
|
||||
margin: 3px 3px 5px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-single-line {
|
||||
white-space: nowrap;
|
||||
|
|
|
@ -145,6 +145,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.modal-dialog .modal-content .modal-header {
|
||||
//padding: 10px;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
//.alert-sm {
|
||||
// padding: 5px;
|
||||
// margin-bottom: 10px;
|
||||
|
|
|
@ -202,7 +202,7 @@ body {
|
|||
position: absolute;
|
||||
min-height: @error-icon-size+30px;
|
||||
overflow: hidden;
|
||||
.fa {
|
||||
& > .fa {
|
||||
margin-top: 30px;
|
||||
font-size: @error-icon-size;
|
||||
//color: rgb(255, 101, 0);
|
||||
|
@ -227,7 +227,7 @@ body {
|
|||
hr {
|
||||
//border-top: 1px solid #d3d3d3;
|
||||
//border-bottom: 1px solid #fff;
|
||||
margin: 10px 0 20px;
|
||||
//margin: 10px 0 20px;
|
||||
border-top: none;
|
||||
border-bottom: 1px dashed #d3d3d3;
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
<span class="input-group-addon"><i class="fa fa-clock-o fa-fw"></i></span>
|
||||
<input id="oath-code" type="text" class="form-control" placeholder="6位数字身份验证器动态验证码" data-toggle="popover" data-trigger="manual" data-placement="top">
|
||||
</div>
|
||||
<p class="input-addon-desc"><a href="/user/bind-oath">尚未绑定身份验证器?</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
<%!
|
||||
page_title_ = '绑定身份验证器'
|
||||
%>
|
||||
<%inherit file="../page_single_base.mako"/>
|
||||
|
||||
<%block name="extend_js_file">
|
||||
<script type="text/javascript" src="${ static_url('js/user/bind-oath.js') }"></script>
|
||||
</%block>
|
||||
|
||||
<%block name="embed_css">
|
||||
<style type="text/css">
|
||||
.step-name {
|
||||
font-size: 18px;
|
||||
## font-weight:bold;
|
||||
color: #e55a1d;
|
||||
}
|
||||
|
||||
.input-addon-desc {
|
||||
text-align: right;
|
||||
font-size: 90%;
|
||||
color: #707070;
|
||||
}
|
||||
|
||||
.time-box {
|
||||
display: inline-block;
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
border: 1px solid #bcffbd;
|
||||
background-color: #d0ffcd;
|
||||
border-radius: 5px;
|
||||
color: #646464;
|
||||
}
|
||||
|
||||
.tp-time {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
color: #063c06;
|
||||
}
|
||||
|
||||
#area-qrcode {
|
||||
margin: 10px auto 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#area-qrcode .qrcode-name {
|
||||
font-size: 18px;
|
||||
color: #5483ff;
|
||||
}
|
||||
|
||||
img.qrcode-img {
|
||||
margin: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
img.qrcode-img.selected {
|
||||
border: 1px solid #bdbdbd;
|
||||
}
|
||||
|
||||
.oath-code {
|
||||
font-family: 'Courier New', Consolas, Lucida Console, Monaco, Courier, monospace;
|
||||
font-size: 26px;
|
||||
line-height: 26px;
|
||||
font-weight: bold;
|
||||
color: #559f47;
|
||||
}
|
||||
|
||||
</style>
|
||||
</%block>
|
||||
|
||||
<%block name="page_header">
|
||||
<div class="container-fluid top-navbar">
|
||||
<div class="brand"><a href="/"><span class="site-logo"></span></a></div>
|
||||
<div class="breadcrumb-container">
|
||||
<ol class="breadcrumb">
|
||||
<li><i class="fa fa-key"></i> 绑定身份验证器</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</%block>
|
||||
|
||||
<div class="page-content">
|
||||
<div class="info-box">
|
||||
<div class="info-icon-box">
|
||||
<i class="fa fa-shield" style="color:#8140f1;"></i>
|
||||
</div>
|
||||
<div class="info-message-box">
|
||||
<div class="title">绑定身份验证器</div>
|
||||
<hr/>
|
||||
<div id="content" class="content">
|
||||
<p class="step-name">第一步:安装身份验证器</p>
|
||||
<p>请在你的手机上安装身份验证器App。<a href="javascript:;" id="btn-show-oath-app">点击此处获取安装方式</a></p>
|
||||
|
||||
<hr/>
|
||||
<p class="step-name">第二步:检查服务器时间</p>
|
||||
<p>请注意检查您的手机时间与teleport服务器时间是否同步,如果两者<span class="important">时间偏差超过两分钟则无法绑定</span>,请及时通知系统管理员处理!</p>
|
||||
<div class="time-box"><i class="fa fa-clock-o"></i> TELEPORT服务器时间:<span class="tp-time mono" id="teleport-time">-</span></div>
|
||||
|
||||
<hr/>
|
||||
<p class="step-name">第三步:认证并绑定</p>
|
||||
<div class="row" style="padding:0 20px;">
|
||||
<div id="area-auth">
|
||||
<div class="col-md-5">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-user-circle-o fa-fw"></i></span>
|
||||
<input data-field="input-username" type="text" class="form-control mono" placeholder="teleport系统用户名" data-toggle="popover" data-trigger="manual" data-placement="top">
|
||||
</div>
|
||||
<div class="input-group" style="margin-top:10px;">
|
||||
<span class="input-group-addon"><i class="fa fa-key fa-fw"></i></span>
|
||||
<input data-field="input-password" type="password" class="form-control mono" placeholder="请输入密码" data-toggle="popover" data-trigger="manual" data-placement="top">
|
||||
</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" class="alert alert-danger" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%block name="extend_content">
|
||||
<div class="modal fade" id="dlg-oath-app" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title"><i class="fa fa-shield"></i> 身份验证器</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<p>选择您喜欢的身份验证器进行安装:</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<ul class="list">
|
||||
<li>谷歌身份验证器
|
||||
<ul class="list">
|
||||
<li><a href="javascript:;" data-switch="g-ios-appstore"><i class="fa fa-apple fa-fw"></i> iOS(Apple Store)</a></li>
|
||||
<li><a href="javascript:;" data-switch="g-android-baidu"><i class="fa fa-android fa-fw"></i> Android(百度手机助手)</a></li>
|
||||
<li><a href="javascript:;" data-switch="g-android-google"><i class="fa fa-android fa-fw"></i> Android(Google Play)</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>小米安全令牌
|
||||
<ul class="list">
|
||||
<li><a href="javascript:;" data-switch="mi-ios-appstore"><i class="fa fa-apple fa-fw"></i> iOS(Apple Store)</a></li>
|
||||
<li><a href="javascript:;" data-switch="mi-android-mi"><i class="fa fa-android fa-fw"></i> Android(小米应用商店)</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>微信小程序
|
||||
<ul class="list">
|
||||
<li><a href="javascript:;" data-switch="wechat"><i class="fa fa-wechat fa-fw"></i> 二次验证码</a> <span class="label label-success">推荐</span></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div id="area-qrcode">
|
||||
<p data-field="name" class="qrcode-name"></p>
|
||||
<img class="qrcode-img" data-field="qrcode" src="${ static_url('img/qrcode/select-oath-app.png') }">
|
||||
<p data-field="desc"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-sm btn-default" data-dismiss="modal"><i class="fa fa-close fa-fw"></i> 关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="dlg-bind-oath" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">绑定身份验证器</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<p>请在手机上打开身份验证器,点击增加账号按钮,然后选择“扫描条形码”并扫描下面的二维码来完成账号绑定。</p>
|
||||
<p style="text-align: center;"><img data-field="oath-secret-qrcode" src="" style="border:1px solid #b7b7b7;"></p>
|
||||
<p>如果无法扫描二维码,则可以选择“手动输入验证码”,设置一个容易记忆的账号名称,并确保“基于时间”一项是选中的,然后在“密钥”一项中输入下列密钥:</p>
|
||||
<div style="text-align:center;" class="oath-code"><span data-field="tmp-oath-secret"></span></div>
|
||||
|
||||
<hr/>
|
||||
<p>然后请在下面的动态验证码输入框中输入身份验证器提供的6位数字:</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-4" style="text-align:right;">
|
||||
<span style="line-height:34px;">动态验证码:</span>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" data-field="oath-code" data-toggle="popover" data-trigger="manual" data-placement="top">
|
||||
</div>
|
||||
</div>
|
||||
<div data-field="message" class="alert alert-danger" style="display:none;margin-top:10px;">aabb</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-sm btn-primary" data-field="btn-submit"><i class="fa fa-check fa-fw"></i> 验证并完成绑定</button>
|
||||
<button type="button" class="btn btn-sm btn-default" data-dismiss="modal"><i class="fa fa-close fa-fw"></i> 取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</%block>
|
||||
|
||||
<%block name="embed_js">
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
## $app.add_options(${page_param});
|
||||
## console.log($app.options);
|
||||
</script>
|
||||
</%block>
|
|
@ -31,7 +31,7 @@
|
|||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="#info" data-toggle="tab">个人信息</a></li>
|
||||
<li><a href="#password" data-toggle="tab">修改密码</a></li>
|
||||
<li><a href="#oath" data-toggle="tab">身份认证器</a></li>
|
||||
<li><a href="#oath" data-toggle="tab">身份验证器</a></li>
|
||||
</ul>
|
||||
|
||||
<!-- Tab panes -->
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
<%block name="page_header">
|
||||
<div class="container-fluid top-navbar">
|
||||
<div class="brand"><a href="/" target="_blank"><span class="site-logo"></span></a></div>
|
||||
<div class="brand"><a href="/"><span class="site-logo"></span></a></div>
|
||||
<div class="breadcrumb-container">
|
||||
<ol class="breadcrumb">
|
||||
<li><i class="fa fa-key"></i> 密码管理</li>
|
||||
|
|
|
@ -83,10 +83,20 @@ controllers = [
|
|||
(r'/user/update-users', user.DoUpdateUsersHandler),
|
||||
# - [json] 获取用户列表
|
||||
(r'/user/get-users', user.DoGetUsersHandler),
|
||||
# - 用户重设密码页面 /auth/reset-password
|
||||
(r'/user/reset-password', user.ResetPasswordHandler),
|
||||
# - [json] 重置密码
|
||||
(r'/user/do-reset-password', user.DoResetPasswordHandler),
|
||||
# - 用户重设密码页面 /auth/reset-password?token=D3672DFF256B6B6F37AF8A922D7D83B4
|
||||
(r'/user/reset-password', user.ResetPasswordHandler),
|
||||
# - 用户绑定OATH
|
||||
(r'/user/bind-oath', user.BindOathHandler),
|
||||
# - [json] 用户绑定OATH
|
||||
(r'/user/gen-oath-secret', user.DoGenerateOathSecretHandler),
|
||||
# - 显示OATH密钥二维码
|
||||
(r'/user/oath-secret-qrcode', user.OathSecretQrCodeHandler),
|
||||
# - [json] 获取用户信息
|
||||
(r'/user/verify-user', user.DoVerifyUserHandler),
|
||||
# - [json] 绑定身份认证器
|
||||
(r'/user/do-bind-oath', user.DoBindOathHandler),
|
||||
|
||||
# - 用户组管理页面
|
||||
(r'/user/group', user.GroupListHandler),
|
||||
|
|
|
@ -2,16 +2,15 @@
|
|||
|
||||
import json
|
||||
|
||||
from app.const import *
|
||||
from app.base.configs import get_cfg
|
||||
from app.base.controller import TPBaseHandler, TPBaseJsonHandler
|
||||
from app.base.logger import log
|
||||
from app.logic.auth.oath import tp_oath_generate_secret, tp_oath_generate_qrcode, tp_oath_verify_code
|
||||
from app.logic.auth.captcha import tp_captcha_generate_image
|
||||
from app.model import user
|
||||
from app.model import syslog
|
||||
from app.logic.auth.password import tp_password_verify
|
||||
from app.base.utils import tp_timestamp_utc_now
|
||||
from app.const import *
|
||||
from app.logic.auth.captcha import tp_captcha_generate_image
|
||||
from app.logic.auth.oath import tp_oath_verify_code
|
||||
from app.logic.auth.password import tp_password_verify
|
||||
from app.model import syslog
|
||||
from app.model import user
|
||||
|
||||
|
||||
class LoginHandler(TPBaseHandler):
|
||||
|
@ -98,58 +97,75 @@ class DoLoginHandler(TPBaseJsonHandler):
|
|||
if len(username) == 0:
|
||||
return self.write_json(TPE_PARAM, '未提供登录用户名')
|
||||
|
||||
err, user_info = user.get_by_username(username)
|
||||
if login_type not in [TP_LOGIN_AUTH_USERNAME_PASSWORD,
|
||||
TP_LOGIN_AUTH_USERNAME_PASSWORD_CAPTCHA,
|
||||
TP_LOGIN_AUTH_USERNAME_PASSWORD_OATH
|
||||
]:
|
||||
password = None
|
||||
if login_type not in [TP_LOGIN_AUTH_USERNAME_PASSWORD_OATH,
|
||||
TP_LOGIN_AUTH_USERNAME_OATH
|
||||
]:
|
||||
oath = None
|
||||
|
||||
err, user_info = user.login(self, username, password=password, oath_code=oath)
|
||||
if err != TPE_OK:
|
||||
if err == TPE_NOT_EXISTS:
|
||||
err = TPE_USER_AUTH
|
||||
syslog.sys_log({'username': username, 'surname': username}, self.request.remote_ip, TPE_NOT_EXISTS, '登录失败,用户`{}`不存在'.format(username))
|
||||
return self.write_json(err)
|
||||
|
||||
if user_info.privilege == 0:
|
||||
# 尚未为此用户设置角色
|
||||
return self.write_json(TPE_PRIVILEGE, '用户尚未分配角色')
|
||||
|
||||
if user_info['state'] == TP_STATE_LOCKED:
|
||||
# 用户已经被锁定,如果系统配置为一定时间后自动解锁,则更新一下用户信息
|
||||
if sys_cfg.login.lock_timeout != 0:
|
||||
if tp_timestamp_utc_now() - user_info.lock_time > sys_cfg.login.lock_timeout * 60:
|
||||
user_info.fail_count = 0
|
||||
user_info.state = TP_STATE_NORMAL
|
||||
if user_info['state'] == TP_STATE_LOCKED:
|
||||
syslog.sys_log(user_info, self.request.remote_ip, TPE_USER_LOCKED, '登录失败,用户已被锁定')
|
||||
return self.write_json(TPE_USER_LOCKED)
|
||||
elif user_info['state'] == TP_STATE_DISABLED:
|
||||
syslog.sys_log(user_info, self.request.remote_ip, TPE_USER_DISABLED, '登录失败,用户已被禁用')
|
||||
return self.write_json(TPE_USER_DISABLED)
|
||||
elif user_info['state'] != TP_STATE_NORMAL:
|
||||
syslog.sys_log(user_info, self.request.remote_ip, TPE_FAILED, '登录失败,系统内部错误')
|
||||
return self.write_json(TPE_FAILED)
|
||||
|
||||
err_msg = ''
|
||||
if login_type in [TP_LOGIN_AUTH_USERNAME_PASSWORD, TP_LOGIN_AUTH_USERNAME_PASSWORD_CAPTCHA, TP_LOGIN_AUTH_USERNAME_PASSWORD_OATH]:
|
||||
# 如果系统配置了密码有效期,则检查用户的密码是否失效
|
||||
if sys_cfg.password.timeout != 0:
|
||||
pass
|
||||
|
||||
if not tp_password_verify(password, user_info['password']):
|
||||
err, is_locked = user.update_fail_count(self, user_info)
|
||||
if is_locked:
|
||||
err_msg = '用户被临时锁定!'
|
||||
syslog.sys_log(user_info, self.request.remote_ip, TPE_USER_AUTH, '登录失败,密码错误!{}'.format(err_msg))
|
||||
return self.write_json(TPE_USER_AUTH)
|
||||
|
||||
if login_type in [TP_LOGIN_AUTH_USERNAME_OATH, TP_LOGIN_AUTH_USERNAME_PASSWORD_OATH]:
|
||||
# use oath
|
||||
if not tp_oath_verify_code(user_info['oath_secret'], oath):
|
||||
err, is_locked = user.update_fail_count(self, user_info)
|
||||
if is_locked:
|
||||
err_msg = '用户被临时锁定!'
|
||||
syslog.sys_log(user_info, self.request.remote_ip, TPE_OATH_MISMATCH, "登录失败,身份验证器动态验证码错误!{}".format(err_msg))
|
||||
return self.write_json(TPE_OATH_MISMATCH)
|
||||
# err, user_info = user.get_by_username(username)
|
||||
# if err != TPE_OK:
|
||||
# if err == TPE_NOT_EXISTS:
|
||||
# syslog.sys_log({'username': username, 'surname': username}, self.request.remote_ip, TPE_NOT_EXISTS, '登录失败,用户`{}`不存在'.format(username))
|
||||
# return self.write_json(err)
|
||||
#
|
||||
# if user_info.privilege == 0:
|
||||
# # 尚未为此用户设置角色
|
||||
# return self.write_json(TPE_PRIVILEGE, '用户尚未分配角色')
|
||||
#
|
||||
# if user_info['state'] == TP_STATE_LOCKED:
|
||||
# # 用户已经被锁定,如果系统配置为一定时间后自动解锁,则更新一下用户信息
|
||||
# if sys_cfg.login.lock_timeout != 0:
|
||||
# if tp_timestamp_utc_now() - user_info.lock_time > sys_cfg.login.lock_timeout * 60:
|
||||
# user_info.fail_count = 0
|
||||
# user_info.state = TP_STATE_NORMAL
|
||||
# if user_info['state'] == TP_STATE_LOCKED:
|
||||
# syslog.sys_log(user_info, self.request.remote_ip, TPE_USER_LOCKED, '登录失败,用户已被锁定')
|
||||
# return self.write_json(TPE_USER_LOCKED)
|
||||
# elif user_info['state'] == TP_STATE_DISABLED:
|
||||
# syslog.sys_log(user_info, self.request.remote_ip, TPE_USER_DISABLED, '登录失败,用户已被禁用')
|
||||
# return self.write_json(TPE_USER_DISABLED)
|
||||
# elif user_info['state'] != TP_STATE_NORMAL:
|
||||
# syslog.sys_log(user_info, self.request.remote_ip, TPE_FAILED, '登录失败,系统内部错误')
|
||||
# return self.write_json(TPE_FAILED)
|
||||
#
|
||||
# err_msg = ''
|
||||
# if login_type in [TP_LOGIN_AUTH_USERNAME_PASSWORD, TP_LOGIN_AUTH_USERNAME_PASSWORD_CAPTCHA, TP_LOGIN_AUTH_USERNAME_PASSWORD_OATH]:
|
||||
# # 如果系统配置了密码有效期,则检查用户的密码是否失效
|
||||
# if sys_cfg.password.timeout != 0:
|
||||
# pass
|
||||
#
|
||||
# if not tp_password_verify(password, user_info['password']):
|
||||
# err, is_locked = user.update_fail_count(self, user_info)
|
||||
# if is_locked:
|
||||
# err_msg = '用户被临时锁定!'
|
||||
# syslog.sys_log(user_info, self.request.remote_ip, TPE_USER_AUTH, '登录失败,密码错误!{}'.format(err_msg))
|
||||
# return self.write_json(TPE_USER_AUTH)
|
||||
#
|
||||
# if login_type in [TP_LOGIN_AUTH_USERNAME_OATH, TP_LOGIN_AUTH_USERNAME_PASSWORD_OATH]:
|
||||
# # use oath
|
||||
# if not tp_oath_verify_code(user_info['oath_secret'], oath):
|
||||
# err, is_locked = user.update_fail_count(self, user_info)
|
||||
# if is_locked:
|
||||
# err_msg = '用户被临时锁定!'
|
||||
# syslog.sys_log(user_info, self.request.remote_ip, TPE_OATH_MISMATCH, "登录失败,身份验证器动态验证码错误!{}".format(err_msg))
|
||||
# return self.write_json(TPE_OATH_MISMATCH)
|
||||
|
||||
self._user = user_info
|
||||
self._user['_is_login'] = True
|
||||
del self._user['password']
|
||||
del self._user['oath_secret']
|
||||
# del self._user['password']
|
||||
# del self._user['oath_secret']
|
||||
|
||||
if remember:
|
||||
self.set_session('user', self._user, 12 * 60 * 60)
|
||||
|
@ -185,7 +201,7 @@ class CaptchaHandler(TPBaseHandler):
|
|||
def get(self):
|
||||
h = int(self.get_argument('h', 36))
|
||||
code, img_data = tp_captcha_generate_image(h)
|
||||
self.set_session('captcha', code)
|
||||
self.set_session('captcha', code, expire=10 * 60) # 验证码有效期为10分钟
|
||||
self.set_header('Content-Type', 'image/jpeg')
|
||||
self.write(img_data)
|
||||
|
||||
|
@ -214,7 +230,6 @@ class VerifyCaptchaHandler(TPBaseJsonHandler):
|
|||
|
||||
return self.write_json(TPE_OK)
|
||||
|
||||
|
||||
# class ModifyPwd(TPBaseUserAuthJsonHandler):
|
||||
# def post(self):
|
||||
# args = self.get_argument('args', None)
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
from app.const import *
|
||||
from app.base.configs import get_cfg
|
||||
from app.base.session import session_manager
|
||||
from app.base import mail
|
||||
from app.model import user
|
||||
from app.model import group
|
||||
from app.logic.auth.password import tp_password_generate_secret
|
||||
from app.base.utils import tp_check_strong_password
|
||||
import tornado.gen
|
||||
from app.base.logger import *
|
||||
from app.base import mail
|
||||
from app.base.configs import get_cfg
|
||||
from app.base.controller import TPBaseHandler, TPBaseJsonHandler
|
||||
from app.base.logger import *
|
||||
from app.base.session import session_manager
|
||||
from app.base.utils import tp_check_strong_password
|
||||
from app.base.utils import tp_timestamp_utc_now
|
||||
from app.logic.auth.oath import tp_oath_verify_code
|
||||
from app.const import *
|
||||
from app.logic.auth.oath import tp_oath_generate_secret, tp_oath_generate_qrcode
|
||||
from app.logic.auth.password import tp_password_generate_secret, tp_password_verify
|
||||
from app.model import group
|
||||
from app.model import syslog
|
||||
from app.model import user
|
||||
|
||||
|
||||
class UserListHandler(TPBaseHandler):
|
||||
|
@ -110,6 +114,98 @@ class ResetPasswordHandler(TPBaseHandler):
|
|||
self.render('user/reset-password.mako', page_param=json.dumps(param))
|
||||
|
||||
|
||||
class BindOathHandler(TPBaseHandler):
|
||||
def get(self):
|
||||
self.render('user/bind-oath.mako')
|
||||
|
||||
|
||||
class DoGenerateOathSecretHandler(TPBaseJsonHandler):
|
||||
def post(self):
|
||||
oath_secret = tp_oath_generate_secret()
|
||||
self.set_session('tmp_oath_secret', oath_secret)
|
||||
return self.write_json(TPE_OK, data={"tmp_oath_secret": oath_secret})
|
||||
|
||||
|
||||
class DoVerifyUserHandler(TPBaseJsonHandler):
|
||||
def post(self):
|
||||
args = self.get_argument('args', None)
|
||||
if args is None:
|
||||
return self.write_json(TPE_PARAM)
|
||||
try:
|
||||
args = json.loads(args)
|
||||
except:
|
||||
return self.write_json(TPE_JSON_FORMAT)
|
||||
|
||||
try:
|
||||
username = args['username']
|
||||
password = args['password']
|
||||
except:
|
||||
return self.write_json(TPE_PARAM)
|
||||
|
||||
err, user_info = user.login(self, username, password=password)
|
||||
if err != TPE_OK:
|
||||
if err == TPE_NOT_EXISTS:
|
||||
err = TPE_USER_AUTH
|
||||
return self.write_json(err)
|
||||
|
||||
return self.write_json(TPE_OK)
|
||||
|
||||
|
||||
class DoBindOathHandler(TPBaseJsonHandler):
|
||||
def post(self):
|
||||
args = self.get_argument('args', None)
|
||||
if args is None:
|
||||
return self.write_json(TPE_PARAM)
|
||||
try:
|
||||
args = json.loads(args)
|
||||
except:
|
||||
return self.write_json(TPE_JSON_FORMAT)
|
||||
|
||||
try:
|
||||
username = args['username']
|
||||
password = args['password']
|
||||
oath_code = args['oath_code']
|
||||
except:
|
||||
return self.write_json(TPE_PARAM)
|
||||
|
||||
err, user_info = user.login(self, username, password=password)
|
||||
if err != TPE_OK:
|
||||
if err == TPE_NOT_EXISTS:
|
||||
err = TPE_USER_AUTH
|
||||
return self.write_json(err)
|
||||
|
||||
secret = self.get_session('tmp_oath_secret', None)
|
||||
if secret is None:
|
||||
return self.write_json(TPE_FAILED, '内部错误!')
|
||||
self.del_session('tmp_oath_secret')
|
||||
|
||||
if not tp_oath_verify_code(secret, oath_code):
|
||||
return self.write_json(TPE_OATH_MISMATCH)
|
||||
|
||||
err = user.update_oath_secret(user_info['id'], secret)
|
||||
if err != TPE_OK:
|
||||
return self.write_json(err)
|
||||
|
||||
return self.write_json(TPE_OK)
|
||||
|
||||
|
||||
class OathSecretQrCodeHandler(TPBaseHandler):
|
||||
def get(self):
|
||||
username = self.get_argument('u', None)
|
||||
if username is None:
|
||||
user_info = self.get_current_user()
|
||||
username = user_info['username']
|
||||
|
||||
username = username + '@teleport'
|
||||
|
||||
secret = self.get_session('tmp_oath_secret', None)
|
||||
|
||||
img_data = tp_oath_generate_qrcode(username, secret)
|
||||
|
||||
self.set_header('Content-Type', 'image/jpeg')
|
||||
self.write(img_data)
|
||||
|
||||
|
||||
class DoGetUserInfoHandler(TPBaseJsonHandler):
|
||||
def post(self, user_id):
|
||||
ret = self.check_privilege(TP_PRIVILEGE_USER_CREATE | TP_PRIVILEGE_USER_DELETE | TP_PRIVILEGE_USER_LOCK | TP_PRIVILEGE_USER_GROUP)
|
||||
|
|
|
@ -19,43 +19,6 @@ from wheezy.captcha.image import warp
|
|||
_captcha_chars = 'AaCDdEeFfHJjKkLMmNnPpQRTtVvWwXxYy34679'
|
||||
|
||||
|
||||
def __tp_captcha_generate_image():
|
||||
captcha_image_t = captcha(
|
||||
width=136,
|
||||
height=36,
|
||||
drawings=[
|
||||
background(color='#eeeeee'),
|
||||
# curve(color='#4388d5', width=1, number=10),
|
||||
curve(color='#4388d5', width=1, number=10),
|
||||
curve(color='#af6fff', width=3, number=16),
|
||||
noise(number=80, color='#eeeeee', level=3),
|
||||
text(fonts=[
|
||||
os.path.join(get_cfg().res_path, 'fonts', '001.ttf')
|
||||
],
|
||||
# font_sizes=(28, 34, 36, 32),
|
||||
font_sizes=(34, 40, 32, 36),
|
||||
color='#63a8f5',
|
||||
# squeeze_factor=1.2,
|
||||
squeeze_factor=0.9,
|
||||
drawings=[
|
||||
# warp(dx_factor=0.05, dy_factor=0.05),
|
||||
warp(dx_factor=0.03, dy_factor=0.03),
|
||||
rotate(angle=20),
|
||||
offset()
|
||||
]),
|
||||
# curve(color='#af6fff', width=3, number=16),
|
||||
noise(number=30, color='#eeeeee', level=2),
|
||||
smooth(),
|
||||
])
|
||||
|
||||
chars_t = random.sample(_captcha_chars, 4)
|
||||
image = captcha_image_t(chars_t)
|
||||
|
||||
out = io.BytesIO()
|
||||
image.save(out, "jpeg", quality=100)
|
||||
return ''.join(chars_t), out.getvalue()
|
||||
|
||||
|
||||
def tp_captcha_generate_image(h):
|
||||
if h >= 32:
|
||||
captcha_image_t = captcha(
|
||||
|
@ -67,20 +30,6 @@ def tp_captcha_generate_image(h):
|
|||
curve(color='#4388d5', width=1, number=10),
|
||||
curve(color='#af6fff', width=3, number=16),
|
||||
noise(number=80, color='#eeeeee', level=3),
|
||||
# text(fonts=[
|
||||
# os.path.join(get_cfg().res_path, 'fonts', '001.ttf')
|
||||
# ],
|
||||
# # font_sizes=(28, 34, 36, 32),
|
||||
# font_sizes=(24, 20, 22, 26),
|
||||
# color='#cecece',
|
||||
# # squeeze_factor=1.2,
|
||||
# squeeze_factor=0.9,
|
||||
# drawings=[
|
||||
# # warp(dx_factor=0.05, dy_factor=0.05),
|
||||
# warp(dx_factor=0.03, dy_factor=0.03),
|
||||
# rotate(angle=20),
|
||||
# offset()
|
||||
# ]),
|
||||
smooth(),
|
||||
text(fonts=[
|
||||
os.path.join(get_cfg().res_path, 'fonts', '001.ttf')
|
||||
|
|
|
@ -8,6 +8,8 @@ from app.base.logger import log
|
|||
from app.base.utils import tp_timestamp_utc_now, tp_generate_random
|
||||
from app.const import *
|
||||
from app.model import syslog
|
||||
from app.logic.auth.password import tp_password_verify
|
||||
from app.logic.auth.oath import tp_oath_verify_code
|
||||
|
||||
|
||||
def get_user_info(user_id):
|
||||
|
@ -46,6 +48,67 @@ def get_by_username(username):
|
|||
return TPE_OK, s.recorder[0]
|
||||
|
||||
|
||||
def login(handler, username, password=None, oath_code=None):
|
||||
sys_cfg = get_cfg().sys
|
||||
|
||||
err, user_info = get_by_username(username)
|
||||
if err != TPE_OK:
|
||||
# if err == TPE_NOT_EXISTS:
|
||||
# syslog.sys_log({'username': username, 'surname': username}, handler.request.remote_ip, TPE_NOT_EXISTS, '用户身份验证失败,用户`{}`不存在'.format(username))
|
||||
return err, None
|
||||
|
||||
print(user_info)
|
||||
|
||||
if user_info.privilege == 0:
|
||||
# 尚未为此用户设置角色
|
||||
return TPE_PRIVILEGE, None
|
||||
|
||||
if user_info['state'] == TP_STATE_LOCKED:
|
||||
# 用户已经被锁定,如果系统配置为一定时间后自动解锁,则更新一下用户信息
|
||||
if sys_cfg.login.lock_timeout != 0:
|
||||
if tp_timestamp_utc_now() - user_info.lock_time > sys_cfg.login.lock_timeout * 60:
|
||||
user_info.fail_count = 0
|
||||
user_info.state = TP_STATE_NORMAL
|
||||
if user_info['state'] == TP_STATE_LOCKED:
|
||||
syslog.sys_log(user_info, handler.request.remote_ip, TPE_USER_LOCKED, '用户已被临时锁定')
|
||||
return TPE_USER_LOCKED, None
|
||||
elif user_info['state'] == TP_STATE_DISABLED:
|
||||
syslog.sys_log(user_info, handler.request.remote_ip, TPE_USER_DISABLED, '用户已被禁用')
|
||||
return TPE_USER_DISABLED, None
|
||||
elif user_info['state'] != TP_STATE_NORMAL:
|
||||
syslog.sys_log(user_info, handler.request.remote_ip, TPE_FAILED, '用户身份验证失败,系统内部错误')
|
||||
return TPE_FAILED, None
|
||||
|
||||
err_msg = ''
|
||||
if password is not None:
|
||||
# 如果系统配置了密码有效期,则检查用户的密码是否失效
|
||||
if sys_cfg.password.timeout != 0:
|
||||
pass
|
||||
|
||||
if not tp_password_verify(password, user_info['password']):
|
||||
err, is_locked = update_fail_count(handler, user_info)
|
||||
if is_locked:
|
||||
err_msg = '用户被临时锁定!'
|
||||
syslog.sys_log(user_info, handler.request.remote_ip, TPE_USER_AUTH, '登录失败,密码错误!{}'.format(err_msg))
|
||||
return TPE_USER_AUTH, None
|
||||
|
||||
if oath_code is not None:
|
||||
# use oath
|
||||
if len(user_info['oath_secret']) == 0:
|
||||
return TPE_OATH_MISMATCH, None
|
||||
|
||||
if not tp_oath_verify_code(user_info['oath_secret'], oath_code):
|
||||
err, is_locked = update_fail_count(handler, user_info)
|
||||
if is_locked:
|
||||
err_msg = '用户被临时锁定!'
|
||||
syslog.sys_log(user_info, handler.request.remote_ip, TPE_OATH_MISMATCH, "登录失败,身份验证器动态验证码错误!{}".format(err_msg))
|
||||
return TPE_OATH_MISMATCH, None
|
||||
|
||||
del user_info['password']
|
||||
del user_info['oath_secret']
|
||||
return TPE_OK, user_info
|
||||
|
||||
|
||||
def get_users(sql_filter, sql_order, sql_limit, sql_restrict, sql_exclude):
|
||||
dbtp = get_db().table_prefix
|
||||
s = SQL(get_db())
|
||||
|
@ -339,7 +402,19 @@ def update_login_info(handler, user_id):
|
|||
''.format(db.table_prefix,
|
||||
login_time=_time_now, ip=handler.request.remote_ip, user_id=user_id
|
||||
)
|
||||
db_ret = db.exec(sql)
|
||||
if db.exec(sql):
|
||||
return TPE_OK
|
||||
else:
|
||||
return TPE_DATABASE
|
||||
|
||||
|
||||
def update_oath_secret(user_id, oath_secret):
|
||||
db = get_db()
|
||||
sql = 'UPDATE `{dbtp}user` SET oath_secret="{secret}" WHERE id={user_id}'.format(dbtp=db.table_prefix, secret=oath_secret, user_id=user_id)
|
||||
if db.exec(sql):
|
||||
return TPE_OK
|
||||
else:
|
||||
return TPE_DATABASE
|
||||
|
||||
|
||||
def update_users_state(handler, user_ids, state):
|
||||
|
@ -467,16 +542,6 @@ def remove_users(handler, users):
|
|||
# return -102
|
||||
#
|
||||
#
|
||||
# def update_oath_secret(user_id, oath_secret):
|
||||
# db = get_db()
|
||||
# sql = 'UPDATE `{}account` SET `oath_secret`="{}" WHERE `account_id`={}'.format(db.table_prefix, oath_secret, int(user_id))
|
||||
# db_ret = db.exec(sql)
|
||||
# if db_ret:
|
||||
# return 0
|
||||
# else:
|
||||
# return -102
|
||||
|
||||
|
||||
# def get_user_list(with_admin=False):
|
||||
# db = get_db()
|
||||
# ret = list()
|
||||
|
|
Loading…
Reference in New Issue