Enable refresh_token when it's about to expire(5 minutes)

This commit is contained in:
jetli 2024-09-08 15:19:59 -07:00
parent c1fb9cfa50
commit 8632070391
4 changed files with 193 additions and 36 deletions

View File

@ -19,6 +19,7 @@
"echarts": "^5.5.1",
"graphql": "^16.9.0",
"graphql-tag": "^2.12.6",
"jwt-decode": "^4.0.0",
"pdfjs-dist": "^4.3.136",
"pinia": "^2.1.6",
"vue": "^3.3.4",
@ -41,4 +42,4 @@
"vite-plugin-svg-icons": "^2.0.1",
"webpack": "^5.88.2"
}
}
}

View File

@ -78,6 +78,14 @@ export default {
UserAuthApi.signinByEmail(this.email, this.password)
.then((response) => {
// Extract the necessary data from the response
const { access_token, refresh_token, expires_in } = response.data;
// Save tokens and expiration time in localStorage
localStorage.setItem('access_token', access_token);
localStorage.setItem('refresh_token', refresh_token);
localStorage.setItem('expires_in', expires_in);
let signinAction = response.data.signin_result
switch (signinAction) {
case signinActionEnum.EXISTING_USER_PASSWORD_REQUIRED:

View File

@ -1,20 +1,39 @@
<template>
<empty-content :loading="loading" :empty="requestGroups?.length === 0" />
<div class="request-hub">
<div v-for="(group, index) in requestGroups" :key="index" class="request-invitations" :id="group.name">
<div
v-for="(group, index) in requestGroups"
:key="index"
class="request-invitations"
:id="group.name"
>
<div v-if="group.data" id="request-invitation-container">
<div class="accordion accordion-list" v-for="(request, index) in group.data" :key="index" :id="request.id">
<div
class="accordion accordion-list"
v-for="(request, index) in group.data"
:key="index"
:id="request.id"
>
<div class="accordion-item my-3">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
:data-bs-target="'#collapse' + index" aria-expanded="false" :aria-controls="'collapse' + index">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
:data-bs-target="'#collapse' + index"
aria-expanded="false"
:aria-controls="'collapse' + index"
>
<div class="dashed-container request-content-container">
<div class="request-content-issuer-container">
<label class="request-content-label" for="request-content-issuer-box">{{ $t('Issuer') }}</label>
<label class="request-content-label" for="request-content-issuer-box">{{
$t('Issuer')
}}</label>
<div class="request-content-box" id="request-content-issuer-box">
<span class="request-content-issuer-text">
{{ request.issuer_profile.first_name }}
{{ request.issuer_profile.last_name }}</span>
{{ request.issuer_profile.last_name }}</span
>
</div>
</div>
<div class="request-content-title-container">
@ -48,8 +67,11 @@
</div>
</button>
</h2>
<div :id="'collapse' + index" class="accordion-collapse collapse"
data-bs-parent="#request-invitation-container">
<div
:id="'collapse' + index"
class="accordion-collapse collapse"
data-bs-parent="#request-invitation-container"
>
<div class="accordion-body">
<div class="request-description-container">
<button class="make-proposal-button" type="button" @click="Propose(request)">
@ -59,13 +81,23 @@
{{ $t('Message') }}
</button>
<div class="request-description-content" v-html="request.content"></div>
<div class="pdf-actions" v-for="(file, index) in request.attached_files" :key="index">
<button class="btn btn-link" data-bs-toggle="modal" data-bs-target="#pdf-viewer"
@click="previewAttachedFile(request.id, file.document_id, file.file_name)">
<div
class="pdf-actions"
v-for="(file, index) in request.attached_files"
:key="index"
>
<button
class="btn btn-link"
data-bs-toggle="modal"
data-bs-target="#pdf-viewer"
@click="previewAttachedFile(request.id, file.document_id, file.file_name)"
>
{{ $t('Preview') }}{{ file.file_name }}
</button>
<button class="btn btn-link"
@click="downloadAttachedFile(request.id, file.document_id, file.file_name)">
<button
class="btn btn-link"
@click="downloadAttachedFile(request.id, file.document_id, file.file_name)"
>
{{ $t('Download') }}{{ file.file_name }}
</button>
</div>
@ -76,45 +108,82 @@
> -->
<div class="issuer-achievement-container" id="issuer-achievement-container">
<div class="issuer-achievement-isssuer-container">
<label class="issuer-achievement-label" for="issuer-achievement-isssuer-content-div">{{ $t('Name')
}}</label>
<div class="issuer-achievement-content-container" id="issuer-achievement-isssuer-content-div">
<label
class="issuer-achievement-label"
for="issuer-achievement-isssuer-content-div"
>{{ $t('Name') }}</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-isssuer-content-div"
>
<span class="issuer-achievement-issuer-text">
{{ request.issuer_profile.first_name }}
{{ request.issuer_profile.last_name }}</span>
{{ request.issuer_profile.last_name }}</span
>
</div>
</div>
<div class="issuer-achievement-stay-container">
<label class="issuer-achievement-label" for="issuer-achievement-stay-content-div">{{ $t('Stay on Freeleaps') }} </label>
<div class="issuer-achievement-content-container" id="issuer-achievement-stay-content-div">
<label
class="issuer-achievement-label"
for="issuer-achievement-stay-content-div"
>{{ $t('Stay on Freeleaps') }}
</label>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-stay-content-div"
>
<span class="issuer-achievement-stay-content-text">
{{ request.issuer_achievement.activeness.days_of_staying_on }}
{{ $t('day(s)') }}</span>
{{ $t('day(s)') }}</span
>
</div>
</div>
<div class="issuer-achievement-paid-container">
<label class="issuer-achievement-label" for="issuer-achievement-paid-content-div">{{ $t('Total payment') }}</label>
<div class="issuer-achievement-content-container" id="issuer-achievement-stay-content-div">
<label
class="issuer-achievement-label"
for="issuer-achievement-paid-content-div"
>{{ $t('Total payment') }}</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-stay-content-div"
>
<span class="issuer-achievement-paid-content-text">
{{ request.issuer_achievement.issuer.spending.total }}
{{ request.issuer_achievement.issuer.spending.currency }}</span>
{{ request.issuer_achievement.issuer.spending.currency }}</span
>
</div>
</div>
<div class="issuer-achievement-deposit-container">
<label class="issuer-achievement-label" for="issuer-achievement-deposit-content-div">{{
$t('Deposit') }}</label>
<div class="issuer-achievement-content-container" id="issuer-achievement-deposit-content-div">
<label
class="issuer-achievement-label"
for="issuer-achievement-deposit-content-div"
>{{ $t('Deposit') }}</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-deposit-content-div"
>
<span class="issuer-achievement-deposit-content-text">
{{ request.issuer_achievement.issuer.deposit.available }}
{{ request.issuer_achievement.issuer.deposit.currency }}</span>
{{ request.issuer_achievement.issuer.deposit.currency }}</span
>
</div>
</div>
<div class="issuer-achievement-credit-container">
<label class="issuer-achievement-label" for="issuer-achievement-credit-content-div">{{
$t('Credit') }}</label>
<div class="issuer-achievement-content-container" id="issuer-achievement-credit-content-div">
<label
class="issuer-achievement-label"
for="issuer-achievement-credit-content-div"
>{{ $t('Credit') }}</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-credit-content-div"
>
<span class="issuer-achievement-credit-content-text">
{{ request.issuer_achievement.activeness.credit }}</span>
{{ request.issuer_achievement.activeness.credit }}</span
>
</div>
</div>
</div>
@ -126,12 +195,23 @@
</div>
</div>
</div>
<div class="modal fade" id="pdf-viewer" tabindex="-1" aria-labelledby="pdf-viewer-label" aria-hidden="true">
<div
class="modal fade"
id="pdf-viewer"
tabindex="-1"
aria-labelledby="pdf-viewer-label"
aria-hidden="true"
>
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="pdf-viewer-label">{{ pdfDocument.title }}</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
</div>
<div class="modal-body">
<PDFReader :doc="pdfDocument.doc" />
@ -213,7 +293,7 @@ export default {
.then((response) => {
console.log('resposne', response)
// create file link in browser's memory
// const href = URL.createObjectURL(response.data)
// const ahref = URL.createObjectURL(response.data.download_url)
// create "a" HTML element with href to file & click
const link = document.createElement('a')

View File

@ -1,7 +1,75 @@
import axios from 'axios'
import { jwtDecode } from 'jwt-decode';
const backendAxios = axios.create({
baseURL: ''
baseURL: '',
headers: {
'Content-Type': 'application/json'
}
})
// Function to check if the token has expired
function isTokenExpired(token) {
// Ensure token is a non-empty string
if (!token || typeof token !== 'string') {
console.error('Invalid token: must be a non-empty string')
return true // Treat it as expired
}
try {
const decodedToken = jwtDecode(token)
console.log('this is decoded token', decodedToken)
const now = Math.floor(Date.now() / 1000)
// Buffer as 5 minu = 300 seconds
return decodedToken.exp - 300 < now
} catch (error) {
console.error('Error decoding token:', error)
return true // Treat it as expired if decoding fails
}
}
// Interceptor for handling token expiration and refreshing
backendAxios.interceptors.request.use(
async (config) => {
let accessToken = localStorage.getItem('access_token')
const refreshToken = localStorage.getItem('refresh_token')
// Check if the access token is expired
if (!accessToken || isTokenExpired(accessToken)) {
if (!refreshToken) {
return config
}
try {
// If access token is expired, refresh it using the refresh token
const response = await axios.post('/api/user/signin/refresh-token', {}, {
headers: {
Authorization: `Bearer ${refreshToken}`,
'Content-Type': 'application/json'
}
});
accessToken = response.data.access_token
const new_refreshToken = response.data.refresh_token
// Save the new access token to localStorage
localStorage.setItem('access_token', accessToken)
localStorage.setItem('refresh_token', new_refreshToken)
} catch (error) {
console.error('Token refresh failed. Redirecting to login.')
// Optionally, handle token refresh failure (e.g., redirect to login)
window.location.href = '/front-door'
return Promise.reject(error)
}
}
// Add the (new) access token to the request headers
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
export { backendAxios }