Kaynağa Gözat

实现发送重置登陆密码URL到邮箱的功能

James Iter 8 yıl önce
ebeveyn
işleme
057780f2c1

+ 76 - 1
api/user.py

@@ -2,12 +2,13 @@
 # -*- coding: utf-8 -*-
 
 
-from flask import Blueprint, request, g, make_response
+from flask import Blueprint, g, make_response
 from flask import request
 from flask import session
 import json
 import jimit as ji
 
+from models.initialize import app
 from api.base import Base
 from models import User
 from models import Utils
@@ -109,3 +110,77 @@ def r_change_password():
     except ji.PreviewingError, e:
         return json.loads(e.message)
 
+
+@Utils.dumps2response
+def r_reset_password(token):
+
+    args_rules = [
+        Rules.TOKEN.value
+    ]
+
+    try:
+        ji.Check.previewing(args_rules, {'token': token})
+
+        token = Utils.verify_token(token, audience='r_reset_password')
+
+        user = User()
+        user.id = token['uid']
+        user.get()
+
+        args_rules = [
+            Rules.PASSWORD.value
+        ]
+
+        user.password = request.json.get('password')
+
+        ji.Check.previewing(args_rules, user.__dict__)
+        user.password = ji.Security.ji_pbkdf2(user.password)
+        user.update()
+    except ji.PreviewingError, e:
+        return json.loads(e.message)
+
+
+@Utils.dumps2response
+def r_send_reset_password_email(login_name):
+
+    args_rules = [
+        Rules.LOGIN_NAME.value
+    ]
+
+    try:
+        ji.Check.previewing(args_rules, {'login_name': login_name})
+
+        user = User()
+        try:
+            user.login_name = login_name
+            user.get_by('login_name')
+
+        except ji.PreviewingError, e:
+            # 如果 login_name 没有找到,则尝试从email里面查找
+            # 因为用户可能会把登录名理解成email
+            user.email = login_name
+            user.get_by('email')
+
+        host_url = request.host_url.rstrip('/')
+        # 5 分钟有效期
+        token = Utils.generate_token(uid=user.id, ttl=300, audience='r_reset_password')
+
+        reset_password_url = '/'.join([host_url, 'reset_password', token])
+
+        smtp_server = ji.NetUtils.smtp_init(host=app.config['smtp_host'], port=app.config.get('smtp_port', None),
+                                            login_name=app.config['smtp_user'], password=app.config['smtp_password'],
+                                            tls=app.config['smtp_starttls'])
+
+        ji.NetUtils.send_mail(smtp_server=smtp_server, sender=app.config['smtp_user'],
+                              receivers=[user.email], title=u'重置登录密码',
+                              message=u'请复制以下地址到浏览器中打开:' + reset_password_url)
+
+        ret = dict()
+        ret['state'] = ji.Common.exchange_state(20000)
+        ret['data'] = {'email': user.email}
+
+        return ret
+
+    except ji.PreviewingError, e:
+        return json.loads(e.message)
+

+ 3 - 0
api_route_table.py

@@ -32,6 +32,9 @@ add_rule_api(config.blueprint, '', api_func='config.r_get', methods=['GET'])
 add_rule_api(user.blueprint, '/_sign_in', api_func='user.r_sign_in', methods=['POST'])
 add_rule_api(user.blueprint, '/_sign_out', api_func='user.r_sign_out', methods=['GET'])
 add_rule_api(user.blueprint, '/_change_password', api_func='user.r_change_password', methods=['POST'])
+add_rule_api(user.blueprint, '/_reset_password/<token>', api_func='user.r_reset_password', methods=['POST'])
+add_rule_api(user.blueprint, '/_send_reset_password_email/<login_name>', api_func='user.r_send_reset_password_email',
+             methods=['PUT'])
 
 # 系统启动作业配置操作
 add_rule_api(boot_job.blueprint, '', api_func='boot_job.r_create', methods=['POST'])

+ 6 - 1
jimvc.conf

@@ -12,5 +12,10 @@
   "redis_dbid": 0,
   "log_file_path": "/var/log/jimv/jimvc.log",
   "jwt_secret": "kcMsj4qj0EAvAvj3nlFlPZd2x7P9kBBQ$deae64883a03982913e6c5f0ba265c2c5dfd0cfx'",
-  "SECRET_KEY": "QSYI73re6x553wmcNwT9tk4OCNK9OUS9xNUulDShEcvRw00YhCKaqHhEGYOGKOSB"
+  "SECRET_KEY": "QSYI73re6x553wmcNwT9tk4OCNK9OUS9xNUulDShEcvRw00YhCKaqHhEGYOGKOSB",
+  "smtp_host": "smtp.163.com",
+  "smtp_port": 25,
+  "smtp_user": "jamesiter@163.com",
+  "smtp_password": "ixq3s7O3",
+  "smtp_starttls": true
 }

+ 6 - 2
main.py

@@ -17,7 +17,7 @@ from flask.ext.session import Session
 
 from models import Utils
 from models.event_processor import EventProcessor
-from models.initialize import socketio, logger, q_ws, Init
+from models.initialize import logger, q_ws, Init
 import api_route_table
 import views_route_table
 from models import Database as db
@@ -105,7 +105,11 @@ def is_not_need_to_auth(endpoint):
         'api_config.r_create',
         'v_config.create',
         'v_misc.login',
-        'api_user.r_sign_in'
+        'v_misc.recover_password',
+        'v_misc.reset_password',
+        'api_user.r_sign_in',
+        'api_user.r_reset_password',
+        'api_user.r_send_reset_password_email'
     ]
 
     if endpoint in not_auth_table:

+ 2 - 0
models/rules.py

@@ -78,3 +78,5 @@ class Rules(Enum):
     OPERATE_RULE_CONTENT = (basestring, 'content')
     OPERATE_RULE_COMMAND = (basestring, 'command')
 
+    TOKEN = (basestring, 'token')
+

+ 13 - 4
models/utils.py

@@ -87,21 +87,30 @@ class Utils(object):
         return _superuser
 
     @staticmethod
-    def generate_token(uid):
+    def generate_token(uid, ttl=app.config['token_ttl'], audience=None):
         payload = {
             'iat': ji.Common.ts(),                                                  # 创建于
             'nbf': ji.Common.ts(),                                                  # 在此之前不可用
-            'exp': ji.Common.ts() + app.config['token_ttl'],                        # 过期时间
+            'exp': ji.Common.ts() + ttl,                        # 过期时间
             'uid': uid
         }
+
+        if audience is not None:
+            payload['aud'] = audience
+
         return jwt.encode(payload=payload, key=app.config['jwt_secret'], algorithm=app.config['jwt_algorithm'])
 
     @staticmethod
-    def verify_token(token):
+    def verify_token(token, audience=None):
         ret = dict()
         ret['state'] = ji.Common.exchange_state(20000)
         try:
-            payload = jwt.decode(jwt=token, key=app.config['jwt_secret'], algorithms=app.config['jwt_algorithm'])
+            if audience is None:
+                payload = jwt.decode(jwt=token, key=app.config['jwt_secret'], algorithms=app.config['jwt_algorithm'])
+            else:
+                payload = jwt.decode(jwt=token, key=app.config['jwt_secret'], algorithms=app.config['jwt_algorithm'],
+                                     audience=audience)
+
             return payload
         except jwt.InvalidTokenError, e:
             logger.error(e.message)

+ 0 - 1
requirements.txt

@@ -9,4 +9,3 @@ gunicorn
 eventlet
 redis
 websockify
-flask-socketio

+ 76 - 2
static/themes/admin/color-schemes/default.css

@@ -1,7 +1,81 @@
 /* Default color scheme */
 
-@import url(http://fonts.googleapis.com/css?family=Raleway:300);
-@import url(http://fonts.googleapis.com/css?family=Open+Sans);
+/* @import url(http://fonts.googleapis.com/css?family=Raleway:300); */
+/* latin-ext */
+@font-face {
+  font-family: 'Raleway';
+  font-style: normal;
+  font-weight: 300;
+  src: local('Raleway Light'), local('Raleway-Light'), url(http://fonts.gstatic.com/s/raleway/v12/ZKwULyCG95tk6mOqHQfRBAsYbbCjybiHxArTLjt7FRU.woff2) format('woff2');
+  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+  font-family: 'Raleway';
+  font-style: normal;
+  font-weight: 300;
+  src: local('Raleway Light'), local('Raleway-Light'), url(http://fonts.gstatic.com/s/raleway/v12/-_Ctzj9b56b8RgXW8FAriQzyDMXhdD8sAj6OAJTFsBI.woff2) format('woff2');
+  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
+}
+
+/* @import url(http://fonts.googleapis.com/css?family=Open+Sans); */
+/* cyrillic-ext */
+@font-face {
+  font-family: 'Open Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Open Sans Regular'), local('OpenSans-Regular'), url(http://fonts.gstatic.com/s/opensans/v15/K88pR3goAWT7BTt32Z01m4X0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
+  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
+}
+/* cyrillic */
+@font-face {
+  font-family: 'Open Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Open Sans Regular'), local('OpenSans-Regular'), url(http://fonts.gstatic.com/s/opensans/v15/RjgO7rYTmqiVp7vzi-Q5UYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
+  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+  font-family: 'Open Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Open Sans Regular'), local('OpenSans-Regular'), url(http://fonts.gstatic.com/s/opensans/v15/LWCjsQkB6EMdfHrEVqA1KYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
+  unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+  font-family: 'Open Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Open Sans Regular'), local('OpenSans-Regular'), url(http://fonts.gstatic.com/s/opensans/v15/xozscpT2726on7jbcb_pAoX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
+  unicode-range: U+0370-03FF;
+}
+/* vietnamese */
+@font-face {
+  font-family: 'Open Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Open Sans Regular'), local('OpenSans-Regular'), url(http://fonts.gstatic.com/s/opensans/v15/59ZRklaO5bWGqF5A9baEEYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
+  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+  font-family: 'Open Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Open Sans Regular'), local('OpenSans-Regular'), url(http://fonts.gstatic.com/s/opensans/v15/u-WUoqrET9fUeobQW7jkRYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
+  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+  font-family: 'Open Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Open Sans Regular'), local('OpenSans-Regular'), url(http://fonts.gstatic.com/s/opensans/v15/cJZKeOuBrn4kERxqtaUH3ZBw1xU1rKptJj_0jans920.woff2) format('woff2');
+  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
+}
+
 
 /* General */
 

+ 76 - 2
static/themes/frontend/color-schemes/default.css

@@ -1,7 +1,81 @@
 /* Default color schemes */
 
-@import url(http://fonts.googleapis.com/css?family=Raleway:300);
-@import url(http://fonts.googleapis.com/css?family=Open+Sans);
+/* @import url(http://fonts.googleapis.com/css?family=Raleway:300); */
+/* latin-ext */
+@font-face {
+  font-family: 'Raleway';
+  font-style: normal;
+  font-weight: 300;
+  src: local('Raleway Light'), local('Raleway-Light'), url(http://fonts.gstatic.com/s/raleway/v12/ZKwULyCG95tk6mOqHQfRBAsYbbCjybiHxArTLjt7FRU.woff2) format('woff2');
+  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+  font-family: 'Raleway';
+  font-style: normal;
+  font-weight: 300;
+  src: local('Raleway Light'), local('Raleway-Light'), url(http://fonts.gstatic.com/s/raleway/v12/-_Ctzj9b56b8RgXW8FAriQzyDMXhdD8sAj6OAJTFsBI.woff2) format('woff2');
+  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
+}
+
+/* @import url(http://fonts.googleapis.com/css?family=Open+Sans); */
+/* cyrillic-ext */
+@font-face {
+  font-family: 'Open Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Open Sans Regular'), local('OpenSans-Regular'), url(http://fonts.gstatic.com/s/opensans/v15/K88pR3goAWT7BTt32Z01m4X0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
+  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
+}
+/* cyrillic */
+@font-face {
+  font-family: 'Open Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Open Sans Regular'), local('OpenSans-Regular'), url(http://fonts.gstatic.com/s/opensans/v15/RjgO7rYTmqiVp7vzi-Q5UYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
+  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+  font-family: 'Open Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Open Sans Regular'), local('OpenSans-Regular'), url(http://fonts.gstatic.com/s/opensans/v15/LWCjsQkB6EMdfHrEVqA1KYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
+  unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+  font-family: 'Open Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Open Sans Regular'), local('OpenSans-Regular'), url(http://fonts.gstatic.com/s/opensans/v15/xozscpT2726on7jbcb_pAoX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
+  unicode-range: U+0370-03FF;
+}
+/* vietnamese */
+@font-face {
+  font-family: 'Open Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Open Sans Regular'), local('OpenSans-Regular'), url(http://fonts.gstatic.com/s/opensans/v15/59ZRklaO5bWGqF5A9baEEYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
+  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+  font-family: 'Open Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Open Sans Regular'), local('OpenSans-Regular'), url(http://fonts.gstatic.com/s/opensans/v15/u-WUoqrET9fUeobQW7jkRYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
+  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+  font-family: 'Open Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Open Sans Regular'), local('OpenSans-Regular'), url(http://fonts.gstatic.com/s/opensans/v15/cJZKeOuBrn4kERxqtaUH3ZBw1xU1rKptJj_0jans920.woff2) format('woff2');
+  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
+}
+
 
 /* General */
 

+ 7 - 2
templates/login.html

@@ -84,7 +84,7 @@
                             <span class="input-group-addon addon-inside bg-gray">
                                 <i class="glyph-icon icon-user"></i>
                             </span>
-                            <input type="text" class="form-control" id="login_name" placeholder="用户名" value="admin" disabled>
+                            <input type="text" class="form-control" id="login_name" placeholder="用户名" value="admin" readonly>
                         </div>
                     </div>
                     <div class="form-group">
@@ -95,9 +95,14 @@
                             <input type="password" class="form-control" id="password" placeholder="默认密码:jimv.pswd.com">
                         </div>
                     </div>
-                    <div class="form-group">
+                    <div class="form-group" style="margin-bottom: 10px;">
                         <button id="login_submit" type="button" class="btn btn-block btn-primary">登录</button>
                     </div>
+                    <div class="row">
+                        <div class="text-right col-md-12">
+                            <a href="/recover_password" title="找回密码">忘记密码?&nbsp;&nbsp;&nbsp;&nbsp;</a>
+                        </div>
+                    </div>
                 </div>
             </div>
         </form>

+ 68 - 0
templates/recover_password.html

@@ -0,0 +1,68 @@
+{% extends "layout.html" %}
+{% block head %}
+    {{ super() }}
+
+    <style type="text/css">
+
+        label>span {
+            color: deepskyblue;
+        }
+
+        .btn,
+        .content-box,
+        .form-group>div>div,
+        .form-control {
+            border-radius: 0 !important;
+        }
+
+        .form-horizontal > .form-group {
+            margin: 0;
+        }
+
+        html,body {
+            height: 100%;
+        }
+
+        .page-box .server-message {
+            padding: 35px 0;
+        }
+        body {
+            overflow: hidden;
+        }
+    </style>
+{% endblock head %}
+{% block body %}
+<script type="text/javascript">
+    $(document).ready(function() {
+        $('body').addClass('add-transition');
+        $('.add-page-transition').on('click', function(){
+            var transAttr = $(this).attr('data-transition');
+            $('.add-transition').attr('class', 'add-transition');
+            $('.add-transition').addClass(transAttr);
+        });
+    });
+
+</script>
+<div class="center-vertical">
+    <div class="center-content row wow">
+        <form id="login_form" class="col-md-4 col-sm-5 col-xs-11 col-lg-3 center-margin" action="/recover_password" method="post">
+            <h3 class="text-center pad25B font-primary" style="font-family: 'serif'; opacity: 0.83; font-size: -webkit-xxx-large;">找回密码</h3>
+            <div id="login-form" class="content-box bg-default">
+                <div class="content-box-wrapper pad20A">
+                    <div class="form-group">
+                        <div class="input-group">
+                            <span class="input-group-addon addon-inside bg-gray">
+                                <i class="glyph-icon icon-user"></i>
+                            </span>
+                            <input type="text" class="form-control" id="login_name" name="login_name" placeholder="用户名" value="admin" readonly>
+                        </div>
+                    </div>
+                    <div class="form-group" style="margin-bottom: 10px;">
+                        <button id="login_submit" class="btn btn-block btn-primary">下一步</button>
+                    </div>
+                </div>
+            </div>
+        </form>
+    </div>
+</div>
+{% endblock body %}

+ 111 - 0
templates/reset_password.html

@@ -0,0 +1,111 @@
+{% extends "layout.html" %}
+{% block head %}
+    {{ super() }}
+
+    <style type="text/css">
+
+        label>span {
+            color: deepskyblue;
+        }
+
+        .btn,
+        .content-box,
+        .form-group>div>div,
+        .form-control {
+            border-radius: 0 !important;
+        }
+
+        .form-horizontal > .form-group {
+            margin: 0;
+        }
+
+        html,body {
+            height: 100%;
+        }
+
+        .page-box .server-message {
+            padding: 35px 0;
+        }
+        body {
+            overflow: hidden;
+        }
+    </style>
+{% endblock head %}
+{% block body %}
+<script type="text/javascript">
+    $(document).ready(function() {
+        $('body').addClass('add-transition');
+        $('.add-page-transition').on('click', function(){
+            var transAttr = $(this).attr('data-transition');
+            $('.add-transition').attr('class', 'add-transition');
+            $('.add-transition').addClass(transAttr);
+        });
+
+        $("#login_form").delegate('#login_submit', 'click', function() {
+            login();
+
+        });
+
+        $("body").keydown(function() {
+            if (event.keyCode == "13") {//keyCode=13是回车键
+                login();
+            }
+        });
+    });
+
+    function login() {
+        $.ajax({
+            url : '/api/user/_sign_in',
+            type : 'POST',
+            contentType: "application/json; charset=utf-8",
+            dataType: "json",
+            data : JSON.stringify({login_name: $('#login_name').val(), password: $('#password').val()}),
+            error : function(data, textStatus, xhr) {
+                if (data.status === 401) {
+                    insert_warning_window($('#login_submit'), '<strong>登录失败: </strong>&nbsp;&nbsp;请查证账号、密码重新登录.');
+                } else {
+                    insert_warning_window($('#login_submit'), data.responseText);
+                }
+            },
+            success : function(data, textStatus, xhr) {
+                window.location.href="/";
+            }
+        });
+    }
+</script>
+<div class="center-vertical">
+    <div class="center-content row wow">
+        <form id="login_form" class="col-md-4 col-sm-5 col-xs-11 col-lg-3 center-margin">
+            <h3 class="text-center pad25B font-primary" style="font-family: 'Baskerville'; opacity: 0.83; font-size: -webkit-xxx-large;">JimV<span style="font-family: 'serif';"> Admin</span></h3>
+            <div id="login-form" class="content-box bg-default">
+                <div class="content-box-wrapper pad20A">
+                    <div class="form-group">
+                        <div class="input-group">
+                            <span class="input-group-addon addon-inside bg-gray">
+                                <i class="glyph-icon icon-user"></i>
+                            </span>
+                            <input type="text" class="form-control" id="login_name" placeholder="用户名" value="admin" disabled>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <div class="input-group">
+                            <span class="input-group-addon addon-inside bg-gray">
+                                <i class="glyph-icon icon-lock"></i>
+                            </span>
+                            <input type="password" class="form-control" id="password" placeholder="默认密码:jimv.pswd.com">
+                        </div>
+                    </div>
+                    <div class="form-group" style="margin-bottom: 10px;">
+                        <button id="login_submit" type="button" class="btn btn-block btn-primary">登录</button>
+                    </div>
+                    <div class="row">
+                        <div class="text-right col-md-12">
+                            <a href="#" class="switch-button" title="找回密码">忘记密码?&nbsp;&nbsp;&nbsp;&nbsp;</a>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </form>
+    </div>
+</div>
+{% endblock body %}

+ 28 - 1
views/misc.py

@@ -2,7 +2,9 @@
 # -*- coding: utf-8 -*-
 
 
-from flask import Blueprint, render_template
+import requests
+import json
+from flask import Blueprint, render_template, request
 
 
 __author__ = 'James Iter'
@@ -25,3 +27,28 @@ def login():
 def change_password():
     return render_template('change_password.html')
 
+
+def recover_password():
+    host_url = request.host_url.rstrip('/')
+
+    if request.method == 'POST':
+        login_name = request.form.get('login_name')
+
+        url = host_url + '/api/user/_send_reset_password_email/' + login_name
+
+        r = requests.put(url)
+        j_r = json.loads(r.content)
+
+        return render_template('success.html',
+                               go_back_url='/login',
+                               timeout=10000, title=u'提交成功',
+                               message_title=u'恢复密码的请求已被接受',
+                               message=u'恢复密码的URL已发送至您的邮箱,请查看您预留的邮箱:' + j_r['data']['email'] + u'。页面将在10秒钟后自动跳转到登录页面!')
+
+    else:
+        return render_template('recover_password.html')
+
+
+def reset_password():
+    return render_template('reset_password.html')
+

+ 2 - 0
views_route_table.py

@@ -17,6 +17,8 @@ add_rule_views(config.blueprint, '/create', views_func='config.create', methods=
 
 add_rule_views(misc.blueprint, 'login', views_func='misc.login', methods=['GET'])
 add_rule_views(misc.blueprint, 'change_password', views_func='misc.change_password', methods=['GET'])
+add_rule_views(misc.blueprint, 'recover_password', views_func='misc.recover_password', methods=['GET', 'POST'])
+add_rule_views(misc.blueprint, 'reset_password', views_func='misc.reset_password', methods=['GET'])
 
 add_rule_views(dashboard.blueprint, '', views_func='dashboard.show', methods=['GET'])