auth.js•5.46 kB
import { authenticate } from "@google-cloud/local-auth";
import { google } from "googleapis";
import fs from "fs";
import path from "path";
export const SCOPES = [
    "https://www.googleapis.com/auth/drive.readonly",
    "https://www.googleapis.com/auth/spreadsheets",
];
// Get credentials directory from environment variable or use default
const CREDS_DIR = process.env.GDRIVE_CREDS_DIR ||
    path.join(path.dirname(new URL(import.meta.url).pathname), "../../../");
// Ensure the credentials directory exists
function ensureCredsDirectory() {
    try {
        fs.mkdirSync(CREDS_DIR, { recursive: true });
        console.error(`Ensured credentials directory exists at: ${CREDS_DIR}`);
    }
    catch (error) {
        console.error(`Failed to create credentials directory: ${CREDS_DIR}`, error);
        throw error;
    }
}
const credentialsPath = path.join(CREDS_DIR, ".gdrive-server-credentials.json");
async function authenticateWithTimeout(keyfilePath, SCOPES, timeoutMs = 30000) {
    const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Authentication timed out")), timeoutMs));
    const authPromise = authenticate({
        keyfilePath,
        scopes: SCOPES,
    });
    try {
        return await Promise.race([authPromise, timeoutPromise]);
    }
    catch (error) {
        console.error(error);
        return null;
    }
}
async function authenticateAndSaveCredentials() {
    console.error("Launching auth flow…");
    console.error("Using credentials path:", credentialsPath);
    const keyfilePath = path.join(CREDS_DIR, "gcp-oauth.keys.json");
    console.error("Using keyfile path:", keyfilePath);
    const auth = await authenticateWithTimeout(keyfilePath, SCOPES);
    if (auth) {
        const newAuth = new google.auth.OAuth2();
        newAuth.setCredentials(auth.credentials);
    }
    try {
        const { credentials } = await auth.refreshAccessToken();
        console.error("Received new credentials with scopes:", credentials.scope);
        // Ensure directory exists before saving
        ensureCredsDirectory();
        fs.writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2));
        console.error("Credentials saved successfully with refresh token to:", credentialsPath);
        auth.setCredentials(credentials);
        return auth;
    }
    catch (error) {
        console.error("Error refreshing token during initial auth:", error);
        return auth;
    }
}
// Try to load credentials without prompting for auth
export async function loadCredentialsQuietly() {
    console.error("Attempting to load credentials from:", credentialsPath);
    const oauth2Client = new google.auth.OAuth2(process.env.CLIENT_ID, process.env.CLIENT_SECRET);
    if (!fs.existsSync(credentialsPath)) {
        console.error("No credentials file found");
        return null;
    }
    try {
        const savedCreds = JSON.parse(fs.readFileSync(credentialsPath, "utf-8"));
        console.error("Loaded existing credentials with scopes:", savedCreds.scope);
        oauth2Client.setCredentials(savedCreds);
        const expiryDate = new Date(savedCreds.expiry_date);
        const now = new Date();
        const fiveMinutes = 5 * 60 * 1000;
        const timeToExpiry = expiryDate.getTime() - now.getTime();
        console.error("Token expiry status:", {
            expiryDate: expiryDate.toISOString(),
            timeToExpiryMinutes: Math.floor(timeToExpiry / (60 * 1000)),
            hasRefreshToken: !!savedCreds.refresh_token,
        });
        if (timeToExpiry < fiveMinutes && savedCreds.refresh_token) {
            console.error("Attempting to refresh token using refresh_token");
            try {
                const response = await oauth2Client.refreshAccessToken();
                const newCreds = response.credentials;
                ensureCredsDirectory();
                fs.writeFileSync(credentialsPath, JSON.stringify(newCreds, null, 2));
                oauth2Client.setCredentials(newCreds);
                console.error("Token refreshed and saved successfully");
            }
            catch (error) {
                console.error("Failed to refresh token:", error);
                return null;
            }
        }
        return oauth2Client;
    }
    catch (error) {
        console.error("Error loading credentials:", error);
        return null;
    }
}
// Get valid credentials, prompting for auth if necessary
export async function getValidCredentials(forceAuth = false) {
    if (!forceAuth) {
        const quietAuth = await loadCredentialsQuietly();
        if (quietAuth) {
            return quietAuth;
        }
    }
    return await authenticateAndSaveCredentials();
}
// Background refresh that never prompts for auth
export function setupTokenRefresh() {
    console.error("Setting up automatic token refresh interval (45 minutes)");
    return setInterval(async () => {
        try {
            console.error("Running scheduled token refresh check");
            const auth = await loadCredentialsQuietly();
            if (auth) {
                google.options({ auth });
                console.error("Completed scheduled token refresh");
            }
            else {
                console.error("Skipping token refresh - no valid credentials");
            }
        }
        catch (error) {
            console.error("Error in automatic token refresh:", error);
        }
    }, 45 * 60 * 1000);
}