support setting token min refresh interval
This commit is contained in:
@@ -680,5 +680,6 @@ func printUserInfo(user *models.User) {
|
|||||||
func printTokenInfo(token *models.TokenRecord) {
|
func printTokenInfo(token *models.TokenRecord) {
|
||||||
fmt.Printf("[CreatedAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(token.CreatedUnixTime), token.CreatedUnixTime)
|
fmt.Printf("[CreatedAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(token.CreatedUnixTime), token.CreatedUnixTime)
|
||||||
fmt.Printf("[ExpiredAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(token.ExpiredUnixTime), token.ExpiredUnixTime)
|
fmt.Printf("[ExpiredAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(token.ExpiredUnixTime), token.ExpiredUnixTime)
|
||||||
|
fmt.Printf("[LastSeen] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(token.LastSeenUnixTime), token.LastSeenUnixTime)
|
||||||
fmt.Printf("[UserAgent] %s\n", token.UserAgent)
|
fmt.Printf("[UserAgent] %s\n", token.UserAgent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,10 @@ enable_two_factor = true
|
|||||||
# Token expired seconds (60 - 4294967295), default is 2592000 (30 days)
|
# Token expired seconds (60 - 4294967295), default is 2592000 (30 days)
|
||||||
token_expired_time = 2592000
|
token_expired_time = 2592000
|
||||||
|
|
||||||
|
# Token minimum refresh interval (0 - 4294967295), the value should be less than token expired time
|
||||||
|
# Set to 0 to refresh the token every time when refreshing the front end, default is 86400 (1 day)
|
||||||
|
token_min_refresh_interval = 86400
|
||||||
|
|
||||||
# Temporary token expired seconds (60 - 4294967295), default is 300 (5 minutes)
|
# Temporary token expired seconds (60 - 4294967295), default is 300 (5 minutes)
|
||||||
temporary_token_expired_time = 300
|
temporary_token_expired_time = 300
|
||||||
|
|
||||||
|
|||||||
+36
-3
@@ -2,12 +2,14 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/services"
|
"github.com/mayswind/ezbookkeeping/pkg/services"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -44,8 +46,7 @@ func (a *TokensApi) TokenListHandler(c *core.Context) (any, *errs.Error) {
|
|||||||
TokenId: a.tokens.GenerateTokenId(token),
|
TokenId: a.tokens.GenerateTokenId(token),
|
||||||
TokenType: token.TokenType,
|
TokenType: token.TokenType,
|
||||||
UserAgent: token.UserAgent,
|
UserAgent: token.UserAgent,
|
||||||
CreatedAt: token.CreatedUnixTime,
|
LastSeen: token.LastSeenUnixTime,
|
||||||
ExpiredAt: token.ExpiredUnixTime,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.Uid == claims.Uid && utils.Int64ToString(token.UserTokenId) == claims.UserTokenId && token.CreatedUnixTime == claims.IssuedAt {
|
if token.Uid == claims.Uid && utils.Int64ToString(token.UserTokenId) == claims.UserTokenId && token.CreatedUnixTime == claims.IssuedAt {
|
||||||
@@ -176,6 +177,39 @@ func (a *TokensApi) TokenRefreshHandler(c *core.Context) (any, *errs.Error) {
|
|||||||
return nil, errs.ErrUserNotFound
|
return nil, errs.ErrUserNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
oldTokenClaims := c.GetTokenClaims()
|
||||||
|
|
||||||
|
if now-oldTokenClaims.IssuedAt < int64(settings.Container.Current.TokenMinRefreshInterval) {
|
||||||
|
log.InfofWithRequestId(c, "[token.TokenRefreshHandler] token of user \"uid:%d\" does not need to be refreshed", uid)
|
||||||
|
|
||||||
|
userTokenId, err := utils.StringToInt64(oldTokenClaims.UserTokenId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[tokens.TokenRefreshHandler] parse user token id failed, because %s", err.Error())
|
||||||
|
} else {
|
||||||
|
tokenRecord := &models.TokenRecord{
|
||||||
|
Uid: oldTokenClaims.Uid,
|
||||||
|
UserTokenId: userTokenId,
|
||||||
|
CreatedUnixTime: oldTokenClaims.IssuedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenId := a.tokens.GenerateTokenId(tokenRecord)
|
||||||
|
|
||||||
|
err = a.tokens.UpdateTokenLastSeen(c, tokenRecord)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[token.TokenRefreshHandler] failed to update last seen of token \"id:%s\" for user \"uid:%d\", because %s", tokenId, uid, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshResp := &models.TokenRefreshResponse{
|
||||||
|
User: user.ToUserBasicInfo(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return refreshResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
token, claims, err := a.tokens.CreateToken(c, user)
|
token, claims, err := a.tokens.CreateToken(c, user)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -183,7 +217,6 @@ func (a *TokensApi) TokenRefreshHandler(c *core.Context) (any, *errs.Error) {
|
|||||||
return nil, errs.Or(err, errs.ErrTokenGenerating)
|
return nil, errs.Or(err, errs.ErrTokenGenerating)
|
||||||
}
|
}
|
||||||
|
|
||||||
oldTokenClaims := c.GetTokenClaims()
|
|
||||||
oldUserTokenId, _ := utils.StringToInt64(oldTokenClaims.UserTokenId)
|
oldUserTokenId, _ := utils.StringToInt64(oldTokenClaims.UserTokenId)
|
||||||
oldTokenRecord := &models.TokenRecord{
|
oldTokenRecord := &models.TokenRecord{
|
||||||
Uid: uid,
|
Uid: uid,
|
||||||
|
|||||||
+8
-7
@@ -13,11 +13,12 @@ var (
|
|||||||
ErrInvalidDuplicateCheckerType = NewSystemError(SystemSubcategorySetting, 6, http.StatusInternalServerError, "invalid duplicate checker type")
|
ErrInvalidDuplicateCheckerType = NewSystemError(SystemSubcategorySetting, 6, http.StatusInternalServerError, "invalid duplicate checker type")
|
||||||
ErrInvalidInMemoryDuplicateCheckerCleanupInterval = NewSystemError(SystemSubcategorySetting, 7, http.StatusInternalServerError, "invalid in-memory duplicate checker cleanup interval")
|
ErrInvalidInMemoryDuplicateCheckerCleanupInterval = NewSystemError(SystemSubcategorySetting, 7, http.StatusInternalServerError, "invalid in-memory duplicate checker cleanup interval")
|
||||||
ErrInvalidTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 8, http.StatusInternalServerError, "invalid token expired time")
|
ErrInvalidTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 8, http.StatusInternalServerError, "invalid token expired time")
|
||||||
ErrInvalidTemporaryTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 9, http.StatusInternalServerError, "invalid temporary token expired time")
|
ErrInvalidTokenMinRefreshInterval = NewSystemError(SystemSubcategorySetting, 9, http.StatusInternalServerError, "invalid token min refresh interval")
|
||||||
ErrInvalidEmailVerifyTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 10, http.StatusInternalServerError, "invalid email verify token expired time")
|
ErrInvalidTemporaryTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 10, http.StatusInternalServerError, "invalid temporary token expired time")
|
||||||
ErrInvalidAvatarProvider = NewSystemError(SystemSubcategorySetting, 11, http.StatusInternalServerError, "invalid avatar provider")
|
ErrInvalidEmailVerifyTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 11, http.StatusInternalServerError, "invalid email verify token expired time")
|
||||||
ErrInvalidMapProvider = NewSystemError(SystemSubcategorySetting, 12, http.StatusInternalServerError, "invalid map provider")
|
ErrInvalidAvatarProvider = NewSystemError(SystemSubcategorySetting, 12, http.StatusInternalServerError, "invalid avatar provider")
|
||||||
ErrInvalidAmapSecurityVerificationMethod = NewSystemError(SystemSubcategorySetting, 13, http.StatusInternalServerError, "invalid amap security verification method")
|
ErrInvalidMapProvider = NewSystemError(SystemSubcategorySetting, 13, http.StatusInternalServerError, "invalid map provider")
|
||||||
ErrInvalidPasswordResetTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 14, http.StatusInternalServerError, "invalid password reset token expired time")
|
ErrInvalidAmapSecurityVerificationMethod = NewSystemError(SystemSubcategorySetting, 14, http.StatusInternalServerError, "invalid amap security verification method")
|
||||||
ErrInvalidExchangeRatesDataSource = NewSystemError(SystemSubcategorySetting, 15, http.StatusInternalServerError, "invalid exchange rates data source")
|
ErrInvalidPasswordResetTokenExpiredTime = NewSystemError(SystemSubcategorySetting, 15, http.StatusInternalServerError, "invalid password reset token expired time")
|
||||||
|
ErrInvalidExchangeRatesDataSource = NewSystemError(SystemSubcategorySetting, 16, http.StatusInternalServerError, "invalid exchange rates data source")
|
||||||
)
|
)
|
||||||
|
|||||||
+12
-12
@@ -7,13 +7,14 @@ const TokenMaxUserAgentLength = 255
|
|||||||
|
|
||||||
// TokenRecord represents token data stored in database
|
// TokenRecord represents token data stored in database
|
||||||
type TokenRecord struct {
|
type TokenRecord struct {
|
||||||
Uid int64 `xorm:"PK INDEX(IDX_token_record_uid_type_expired_time)"`
|
Uid int64 `xorm:"PK INDEX(IDX_token_record_uid_type_expired_time)"`
|
||||||
UserTokenId int64 `xorm:"PK"`
|
UserTokenId int64 `xorm:"PK"`
|
||||||
TokenType core.TokenType `xorm:"INDEX(IDX_token_record_uid_type_expired_time) TINYINT NOT NULL"`
|
TokenType core.TokenType `xorm:"INDEX(IDX_token_record_uid_type_expired_time) TINYINT NOT NULL"`
|
||||||
Secret string `xorm:"VARCHAR(10) NOT NULL"`
|
Secret string `xorm:"VARCHAR(10) NOT NULL"`
|
||||||
UserAgent string `xorm:"VARCHAR(255)"`
|
UserAgent string `xorm:"VARCHAR(255)"`
|
||||||
CreatedUnixTime int64 `xorm:"PK"`
|
CreatedUnixTime int64 `xorm:"PK"`
|
||||||
ExpiredUnixTime int64 `xorm:"INDEX(IDX_token_record_uid_type_expired_time)"`
|
ExpiredUnixTime int64 `xorm:"INDEX(IDX_token_record_uid_type_expired_time)"`
|
||||||
|
LastSeenUnixTime int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenRevokeRequest represents all parameters of token revoking request
|
// TokenRevokeRequest represents all parameters of token revoking request
|
||||||
@@ -23,8 +24,8 @@ type TokenRevokeRequest struct {
|
|||||||
|
|
||||||
// TokenRefreshResponse represents all parameters of token refreshing request
|
// TokenRefreshResponse represents all parameters of token refreshing request
|
||||||
type TokenRefreshResponse struct {
|
type TokenRefreshResponse struct {
|
||||||
NewToken string `json:"newToken"`
|
NewToken string `json:"newToken,omitempty"`
|
||||||
OldTokenId string `json:"oldTokenId"`
|
OldTokenId string `json:"oldTokenId,omitempty"`
|
||||||
User *UserBasicInfo `json:"user"`
|
User *UserBasicInfo `json:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,8 +34,7 @@ type TokenInfoResponse struct {
|
|||||||
TokenId string `json:"tokenId"`
|
TokenId string `json:"tokenId"`
|
||||||
TokenType core.TokenType `json:"tokenType"`
|
TokenType core.TokenType `json:"tokenType"`
|
||||||
UserAgent string `json:"userAgent"`
|
UserAgent string `json:"userAgent"`
|
||||||
CreatedAt int64 `json:"createdAt"`
|
LastSeen int64 `json:"lastSeen"`
|
||||||
ExpiredAt int64 `json:"expiredAt"`
|
|
||||||
IsCurrent bool `json:"isCurrent"`
|
IsCurrent bool `json:"isCurrent"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,5 +53,5 @@ func (a TokenInfoResponseSlice) Swap(i, j int) {
|
|||||||
|
|
||||||
// Less reports whether the first item is less than the second one
|
// Less reports whether the first item is less than the second one
|
||||||
func (a TokenInfoResponseSlice) Less(i, j int) bool {
|
func (a TokenInfoResponseSlice) Less(i, j int) bool {
|
||||||
return a[i].ExpiredAt > a[j].ExpiredAt
|
return a[i].LastSeen > a[j].LastSeen
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-7
@@ -58,7 +58,7 @@ func (s *TokenService) GetAllUnexpiredNormalTokensByUid(c *core.Context, uid int
|
|||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
|
|
||||||
var tokenRecords []*models.TokenRecord
|
var tokenRecords []*models.TokenRecord
|
||||||
err := s.TokenDB(uid).NewSession(c).Cols("uid", "user_token_id", "token_type", "user_agent", "created_unix_time", "expired_unix_time").Where("uid=? AND token_type=? AND expired_unix_time>?", uid, core.USER_TOKEN_TYPE_NORMAL, now).Find(&tokenRecords)
|
err := s.TokenDB(uid).NewSession(c).Cols("uid", "user_token_id", "token_type", "user_agent", "created_unix_time", "expired_unix_time", "last_seen_unix_time").Where("uid=? AND token_type=? AND expired_unix_time>?", uid, core.USER_TOKEN_TYPE_NORMAL, now).Find(&tokenRecords)
|
||||||
|
|
||||||
return tokenRecords, err
|
return tokenRecords, err
|
||||||
}
|
}
|
||||||
@@ -98,6 +98,31 @@ func (s *TokenService) CreatePasswordResetToken(c *core.Context, user *models.Us
|
|||||||
return s.createToken(c, user, core.USER_TOKEN_TYPE_PASSWORD_RESET, s.getUserAgent(c), s.CurrentConfig().PasswordResetTokenExpiredTimeDuration)
|
return s.createToken(c, user, core.USER_TOKEN_TYPE_PASSWORD_RESET, s.getUserAgent(c), s.CurrentConfig().PasswordResetTokenExpiredTimeDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateTokenLastSeen updates the last seen time of specified token
|
||||||
|
func (s *TokenService) UpdateTokenLastSeen(c *core.Context, tokenRecord *models.TokenRecord) error {
|
||||||
|
if tokenRecord.Uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenRecord.UserTokenId <= 0 {
|
||||||
|
return errs.ErrInvalidUserTokenId
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenRecord.LastSeenUnixTime = time.Now().Unix()
|
||||||
|
|
||||||
|
return s.TokenDB(tokenRecord.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||||
|
updatedRows, err := sess.Cols("last_seen_unix_time").Where("uid=? AND user_token_id=? AND created_unix_time=?", tokenRecord.Uid, tokenRecord.UserTokenId, tokenRecord.CreatedUnixTime).Update(tokenRecord)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if updatedRows < 1 {
|
||||||
|
return errs.ErrTokenRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteToken deletes given token from database
|
// DeleteToken deletes given token from database
|
||||||
func (s *TokenService) DeleteToken(c *core.Context, tokenRecord *models.TokenRecord) error {
|
func (s *TokenService) DeleteToken(c *core.Context, tokenRecord *models.TokenRecord) error {
|
||||||
if tokenRecord.Uid <= 0 {
|
if tokenRecord.Uid <= 0 {
|
||||||
@@ -294,12 +319,13 @@ func (s *TokenService) createToken(c *core.Context, user *models.User, tokenType
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
tokenRecord := &models.TokenRecord{
|
tokenRecord := &models.TokenRecord{
|
||||||
Uid: user.Uid,
|
Uid: user.Uid,
|
||||||
UserTokenId: s.getUserTokenId(),
|
UserTokenId: s.getUserTokenId(),
|
||||||
TokenType: tokenType,
|
TokenType: tokenType,
|
||||||
UserAgent: userAgent,
|
UserAgent: userAgent,
|
||||||
CreatedUnixTime: now.Unix(),
|
CreatedUnixTime: now.Unix(),
|
||||||
ExpiredUnixTime: now.Add(expiryDate).Unix(),
|
ExpiredUnixTime: now.Add(expiryDate).Unix(),
|
||||||
|
LastSeenUnixTime: now.Unix(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if tokenRecord.Secret, err = utils.GetRandomString(10); err != nil {
|
if tokenRecord.Secret, err = utils.GetRandomString(10); err != nil {
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ const (
|
|||||||
|
|
||||||
defaultSecretKey string = "ezbookkeeping"
|
defaultSecretKey string = "ezbookkeeping"
|
||||||
defaultTokenExpiredTime uint32 = 2592000 // 30 days
|
defaultTokenExpiredTime uint32 = 2592000 // 30 days
|
||||||
|
defaultTokenMinRefreshInterval uint32 = 86400 // 1 day
|
||||||
defaultTemporaryTokenExpiredTime uint32 = 300 // 5 minutes
|
defaultTemporaryTokenExpiredTime uint32 = 300 // 5 minutes
|
||||||
defaultEmailVerifyTokenExpiredTime uint32 = 3600 // 60 minutes
|
defaultEmailVerifyTokenExpiredTime uint32 = 3600 // 60 minutes
|
||||||
defaultPasswordResetTokenExpiredTime uint32 = 3600 // 60 minutes
|
defaultPasswordResetTokenExpiredTime uint32 = 3600 // 60 minutes
|
||||||
@@ -217,6 +218,7 @@ type Config struct {
|
|||||||
EnableTwoFactor bool
|
EnableTwoFactor bool
|
||||||
TokenExpiredTime uint32
|
TokenExpiredTime uint32
|
||||||
TokenExpiredTimeDuration time.Duration
|
TokenExpiredTimeDuration time.Duration
|
||||||
|
TokenMinRefreshInterval uint32
|
||||||
TemporaryTokenExpiredTime uint32
|
TemporaryTokenExpiredTime uint32
|
||||||
TemporaryTokenExpiredTimeDuration time.Duration
|
TemporaryTokenExpiredTimeDuration time.Duration
|
||||||
EmailVerifyTokenExpiredTime uint32
|
EmailVerifyTokenExpiredTime uint32
|
||||||
@@ -572,6 +574,12 @@ func loadSecurityConfiguration(config *Config, configFile *ini.File, sectionName
|
|||||||
|
|
||||||
config.TokenExpiredTimeDuration = time.Duration(config.TokenExpiredTime) * time.Second
|
config.TokenExpiredTimeDuration = time.Duration(config.TokenExpiredTime) * time.Second
|
||||||
|
|
||||||
|
config.TokenMinRefreshInterval = getConfigItemUint32Value(configFile, sectionName, "token_min_refresh_interval", defaultTokenMinRefreshInterval)
|
||||||
|
|
||||||
|
if config.TokenMinRefreshInterval >= config.TokenExpiredTime {
|
||||||
|
return errs.ErrInvalidTokenMinRefreshInterval
|
||||||
|
}
|
||||||
|
|
||||||
config.TemporaryTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "temporary_token_expired_time", defaultTemporaryTokenExpiredTime)
|
config.TemporaryTokenExpiredTime = getConfigItemUint32Value(configFile, sectionName, "temporary_token_expired_time", defaultTemporaryTokenExpiredTime)
|
||||||
|
|
||||||
if config.TemporaryTokenExpiredTime < 60 {
|
if config.TemporaryTokenExpiredTime < 60 {
|
||||||
|
|||||||
+5
-5
@@ -40,14 +40,14 @@ export const useTokensStore = defineStore('tokens', {
|
|||||||
services.refreshToken().then(response => {
|
services.refreshToken().then(response => {
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
|
|
||||||
|
if (data && data.success && data.result && data.result.user && isObject(data.result.user)) {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
userStore.storeUserInfo(data.result.user);
|
||||||
|
}
|
||||||
|
|
||||||
if (data && data.success && data.result && data.result.newToken) {
|
if (data && data.success && data.result && data.result.newToken) {
|
||||||
userState.updateToken(data.result.newToken);
|
userState.updateToken(data.result.newToken);
|
||||||
|
|
||||||
if (data.result.user && isObject(data.result.user)) {
|
|
||||||
const userStore = useUserStore();
|
|
||||||
userStore.storeUserInfo(data.result.user);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.result.oldTokenId) {
|
if (data.result.oldTokenId) {
|
||||||
self.revokeToken({
|
self.revokeToken({
|
||||||
tokenId: data.result.oldTokenId,
|
tokenId: data.result.oldTokenId,
|
||||||
|
|||||||
@@ -118,7 +118,7 @@
|
|||||||
{{ session.deviceType }}
|
{{ session.deviceType }}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-sm">{{ session.deviceInfo }}</td>
|
<td class="text-sm">{{ session.deviceInfo }}</td>
|
||||||
<td class="text-sm">{{ session.createdAt }}</td>
|
<td class="text-sm">{{ session.lastSeen }}</td>
|
||||||
<td class="text-sm text-right">
|
<td class="text-sm text-right">
|
||||||
<v-btn density="comfortable" color="error" variant="tonal"
|
<v-btn density="comfortable" color="error" variant="tonal"
|
||||||
:disabled="session.isCurrent || loadingSession"
|
:disabled="session.isCurrent || loadingSession"
|
||||||
@@ -210,7 +210,7 @@ export default {
|
|||||||
deviceType: this.$t(token.isCurrent ? 'Current' : 'Other Device'),
|
deviceType: this.$t(token.isCurrent ? 'Current' : 'Other Device'),
|
||||||
deviceInfo: parseDeviceInfo(token.userAgent),
|
deviceInfo: parseDeviceInfo(token.userAgent),
|
||||||
icon: this.getTokenIcon(token),
|
icon: this.getTokenIcon(token),
|
||||||
createdAt: this.$locale.formatUnixTimeToLongDateTime(this.userStore, token.createdAt)
|
lastSeen: token.lastSeen ? this.$locale.formatUnixTimeToLongDateTime(this.userStore, token.lastSeen) : '-'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<f7-icon :f7="session.icon"></f7-icon>
|
<f7-icon :f7="session.icon"></f7-icon>
|
||||||
</template>
|
</template>
|
||||||
<template #after>
|
<template #after>
|
||||||
<small>{{ session.createdAt }}</small>
|
<small>{{ session.lastSeen }}</small>
|
||||||
</template>
|
</template>
|
||||||
<f7-swipeout-actions right v-if="!session.isCurrent">
|
<f7-swipeout-actions right v-if="!session.isCurrent">
|
||||||
<f7-swipeout-button color="red" :text="$t('Log Out')" @click="revoke(session)"></f7-swipeout-button>
|
<f7-swipeout-button color="red" :text="$t('Log Out')" @click="revoke(session)"></f7-swipeout-button>
|
||||||
@@ -82,7 +82,7 @@ export default {
|
|||||||
deviceType: this.$t(token.isCurrent ? 'Current' : 'Other Device'),
|
deviceType: this.$t(token.isCurrent ? 'Current' : 'Other Device'),
|
||||||
deviceInfo: parseDeviceInfo(token.userAgent),
|
deviceInfo: parseDeviceInfo(token.userAgent),
|
||||||
icon: this.getTokenIcon(token),
|
icon: this.getTokenIcon(token),
|
||||||
createdAt: this.$locale.formatUnixTimeToLongDateTime(this.userStore, token.createdAt)
|
lastSeen: token.lastSeen ? this.$locale.formatUnixTimeToLongDateTime(this.userStore, token.lastSeen) : '-'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user