Skip to content

Commit

Permalink
idle timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
asika32764 committed Mar 24, 2024
1 parent a52ae6a commit e6229da
Show file tree
Hide file tree
Showing 11 changed files with 115 additions and 81 deletions.
2 changes: 2 additions & 0 deletions .env.dist
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ VITE_BASE_URL=
VITE_API_ENDPOINT=
VITE_TEST_USERNAME=
VITE_TEST_PASSWORD=
# 5min
VITE_IDLE_TIMEOUT=300
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"dayjs": "^1.11.10",
"eslint": "^8.35.0",
"eslint-plugin-vue": "^9.9.0",
"idle-timeout": "^2.0.1",
"jsdom": "^22.1.0",
"libsodium-wrappers": "^0.7.13",
"lodash-es": "^4.17.21",
Expand Down
6 changes: 5 additions & 1 deletion src/components/layout/HeaderCondense.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
<script setup lang="ts">
import { IonHeader, IonTitle, IonToolbar } from '@ionic/vue';
defineProps<{
color?: string;
}>();
</script>

<template>
<ion-header collapse="condense">
<ion-toolbar>
<ion-toolbar :color>
<ion-title size="large">
<slot></slot>
</ion-title>
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/MainLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ withDefaults(
</ion-header>

<ion-content :color fullscreen>
<HeaderCondense v-if="title && headerCondense">
<HeaderCondense v-if="title && headerCondense" :color>
{{ title }}
</HeaderCondense>

Expand Down
5 changes: 4 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { isLogin } from '@/store/main-store';
import sodium from 'libsodium-wrappers';
import { createApp } from 'vue'
import { createApp, nextTick } from 'vue';
import App from './App.vue'
import router from './router';
import lockScreenService from '@/service/lock-screen-service';

import('@asika32764/vue-animate/dist/vue-animate.css');

Expand Down Expand Up @@ -36,6 +37,8 @@ router.isReady().then(async () => {

app.mount('#app');

lockScreenService.listenIdleTimeout();

if (!isLogin.value) {
router.replace('/auth/login');
} else {
Expand Down
39 changes: 0 additions & 39 deletions src/service/idle-service.ts

This file was deleted.

49 changes: 44 additions & 5 deletions src/service/lock-screen-service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import router from '@/router';
import encryptionService from '@/service/encryption-service';
import { isLock, isManuallyLock, kekStorage, saltStorage } from '@/store/main-store';
import { enableBiometricsOption } from '@/store/options-store';
import { isLock, noInstantUnlock, kekStorage, saltStorage } from '@/store/main-store';
import { simpleAlert } from '@/utilities/alert';
import secretToolkit, { Encoder } from '@/utilities/secret-toolkit';
import {
Expand All @@ -11,23 +10,35 @@ import {
} from '@aparajita/capacitor-biometric-auth';
import { Capacitor } from '@capacitor/core';
import { timingSafeEquals } from '@windwalker-io/srp';
import idleTimeout from 'idle-timeout';
import IdleTimeout from 'idle-timeout/dist/IdleTimeout';

const IDLE_TIMEOUT = (Number(import.meta.env.VITE_IDLE_TIMEOUT) || (5 * 60)) * 1000;

export default new class {
idleInstance?: IdleTimeout;

async lock() {
isLock.value = true;
const idleInstance = this.getIdleInstance();
idleInstance.pause();

router.replace({ name: 'lock' });
}

async unlock() {
isLock.value = false;
isManuallyLock.value = false;
noInstantUnlock.value = false;

const idleInstance = this.getIdleInstance();
idleInstance.reset();
idleInstance.resume();
}

async passwordAuthenticate(password: string) {
const kek = secretToolkit.encode(
await encryptionService.deriveKek(password, saltStorage.value),
Encoder.HEX
Encoder.HEX,
);

if (!timingSafeEquals(kekStorage.value, kek)) {
Expand Down Expand Up @@ -70,5 +81,33 @@ export default new class {
androidBiometryStrength: AndroidBiometryStrength.weak,
});
}
}

listenIdleTimeout() {
this.getIdleInstance();
}

getIdleInstance() {
return this.idleInstance = this.idleInstance || idleTimeout(
() => {
// if (isLock.value) {
// return;
// }

console.log('Idle timeout, lock screen.');

noInstantUnlock.value = true;

this.lock();
},
{
element: document.body,
timeout: IDLE_TIMEOUT,
loop: false
}
);
}

routerLock() {
//
}
};
2 changes: 1 addition & 1 deletion src/store/main-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ export const accountsStorage = useLocalStorage<Account<string>[]>('@authman:acco

// Lock
export const isLock = ref(true);
export const isManuallyLock = ref(false);
export const noInstantUnlock = ref(false);
79 changes: 50 additions & 29 deletions src/views/LockScreen.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import logo from '@/assets/images/logo-sq-w.svg';
import lockScreenService from '@/service/lock-screen-service';
import { isManuallyLock, userStorage } from '@/store/main-store';
import { noInstantUnlock, userStorage } from '@/store/main-store';
import { enableBiometricsOption } from '@/store/options-store';
import { simpleToast } from '@/utilities/alert';
import useLoading from '@/utilities/loading';
import { headShake } from '@asika32764/vue-animate';
Expand All @@ -16,14 +17,15 @@ import {
IonContent,
IonInput,
IonPage,
IonSpinner,
IonSpinner, onIonViewWillEnter,
useBackButton,
useIonRouter,
} from '@ionic/vue';
import { type ComponentPublicInstance, onMounted, ref } from 'vue';
const router = useIonRouter();
const user = userStorage.value;
const unlockMode = ref<'biometrics' | 'password'>('password');
if (!user) {
router.replace({ name: 'login' });
Expand All @@ -33,16 +35,19 @@ useBackButton(500, (e) => {
console.log('Android back button', e);
});
onMounted(async () => {
onIonViewWillEnter(async () => {
password.value = import.meta.env.VITE_TEST_PASSWORD || '';
unlockMode.value = enableBiometricsOption.value ? 'biometrics' : 'password';
if (!noInstantUnlock.value && enableBiometricsOption.value) {
await biometricsUnlock();
}
})
onMounted(async () => {
document.addEventListener('ionBackButton', (e) => {
console.log('ionBackButton', e);
});
if (!isManuallyLock.value) {
await biometricsUnlock();
}
});
const email = ref(user?.email);
Expand All @@ -63,6 +68,8 @@ async function biometricsUnlock() {
simpleToast(e.message);
}
unlockMode.value = 'password';
passwordInput.value!.$el.setFocus(true);
}
}
Expand Down Expand Up @@ -101,28 +108,42 @@ async function passwordUnlock() {
</p>

<div style="width: 100%; display: grid; gap: 1rem;">
<ion-input
ref="passwordInput"
type="password"
fill="solid"
label-placement="floating"
placeholder="Password"
v-model="password"
error-text="Invalid Password"
style="width: 85%; margin-left: auto; margin-right: auto"
>
</ion-input>

<ion-button expand="block" @click="passwordUnlock"
fill="clear"
:disabled="loading || password === ''">
<template v-if="!loading">
Unlock
</template>
<template v-else>
<ion-spinner name="dots" />
</template>
</ion-button>
<template v-if="unlockMode === 'password'">
<ion-input
ref="passwordInput"
type="password"
fill="solid"
label-placement="floating"
placeholder="Password"
v-model="password"
error-text="Invalid Password"
style="width: 85%; margin-left: auto; margin-right: auto"
>
</ion-input>

<ion-button expand="block" @click="passwordUnlock"
fill="clear"
:disabled="loading || password === ''">
<template v-if="!loading">
Unlock
</template>
<template v-else>
<ion-spinner name="dots" />
</template>
</ion-button>
</template>
<template v-else>
<ion-button expand="block" @click="biometricsUnlock"
fill="clear"
:disabled="loading">
<template v-if="!loading">
Unlock
</template>
<template v-else>
<ion-spinner name="dots" />
</template>
</ion-button>
</template>
</div>
</div>
</ion-content>
Expand Down
6 changes: 2 additions & 4 deletions src/views/OptionsPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
import MainLayout from '@/components/layout/MainLayout.vue';
import lockScreenService from '@/service/lock-screen-service';
import userService from '@/service/user-service';
import { isManuallyLock } from '@/store/main-store';
import { noInstantUnlock } from '@/store/main-store';
import { enableBiometricsOption } from '@/store/options-store';
import { simpleConfirm } from '@/utilities/alert';
import { faFingerprint, faKey, faLock, faSignOut } from '@fortawesome/free-solid-svg-icons';
Expand All @@ -65,12 +65,10 @@ watch(enableBiometrics, async (v) => {
}
enableBiometricsOption.value = enableBiometrics.value;
console.log(enableBiometricsOption.value);
});
function lockScreen() {
isManuallyLock.value = true;
noInstantUnlock.value = true;
lockScreenService.lock();
}
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3495,6 +3495,11 @@ iconv-lite@0.6.3:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"

idle-timeout@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/idle-timeout/-/idle-timeout-2.0.1.tgz#0b0c322f5cdbe947ef928524a649d4490b739a4d"
integrity sha512-P26RMYHYajuCUxG7MG4DcggX8vmWWsEjfisUCT/l6OZ06sIAM9jWH8AeiGHAtm4JEGEv0fsY49U/BXOBdfCjSw==

ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
Expand Down

0 comments on commit e6229da

Please sign in to comment.