fixed login loop
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* Job-Info Auth Flow - Standalone
|
||||
* Complete auth lifecycle: acquire tokens, refresh, persist, retrieve
|
||||
* No external imports - loads PocketBase from global scope
|
||||
*/
|
||||
|
||||
class JobInfoAuthFlow {
|
||||
constructor() {
|
||||
this.pb = null;
|
||||
this.tokens = {};
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize: load persisted tokens, check if active, prompt login if needed
|
||||
*/
|
||||
async init() {
|
||||
console.log('[JobInfoAuth] init() called');
|
||||
|
||||
// PocketBase is loaded via script tag
|
||||
if (typeof PocketBase === 'undefined') {
|
||||
throw new Error('PocketBase SDK not loaded. Check script tag in HTML.');
|
||||
}
|
||||
|
||||
this.pb = new PocketBase('http://localhost:8090');
|
||||
console.log('[JobInfoAuth] PocketBase initialized:', this.pb);
|
||||
|
||||
// Load tokens from localStorage
|
||||
this.loadTokens();
|
||||
console.log('[JobInfoAuth] Tokens loaded from storage:', Object.keys(this.tokens));
|
||||
|
||||
// Check if tokens are active
|
||||
const hasActiveTokens = this.isTokenActive('pb-user') || this.isTokenActive('graph-user');
|
||||
|
||||
if (!hasActiveTokens) {
|
||||
console.log('[JobInfoAuth] No active tokens, prompting login...');
|
||||
await this.promptLogin();
|
||||
} else {
|
||||
console.log('[JobInfoAuth] Tokens active, refreshing...');
|
||||
await this.refreshAllTokens();
|
||||
}
|
||||
|
||||
console.log('[JobInfoAuth] init() complete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt user to log in via OAuth popup
|
||||
*/
|
||||
async promptLogin() {
|
||||
console.log('[JobInfoAuth] promptLogin() called');
|
||||
|
||||
try {
|
||||
const authData = await this.pb.collection('users').authWithOAuth2({
|
||||
provider: 'microsoft'
|
||||
});
|
||||
|
||||
console.log('[JobInfoAuth] OAuth successful, authData:', authData);
|
||||
|
||||
// Store PocketBase user token
|
||||
const pbToken = authData.token;
|
||||
const displayName = authData.record?.name || authData.record?.email || 'Unknown';
|
||||
|
||||
this.storeToken('pb-user', pbToken, displayName);
|
||||
console.log('[JobInfoAuth] pb-user token stored');
|
||||
|
||||
// Extract and store Graph token from OAuth metadata
|
||||
const graphToken = this.extractGraphToken(authData.meta || {});
|
||||
if (graphToken) {
|
||||
this.storeToken('graph-user', graphToken, displayName);
|
||||
console.log('[JobInfoAuth] graph-user token stored');
|
||||
} else {
|
||||
console.log('[JobInfoAuth] No graph token in OAuth response');
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('[JobInfoAuth] OAuth failed:', err);
|
||||
throw new Error(`Login failed: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract Graph token from OAuth metadata
|
||||
*/
|
||||
extractGraphToken(metadata) {
|
||||
console.log('[JobInfoAuth] extractGraphToken() called, metadata:', metadata);
|
||||
|
||||
if (!metadata) return null;
|
||||
|
||||
// Microsoft OAuth response includes access_token for Graph
|
||||
if (metadata.accessToken) {
|
||||
return metadata.accessToken;
|
||||
}
|
||||
if (metadata.access_token) {
|
||||
return metadata.access_token;
|
||||
}
|
||||
|
||||
console.log('[JobInfoAuth] No Graph token found in metadata');
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh all tokens - keep them fresh, only replace pb-user on new token
|
||||
*/
|
||||
async refreshAllTokens() {
|
||||
console.log('[JobInfoAuth] refreshAllTokens() called');
|
||||
|
||||
try {
|
||||
// Refresh pb-user token
|
||||
if (this.tokens['pb-user']) {
|
||||
const oldToken = this.tokens['pb-user'].value;
|
||||
|
||||
// Use PocketBase refresh
|
||||
const authData = await this.pb.collection('users').authRefresh();
|
||||
const newToken = authData.token;
|
||||
|
||||
// Only replace if genuinely new
|
||||
if (newToken && newToken !== oldToken) {
|
||||
console.log('[JobInfoAuth] pb-user token refreshed (new token)');
|
||||
const displayName = this.tokens['pb-user'].displayName;
|
||||
this.storeToken('pb-user', newToken, displayName);
|
||||
} else {
|
||||
console.log('[JobInfoAuth] pb-user token still valid, not replacing');
|
||||
}
|
||||
}
|
||||
|
||||
// Graph token: don't require for refresh, but use if available
|
||||
if (this.tokens['graph-user']) {
|
||||
console.log('[JobInfoAuth] graph-user token exists, keeping it');
|
||||
// Optionally refresh graph token here if needed
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.warn('[JobInfoAuth] Token refresh had issues:', err.message);
|
||||
// Don't throw - tokens might still be valid
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store token with metadata
|
||||
*/
|
||||
storeToken(type, token, displayName) {
|
||||
console.log('[JobInfoAuth] storeToken():', type);
|
||||
|
||||
const lastFourDigits = token.slice(-4);
|
||||
const decoded = this.decodeToken(token);
|
||||
const expiresAt = decoded?.exp ? decoded.exp * 1000 : Date.now() + (3600 * 1000); // Default 1 hour
|
||||
|
||||
this.tokens[type] = {
|
||||
value: token,
|
||||
displayName,
|
||||
lastFourDigits,
|
||||
expiresAt,
|
||||
storedAt: Date.now()
|
||||
};
|
||||
|
||||
// Persist to localStorage
|
||||
localStorage.setItem(`job-info-${type}`, JSON.stringify(this.tokens[type]));
|
||||
console.log(`[JobInfoAuth] ${type} stored, expires at:`, new Date(expiresAt).toISOString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Load tokens from localStorage
|
||||
*/
|
||||
loadTokens() {
|
||||
console.log('[JobInfoAuth] loadTokens() called');
|
||||
|
||||
const types = ['pb-user', 'graph-user', 'pb-agent', 'graph-agent'];
|
||||
|
||||
types.forEach(type => {
|
||||
const stored = localStorage.getItem(`job-info-${type}`);
|
||||
if (stored) {
|
||||
try {
|
||||
this.tokens[type] = JSON.parse(stored);
|
||||
console.log(`[JobInfoAuth] Loaded ${type} from localStorage`);
|
||||
} catch (err) {
|
||||
console.warn(`[JobInfoAuth] Failed to parse ${type}:`, err);
|
||||
localStorage.removeItem(`job-info-${type}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if token is active (exists and not expired)
|
||||
*/
|
||||
isTokenActive(type) {
|
||||
const token = this.tokens[type];
|
||||
if (!token) {
|
||||
console.log(`[JobInfoAuth] isTokenActive(${type}): no token stored`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const isExpired = token.expiresAt && Date.now() > token.expiresAt;
|
||||
if (isExpired) {
|
||||
console.log(`[JobInfoAuth] isTokenActive(${type}): expired`);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log(`[JobInfoAuth] isTokenActive(${type}): active`);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current auth state
|
||||
*/
|
||||
getState() {
|
||||
return {
|
||||
'pb-user': this.tokens['pb-user'] || null,
|
||||
'graph-user': this.tokens['graph-user'] || null,
|
||||
'pb-agent': this.tokens['pb-agent'] || null,
|
||||
'graph-agent': this.tokens['graph-agent'] || null,
|
||||
userDisplayName: this.tokens['pb-user']?.displayName || 'Unknown'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout - clear all tokens
|
||||
*/
|
||||
logout() {
|
||||
console.log('[JobInfoAuth] logout() called');
|
||||
|
||||
const types = ['pb-user', 'graph-user', 'pb-agent', 'graph-agent'];
|
||||
types.forEach(type => {
|
||||
localStorage.removeItem(`job-info-${type}`);
|
||||
});
|
||||
|
||||
this.tokens = {};
|
||||
|
||||
if (this.pb) {
|
||||
this.pb.authStore.clear();
|
||||
}
|
||||
|
||||
console.log('[JobInfoAuth] All tokens cleared');
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode JWT token to get exp claim
|
||||
*/
|
||||
decodeToken(token) {
|
||||
try {
|
||||
const parts = token.split('.');
|
||||
if (parts.length !== 3) return null;
|
||||
|
||||
const decoded = JSON.parse(atob(parts[1]));
|
||||
return decoded;
|
||||
} catch (err) {
|
||||
console.warn('[JobInfoAuth] Failed to decode token:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
const JobInfoAuth = new JobInfoAuthFlow();
|
||||
|
||||
// Expose globally
|
||||
if (typeof window !== 'undefined') {
|
||||
window.JobInfoAuth = JobInfoAuth;
|
||||
}
|
||||
Reference in New Issue
Block a user