From 2c72ce5f5cee96a555f318215a0cdc13ddc554e9 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Wed, 25 Nov 2020 00:21:44 +0800 Subject: [PATCH] don't clear application lock when user token expired --- src/lib/services.js | 2 +- src/lib/userstate.js | 73 +++++++++++++++++----------- src/lib/webauthn.js | 4 +- src/views/mobile/ApplicationLock.vue | 11 ++++- src/views/mobile/Login.vue | 26 +++++++--- src/views/mobile/Settings.vue | 2 +- src/views/mobile/Unlock.vue | 14 ++++-- 7 files changed, 85 insertions(+), 47 deletions(-) diff --git a/src/lib/services.js b/src/lib/services.js index 8e4ce058..603c1a57 100644 --- a/src/lib/services.js +++ b/src/lib/services.js @@ -44,7 +44,7 @@ axios.interceptors.response.use(response => { || errorCode === 202005 // current token requires two factor authorization || errorCode === 202006 // current token does not require two factor authorization ) { - userState.clearTokenAndUserInfo(); + userState.clearTokenAndUserInfo(false); location.reload(); return Promise.reject({ processed: true }); } diff --git a/src/lib/userstate.js b/src/lib/userstate.js index 35590ec9..8800ad21 100644 --- a/src/lib/userstate.js +++ b/src/lib/userstate.js @@ -10,19 +10,21 @@ const webauthnConfigLocalStorageKey = 'lab_user_webauthn_config'; const userInfoLocalStorageKey = 'lab_user_info'; const tokenSessionStorageKey = 'lab_user_session_token'; -const appLockSecretSessionStorageKey = 'lab_user_app_lock_secret'; +const appLockStateSessionStorageKey = 'lab_user_app_lock_state'; // { 'username': '', secret: '' } function getAppLockSecret(pinCode) { const hashedPinCode = CryptoJS.SHA256(appLockSecretBaseStringPrefix + pinCode).toString(); return hashedPinCode.substr(0, 24); // put secret into user id of webauthn (user id total length must less 64 bytes) } -function getEncryptedToken(token, secret) { - return CryptoJS.AES.encrypt(token, secret).toString(); +function getEncryptedToken(token, appLockState) { + const key = CryptoJS.SHA256(`${appLockSecretBaseStringPrefix}|${appLockState.username}|${appLockState.secret}`).toString(); + return CryptoJS.AES.encrypt(token, key).toString(); } -function getDecryptedToken(encryptedToken, secret) { - const bytes = CryptoJS.AES.decrypt(encryptedToken, secret); +function getDecryptedToken(encryptedToken, appLockState) { + const key = CryptoJS.SHA256(`${appLockSecretBaseStringPrefix}|${appLockState.username}|${appLockState.secret}`).toString(); + const bytes = CryptoJS.AES.decrypt(encryptedToken, key); return bytes.toString(CryptoJS.enc.Utf8); } @@ -39,9 +41,9 @@ function getUserInfo() { return JSON.parse(data); } -function getUserAppLockSecret() { - const currentSecret = sessionStorage.getItem(appLockSecretSessionStorageKey); - return currentSecret; +function getUserAppLockState() { + const data = sessionStorage.getItem(appLockStateSessionStorageKey); + return JSON.parse(data); } function isUserLogined() { @@ -57,7 +59,7 @@ function isUserUnlocked() { return true; } - return !!sessionStorage.getItem(appLockSecretSessionStorageKey) && !!sessionStorage.getItem(tokenSessionStorageKey); + return !!sessionStorage.getItem(appLockStateSessionStorageKey) && !!sessionStorage.getItem(tokenSessionStorageKey); } function getWebAuthnCredentialId() { @@ -79,7 +81,7 @@ function clearWebAuthnConfig() { localStorage.removeItem(webauthnConfigLocalStorageKey); } -function unlockTokenByWebAuthn(credentialId, userSecret) { +function unlockTokenByWebAuthn(credentialId, userName, userSecret) { const webauthnConfigData = localStorage.getItem(webauthnConfigLocalStorageKey); const webauthnConfig = JSON.parse(webauthnConfigData); @@ -88,28 +90,37 @@ function unlockTokenByWebAuthn(credentialId, userSecret) { } const encryptedToken = localStorage.getItem(tokenLocalStorageKey); - const secret = userSecret; - const token = getDecryptedToken(encryptedToken, secret); + const appLockState = { + username: userName, + secret: userSecret + }; + const token = getDecryptedToken(encryptedToken, appLockState); - sessionStorage.setItem(appLockSecretSessionStorageKey, secret); + sessionStorage.setItem(appLockStateSessionStorageKey, JSON.stringify(appLockState)); sessionStorage.setItem(tokenSessionStorageKey, token); } -function unlockTokenByPinCode(pinCode) { +function unlockTokenByPinCode(userName, pinCode) { const encryptedToken = localStorage.getItem(tokenLocalStorageKey); - const secret = getAppLockSecret(pinCode); - const token = getDecryptedToken(encryptedToken, secret); + const appLockState = { + username: userName, + secret: getAppLockSecret(pinCode) + }; + const token = getDecryptedToken(encryptedToken, appLockState); - sessionStorage.setItem(appLockSecretSessionStorageKey, secret); + sessionStorage.setItem(appLockStateSessionStorageKey, JSON.stringify(appLockState)); sessionStorage.setItem(tokenSessionStorageKey, token); } -function encryptToken(pinCode) { +function encryptToken(userName, pinCode) { const token = localStorage.getItem(tokenLocalStorageKey); - const secret = getAppLockSecret(pinCode); - const encryptedToken = getEncryptedToken(token, secret); + const appLockState = { + username: userName, + secret: getAppLockSecret(pinCode) + }; + const encryptedToken = getEncryptedToken(token, appLockState); - sessionStorage.setItem(appLockSecretSessionStorageKey, secret); + sessionStorage.setItem(appLockStateSessionStorageKey, JSON.stringify(appLockState)); sessionStorage.setItem(tokenSessionStorageKey, token); localStorage.setItem(tokenLocalStorageKey, encryptedToken); } @@ -119,14 +130,14 @@ function decryptToken() { localStorage.setItem(tokenLocalStorageKey, token); sessionStorage.removeItem(tokenSessionStorageKey); - sessionStorage.removeItem(appLockSecretSessionStorageKey); + sessionStorage.removeItem(appLockStateSessionStorageKey); } function isCorrectPinCode(pinCode) { const secret = getAppLockSecret(pinCode); - const currentSecret = sessionStorage.getItem(appLockSecretSessionStorageKey); + const appLockState = getUserAppLockState(); - return secret === currentSecret; + return appLockState && secret === appLockState.secret; } function updateToken(token) { @@ -134,8 +145,9 @@ function updateToken(token) { if (settings.isEnableApplicationLock()) { sessionStorage.setItem(tokenSessionStorageKey, token); - const secret = sessionStorage.getItem(appLockSecretSessionStorageKey); - localStorage.setItem(tokenLocalStorageKey, getEncryptedToken(token, secret)); + const appLockState = getUserAppLockState(); + const encryptedToken = getEncryptedToken(token, appLockState); + localStorage.setItem(tokenLocalStorageKey, encryptedToken); } else { localStorage.setItem(tokenLocalStorageKey, token); } @@ -160,9 +172,12 @@ function updateTokenAndUserInfo(item) { } } -function clearTokenAndUserInfo() { +function clearTokenAndUserInfo(clearAppLockState) { + if (clearAppLockState) { + sessionStorage.removeItem(appLockStateSessionStorageKey); + } + sessionStorage.removeItem(tokenSessionStorageKey); - sessionStorage.removeItem(appLockSecretSessionStorageKey); localStorage.removeItem(tokenLocalStorageKey); localStorage.removeItem(userInfoLocalStorageKey); } @@ -170,7 +185,7 @@ function clearTokenAndUserInfo() { export default { getToken, getUserInfo, - getUserAppLockSecret, + getUserAppLockState, isUserLogined, isUserUnlocked, getWebAuthnCredentialId, diff --git a/src/lib/webauthn.js b/src/lib/webauthn.js index c169e308..39942d4a 100644 --- a/src/lib/webauthn.js +++ b/src/lib/webauthn.js @@ -39,7 +39,7 @@ function isCompletelySupported() { return window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(); } -function registerCredential({ username, nickname }, userSecret) { +function registerCredential({ username, secret }, { nickname }) { if (!window.location || !window.location.hostname) { return Promise.reject({ notSupported: true @@ -53,7 +53,7 @@ function registerCredential({ username, nickname }, userSecret) { } const challenge = utils.generateRandomString(); - const userId = `${username}|${userSecret}`; // username 32bytes(max) + userSecret 24bytes = 56bytes(max) + const userId = `${username}|${secret}`; // username 32bytes(max) + secret 24bytes = 56bytes(max) const publicKeyCredentialCreationOptions = Object.assign({}, publicKeyCredentialCreationOptionsBaseTemplate, { challenge: utils.stringToArrayBuffer(challenge), diff --git a/src/views/mobile/ApplicationLock.vue b/src/views/mobile/ApplicationLock.vue index da78563f..3e9deb4e 100644 --- a/src/views/mobile/ApplicationLock.vue +++ b/src/views/mobile/ApplicationLock.vue @@ -104,8 +104,8 @@ export default { self.$showLoading(); self.$webauthn.registerCredential( + self.$user.getUserAppLockState(), self.$user.getUserInfo(), - self.$user.getUserAppLockSecret(), ).then(({ id }) => { self.$hideLoading(); @@ -160,7 +160,14 @@ export default { return; } - this.$user.encryptToken(pinCode); + const user = this.$user.getUserInfo(); + + if (!user || !user.username) { + this.$alert('An error has occurred'); + return; + } + + this.$user.encryptToken(user.username, pinCode); this.$settings.setEnableApplicationLock(true); this.isEnableApplicationLock = true; diff --git a/src/views/mobile/Login.vue b/src/views/mobile/Login.vue index 62234028..cdff7b85 100644 --- a/src/views/mobile/Login.vue +++ b/src/views/mobile/Login.vue @@ -185,10 +185,15 @@ export default { return; } - if (self.$settings.isEnableApplicationLock()) { - self.$settings.setEnableApplicationLock(false); - self.$settings.setEnableApplicationLockWebAuthn(false); - self.$user.clearWebAuthnConfig(); + if (self.$settings.isEnableApplicationLock() || self.$user.getUserAppLockState()) { + const appLockState = self.$user.getUserAppLockState(); + + if (!appLockState || appLockState.username !== data.result.user.username) { + self.$user.clearTokenAndUserInfo(true); + self.$settings.setEnableApplicationLock(false); + self.$settings.setEnableApplicationLockWebAuthn(false); + self.$user.clearWebAuthnConfig(); + } } self.$user.updateTokenAndUserInfo(data.result); @@ -264,10 +269,15 @@ export default { return; } - if (self.$settings.isEnableApplicationLock()) { - self.$settings.setEnableApplicationLock(false); - self.$settings.setEnableApplicationLockWebAuthn(false); - self.$user.clearWebAuthnConfig(); + if (self.$settings.isEnableApplicationLock() || self.$user.getUserAppLockState()) { + const appLockState = self.$user.getUserAppLockState(); + + if (!appLockState || appLockState.username !== data.result.user.username) { + self.$user.clearTokenAndUserInfo(true); + self.$settings.setEnableApplicationLock(false); + self.$settings.setEnableApplicationLockWebAuthn(false); + self.$user.clearWebAuthnConfig(); + } } self.$user.updateTokenAndUserInfo(data.result); diff --git a/src/views/mobile/Settings.vue b/src/views/mobile/Settings.vue index 02f531a2..c2e94ac2 100644 --- a/src/views/mobile/Settings.vue +++ b/src/views/mobile/Settings.vue @@ -184,7 +184,7 @@ export default { return; } - self.$user.clearTokenAndUserInfo(); + self.$user.clearTokenAndUserInfo(true); self.$user.clearWebAuthnConfig(); self.$exchangeRates.clearExchangeRates(); self.$settings.clearSettings(); diff --git a/src/views/mobile/Unlock.vue b/src/views/mobile/Unlock.vue index 479ee012..bdccde34 100644 --- a/src/views/mobile/Unlock.vue +++ b/src/views/mobile/Unlock.vue @@ -53,10 +53,10 @@ export default { self.$webauthn.verifyCredential( self.$user.getUserInfo(), self.$user.getWebAuthnCredentialId() - ).then(({ id, userSecret }) => { + ).then(({ id, userName, userSecret }) => { self.$hideLoading(); - self.$user.unlockTokenByWebAuthn(id, userSecret); + self.$user.unlockTokenByWebAuthn(id, userName, userSecret); self.$services.refreshToken(); if (self.$settings.isAutoUpdateExchangeRatesData()) { @@ -92,9 +92,15 @@ export default { } const router = this.$f7router; + const user = this.$user.getUserInfo(); + + if (!user || !user.username) { + this.$alert('An error has occurred'); + return; + } try { - this.$user.unlockTokenByPinCode(this.pinCode); + this.$user.unlockTokenByPinCode(user.username, this.pinCode); this.$services.refreshToken(); if (this.$settings.isAutoUpdateExchangeRatesData()) { @@ -112,7 +118,7 @@ export default { const router = self.$f7router; self.$confirm('Are you sure you want to re-login?', () => { - self.$user.clearTokenAndUserInfo(); + self.$user.clearTokenAndUserInfo(true); self.$user.clearWebAuthnConfig(); self.$exchangeRates.clearExchangeRates(); self.$settings.clearSettings();