From e518fb6f425b186f78d6d5e09c583e6e4d426e58 Mon Sep 17 00:00:00 2001 From: Yong Sheng Tan Date: Sun, 31 Mar 2024 17:35:13 +0800 Subject: [PATCH] Introduce biometrics expiry to reduce challenges on refresh --- src/core/session.jsx | 36 +++++++++++++++++++++--------------- src/public/login.jsx | 4 ++-- src/settings/profile.jsx | 9 +++++---- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/core/session.jsx b/src/core/session.jsx index 1da7962..c7ca213 100644 --- a/src/core/session.jsx +++ b/src/core/session.jsx @@ -15,14 +15,9 @@ const Session = () => { const isPublicEndpoint = () => [ '/login', '/register' ].indexOf(location.pathname) > -1; useEffect(() => { - if (session !== undefined) { - console.debug('UE1: Session exists'); - return; - } - console.debug('UE1: Session does not exist'); const token = window.localStorage.getItem('token'); if (!token) { - console.debug('UE1: No stored token'); + console.debug('UE1: No stored token exists'); if (!isPublicEndpoint()) { navigate('/login', { replace: true }); } @@ -30,7 +25,7 @@ const Session = () => { return; } let jwt = parseJwt(token); - console.debug('UE1: Stored token', jwt); + console.debug('UE1: Stored token exists:', jwt); if (new Date().getTime() >= (jwt.exp * 1000)) { console.debug('UE1: Stored token has expired'); window.localStorage.clear(); @@ -41,17 +36,24 @@ const Session = () => { setLoading(false); } else { console.debug('UE1: Stored token still valid'); - const biometrics = window.localStorage.getItem('biometrics'); + const biometrics = JSON.parse(window.localStorage.getItem('biometrics')); + if (!biometrics) { - console.debug('UE1: Biometrics not enabled'); + console.debug('UE1: Biometrics is not enabled'); + setSession({ token, name: jwt.name, email: jwt.sub, admin: jwt.admin }); + setLoading(false); + return; + } + console.debug('UE1: Biometrics is enabled'); + if (new Date() < new Date(biometrics?.expiry)) { + console.debug('UE1: Biometrics not expired yet, skip challenge'); setSession({ token, name: jwt.name, email: jwt.sub, admin: jwt.admin }); setLoading(false); return; } - console.debug('UE1: Biometrics enabled'); + console.debug('UE1: Biometrics expired, initiating challenge'); challenge((response) => { - const { id } = JSON.parse(biometrics); - let originalId = new Uint8Array(atob(id).split('').map((c) => c.charCodeAt(0))); + let originalId = new Uint8Array(atob(biometrics.id).split('').map((c) => c.charCodeAt(0))); const publicKey = { challenge: Uint8Array.from(response.token, c => c.charCodeAt(0)), @@ -63,10 +65,14 @@ const Session = () => { }; navigator.credentials.get({ publicKey }).then( () => { - console.debug('UE1: Biometrics challenge successful'); + console.debug('UE1: Biometrics challenge successful, unlocking session'); setSession({ token, name: jwt.name, email: jwt.sub, admin: jwt.admin }); showStatus('success', 'Session unlocked'); setLoading(false); + window.localStorage.setItem('biometrics', JSON.stringify({ + ...biometrics, + expiry: new Date((new Date()).getTime() + (5 * 60 * 1000)).toISOString(), + })); }, (err) => { console.debug('UE1: Biometrics challenge failed'); @@ -76,6 +82,7 @@ const Session = () => { navigate('/login', { replace: true }); showStatus('error', 'Invalid biometric credentials'); setLoading(false); + console.debug('UE1: Biometrics challenge failed, logging'); } ); }); @@ -84,7 +91,6 @@ const Session = () => { useEffect(() => { if (!session) { - console.debug('UE2: No session exists'); return; } console.debug('UE2: Session exists'); @@ -93,7 +99,7 @@ const Session = () => { console.debug('UE2: Session still fresh'); return; } - console.debug('UE2: Session not fresh'); + console.debug('UE2: Session near expiry, initiating refresh'); refreshToken(({ token }) => { console.debug(`UE2: Obtained new token: ${token}`); window.localStorage.setItem('token', token); diff --git a/src/public/login.jsx b/src/public/login.jsx index ffd93aa..85fe6e5 100644 --- a/src/public/login.jsx +++ b/src/public/login.jsx @@ -42,8 +42,8 @@ const Login = () => {
Login - - + + } diff --git a/src/settings/profile.jsx b/src/settings/profile.jsx index 63779b8..1acb109 100644 --- a/src/settings/profile.jsx +++ b/src/settings/profile.jsx @@ -74,7 +74,7 @@ const Profile = () => { name: session.email, displayName: session.name, }, - pubKeyCredParams: [{ type, alg: -7 }], + pubKeyCredParams: [{ type, alg: -7 }, { type, alg: -257 }], authenticatorSelection: { requireResidentKey: true, userVerification: 'required', @@ -96,7 +96,8 @@ const Profile = () => { const attestation = { id: credentialIdBase64, clientDataJSON: arrayBufferToString(credential.response.clientDataJSON), - attestationObject: base64encode(credential.response.attestationObject) + attestationObject: base64encode(credential.response.attestationObject), + expiry: new Date().toISOString(), }; localStorage.setItem('biometrics', JSON.stringify(attestation)); showStatus('success', 'Biometrics registered successfully') @@ -115,8 +116,8 @@ const Profile = () => { color={localStorage.getItem("biometrics") ? 'success' : 'info'} /> - - + +