From 45458f10a44c352a2af12e06c21c374ef871ccff Mon Sep 17 00:00:00 2001
From: Beeant <huangxb0512@gmail.com>
Date: Mon, 15 Nov 2021 13:25:33 +0800
Subject: [PATCH 1/3] =?UTF-8?q?fix:=20LDAP=E8=B4=A6=E5=8F=B7=E7=99=BB?=
 =?UTF-8?q?=E5=BD=95=EF=BC=8C=E4=B8=8D=E8=83=BD=E5=88=9D=E5=A7=8B=E5=8C=96?=
 =?UTF-8?q?=E7=94=A8=E6=88=B7=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 models/member.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/models/member.go b/models/member.go
index e19b9a5c..b0fbd1b2 100644
--- a/models/member.go
+++ b/models/member.go
@@ -144,7 +144,7 @@ func (m *Member) ldapLogin(account string, password string) (*Member, error) {
 		m.Avatar = "/static/images/headimgurl.jpg"
 		m.Role = beego.AppConfig.DefaultInt("ldap_user_role", 2)
 		m.CreateTime = time.Now()
-
+		m.Password = password
 		err = m.Add()
 		if err != nil {
 			logs.Error("自动注册LDAP用户错误", err)

From 5941501093816f9957bd4983ab21717286ff4cc4 Mon Sep 17 00:00:00 2001
From: Beeant <huangxb0512@gmail.com>
Date: Tue, 16 Nov 2021 14:30:57 +0800
Subject: [PATCH 2/3] =?UTF-8?q?fix:=20ldap=E7=99=BB=E5=BD=95=E5=90=8E?=
 =?UTF-8?q?=E6=98=B5=E7=A7=B0=E6=9C=AA=E8=AE=BE=E7=BD=AE=E5=AF=BC=E8=87=B4?=
 =?UTF-8?q?=E7=9A=84=E7=AC=AC=E4=BA=8C=E4=B8=AALDAP=E7=94=A8=E6=88=B7?=
 =?UTF-8?q?=E6=97=A0=E6=B3=95=E7=99=BB=E5=BD=95=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 models/member.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/models/member.go b/models/member.go
index b0fbd1b2..e4e55156 100644
--- a/models/member.go
+++ b/models/member.go
@@ -145,6 +145,7 @@ func (m *Member) ldapLogin(account string, password string) (*Member, error) {
 		m.Role = beego.AppConfig.DefaultInt("ldap_user_role", 2)
 		m.CreateTime = time.Now()
 		m.Password = password
+		m.Nickname = account
 		err = m.Add()
 		if err != nil {
 			logs.Error("自动注册LDAP用户错误", err)

From 530b69c58ffe3acee444b7cb5930418cc63b22cc Mon Sep 17 00:00:00 2001
From: magicwind <2814461814@qq.com>
Date: Tue, 7 Jun 2022 14:03:11 +0800
Subject: [PATCH 3/3] feat: integrate Casdoor

---
 conf/oauth.conf.example                |  65 +--
 controllers/AcountController.go        | 611 +++----------------------
 controllers/BaseController.go          | 316 +++++++------
 controllers/SettingController.go       | 195 +-------
 controllers/UserController.go          |   1 +
 controllers/token_jwt_key.pem          |  29 ++
 go.mod                                 |   4 +-
 go.sum                                 | 369 ++++++++++++++-
 models/member.go                       |   7 -
 routers/web.go                         |  10 +-
 views/account/bind.html                | 135 ------
 views/account/find_password_setp1.html | 112 -----
 views/account/find_password_setp2.html | 125 -----
 views/account/mail_template.html       |  98 ----
 views/setting/index.html               | 219 ---------
 views/setting/menu.html                |   3 -
 views/setting/password.html            |  94 ----
 views/widgets/header.html              |   2 +-
 18 files changed, 651 insertions(+), 1744 deletions(-)
 create mode 100644 controllers/token_jwt_key.pem
 delete mode 100644 views/account/bind.html
 delete mode 100644 views/account/find_password_setp1.html
 delete mode 100644 views/account/find_password_setp2.html
 delete mode 100644 views/account/mail_template.html
 delete mode 100644 views/setting/index.html
 delete mode 100644 views/setting/password.html

diff --git a/conf/oauth.conf.example b/conf/oauth.conf.example
index e897adcd..10e399a4 100644
--- a/conf/oauth.conf.example
+++ b/conf/oauth.conf.example
@@ -1,57 +1,10 @@
-# 第三方登录配置
 [oauth]
-
-##### Gitee(码云) ####
-# 申请地址（需要先登录）：https://gitee.com/oauth/applications
-
-# 您的ClientId
-giteeClientId=
-
-# 您的ClientSecret
-giteeClientSecret=
-
-# 回调地址，把下面的bookstack.cn的域名换成你的即可
-giteeCallback=http://www.bookstack.cn/login/gitee
-
-# 下面这两项不要动
-giteeAccesstoken=https://gitee.com/oauth/token
-giteeUserInfo=https://gitee.com/api/v5/user
-
-
-
-######## GitHub ########
-# 申请地址(需要先登录你的GitHub)：https://github.com/settings/developers
-
-# 您的ClientId
-githubClientId=
-
-# 您的ClientSecret
-githubClientSecret=
-
-# 回调地址，把下面的bookstack.cn的域名换成你的即可
-githubCallback=http://www.bookstack.cn/login/github
-
-# 下面这两项不要动
-githubAccesstoken=https://github.com/login/oauth/access_token
-githubUserInfo=https://api.github.com/user
-
-
-
-#### QQ ####
-# 申请地址（需要先登录你的QQ）:https://connect.qq.com/manage.html
-
-#ClientId，即 APP ID
-qqClientId=
-
-#ClientSecret，即 APP Key
-qqClientSecret=
-
-# 回调地址，把下面的bookstack.cn的域名换成你的即可
-qqCallback=http://www.bookstack.cn/login/qq
-
-# 下面这三项不要动
-qqAccesstoken=https://graph.qq.com/oauth2.0/token
-qqOpenId=https://graph.qq.com/oauth2.0/me
-qqUserInfo=https://graph.qq.com/user/get_user_info
-
-### TODO 微信和微博登录，主要是我这边忘记了以前注册的个人开发者信息，当前没开发，后续会开发出来 ####
+######## casdoor ########
+# 先部署casdoor
+
+casdoorOrganization =
+casdoorApplication = "bookstack"
+casdoorEndpoint = http://localhost:8000
+clientId =
+clientSecret =
+redirectUrl = http://localhost:8181/login/callback
\ No newline at end of file
diff --git a/controllers/AcountController.go b/controllers/AcountController.go
index 5282b6cc..fc0c3f68 100644
--- a/controllers/AcountController.go
+++ b/controllers/AcountController.go
@@ -1,25 +1,21 @@
 package controllers
 
 import (
-	"regexp"
-	"strings"
-	"time"
-
-	"errors"
-
+	_ "embed"
 	"fmt"
-
 	"github.com/TruthHun/BookStack/conf"
 	"github.com/TruthHun/BookStack/models"
-	"github.com/TruthHun/BookStack/oauth"
-	"github.com/TruthHun/BookStack/utils"
 	"github.com/astaxie/beego"
 	"github.com/astaxie/beego/cache"
-	"github.com/astaxie/beego/orm"
 	"github.com/astaxie/beego/utils/captcha"
+	"github.com/casdoor/casdoor-go-sdk/auth"
+	"strings"
 	//"github.com/lifei6671/gocaptcha"
 )
 
+//go:embed token_jwt_key.pem
+var JwtPublicKey string
+
 // AccountController 用户登录与注册.
 type AccountController struct {
 	BaseController
@@ -28,576 +24,111 @@ type AccountController struct {
 var cpt *captcha.Captcha
 
 func init() {
+	InitAuthConfig()
 	// use beego cache system store the captcha data
 	fc := &cache.FileCache{CachePath: "./cache/captcha"}
 	cpt = captcha.NewWithFilter("/captcha/", fc)
 }
 
-//第三方登录回调
-//封装一个内部调用的函数，loginByMemberId
-func (this *AccountController) Oauth() {
-
-	var (
-		nickname  string //昵称
-		avatar    string //头像的http链接地址
-		email     string //邮箱地址
-		username  string //用户名
-		tips      string
-		id        interface{} //第三方的用户id，唯一识别码
-		IsEmail   bool        //是否是使用邮箱注册
-		captchaOn bool        //是否开启了验证码
-	)
-
-	//如果开启了验证码
-	if v, ok := this.Option["ENABLED_CAPTCHA"]; ok && strings.EqualFold(v, "true") {
-		captchaOn = true
-		this.Data["CaptchaOn"] = captchaOn
-	}
-
-	oauthLogin := false
-	if v, ok := this.Option["LOGIN_QQ"]; ok && strings.EqualFold(v, "true") {
-		this.Data["LoginQQ"] = true
-		oauthLogin = true
-	}
-	if v, ok := this.Option["LOGIN_GITHUB"]; ok && strings.EqualFold(v, "true") {
-		this.Data["LoginGitHub"] = true
-		oauthLogin = true
-	}
-	if v, ok := this.Option["LOGIN_GITEE"]; ok && strings.EqualFold(v, "true") {
-		this.Data["LoginGitee"] = true
-		oauthLogin = true
-	}
-	this.Data["OauthLogin"] = oauthLogin
-
-	oa := this.GetString(":oauth")
-	code := this.GetString("code")
-	switch oa {
-	case "gitee":
-		tips = `您正在使用【码云】登录`
-		token, err := oauth.GetGiteeAccessToken(code)
-		if err != nil {
-			beego.Error(err)
-			this.Abort("404")
-		}
-
-		info, err := oauth.GetGiteeUserInfo(token.AccessToken)
-		if err != nil {
-			beego.Error(err)
-			this.Abort("404")
-		}
-
-		if info.Id > 0 {
-			existInfo, _ := models.ModelGitee.GetUserByGiteeId(info.Id, "id", "member_id")
-			if existInfo.MemberId > 0 { //直接登录
-				err = this.loginByMemberId(existInfo.MemberId)
-				if err != nil {
-					beego.Error(err)
-					this.Abort("404")
-				}
-				this.Redirect(beego.URLFor("HomeController.Index"), 302)
-				return
-			}
-			if existInfo.Id == 0 { //原本不存在于数据库中的数据需要入库
-				orm.NewOrm().Insert(&models.Gitee{GiteeUser: info})
-			}
-			nickname = info.Name
-			username = info.Login
-			avatar = info.AvatarURL
-			email = info.Email
-			id = info.Id
-		} else {
-			err = errors.New("获取gitee用户数据失败")
-			beego.Error(err)
-			this.Abort("404")
-		}
-	case "github":
-		tips = `您正在使用【GitHub】登录`
-		token, err := oauth.GetGithubAccessToken(code)
-		if err != nil {
-			beego.Error(err.Error())
-			this.Abort("404")
-		}
-
-		info, err := oauth.GetGithubUserInfo(token.AccessToken)
-		if err != nil {
-			beego.Error(err.Error())
-			this.Abort("404")
-		}
-
-		if info.Id > 0 {
-			existInfo, _ := models.ModelGithub.GetUserByGithubId(info.Id, "id", "member_id")
-			if existInfo.MemberId > 0 { //直接登录
-				err = this.loginByMemberId(existInfo.MemberId)
-				if err != nil {
-					beego.Error(err.Error())
-					this.Abort("404")
-				}
-				this.Redirect(beego.URLFor("HomeController.Index"), 302)
-				return
-			}
-			if existInfo.Id == 0 { //原本不存在于数据库中的数据需要入库
-				orm.NewOrm().Insert(&models.Github{GithubUser: info})
-			}
-			nickname = info.Name
-			username = info.Login
-			avatar = info.AvatarURL
-			email = info.Email
-			id = info.Id
-		} else {
-			err = errors.New("获取github用户数据失败")
-			beego.Error(err.Error())
-			this.Abort("404")
-		}
-
-	case "qq":
-		tips = `您正在使用【QQ】登录`
-		token, err := oauth.GetQQAccessToken(code)
-		if err != nil {
-			beego.Error(err)
-			this.Abort("404")
-		}
-
-		openid, err := oauth.GetQQOpenId(token)
-		if err != nil {
-			beego.Error(err.Error())
-			this.Abort("404")
-		}
-
-		info, err := oauth.GetQQUserInfo(token.AccessToken, openid)
-		if err != nil {
-			beego.Error(err.Error())
-			this.Abort("404")
-		}
-
-		if info.Ret == 0 {
-			existInfo, _ := models.ModelQQ.GetUserByOpenid(openid, "id", "member_id")
-			if existInfo.MemberId > 0 { //直接登录
-				err = this.loginByMemberId(existInfo.MemberId)
-				if err != nil {
-					beego.Error(err.Error())
-					this.Abort("404")
-				}
-				this.Redirect(beego.URLFor("HomeController.Index"), 302)
-				return
-			}
+func InitAuthConfig() {
+	casdoorEndpoint := beego.AppConfig.String("oauth::casdoorEndpoint")
+	clientId := beego.AppConfig.String("oauth::clientId")
+	clientSecret := beego.AppConfig.String("oauth::clientSecret")
+	casdoorOrganization := beego.AppConfig.String("oauth::casdoorOrganization")
+	casdoorApplication := beego.AppConfig.String("oauth::casdoorApplication")
 
-			if existInfo.Id == 0 { //原本不存在于数据库中的数据需要入库
-				orm.NewOrm().Insert(&models.QQ{
-					OpenId:    openid,
-					Name:      info.Name,
-					Gender:    info.Gender,
-					AvatarURL: info.AvatarURL,
-				})
-			}
-			nickname = info.Name
-			username = ""
-			avatar = info.AvatarURL
-			email = ""
-			id = openid
-		} else {
-			err = errors.New(info.Msg)
-			beego.Error(err)
-			this.Abort("404")
-		}
-	default: //email
-		IsEmail = true
-	}
+	auth.InitConfig(casdoorEndpoint, clientId, clientSecret, JwtPublicKey, casdoorOrganization, casdoorApplication)
+}
 
-	this.Data["IsEmail"] = IsEmail
-	this.Data["Nickname"] = nickname
-	this.Data["Avatar"] = avatar
-	this.Data["Email"] = email
-	this.Data["Username"] = username
-	this.Data["AuthType"] = oa
-	this.Data["SeoTitle"] = "完善信息"
-	this.Data["Tips"] = tips
-	this.Data["Id"] = id
-	this.Data["GiteeClientId"] = beego.AppConfig.String("oauth::giteeClientId")
-	this.Data["GiteeCallback"] = beego.AppConfig.String("oauth::giteeCallback")
-	this.Data["GithubClientId"] = beego.AppConfig.String("oauth::githubClientId")
-	this.Data["GithubCallback"] = beego.AppConfig.String("oauth::githubCallback")
-	this.Data["QQClientId"] = beego.AppConfig.String("oauth::qqClientId")
-	this.Data["QQCallback"] = beego.AppConfig.String("oauth::qqCallback")
-	this.Data["RandomStr"] = time.Now().Unix()
-	this.SetSession("auth", fmt.Sprintf("%v-%v", oa, id)) //存储标识，以标记是哪个用户，在完善用户信息的时候跟传递过来的auth和id进行校验
-	this.TplName = "account/bind.html"
+// @Title Login
+func (c *BaseController) Login() {
+	redirectUrl := auth.GetSigninUrl(beego.AppConfig.String("oauth::redirectUrl"))
+	c.Redirect(redirectUrl, 302)
+}
 
+// @Title Signup
+func (c *BaseController) Signup() {
+	redirectUrl := auth.GetSignupUrl(true, beego.AppConfig.String("oauth::redirectUrl"))
+	c.Redirect(redirectUrl, 302)
 }
 
-// Login 用户登录.
-func (this *AccountController) Login() {
+// @Title Callback
+// @Description sign in as a member
+func (c *BaseController) Callback() {
+
 	var (
-		remember  CookieRemember
-		captchaOn bool //是否开启了验证码
+		nickname string //昵称
+		avatar   string //头像的http链接地址
+		email    string //邮箱地址
+		username string //用户名
 	)
 
-	this.TplName = "account/login.html"
-
-	//如果开启了验证码
-	if v, ok := this.Option["ENABLED_CAPTCHA"]; ok && strings.EqualFold(v, "true") {
-		captchaOn = true
-		this.Data["CaptchaOn"] = captchaOn
-	}
-
-	oauthLogin := false
-	if v, ok := this.Option["LOGIN_QQ"]; ok && strings.EqualFold(v, "true") {
-		this.Data["LoginQQ"] = true
-		oauthLogin = true
-	}
-	if v, ok := this.Option["LOGIN_GITHUB"]; ok && strings.EqualFold(v, "true") {
-		this.Data["LoginGitHub"] = true
-		oauthLogin = true
-	}
-	if v, ok := this.Option["LOGIN_GITEE"]; ok && strings.EqualFold(v, "true") {
-		this.Data["LoginGitee"] = true
-		oauthLogin = true
-	}
-	this.Data["OauthLogin"] = oauthLogin
-
-	//如果Cookie中存在登录信息
-	if cookie, ok := this.GetSecureCookie(conf.GetAppKey(), "login"); ok {
-		if err := utils.Decode(cookie, &remember); err == nil {
-			if err = this.loginByMemberId(remember.MemberId); err == nil {
-				this.Redirect(beego.URLFor("HomeController.Index"), 302)
-				return
-			}
-		}
-	}
-
-	if this.Ctx.Input.IsPost() {
-		account := this.GetString("account")
-		password := this.GetString("password")
-
-		if captchaOn && !cpt.VerifyReq(this.Ctx.Request) {
-			this.JsonResult(1, "验证码不正确")
-		}
-
-		member, err := models.NewMember().Login(account, password)
-
-		//如果没有数据
-		if err != nil {
-			beego.Error("用户登录 =>", err)
-			this.JsonResult(500, "账号或密码错误", nil)
-		}
-		member.LastLoginTime = time.Now()
-		member.Update()
-		this.SetMember(*member)
-		remember.MemberId = member.MemberId
-		remember.Account = member.Account
-		remember.Time = time.Now()
-		v, err := utils.Encode(remember)
-		if err == nil {
-			this.SetSecureCookie(conf.GetAppKey(), "login", v, 24*3600*365)
-		}
-		this.JsonResult(0, "ok")
-	}
-
-	this.Data["GiteeClientId"] = beego.AppConfig.String("oauth::giteeClientId")
-	this.Data["GiteeCallback"] = beego.AppConfig.String("oauth::giteeCallback")
-	this.Data["GithubClientId"] = beego.AppConfig.String("oauth::githubClientId")
-	this.Data["GithubCallback"] = beego.AppConfig.String("oauth::githubCallback")
-	this.Data["QQClientId"] = beego.AppConfig.String("oauth::qqClientId")
-	this.Data["QQCallback"] = beego.AppConfig.String("oauth::qqCallback")
-	this.Data["RandomStr"] = time.Now().Unix()
-	this.GetSeoByPage("login", map[string]string{
-		"title":       "登录 - " + this.Sitename,
-		"keywords":    "登录," + this.Sitename,
-		"description": this.Sitename + "专注于文档在线写作、协作、分享、阅读与托管，让每个人更方便地发布、分享和获得知识。",
-	})
-}
-
-//用户注册.[移除用户注册，直接叫用户绑定]
-//注意：如果用户输入的账号密码跟现有的账号密码相一致，则表示绑定账号，否则表示注册新账号。
-func (this *AccountController) Bind() {
-	var err error
-	account := this.GetString("account")
-	nickname := strings.TrimSpace(this.GetString("nickname"))
-	password1 := this.GetString("password1")
-	password2 := this.GetString("password2")
-	email := this.GetString("email")
-	oauthType := this.GetString("oauth")
-	oauthId := this.GetString("id")
-	avatar := this.GetString("avatar") //用户头像
-	isbind, _ := this.GetInt("isbind", 0)
+	code := c.Input().Get("code")
+	state := c.Input().Get("state")
 
-	ibind := func(oauthType string, oauthId, memberId interface{}) (err error) {
-		//注册成功，绑定用户
-		switch oauthType {
-		case "gitee":
-			err = models.ModelGitee.Bind(oauthId, memberId)
-		case "github":
-			err = models.ModelGithub.Bind(oauthId, memberId)
-		case "qq":
-			err = models.ModelQQ.Bind(oauthId, memberId)
-		}
-		return
+	token, err := auth.GetOAuthToken(code, state)
+	if err != nil {
+		beego.Error(err)
+		c.Abort("404")
 	}
 
-	if oauthType != "email" {
-		if auth, ok := this.GetSession("auth").(string); !ok || fmt.Sprintf("%v-%v", oauthType, oauthId) != auth {
-			this.JsonResult(6005, "绑定信息有误，授权类型不符")
-		}
-	} else { //邮箱登录，如果开启了验证码，则对验证码进行校验
-		if v, ok := this.Option["ENABLED_CAPTCHA"]; ok && strings.EqualFold(v, "true") {
-			if !cpt.VerifyReq(this.Ctx.Request) {
-				this.JsonResult(1, "验证码不正确")
-			}
-		}
+	claims, err := auth.ParseJwtToken(token.AccessToken)
+	if err != nil {
+		panic(err)
 	}
 
-	member := models.NewMember()
-
-	if isbind == 1 {
-		if member, err = models.NewMember().Login(account, password1); err != nil || member.MemberId == 0 {
-			beego.Error("绑定用户失败", err, member)
-			this.JsonResult(1, "绑定用户失败，用户名或密码不正确")
-		}
-	} else {
-		if password1 != password2 {
-			this.JsonResult(6003, "登录密码与确认密码不一致")
-		}
-
-		if ok, err := regexp.MatchString(conf.RegexpAccount, account); account == "" || !ok || err != nil {
-			this.JsonResult(6001, "用户名只能由英文字母数字组成，且在3-50个字符")
-		}
-		if l := strings.Count(password1, ""); password1 == "" || l > 50 || l < 6 {
-			this.JsonResult(6002, "密码必须在6-50个字符之间")
-		}
+	claims.AccessToken = token.AccessToken
+	c.SetSessionClaims(claims)
 
-		if ok, err := regexp.MatchString(conf.RegexpEmail, email); !ok || err != nil || email == "" {
-			this.JsonResult(6004, "邮箱格式不正确")
-		}
-		if l := strings.Count(nickname, "") - 1; l < 2 || l > 20 {
-			this.JsonResult(6004, "用户昵称限制在2-20个字符")
-		}
+	info := &claims.User
+	nickname = info.DisplayName
+	username = info.Name
+	avatar = info.Avatar
+	email = info.Email
 
-		//出错或者用户不存在，则重新注册用户，否则直接登录
-		member.Account = account
+	member, err := models.NewMember().FindByAccount(username)
+	if member.MemberId == 0 {
+		//用户不存在，则重新注册用户
+		member.Account = username
 		member.Nickname = nickname
-		member.Password = password1
 		member.Role = conf.MemberGeneralRole
-		member.Avatar = conf.GetDefaultAvatar()
+		member.Avatar = avatar
 		member.CreateAt = 0
 		member.Email = email
 		member.Status = 0
-		if len(avatar) > 0 {
-			member.Avatar = avatar
-		}
 		if err := member.Add(); err != nil {
 			beego.Error(err)
-			this.JsonResult(6006, err.Error())
-		}
-	}
-	if err = this.loginByMemberId(member.MemberId); err != nil {
-		beego.Error(err.Error())
-		this.JsonResult(1, err.Error())
-	}
-
-	if err = ibind(oauthType, oauthId, member.MemberId); err != nil {
-		beego.Error(err)
-		this.JsonResult(0, "登录失败")
-	}
-
-	if oauthType == "email" {
-		this.JsonResult(0, "注册成功")
-	}
-	this.JsonResult(0, "登录成功")
-}
-
-//找回密码.
-func (this *AccountController) FindPassword() {
-
-	this.TplName = "account/find_password_setp1.html"
-	mailConf := conf.GetMailConfig()
-
-	if this.Ctx.Input.IsPost() {
-
-		email := this.GetString("email")
-
-		if email == "" {
-			this.JsonResult(6005, "邮箱地址不能为空")
-		}
-		if !mailConf.EnableMail {
-			this.JsonResult(6004, "未启用邮件服务")
-		}
-
-		//captcha := this.GetString("code")
-		//如果开启了验证码
-		//if v, ok := this.Option["ENABLED_CAPTCHA"]; ok && strings.EqualFold(v, "true") {
-		//	v, ok := this.GetSession(conf.CaptchaSessionName).(string)
-		//	if !ok || !strings.EqualFold(v, captcha) {
-		//		this.JsonResult(6001, "验证码不正确")
-		//	}
-		//}
-
-		if !cpt.VerifyReq(this.Ctx.Request) {
-			this.JsonResult(6001, "验证码不正确")
-		}
-
-		member, err := models.NewMember().FindByFieldFirst("email", email)
-		if err != nil {
-			beego.Error(err)
-			this.JsonResult(6006, "邮箱不存在")
-		}
-		if member.Status != 0 {
-			this.JsonResult(6007, "账号已被禁用")
-		}
-		if member.AuthMethod == conf.AuthMethodLDAP {
-			this.JsonResult(6011, "当前用户不支持找回密码")
-		}
-
-		count, err := models.NewMemberToken().FindSendCount(email, time.Now().Add(-1*time.Hour), time.Now())
-
-		if err != nil {
-			beego.Error(err)
-			this.JsonResult(6008, "发送邮件失败")
-		}
-		if count > mailConf.MailNumber {
-			this.JsonResult(6008, "发送次数太多，请稍候再试")
-		}
-
-		memberToken := models.NewMemberToken()
-
-		memberToken.Token = string(utils.Krand(32, utils.KC_RAND_KIND_ALL))
-		memberToken.Email = email
-		memberToken.MemberId = member.MemberId
-		memberToken.IsValid = false
-		if _, err := memberToken.InsertOrUpdate(); err != nil {
-			this.JsonResult(6009, "邮件发送失败")
-		}
-
-		data := map[string]interface{}{
-			"SITE_NAME": this.Option["SITE_NAME"],
-			"url":       this.BaseUrl() + beego.URLFor("AccountController.FindPassword", "token", memberToken.Token, "mail", email),
-		}
-
-		body, err := this.ExecuteViewPathTemplate("account/mail_template.html", data)
-		if err != nil {
-			beego.Error(err)
-			this.JsonResult(6003, "邮件发送失败")
-		}
-
-		if err = utils.SendMail(mailConf, "找回密码", email, body); err != nil {
-			beego.Error(err)
-			this.JsonResult(6003, "邮件发送失败")
-		}
-
-		this.JsonResult(0, "ok", this.BaseUrl()+beego.URLFor("AccountController.Login"))
-	}
-
-	this.GetSeoByPage("findpwd", map[string]string{
-		"title":       "找回密码 - " + this.Sitename,
-		"keywords":    "找回密码",
-		"description": this.Sitename + "专注于文档在线写作、协作、分享、阅读与托管，让每个人更方便地发布、分享和获得知识。",
-	})
-
-	token := this.GetString("token")
-	mail := this.GetString("mail")
-
-	if token != "" && mail != "" {
-		memberToken, err := models.NewMemberToken().FindByFieldFirst("token", token)
-
-		if err != nil {
-			beego.Error(err)
-			this.Data["ErrorMessage"] = "邮件已失效"
-			this.TplName = "errors/error.html"
-			return
 		}
-		subTime := memberToken.SendTime.Sub(time.Now())
-
-		if !strings.EqualFold(memberToken.Email, mail) || subTime.Minutes() > float64(mailConf.MailExpired) || !memberToken.ValidTime.IsZero() {
-			this.Data["ErrorMessage"] = "验证码已过期，请重新操作。"
-			this.TplName = "errors/error.html"
-			return
-		}
-		this.Data["Email"] = memberToken.Email
-		this.Data["Token"] = memberToken.Token
-		this.TplName = "account/find_password_setp2.html"
-
-	}
-
-}
-
-//校验邮件并修改密码.
-func (this *AccountController) ValidEmail() {
-	password1 := this.GetString("password1")
-	password2 := this.GetString("password2")
-	token := this.GetString("token")
-	mail := this.GetString("mail")
-
-	if password1 == "" {
-		this.JsonResult(6001, "密码不能为空")
-	}
-	if l := strings.Count(password1, ""); l < 6 || l > 50 {
-		this.JsonResult(6001, "密码不能为空且必须在6-50个字符之间")
-	}
-	if password2 == "" {
-		this.JsonResult(6002, "确认密码不能为空")
-	}
-	if password1 != password2 {
-		this.JsonResult(6003, "确认密码输入不正确")
-	}
-
-	if !cpt.VerifyReq(this.Ctx.Request) {
-		this.JsonResult(6001, "验证码不正确")
 	}
 
-	mailConf := conf.GetMailConfig()
-	memberToken, err := models.NewMemberToken().FindByFieldFirst("token", token)
-
-	if err != nil {
-		beego.Error(err)
-		this.JsonResult(6007, "邮件已失效")
-	}
-	subTime := memberToken.SendTime.Sub(time.Now())
-
-	if !strings.EqualFold(memberToken.Email, mail) || subTime.Minutes() > float64(mailConf.MailExpired) || !memberToken.ValidTime.IsZero() {
-
-		this.JsonResult(6008, "验证码已过期，请重新操作。")
-	}
-	member, err := models.NewMember().Find(memberToken.MemberId)
-	if err != nil {
-		beego.Error(err)
-		this.JsonResult(6005, "用户不存在")
-	}
-	hash, err := utils.PasswordHash(password1)
-
-	if err != nil {
-		beego.Error(err)
-		this.JsonResult(6006, "保存密码失败")
-	}
-
-	member.Password = hash
-
-	err = member.Update("password")
-	memberToken.ValidTime = time.Now()
-	memberToken.IsValid = true
-	memberToken.InsertOrUpdate()
-
-	if err != nil {
-		beego.Error(err)
-		this.JsonResult(6006, "保存密码失败")
+	if err = c.loginByMemberId(member.MemberId); err != nil {
+		beego.Error(err.Error())
 	}
-	this.JsonResult(0, "ok", this.BaseUrl()+beego.URLFor("AccountController.Login"))
+	c.Redirect(beego.URLFor("HomeController.Index"), 302)
 }
 
-// Logout 退出登录.
-func (this *AccountController) Logout() {
-	this.SetMember(models.Member{})
+// @Title Signout
+// @Description sign out the current member
+// @Success 200 {object} controllers.api_controller.Response The Response object
+// @router /signout [post]
+// @Tag Account API
+func (c *BaseController) Logout() {
 
-	this.SetSecureCookie(conf.GetAppKey(), "login", "", -3600)
+	c.SetSessionClaims(nil)
+	c.SetMember(models.Member{})
 
-	this.Redirect(beego.URLFor("AccountController.Login"), 302)
+	c.SetSecureCookie(conf.GetAppKey(), "login", "", -3600)
+	c.Redirect(beego.URLFor("HomeController.Index"), 302)
 }
 
 //记录笔记
-func (this *AccountController) Note() {
-	docid, _ := this.GetInt("doc_id")
+func (c *AccountController) Note() {
+	docid, _ := c.GetInt("doc_id")
 	fmt.Println(docid)
-	if strings.ToLower(this.Ctx.Request.Method) == "post" {
+	if strings.ToLower(c.Ctx.Request.Method) == "post" {
 
 	} else {
-		this.Data["SeoTitle"] = "笔记"
-		this.TplName = "account/note.html"
+		c.Data["SeoTitle"] = "笔记"
+		c.TplName = "account/note.html"
 	}
 }
diff --git a/controllers/BaseController.go b/controllers/BaseController.go
index cd85aa03..869b8ef6 100644
--- a/controllers/BaseController.go
+++ b/controllers/BaseController.go
@@ -2,13 +2,13 @@ package controllers
 
 import (
 	"bytes"
+	"encoding/gob"
 	"fmt"
+
 	"net/url"
 	"strconv"
 	"unicode/utf8"
 
-	"github.com/TruthHun/BookStack/utils"
-
 	"encoding/json"
 	"io"
 	"strings"
@@ -22,8 +22,10 @@ import (
 	"github.com/PuerkitoBio/goquery"
 	"github.com/TruthHun/BookStack/conf"
 	"github.com/TruthHun/BookStack/models"
+	"github.com/TruthHun/BookStack/utils"
 	"github.com/astaxie/beego"
 	"github.com/astaxie/beego/orm"
+	"github.com/casdoor/casdoor-go-sdk/auth"
 )
 
 type BaseController struct {
@@ -46,8 +48,54 @@ type CookieRemember struct {
 	Time     time.Time
 }
 
-func (this *BaseController) refreshReferer() {
-	referer := this.Ctx.Request.Header.Get("referer")
+func init() {
+	gob.Register(auth.Claims{})
+}
+
+func (c *BaseController) GetSessionClaims() *auth.Claims {
+	s := c.GetSession("user")
+	if s == nil {
+		return nil
+	}
+
+	claims := s.(auth.Claims)
+	return &claims
+}
+
+func (c *BaseController) SetSessionClaims(claims *auth.Claims) {
+	if claims == nil {
+		c.DelSession("user")
+		return
+	}
+
+	c.SetSession("user", *claims)
+}
+
+func (c *BaseController) refreshUser() {
+	//casdoor用户信息更新,需要同步
+	if c.GetSession("isUpdateUser") == 1 {
+		account := c.GetSessionClaims()
+		if account == nil {
+			c.Redirect(beego.URLFor("AccountController.Login"), 302)
+		}
+		user, err := auth.GetUser(account.Name)
+		if err != nil {
+			return
+		}
+		if member, err := models.NewMember().Find(c.Member.MemberId); err == nil {
+			member.Avatar = user.Avatar
+			member.Nickname = user.DisplayName
+			err = member.Update()
+			if err != nil {
+				c.JsonResult(60001, "更新用户信息失败")
+			}
+		}
+		c.SetSession("isUpdateUser", 0)
+	}
+}
+
+func (c *BaseController) refreshReferer() {
+	referer := c.Ctx.Request.Header.Get("referer")
 	if referer != "" {
 		referer, _ = url.QueryUnescape(referer)
 		referer = strings.ToLower(referer)
@@ -56,12 +104,12 @@ func (this *BaseController) refreshReferer() {
 			for _, item := range forbid {
 				item = strings.ToLower(strings.TrimSpace(item))
 				// 先判断是否带有非法关键字
-				if item != "" && strings.Contains(referer, item) && !strings.HasSuffix(referer, strings.ToLower(this.Ctx.Request.RequestURI)) {
+				if item != "" && strings.Contains(referer, item) && !strings.HasSuffix(referer, strings.ToLower(c.Ctx.Request.RequestURI)) {
 					if u, err := url.Parse(referer); err == nil {
 						// 且referer的host与当前请求的host不是同一个，则进行302跳转以刷新过滤当前referer
-						if strings.ToLower(u.Host) != strings.ToLower(this.Ctx.Request.Host) {
-							this.Redirect(this.Ctx.Request.RequestURI, 302)
-							this.StopRun()
+						if strings.ToLower(u.Host) != strings.ToLower(c.Ctx.Request.Host) {
+							c.Redirect(c.Ctx.Request.RequestURI, 302)
+							c.StopRun()
 							return
 						}
 					}
@@ -72,124 +120,124 @@ func (this *BaseController) refreshReferer() {
 }
 
 // Prepare 预处理.
-func (this *BaseController) Prepare() {
-	this.refreshReferer()
-
-	this.Data["Version"] = utils.Version
-	this.IsMobile = utils.IsMobile(this.Ctx.Request.UserAgent())
-	this.Data["IsMobile"] = this.IsMobile
-	this.Member = models.NewMember() //初始化
-	this.EnableAnonymous = false
-	this.AllowRegister = true
-	this.EnableDocumentHistory = 0
-	this.OssDomain = strings.TrimRight(beego.AppConfig.String("oss::Domain"), "/ ")
-	this.Data["OssDomain"] = this.OssDomain
-	this.StaticDomain = strings.Trim(beego.AppConfig.DefaultString("static_domain", ""), "/")
-	this.Data["StaticDomain"] = this.StaticDomain
+func (c *BaseController) Prepare() {
+	c.refreshReferer()
+
+	c.Data["Version"] = utils.Version
+	c.IsMobile = utils.IsMobile(c.Ctx.Request.UserAgent())
+	c.Data["IsMobile"] = c.IsMobile
+	c.Member = models.NewMember() //初始化
+	c.EnableAnonymous = false
+	c.AllowRegister = true
+	c.EnableDocumentHistory = 0
+	c.OssDomain = strings.TrimRight(beego.AppConfig.String("oss::Domain"), "/ ")
+	c.Data["OssDomain"] = c.OssDomain
+	c.StaticDomain = strings.Trim(beego.AppConfig.DefaultString("static_domain", ""), "/")
+	c.Data["StaticDomain"] = c.StaticDomain
 
 	//从session中获取用户信息
-	if member, ok := this.GetSession(conf.LoginSessionName).(models.Member); ok && member.MemberId > 0 {
+	if member, ok := c.GetSession(conf.LoginSessionName).(models.Member); ok && member.MemberId > 0 {
 		m, _ := models.NewMember().Find(member.MemberId)
-		this.Member = m
+		c.Member = m
 	} else {
 		//如果Cookie中存在登录信息，从cookie中获取用户信息
-		if cookie, ok := this.GetSecureCookie(conf.GetAppKey(), "login"); ok {
+		if cookie, ok := c.GetSecureCookie(conf.GetAppKey(), "login"); ok {
 			var remember CookieRemember
 			err := utils.Decode(cookie, &remember)
 			if err == nil {
 				member, err := models.NewMember().Find(remember.MemberId)
 				if err == nil {
-					this.SetMember(*member)
-					this.Member = member
+					c.SetMember(*member)
+					c.Member = member
 				}
 			}
 		}
 
 	}
-	if this.Member.RoleName == "" {
-		this.Member.ResolveRoleName()
+	if c.Member.RoleName == "" {
+		c.Member.ResolveRoleName()
 	}
-	this.Data["Member"] = this.Member
-	this.Data["BaseUrl"] = this.BaseUrl()
-	this.Data["IsSignedToday"] = false
-	if this.Member.MemberId > 0 {
-		this.Data["IsSignedToday"] = models.NewSign().IsSignToday(this.Member.MemberId)
+	c.Data["Member"] = c.Member
+	c.Data["BaseUrl"] = c.BaseUrl()
+	c.Data["IsSignedToday"] = false
+	if c.Member.MemberId > 0 {
+		c.Data["IsSignedToday"] = models.NewSign().IsSignToday(c.Member.MemberId)
 	}
 
 	if options, err := models.NewOption().All(); err == nil {
-		this.Option = make(map[string]string, len(options))
+		c.Option = make(map[string]string, len(options))
 		for _, item := range options {
 			if item.OptionName == "SITE_NAME" {
-				this.Sitename = item.OptionValue
+				c.Sitename = item.OptionValue
 			}
-			this.Data[item.OptionName] = item.OptionValue
-			this.Option[item.OptionName] = item.OptionValue
+			c.Data[item.OptionName] = item.OptionValue
+			c.Option[item.OptionName] = item.OptionValue
 			if strings.EqualFold(item.OptionName, "ENABLE_ANONYMOUS") && item.OptionValue == "true" {
-				this.EnableAnonymous = true
+				c.EnableAnonymous = true
 			}
 
 			if strings.EqualFold(item.OptionName, "ENABLED_REGISTER") && item.OptionValue == "false" {
-				this.AllowRegister = false
+				c.AllowRegister = false
 			}
 
 			if verNum, _ := strconv.Atoi(item.OptionValue); strings.EqualFold(item.OptionName, "ENABLE_DOCUMENT_HISTORY") && verNum > 0 {
-				this.EnableDocumentHistory = verNum
+				c.EnableDocumentHistory = verNum
 			}
 		}
 	}
 
-	if v, ok := this.Option["CLOSE_OPEN_SOURCE_LINK"]; ok {
-		this.Data["CloseOpenSourceLink"] = v == "true"
+	if v, ok := c.Option["CLOSE_OPEN_SOURCE_LINK"]; ok {
+		c.Data["CloseOpenSourceLink"] = v == "true"
 	}
 
-	if v, ok := this.Option["HIDE_TAG"]; ok {
-		this.Data["HideTag"] = v == "true"
+	if v, ok := c.Option["HIDE_TAG"]; ok {
+		c.Data["HideTag"] = v == "true"
 	}
 
-	if v, ok := this.Option["CLOSE_SUBMIT_ENTER"]; ok {
-		this.Data["CloseSubmitEnter"] = v == "true"
+	if v, ok := c.Option["CLOSE_SUBMIT_ENTER"]; ok {
+		c.Data["CloseSubmitEnter"] = v == "true"
 	}
 
-	this.Data["SiteName"] = this.Sitename
+	c.Data["SiteName"] = c.Sitename
 
 	// 默认显示创建书籍的入口
 	ShowCreateBookEntrance := false
 
-	if this.Member.MemberId > 0 {
+	if c.Member.MemberId > 0 {
 		ShowCreateBookEntrance = true
 		if opt, err := models.NewOption().FindByKey("ALL_CAN_WRITE_BOOK"); err == nil {
-			if opt.OptionValue == "false" && this.Member.Role == conf.MemberGeneralRole {
+			if opt.OptionValue == "false" && c.Member.Role == conf.MemberGeneralRole {
 				// 如果用户现在是普通用户，但是之前是作者或者之前有新建书籍书籍的权限并且创建了书籍，则也给用户显示入口
-				ShowCreateBookEntrance = models.NewRelationship().HasRelatedBook(this.Member.MemberId)
+				ShowCreateBookEntrance = models.NewRelationship().HasRelatedBook(c.Member.MemberId)
 			}
 		}
 	}
 
-	this.Data["ShowCreateBookEntrance"] = ShowCreateBookEntrance
+	c.Data["ShowCreateBookEntrance"] = ShowCreateBookEntrance
 
-	if this.Member.MemberId == 0 {
-		if this.EnableAnonymous == false && !this.NoNeedLoginRouter { // 不允许游客访问
+	if c.Member.MemberId == 0 {
+		if c.EnableAnonymous == false && !c.NoNeedLoginRouter { // 不允许游客访问
 			allowPaths := map[string]bool{
 				beego.URLFor("AccountController.Login"):        true,
 				beego.URLFor("AccountController.Logout"):       true,
 				beego.URLFor("AccountController.FindPassword"): true,
 				beego.URLFor("AccountController.ValidEmail"):   true,
 			}
-			if _, ok := allowPaths[this.Ctx.Request.URL.Path]; !ok {
-				this.Redirect(beego.URLFor("AccountController.Login"), 302)
+			if _, ok := allowPaths[c.Ctx.Request.URL.Path]; !ok {
+				c.Redirect(beego.URLFor("AccountController.Login"), 302)
 				return
 			}
 		}
 
-		if this.AllowRegister == false { // 不允许用户注册
+		if c.AllowRegister == false { // 不允许用户注册
 			denyPaths := map[string]bool{
 				// 第三方登录，如果是新注册的话，需要绑定信息，这里不让绑定信息就是不让注册
 				beego.URLFor("AccountController.Bind"): true,
 				// 禁止邮箱注册
 				beego.URLFor("AccountController.Oauth", ":oauth", "email"): true,
 			}
-			if _, ok := denyPaths[this.Ctx.Request.URL.Path]; ok {
-				this.Redirect("/login", 302)
+			if _, ok := denyPaths[c.Ctx.Request.URL.Path]; ok {
+				c.Redirect("/login", 302)
 				return
 			}
 		}
@@ -198,20 +246,20 @@ func (this *BaseController) Prepare() {
 }
 
 // SetMember 获取或设置当前登录用户信息,如果 MemberId 小于 0 则标识删除 Session
-func (this *BaseController) SetMember(member models.Member) {
+func (c *BaseController) SetMember(member models.Member) {
 
 	if member.MemberId <= 0 {
-		this.DelSession(conf.LoginSessionName)
-		this.DelSession("uid")
-		this.DestroySession()
+		c.DelSession(conf.LoginSessionName)
+		c.DelSession("uid")
+		c.DestroySession()
 	} else {
-		this.SetSession(conf.LoginSessionName, member)
-		this.SetSession("uid", member.MemberId)
+		c.SetSession(conf.LoginSessionName, member)
+		c.SetSession("uid", member.MemberId)
 	}
 }
 
 // JsonResult 响应 json 结果
-func (this *BaseController) JsonResult(errCode int, errMsg string, data ...interface{}) {
+func (c *BaseController) JsonResult(errCode int, errMsg string, data ...interface{}) {
 	jsonData := make(map[string]interface{}, 3)
 	jsonData["errcode"] = errCode
 	jsonData["message"] = errMsg
@@ -223,29 +271,29 @@ func (this *BaseController) JsonResult(errCode int, errMsg string, data ...inter
 	if err != nil {
 		beego.Error(err)
 	}
-	this.Ctx.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8")
+	c.Ctx.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8")
 	//this.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache, no-store")//解决回退出现json的问题
 	//使用gzip原始，json数据会只有原本数据的10分之一左右
-	if strings.Contains(strings.ToLower(this.Ctx.Request.Header.Get("Accept-Encoding")), "gzip") {
-		this.Ctx.ResponseWriter.Header().Set("Content-Encoding", "gzip")
+	if strings.Contains(strings.ToLower(c.Ctx.Request.Header.Get("Accept-Encoding")), "gzip") {
+		c.Ctx.ResponseWriter.Header().Set("Content-Encoding", "gzip")
 		//gzip压缩
-		w := gzip.NewWriter(this.Ctx.ResponseWriter)
+		w := gzip.NewWriter(c.Ctx.ResponseWriter)
 		defer w.Close()
 		w.Write(returnJSON)
 		w.Flush()
 	} else {
-		io.WriteString(this.Ctx.ResponseWriter, string(returnJSON))
+		io.WriteString(c.Ctx.ResponseWriter, string(returnJSON))
 	}
-	this.StopRun()
+	c.StopRun()
 }
 
 // ExecuteViewPathTemplate 执行指定的模板并返回执行结果.
-func (this *BaseController) ExecuteViewPathTemplate(tplName string, data interface{}) (string, error) {
+func (c *BaseController) ExecuteViewPathTemplate(tplName string, data interface{}) (string, error) {
 	var buf bytes.Buffer
 
-	viewPath := this.ViewPath
+	viewPath := c.ViewPath
 
-	if this.ViewPath == "" {
+	if c.ViewPath == "" {
 		viewPath = beego.BConfig.WebConfig.ViewsPath
 
 	}
@@ -256,33 +304,33 @@ func (this *BaseController) ExecuteViewPathTemplate(tplName string, data interfa
 	return buf.String(), nil
 }
 
-func (this *BaseController) BaseUrl() string {
+func (c *BaseController) BaseUrl() string {
 	host := beego.AppConfig.String("sitemap_host")
 	if len(host) > 0 {
 		if strings.HasPrefix(host, "http://") || strings.HasPrefix(host, "https://") {
 			return host
 		}
-		return this.Ctx.Input.Scheme() + "://" + host
+		return c.Ctx.Input.Scheme() + "://" + host
 	}
-	return this.Ctx.Input.Scheme() + "://" + this.Ctx.Request.Host
+	return c.Ctx.Input.Scheme() + "://" + c.Ctx.Request.Host
 }
 
 //显示错误信息页面.
-func (this *BaseController) ShowErrorPage(errCode int, errMsg string) {
-	this.TplName = "errors/error.html"
-	this.Data["ErrorMessage"] = errMsg
-	this.Data["ErrorCode"] = errCode
-	this.StopRun()
+func (c *BaseController) ShowErrorPage(errCode int, errMsg string) {
+	c.TplName = "errors/error.html"
+	c.Data["ErrorMessage"] = errMsg
+	c.Data["ErrorCode"] = errCode
+	c.StopRun()
 }
 
 //根据页面获取seo
 //@param			page			页面标识
 //@param			defSeo			默认的seo的map，必须有title、keywords和description字段
-func (this *BaseController) GetSeoByPage(page string, defSeo map[string]string) {
+func (c *BaseController) GetSeoByPage(page string, defSeo map[string]string) {
 	var seo models.Seo
 
 	orm.NewOrm().QueryTable(models.TableSeo).Filter("Page", page).One(&seo)
-	defSeo["sitename"] = this.Sitename
+	defSeo["sitename"] = c.Sitename
 	if seo.Id > 0 {
 		for k, v := range defSeo {
 			seo.Title = strings.Replace(seo.Title, fmt.Sprintf("{%v}", k), v, -1)
@@ -290,29 +338,29 @@ func (this *BaseController) GetSeoByPage(page string, defSeo map[string]string)
 			seo.Description = strings.Replace(seo.Description, fmt.Sprintf("{%v}", k), v, -1)
 		}
 	}
-	this.Data["SeoTitle"] = seo.Title
-	this.Data["SeoKeywords"] = seo.Keywords
-	this.Data["SeoDescription"] = seo.Description
+	c.Data["SeoTitle"] = seo.Title
+	c.Data["SeoKeywords"] = seo.Keywords
+	c.Data["SeoDescription"] = seo.Description
 }
 
 //站点地图
-func (this *BaseController) Sitemap() {
-	this.Data["SeoTitle"] = "站点地图 - " + this.Sitename
-	page, _ := this.GetInt("page")
+func (c *BaseController) Sitemap() {
+	c.Data["SeoTitle"] = "站点地图 - " + c.Sitename
+	page, _ := c.GetInt("page")
 	listRows := 100
 	totalCount, docs := models.SitemapData(page, listRows)
 	if totalCount > 0 {
-		html := utils.GetPagerHtml(this.Ctx.Request.RequestURI, page, listRows, int(totalCount))
-		this.Data["PageHtml"] = html
+		html := utils.GetPagerHtml(c.Ctx.Request.RequestURI, page, listRows, int(totalCount))
+		c.Data["PageHtml"] = html
 	} else {
-		this.Data["PageHtml"] = ""
+		c.Data["PageHtml"] = ""
 	}
 	//this.JsonResult(0, "aaa", docs)
-	this.Data["Docs"] = docs
-	this.TplName = "widgets/sitemap.html"
+	c.Data["Docs"] = docs
+	c.TplName = "widgets/sitemap.html"
 }
 
-func (this *BaseController) loginByMemberId(memberId int) (err error) {
+func (c *BaseController) loginByMemberId(memberId int) (err error) {
 	member, err := models.NewMember().Find(memberId)
 	if member.MemberId == 0 {
 		return errors.New("用户不存在")
@@ -323,20 +371,20 @@ func (this *BaseController) loginByMemberId(memberId int) (err error) {
 	}
 	member.LastLoginTime = time.Now()
 	member.Update()
-	this.SetMember(*member)
+	c.SetMember(*member)
 	var remember CookieRemember
 	remember.MemberId = member.MemberId
 	remember.Account = member.Account
 	remember.Time = time.Now()
 	v, err := utils.Encode(remember)
 	if err == nil {
-		this.SetSecureCookie(conf.GetAppKey(), "login", v, 24*3600*365)
+		c.SetSecureCookie(conf.GetAppKey(), "login", v, 24*3600*365)
 	}
 	return err
 }
 
 //在markdown头部加上<bookstack></bookstack>或者<bookstack/>，即解析markdown中的ul>li>a链接作为目录
-func (this *BaseController) sortBySummary(bookIdentify, htmlStr string, bookId int) string {
+func (c *BaseController) sortBySummary(bookIdentify, htmlStr string, bookId int) string {
 	debug := beego.AppConfig.String("runmod") != "prod"
 	o := orm.NewOrm()
 	qs := o.QueryTable("md_documents").Filter("book_id", bookId)
@@ -450,7 +498,7 @@ func (this *BaseController) sortBySummary(bookIdentify, htmlStr string, bookId i
 
 	htmlStr, _ = doc.Find("body").Html()
 	if len(hrefs) > 0 { //如果有新创建的文档，则再调用一遍，用于处理排序
-		htmlStr = this.replaceLinks(bookIdentify, htmlStr, true)
+		htmlStr = c.replaceLinks(bookIdentify, htmlStr, true)
 	}
 	return htmlStr
 }
@@ -465,7 +513,7 @@ type Sort struct {
 
 //替换链接
 //如果是summary，则根据这个进行排序调整
-func (this *BaseController) replaceLinks(bookIdentify string, docHtml string, isSummary ...bool) string {
+func (c *BaseController) replaceLinks(bookIdentify string, docHtml string, isSummary ...bool) string {
 	var (
 		book models.Book
 		docs []models.Document
@@ -510,7 +558,7 @@ func (this *BaseController) replaceLinks(bookIdentify string, docHtml string, is
 				if newHtml, err := gq.Find("body").Html(); err == nil {
 					docHtml = newHtml
 					if len(isSummary) > 0 && isSummary[0] == true { //更新排序
-						docHtml = this.sortBySummary(bookIdentify, docHtml, book.BookId) //更新排序
+						docHtml = c.sortBySummary(bookIdentify, docHtml, book.BookId) //更新排序
 					}
 				}
 			} else {
@@ -522,58 +570,58 @@ func (this *BaseController) replaceLinks(bookIdentify string, docHtml string, is
 }
 
 //内容采集
-func (this *BaseController) Crawl() {
-	if this.Member.MemberId > 0 {
-		if val, ok := this.GetSession("crawl").(string); ok && val == "1" {
-			this.JsonResult(1, "您提交的上一次采集未完成，请稍后再提交新的内容采集")
+func (c *BaseController) Crawl() {
+	if c.Member.MemberId > 0 {
+		if val, ok := c.GetSession("crawl").(string); ok && val == "1" {
+			c.JsonResult(1, "您提交的上一次采集未完成，请稍后再提交新的内容采集")
 		}
-		this.SetSession("crawl", "1")
-		defer this.DelSession("crawl")
-		urlStr := this.GetString("url")
-		force, _ := this.GetBool("force")              //是否是强力采集，强力采集，使用Chrome
-		intelligence, _ := this.GetInt("intelligence") //是否是强力采集，强力采集，使用Chrome
-		contType, _ := this.GetInt("type")
-		diySel := this.GetString("diy")
+		c.SetSession("crawl", "1")
+		defer c.DelSession("crawl")
+		urlStr := c.GetString("url")
+		force, _ := c.GetBool("force")              //是否是强力采集，强力采集，使用Chrome
+		intelligence, _ := c.GetInt("intelligence") //是否是强力采集，强力采集，使用Chrome
+		contType, _ := c.GetInt("type")
+		diySel := c.GetString("diy")
 		content, err := utils.CrawlHtml2Markdown(urlStr, contType, force, intelligence, diySel, []string{}, nil)
 		if err != nil {
-			this.JsonResult(1, "采集失败："+err.Error())
+			c.JsonResult(1, "采集失败："+err.Error())
 		}
-		this.JsonResult(0, "采集成功", content)
+		c.JsonResult(0, "采集成功", content)
 	}
-	this.JsonResult(1, "请先登录再操作")
+	c.JsonResult(1, "请先登录再操作")
 }
 
 //关注或取消关注
-func (this *BaseController) SetFollow() {
+func (c *BaseController) SetFollow() {
 	var cancel bool
-	if this.Member == nil || this.Member.MemberId == 0 {
-		this.JsonResult(1, "请先登录")
+	if c.Member == nil || c.Member.MemberId == 0 {
+		c.JsonResult(1, "请先登录")
 	}
-	uid, _ := this.GetInt(":uid")
-	if uid == this.Member.MemberId {
-		this.JsonResult(1, "自己不能关注自己")
+	uid, _ := c.GetInt(":uid")
+	if uid == c.Member.MemberId {
+		c.JsonResult(1, "自己不能关注自己")
 	}
-	cancel, _ = new(models.Fans).FollowOrCancel(uid, this.Member.MemberId)
+	cancel, _ = new(models.Fans).FollowOrCancel(uid, c.Member.MemberId)
 	if cancel {
-		this.JsonResult(0, "您已经成功取消了关注")
+		c.JsonResult(0, "您已经成功取消了关注")
 	}
-	this.JsonResult(0, "您已经成功关注了Ta")
+	c.JsonResult(0, "您已经成功关注了Ta")
 }
 
-func (this *BaseController) SignToday() {
-	if this.Member == nil || this.Member.MemberId == 0 {
-		this.JsonResult(1, "请先登录")
+func (c *BaseController) SignToday() {
+	if c.Member == nil || c.Member.MemberId == 0 {
+		c.JsonResult(1, "请先登录")
 	}
-	reward, err := models.NewSign().Sign(this.Member.MemberId, false)
+	reward, err := models.NewSign().Sign(c.Member.MemberId, false)
 	if err != nil {
-		this.JsonResult(1, "签到失败："+err.Error())
+		c.JsonResult(1, "签到失败："+err.Error())
 	}
-	this.JsonResult(0, fmt.Sprintf("恭喜您，签到成功,奖励阅读时长 %v 秒", reward))
+	c.JsonResult(0, fmt.Sprintf("恭喜您，签到成功,奖励阅读时长 %v 秒", reward))
 }
 
-func (this *BaseController) forbidGeneralRole() bool {
+func (c *BaseController) forbidGeneralRole() bool {
 	// 如果只有作者和管理员才能写作的话，那么已创建了书籍的普通用户无法将书籍转为公开或者是私密分享
-	if this.Member.Role == conf.MemberGeneralRole && models.GetOptionValue("ALL_CAN_WRITE_BOOK", "true") != "true" {
+	if c.Member.Role == conf.MemberGeneralRole && models.GetOptionValue("ALL_CAN_WRITE_BOOK", "true") != "true" {
 		return true
 	}
 	return false
diff --git a/controllers/SettingController.go b/controllers/SettingController.go
index df3583f5..724517e7 100644
--- a/controllers/SettingController.go
+++ b/controllers/SettingController.go
@@ -3,21 +3,18 @@ package controllers
 import (
 	"os"
 	"path/filepath"
-	"strconv"
 	"strings"
 	"time"
 
-	"github.com/TruthHun/BookStack/graphics"
-	"github.com/TruthHun/BookStack/models/store"
-
 	"fmt"
 
 	"github.com/TruthHun/BookStack/conf"
 	"github.com/TruthHun/BookStack/models"
+	"github.com/TruthHun/BookStack/models/store"
 	"github.com/TruthHun/BookStack/utils"
 	"github.com/astaxie/beego"
-	"github.com/astaxie/beego/logs"
 	"github.com/astaxie/beego/orm"
+	"github.com/casdoor/casdoor-go-sdk/auth"
 )
 
 type SettingController struct {
@@ -26,91 +23,14 @@ type SettingController struct {
 
 //基本信息
 func (this *SettingController) Index() {
-	if this.Ctx.Input.IsPost() {
-		email := strings.TrimSpace(this.GetString("email", ""))
-		phone := strings.TrimSpace(this.GetString("phone"))
-		wechatNO := strings.TrimSpace(this.GetString("wechat_no"))
-		description := strings.TrimSpace(this.GetString("description"))
-		nickname := strings.TrimSpace(this.GetString("nickname"))
-		if email == "" {
-			this.JsonResult(601, "邮箱不能为空")
-		}
-
-		if l := strings.Count(nickname, "") - 1; l < 2 || l > 20 {
-			this.JsonResult(6004, "用户昵称限制在2-20个字符")
-		}
-
-		existMember := models.NewMember().FindByNickname(nickname, "member_id")
-		if existMember.MemberId > 0 && this.Member.MemberId != existMember.MemberId {
-			this.JsonResult(6004, "用户昵称已存在，请换一个")
-		}
-
-		member := this.Member
-		member.Email = email
-		member.Phone = phone
-		member.WechatNO = wechatNO
-		member.Description = description
-		if err := member.Update(); err != nil {
-			this.JsonResult(602, err.Error())
-		}
-		this.SetMember(*member)
-		this.JsonResult(0, "ok")
-	}
-	this.Data["SeoTitle"] = "基本信息 - " + this.Sitename
-	this.Data["SettingBasic"] = true
-	this.TplName = "setting/index.html"
-}
-
-//修改密码
-func (this *SettingController) Password() {
-
-	if this.Ctx.Input.IsPost() {
-		if this.Member.AuthMethod == conf.AuthMethodLDAP {
-			this.JsonResult(6009, "当前用户不支持修改密码")
-		}
-		password1 := this.GetString("password1")
-		password2 := this.GetString("password2")
-		password3 := this.GetString("password3")
-		if password1 == "" {
-			this.JsonResult(6003, "原密码不能为空")
-		}
-
-		if password2 == "" {
-			this.JsonResult(6004, "新密码不能为空")
-		}
-
-		if count := strings.Count(password2, ""); count < 6 || count > 18 {
-			this.JsonResult(6009, "密码必须在6-18字之间")
-		}
-
-		if password2 != password3 {
-			this.JsonResult(6003, "确认密码不正确")
-		}
-
-		if ok, _ := utils.PasswordVerify(this.Member.Password, password1); !ok {
-			this.JsonResult(6005, "原始密码不正确")
-		}
-
-		if password1 == password2 {
-			this.JsonResult(6006, "新密码不能和原始密码相同")
-		}
-
-		pwd, err := utils.PasswordHash(password2)
-		if err != nil {
-			this.JsonResult(6007, "密码加密失败")
-		}
-
-		this.Member.Password = pwd
-		if err := this.Member.Update(); err != nil {
-			this.JsonResult(6008, err.Error())
-		}
-
-		this.JsonResult(0, "ok")
+	this.SetSession("isUpdateUser", 1)
+	account := this.GetSessionClaims()
+	if account == nil {
+		this.Redirect(beego.URLFor("AccountController.Login"), 302)
+	} else {
+		myProfileUrl := auth.GetMyProfileUrl(account.AccessToken)
+		this.Redirect(myProfileUrl, 302)
 	}
-
-	this.Data["SettingPwd"] = true
-	this.Data["SeoTitle"] = "修改密码 - " + this.Sitename
-	this.TplName = "setting/password.html"
 }
 
 //收藏
@@ -225,100 +145,3 @@ func (this *SettingController) Qrcode() {
 	this.Data["Qrcode"] = new(models.Member).GetQrcodeByUid(this.Member.MemberId)
 	this.Data["SettingQrcode"] = true
 }
-
-// Upload 上传图片
-func (this *SettingController) Upload() {
-
-	file, moreFile, err := this.GetFile("image-file")
-	if err != nil {
-		logs.Error("", err.Error())
-		this.JsonResult(500, "读取文件异常")
-	}
-	defer file.Close()
-
-	ext := filepath.Ext(moreFile.Filename)
-	if !strings.EqualFold(ext, ".png") && !strings.EqualFold(ext, ".jpg") && !strings.EqualFold(ext, ".gif") && !strings.EqualFold(ext, ".jpeg") {
-		this.JsonResult(500, "不支持的图片格式")
-	}
-
-	x1, _ := strconv.ParseFloat(this.GetString("x"), 10)
-	y1, _ := strconv.ParseFloat(this.GetString("y"), 10)
-	w1, _ := strconv.ParseFloat(this.GetString("width"), 10)
-	h1, _ := strconv.ParseFloat(this.GetString("height"), 10)
-
-	x := int(x1)
-	y := int(y1)
-	width := int(w1)
-	height := int(h1)
-
-	// fmt.Println(x, x1, y, y1)
-
-	fileName := strconv.FormatInt(time.Now().UnixNano(), 16)
-
-	filePath := filepath.Join("uploads", time.Now().Format("2006/01"), fileName+ext)
-
-	path := filepath.Dir(filePath)
-
-	os.MkdirAll(path, os.ModePerm)
-
-	err = this.SaveToFile("image-file", filePath)
-
-	if err != nil {
-		logs.Error("", err)
-		this.JsonResult(500, "图片保存失败")
-	}
-
-	//剪切图片
-	subImg, err := graphics.ImageCopyFromFile(filePath, x, y, width, height)
-
-	if err != nil {
-		logs.Error("ImageCopyFromFile => ", err)
-		this.JsonResult(6001, "头像剪切失败")
-	}
-	os.Remove(filePath)
-
-	filePath = filepath.Join("uploads", time.Now().Format("200601"), fileName+ext)
-
-	err = graphics.ImageResizeSaveFile(subImg, 120, 120, filePath)
-	err = graphics.SaveImage(filePath, subImg)
-
-	if err != nil {
-		logs.Error("保存文件失败 => ", err.Error())
-		this.JsonResult(500, "保存文件失败")
-	}
-
-	url := "/" + strings.Replace(filePath, "\\", "/", -1)
-	if strings.HasPrefix(url, "//") {
-		url = string(url[1:])
-	}
-
-	if member, err := models.NewMember().Find(this.Member.MemberId); err == nil {
-		avatar := member.Avatar
-		member.Avatar = url
-		err = member.Update()
-		if err != nil {
-			this.JsonResult(60001, "保存头像失败")
-		}
-		avatar = strings.TrimLeft(avatar, "./")
-		if strings.HasPrefix(avatar, "uploads/") {
-			os.Remove(avatar)
-		}
-		this.SetMember(*member)
-	}
-	switch utils.StoreType {
-	case utils.StoreOss: //oss存储
-		if err := store.ModelStoreOss.MoveToOss("."+url, strings.TrimLeft(url, "./"), true, false); err != nil {
-			beego.Error(err.Error())
-		} else {
-			url = strings.TrimRight(beego.AppConfig.String("oss::Domain"), "/ ") + url + "/avatar"
-		}
-	case utils.StoreLocal: //本地存储
-		if err := store.ModelStoreLocal.MoveToStore("."+url, strings.TrimLeft(url, "./")); err != nil {
-			beego.Error(err.Error())
-		} else {
-			url = "/" + strings.TrimLeft(url, "./")
-		}
-	}
-
-	this.JsonResult(0, "ok", url)
-}
diff --git a/controllers/UserController.go b/controllers/UserController.go
index 5d194d7d..1bca9902 100644
--- a/controllers/UserController.go
+++ b/controllers/UserController.go
@@ -49,6 +49,7 @@ func (this *UserController) Index() {
 	books, totalCount, _ := models.NewBook().FindToPager(page, pageSize, this.UcenterMember.MemberId, 0)
 	this.Data["Books"] = books
 
+	this.refreshUser()
 	if totalCount > 0 {
 		html := utils.NewPaginations(conf.RollPage, totalCount, pageSize, page, beego.URLFor("UserController.Index", ":username", this.UcenterMember.Account), "")
 		this.Data["PageHtml"] = html
diff --git a/controllers/token_jwt_key.pem b/controllers/token_jwt_key.pem
new file mode 100644
index 00000000..c2d6d5f5
--- /dev/null
+++ b/controllers/token_jwt_key.pem
@@ -0,0 +1,29 @@
+-----BEGIN CERTIFICATE-----
+MIIE+TCCAuGgAwIBAgIDAeJAMA0GCSqGSIb3DQEBCwUAMDYxHTAbBgNVBAoTFENh
+c2Rvb3IgT3JnYW5pemF0aW9uMRUwEwYDVQQDEwxDYXNkb29yIENlcnQwHhcNMjEx
+MDE1MDgxMTUyWhcNNDExMDE1MDgxMTUyWjA2MR0wGwYDVQQKExRDYXNkb29yIE9y
+Z2FuaXphdGlvbjEVMBMGA1UEAxMMQ2FzZG9vciBDZXJ0MIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEAsInpb5E1/ym0f1RfSDSSE8IR7y+lw+RJjI74e5ej
+rq4b8zMYk7HeHCyZr/hmNEwEVXnhXu1P0mBeQ5ypp/QGo8vgEmjAETNmzkI1NjOQ
+CjCYwUrasO/f/MnI1C0j13vx6mV1kHZjSrKsMhYY1vaxTEP3+VB8Hjg3MHFWrb07
+uvFMCJe5W8+0rKErZCKTR8+9VB3janeBz//zQePFVh79bFZate/hLirPK0Go9P1g
+OvwIoC1A3sarHTP4Qm/LQRt0rHqZFybdySpyWAQvhNaDFE7mTstRSBb/wUjNCUBD
+PTSLVjC04WllSf6Nkfx0Z7KvmbPstSj+btvcqsvRAGtvdsB9h62Kptjs1Yn7GAuo
+I3qt/4zoKbiURYxkQJXIvwCQsEftUuk5ew5zuPSlDRLoLByQTLbx0JqLAFNfW3g/
+pzSDjgd/60d6HTmvbZni4SmjdyFhXCDb1Kn7N+xTojnfaNkwep2REV+RMc0fx4Gu
+hRsnLsmkmUDeyIZ9aBL9oj11YEQfM2JZEq+RVtUx+wB4y8K/tD1bcY+IfnG5rBpw
+IDpS262boq4SRSvb3Z7bB0w4ZxvOfJ/1VLoRftjPbLIf0bhfr/AeZMHpIKOXvfz4
+yE+hqzi68wdF0VR9xYc/RbSAf7323OsjYnjjEgInUtRohnRgCpjIk/Mt2Kt84Kb0
+wn8CAwEAAaMQMA4wDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAn2lf
+DKkLX+F1vKRO/5gJ+Plr8P5NKuQkmwH97b8CS2gS1phDyNgIc4/LSdzuf4Awe6ve
+C06lVdWSIis8UPUPdjmT2uMPSNjwLxG3QsrimMURNwFlLTfRem/heJe0Zgur9J1M
+8haawdSdJjH2RgmFoDeE2r8NVRfhbR8KnCO1ddTJKuS1N0/irHz21W4jt4rxzCvl
+2nR42Fybap3O/g2JXMhNNROwZmNjgpsF7XVENCSuFO1jTywLaqjuXCg54IL7XVLG
+omKNNNcc8h1FCeKj/nnbGMhodnFWKDTsJcbNmcOPNHo6ixzqMy/Hqc+mWYv7maAG
+Jtevs3qgMZ8F9Qzr3HpUc6R3ZYYWDY/xxPisuKftOPZgtH979XC4mdf0WPnOBLqL
+2DJ1zaBmjiGJolvb7XNVKcUfDXYw85ZTZQ5b9clI4e+6bmyWqQItlwt+Ati/uFEV
+XzCj70B4lALX6xau1kLEpV9O1GERizYRz5P9NJNA7KoO5AVMp9w0DQTkt+LbXnZE
+HHnWKy8xHQKZF9sR7YBPGLs/Ac6tviv5Ua15OgJ/8dLRZ/veyFfGo2yZsI+hKVU5
+nCCJHBcAyFnm1hdvdwEdH33jDBjNB6ciotJZrf/3VYaIWSalADosHAgMWfXuWP+h
+8XKXmzlxuHbTMQYtZPDgspS5aK+S4Q9wb8RRAYo=
+-----END CERTIFICATE-----
diff --git a/go.mod b/go.mod
index 72a3f719..3b8e73eb 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/TruthHun/BookStack
 
-go 1.13
+go 1.16
 
 replace github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 => github.com/ugorji/go v1.1.7
 
@@ -17,6 +17,7 @@ require (
 	github.com/astaxie/beego v1.12.0
 	github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
 	github.com/boombuler/barcode v1.0.0
+	github.com/casdoor/casdoor-go-sdk v0.4.1
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/disintegration/imaging v1.6.1
 	github.com/go-sql-driver/mysql v1.5.0
@@ -31,7 +32,6 @@ require (
 	github.com/stretchr/testify v1.6.1 // indirect
 	github.com/unknwon/com v1.0.1
 	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
-	golang.org/x/image v0.0.0-20190802002840-cff245a6509b // indirect
 	golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
 	golang.org/x/sys v0.0.0-20201223074533-0d417f636930 // indirect
 	golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect
diff --git a/go.sum b/go.sum
index 985cffae..fa20a549 100644
--- a/go.sum
+++ b/go.sum
@@ -1,15 +1,45 @@
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM=
-github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk=
 github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
 github.com/PuerkitoBio/goquery v1.6.0 h1:j7taAbelrdcsOlGeMenZxc2AWXD5fieT1/znArdnx94=
 github.com/PuerkitoBio/goquery v1.6.0/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
 github.com/TruthHun/converter v0.0.0-20180618131031-80268d67d7a8 h1:E+006K+eHyJkdvR1IPv8jFGA9zvuK47OxlNKoDxMvWM=
 github.com/TruthHun/converter v0.0.0-20180618131031-80268d67d7a8/go.mod h1:bW/ArSdu0OB6XR3NcPdPBIEeGdQ1GLW9fQavW3Auy2A=
-github.com/TruthHun/gotil v0.0.0-20191003091818-17b80aad8a45 h1:o+hC+QvRJlpj6meIX4AC/LKi7UxdEWFs+6zOsLzGoBs=
-github.com/TruthHun/gotil v0.0.0-20191003091818-17b80aad8a45/go.mod h1:0V5BlPJa24R0PzuXVURUfjaQ8Z7cmqsVSW7ftuhesN0=
 github.com/TruthHun/gotil v0.0.0-20210817134928-d2d642c3c7b7 h1:TL0LZ/cmjfX0sEsygVruVoJNGR9krd8UiTPlSg4nT+o=
 github.com/TruthHun/gotil v0.0.0-20210817134928-d2d642c3c7b7/go.mod h1:i5tqOvH+rsm0voXHnKkzayNkWnv8L/F7wVv+mBdrAL0=
 github.com/TruthHun/html2article v0.0.0-20180202140721-67d6ff09647b h1:JXD7vWKiJGRqkRuitwA8BxJVGr1yp2hQULQII7RaAXo=
@@ -20,9 +50,7 @@ github.com/alexcesaro/mail v0.0.0-20141015155039-29068ce49a17 h1:S0JNYJ6Gv0RGmbz
 github.com/alexcesaro/mail v0.0.0-20141015155039-29068ce49a17/go.mod h1:WVybKoXJAI3a3TlXlkzIMWs3SmoF29WtmOgInQpL0Ps=
 github.com/aliyun/aliyun-oss-go-sdk v2.1.0+incompatible h1:90Z2Cp7EqcbaYfVwVjmQoK8kgoFPz+doQlujcwe1BRg=
 github.com/aliyun/aliyun-oss-go-sdk v2.1.0+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
-github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
 github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
-github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
 github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
 github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE=
 github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
@@ -41,7 +69,15 @@ github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDd
 github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
 github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
+github.com/casdoor/casdoor-go-sdk v0.4.1 h1:rCvfmMlxLqSx05kFkDGJ6xh5w4/+mSKny8HA/8JA8Js=
+github.com/casdoor/casdoor-go-sdk v0.4.1/go.mod h1:MBed3ISHQfXTtoOCAk5T8l5lt4wFvsyynrw0awggydY=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -60,47 +96,96 @@ github.com/disintegration/imaging v1.6.1/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7
 github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
 github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/gin-contrib/cors v1.3.0 h1:PolezCc89peu+NgkIWt9OB01Kbzt6IP0J/JvkG6xxlg=
 github.com/gin-contrib/cors v1.3.0/go.mod h1:artPvLlhkF7oG06nK8v3U8TNz6IeX+w1uzCSEId5/Vc=
-github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc=
 github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
 github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
-github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
 github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
-github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
 github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
-github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
+github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/kardianos/service v1.0.0 h1:HgQS3mFfOlyntWX8Oke98JcJLqt1DBcHR4kxShpYef0=
 github.com/kardianos/service v1.0.0/go.mod h1:8CzDhVuCuugtsHyZoTvsOBuvonN/UDBvl0kH+BUxvbo=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -110,29 +195,25 @@ github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lifei6671/gocaptcha v0.0.0-20190301083731-c467a25bc100 h1:DkXBDLKHcTtca65BA9tZoFdWMZ9YJtNm5YyC36tKX2U=
 github.com/lifei6671/gocaptcha v0.0.0-20190301083731-c467a25bc100/go.mod h1:jEGhhJ8Op4YifySgvIySNtK5vxJta0V8nwwu+1uoVmE=
-github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
 github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/mssola/user_agent v0.5.0 h1:gRF7/x8cKt8qzAosYGsBNyirta+F8fvYDlJrgXws9AQ=
 github.com/mssola/user_agent v0.5.0/go.mod h1:UFiKPVaShrJGW93n4uo8dpPdg1BSVpw2P9bneo0Mtp8=
 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
-github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk=
 github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -148,8 +229,10 @@ github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBf
 github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
 github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
 github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
@@ -161,6 +244,8 @@ github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXc
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
@@ -171,51 +256,276 @@ github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
 github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
 github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
 golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
 golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
 golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
 gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
 gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
 gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU=
@@ -224,4 +534,15 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/models/member.go b/models/member.go
index e19b9a5c..a831821a 100644
--- a/models/member.go
+++ b/models/member.go
@@ -169,10 +169,6 @@ func (m *Member) Add() error {
 		return errors.New("邮箱格式不正确")
 	}
 
-	if l := strings.Count(m.Password, ""); l < 7 || l >= 50 {
-		return errors.New("密码不能为空且必须在6-50个字符之间")
-	}
-
 	cond := orm.NewCondition().Or("email", m.Email).Or("nickname", m.Nickname).Or("account", m.Account)
 	var one Member
 	if o.QueryTable(m.TableNameWithPrefix()).SetCond(cond).One(&one, "member_id", "nickname", "account", "email"); one.MemberId > 0 {
@@ -217,9 +213,6 @@ func (m *Member) Add() error {
 func (m *Member) Update(cols ...string) error {
 	o := orm.NewOrm()
 
-	if m.Email == "" {
-		return errors.New("邮箱不能为空")
-	}
 	if _, err := o.Update(m, cols...); err != nil {
 		return err
 	}
diff --git a/routers/web.go b/routers/web.go
index 5163d404..2bd8b212 100644
--- a/routers/web.go
+++ b/routers/web.go
@@ -16,13 +16,9 @@ func webRouter() {
 	beego.Router("/submit", &controllers.SubmitController{}, "post:Post")
 
 	beego.Router("/login", &controllers.AccountController{}, "*:Login")
-	beego.Router("/login/:oauth", &controllers.AccountController{}, "*:Oauth")
+	beego.Router("/signup", &controllers.AccountController{}, "*:Signup")
+	beego.Router("/login/callback", &controllers.AccountController{}, "*:Callback")
 	beego.Router("/logout", &controllers.AccountController{}, "*:Logout")
-	beego.Router("/bind", &controllers.AccountController{}, "post:Bind")
-	beego.Router("/note", &controllers.AccountController{}, "get,post:Note")
-	beego.Router("/find_password", &controllers.AccountController{}, "*:FindPassword")
-	beego.Router("/valid_email", &controllers.AccountController{}, "post:ValidEmail")
-	//beego.Router("/captcha", &controllers.AccountController{}, "*:Captcha")
 
 	beego.Router("/manager", &controllers.ManagerController{}, "*:Index")
 	beego.Router("/manager/users", &controllers.ManagerController{}, "*:Users")
@@ -73,8 +69,6 @@ func webRouter() {
 	beego.Router("/manager/del-tags", &controllers.ManagerController{}, "get:DelTags")
 
 	beego.Router("/setting", &controllers.SettingController{}, "*:Index")
-	beego.Router("/setting/password", &controllers.SettingController{}, "*:Password")
-	beego.Router("/setting/upload", &controllers.SettingController{}, "*:Upload")
 	beego.Router("/setting/star", &controllers.SettingController{}, "*:Star")
 	beego.Router("/setting/qrcode", &controllers.SettingController{}, "*:Qrcode")
 
diff --git a/views/account/bind.html b/views/account/bind.html
deleted file mode 100644
index 17374ba8..00000000
--- a/views/account/bind.html
+++ /dev/null
@@ -1,135 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh-cn">
-<head>
-    {{template "widgets/head.html" .}}
-        <style>
-            h3{font-weight: normal;margin-bottom: 20px;font-size: 20px;margin-top: 15px;}
-            .bind-options label{font-weight: normal;margin-right: 8px;cursor: pointer;}
-        </style>
-</head>
-<body class="manual-container">
-<header class="navbar navbar-static-top smart-nav navbar-fixed-top manual-header" role="banner">
-    <div class="container">
-        <div class="navbar-header col-sm-12 col-md-6 col-lg-5">
-            <a href="/" class="navbar-brand" title="{{.SITE_NAME}}">
-                <img class="logo" src="/static/images/logo.png" alt="{{.SITE_NAME}}">
-            </a>
-        </div>
-    </div>
-</header>
-<div class="container manual-body">
-    <div class="row login">
-        <div class="col-xs-12">
-                <div class="login-body">
-                        <form role="form" class="ajax-form" data-url="{{urlfor "HomeController.Index"}}" action="{{urlfor "AccountController.Bind"}}" method="post">
-                            <h3>{{if .IsEmail}}用户注册{{else}}完善资料{{end}} <small class="pull-right text-muted">{{.Tips}}</small></h3>
-                            <div class="form-group">
-                                <div class="input-group">
-                                    <div class="input-group-addon">&nbsp;用 户 名</div>
-                                    <input type="text" class="form-control" name="account" placeholder="由英文字母数字组成，限3-50个字符" id="account" value="{{.Username}}" autocomplete="off">
-                                </div>
-                            </div>
-                            <div class="form-group">
-                                <div class="input-group">
-                                    <div class="input-group-addon">登录密码</div>
-                                    <input type="password" class="form-control" placeholder="密码必须在6-50个字符之间" name="password1" id="password1" autocomplete="off">
-                                </div>
-                            </div>
-                            <div class="form-group has-hidden">
-                                <div class="input-group">
-                                    <div class="input-group-addon">确认密码</div>
-                                    <input type="password" class="form-control" placeholder="密码必须在6-50个字符之间" name="password2" id="password2" autocomplete="off">
-                                </div>
-                            </div>
-                            <div class="form-group has-hidden">
-                                <div class="input-group">
-                                    <div class="input-group-addon">用户昵称</div>
-                                    <input type="text" class="form-control" name="nickname" id="nickname" value="{{.Nickname}}" autocomplete="off">
-                                </div>
-                            </div>
-                            <div class="form-group has-hidden">
-                                <div class="input-group">
-                                    <div class="input-group-addon">用户邮箱</div>
-                                    <input type="email" class="form-control" name="email" id="email" value="{{.Email}}" autocomplete="off">
-                                </div>
-                            </div>
-                            <input type="hidden" name="avatar" value="{{.Avatar}}">
-                            <input type="hidden" name="id" value="{{.Id}}">
-                            <input type="hidden" name="oauth" value="{{.AuthType}}">
-                            {{if eq .IsEmail false}}
-                                <div class="form-group bind-options">
-                                    <label><input type="radio" name="isbind" checked value="0"> 注册新账号</label>
-                                    <label><input type="radio" name="isbind" value="1"> 绑定已有账号</label>
-                                </div>
-                            {{else}}
-                                {{if .CaptchaOn}}
-                                    <div class="form-group">
-                                        <div class="input-group">
-                                            <div class="input-group-addon">
-                                                &nbsp;验 证 码&nbsp;
-                                            </div>
-                                            <input type="text" class="form-control" placeholder="验证码" name="captcha" autocomplete="off">
-                                        </div>
-                                    </div>
-                                {{create_captcha}}
-                                {{end}}
-                            {{end}}
-                            <div class="help-block text-danger">温馨提示：用户名注册之后不可再修改</div>
-                            <div class="form-group mgt-15px">
-                                <button type="submit" class="btn btn-success btn-block" data-has="绑定已有账号" data-not="注册新账号">注册新账号</button>
-                            </div>
-
-                            <div class="help-block">
-                                 {{if $.OauthLogin}}使用以下方式一键登录{{end}}
-                                 <span class="pull-right"> 已有账号？ <a href="{{urlfor "AccountController.Login"}}" title="立即登录" class="tooltips text-primary">立即登录</a></span>
-                            </div>
-                            <div class="form-group login-by-third">
-                                {{if $.LoginQQ}}
-                                <a class="tooltips" href="https://graph.qq.com/oauth2.0/authorize?client_id={{.QQClientId}}&redirect_uri={{.QQCallback}}&response_type=code&state=bookstack.cn{{.RandomStr}}" rel="nofollow" title="使用QQ一键登录">
-                                    <img src="/static/images/qq.png" alt="QQ">
-                                </a>
-                                {{end}}
-                                {{if $.LoginGitHub}}
-                                <a class="tooltips" href="https://github.com/login/oauth/authorize?client_id={{.GithubClientId}}&redirect_uri={{.GithubCallback}}" rel="nofollow" title="使用GitHub一键登录">
-                                    <img src="/static/images/github.png" alt="GitHub">
-                                </a>
-                                {{end}}
-                                {{if $.LoginGitee}}
-                                <a class="tooltips" rel="nofollow" title="使用码云(Gitee)一键登录" href="https://gitee.com/oauth/authorize?client_id={{.GiteeClientId}}&redirect_uri={{.GiteeCallback}}&response_type=code">
-                                    <img src="/static/images/gitee.png" alt="码云(Gitee)">
-                                </a>
-                                {{end}}
-                            </div>
-                        </form>
-                    </div>
-        </div>
-        
-    </div>
-    <div class="clearfix"></div>
-</div>
-{{/*<script src="/static/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>*/}}
-<script src="{{$.StaticDomain}}/static/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>
-{{/*<script src="/static/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>*/}}
-<script src="{{$.StaticDomain}}/static/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
-<script src="{{$.StaticDomain}}/static/js/toast.script.js" type="text/javascript"></script>
-<script src="/static/js/main.js?version={{$.Version}}" type="text/javascript"></script>
-{{/*js直接写在这里了*/}}
-<script>$(function () {
-    $(".bind-options label").click(function () {
-        var options=$(this);
-        $.each(options,function () {
-            if($(this).find("input").prop("checked")==true){
-                var btnSubmit=$("form [type=submit]");
-                if ($(this).find("input").val()==1){//绑定已有账号
-                    $(".has-hidden").hide();
-                    btnSubmit.text(btnSubmit.attr("data-has"));
-                }else{//注册新账号
-                    $(".has-hidden").show();
-                    btnSubmit.text(btnSubmit.attr("data-not"));
-                }
-            }
-        })
-    })
-})</script>
-</body>
-</html>
\ No newline at end of file
diff --git a/views/account/find_password_setp1.html b/views/account/find_password_setp1.html
deleted file mode 100644
index b8978701..00000000
--- a/views/account/find_password_setp1.html
+++ /dev/null
@@ -1,112 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh-cn">
-<head>
-    {{template "widgets/head.html" .}}
-</head>
-<body class="manual-container">
-<header class="navbar navbar-static-top smart-nav navbar-fixed-top manual-header" role="banner">
-    <div class="container">
-        <div class="navbar-header col-sm-12 col-md-6 col-lg-5">
-            <a href="/" class="navbar-brand">
-                <img class="logo" src="/static/images/logo.png" alt="{{.SITE_NAME}}">
-            </a>
-        </div>
-    </div>
-</header>
-<div class="container manual-body">
-    <div class="row login">
-        <div class="login-body">
-            <form role="form" method="post" id="findPasswordForm">
-                <h3 class="text-center">找回密码</h3>
-                <div class="form-group">
-                    <div class="input-group">
-                        <div class="input-group-addon">
-                            <i class="fa fa-at"></i>
-                        </div>
-                        <input type="text" class="form-control" placeholder="邮箱" name="email" id="email" autocomplete="off">
-                    </div>
-                </div>
-                <div class="form-group">
-                    <div class="input-group" style="float: left;width: 195px;">
-                        <div class="input-group-addon">
-                            <i class="fa fa-check-square"></i>
-                        </div>
-                        <input type="text" name="captcha" id="code" class="form-control" style="width: 150px" maxlength="6" placeholder="验证码" autocomplete="off">&nbsp;
-                    </div>
-                    {{create_captcha}}
-                    <div class="clearfix"></div>
-                </div>
-
-                <div class="form-group">
-                    <button type="submit" id="btnSendMail" class="btn btn-success" style="width: 100%"  data-loading-text="正在处理..." autocomplete="off">找回密码</button>
-                </div>
-
-            </form>
-        </div>
-    </div>
-    <div class="clearfix"></div>
-</div>
-{{template "widgets/footer.html" .}}
-<!-- Include all compiled plugins (below), or include individual files as needed -->
-<script src="{{$.StaticDomain}}/static/layer/layer.js" type="text/javascript"></script>
-<script src="{{$.StaticDomain}}/static/js/jquery.form.js" type="text/javascript"></script>
-<script type="text/javascript">
-    $(function () {
-        $("#email,#code").on('focus',function () {
-            $(this).tooltip('destroy').parents('.form-group').removeClass('has-error');;
-        });
-
-        $(document).keydown(function (e) {
-            var event = document.all ? window.event : e;
-            if(event.keyCode == 13){
-                $("#btn-login").click();
-            }
-        });
-
-        $("#findPasswordForm").ajaxForm({
-            beforeSubmit : function () {
-                var $btn = $(this).button('loading');
-
-                var email = $.trim($("#email").val());
-                if(email === ""){
-                    $("#email").tooltip({placement:"auto",title : "邮箱不能为空",trigger : 'manual'})
-                        .tooltip('show')
-                        .parents('.form-group').addClass('has-error');
-                    $btn.button('reset');
-                    return false;
-
-                }
-                var code = $.trim($("#code").val());
-                if(code === ""){
-                    $("#code").tooltip({title : '验证码不能为空',trigger : 'manual'})
-                        .tooltip('show')
-                        .parents('.form-group').addClass('has-error');
-                    $btn.button('reset');
-                    return false;
-                }
-                $("#btnSendMail").button("loading");
-            },
-            success : function (res) {
-
-                if(res.errcode !== 0){
-                    $("#captcha-img").click();
-                    $("#code").val('');
-                    layer.msg(res.message);
-                    $("#btnSendMail").button('reset');
-                }else{
-                    alert("邮件发送成功，请登录邮箱查看。")
-                    window.location = res.data;
-                }
-            },
-            error :function () {
-                $("#captcha-img").click();
-                $("#code").val('');
-                layer.msg('系统错误');
-                $("#btnSendMail").button('reset');
-            }
-        });
-
-    });
-</script>
-</body>
-</html>
\ No newline at end of file
diff --git a/views/account/find_password_setp2.html b/views/account/find_password_setp2.html
deleted file mode 100644
index 4ae2360d..00000000
--- a/views/account/find_password_setp2.html
+++ /dev/null
@@ -1,125 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh-cn">
-<head>
-{{template "widgets/head.html" .}}
-</head>
-<body class="manual-container">
-<header class="navbar navbar-static-top smart-nav navbar-fixed-top manual-header" role="banner">
-    <div class="container">
-        <div class="navbar-header col-sm-12 col-md-6 col-lg-5">
-            <a href="/" class="navbar-brand" title="{{.SITE_NAME}}">
-                <img class="logo" src="/static/images/logo.png" alt="{{.SITE_NAME}}">
-            </a>
-        </div>
-    </div>
-</header>
-<div class="container manual-body">
-    <div class="row login">
-        <div class="login-body">
-            <form role="form" method="post" id="findPasswordForm" action="{{urlfor "AccountController.ValidEmail"}}">
-                <input type="hidden" name="token" value="{{.Token}}">
-                <input type="hidden" name="mail" value="{{.Email}}">
-                <h3 class="text-center">找回密码</h3>
-                <div class="form-group">
-                    <label for="newPasswd">新密码</label>
-                    <input type="password" class="form-control" name="password1" id="newPassword" maxlength="20" placeholder="新密码"  autocomplete="off">
-                </div>
-                <div class="form-group">
-                    <label for="configPasswd">确认密码</label>
-                    <input type="password" class="form-control" id="confirmPassword" name="password2" maxlength="20" placeholder="确认密码"  autocomplete="off">
-                </div>
-
-                <div class="form-group">
-                    <div class="input-group" style="float: left;width: 195px;">
-                        <div class="input-group-addon">
-                            <i class="fa fa-check-square"></i>
-                        </div>
-                        <input type="text" name="captcha" id="code" class="form-control" style="width: 150px" maxlength="6" placeholder="验证码" autocomplete="off">&nbsp;
-                    </div>
-                    {{create_captcha}}
-                    <div class="clearfix"></div>
-                </div>
-                <div class="form-group">
-                    <button type="submit" id="btnSendMail" class="btn btn-success" style="width: 100%"  data-loading-text="正在处理..." autocomplete="off">找回密码</button>
-                </div>
-
-            </form>
-        </div>
-    </div>
-    <div class="clearfix"></div>
-</div>
-{{template "widgets/footer.html" .}}
-<script src="{{$.StaticDomain}}/static/layer/layer.js" type="text/javascript"></script>
-<script src="{{$.StaticDomain}}/static/js/jquery.form.js" type="text/javascript"></script>
-<script type="text/javascript">
-    $(function () {
-        $("#email,#code").on('focus',function () {
-            $(this).tooltip('destroy').parents('.form-group').removeClass('has-error');;
-        });
-
-        $(document).keydown(function (e) {
-            var event = document.all ? window.event : e;
-            if(event.keyCode == 13){
-                $("#btn-login").click();
-            }
-        });
-
-        $("#findPasswordForm").ajaxForm({
-            beforeSubmit : function () {
-
-                var newPassword = $.trim($("#newPassword").val());
-                var confirmPassword = $.trim($("#confirmPassword").val());
-                var code = $.trim($("#code").val());
-
-                if(newPassword === ""){
-                    $("#newPassword").tooltip({placement:"auto",title : "密码不能为空",trigger : 'manual'})
-                        .tooltip('show')
-                        .parents('.form-group').addClass('has-error');
-
-                    return false;
-
-                }else if(confirmPassword === ""){
-                    $("#confirmPassword").tooltip({placement:"auto",title : "确认密码不能为空",trigger : 'manual'})
-                        .tooltip('show')
-                        .parents('.form-group').addClass('has-error');
-
-                    return false;
-                }else if(newPassword !== confirmPassword) {
-                    $("#confirmPassword").tooltip({placement:"auto",title : "确认密码输入不正确",trigger : 'manual'})
-                        .tooltip('show')
-                        .parents('.form-group').addClass('has-error');
-
-                    return false;
-                }else if(code === ""){
-                    $("#code").tooltip({title : '验证码不能为空',trigger : 'manual'})
-                        .tooltip('show')
-                        .parents('.form-group').addClass('has-error');
-
-                    return false;
-                }
-
-                $("#btnSendMail").button("loading");
-            },
-            success : function (res) {
-
-                if(res.errcode !== 0){
-                    $("#captcha-img").click();
-                    $("#code").val('');
-                    layer.msg(res.message);
-                    $("#btnSendMail").button('reset');
-                }else{
-                    window.location = res.data;
-                }
-            },
-            error :function () {
-                $("#captcha-img").click();
-                $("#code").val('');
-                layer.msg('系统错误');
-                $("#btnSendMail").button('reset');
-            }
-        });
-
-    });
-</script>
-</body>
-</html>
\ No newline at end of file
diff --git a/views/account/mail_template.html b/views/account/mail_template.html
deleted file mode 100644
index 875c1c6d..00000000
--- a/views/account/mail_template.html
+++ /dev/null
@@ -1,98 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta name="author" content="SmartWiki" />
-    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
-    <title>找回密码 - {{.SITE_NAME}}</title>
-    <style type="text/css">
-        .ua-macos::-webkit-scrollbar{ display: none; }
-        html,body{background-color: transparent;margin:0;padding: 0;}
-        body{font: 16px/1.5 "Microsoft Yahei", "微软雅黑", verdana;word-wrap:break-word;}
-        .js-dialog{font-size: 14px;}
-        pre, .js-pre {
-            white-space: pre-wrap;
-            white-space: -moz-pre-wrap;
-            white-space: -pre-wrap;
-            white-space: -o-pre-wrap;
-            word-wrap: break-word;
-            font: 16px/1.5 "Microsoft Yahei", "微软雅黑", verdana;
-            padding:8px 10px;margin:0;
-        }
-        .rm_line{border-top:2px solid #F1F1F1; font-size:0; margin:15px 0}
-        .atchImg img{border:2px solid #c3d9ff;}
-        .lnkTxt{ color:#0066CC}
-        .rm_PicArea *{ font-family: "Microsoft Yahei", "微软雅黑", verdana;font-size:16px;font-weight:700;}
-        .fbk3{ color:#333; line-height:160%}
-        .fTip{ font-size:11px; font-weight:normal}
-
-        img{border:none;vertical-align: middle;}
-        iframe{display:none;}
-        *{word-break:break-word;}
-        #neteaseEncryptedMail{display:none;}
-        #jy-translate{
-            position: absolute;
-            max-width: 500px;
-            min-width: 100px;
-            _width:300px;
-            border: 1px solid rgb(204, 204, 204);
-            padding: 4px 18px 4px 10px;
-            background-color: #f9f9f9;
-            -webkit-border-radius:3px;
-            -moz-border-radius:3px;
-            border-radius:3px;
-            -webkit-box-shadow:#dddddd 0px 0px 10px;
-            -moz-box-shadow:#dddddd 0px 0px 10px;
-            box-shadow:#dddddd 0px 0px 10px;
-        }
-        #jy-translate h2,
-        #jy-translate p{color:#555;margin:0;padding:0;}
-        #jy-translate h2{line-height: 28px;font-size: 14px;}
-        #jy-translate p{line-height: 24px;font-size: 12px;}
-        #jy-translate h2 span{font-weight:normal;}
-        .ua-noyahei,
-        .ua-noyahei .pre,
-        .ua-noyahei .js-pre,
-        .ua-noyahei .rm_PicArea *{font-family: \5b8b\4f53, sans-serif;}
-        .ua-macos,
-        .ua-macos .pre,
-        .ua-macos .js-pre,
-        .ua-macos .rm_PicArea *{font-family: "Lucida Grande","Hiragino Sans GB","Hiragino Sans GB W3", verdana;}
-
-        .jy-contact{float: left;}
-        .jy-contact-hover{background: #eee;}
-        .jy-contact img.oprt{width: 23px;height: 23px;border: 0;vertical-align: middle;cursor: pointer;}
-    </style>
-</head>
-<body onunload="" class="js-body">
-<div>
-    <div class="wrapper" style="margin: 20px auto 0; width: 500px; padding-top:16px; padding-bottom:10px;">
-        <div class="header clearfix">
-            <a class="logo" href="http://www.bookstack.cn/" target="_blank"><b>{{.SITE_NAME}}</b></a>
-        </div>
-        <br style="clear:both; height:0">
-        <div class="content" style="background: none repeat scroll 0 0 #FFFFFF; border: 1px solid #E9E9E9; margin: 2px 0 0; padding: 30px;">
-
-            <p>您好: </p>
-
-            <p>您在 {{.SITE_NAME}} 提交了找回密码申请。<br>如果您没有提交修改密码的申请, 请忽略本邮件</p>
-
-            <p style="border-top: 1px solid #DDDDDD;margin: 15px 0 25px;padding: 15px;">
-                请点击链接继续: <a href="{{.url}}" target="_blank">{{.url}}</a>
-            </p>
-            <p>
-                好的密码，不但应该容易记住，还要尽量符合以下强度标准：
-            <ul>
-                <li>包含大小写字母、数字和符号</li>
-                <li>不少于 10 位 </li>
-                <li>不包含生日、手机号码等易被猜出的信息</li>
-            </ul>
-            </p>
-            <p class="footer" style="border-top: 1px solid #DDDDDD; padding-top:6px; margin-top:25px; color:#838383;">
-                请勿回复本邮件, 此邮箱未受监控, 您不会得到任何回复. 要获得帮助, 请登录网站<br><br>
-                <a href="https://www.bookstack.cn/" target="_blank">书栈(BookStack.CN)</a>
-            </p>
-        </div>
-    </div>
-</div>
-</body>
-</html>
\ No newline at end of file
diff --git a/views/setting/index.html b/views/setting/index.html
deleted file mode 100644
index 9c9dd8d6..00000000
--- a/views/setting/index.html
+++ /dev/null
@@ -1,219 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-<head>
-    {{template "widgets/head.html" .}}
-
-    <link href="{{$.StaticDomain}}/static/webuploader/webuploader.css" rel="stylesheet">
-    <link href="{{$.StaticDomain}}/static/cropper/2.3.4/cropper.min.css" rel="stylesheet">
-</head>
-<body>
-<div class="manual-reader">
-    {{template "widgets/header.html" .}}
-    <div class="container manual-body">
-        <div class="row">
-
-            {{template "setting/menu.html" .}}
-
-            <div class="page-right">
-                <div class="m-box">
-                    <div class="box-head">
-                        <strong class="box-title">个人设置</strong>
-                    </div>
-                </div>
-                <div class="box-body">
-                    <div class="form-right">
-                        <label>用户头像</label>
-                        <label>
-                            <a href="javascript:;" data-toggle="modal" title="点击修改头像" data-target="#upload-logo-panel">
-                                <img src="{{showImg .Member.Avatar "avatar"}}" onerror="this.src='/static/images/avatar.png'" class="img-circle border" alt="头像" style="margin-left:15px;max-width: 120px;max-height: 120px;" id="headimgurl">
-                            </a>
-                        </label>
-                    </div>
-                    <div class="form-left">
-                        <form role="form" method="post" id="memberInfoForm">
-                            <div class="form-group">
-                                <label>用户名</label>
-                                <input type="text" class="form-control" value="{{.Member.Account}}" disabled>
-                            </div>
-                            <div class="form-group">
-                                <label>昵称</label>
-                                <input type="text" class="form-control" name="nickname" value="{{.Member.Nickname}}">
-                            </div>
-                            <div class="form-group">
-                                <label for="user-email">邮箱<strong class="text-danger">*</strong></label>
-                                <input type="email" class="form-control" value="{{.Member.Email}}" id="userEmail" name="email" max="100" placeholder="邮箱">
-                            </div>
-                            <div class="form-group">
-                                <label>手机号</label>
-                                <input type="text" class="form-control" id="userPhone" name="phone" maxlength="20" title="手机号码" placeholder="手机号码" value="{{.Member.Phone}}">
-                            </div>
-                            <div class="form-group">
-                                <label>微信号</label>
-                                <input type="text" class="form-control" id="wechatNO" name="wechat_no" maxlength="50" title="微信号" placeholder="请输入您的微信号" value="{{.Member.WechatNO}}">
-                            </div>
-                            <div class="form-group">
-                                <label class="description">描述</label>
-                                <textarea class="form-control" rows="3" title="描述" name="description" id="description" maxlength="500">{{.Member.Description}}</textarea>
-                                <p style="color: #999;font-size: 12px;">描述不能超过500字</p>
-                            </div>
-                            <div class="form-group">
-                                <button type="submit" class="btn btn-success" data-loading-text="保存中...">保存修改</button>
-                                <span id="form-error-message" class="error-message"></span>
-                            </div>
-                        </form>
-                    </div>
-
-                </div>
-            </div>
-        </div>
-    </div>
-</div>
-<!-- Start Modal -->
-<div class="modal fade" id="upload-logo-panel" tabindex="-1" role="dialog" aria-labelledby="修改头像" aria-hidden="true">
-    <div class="modal-dialog">
-        <div class="modal-content">
-            <div class="modal-header">
-                <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
-                <h4 class="modal-title">修改头像</h4>
-            </div>
-            <div class="modal-body">
-                <div class="wraper">
-                    <div id="image-wraper">
-
-                    </div>
-                </div>
-                <div class="watch-crop-list">
-                    <div class="preview-title">预览</div>
-                    <ul>
-                        <li>
-                            <div class="img-preview preview-lg"></div>
-                        </li>
-                        <li>
-                            <div class="img-preview preview-sm"></div>
-                        </li>
-                    </ul>
-                </div>
-                <div style="clear: both"></div>
-            </div>
-            <div class="modal-footer">
-                <span id="error-message"></span>
-                <div id="filePicker" class="btn">选择</div>
-                <button type="button" id="saveImage" class="btn btn-success" style="height: 40px;width: 77px;" data-loading-text="上传中...">上传</button>
-            </div>
-        </div>
-    </div>
-</div>
-
-
-{{/*<script src="/static/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>*/}}
-<script src="/static/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>
-{{/*<script src="/static/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>*/}}
-<script src="/static/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
-
-<script src="{{$.StaticDomain}}/static/webuploader/webuploader.min.js" type="text/javascript"></script>
-<script src="{{$.StaticDomain}}/static/cropper/2.3.4/cropper.min.js" type="text/javascript"></script>
-<script src="{{$.StaticDomain}}/static/js/jquery.form.js" type="text/javascript"></script>
-<script src="/static/js/main.js?version={{$.Version}}" type="text/javascript"></script>
-<script type="text/javascript">
-    $(function () {
-        $("#upload-logo-panel").on("hidden.bs.modal",function () {
-            $("#upload-logo-panel").find(".modal-body").html(window.modalHtml);
-        }).on("show.bs.modal",function () {
-            window.modalHtml = $("#upload-logo-panel").find(".modal-body").html();
-        });
-
-
-        $("#memberInfoForm").ajaxForm({
-            beforeSubmit : function () {
-
-                var email = $.trim($("#userEmail").val());
-                if(!email){
-                    return showError('邮箱不能为空');
-                }
-                $("button[type='submit']").button('loading');
-            },
-            success : function (res) {
-                $("button[type='submit']").button('reset');
-                if(res.errcode === 0){
-                    showSuccess("保存成功");
-                }else{
-                    showError(res.message);
-                }
-            }
-        });
-
-        try {
-            var uploader = WebUploader.create({
-                auto: false,
-                swf: '/static/webuploader/Uploader.swf',
-                server: '{{urlfor "SettingController.Upload"}}',
-                pick: "#filePicker",
-                fileVal : "image-file",
-                fileNumLimit : 1,
-                compress : false,
-                accept: {
-                    title: 'Images',
-                    extensions: 'jpg,jpeg,png',
-                    mimeTypes: 'image/jpg,image/jpeg,image/png'
-                }
-            }).on("beforeFileQueued",function (file) {
-                uploader.reset();
-            }).on( 'fileQueued', function( file ) {
-                uploader.makeThumb( file, function( error, src ) {
-                    $img = '<img src="' + src +'" style="max-width: 360px;max-height: 360px;">';
-                    if ( error ) {
-                        $img.replaceWith('<span>不能预览</span>');
-                        return;
-                    }
-
-                    $("#image-wraper").html($img);
-                    window.ImageCropper = $('#image-wraper>img').cropper({
-                        aspectRatio: 1 / 1,
-                        dragMode : 'move',
-                        viewMode : 1,
-                        preview : ".img-preview"
-                    });
-                }, 1, 1 );
-            }).on("uploadError",function (file,reason) {
-                console.log(reason);
-                $("#error-message").text("上传失败:" + reason);
-
-            }).on("uploadSuccess",function (file, res) {
-
-                if(res.errcode === 0){
-                    console.log(res);
-                    $("#upload-logo-panel").modal('hide');
-                    $("#headimgurl").attr('src',res.data);
-                }else{
-                    $("#error-message").text(res.message);
-                }
-            }).on("beforeFileQueued",function (file) {
-                if(file.size > 1024*1024*2){
-                    uploader.removeFile(file);
-                    uploader.reset();
-                    alert("文件必须小于2MB");
-                    return false;
-                }
-            }).on("uploadComplete",function () {
-                $("#saveImage").button('reset');
-            });
-            $("#saveImage").on("click",function () {
-                var files = uploader.getFiles();
-                if(files.length > 0) {
-                    $("#saveImage").button('loading');
-                    var cropper = window.ImageCropper.cropper("getData");
-
-                    uploader.option("formData", cropper);
-
-                    uploader.upload();
-                }else{
-                    alert("请选择头像");
-                }
-            });
-        }catch(e){
-            console.log(e);
-        }
-    });
-</script>
-</body>
-</html>
\ No newline at end of file
diff --git a/views/setting/menu.html b/views/setting/menu.html
index a2bdce26..a46c2bff 100644
--- a/views/setting/menu.html
+++ b/views/setting/menu.html
@@ -1,9 +1,6 @@
 <div class="page-left">
     <ul class="menu">
         <li {{if .SettingBasic}}class="active"{{end}}><a href="{{urlfor "SettingController.Index"}}" class="item"><i class="fa fa-cogs" aria-hidden="true"></i> 个人设置</a> </li>
-    {{if ne .Member.AuthMethod "ldap"}}
-        <li {{if .SettingPwd}}class="active"{{end}}><a href="{{urlfor "SettingController.Password"}}" class="item"><i class="fa fa-user-o" aria-hidden="true"></i> 修改密码</a> </li>
-    {{end}}
 
         {{if $.ShowCreateBookEntrance}}
         <li {{if .SettingBook}}class="active"{{end}}><a href="{{urlfor "BookController.Index"}}" class="item"><i class="fa fa-sitemap" aria-hidden="true"></i> 我的书籍</a> </li>
diff --git a/views/setting/password.html b/views/setting/password.html
deleted file mode 100644
index 282d73be..00000000
--- a/views/setting/password.html
+++ /dev/null
@@ -1,94 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-<head>
-{{template "widgets/head.html" .}}
-</head>
-<body>
-<div class="manual-reader">
-    {{template "widgets/header.html" .}}
-    <div class="container manual-body">
-        <div class="row">
-
-        {{template "setting/menu.html" .}}
-
-
-            <div class="page-right">
-                <div class="m-box">
-                    <div class="box-head">
-                        <strong class="box-title">修改密码</strong>
-                    </div>
-                </div>
-                <div class="box-body">
-                    <form role="form" method="post" id="securityForm">
-                        <div class="form-group">
-                            <label for="password1">原始密码</label>
-                            <input type="password" name="password1" id="password1" class="form-control disabled" placeholder="原始密码">
-                        </div>
-                        <div class="form-group">
-                            <label for="password2">新密码</label>
-                            <input type="password" class="form-control" name="password2" id="password2" max="50" placeholder="新密码">
-                        </div>
-                        <div class="form-group">
-                            <label for="password3">确认密码</label>
-                            <input type="password" class="form-control" id="password3" name="password3" placeholder="确认密码">
-                        </div>
-                        <div class="form-group">
-                            <button type="submit" class="btn btn-success" data-loading-text="保存中...">保存修改</button>
-                            <span id="form-error-message" class="error-message"></span>
-                        </div>
-                    </form>
-                </div>
-
-
-            </div>
-        </div>
-    </div>
-</div>
-
-{{/*<script src="/static/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>*/}}
-<script src="/static/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>
-{{/*<script src="/static/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>*/}}
-<script src="/static/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
-
-<script src="{{$.StaticDomain}}/static/js/jquery.form.js" type="text/javascript"></script>
-<script src="/static/js/main.js?version={{$.Version}}" type="text/javascript"></script>
-<script type="text/javascript">
-    $(function () {
-
-        $("#securityForm").ajaxForm({
-            beforeSubmit : function () {
-                var oldPasswd = $("#password1").val();
-                var newPasswd = $("#password2").val();
-                var confirmPassword = $("#password3").val();
-                if(!oldPasswd ){
-                    showError("原始密码不能为空");
-                    return false;
-                }
-                if(!newPasswd){
-                    showError("新密码不能为空");
-                    return false;
-                }
-                if(!confirmPassword){
-                    showError("确认密码不能为空");
-                    return false;
-                }
-                if(confirmPassword !== newPasswd){
-                    showError("确认密码不正确");
-                    return false;
-                }
-            },
-            success : function (res) {
-                if(res.errcode === 0){
-                    showSuccess('保存成功');
-                    $("#password1").val('');
-                    $("#password2").val('');
-                    $("#password3").val('');
-                }else{
-                    showError(res.message);
-                }
-            }
-        }) ;
-    });
-</script>
-</body>
-</html>
\ No newline at end of file
diff --git a/views/widgets/header.html b/views/widgets/header.html
index e4bc2e50..3b39a1c9 100644
--- a/views/widgets/header.html
+++ b/views/widgets/header.html
@@ -145,7 +145,7 @@
                     </ul>
                 </li>
                 {{else}}
-                    <li><a href="{{urlfor "AccountController.Oauth" ":oauth" "email"}}" title="注册"><i class="fa fa-user-plus"></i> 注册</a></li>
+                    <li><a href="{{urlfor "AccountController.Signup"}}" title="注册"><i class="fa fa-user-plus"></i> 注册</a></li>
                     <li><a href="{{urlfor "AccountController.Login"}}" title="登录"><i class="fa fa-sign-in"></i> 登录</a></li>
                 {{end}}
             </ul>
