@clduab11/gemini-flow
Version:
Revolutionary AI agent swarm coordination platform with Google Services integration, multimedia processing, and production-ready monitoring. Features 8 Google AI services, quantum computing capabilities, and enterprise-grade security.
1,537 lines (1,365 loc) • 44.4 kB
text/typescript
/**
* Authentication Manager
*
* Handles Google authentication and user tier detection
* Integrates with Google Cloud Identity for enterprise features
*/
import { Logger } from "../utils/logger.js";
import { CacheManager } from "./cache-manager.js";
import { EventEmitter } from "events";
import {
safeImport,
getFeatureCapabilities,
} from "../utils/feature-detection.js";
import {
OAuth2Tokens,
RefreshTokenResult,
ValidationResult,
} from "../types/auth.js";
export interface UserProfile {
id: string;
email: string;
name: string;
tier: "free" | "pro" | "enterprise" | "ultra";
organization?: string;
permissions: string[];
quotas: {
daily: number;
monthly: number;
concurrent: number;
};
metadata: {
createdAt: Date;
lastActive: Date;
totalRequests: number;
subscription?: any;
tierDetection?: {
method: string;
confidence: number;
detectedAt: Date;
features: string[];
};
};
}
export interface AuthConfig {
clientId?: string;
clientSecret?: string;
redirectUri?: string;
scopes?: string[];
serviceAccountPath?: string;
projectId?: string;
tierDetection?: {
enableVertexAI?: boolean;
enableWorkspaceIntegration?: boolean;
customEnterprisePatterns?: string[];
ultraFeatureChecks?: string[];
};
}
export class AuthenticationManager extends EventEmitter {
private oauth2Client?: any; // OAuth2Client when available
private googleAuth?: any; // GoogleAuth when available
private cache: CacheManager;
private logger: Logger;
private config: AuthConfig;
private userTokens: Map<string, OAuth2Tokens> = new Map(); // User token storage
private tokenRefreshTimers: Map<string, ReturnType<typeof setTimeout>> =
new Map(); // Auto-refresh timers
private readonly TOKEN_REFRESH_BUFFER = 300000; // 5 minutes before expiry
// Default scopes for user authentication
private readonly DEFAULT_SCOPES = [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/cloud-platform",
];
// Enhanced scopes for tier detection
private readonly TIER_DETECTION_SCOPES = [
"https://www.googleapis.com/auth/admin.directory.user.readonly",
"https://www.googleapis.com/auth/admin.directory.domain.readonly",
"https://www.googleapis.com/auth/apps.licensing",
"https://www.googleapis.com/auth/cloud-billing.readonly",
];
constructor(config: AuthConfig = {}) {
super();
this.config = config;
this.logger = new Logger("AuthManager");
this.cache = new CacheManager();
// Initialize auth asynchronously
this.initializeAuth().catch((error) => {
this.logger.error("Failed to initialize authentication", error);
});
}
/**
* Initialize authentication clients
*/
private async initializeAuth(): Promise<void> {
try {
// Check if Google services are available
const capabilities = await getFeatureCapabilities();
if (!capabilities.hasGoogleServices) {
this.logger.warn(
"Google authentication services not available. Some features may be limited.",
);
return;
}
const [googleApis, googleAuth] = await Promise.all([
safeImport("googleapis"),
safeImport("google-auth-library"),
]);
// OAuth2 client for user authentication
if (
this.config.clientId &&
this.config.clientSecret &&
googleApis?.google?.auth?.OAuth2
) {
this.oauth2Client = new googleApis.google.auth.OAuth2(
this.config.clientId,
this.config.clientSecret,
this.config.redirectUri || "http://localhost:3000/callback",
);
}
// Service account authentication for server-to-server
if (
(this.config.serviceAccountPath || this.config.projectId) &&
googleAuth?.GoogleAuth
) {
this.googleAuth = new googleAuth.GoogleAuth({
keyFilename: this.config.serviceAccountPath,
projectId: this.config.projectId,
scopes: this.DEFAULT_SCOPES,
});
}
this.logger.info("Authentication initialized", {
hasOAuth: !!this.oauth2Client,
hasServiceAccount: !!this.config.serviceAccountPath,
projectId: this.config.projectId,
googleServicesAvailable: capabilities.hasGoogleServices,
});
} catch (error) {
this.logger.error("Authentication initialization failed", error);
// Don't throw in constructor context, just log the error
}
}
/**
* Generate OAuth URL for user authentication
*/
generateAuthUrl(state?: string): string {
if (!this.oauth2Client) {
throw new Error("OAuth2 client not configured");
}
return this.oauth2Client.generateAuthUrl({
access_type: "offline",
scope: this.config.scopes || this.DEFAULT_SCOPES,
state,
prompt: "consent",
});
}
/**
* Exchange authorization code for tokens
*/
async authenticateUser(code: string): Promise<UserProfile> {
if (!this.oauth2Client) {
throw new Error("OAuth2 client not configured");
}
try {
// Exchange code for tokens
const { tokens } = await this.oauth2Client.getToken(code);
this.oauth2Client.setCredentials(tokens);
// Store tokens for refresh functionality
const oauth2Tokens: OAuth2Tokens = {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
tokenType: tokens.token_type || "Bearer",
expiresIn: tokens.expiry_date
? Math.floor((tokens.expiry_date - Date.now()) / 1000)
: 3600,
expiresAt: tokens.expiry_date || Date.now() + 3600000,
scope: tokens.scope
? tokens.scope.split(" ")
: this.config.scopes || this.DEFAULT_SCOPES,
idToken: tokens.id_token,
};
// Get user information
const googleApis = await safeImport("googleapis");
if (!googleApis?.google?.oauth2) {
throw new Error("Google APIs not available for user info retrieval");
}
const oauth2 = googleApis.google.oauth2({
version: "v2",
auth: this.oauth2Client!,
});
const userInfo = await oauth2.userinfo.get();
// Detect comprehensive user tier
const tierResult = await this.detectUserTier(
userInfo.data.email!,
tokens,
);
// Create user profile
const profile: UserProfile = {
id: userInfo.data.id!,
email: userInfo.data.email!,
name: userInfo.data.name!,
tier: tierResult.tier,
organization: await this.getOrganization(userInfo.data.email!),
permissions: await this.getUserPermissions(
userInfo.data.email!,
tierResult.tier,
),
quotas: this.getTierQuotas(tierResult.tier),
metadata: {
createdAt: new Date(),
lastActive: new Date(),
totalRequests: 0,
tierDetection: {
method: tierResult.method,
confidence: tierResult.confidence,
detectedAt: new Date(),
features: tierResult.features,
},
},
};
// Store tokens and schedule auto-refresh
this.userTokens.set(profile.id, oauth2Tokens);
this.scheduleTokenRefresh(profile.id, oauth2Tokens);
// Cache user profile
await this.cache.set(`user:${profile.id}`, profile, 3600); // 1 hour
this.logger.info("User authenticated", {
userId: profile.id,
email: profile.email,
tier: profile.tier,
tokenExpiry: new Date(oauth2Tokens.expiresAt),
hasRefreshToken: !!oauth2Tokens.refreshToken,
});
this.emit("user_authenticated", profile);
return profile;
} catch (error) {
this.logger.error("User authentication failed", error);
throw error;
}
}
/**
* Refresh OAuth2 tokens for a user
*/
async refreshToken(userId: string): Promise<RefreshTokenResult> {
try {
if (!this.oauth2Client) {
return {
success: false,
requiresReauth: true,
error: {
name: "ConfigurationError",
message: "OAuth2 client not configured",
code: "OAUTH2_CLIENT_NOT_CONFIGURED",
type: "configuration",
retryable: false,
} as any,
};
}
const storedTokens = this.userTokens.get(userId);
if (!storedTokens || !storedTokens.refreshToken) {
this.logger.warn("No refresh token available for user", { userId });
return {
success: false,
requiresReauth: true,
error: {
name: "AuthenticationError",
message: "No refresh token available",
code: "NO_REFRESH_TOKEN",
type: "authentication",
retryable: false,
} as any,
};
}
this.logger.info("Refreshing tokens for user", { userId });
// Set the refresh token on the OAuth client
this.oauth2Client.setCredentials({
refresh_token: storedTokens.refreshToken,
});
try {
// Refresh the token
const { credentials } = await this.oauth2Client.refreshAccessToken();
// Update stored tokens
const updatedTokens: OAuth2Tokens = {
...storedTokens,
accessToken: credentials.access_token!,
refreshToken: credentials.refresh_token || storedTokens.refreshToken,
expiresAt: credentials.expiry_date!,
expiresIn: Math.floor((credentials.expiry_date! - Date.now()) / 1000),
scope: credentials.scope
? credentials.scope.split(" ")
: storedTokens.scope,
};
// Update stored tokens and reschedule refresh
this.userTokens.set(userId, updatedTokens);
this.scheduleTokenRefresh(userId, updatedTokens);
// Update OAuth client credentials
this.oauth2Client.setCredentials(credentials);
this.logger.info("Token refresh successful", {
userId,
newExpiry: new Date(updatedTokens.expiresAt),
hasNewRefreshToken:
credentials.refresh_token !== storedTokens.refreshToken,
});
this.emit("token_refreshed", { userId, tokens: updatedTokens });
return {
success: true,
credentials: {
type: "oauth2",
provider: "google",
accessToken: updatedTokens.accessToken,
refreshToken: updatedTokens.refreshToken,
expiresAt: updatedTokens.expiresAt,
scope: updatedTokens.scope,
issuedAt: Date.now(),
metadata: {
tokenType: updatedTokens.tokenType,
refreshedAt: Date.now(),
},
},
};
} catch (refreshError: any) {
this.logger.error("Token refresh failed", {
userId,
error: refreshError,
});
// Check if refresh token is invalid (requires re-authentication)
const requiresReauth = this.isRefreshTokenInvalid(refreshError);
if (requiresReauth) {
// Clean up stored tokens
this.userTokens.delete(userId);
this.clearTokenRefreshTimer(userId);
}
return {
success: false,
requiresReauth,
error: {
name: refreshError.name || "TokenRefreshError",
message: refreshError.message || "Failed to refresh token",
code: "TOKEN_REFRESH_FAILED",
type: "authentication",
retryable: !requiresReauth,
originalError: refreshError,
} as any,
};
}
} catch (error: any) {
this.logger.error("Token refresh process failed", { userId, error });
return {
success: false,
requiresReauth: false,
error: {
name: error.name || "TokenRefreshError",
message: error.message || "Token refresh process failed",
code: "TOKEN_REFRESH_PROCESS_FAILED",
type: "authentication",
retryable: true,
originalError: error,
} as any,
};
}
}
/**
* Validate OAuth2 tokens for a user
*/
async validateTokens(userId: string): Promise<ValidationResult> {
try {
const storedTokens = this.userTokens.get(userId);
if (!storedTokens) {
return {
valid: false,
error: "No tokens found for user",
};
}
const now = Date.now();
const timeUntilExpiry = storedTokens.expiresAt - now;
const expiresIn = Math.floor(timeUntilExpiry / 1000);
// Check if token is expired
if (timeUntilExpiry <= 0) {
this.logger.debug("Token expired", {
userId,
expiredAt: new Date(storedTokens.expiresAt),
});
return {
valid: false,
expired: true,
error: "Access token has expired",
};
}
// Check if token is about to expire (within refresh buffer)
const needsRefresh = timeUntilExpiry <= this.TOKEN_REFRESH_BUFFER;
if (needsRefresh && storedTokens.refreshToken) {
this.logger.debug("Token needs refresh soon", { userId, expiresIn });
}
return {
valid: true,
expired: false,
expiresIn,
scopes: storedTokens.scope,
};
} catch (error: any) {
this.logger.error("Token validation failed", { userId, error });
return {
valid: false,
error: error.message || "Token validation failed",
};
}
}
/**
* Schedule automatic token refresh before expiration
*/
private scheduleTokenRefresh(userId: string, tokens: OAuth2Tokens): void {
// Clear any existing timer
this.clearTokenRefreshTimer(userId);
if (!tokens.refreshToken) {
this.logger.debug(
"No refresh token available, skipping auto-refresh scheduling",
{ userId },
);
return;
}
const now = Date.now();
const timeUntilExpiry = tokens.expiresAt - now;
const refreshTime = timeUntilExpiry - this.TOKEN_REFRESH_BUFFER;
// Only schedule if there's enough time before expiry
if (refreshTime <= 0) {
this.logger.warn("Token expires too soon to schedule refresh", {
userId,
expiresAt: new Date(tokens.expiresAt),
timeUntilExpiry: Math.floor(timeUntilExpiry / 1000),
});
return;
}
const timer = setTimeout(async () => {
this.logger.debug("Auto-refreshing token for user", { userId });
try {
const result = await this.refreshToken(userId);
if (!result.success) {
this.logger.warn("Auto token refresh failed", {
userId,
error: result.error,
});
this.emit("token_refresh_failed", { userId, error: result.error });
}
} catch (error) {
this.logger.error("Auto token refresh error", { userId, error });
this.emit("token_refresh_failed", { userId, error });
}
}, refreshTime);
this.tokenRefreshTimers.set(userId, timer);
this.logger.debug("Scheduled token refresh", {
userId,
refreshIn: Math.floor(refreshTime / 1000),
expiresAt: new Date(tokens.expiresAt),
});
}
/**
* Clear token refresh timer for a user
*/
private clearTokenRefreshTimer(userId: string): void {
const timer = this.tokenRefreshTimers.get(userId);
if (timer) {
clearTimeout(timer);
this.tokenRefreshTimers.delete(userId);
this.logger.debug("Cleared token refresh timer", { userId });
}
}
/**
* Check if refresh token error indicates need for re-authentication
*/
private isRefreshTokenInvalid(error: any): boolean {
if (!error) return false;
const errorMessage = (error.message || "").toLowerCase();
const invalidTokenIndicators = [
"invalid_grant",
"invalid_request",
"unauthorized_client",
"refresh token is invalid",
"refresh token has expired",
"token has been expired or revoked",
];
return invalidTokenIndicators.some((indicator) =>
errorMessage.includes(indicator),
);
}
/**
* Get stored tokens for a user
*/
getUserTokens(userId: string): OAuth2Tokens | undefined {
return this.userTokens.get(userId);
}
/**
* Check if user needs token refresh
*/
async needsTokenRefresh(userId: string): Promise<boolean> {
const validation = await this.validateTokens(userId);
if (!validation.valid) return true;
// Check if expiring within buffer time
return (validation.expiresIn || 0) <= this.TOKEN_REFRESH_BUFFER / 1000;
}
/**
* Force refresh token for a user (even if not expired)
*/
async forceRefreshToken(userId: string): Promise<RefreshTokenResult> {
this.logger.info("Force refreshing token", { userId });
return this.refreshToken(userId);
}
/**
* Comprehensive user tier detection with multiple strategies
*/
async detectUserTier(
email?: string,
tokens?: any,
): Promise<{
tier: "free" | "pro" | "enterprise" | "ultra";
method: string;
confidence: number;
features: string[];
}> {
if (!email) {
return {
tier: "free",
method: "default",
confidence: 1.0,
features: [],
};
}
try {
// Check cache first
const cacheKey = `tier-detection:${email}`;
const cachedResult = await this.cache.get(cacheKey);
if (cachedResult) {
this.logger.debug("Using cached tier detection", {
email,
tier: cachedResult.tier,
});
return cachedResult;
}
// Initialize detection result
let detectionResult = {
tier: "free" as "free" | "pro" | "enterprise" | "ultra",
method: "domain-analysis",
confidence: 0.5,
features: [] as string[],
};
const domain = email.split("@")[1];
const detectedFeatures: string[] = [];
// 1. Ultra Tier Detection (Highest Priority)
const ultraResult = await this.detectUltraTier(email, domain, tokens);
if (ultraResult.isUltra) {
detectionResult = {
tier: "ultra",
method: "ultra-features",
confidence: ultraResult.confidence,
features: ultraResult.features,
};
detectedFeatures.push(...ultraResult.features);
}
// 2. Enterprise Tier Detection (High Priority)
else {
const enterpriseResult = await this.detectEnterpriseTier(
email,
domain,
tokens,
);
if (enterpriseResult.isEnterprise) {
detectionResult = {
tier: "enterprise",
method: "enterprise-workspace",
confidence: enterpriseResult.confidence,
features: enterpriseResult.features,
};
detectedFeatures.push(...enterpriseResult.features);
}
// 3. Pro Tier Detection (Medium Priority)
else {
const proResult = await this.detectProTier(email, domain);
if (proResult.isPro) {
detectionResult = {
tier: "pro",
method: "subscription-check",
confidence: proResult.confidence,
features: proResult.features,
};
detectedFeatures.push(...proResult.features);
}
}
}
// Cache the result for 24 hours
await this.cache.set(cacheKey, detectionResult, 86400);
this.logger.info("Tier detection completed", {
email,
tier: detectionResult.tier,
method: detectionResult.method,
confidence: detectionResult.confidence,
features: detectedFeatures.length,
});
return detectionResult;
} catch (error) {
this.logger.error("Tier detection failed", { email, error });
return {
tier: "free",
method: "error-fallback",
confidence: 1.0,
features: ["error-occurred"],
};
}
}
/**
* Detect Ultra tier features (Google AI Advanced, Vertex AI Enterprise)
*/
private async detectUltraTier(
email: string,
domain: string,
tokens?: any,
): Promise<{
isUltra: boolean;
confidence: number;
features: string[];
}> {
const features: string[] = [];
let confidence = 0;
try {
// Check for Vertex AI Enterprise access
if (tokens && this.config.tierDetection?.enableVertexAI !== false) {
const hasVertexAI = await this.checkVertexAIAccess(tokens);
if (hasVertexAI) {
features.push("vertex-ai-enterprise");
confidence += 0.4;
}
}
// Check for Google AI Advanced subscription markers
const hasAdvancedAI = await this.checkGoogleAIAdvanced(email);
if (hasAdvancedAI) {
features.push("google-ai-advanced");
confidence += 0.3;
}
// Check for enterprise billing patterns
const hasEnterpriseBilling = await this.checkEnterpriseBilling(
email,
domain,
);
if (hasEnterpriseBilling) {
features.push("enterprise-billing");
confidence += 0.2;
}
// Check for ultra-specific domains or patterns
const ultraDomains = this.config.tierDetection?.ultraFeatureChecks || [
"x.company",
"meta.com",
"openai.com",
"anthropic.com",
];
if (ultraDomains.includes(domain)) {
features.push("ultra-domain");
confidence += 0.3;
}
// Check for custom integration patterns
const hasCustomIntegrations = await this.checkCustomIntegrations(email);
if (hasCustomIntegrations) {
features.push("custom-integrations");
confidence += 0.2;
}
return {
isUltra: confidence >= 0.7, // Require high confidence for ultra tier
confidence: Math.min(confidence, 1.0),
features,
};
} catch (error) {
this.logger.debug("Ultra tier detection error", { email, error });
return { isUltra: false, confidence: 0, features: [] };
}
}
/**
* Detect Enterprise tier (Google Workspace + Enterprise features)
*/
private async detectEnterpriseTier(
email: string,
domain: string,
tokens?: any,
): Promise<{
isEnterprise: boolean;
confidence: number;
features: string[];
}> {
const features: string[] = [];
let confidence = 0;
try {
// Check known enterprise domains
const enterpriseDomains = process.env.ENTERPRISE_DOMAINS?.split(",") || [
"google.com",
"alphabet.com",
"deepmind.com",
"microsoft.com",
"amazon.com",
"apple.com",
];
// Add custom enterprise patterns
const customPatterns =
this.config.tierDetection?.customEnterprisePatterns || [];
const allPatterns = [...enterpriseDomains, ...customPatterns];
if (allPatterns.includes(domain)) {
features.push("enterprise-domain");
confidence += 0.6;
}
// Check Google Workspace status
if (
tokens &&
this.config.tierDetection?.enableWorkspaceIntegration !== false
) {
const workspaceResult = await this.checkGoogleWorkspace(email, tokens);
if (workspaceResult.isWorkspace) {
features.push("google-workspace");
confidence += 0.4;
if (workspaceResult.isEnterprise) {
features.push("workspace-enterprise");
confidence += 0.3;
}
}
}
// Check domain-based patterns
const domainAnalysis = this.analyzeDomainPatterns(domain);
if (domainAnalysis.isEnterprise) {
features.push(...domainAnalysis.indicators);
confidence += domainAnalysis.score;
}
// Check for enterprise OAuth scopes
const hasEnterpriseScopes = await this.checkEnterpriseScopes(tokens);
if (hasEnterpriseScopes) {
features.push("enterprise-scopes");
confidence += 0.2;
}
return {
isEnterprise: confidence >= 0.5, // Moderate confidence threshold
confidence: Math.min(confidence, 1.0),
features,
};
} catch (error) {
this.logger.debug("Enterprise tier detection error", { email, error });
return { isEnterprise: false, confidence: 0, features: [] };
}
}
/**
* Detect Pro tier (Paid subscription)
*/
private async detectProTier(
email: string,
_domain: string,
): Promise<{
isPro: boolean;
confidence: number;
features: string[];
}> {
const features: string[] = [];
let confidence = 0;
try {
// Check billing/subscription systems
const hasProSubscription = await this.checkProSubscription(email);
if (hasProSubscription) {
features.push("pro-subscription");
confidence += 0.8;
}
// Check payment method presence
const hasPaymentMethod = await this.checkPaymentMethod(email);
if (hasPaymentMethod) {
features.push("payment-method");
confidence += 0.3;
}
// Check usage patterns that suggest paid tier
const usagePatterns = await this.analyzeUsagePatterns(email);
if (usagePatterns.suggestsPro) {
features.push("pro-usage-patterns");
confidence += 0.2;
}
return {
isPro: confidence >= 0.5,
confidence: Math.min(confidence, 1.0),
features,
};
} catch (error) {
this.logger.debug("Pro tier detection error", { email, error });
return { isPro: false, confidence: 0, features: [] };
}
}
/**
* Check Vertex AI Enterprise access
*/
private async checkVertexAIAccess(tokens: any): Promise<boolean> {
try {
const googleApis = await safeImport("googleapis");
if (!googleApis?.google) {
return false; // Can't check without Google APIs
}
// Set up temporary OAuth client with tokens
const tempClient = new googleApis.google.auth.OAuth2(
this.config.clientId,
this.config.clientSecret,
);
tempClient.setCredentials(tokens);
// Try to access Vertex AI APIs
const cloudResourceManager = googleApis.google.cloudresourcemanager({
version: "v1",
auth: tempClient,
});
const projects = await cloudResourceManager.projects.list();
// Check if any projects have Vertex AI enabled
if (projects.data.projects && projects.data.projects.length > 0) {
for (const project of projects.data.projects) {
try {
const serviceUsage = googleApis.google.serviceusage({
version: "v1",
auth: tempClient,
});
const services = await serviceUsage.services.list({
parent: `projects/${project.projectId}`,
filter: "state:ENABLED",
});
const hasVertexAI = services.data.services?.some((service) =>
service.name?.includes("aiplatform.googleapis.com"),
);
if (hasVertexAI) {
return true;
}
} catch (error) {
// Continue checking other projects
continue;
}
}
}
return false;
} catch (error) {
this.logger.debug("Vertex AI access check failed", error);
return false;
}
}
/**
* Check Google AI Advanced subscription
*/
private async checkGoogleAIAdvanced(_email: string): Promise<boolean> {
// This would integrate with Google AI subscription API
// For now, return false as placeholder
// TODO: Implement actual Google AI Advanced API integration
// When implementing, wrap the actual API call in try-catch:
// try {
// const result = await googleAIAPI.checkSubscription(email);
// return result.hasAdvanced;
// } catch (error) {
// this.logger.debug('Google AI Advanced check failed', { email, error });
// return false;
// }
return false;
}
/**
* Check enterprise billing patterns
*/
private async checkEnterpriseBilling(
_email: string,
_domain: string,
): Promise<boolean> {
// Check for enterprise billing indicators
// This would integrate with Google Cloud Billing API
// TODO: Implement actual billing API integration
// When implementing, wrap the actual API call in try-catch:
// try {
// const result = await billingAPI.checkEnterprise(email, _domain);
// return result.isEnterprise;
// } catch (error) {
// this.logger.debug('Enterprise billing check failed', { email, error });
// return false;
// }
return false;
}
/**
* Check for custom integrations
*/
private async checkCustomIntegrations(_email: string): Promise<boolean> {
// Check for signs of custom API integrations
// This could check API key usage, custom endpoints, etc.
// TODO: Implement custom integration detection
// When implementing, wrap the actual API call in try-catch:
// try {
// const result = await customAPI.checkIntegrations(email);
// return result.hasCustomIntegrations;
// } catch (error) {
// this.logger.debug('Custom integrations check failed', { email, error });
// return false;
// }
return false;
}
/**
* Check Google Workspace status
*/
private async checkGoogleWorkspace(
email: string,
tokens: any,
): Promise<{
isWorkspace: boolean;
isEnterprise: boolean;
}> {
try {
const googleApis = await safeImport("googleapis");
if (!googleApis?.google) {
return { isWorkspace: false, isEnterprise: false }; // Can't check without Google APIs
}
// Set up temporary OAuth client with tokens
const tempClient = new googleApis.google.auth.OAuth2(
this.config.clientId,
this.config.clientSecret,
);
tempClient.setCredentials(tokens);
// Try to access Admin Directory API
const admin = googleApis.google.admin({
version: "directory_v1",
auth: tempClient,
});
const domain = email.split("@")[1];
try {
// This would require admin privileges to work fully
// Fix domains.get API parameter - use customer instead of domain
const domainInfo = await admin.domains.get({
customer: "my_customer",
domainName: domain,
} as any);
if (domainInfo.data) {
// Check for enterprise features
const isEnterprise =
(domainInfo.data as any).verified &&
(domainInfo.data as any).domainName === domain;
return {
isWorkspace: true,
isEnterprise,
};
}
} catch (adminError) {
// If admin API fails, try alternate detection methods
// Check OAuth scopes to infer workspace status
if (tokens.scope && tokens.scope.includes("admin.directory")) {
return { isWorkspace: true, isEnterprise: false };
}
}
return { isWorkspace: false, isEnterprise: false };
} catch (error) {
this.logger.debug("Google Workspace check failed", { email, error });
return { isWorkspace: false, isEnterprise: false };
}
}
/**
* Analyze domain patterns for enterprise indicators
*/
private analyzeDomainPatterns(domain: string): {
isEnterprise: boolean;
score: number;
indicators: string[];
} {
const indicators: string[] = [];
let score = 0;
// Common enterprise domain patterns
const enterprisePatterns = [
/\.corp$/i,
/\.company$/i,
/\.enterprise$/i,
/\.inc$/i,
/\.llc$/i,
/\.ltd$/i,
];
for (const pattern of enterprisePatterns) {
if (pattern.test(domain)) {
indicators.push("enterprise-tld");
score += 0.2;
break;
}
}
// Check for fortune 500 patterns or known enterprise domains
const knownEnterprises = [
"walmart.com",
"amazon.com",
"apple.com",
"berkshirehathaway.com",
"unitedhealth.com",
"mckesson.com",
"cvshealth.com",
"alphabet.com",
];
if (knownEnterprises.includes(domain.toLowerCase())) {
indicators.push("fortune-500");
score += 0.4;
}
// Check domain length and complexity (enterprise domains often longer)
if (domain.length > 15 && domain.includes("-")) {
indicators.push("complex-domain");
score += 0.1;
}
return {
isEnterprise: score >= 0.2,
score,
indicators,
};
}
/**
* Check for enterprise OAuth scopes
*/
private async checkEnterpriseScopes(tokens: any): Promise<boolean> {
if (!tokens || !tokens.scope) {
return false;
}
const enterpriseScopes = [
"admin.directory",
"admin.reports",
"apps.licensing",
"cloud-billing",
"cloud-platform",
];
const hasEnterpriseScope = enterpriseScopes.some((scope) =>
tokens.scope.includes(scope),
);
return hasEnterpriseScope;
}
/**
* Check for payment method (enhanced from existing stub)
*/
private async checkPaymentMethod(_email: string): Promise<boolean> {
// This would integrate with payment processors (Stripe, etc.)
// Check if user has valid payment method on file
// TODO: Implement actual payment method verification
// When implementing, wrap the actual API call in try-catch:
// try {
// const result = await paymentAPI.checkPaymentMethod(email);
// return result.hasValidPayment;
// } catch (error) {
// this.logger.debug('Payment method check failed', { email, error });
// return false;
// }
return false;
}
/**
* Analyze usage patterns to suggest tier
*/
private async analyzeUsagePatterns(email: string): Promise<{
suggestsPro: boolean;
indicators: string[];
}> {
try {
const indicators: string[] = [];
// Check historical usage from cache
const usageHistory = await this.cache.get(`usage:${email}`);
if (usageHistory) {
const { daily, requests, features } = usageHistory;
// High daily usage suggests pro tier
if (daily > 500) {
indicators.push("high-daily-usage");
}
// Regular API usage
if (requests > 50) {
indicators.push("frequent-api-usage");
}
// Advanced feature usage
if (features && features.includes("batch_processing")) {
indicators.push("advanced-features");
}
}
return {
suggestsPro: indicators.length >= 2,
indicators,
};
} catch (error) {
this.logger.debug("Usage pattern analysis failed", { email, error });
return { suggestsPro: false, indicators: [] };
}
}
/**
* Get organization information from email domain
*/
private async getOrganization(email: string): Promise<string | undefined> {
try {
const domain = email.split("@")[1];
// For now, return domain as organization
// TODO: Implement proper Google Workspace organization lookup
return domain;
} catch (error) {
this.logger.debug("Organization lookup failed", { email, error });
return undefined;
}
}
/**
* Get user permissions based on email and tier
*/
private async getUserPermissions(
email: string,
tier: "free" | "pro" | "enterprise" | "ultra",
): Promise<string[]> {
const basePermissions = ["read", "basic_ai"];
switch (tier) {
case "free":
return basePermissions;
case "pro":
return [
...basePermissions,
"advanced_ai",
"batch_processing",
"priority_support",
];
case "enterprise":
return [
...basePermissions,
"advanced_ai",
"batch_processing",
"priority_support",
"custom_models",
"enterprise_security",
"audit_logs",
"admin_access",
];
case "ultra":
return [
...basePermissions,
"advanced_ai",
"batch_processing",
"priority_support",
"custom_models",
"enterprise_security",
"audit_logs",
"admin_access",
"vertex_ai_access",
"unlimited_requests",
"custom_integrations",
"dedicated_support",
"early_access_features",
];
default:
return basePermissions;
}
}
/**
* Get quota limits for user tier
*/
private getTierQuotas(tier: "free" | "pro" | "enterprise" | "ultra") {
const quotas = {
free: { daily: 100, monthly: 1000, concurrent: 2 },
pro: { daily: 1000, monthly: 20000, concurrent: 10 },
enterprise: { daily: 10000, monthly: 500000, concurrent: 50 },
ultra: { daily: -1, monthly: -1, concurrent: 200 }, // -1 means unlimited
};
return quotas[tier];
}
/**
* Check for pro subscription (placeholder for billing integration)
*/
private async checkProSubscription(_email: string): Promise<boolean> {
// TODO: Integrate with billing system (Stripe, Google Cloud Billing, etc.)
// For now, return false
return false;
}
/**
* Validate and refresh user session
*/
async validateSession(userId: string): Promise<UserProfile | null> {
try {
// Check cache first
const cachedProfile = await this.cache.get(`user:${userId}`);
if (!cachedProfile) {
this.logger.info("Session expired - no cached profile", { userId });
this.emit("session_expired", userId);
return null;
}
// Validate tokens if available
const tokenValidation = await this.validateTokens(userId);
if (!tokenValidation.valid) {
// Try to refresh if token is expired but we have a refresh token
if (tokenValidation.expired) {
const storedTokens = this.userTokens.get(userId);
if (storedTokens?.refreshToken) {
this.logger.debug("Attempting token refresh for expired session", {
userId,
});
const refreshResult = await this.refreshToken(userId);
if (refreshResult.success) {
this.logger.info("Session restored via token refresh", {
userId,
});
} else if (refreshResult.requiresReauth) {
this.logger.warn("Session requires re-authentication", {
userId,
});
// Clean up session
await this.cache.delete(`user:${userId}`);
this.emit("session_expired", userId);
return null;
}
} else {
this.logger.warn("Session expired and no refresh token available", {
userId,
});
await this.cache.delete(`user:${userId}`);
this.emit("session_expired", userId);
return null;
}
}
}
// Update last active
cachedProfile.metadata.lastActive = new Date();
await this.cache.set(`user:${userId}`, cachedProfile, 3600);
return cachedProfile as UserProfile;
} catch (error) {
this.logger.error("Session validation failed", { userId, error });
return null;
}
}
/**
* Check if user has permission
*/
async hasPermission(userId: string, permission: string): Promise<boolean> {
const profile = await this.validateSession(userId);
if (!profile) {
return false;
}
return profile.permissions.includes(permission);
}
/**
* Check quota usage
*/
async checkQuota(userId: string, requestCount: number = 1): Promise<boolean> {
const profile = await this.validateSession(userId);
if (!profile) {
return false;
}
// Simple daily quota check (would be more sophisticated in production)
const today = new Date().toISOString().split("T")[0];
const dailyUsage = (await this.cache.get(`quota:${userId}:${today}`)) || 0;
if (dailyUsage + requestCount > profile.quotas.daily) {
this.logger.warn("Quota exceeded", {
userId,
dailyUsage,
requestCount,
limit: profile.quotas.daily,
});
this.emit("quota_exceeded", {
userId,
usage: dailyUsage,
limit: profile.quotas.daily,
});
return false;
}
// Update usage
await this.cache.set(
`quota:${userId}:${today}`,
dailyUsage + requestCount,
86400,
);
return true;
}
/**
* Service account authentication for internal operations
*/
async getServiceAccountAuth(): Promise<any> {
try {
if (!this.googleAuth) {
throw new Error("Google Auth not initialized");
}
return await this.googleAuth.getClient();
} catch (error) {
this.logger.error("Service account authentication failed", error);
throw error;
}
}
/**
* Revoke user tokens
*/
async revokeUser(userId: string): Promise<void> {
try {
// Remove from cache
await this.cache.delete(`user:${userId}`);
// Clean up stored tokens and timers
const storedTokens = this.userTokens.get(userId);
if (storedTokens) {
this.userTokens.delete(userId);
this.clearTokenRefreshTimer(userId);
// Revoke OAuth tokens at provider if available
if (this.oauth2Client && storedTokens.accessToken) {
try {
this.oauth2Client.setCredentials({
access_token: storedTokens.accessToken,
refresh_token: storedTokens.refreshToken,
});
await this.oauth2Client.revokeCredentials();
this.logger.debug("OAuth tokens revoked at provider", { userId });
} catch (error) {
this.logger.debug("Token revocation at provider failed", {
userId,
error,
});
}
}
}
this.logger.info("User session revoked", { userId });
this.emit("user_revoked", userId);
} catch (error) {
this.logger.error("User revocation failed", { userId, error });
throw error;
}
}
/**
* Get current user context for security operations
*/
async getCurrentUserContext(): Promise<{
userId: string;
tier: string;
permissions: string[];
} | null> {
// This would typically get from current session/token
// For now, return null - should be implemented based on session management
// When implementing, wrap the actual session logic in try-catch:
// try {
// const session = await this.getCurrentSession();
// return session ? session.userContext : null;
// } catch (error) {
// this.logger.error('Get current user context failed', error);
// return null;
// }
return null;
}
/**
* Get current user ID from active session
*/
async getCurrentUserId(): Promise<string | null> {
// This would typically extract from JWT token or session
// For now, return null - should be implemented based on session management
// When implementing, wrap the actual token logic in try-catch:
// try {
// const token = await this.getCurrentToken();
// return token ? token.userId : null;
// } catch (error) {
// this.logger.error('Get current user ID failed', error);
// return null;
// }
return null;
}
/**
* Determine user tier (alias for detectUserTier for backwards compatibility)
*/
async determineUserTier(
email?: string,
tokens?: any,
): Promise<{
tier: "free" | "pro" | "enterprise" | "ultra";
method: string;
confidence: number;
features: string[];
}> {
return this.detectUserTier(email, tokens);
}
/**
* Get authentication metrics
*/
getMetrics() {
return {
configuredClients: {
oauth2: !!this.oauth2Client,
serviceAccount: !!this.config.serviceAccountPath,
},
scopes: this.config.scopes || this.DEFAULT_SCOPES,
tokenManagement: {
activeUsers: this.userTokens.size,
scheduledRefreshes: this.tokenRefreshTimers.size,
refreshBufferMs: this.TOKEN_REFRESH_BUFFER,
},
};
}
}