Google Oauth 401 error

import * as coda from "@codahq/packs-sdk";

export const pack = coda.newPack();

// Add necessary network domain
pack.addNetworkDomain("googleapis.com");

// Set up OAuth2 authentication for both private and shared accounts
pack.setUserAuthentication({
  type: coda.AuthenticationType.OAuth2,
  authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",
  tokenUrl: "https://oauth2.googleapis.com/token",
  scopes: [
    "https://www.googleapis.com/auth/drive.file", // Access to Google Drive files (for both private and shared accounts)
  ],
  additionalParams: {
    access_type: "offline", // Allows refresh tokens for long-term access
    prompt: "consent", // Ensures the user can select between private and shared accounts
  },
});

// Function to extract folder ID from URL
function extractFolderIdFromUrl(folderUrl?: string): string | undefined {
  if (!folderUrl) return undefined;
  const parts = folderUrl.split("/folders/");
  if (parts.length > 1) {
    return parts[1].split("?")[0]; // Remove any query parameters
  }
  return undefined;
}

// Function to handle API requests with token validation first
async function fetchWithTokenValidation(context: coda.ExecutionContext, fetchOptions: any) {
  try {
    let response = await context.fetcher.fetch(fetchOptions);
    return response;
  } catch (error) {
    if (error.statusCode === 401) {
      // Token expired, re-throw the error to trigger a token refresh by Coda
      throw error;
    }
    // Handle other errors as normal
    throw new Error(`API request failed. Status code: ${error.statusCode}, Message: ${error.message}`);
  }
}

// Function to create a folder in Google Drive
async function createFolder(context: coda.ExecutionContext, folderName: string, parentFolderURL?: string): Promise<string> {
  try {
    // Extract folder ID from URL
    const parentFolderId = extractFolderIdFromUrl(parentFolderURL);

    const body = {
      name: folderName,
      mimeType: "application/vnd.google-apps.folder",
      parents: parentFolderId ? [parentFolderId] : [],
    };

    const fetchOptions = {
      method: "POST",
      url: "https://www.googleapis.com/drive/v3/files?fields=id",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    };

    const response = await fetchWithTokenValidation(context, fetchOptions);

    if (response.status !== 200 && response.status !== 201) {
      throw new Error(`Failed to create folder. Status code: ${response.status}`);
    }

    const newFolderId = response.body.id;
    return `https://drive.google.com/drive/folders/${newFolderId}`;
  } catch (error) {
    throw new Error(`Error creating folder: ${error.message}`);
  }
}

// CreateFolderAndGetURL action that returns the folder URL
pack.addFormula({
  name: "CreateFolderAndGetURL",
  description: "Creates a folder in Google Drive and returns the folder URL.",
  parameters: [
    coda.makeParameter({
      type: coda.ParameterType.String,
      name: "folderName",
      description: "The name of the folder to create.",
    }),
    coda.makeParameter({
      type: coda.ParameterType.String,
      name: "parentFolderURL",
      description: "The URL of the parent folder.",
      optional: true,
    }),
  ],
  
   resultType:coda.ValueType.String,

   isAction:true,

   execute :async function ([folderName,parentFolderURL],context){

     try {

       const folderUrl=await createFolder(context, folderName, parentFolderURL);
       return folderUrl;

     }catch(error){

         throw new Error(`Error in CreateFolderAndGetURL formula execution. ${error.message}`);

     }

   },

});

// CreateFolder action that returns success/failure message after creation
pack.addFormula({
   name:'CreateFolder',
   
   description:'Creates a folder in Google Drive and returns a success or failure message.',
   
   parameters:[
     coda.makeParameter({
       type:coda.ParameterType.String,
       name:'folderName',
       description:'The name of the folder to create.',
     }),
     coda.makeParameter({
       type:coda.ParameterType.String,
       name:'parentFolderURL',
       description:'The URL of the parent folder.',
       optional:true,
     }),
   ],

   resultType:coda.ValueType.String,

   isAction:true,

   execute :async function ([folderName,parentFolderURL],context){

     try {

       await createFolder(context, folderName, parentFolderURL);
       return `폴더 생성 성공!`;

     }catch(error){

         return `폴더 생성 실패! 오류 메시지:${error.message}`;

     }

   },

});

Hello! :slight_smile:

I’m in the middle of making google drive folder pack.
I think I did eveyrthing right to satisfy Oauth 2.0 requirements, especially for token expiry, but everytime I use the buttons in different computers or time passes, this pack keeps giving me 401 error. What should I do? Thank you!! :slight_smile:

Error in CreateFolderAndGetURL formula execution. 401 - {“error”:{“code”:401,“message”:“Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",“errors”:[{“message”:"Invalid Credentials”,“domain”:“global”,“reason”:“authError”,“location”:“Authorization”,“locationType”:“header”}],“status”:“UNAUTHENTICATED”}}

it keeps giving me this error.
:frowning:

Your code looks good, and the normal causes don’t appear there. Namely you have the additional params needed to get a refresh_token:

additionalParams: {
  access_type: "offline", // Allows refresh tokens for long-term access
  prompt: "consent", // Ensures the user can select between private and shared accounts
},

And your are ensuring that 401 errors are being thrown to trigger token refresh:

if (error.statusCode === 401) {
  // Token expired, re-throw the error to trigger a token refresh by Coda
  throw error;
}

Can you check that you are running the latest version of your Pack code in the doc? Can you also confirm that you see the account selection screen during the OAuth flow?