48ea61cdcc
- Product documentation in docs/products/ - Updated MEMORY.md with product info - quiXzoom Auth Core as AAMOS Identity product
224 lines
4.7 KiB
JavaScript
224 lines
4.7 KiB
JavaScript
/**
|
|
* quiXzoom SSO Client
|
|
* Universal auth client for all quixzoom properties
|
|
*
|
|
* Usage:
|
|
* import { quixzoomAuth } from './sso-client.js';
|
|
*
|
|
* // Check if logged in
|
|
* const user = await quixzoomAuth.getUser();
|
|
*
|
|
* // Login
|
|
* await quixzoomAuth.login(email, password);
|
|
*
|
|
* // Logout
|
|
* await quixzoomAuth.logout();
|
|
*/
|
|
|
|
const AUTH_BASE_URL = 'https://auth.quixzoom.com';
|
|
const COOKIE_NAMES = ['qz_access_token', 'qz_refresh_token'];
|
|
|
|
class QuixzoomAuth {
|
|
constructor() {
|
|
this.user = null;
|
|
this.tokenRefreshTimer = null;
|
|
}
|
|
|
|
/**
|
|
* Initialize auth - call on page load
|
|
*/
|
|
async init() {
|
|
// Check if we have a valid session
|
|
const user = await this.getUser();
|
|
|
|
if (user) {
|
|
this.startTokenRefresh();
|
|
}
|
|
|
|
return user;
|
|
}
|
|
|
|
/**
|
|
* Get current user (checks cookie automatically)
|
|
*/
|
|
async getUser() {
|
|
try {
|
|
const response = await fetch(`${AUTH_BASE_URL}/auth/me`, {
|
|
method: 'GET',
|
|
credentials: 'include', // Important: sends cookies
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
this.user = data.user;
|
|
return data.user;
|
|
}
|
|
|
|
// Try to refresh if access token expired
|
|
if (response.status === 401) {
|
|
const refreshed = await this.refreshToken();
|
|
if (refreshed) {
|
|
return this.getUser();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
} catch (error) {
|
|
console.error('Auth check failed:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Login with email/password
|
|
*/
|
|
async login(email, password, deviceInfo = {}) {
|
|
const response = await fetch(`${AUTH_BASE_URL}/auth/login`, {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
email,
|
|
password,
|
|
device_info: deviceInfo,
|
|
redirect_url: window.location.href,
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.error || 'Login failed');
|
|
}
|
|
|
|
const data = await response.json();
|
|
this.user = data.user;
|
|
this.startTokenRefresh();
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Refresh access token
|
|
*/
|
|
async refreshToken() {
|
|
try {
|
|
const response = await fetch(`${AUTH_BASE_URL}/auth/refresh`, {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
this.user = data.user;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
} catch (error) {
|
|
console.error('Token refresh failed:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Logout
|
|
*/
|
|
async logout() {
|
|
try {
|
|
await fetch(`${AUTH_BASE_URL}/auth/logout`, {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
});
|
|
} catch (error) {
|
|
console.error('Logout error:', error);
|
|
}
|
|
|
|
this.user = null;
|
|
this.stopTokenRefresh();
|
|
|
|
// Clear any local storage
|
|
localStorage.removeItem('quixzoom_user');
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check if user is authenticated
|
|
*/
|
|
isAuthenticated() {
|
|
return this.user !== null;
|
|
}
|
|
|
|
/**
|
|
* Redirect to login if not authenticated
|
|
*/
|
|
async requireAuth(redirectUrl = window.location.href) {
|
|
const user = await this.getUser();
|
|
|
|
if (!user) {
|
|
// Store intended URL
|
|
sessionStorage.setItem('auth_redirect', redirectUrl);
|
|
|
|
// Redirect to login page
|
|
const loginUrl = `https://auth.quixzoom.com/login?redirect=${encodeURIComponent(redirectUrl)}`;
|
|
window.location.href = loginUrl;
|
|
return null;
|
|
}
|
|
|
|
return user;
|
|
}
|
|
|
|
/**
|
|
* Start automatic token refresh
|
|
*/
|
|
startTokenRefresh() {
|
|
// Refresh 2 minutes before expiry
|
|
const refreshInterval = (15 - 2) * 60 * 1000; // 13 minutes
|
|
|
|
this.stopTokenRefresh();
|
|
this.tokenRefreshTimer = setInterval(() => {
|
|
this.refreshToken();
|
|
}, refreshInterval);
|
|
}
|
|
|
|
/**
|
|
* Stop automatic token refresh
|
|
*/
|
|
stopTokenRefresh() {
|
|
if (this.tokenRefreshTimer) {
|
|
clearInterval(this.tokenRefreshTimer);
|
|
this.tokenRefreshTimer = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get auth headers for API requests
|
|
*/
|
|
getAuthHeaders() {
|
|
// Cookies are sent automatically, but we can add Bearer if needed
|
|
return {
|
|
'Accept': 'application/json',
|
|
};
|
|
}
|
|
}
|
|
|
|
// Singleton instance
|
|
export const quixzoomAuth = new QuixzoomAuth();
|
|
|
|
// Auto-init on import
|
|
quixzoomAuth.init();
|
|
|
|
// Also expose for non-module usage
|
|
if (typeof window !== 'undefined') {
|
|
window.quixzoomAuth = quixzoomAuth;
|
|
}
|