/**
* quiXzoom SSO Client v2.0
* Universal cross-domain silent login
*
* Usage:
*
*
*/
(function(global) {
'use strict';
const DEFAULT_CONFIG = {
ssoUrl: 'https://auth.quixzoom.com',
checkInterval: 60000, // Check auth every 60 seconds
silentTimeout: 5000,
};
class QZAuthClient {
constructor() {
this.config = { ...DEFAULT_CONFIG };
this.user = null;
this.isAuthenticated = false;
this.callbacks = {
onLogin: null,
onLogout: null,
onError: null,
};
this.checkIntervalId = null;
}
init(config = {}) {
this.config = { ...this.config, ...config };
if (config.onLogin) this.callbacks.onLogin = config.onLogin;
if (config.onLogout) this.callbacks.onLogout = config.onLogout;
if (config.onError) this.callbacks.onError = config.onError;
// Check auth immediately
this.checkAuth();
// Set up periodic checks
this.checkIntervalId = setInterval(() => {
this.checkAuth();
}, this.config.checkInterval);
// Listen for storage events (login from other tabs)
window.addEventListener('storage', (e) => {
if (e.key === 'qz_auth_event') {
const event = JSON.parse(e.newValue || '{}');
if (event.type === 'login') {
this.checkAuth();
} else if (event.type === 'logout') {
this.logout();
}
}
});
// Listen for messages from SSO iframe
window.addEventListener('message', (e) => {
if (e.origin !== this.config.ssoUrl) return;
if (e.data.type === 'qz_auth_silent_response') {
this.handleSilentResponse(e.data);
}
});
console.log('[QZAuth] Initialized');
}
async checkAuth() {
try {
// Try silent login first (uses refresh token cookie)
const response = await fetch(`${this.config.ssoUrl}/auth/silent`, {
method: 'GET',
credentials: 'include',
});
if (response.ok) {
const data = await response.json();
if (data.authenticated) {
this.setUser(data.user);
return;
}
}
// Silent login failed, check if we have a token in URL (OAuth redirect)
const urlParams = new URLSearchParams(window.location.search);
const accessToken = urlParams.get('access_token');
if (accessToken) {
// Verify token with SSO
const verifyResponse = await fetch(`${this.config.ssoUrl}/auth/me`, {
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
if (verifyResponse.ok) {
const user = await verifyResponse.json();
this.setUser(user);
// Clean URL
window.history.replaceState({}, document.title, window.location.pathname);
return;
}
}
// Not authenticated
this.clearUser();
} catch (error) {
console.error('[QZAuth] Auth check failed:', error);
if (this.callbacks.onError) {
this.callbacks.onError(error);
}
}
}
handleSilentResponse(data) {
if (data.authenticated) {
this.setUser(data.user);
} else {
this.clearUser();
}
}
setUser(user) {
const wasAuthenticated = this.isAuthenticated;
this.user = user;
this.isAuthenticated = true;
// Store in localStorage for other tabs
localStorage.setItem('qz_user', JSON.stringify(user));
localStorage.setItem('qz_auth_event', JSON.stringify({
type: 'login',
timestamp: Date.now(),
}));
// Update UI
this.updateUI();
// Trigger callback if newly logged in
if (!wasAuthenticated && this.callbacks.onLogin) {
this.callbacks.onLogin(user);
}
}
clearUser() {
const wasAuthenticated = this.isAuthenticated;
this.user = null;
this.isAuthenticated = false;
localStorage.removeItem('qz_user');
localStorage.setItem('qz_auth_event', JSON.stringify({
type: 'logout',
timestamp: Date.now(),
}));
this.updateUI();
if (wasAuthenticated && this.callbacks.onLogout) {
this.callbacks.onLogout();
}
}
updateUI() {
// Update all elements with data-qz-auth attribute
document.querySelectorAll('[data-qz-auth]').forEach(el => {
const action = el.getAttribute('data-qz-auth');
if (action === 'show-when-authenticated') {
el.style.display = this.isAuthenticated ? '' : 'none';
} else if (action === 'show-when-guest') {
el.style.display = !this.isAuthenticated ? '' : 'none';
} else if (action === 'user-name') {
el.textContent = this.user?.name || '';
} else if (action === 'user-email') {
el.textContent = this.user?.email || '';
} else if (action === 'user-avatar') {
el.src = this.user?.avatar || '/default-avatar.png';
}
});
}
async login(email, password) {
try {
const response = await fetch(`${this.config.ssoUrl}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('Login failed');
}
const data = await response.json();
this.setUser(data.user);
return data;
} catch (error) {
console.error('[QZAuth] Login failed:', error);
throw error;
}
}
async logout() {
try {
await fetch(`${this.config.ssoUrl}/auth/logout`, {
method: 'POST',
credentials: 'include',
});
} catch (error) {
console.error('[QZAuth] Logout error:', error);
} finally {
this.clearUser();
}
}
getUser() {
return this.user;
}
isLoggedIn() {
return this.isAuthenticated;
}
destroy() {
if (this.checkIntervalId) {
clearInterval(this.checkIntervalId);
}
}
}
// Create global instance
global.QZAuth = new QZAuthClient();
})(window);