Fix refresh token
This commit is contained in:
138
src/main.js
138
src/main.js
@@ -7,7 +7,8 @@ import { createPinia } from 'pinia';
|
|||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
|
import { AuthService } from './service/AuthService.js';
|
||||||
|
import { TokenRefreshManager } from './service/TokenRefreshManager.js';
|
||||||
|
|
||||||
import '@/assets/styles.scss';
|
import '@/assets/styles.scss';
|
||||||
import '@/assets/tailwind.css';
|
import '@/assets/tailwind.css';
|
||||||
@@ -20,16 +21,16 @@ import ConfirmationService from 'primevue/confirmationservice';
|
|||||||
import ToastService from 'primevue/toastservice';
|
import ToastService from 'primevue/toastservice';
|
||||||
import { LoadingStore } from './stores/LoadingStore.js';
|
import { LoadingStore } from './stores/LoadingStore.js';
|
||||||
|
|
||||||
|
// Declare variables that will be used globally
|
||||||
|
let authInstance = null;
|
||||||
|
let tokenRefreshManager = null;
|
||||||
|
|
||||||
config({
|
config({
|
||||||
editorConfig: {
|
editorConfig: {
|
||||||
renderDelay: 0,
|
renderDelay: 0,
|
||||||
zIndex: 200000000
|
zIndex: 200000000
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var auth = createAuth({
|
var auth = createAuth({
|
||||||
plugins: {
|
plugins: {
|
||||||
http: axios,
|
http: axios,
|
||||||
@@ -40,23 +41,20 @@ var auth = createAuth({
|
|||||||
auth: driverAuthBearer,
|
auth: driverAuthBearer,
|
||||||
router: driverRouterVueRouter
|
router: driverRouterVueRouter
|
||||||
},
|
},
|
||||||
options:{
|
options: {
|
||||||
notFoundRedirect: '/',
|
notFoundRedirect: '/',
|
||||||
authRedirect: '/',
|
authRedirect: '/',
|
||||||
authRedirect: true, // niente redirect automatici
|
authRedirect: true, // niente redirect automatici
|
||||||
notFoundRedirect: false,
|
notFoundRedirect: false,
|
||||||
loginData: {url: '/api/auth/login', method: 'POST', redirect: '/home'},
|
loginData: { url: '/api/auth/login', method: 'POST', redirect: '/home' },
|
||||||
logoutData: {url: '/api/auth/logout', redirect: '/auth/login'},
|
logoutData: { url: '/api/auth/logout', redirect: '/auth/login' },
|
||||||
fetchData: {url: '/api/auth/fetch-user', method: 'GET', enabled: false},
|
fetchData: { url: '/api/auth/fetch-user', method: 'GET', enabled: false },
|
||||||
refreshData: {url: '/api/auth/refresh-token', method: 'GET', enabled: true}
|
refreshData: { url: '/api/auth/refresh-token', method: 'GET', enabled: true }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
axios.defaults.baseURL = import.meta.env.VITE_BACKEND_URL;
|
axios.defaults.baseURL = import.meta.env.VITE_BACKEND_URL;
|
||||||
//axios.defaults.baseURL ='http://localhost:8081';
|
//axios.defaults.baseURL = 'http://localhost:8081';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
console.log(import.meta.env.VITE_BACKEND_URL);
|
console.log(import.meta.env.VITE_BACKEND_URL);
|
||||||
|
|
||||||
@@ -95,7 +93,7 @@ const preset = definePreset(Nora, {
|
|||||||
},
|
},
|
||||||
formField: {
|
formField: {
|
||||||
hoverBorderColor: '{primary.color}',
|
hoverBorderColor: '{primary.color}',
|
||||||
borderColor: '{primary.color}',
|
borderColor: '{primary.color}'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
@@ -115,7 +113,7 @@ const preset = definePreset(Nora, {
|
|||||||
},
|
},
|
||||||
formField: {
|
formField: {
|
||||||
hoverBorderColor: '{primary.color}',
|
hoverBorderColor: '{primary.color}',
|
||||||
borderColor: '{primary.color}',
|
borderColor: '{primary.color}'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -137,7 +135,7 @@ app.use(PrimeVue, {
|
|||||||
prefix: 'p',
|
prefix: 'p',
|
||||||
darkModeSelector: '.app-dark',
|
darkModeSelector: '.app-dark',
|
||||||
cssLayer: false
|
cssLayer: false
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
app.use(ToastService);
|
app.use(ToastService);
|
||||||
@@ -148,20 +146,108 @@ app.component('BlockViewer', BlockViewer);
|
|||||||
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
|
||||||
|
// Store auth instance for interceptor and token refresh manager after app is mounted
|
||||||
|
authInstance = app.config.globalProperties.$auth;
|
||||||
|
tokenRefreshManager = new TokenRefreshManager(authInstance);
|
||||||
|
|
||||||
|
// Start token refresh timer when user is authenticated
|
||||||
|
if (authInstance.check()) {
|
||||||
|
tokenRefreshManager.startRefreshTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for successful login to start token refresh timer
|
||||||
|
window.addEventListener('auth-login-success', () => {
|
||||||
|
console.log('[Main] Login success event received, starting token refresh timer');
|
||||||
|
if (tokenRefreshManager) {
|
||||||
|
tokenRefreshManager.startRefreshTimer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stop token refresh timer on logout
|
||||||
|
window.addEventListener('auth-logout', () => {
|
||||||
|
console.log('[Main] Logout event received, stopping token refresh timer');
|
||||||
|
if (tokenRefreshManager) {
|
||||||
|
tokenRefreshManager.stopRefreshTimer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const loadingStore = LoadingStore();
|
const loadingStore = LoadingStore();
|
||||||
|
|
||||||
axios.interceptors.request.use(function (config) {
|
axios.interceptors.request.use(
|
||||||
|
function (config) {
|
||||||
loadingStore.another_loading = true;
|
loadingStore.another_loading = true;
|
||||||
return config
|
return config;
|
||||||
}, function (error) {
|
},
|
||||||
|
function (error) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
axios.interceptors.response.use(function (response) {
|
axios.interceptors.response.use(
|
||||||
|
function (response) {
|
||||||
|
loadingStore.another_loading = false;
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
async function (error) {
|
||||||
loadingStore.another_loading = false;
|
loadingStore.another_loading = false;
|
||||||
|
|
||||||
return response;
|
// Don't handle auth-related URLs to prevent infinite loops
|
||||||
}, function (error) {
|
const isAuthUrl = error.config && (error.config.url.includes('/api/auth/') || error.config.url.includes('/msauth/'));
|
||||||
return Promise.reject(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Handle 403 errors by attempting token refresh (but not for auth URLs)
|
||||||
|
if (error.response && error.response.status === 403 && !isAuthUrl) {
|
||||||
|
console.log('[Interceptor] 403 error detected, attempting token refresh...');
|
||||||
|
|
||||||
|
// Check if we have an authenticated user and authInstance is available
|
||||||
|
if (authInstance && authInstance.check && authInstance.check()) {
|
||||||
|
try {
|
||||||
|
// Use our custom AuthService to refresh tokens (both MSAL and classic)
|
||||||
|
const refreshResult = await AuthService.refreshToken(authInstance);
|
||||||
|
|
||||||
|
if (refreshResult.success && refreshResult.token) {
|
||||||
|
console.log('[Interceptor] Token refresh successful, updating auth and retrying request...');
|
||||||
|
|
||||||
|
// Update the auth token
|
||||||
|
authInstance.token(null, refreshResult.token, false);
|
||||||
|
|
||||||
|
// Update the original request with new token
|
||||||
|
error.config.headers['Authorization'] = `Bearer ${refreshResult.token}`;
|
||||||
|
|
||||||
|
// Retry the original request
|
||||||
|
return axios.request(error.config);
|
||||||
|
} else {
|
||||||
|
console.error('[Interceptor] Token refresh failed:', refreshResult.error);
|
||||||
|
throw new Error(refreshResult.error);
|
||||||
|
}
|
||||||
|
} catch (refreshError) {
|
||||||
|
console.error('[Interceptor] Token refresh process failed:', refreshError);
|
||||||
|
|
||||||
|
// If refresh fails, logout and redirect to login
|
||||||
|
try {
|
||||||
|
// Stop token refresh timer
|
||||||
|
window.dispatchEvent(new CustomEvent('auth-logout'));
|
||||||
|
|
||||||
|
await AuthService.logout();
|
||||||
|
if (authInstance && authInstance.logout) {
|
||||||
|
authInstance.logout({
|
||||||
|
redirect: '/auth/login'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.location.href = '/auth/login';
|
||||||
|
}
|
||||||
|
} catch (logoutError) {
|
||||||
|
console.error('[Interceptor] Logout failed:', logoutError);
|
||||||
|
// Force redirect to login
|
||||||
|
window.location.href = '/auth/login';
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(refreshError);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('[Interceptor] No authenticated user for non-auth 403, ignoring...');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -61,6 +61,11 @@ const router = createRouter({
|
|||||||
name: 'app-browser',
|
name: 'app-browser',
|
||||||
component: () => import('@/views/pages/ApplicationBrowser.vue')
|
component: () => import('@/views/pages/ApplicationBrowser.vue')
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// path: '/filesystem-browser',
|
||||||
|
// name: 'filesystem-browser',
|
||||||
|
// component: () => import('@/views/pages/FileSystemBrowser.vue')
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
path: '/mdcanvas',
|
path: '/mdcanvas',
|
||||||
name: 'mdcanvas',
|
name: 'mdcanvas',
|
||||||
@@ -95,4 +100,17 @@ const router = createRouter({
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Navigation guard to handle logout when going to login page
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
// Only trigger logout event when coming from an authenticated route to login
|
||||||
|
// and not on initial page load or when already on login/callback pages
|
||||||
|
if (to.name === 'login' && from.name && from.name !== 'login' && from.name !== 'test' && from.meta?.auth === true) {
|
||||||
|
// User is navigating to login page from an authenticated route
|
||||||
|
// This indicates a logout or session expiry
|
||||||
|
console.log('[Router] Navigating from authenticated route to login, triggering logout event');
|
||||||
|
window.dispatchEvent(new CustomEvent('auth-logout'));
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
215
src/service/AuthService.js
Normal file
215
src/service/AuthService.js
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
import { msalInstance, msalrequest } from '@/views/pages/auth/MsalConfig';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export class AuthService {
|
||||||
|
/**
|
||||||
|
* Attempts to refresh tokens based on the authentication type
|
||||||
|
* @param {Object} authInstance - The vue-auth instance
|
||||||
|
* @returns {Promise<{success: boolean, token?: string, error?: string}>}
|
||||||
|
*/
|
||||||
|
static async refreshToken(authInstance = null) {
|
||||||
|
try {
|
||||||
|
console.log('[AuthService] Starting token refresh process...');
|
||||||
|
|
||||||
|
// Check if we have MSAL accounts first
|
||||||
|
await msalInstance.initialize();
|
||||||
|
const accounts = msalInstance.getAllAccounts();
|
||||||
|
|
||||||
|
if (accounts.length > 0) {
|
||||||
|
// User logged in with MSAL - use MSAL refresh
|
||||||
|
console.log('[AuthService] Using MSAL token refresh...');
|
||||||
|
return await this.refreshMsalToken();
|
||||||
|
} else if (authInstance && authInstance.check()) {
|
||||||
|
// User logged in with classic authentication - use vue-auth refresh
|
||||||
|
console.log('[AuthService] Using classic authentication token refresh...');
|
||||||
|
return await this.refreshClassicToken(authInstance);
|
||||||
|
} else {
|
||||||
|
console.warn('[AuthService] No valid authentication method found');
|
||||||
|
return { success: false, error: 'No valid authentication method found' };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[AuthService] Token refresh failed:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes token using classic username/password authentication
|
||||||
|
* @param {Object} authInstance - The vue-auth instance
|
||||||
|
* @returns {Promise<{success: boolean, token?: string, error?: string}>}
|
||||||
|
*/
|
||||||
|
static async refreshClassicToken(authInstance) {
|
||||||
|
try {
|
||||||
|
console.log('[AuthService] Attempting classic token refresh...');
|
||||||
|
|
||||||
|
const refreshResponse = await authInstance.refresh();
|
||||||
|
console.log('[AuthService] Classic refresh response:', refreshResponse);
|
||||||
|
|
||||||
|
// Check multiple possible response structures
|
||||||
|
let token = null;
|
||||||
|
|
||||||
|
if (refreshResponse && refreshResponse.data) {
|
||||||
|
// Try different token field names
|
||||||
|
token = refreshResponse.data.token || refreshResponse.data.accessToken || refreshResponse.data.access_token;
|
||||||
|
|
||||||
|
// If still no token, check if the token is directly in data
|
||||||
|
if (!token && typeof refreshResponse.data === 'string') {
|
||||||
|
token = refreshResponse.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check if token is in headers (as backend sets it there)
|
||||||
|
if (!token && refreshResponse && refreshResponse.headers) {
|
||||||
|
token = refreshResponse.headers.authorization || refreshResponse.headers.Authorization;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
console.log('[AuthService] Classic token refresh successful');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
token: token,
|
||||||
|
response: refreshResponse
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
console.error('[AuthService] No token found in classic refresh response:', refreshResponse);
|
||||||
|
return { success: false, error: 'No token in refresh response' };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[AuthService] Classic token refresh failed:', error);
|
||||||
|
return { success: false, error: 'Classic token refresh failed' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to refresh the MSAL token and exchange it for a new backend token
|
||||||
|
* @returns {Promise<{success: boolean, token?: string, error?: string}>}
|
||||||
|
*/
|
||||||
|
static async refreshMsalToken() {
|
||||||
|
try {
|
||||||
|
console.log('[AuthService] Starting MSAL token refresh process...');
|
||||||
|
|
||||||
|
// Get all accounts from MSAL
|
||||||
|
await msalInstance.initialize();
|
||||||
|
const accounts = msalInstance.getAllAccounts();
|
||||||
|
|
||||||
|
if (accounts.length === 0) {
|
||||||
|
console.warn('[AuthService] No MSAL accounts found for MSAL refresh');
|
||||||
|
return { success: false, error: 'No MSAL accounts found' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = accounts[0];
|
||||||
|
console.log('[AuthService] Using account:', account.username);
|
||||||
|
|
||||||
|
// Try to acquire token silently
|
||||||
|
let tokenResponse;
|
||||||
|
try {
|
||||||
|
tokenResponse = await msalInstance.acquireTokenSilent({
|
||||||
|
scopes: msalrequest.scopes,
|
||||||
|
account: account
|
||||||
|
});
|
||||||
|
console.log('[AuthService] MSAL token acquired silently');
|
||||||
|
} catch (silentError) {
|
||||||
|
console.warn('[AuthService] Silent token acquisition failed, trying interactive...');
|
||||||
|
|
||||||
|
// If silent fails, try interactive
|
||||||
|
try {
|
||||||
|
tokenResponse = await msalInstance.acquireTokenPopup({
|
||||||
|
scopes: msalrequest.scopes,
|
||||||
|
account: account
|
||||||
|
});
|
||||||
|
console.log('[AuthService] MSAL token acquired interactively');
|
||||||
|
} catch (interactiveError) {
|
||||||
|
console.error('[AuthService] Interactive token acquisition failed:', interactiveError);
|
||||||
|
return { success: false, error: 'Token acquisition failed' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tokenResponse || !tokenResponse.accessToken) {
|
||||||
|
console.error('[AuthService] No access token received');
|
||||||
|
return { success: false, error: 'No access token received' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exchange the MSAL token for a backend token
|
||||||
|
try {
|
||||||
|
const exchangeResponse = await axios.post(
|
||||||
|
'/msauth/exchange',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: { Authorization: `Bearer ${tokenResponse.accessToken}` }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (exchangeResponse.data && exchangeResponse.data.token) {
|
||||||
|
console.log('[AuthService] MSAL token refresh successful');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
token: exchangeResponse.data.token,
|
||||||
|
response: exchangeResponse
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
console.error('[AuthService] No token in MSAL exchange response');
|
||||||
|
return { success: false, error: 'No token in exchange response' };
|
||||||
|
}
|
||||||
|
} catch (exchangeError) {
|
||||||
|
console.error('[AuthService] MSAL token exchange failed:', exchangeError);
|
||||||
|
return { success: false, error: 'Token exchange failed' };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[AuthService] MSAL token refresh failed:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current MSAL session is still valid
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
static async isSessionValid() {
|
||||||
|
try {
|
||||||
|
await msalInstance.initialize();
|
||||||
|
const accounts = msalInstance.getAllAccounts();
|
||||||
|
|
||||||
|
if (accounts.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to acquire token silently to check if session is valid
|
||||||
|
try {
|
||||||
|
await msalInstance.acquireTokenSilent({
|
||||||
|
scopes: msalrequest.scopes,
|
||||||
|
account: accounts[0]
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[AuthService] Session validation failed:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[AuthService] Session validation error:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs out from MSAL
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
static async logout() {
|
||||||
|
try {
|
||||||
|
await msalInstance.initialize();
|
||||||
|
const accounts = msalInstance.getAllAccounts();
|
||||||
|
|
||||||
|
if (accounts.length > 0) {
|
||||||
|
const logoutRequest = {
|
||||||
|
account: accounts[0],
|
||||||
|
mainWindowRedirectUri: window.location.origin + '/auth/login'
|
||||||
|
};
|
||||||
|
|
||||||
|
await msalInstance.logoutPopup(logoutRequest);
|
||||||
|
console.log('[AuthService] MSAL logout completed');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[AuthService] Logout error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/service/TokenRefreshManager.js
Normal file
106
src/service/TokenRefreshManager.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import { AuthService } from './AuthService.js';
|
||||||
|
|
||||||
|
export class TokenRefreshManager {
|
||||||
|
constructor(authInstance) {
|
||||||
|
this.authInstance = authInstance;
|
||||||
|
this.refreshInterval = null;
|
||||||
|
this.isRefreshing = false;
|
||||||
|
// Refresh every 50 minutes (tokens expire after 1 hour)
|
||||||
|
this.REFRESH_INTERVAL_MS = 50 * 60 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the automatic token refresh timer
|
||||||
|
*/
|
||||||
|
startRefreshTimer() {
|
||||||
|
console.log('[TokenRefreshManager] Starting automatic token refresh timer...');
|
||||||
|
|
||||||
|
// Clear any existing timer
|
||||||
|
this.stopRefreshTimer();
|
||||||
|
|
||||||
|
this.refreshInterval = setInterval(async () => {
|
||||||
|
await this.performRefresh();
|
||||||
|
}, this.REFRESH_INTERVAL_MS);
|
||||||
|
|
||||||
|
console.log(`[TokenRefreshManager] Timer set to refresh every ${this.REFRESH_INTERVAL_MS / 60000} minutes`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the automatic token refresh timer
|
||||||
|
*/
|
||||||
|
stopRefreshTimer() {
|
||||||
|
if (this.refreshInterval) {
|
||||||
|
clearInterval(this.refreshInterval);
|
||||||
|
this.refreshInterval = null;
|
||||||
|
console.log('[TokenRefreshManager] Token refresh timer stopped');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a token refresh
|
||||||
|
*/
|
||||||
|
async performRefresh() {
|
||||||
|
if (this.isRefreshing) {
|
||||||
|
console.log('[TokenRefreshManager] Refresh already in progress, skipping...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only refresh if user is authenticated
|
||||||
|
if (!this.authInstance || !this.authInstance.check()) {
|
||||||
|
console.log('[TokenRefreshManager] User not authenticated, stopping refresh timer');
|
||||||
|
this.stopRefreshTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isRefreshing = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('[TokenRefreshManager] Performing scheduled token refresh...');
|
||||||
|
|
||||||
|
const refreshResult = await AuthService.refreshToken(this.authInstance);
|
||||||
|
|
||||||
|
if (refreshResult.success && refreshResult.token) {
|
||||||
|
// Update the auth token silently
|
||||||
|
this.authInstance.token(null, refreshResult.token, false);
|
||||||
|
console.log('[TokenRefreshManager] Token refreshed successfully');
|
||||||
|
} else {
|
||||||
|
console.error('[TokenRefreshManager] Scheduled refresh failed:', refreshResult.error);
|
||||||
|
|
||||||
|
// Stop the timer and logout on failure
|
||||||
|
this.stopRefreshTimer();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Dispatch logout event to stop other timers
|
||||||
|
window.dispatchEvent(new CustomEvent('auth-logout'));
|
||||||
|
|
||||||
|
await AuthService.logout();
|
||||||
|
this.authInstance.logout({
|
||||||
|
redirect: '/auth/login'
|
||||||
|
});
|
||||||
|
} catch (logoutError) {
|
||||||
|
console.error('[TokenRefreshManager] Logout failed:', logoutError);
|
||||||
|
window.location.href = '/auth/login';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[TokenRefreshManager] Refresh error:', error);
|
||||||
|
this.stopRefreshTimer();
|
||||||
|
} finally {
|
||||||
|
this.isRefreshing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually triggers a token refresh
|
||||||
|
* @returns {Promise<boolean>} Success status
|
||||||
|
*/
|
||||||
|
async manualRefresh() {
|
||||||
|
if (this.isRefreshing) {
|
||||||
|
console.log('[TokenRefreshManager] Manual refresh requested but already in progress');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.performRefresh();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ onMounted(async () => {
|
|||||||
console.log('[Callback] After initialize on callback');
|
console.log('[Callback] After initialize on callback');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[Callback] Error during MSAL initialization:', e);
|
console.error('[Callback] Error during MSAL initialization:', e);
|
||||||
message.value = "Error during MSAL initialization.";
|
message.value = 'Error during MSAL initialization.';
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait 2 second to avoid race condition
|
// Wait 2 second to avoid race condition
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
message.value = 'Logging in to the application...';
|
message.value = 'Logging in to the application...';
|
||||||
|
|
||||||
// Token exchange function with retry
|
// Token exchange function with retry
|
||||||
@@ -63,7 +63,7 @@ onMounted(async () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!retry) {
|
if (!retry) {
|
||||||
console.warn('[Callback] First attempt failed, waiting 1500ms and retrying...');
|
console.warn('[Callback] First attempt failed, waiting 1500ms and retrying...');
|
||||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||||
return tryTokenExchange(true);
|
return tryTokenExchange(true);
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
@@ -85,6 +85,9 @@ onMounted(async () => {
|
|||||||
const userResponse = await auth.fetch();
|
const userResponse = await auth.fetch();
|
||||||
console.log('[Callback] User fetch response:', userResponse);
|
console.log('[Callback] User fetch response:', userResponse);
|
||||||
|
|
||||||
|
// Start token refresh timer after successful authentication
|
||||||
|
window.dispatchEvent(new CustomEvent('auth-login-success'));
|
||||||
|
|
||||||
const userData = userResponse.data?.data;
|
const userData = userResponse.data?.data;
|
||||||
console.log('[Callback] userResponse.data.data:', userData);
|
console.log('[Callback] userResponse.data.data:', userData);
|
||||||
|
|
||||||
@@ -116,7 +119,6 @@ onMounted(async () => {
|
|||||||
visible.value = true;
|
visible.value = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const tryTokenExchange = async (accessToken, retry = false) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!retry) {
|
if (!retry) {
|
||||||
console.warn('[loginAD] First attempt failed, waiting 1500ms and retrying...');
|
console.warn('[loginAD] First attempt failed, waiting 1500ms and retrying...');
|
||||||
await new Promise(r => setTimeout(r, 1500));
|
await new Promise((r) => setTimeout(r, 1500));
|
||||||
return tryTokenExchange(accessToken, true);
|
return tryTokenExchange(accessToken, true);
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
@@ -63,7 +63,7 @@ const loginAD = async () => {
|
|||||||
console.log('[loginAD] Token MSAL ottenuto:', token);
|
console.log('[loginAD] Token MSAL ottenuto:', token);
|
||||||
|
|
||||||
// Inserisci una pausa per evitare race condition
|
// Inserisci una pausa per evitare race condition
|
||||||
await new Promise(r => setTimeout(r, 2000));
|
await new Promise((r) => setTimeout(r, 2000));
|
||||||
|
|
||||||
let exchangeResponse;
|
let exchangeResponse;
|
||||||
try {
|
try {
|
||||||
@@ -76,6 +76,9 @@ const loginAD = async () => {
|
|||||||
const resp = await auth.fetch();
|
const resp = await auth.fetch();
|
||||||
console.log('[loginAD] User fetch response:', resp.data.data);
|
console.log('[loginAD] User fetch response:', resp.data.data);
|
||||||
|
|
||||||
|
// Start token refresh timer after successful authentication
|
||||||
|
window.dispatchEvent(new CustomEvent('auth-login-success'));
|
||||||
|
|
||||||
const userData = resp.data.data;
|
const userData = resp.data.data;
|
||||||
if (!userData.selectedProject) {
|
if (!userData.selectedProject) {
|
||||||
console.log('[loginAD] No project selected → projects-list');
|
console.log('[loginAD] No project selected → projects-list');
|
||||||
@@ -96,7 +99,7 @@ const loginAD = async () => {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[loginAD] Error:', e);
|
console.error('[loginAD] Error:', e);
|
||||||
error.value = "Error while login AD. Contact the administrator.";
|
error.value = 'Error while login AD. Contact the administrator.';
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -115,6 +118,10 @@ const loginMsal = async () => {
|
|||||||
|
|
||||||
const logoutAD = async () => {
|
const logoutAD = async () => {
|
||||||
console.log('[logoutAD] Logout AD...');
|
console.log('[logoutAD] Logout AD...');
|
||||||
|
|
||||||
|
// Stop token refresh timer
|
||||||
|
window.dispatchEvent(new CustomEvent('auth-logout'));
|
||||||
|
|
||||||
const logoutRequest = {
|
const logoutRequest = {
|
||||||
account: msaccount.value,
|
account: msaccount.value,
|
||||||
mainWindowRedirectUri: window.location.href
|
mainWindowRedirectUri: window.location.href
|
||||||
@@ -142,6 +149,10 @@ const login_old = async () => {
|
|||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
console.log('[login_old] Login response:', response.data.data);
|
console.log('[login_old] Login response:', response.data.data);
|
||||||
|
|
||||||
|
// Start token refresh timer after successful authentication
|
||||||
|
window.dispatchEvent(new CustomEvent('auth-login-success'));
|
||||||
|
|
||||||
if (!response.data.data.selectedProject) {
|
if (!response.data.data.selectedProject) {
|
||||||
console.log('[login_old] No project selected, redirect to projects-list');
|
console.log('[login_old] No project selected, redirect to projects-list');
|
||||||
router.push({ name: 'projects-list' });
|
router.push({ name: 'projects-list' });
|
||||||
@@ -169,9 +180,9 @@ const login_old = async () => {
|
|||||||
<div class="bg-surface-50 dark:bg-surface-950 flex items-center justify-center min-h-screen min-w-[100vw] overflow-hidden">
|
<div class="bg-surface-50 dark:bg-surface-950 flex items-center justify-center min-h-screen min-w-[100vw] overflow-hidden">
|
||||||
<div class="flex flex-col items-center justify-center">
|
<div class="flex flex-col items-center justify-center">
|
||||||
<div style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
|
<div style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
|
||||||
<div class="w-full bg-surface-0 dark:bg-surface-900 py-20 px-8 sm:px-20" style="border-radius: 53px">
|
<div class="w-full bg-surface-0 dark:bg-surface-900 py-12 px-6 sm:px-12" style="border-radius: 53px">
|
||||||
<div class="logo-container mb-8">
|
<div class="logo-container mb-6">
|
||||||
<svg width="85" height="63" viewBox="0 0 85 63" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="70" height="45" viewBox="0 0 85 63" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
clip-rule="evenodd"
|
clip-rule="evenodd"
|
||||||
@@ -189,36 +200,36 @@ const login_old = async () => {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center mb-12">
|
<div class="text-center mb-8">
|
||||||
<h1 class="text-3xl font-semibold">Welcome to</h1>
|
<h1 class="text-2xl font-semibold">Welcome to</h1>
|
||||||
<h1 class="text-4xl font-bold text-primary">WizardAI - WORKFLOW</h1>
|
<h1 class="text-3xl font-bold text-primary">WizardAI - WORKFLOW</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Username & Password Section -->
|
<!-- Username & Password Section -->
|
||||||
<div class="mb-10 w-full max-w-xl">
|
<div class="mb-6 w-full max-w-md">
|
||||||
<label for="email1" class="block text-surface-900 dark:text-surface-0 text-l font-medium mb-2">Username</label>
|
<label for="email1" class="block text-surface-900 dark:text-surface-0 text-sm font-medium mb-2">Username</label>
|
||||||
<InputText id="email1" type="text" placeholder="Username" class="w-full mb-6" style="padding: 1rem" v-model="username" />
|
<InputText id="email1" type="text" placeholder="Username" class="w-full mb-4" style="padding: 0.75rem" v-model="username" />
|
||||||
|
|
||||||
<label for="password1" class="block text-surface-900 dark:text-surface-0 font-medium text-l mb-2">Password</label>
|
<label for="password1" class="block text-surface-900 dark:text-surface-0 font-medium text-sm mb-2">Password</label>
|
||||||
<Password id="password1" v-model="password" placeholder="Password" :toggleMask="true" class="w-full mb-6" inputClass="w-full" :inputStyle="{ padding: '1rem' }" />
|
<Password id="password1" v-model="password" placeholder="Password" :toggleMask="true" class="w-full mb-4" inputClass="w-full" :inputStyle="{ padding: '0.75rem' }" />
|
||||||
|
|
||||||
<Button @click="login_old" label="Sign In with Username and Password" class="w-full text-xl mb-4" />
|
<Button @click="login_old" label="Sign In with Username and Password" class="w-full text-sm mb-3" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Divider -->
|
<!-- Divider -->
|
||||||
<div class="my-6 w-full max-w-xl flex items-center justify-center">
|
<div class="my-4 w-full max-w-md flex items-center justify-center">
|
||||||
<hr class="flex-grow border-t border-gray-300 dark:border-gray-700" />
|
<hr class="flex-grow border-t border-gray-300 dark:border-gray-700" />
|
||||||
<span class="mx-4 text-gray-500 dark:text-gray-400">Sign in with Microsoft Azure AD</span>
|
<span class="mx-3 text-sm text-gray-500 dark:text-gray-400">Sign in with Microsoft Azure AD</span>
|
||||||
<hr class="flex-grow border-t border-gray-300 dark:border-gray-700" />
|
<hr class="flex-grow border-t border-gray-300 dark:border-gray-700" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Azure AD Section -->
|
<!-- Azure AD Section -->
|
||||||
<div class="w-full max-w-xl">
|
<div class="w-full max-w-md">
|
||||||
<Button @click="loginMsal" v-if="msaccount == null" label="Sign In with Microsoft AD" class="w-full text-l mb-4" />
|
<Button @click="loginMsal" v-if="msaccount == null" label="Sign In with Microsoft AD" class="w-full text-sm mb-3" />
|
||||||
|
|
||||||
<Button @click="loginAD" v-if="msaccount" :label="'Login ' + msaccount.username" class="w-full text-l mb-4" />
|
<Button @click="loginAD" v-if="msaccount" :label="'Login ' + msaccount.username" class="w-full text-sm mb-3" />
|
||||||
|
|
||||||
<Button @click="logoutAD" v-if="msaccount" label="Logout AD" severity="warn" class="w-full text-l mb-4" />
|
<Button @click="logoutAD" v-if="msaccount" label="Logout AD" severity="warn" class="w-full text-sm mb-3" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error Message -->
|
<!-- Error Message -->
|
||||||
@@ -244,8 +255,8 @@ const login_old = async () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 15px;
|
||||||
margin-top: -40px;
|
margin-top: -30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message {
|
.error-message {
|
||||||
|
|||||||
Reference in New Issue
Block a user