docs: add quixzoom-auth-core product to AAMOS
- Product documentation in docs/products/ - Updated MEMORY.md with product info - quiXzoom Auth Core as AAMOS Identity product
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user