Fix refresh token

This commit is contained in:
2025-10-22 14:03:21 +02:00
parent ffda3ad51c
commit 738f627f1f
6 changed files with 552 additions and 114 deletions

View File

@@ -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);
}
);

View File

@@ -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
View 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);
}
}
}

View 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;
}
}

View File

@@ -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>

View File

@@ -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 {