From 18052a22f26b1b40b1a00bdaa892dc3d7cfed4fa Mon Sep 17 00:00:00 2001 From: MaysWind Date: Thu, 22 Oct 2020 00:04:30 +0800 Subject: [PATCH] support clear token in database after logout, support clear browser token when api responses token invalid --- cmd/webserver.go | 3 +++ pkg/api/tokens.go | 28 ++++++++++++++++++++++++++ src/lib/services.js | 13 ++++++++++++ src/locales/en.js | 10 ++++++++++ src/locales/zh_Hans.js | 10 ++++++++++ src/views/mobile/Login.vue | 10 +++++++--- src/views/mobile/Settings.vue | 37 +++++++++++++++++++++++++++++++++-- 7 files changed, 106 insertions(+), 5 deletions(-) diff --git a/cmd/webserver.go b/cmd/webserver.go index 90bd4125..380e7acb 100644 --- a/cmd/webserver.go +++ b/cmd/webserver.go @@ -118,6 +118,9 @@ func startWebServer(c *cli.Context) error { apiV1Route := apiRoute.Group("/v1") apiV1Route.Use(bindMiddleware(middlewares.JWTAuthorization)) { + // Logout + apiV1Route.GET("/logout.json", bindApi(api.Tokens.TokenRevokeCurrentHandler)) + // Tokens apiV1Route.GET("/tokens/list.json", bindApi(api.Tokens.TokenListHandler)) apiV1Route.POST("/tokens/revoke.json", bindApi(api.Tokens.TokenRevokeHandler)) diff --git a/pkg/api/tokens.go b/pkg/api/tokens.go index 04a6fba8..da6e6e4f 100644 --- a/pkg/api/tokens.go +++ b/pkg/api/tokens.go @@ -53,6 +53,34 @@ func (a *TokensApi) TokenListHandler(c *core.Context) (interface{}, *errs.Error) return tokenResps, nil } +func (a *TokensApi) TokenRevokeCurrentHandler(c *core.Context) (interface{}, *errs.Error) { + claims := c.GetTokenClaims() + uid := c.GetCurrentUid() + userTokenId, err := utils.StringToInt64(claims.UserTokenId) + + if err != nil { + log.WarnfWithRequestId(c, "[tokens.TokenRevokeCurrentHandler] parse user token id failed, because %s", err.Error()) + return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) + } + + tokenRecord := &models.TokenRecord{ + Uid: uid, + UserTokenId: userTokenId, + CreatedUnixTime: claims.IssuedAt, + } + + tokenId := a.tokens.GenerateTokenId(tokenRecord) + err = a.tokens.DeleteToken(tokenRecord) + + if err != nil { + log.ErrorfWithRequestId(c, "[token.TokenRevokeCurrentHandler] failed to revoke token \"id:%s\" for user \"uid:%d\", because %s", tokenId, uid, err.Error()) + return nil, errs.Or(err, errs.ErrOperationFailed) + } + + log.InfofWithRequestId(c, "[token.TokenRevokeCurrentHandler] user \"uid:%d\" has revoked token \"id:%s\"", uid, tokenId) + return true, nil +} + func (a *TokensApi) TokenRevokeHandler(c *core.Context) (interface{}, *errs.Error) { var tokenRevokeReq models.TokenRevokeRequest err := c.ShouldBindJSON(&tokenRevokeReq) diff --git a/src/lib/services.js b/src/lib/services.js index 053a2299..b87b22ce 100644 --- a/src/lib/services.js +++ b/src/lib/services.js @@ -17,6 +17,16 @@ axios.interceptors.request.use(config => { axios.interceptors.response.use(response => { return response; }, error => { + if (error.response && error.response.data && error.response.data.errorCode) { + const errorCode = error.response.data.errorCode; + + if (202001 <= errorCode && errorCode <= 202007) { // unauthorized access or token is invalid + userState.clearToken(); + location.reload(); + return Promise.reject({ processed: true }); + } + } + return Promise.reject(error); }); @@ -38,5 +48,8 @@ export default { Authorization: `Bearer ${token}` } }); + }, + logout: () => { + return axios.get('v1/logout.json'); } }; diff --git a/src/locales/en.js b/src/locales/en.js index b4c94327..66280a5a 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -5,6 +5,15 @@ export default { } }, 'error': { + 'unauthorized access': 'Unauthorized access', + 'token is expired': 'Token is expired', + 'token is invalid': 'Token is invalid', + 'user token id is invalid': 'User token id is invalid', + 'token id is invalid': 'Token id is invalid', + 'token is not found': 'Token is not found', + 'token type is invalid': 'Token type is invalid', + 'token requires two factor authorization': 'Token requires two factor authorization', + 'token does not require two factor authorization': 'Token does not require two factor authorization', 'user id is invalid': 'User id is invalid', 'username is empty': 'Username is empty', 'email is empty': 'Email is empty', @@ -51,4 +60,5 @@ export default { 'Language': 'Language', 'Log Out': 'Log Out', 'Are you sure you want to log out?': 'Are you sure you want to log out?', + 'Unable to logout': 'Unable to logout', }; diff --git a/src/locales/zh_Hans.js b/src/locales/zh_Hans.js index fdcdacb0..4b7e7c2c 100644 --- a/src/locales/zh_Hans.js +++ b/src/locales/zh_Hans.js @@ -5,6 +5,15 @@ export default { } }, 'error': { + 'unauthorized access': '未授权的登录', + 'token is expired': '认证令牌已过期', + 'token is invalid': '认证令牌无效', + 'user token id is invalid': '用户认证令牌ID无效', + 'token id is invalid': '认证令牌ID无效', + 'token is not found': '认证令牌不存在', + 'token type is invalid': '认证令牌类型无效', + 'token requires two factor authorization': '认证令牌需要两步验证', + 'token does not require two factor authorization': '认证令牌不需要两步验证', 'user id is invalid': '用户ID无效', 'username is empty': '用户名为空', 'email is empty': '电子邮箱为空', @@ -51,4 +60,5 @@ export default { 'Language': '语言', 'Log Out': '退出登录', 'Are you sure you want to log out?': '您确定是否要退出登录?', + 'Unable to logout': '无法退出登录', }; diff --git a/src/views/mobile/Login.vue b/src/views/mobile/Login.vue index 36f85a93..426ad4a0 100644 --- a/src/views/mobile/Login.vue +++ b/src/views/mobile/Login.vue @@ -137,7 +137,7 @@ export default { password: self.password }).then(response => { hasResponse = true; - self.$f7.preloader.hide(); + app.preloader.hide(); const data = response.data; if (!data || !data.success || !data.result || !data.result.token) { @@ -155,14 +155,18 @@ export default { router.navigate('/'); }).catch(error => { hasResponse = true; - self.$f7.preloader.hide(); + app.preloader.hide(); + + if (error && error.processed) { + return; + } if (error.response && error.response.data && error.response.data.errorMessage) { self.$alert(`error.${error.response.data.errorMessage}`); } else { self.$alert('Unable to login'); } - }) + }); }, verify() { const self = this; diff --git a/src/views/mobile/Settings.vue b/src/views/mobile/Settings.vue index 30d9a3e0..1d1b1200 100644 --- a/src/views/mobile/Settings.vue +++ b/src/views/mobile/Settings.vue @@ -38,11 +38,44 @@ export default { methods: { logout() { const self = this; + const app = self.$f7; const router = self.$f7router; self.$confirm('Are you sure you want to log out?', () => { - self.$user.clearToken(); - router.navigate('/'); + let hasResponse = false; + + setTimeout(() => { + if (!hasResponse) { + app.preloader.show(); + } + }, 200); + + self.$services.logout().then(response => { + hasResponse = true; + app.preloader.hide(); + const data = response.data; + + if (!data || !data.success || !data.result) { + self.$alert('Unable to logout'); + return; + } + + self.$user.clearToken(); + router.navigate('/'); + }).catch(error => { + hasResponse = true; + app.preloader.hide(); + + if (error && error.processed) { + return; + } + + if (error.response && error.response.data && error.response.data.errorMessage) { + self.$alert(`error.${error.response.data.errorMessage}`); + } else { + self.$alert('Unable to logout'); + } + }); }); } }