mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-20 01:34:24 +08:00
add device & sessions page
This commit is contained in:
Generated
+18
@@ -7404,6 +7404,11 @@
|
|||||||
"minimist": "^1.2.5"
|
"minimist": "^1.2.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"moment": {
|
||||||
|
"version": "2.29.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||||
|
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
|
||||||
|
},
|
||||||
"move-concurrently": {
|
"move-concurrently": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||||
@@ -11046,6 +11051,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.22.0.tgz",
|
||||||
"integrity": "sha512-2tYS0bYDPJHKAWPy01aDe5h3wcXDGjhJmboHKOfi2OEYR+6gyXaIzdua1smZCQwOeWdlGsLntwdIgkXWrnLjxg=="
|
"integrity": "sha512-2tYS0bYDPJHKAWPy01aDe5h3wcXDGjhJmboHKOfi2OEYR+6gyXaIzdua1smZCQwOeWdlGsLntwdIgkXWrnLjxg=="
|
||||||
},
|
},
|
||||||
|
"vue-i18n-filter": {
|
||||||
|
"version": "0.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-i18n-filter/-/vue-i18n-filter-0.1.6.tgz",
|
||||||
|
"integrity": "sha512-zYo06SmMZtJwTkEd2l+t1jCH8lrtTbkwwoz/6XLvFzNF4tGd5CPOhfIxLLv0wf9vxMVYE14KRuzOZzw26qfRxA=="
|
||||||
|
},
|
||||||
"vue-loader": {
|
"vue-loader": {
|
||||||
"version": "15.9.3",
|
"version": "15.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.3.tgz",
|
||||||
@@ -11148,6 +11158,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vue-moment": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-moment/-/vue-moment-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-Gzisqpg82ItlrUyiD9d0Kfru+JorW2o4mQOH06lEDZNgxci0tv/fua1Hl0bo4DozDV2JK1r52Atn/8QVCu8qQw==",
|
||||||
|
"requires": {
|
||||||
|
"moment": "^2.19.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"vue-style-loader": {
|
"vue-style-loader": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz",
|
||||||
|
|||||||
+4
-1
@@ -14,8 +14,11 @@
|
|||||||
"framework7-icons": "^3.0.1",
|
"framework7-icons": "^3.0.1",
|
||||||
"framework7-vue": "^5.7.13",
|
"framework7-vue": "^5.7.13",
|
||||||
"js-cookie": "^2.2.1",
|
"js-cookie": "^2.2.1",
|
||||||
|
"moment": "^2.29.1",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-i18n": "^8.22.0"
|
"vue-i18n": "^8.22.0",
|
||||||
|
"vue-i18n-filter": "^0.1.6",
|
||||||
|
"vue-moment": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "~4.5.0",
|
"@vue/cli-plugin-babel": "~4.5.0",
|
||||||
|
|||||||
+12
-10
@@ -5,14 +5,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrTokenGenerating = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 0, http.StatusInternalServerError, "failed to generate token")
|
ErrTokenGenerating = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 0, http.StatusInternalServerError, "failed to generate token")
|
||||||
ErrUnauthorizedAccess = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 1, http.StatusUnauthorized, "unauthorized access")
|
ErrUnauthorizedAccess = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 1, http.StatusUnauthorized, "unauthorized access")
|
||||||
ErrTokenExpired = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 2, http.StatusUnauthorized, "token is expired")
|
ErrCurrentInvalidToken = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 2, http.StatusUnauthorized, "current token is invalid")
|
||||||
ErrInvalidToken = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 3, http.StatusUnauthorized, "token is invalid")
|
ErrCurrentTokenExpired = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 3, http.StatusUnauthorized, "current token is expired")
|
||||||
ErrInvalidUserTokenId = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 4, http.StatusUnauthorized, "user token id is invalid")
|
ErrCurrentInvalidTokenType = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 4, http.StatusUnauthorized, "current token type is invalid")
|
||||||
ErrInvalidTokenId = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 5, http.StatusUnauthorized, "token id is invalid")
|
ErrCurrentTokenRequire2FA = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 5, http.StatusUnauthorized, "current token requires two factor authorization")
|
||||||
ErrTokenRecordNotFound = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 6, http.StatusUnauthorized, "token is not found")
|
ErrCurrentTokenNotRequire2FA = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 6, http.StatusUnauthorized, "current token does not require two factor authorization")
|
||||||
ErrInvalidTokenType = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 7, http.StatusUnauthorized, "token type is invalid")
|
ErrInvalidToken = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 7, http.StatusUnauthorized, "token is invalid")
|
||||||
ErrTokenRequire2FA = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 8, http.StatusUnauthorized, "token requires two factor authorization")
|
ErrInvalidTokenId = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 8, http.StatusUnauthorized, "token id is invalid")
|
||||||
ErrTokenNotRequire2FA = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 9, http.StatusUnauthorized, "token does not require two factor authorization")
|
ErrInvalidUserTokenId = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 9, http.StatusUnauthorized, "user token id is invalid")
|
||||||
|
ErrTokenRecordNotFound = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 10, http.StatusUnauthorized, "token is not found")
|
||||||
|
ErrTokenExpired = NewNormalError(NORMAL_SUBCATEGORY_TOKEN, 11, http.StatusUnauthorized, "token is expired")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ func JWTAuthorization(c *core.Context) {
|
|||||||
|
|
||||||
if claims.Type == core.USER_TOKEN_TYPE_REQUIRE_2FA {
|
if claims.Type == core.USER_TOKEN_TYPE_REQUIRE_2FA {
|
||||||
log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%s\" token requires 2fa", claims.Id)
|
log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%s\" token requires 2fa", claims.Id)
|
||||||
utils.PrintErrorResult(c, errs.ErrTokenRequire2FA)
|
utils.PrintErrorResult(c, errs.ErrCurrentTokenRequire2FA)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if claims.Type != core.USER_TOKEN_TYPE_NORMAL {
|
if claims.Type != core.USER_TOKEN_TYPE_NORMAL {
|
||||||
log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%s\" token type is invalid", claims.Id)
|
log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%s\" token type is invalid", claims.Id)
|
||||||
utils.PrintErrorResult(c, errs.ErrInvalidTokenType)
|
utils.PrintErrorResult(c, errs.ErrCurrentInvalidTokenType)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ func JWTTwoFactorAuthorization(c *core.Context) {
|
|||||||
|
|
||||||
if claims.Type != core.USER_TOKEN_TYPE_REQUIRE_2FA {
|
if claims.Type != core.USER_TOKEN_TYPE_REQUIRE_2FA {
|
||||||
log.WarnfWithRequestId(c, "[authorization.JWTTwoFactorAuthorization] user \"uid:%s\" token is not need two factor authorization", claims.Id)
|
log.WarnfWithRequestId(c, "[authorization.JWTTwoFactorAuthorization] user \"uid:%s\" token is not need two factor authorization", claims.Id)
|
||||||
utils.PrintErrorResult(c, errs.ErrTokenNotRequire2FA)
|
utils.PrintErrorResult(c, errs.ErrCurrentTokenNotRequire2FA)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,17 +62,17 @@ func getTokenClaims(c *core.Context) (*core.UserTokenClaims, *errs.Error) {
|
|||||||
|
|
||||||
if !token.Valid {
|
if !token.Valid {
|
||||||
log.WarnfWithRequestId(c, "[authorization.getTokenClaims] token is invalid")
|
log.WarnfWithRequestId(c, "[authorization.getTokenClaims] token is invalid")
|
||||||
return nil, errs.ErrInvalidToken
|
return nil, errs.ErrCurrentInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
if !claims.VerifyExpiresAt(time.Now().Unix(), true) {
|
if !claims.VerifyExpiresAt(time.Now().Unix(), true) {
|
||||||
log.WarnfWithRequestId(c, "[authorization.getTokenClaims] token is expired")
|
log.WarnfWithRequestId(c, "[authorization.getTokenClaims] token is expired")
|
||||||
return nil, errs.ErrTokenExpired
|
return nil, errs.ErrCurrentTokenExpired
|
||||||
}
|
}
|
||||||
|
|
||||||
if claims.Id == "" {
|
if claims.Id == "" {
|
||||||
log.WarnfWithRequestId(c, "[authorization.getTokenClaims] user id in token is empty")
|
log.WarnfWithRequestId(c, "[authorization.getTokenClaims] user id in token is empty")
|
||||||
return nil, errs.ErrInvalidToken
|
return nil, errs.ErrCurrentInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
return claims, nil
|
return claims, nil
|
||||||
|
|||||||
+17
-2
@@ -32,10 +32,15 @@ axios.interceptors.request.use(config => {
|
|||||||
axios.interceptors.response.use(response => {
|
axios.interceptors.response.use(response => {
|
||||||
return response;
|
return response;
|
||||||
}, error => {
|
}, error => {
|
||||||
if (error.response && error.response.data && error.response.data.errorCode) {
|
if (error.response && !error.response.config.ignoreError && error.response.data && error.response.data.errorCode) {
|
||||||
const errorCode = error.response.data.errorCode;
|
const errorCode = error.response.data.errorCode;
|
||||||
|
|
||||||
if (202001 <= errorCode && errorCode <= 202008) { // unauthorized access or token is invalid
|
if (errorCode === 202001 // unauthorized access
|
||||||
|
&& errorCode <= 202002 // current token is invalid
|
||||||
|
&& errorCode <= 202003 // current token is expired
|
||||||
|
&& errorCode <= 202004 // current token type is invalid
|
||||||
|
&& errorCode <= 202005 // current token requires two factor authorization
|
||||||
|
&& errorCode <= 202006) { // current token does not require two factor authorization
|
||||||
userState.clearToken();
|
userState.clearToken();
|
||||||
location.reload();
|
location.reload();
|
||||||
return Promise.reject({ processed: true });
|
return Promise.reject({ processed: true });
|
||||||
@@ -98,6 +103,8 @@ export default {
|
|||||||
if (data.result.oldTokenId) {
|
if (data.result.oldTokenId) {
|
||||||
axios.post('v1/tokens/revoke.json', {
|
axios.post('v1/tokens/revoke.json', {
|
||||||
tokenId: data.result.oldTokenId
|
tokenId: data.result.oldTokenId
|
||||||
|
}, {
|
||||||
|
ignoreError: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,4 +126,12 @@ export default {
|
|||||||
password
|
password
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
getTokens: () => {
|
||||||
|
return axios.get('v1/tokens/list.json');
|
||||||
|
},
|
||||||
|
revokeToken: ({ tokenId }) => {
|
||||||
|
return axios.post('v1/tokens/revoke.json', {
|
||||||
|
tokenId
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+18
-5
@@ -4,6 +4,11 @@ export default {
|
|||||||
'title': 'lab account book',
|
'title': 'lab account book',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'format': {
|
||||||
|
'datetime': {
|
||||||
|
'long': 'MM/DD/YYYY HH:mm:ss',
|
||||||
|
}
|
||||||
|
},
|
||||||
'error': {
|
'error': {
|
||||||
'system error': 'System Error',
|
'system error': 'System Error',
|
||||||
'api not found': 'Failed to request api',
|
'api not found': 'Failed to request api',
|
||||||
@@ -11,14 +16,16 @@ export default {
|
|||||||
'operation failed': 'Operation failed',
|
'operation failed': 'Operation failed',
|
||||||
'nothing will be updated': 'Nothing will be updated',
|
'nothing will be updated': 'Nothing will be updated',
|
||||||
'unauthorized access': 'Unauthorized access',
|
'unauthorized access': 'Unauthorized access',
|
||||||
'token is expired': 'Token is expired',
|
'current token is invalid': 'Current token is invalid',
|
||||||
|
'current token is expired': 'Current token is expired',
|
||||||
|
'current token type is invalid': 'Current token type is invalid',
|
||||||
|
'current token requires two factor authorization': 'Current token requires two factor authorization',
|
||||||
|
'current token does not require two factor authorization': 'Current token does not require two factor authorization',
|
||||||
'token is invalid': 'Token is invalid',
|
'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 id is invalid': 'Token id is invalid',
|
||||||
|
'user token id is invalid': 'User token id is invalid',
|
||||||
'token is not found': 'Token is not found',
|
'token is not found': 'Token is not found',
|
||||||
'token type is invalid': 'Token type is invalid',
|
'token is expired': 'Token is expired',
|
||||||
'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',
|
'user id is invalid': 'User id is invalid',
|
||||||
'username is empty': 'Username is empty',
|
'username is empty': 'Username is empty',
|
||||||
'email is empty': 'Email is empty',
|
'email is empty': 'Email is empty',
|
||||||
@@ -107,6 +114,12 @@ export default {
|
|||||||
'Nothing has been modified': 'Nothing has been modified',
|
'Nothing has been modified': 'Nothing has been modified',
|
||||||
'Your profile has been successfully updated': 'Your profile has been successfully updated',
|
'Your profile has been successfully updated': 'Your profile has been successfully updated',
|
||||||
'Unable to update user profile': 'Unable to update user profile',
|
'Unable to update user profile': 'Unable to update user profile',
|
||||||
|
'Device & Sessions': 'Device & Sessions',
|
||||||
|
'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',
|
||||||
'Log Out': 'Log Out',
|
'Log Out': 'Log Out',
|
||||||
'Are you sure you want to log out?': 'Are you sure you want to log out?',
|
'Are you sure you want to log out?': 'Are you sure you want to log out?',
|
||||||
'Unable to logout': 'Unable to logout',
|
'Unable to logout': 'Unable to logout',
|
||||||
|
|||||||
+18
-5
@@ -4,6 +4,11 @@ export default {
|
|||||||
'title': 'lab 轻记账',
|
'title': 'lab 轻记账',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'format': {
|
||||||
|
'datetime': {
|
||||||
|
'long': 'YYYY年MM月DD日 HH:mm:ss',
|
||||||
|
}
|
||||||
|
},
|
||||||
'error': {
|
'error': {
|
||||||
'system error': '系统错误',
|
'system error': '系统错误',
|
||||||
'api not found': '接口调用失败',
|
'api not found': '接口调用失败',
|
||||||
@@ -11,14 +16,16 @@ export default {
|
|||||||
'operation failed': '操作失败',
|
'operation failed': '操作失败',
|
||||||
'nothing will be updated': '没有内容更新',
|
'nothing will be updated': '没有内容更新',
|
||||||
'unauthorized access': '未授权的登录',
|
'unauthorized access': '未授权的登录',
|
||||||
'token is expired': '认证令牌已过期',
|
'current token is invalid': '当前认证令牌无效',
|
||||||
|
'current token is expired': '当前认证令牌已过期',
|
||||||
|
'current token type is invalid': '当前认证令牌类型无效',
|
||||||
|
'current token requires two factor authorization': '当前认证令牌需要两步验证',
|
||||||
|
'current token does not require two factor authorization': '当前认证令牌不需要两步验证',
|
||||||
'token is invalid': '认证令牌无效',
|
'token is invalid': '认证令牌无效',
|
||||||
'user token id is invalid': '用户认证令牌ID无效',
|
|
||||||
'token id is invalid': '认证令牌ID无效',
|
'token id is invalid': '认证令牌ID无效',
|
||||||
|
'user token id is invalid': '用户认证令牌ID无效',
|
||||||
'token is not found': '认证令牌不存在',
|
'token is not found': '认证令牌不存在',
|
||||||
'token type is invalid': '认证令牌类型无效',
|
'token is expired': '认证令牌已过期',
|
||||||
'token requires two factor authorization': '认证令牌需要两步验证',
|
|
||||||
'token does not require two factor authorization': '认证令牌不需要两步验证',
|
|
||||||
'user id is invalid': '用户ID无效',
|
'user id is invalid': '用户ID无效',
|
||||||
'username is empty': '用户名为空',
|
'username is empty': '用户名为空',
|
||||||
'email is empty': '电子邮箱为空',
|
'email is empty': '电子邮箱为空',
|
||||||
@@ -107,6 +114,12 @@ export default {
|
|||||||
'Nothing has been modified': '没有修改的项目',
|
'Nothing has been modified': '没有修改的项目',
|
||||||
'Your profile has been successfully updated': '您的用户信息更新成功',
|
'Your profile has been successfully updated': '您的用户信息更新成功',
|
||||||
'Unable to update user profile': '无法更新用户信息',
|
'Unable to update user profile': '无法更新用户信息',
|
||||||
|
'Device & Sessions': '设备和会话',
|
||||||
|
'Unable to get session list': '无法获取会话列表',
|
||||||
|
'Current': '当前',
|
||||||
|
'Other Device': '其他设备',
|
||||||
|
'Are you sure you want to logout from this session?': '您确定是否要退出该会话?',
|
||||||
|
'Unable to logout from this session': '无法退出该会话',
|
||||||
'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': '无法退出登录',
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import VueI18n from 'vue-i18n';
|
import VueI18n from 'vue-i18n';
|
||||||
|
import VueI18nFilter from 'vue-i18n-filter'
|
||||||
import Framework7 from 'framework7/framework7.esm.bundle.js';
|
import Framework7 from 'framework7/framework7.esm.bundle.js';
|
||||||
import Framework7Vue from 'framework7-vue/framework7-vue.esm.bundle.js';
|
import Framework7Vue from 'framework7-vue/framework7-vue.esm.bundle.js';
|
||||||
|
import VueMoment from 'vue-moment';
|
||||||
|
|
||||||
|
import moment from 'moment';
|
||||||
|
import 'moment/min/locales';
|
||||||
|
|
||||||
import 'framework7/css/framework7.bundle.css';
|
import 'framework7/css/framework7.bundle.css';
|
||||||
import 'framework7-icons';
|
import 'framework7-icons';
|
||||||
@@ -13,6 +18,8 @@ import userstate from './lib/userstate.js';
|
|||||||
import App from './Mobile.vue';
|
import App from './Mobile.vue';
|
||||||
|
|
||||||
Vue.use(VueI18n);
|
Vue.use(VueI18n);
|
||||||
|
Vue.use(VueI18nFilter);
|
||||||
|
Vue.use(VueMoment, { moment });
|
||||||
Framework7.use(Framework7Vue);
|
Framework7.use(Framework7Vue);
|
||||||
|
|
||||||
const i18n = new VueI18n(getI18nOptions());
|
const i18n = new VueI18n(getI18nOptions());
|
||||||
@@ -27,6 +34,7 @@ Vue.prototype.$setLanguage = function (locale) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
i18n.locale = locale;
|
i18n.locale = locale;
|
||||||
|
moment.locale(locale);
|
||||||
services.setLocale(locale);
|
services.setLocale(locale);
|
||||||
document.querySelector('html').setAttribute('lang', locale);
|
document.querySelector('html').setAttribute('lang', locale);
|
||||||
return locale;
|
return locale;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import LoginPage from '../views/mobile/Login.vue';
|
|||||||
import SignUpPage from '../views/mobile/Signup.vue';
|
import SignUpPage from '../views/mobile/Signup.vue';
|
||||||
import SettingsPage from '../views/mobile/Settings.vue';
|
import SettingsPage from '../views/mobile/Settings.vue';
|
||||||
import UserProfilePage from "../views/mobile/users/UserProfile.vue";
|
import UserProfilePage from "../views/mobile/users/UserProfile.vue";
|
||||||
|
import SessionListPage from "../views/mobile/users/SessionList.vue";
|
||||||
|
|
||||||
function checkLogin(to, from, resolve, reject) {
|
function checkLogin(to, from, resolve, reject) {
|
||||||
const router = this;
|
const router = this;
|
||||||
@@ -66,6 +67,11 @@ const routes = [
|
|||||||
component: UserProfilePage,
|
component: UserProfilePage,
|
||||||
beforeEnter: checkLogin
|
beforeEnter: checkLogin
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/user/sessions',
|
||||||
|
component: SessionListPage,
|
||||||
|
beforeEnter: checkLogin
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '(.*)',
|
path: '(.*)',
|
||||||
redirect: '/'
|
redirect: '/'
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
<f7-block-title>{{ userNickName }}</f7-block-title>
|
<f7-block-title>{{ userNickName }}</f7-block-title>
|
||||||
<f7-list>
|
<f7-list>
|
||||||
<f7-list-item :title="$t('User Profile')" link="/user/profile"></f7-list-item>
|
<f7-list-item :title="$t('User Profile')" link="/user/profile"></f7-list-item>
|
||||||
|
<f7-list-item :title="$t('Two-Factor Authentication')" link="/user/2fa/overview"></f7-list-item>
|
||||||
|
<f7-list-item :title="$t('Device & Sessions')" link="/user/sessions"></f7-list-item>
|
||||||
<f7-list-button @click="logout">{{ $t('Log Out') }}</f7-list-button>
|
<f7-list-button @click="logout">{{ $t('Log Out') }}</f7-list-button>
|
||||||
</f7-list>
|
</f7-list>
|
||||||
<f7-block-title>{{ $t('Application') }}</f7-block-title>
|
<f7-block-title>{{ $t('Application') }}</f7-block-title>
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<f7-page>
|
||||||
|
<f7-navbar :title="$t('Device & Sessions')" :back-link="$t('Back')"></f7-navbar>
|
||||||
|
|
||||||
|
<f7-list media-list>
|
||||||
|
<f7-list-item swipeout v-for="token in tokens" :key="token.tokenId" :id="token | tokenDomId" :title="token | tokenTitle | t" :after="token.createdAt | moment($t('format.datetime.long'))" :text="token.userAgent">
|
||||||
|
<f7-swipeout-actions right v-if="!token.isCurrent" >
|
||||||
|
<f7-swipeout-button color="red" :text="$t('Log Out')" @click="revoke(token)"></f7-swipeout-button>
|
||||||
|
</f7-swipeout-actions>
|
||||||
|
</f7-list-item>
|
||||||
|
</f7-list>
|
||||||
|
</f7-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tokens: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
const self = this;
|
||||||
|
const app = self.$f7;
|
||||||
|
const router = self.$f7router;
|
||||||
|
|
||||||
|
app.preloader.show();
|
||||||
|
|
||||||
|
self.$services.getTokens().then(response => {
|
||||||
|
app.preloader.hide();
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
|
if (!data || !data.success || !data.result) {
|
||||||
|
self.$alert('Unable to get session list', () => {
|
||||||
|
router.back();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tokens = data.result;
|
||||||
|
}).catch(error => {
|
||||||
|
app.preloader.hide();
|
||||||
|
|
||||||
|
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||||
|
self.$alert({ error: error.response.data }, () => {
|
||||||
|
router.back();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.$alert('Unable to get session list', () => {
|
||||||
|
router.back();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
revoke(token) {
|
||||||
|
const self = this;
|
||||||
|
const app = self.$f7;
|
||||||
|
const $$ = app.$;
|
||||||
|
|
||||||
|
self.$confirm('Are you sure you want to logout from this session?', () => {
|
||||||
|
app.preloader.show();
|
||||||
|
|
||||||
|
self.$services.revokeToken({
|
||||||
|
tokenId: token.tokenId
|
||||||
|
}).then(response => {
|
||||||
|
app.preloader.hide();
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
|
if (!data || !data.success || !data.result) {
|
||||||
|
self.$alert('Unable to logout from this session');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.swipeout.delete($$(`#${self.$options.filters.tokenDomId(token)}`), () => {
|
||||||
|
for (let i = 0; i < self.tokens.length; i++) {
|
||||||
|
if (self.tokens[i].tokenId === token.tokenId) {
|
||||||
|
self.tokens.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).catch(error => {
|
||||||
|
app.preloader.hide();
|
||||||
|
|
||||||
|
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||||
|
self.$alert({error: error.response.data});
|
||||||
|
} else {
|
||||||
|
self.$alert('Unable to logout from this session');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
tokenTitle(token) {
|
||||||
|
if (token.isCurrent) {
|
||||||
|
return 'Current';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Other Device';
|
||||||
|
},
|
||||||
|
tokenDomId(token) {
|
||||||
|
return 'token_' + token.tokenId.replace(/:/g, '_');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user