From 73fecf9c63e6002115e323c9f53f707f4a225d03 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Sun, 15 Nov 2020 20:58:51 +0800 Subject: [PATCH] add revoke all sessions in device & session page --- cmd/webserver.go | 1 + pkg/api/tokens.go | 46 ++++++++++++++++++++++---- pkg/services/tokens.go | 19 +++++++++++ src/lib/services.js | 3 ++ src/locales/en.js | 4 +++ src/locales/zh_Hans.js | 4 +++ src/views/mobile/users/SessionList.vue | 45 ++++++++++++++++++++++++- 7 files changed, 115 insertions(+), 7 deletions(-) diff --git a/cmd/webserver.go b/cmd/webserver.go index 1704fb7b..e4249931 100644 --- a/cmd/webserver.go +++ b/cmd/webserver.go @@ -143,6 +143,7 @@ func startWebServer(c *cli.Context) error { // Tokens apiV1Route.GET("/tokens/list.json", bindApi(api.Tokens.TokenListHandler)) apiV1Route.POST("/tokens/revoke.json", bindApi(api.Tokens.TokenRevokeHandler)) + apiV1Route.POST("/tokens/revokeAll.json", bindApi(api.Tokens.TokenRevokeAllHandler)) apiV1Route.POST("/tokens/refresh.json", bindApi(api.Tokens.TokenRefreshHandler)) // Users diff --git a/pkg/api/tokens.go b/pkg/api/tokens.go index 11658756..f1bff8cd 100644 --- a/pkg/api/tokens.go +++ b/pkg/api/tokens.go @@ -75,8 +75,8 @@ func (a *TokensApi) TokenRevokeCurrentHandler(c *core.Context) (interface{}, *er } tokenRecord := &models.TokenRecord{ - Uid: uid, - UserTokenId: userTokenId, + Uid: uid, + UserTokenId: userTokenId, CreatedUnixTime: claims.IssuedAt, } @@ -129,6 +129,40 @@ func (a *TokensApi) TokenRevokeHandler(c *core.Context) (interface{}, *errs.Erro return true, nil } +func (a *TokensApi) TokenRevokeAllHandler(c *core.Context) (interface{}, *errs.Error) { + uid := c.GetCurrentUid() + tokens, err := a.tokens.GetAllTokensByUid(uid) + + if err != nil { + log.ErrorfWithRequestId(c, "[tokens.TokenRevokeAllHandler] failed to get all tokens for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.ErrOperationFailed + } + + claims := c.GetTokenClaims() + currentTokenIndex := 0 + + for i := 0; i < len(tokens); i++ { + token := tokens[i] + + if utils.Int64ToString(token.Uid) == claims.Id && utils.Int64ToString(token.UserTokenId) == claims.UserTokenId && token.CreatedUnixTime == claims.IssuedAt { + currentTokenIndex = i + break + } + } + + tokens = append(tokens[:currentTokenIndex], tokens[currentTokenIndex+1:]...) + + err = a.tokens.DeleteTokens(uid, tokens) + + if err != nil { + log.ErrorfWithRequestId(c, "[token.TokenRevokeAllHandler] failed to revoke all tokens for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.Or(err, errs.ErrOperationFailed) + } + + log.InfofWithRequestId(c, "[token.TokenRevokeAllHandler] user \"uid:%d\" has revoked all tokens", uid) + return true, nil +} + func (a *TokensApi) TokenRefreshHandler(c *core.Context) (interface{}, *errs.Error) { uid := c.GetCurrentUid() user, err := a.users.GetUserById(uid) @@ -148,8 +182,8 @@ func (a *TokensApi) TokenRefreshHandler(c *core.Context) (interface{}, *errs.Err oldTokenClaims := c.GetTokenClaims() oldUserTokenId, _ := utils.StringToInt64(oldTokenClaims.UserTokenId) oldTokenRecord := &models.TokenRecord{ - Uid: uid, - UserTokenId: oldUserTokenId, + Uid: uid, + UserTokenId: oldUserTokenId, CreatedUnixTime: oldTokenClaims.IssuedAt, } @@ -158,9 +192,9 @@ func (a *TokensApi) TokenRefreshHandler(c *core.Context) (interface{}, *errs.Err log.InfofWithRequestId(c, "[token.TokenRefreshHandler] user \"uid:%d\" token refreshed, new token will be expired at %d", user.Uid, claims.ExpiresAt) refreshResp := &models.TokenRefreshResponse{ - NewToken: token, + NewToken: token, OldTokenId: a.tokens.GenerateTokenId(oldTokenRecord), - User: user.ToUserBasicInfo(), + User: user.ToUserBasicInfo(), } return refreshResp, nil diff --git a/pkg/services/tokens.go b/pkg/services/tokens.go index 80dd1e01..f41ec349 100644 --- a/pkg/services/tokens.go +++ b/pkg/services/tokens.go @@ -116,6 +116,25 @@ func (s *TokenService) DeleteToken(tokenRecord *models.TokenRecord) error { }) } +func (s *TokenService) DeleteTokens(uid int64, tokenRecords []*models.TokenRecord) error { + if uid <= 0 { + return errs.ErrUserIdInvalid + } + + return s.TokenDB(uid).DoTransaction(func(sess *xorm.Session) error { + for i := 0; i < len(tokenRecords); i++ { + tokenRecord := tokenRecords[i] + _, err := sess.Where("uid=? AND user_token_id=? AND created_unix_time=?", uid, tokenRecord.UserTokenId, tokenRecord.CreatedUnixTime).Delete(&models.TokenRecord{}) + + if err != nil { + return err + } + } + + return nil + }) +} + func (s *TokenService) DeleteTokenByClaims(claims *core.UserTokenClaims) error { uid, err := utils.StringToInt64(claims.Id) diff --git a/src/lib/services.js b/src/lib/services.js index c3992dba..d32c2618 100644 --- a/src/lib/services.js +++ b/src/lib/services.js @@ -126,6 +126,9 @@ export default { tokenId }); }, + revokeAllTokens: () => { + return axios.post('v1/tokens/revokeAll.json'); + }, getProfile: () => { return axios.get('v1/users/profile/get.json'); }, diff --git a/src/locales/en.js b/src/locales/en.js index 7723639d..4480b7d7 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -358,11 +358,15 @@ export default { 'Your profile has been successfully updated': 'Your profile has been successfully updated', 'Unable to update user profile': 'Unable to update user profile', 'Device & Sessions': 'Device & Sessions', + 'Logout All': 'Logout All', 'Unable to get session list': 'Unable to get session list', 'Current': 'Current', 'Other Device': 'Other Device', 'Are you sure you want to logout from this session?': 'Are you sure you want to logout from this session?', 'Unable to logout from this session': 'Unable to logout from this session', + 'Are you sure you want to logout all other sessions?': 'Are you sure you want to logout all other sessions?', + 'You have logged out all other sessions': 'You have logged out all other sessions', + 'Unable to logout all other sessions': 'Unable to logout all other sessions', 'Regenerate Backup Codes': 'Regenerate Backup Codes', 'Please use two factor authentication app scan the below qrcode and input current passcode': 'Please use two factor authentication app scan the below qrcode and input current passcode', 'Please enter your current password when disable two factor authentication': 'Please enter your current password when disable two factor authentication', diff --git a/src/locales/zh_Hans.js b/src/locales/zh_Hans.js index f6264565..4a38e2db 100644 --- a/src/locales/zh_Hans.js +++ b/src/locales/zh_Hans.js @@ -358,11 +358,15 @@ export default { 'Your profile has been successfully updated': '您的用户信息更新成功', 'Unable to update user profile': '无法更新用户信息', 'Device & Sessions': '设备和会话', + 'Logout All': '注销全部', 'Unable to get session list': '无法获取会话列表', 'Current': '当前', 'Other Device': '其他设备', 'Are you sure you want to logout from this session?': '您确定是否要退出该会话?', 'Unable to logout from this session': '无法退出该会话', + 'Are you sure you want to logout all other sessions?': '您确定是否要退出其他所有会话?', + 'You have logged out all other sessions': '您已经退出其他所有会话', + 'Unable to logout all other sessions': '无法退出其他所有会话', 'Regenerate Backup Codes': '重新生成备用码', 'Please use two factor authentication app scan the below qrcode and input current passcode': '请使用两步验证应用扫描下方的二维码并输入当前的验证码', 'Please enter your current password when disable two factor authentication': '禁用两步验证时需要输入您的当前密码', diff --git a/src/views/mobile/users/SessionList.vue b/src/views/mobile/users/SessionList.vue index 5d545da5..8490bae4 100644 --- a/src/views/mobile/users/SessionList.vue +++ b/src/views/mobile/users/SessionList.vue @@ -1,6 +1,12 @@