add device & sessions page

This commit is contained in:
MaysWind
2020-10-31 16:19:08 +08:00
parent 0edef6bc8f
commit 34726ffa8b
11 changed files with 216 additions and 29 deletions
+17 -2
View File
@@ -32,10 +32,15 @@ axios.interceptors.request.use(config => {
axios.interceptors.response.use(response => {
return response;
}, 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;
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();
location.reload();
return Promise.reject({ processed: true });
@@ -98,6 +103,8 @@ export default {
if (data.result.oldTokenId) {
axios.post('v1/tokens/revoke.json', {
tokenId: data.result.oldTokenId
}, {
ignoreError: true
});
}
}
@@ -119,4 +126,12 @@ export default {
password
});
},
getTokens: () => {
return axios.get('v1/tokens/list.json');
},
revokeToken: ({ tokenId }) => {
return axios.post('v1/tokens/revoke.json', {
tokenId
});
},
};
+18 -5
View File
@@ -4,6 +4,11 @@ export default {
'title': 'lab account book',
}
},
'format': {
'datetime': {
'long': 'MM/DD/YYYY HH:mm:ss',
}
},
'error': {
'system error': 'System Error',
'api not found': 'Failed to request api',
@@ -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': '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',
'user token id is invalid': 'User 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 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',
'token is expired': 'Token is expired',
'user id is invalid': 'User id is invalid',
'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': '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',
'Are you sure you want to log out?': 'Are you sure you want to log out?',
'Unable to logout': 'Unable to logout',
+18 -5
View File
@@ -4,6 +4,11 @@ export default {
'title': 'lab 轻记账',
}
},
'format': {
'datetime': {
'long': 'YYYY年MM月DD日 HH:mm:ss',
}
},
'error': {
'system error': '系统错误',
'api not found': '接口调用失败',
@@ -11,14 +16,16 @@ export default {
'operation failed': '操作失败',
'nothing will be updated': '没有内容更新',
'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': '认证令牌无效',
'user token id is invalid': '用户认证令牌ID无效',
'token id is invalid': '认证令牌ID无效',
'user 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': '认证令牌不需要两步验证',
'token is expired': '认证令牌已过期',
'user id is invalid': '用户ID无效',
'username is empty': '用户名为空',
'email is empty': '电子邮箱为空',
@@ -107,6 +114,12 @@ export default {
'Nothing has been modified': '没有修改的项目',
'Your profile has been successfully updated': '您的用户信息更新成功',
'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': '退出登录',
'Are you sure you want to log out?': '您确定是否要退出登录?',
'Unable to logout': '无法退出登录',
+8
View File
@@ -1,7 +1,12 @@
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import VueI18nFilter from 'vue-i18n-filter'
import Framework7 from 'framework7/framework7.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-icons';
@@ -13,6 +18,8 @@ import userstate from './lib/userstate.js';
import App from './Mobile.vue';
Vue.use(VueI18n);
Vue.use(VueI18nFilter);
Vue.use(VueMoment, { moment });
Framework7.use(Framework7Vue);
const i18n = new VueI18n(getI18nOptions());
@@ -27,6 +34,7 @@ Vue.prototype.$setLanguage = function (locale) {
}
i18n.locale = locale;
moment.locale(locale);
services.setLocale(locale);
document.querySelector('html').setAttribute('lang', locale);
return locale;
+6
View File
@@ -7,6 +7,7 @@ import LoginPage from '../views/mobile/Login.vue';
import SignUpPage from '../views/mobile/Signup.vue';
import SettingsPage from '../views/mobile/Settings.vue';
import UserProfilePage from "../views/mobile/users/UserProfile.vue";
import SessionListPage from "../views/mobile/users/SessionList.vue";
function checkLogin(to, from, resolve, reject) {
const router = this;
@@ -66,6 +67,11 @@ const routes = [
component: UserProfilePage,
beforeEnter: checkLogin
},
{
path: '/user/sessions',
component: SessionListPage,
beforeEnter: checkLogin
},
{
path: '(.*)',
redirect: '/'
+2
View File
@@ -4,6 +4,8 @@
<f7-block-title>{{ userNickName }}</f7-block-title>
<f7-list>
<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>
<f7-block-title>{{ $t('Application') }}</f7-block-title>
+107
View File
@@ -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>