1a12fb870b
- New /developers/ page with API docs, SDKs, pricing, use cases - OpenAPI 3.0 spec for Orders, Missions, Photos, Analytics - Case study: Glasskiosken i Flatenbadet — complete ROI analysis - Updated /order/ with recurring missions and frequency dropdown
244 lines
6.5 KiB
JavaScript
244 lines
6.5 KiB
JavaScript
/**
|
|
* quiXzoom SSO Client v2.0
|
|
* Universal cross-domain silent login
|
|
*
|
|
* Usage:
|
|
* <script src="https://auth.quixzoom.com/sso-client-v2.js"></script>
|
|
* <script>
|
|
* QZAuth.init({
|
|
* ssoUrl: 'https://auth.quixzoom.com',
|
|
* onLogin: (user) => console.log('Logged in:', user),
|
|
* onLogout: () => console.log('Logged out'),
|
|
* });
|
|
* </script>
|
|
*/
|
|
|
|
(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);
|