import { getConfig, type Config } from './config.js';
export interface AuthCredentials {
access_token: string;
refresh_token?: string;
expiry_date?: number;
token_type: string;
scope?: string;
}
export interface AuthProvider {
getAccessToken(): Promise<string>;
refreshToken?(): Promise<AuthCredentials>;
isExpired(): boolean;
}
export interface OAuthCredentials {
client_id: string;
client_secret: string;
redirect_uri: string;
}
export interface ServiceAccountCredentials {
type: 'service_account';
project_id: string;
private_key_id: string;
private_key: string;
client_email: string;
client_id: string;
auth_uri: string;
token_uri: string;
}
// Error types
export class AuthError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly details?: unknown
) {
super(message);
this.name = 'AuthError';
}
}
export class TokenExpiredError extends AuthError {
constructor(message = 'Token has expired') {
super(message, 'TOKEN_EXPIRED');
this.name = 'TokenExpiredError';
}
}
export class InvalidCredentialsError extends AuthError {
constructor(message = 'Invalid credentials') {
super(message, 'INVALID_CREDENTIALS');
this.name = 'InvalidCredentialsError';
}
}
// Helper functions
export function getAuthMode(): 'oauth' | 'service_account' {
const config = getConfig();
return config.auth_mode;
}
export function getOAuthScopes(): string[] {
const config = getConfig();
const baseScopes = config.google_scopes;
// Adjust scopes based on feature flags
const scopes = new Set<string>();
for (const scope of baseScopes) {
// Skip gmail scopes here, we'll add them based on flags
if (scope.includes('gmail')) {
continue;
}
scopes.add(scope);
}
// Add Gmail scopes based on feature flags
// Always add readonly scope for basic Gmail access
scopes.add('https://www.googleapis.com/auth/gmail.readonly');
// Add send scope if enabled
if (config.features.gmail_send_enabled) {
scopes.add('https://www.googleapis.com/auth/gmail.send');
}
// Add modify scope if labels modification is enabled
if (config.features.gmail_modify_labels_enabled) {
scopes.add('https://www.googleapis.com/auth/gmail.modify');
}
return Array.from(scopes);
}
export function isTokenExpired(expiryDate: number | undefined): boolean {
if (!expiryDate) {
return true;
}
// Consider token expired 5 minutes before actual expiry
const buffer = 5 * 60 * 1000;
return Date.now() >= expiryDate - buffer;
}
export function maskToken(token: string): string {
if (token.length <= 10) {
return '***';
}
return `${token.substring(0, 5)}...${token.substring(token.length - 5)}`;
}
// Check if folder is in allowlist
export function isFolderAllowed(folderId: string, config?: Config): boolean {
const conf = config ?? getConfig();
const allowlist = conf.drive_allowlist_folders;
// If allowlist is empty, all folders are allowed
if (allowlist.length === 0) {
return true;
}
return allowlist.includes(folderId);
}
// Validate operation against feature flags
export function isOperationAllowed(
operation:
| 'gmail_send'
| 'gmail_delete'
| 'gmail_modify_labels'
| 'gmail_draft'
| 'drive_write'
| 'drive_delete'
| 'sheets_write'
| 'docs_write'
| 'calendar_read'
| 'calendar_write',
config?: Config
): boolean {
const conf = config ?? getConfig();
const flags = conf.features;
switch (operation) {
case 'gmail_send':
return flags.gmail_send_enabled;
case 'gmail_delete':
return flags.gmail_delete_enabled;
case 'gmail_modify_labels':
return flags.gmail_modify_labels_enabled;
case 'gmail_draft':
// Draft creation uses gmail_modify_labels flag (separate from send)
return flags.gmail_modify_labels_enabled;
case 'drive_write':
return flags.drive_write_enabled;
case 'drive_delete':
return flags.drive_delete_enabled;
case 'sheets_write':
return flags.sheets_write_enabled;
case 'docs_write':
return flags.docs_write_enabled;
case 'calendar_read':
return flags.calendar_read_enabled;
case 'calendar_write':
return flags.calendar_write_enabled;
default:
return false;
}
}
export class OperationNotAllowedError extends Error {
constructor(operation: string) {
super(
`Operation '${operation}' is not allowed. Check your feature flags in the configuration.`
);
this.name = 'OperationNotAllowedError';
}
}
export class FolderNotAllowedError extends Error {
constructor(folderId: string) {
super(
`Folder '${folderId}' is not in the allowlist. Add it to DRIVE_ALLOWLIST_FOLDERS to enable operations.`
);
this.name = 'FolderNotAllowedError';
}
}