googleapis / google-api-nodejs-client

Google's officially supported Node.js client library for accessing Google APIs. Support for authorization and authentication with OAuth 2.0, API Keys and JWT (Service Tokens) is included.
https://googleapis.dev/nodejs/googleapis/latest/
Apache License 2.0
11.47k stars 1.92k forks source link

tagmanager receiving 404 when setting "type: 'jsm'" #2544

Open tongdatarunsdeep opened 3 years ago

tongdatarunsdeep commented 3 years ago

I’m using a service account to access tagmanager API but getting 404 error for some functions.

After authorising the service account by using credentials.json file, I could access most of the tagmanager API v2 functions except for variable update for custom javascript(jsm) type variable.

The code returned 404 permission denied error. The same code works for other variable types, eg, DataLayer (v) variable (returns 200 code).

The service account has admin access at the GTM account level, and publish access at the container level. (The container itself was created by the same service account).

I have tried the same configuration via API explorer, it uses OAth2 flow to auth my personal email address(has the same access level at account and container level as the service account) and returned 200. I then copied the Auth token generated by the API explorer page and forced the node module oauth2client.js to use this token on /node_modules/google-auth-library/build/src/auth/oauth2client.js:337. from:

            if (r.headers && r.headers.Authorization) {
                opts.headers.Authorization = r.headers.Authorization;
            }

to:

if (r.headers && r.headers.Authorization) {
                opts.headers.Authorization = <token_copied>
            }

After this, the custom javascript variable can be updated by my code. which means the issue is caused by the auth token generated for the service account.

Error log:

/<project_path>/node_modules/gaxios/build/src/gaxios.js:113
        throw new common_1.GaxiosError(`Request failed with status code ${translatedResponse.status}`, opts, translatedResponse);
           ^
GaxiosError: Not found or permission denied.
  at Gaxios._request (/<project_path>/node_modules/gaxios/build/src/gaxios.js:113:23)
  at processTicksAndRejections (node:internal/process/task_queues:94:5)
  at async JWT.requestAsync (/<project_path>/node_modules/google-auth-library/build/src/auth/oauth2client.js:344:18)
  at async testVar (/<project_path>/test.js:220:22)

Auth function used in code

const auth = new google.auth.GoogleAuth({
        scopes: ['https://www.googleapis.com/auth/tagmanager.publish','https://www.googleapis.com/auth/tagmanager.readonly']
    });
    const authClient = await auth.getClient();
    const tagmanager = google.tagmanager({
        version: 'v2',
        auth: authClient
    });
sofisl commented 3 years ago

Hi @tongdatarunsdeep,

Apologies if I've misunderstood the issue; feel free to clear up if necessary.

I've been able to run this code successfully below. Before running the code, I've set GOOGLE_APPLICATION_CREDENTIALS to my service account (json) file.

 const {google} = require('googleapis');
 const tagmanager = google.tagmanager('v2');

 async function main() {
   const auth = new google.auth.GoogleAuth({
     // Scopes can be specified either as an array or as a single, space-delimited string.
     scopes: ['https://www.googleapis.com/auth/tagmanager.edit.containers'],
   });

   // Acquire an auth client, and bind it to all future calls
   const authClient = await auth.getClient();
   google.options({auth: authClient});

   // Do the magic
  const res = await tagmanager.accounts.containers.workspaces.variables.update({
    // When provided, this fingerprint must match the fingerprint of the variable in storage.
    fingerprint: 'placeholder-value',
   // GTM Variable's API relative path. Example: accounts/{account_id\}/containers/{container_id\}/workspaces/{workspace_id\}/variables/{variable_id\}
    path:
      'accounts/my-account/containers/my-container/workspaces/my-workspace/variables/my-variable',
   // Request body metadata
    requestBody: {

    },
   });
   console.log(res.data);
 }

 main().catch(e => {
   console.error(e);
   throw e;
 });

Please make sure the service account has the correct permissions to make these changes.

tongdatarunsdeep commented 3 years ago

Hi @sofisl,

I could run the code in my local environment to generate an authentication token, the issue is the token then get rejected when using it to access tagmanager.accounts.containers.workspaces.variables.update() or .create() functions.

Note that the update/create function works for other variable types except for jsm variable.

I have no issues create or update folders, triggers and non jsm variables (eg, v or j variables).

Could you please try to run .create() for a jsm variable?

tongdatarunsdeep commented 3 years ago

For example: when I'm using the following code to update a dataLayer variable (type: 'v') and a custom javascript variable (type: 'jsm'). The only change is to update variableID.

let variableInfo = await tagmanager.accounts.containers.workspaces.variables.get({
  path: `accounts/${accountID}/containers/${targetGTM}/workspaces/1/variables/${variableID}`
});
let resource = {
  name: `${variableInfo.data.name}_copy`, 
  type: variableInfo.data.type,
  parameter: variableInfo.data.parameter
}
console.log(resource)
let updateVariable = await tagmanager.accounts.containers.workspaces.variables.update({
  path: `accounts/${accountID}/containers/${targetGTM}/workspaces/1/variables/${variableID}`,
  resource: resource
});
console.log(updateVariable.status)
return updateVariable;
}

I get 200 for the dataLayer variable

{
  name: 'dataLayer_variable_copy',
  type: 'v',
  parameter: [
    { type: 'integer', key: 'dataLayerVersion', value: '2' },
    { type: 'boolean', key: 'setDefaultValue', value: 'false' },
    { type: 'template', key: 'name', value: 'data.state' }
  ]
}
200

But 404 for custom javascript one

user@C02ZWBRAMD6M gtm-test % node test.js
{
  name: 'cjs_variable_copy',
  type: 'jsm',
  parameter: [
    {
      type: 'template',
      key: 'javascript',
      value: 'function() {return}'
    }
  ]
}
/Users/user/drd/gtm-test/node_modules/gaxios/build/src/gaxios.js:113
                throw new common_1.GaxiosError(`Request failed with status code ${translatedResponse.status}`, opts, translatedResponse);
                      ^
GaxiosError: Not found or permission denied.
    at Gaxios._request (/Users/user/drd/gtm-test/node_modules/gaxios/build/src/gaxios.js:113:23)
    at processTicksAndRejections (node:internal/process/task_queues:94:5)
    at async JWT.requestAsync (/Users/user/drd/gtm-test/node_modules/google-auth-library/build/src/auth/oauth2client.js:344:18)
    at async test (/Users/user/drd/gtm-test/test.js:217:22) {
  response: {
    config: {
      url: 'https://tagmanager.googleapis.com/tagmanager/v2/accounts/23565264/containers/41163836/workspaces/1/variables/178',
      method: 'PUT',
      userAgentDirectives: [
        {
          product: 'google-api-nodejs-client',
          version: '4.4.3',
          comment: 'gzip'
        }
      ],
      paramsSerializer: [Function (anonymous)],
      data: {
        name: 'cjs_variable_copy',
        type: 'jsm',
        parameter: [
          {
            type: 'template',
            key: 'javascript',
            value: 'function() {return}'
          }
        ]
      },
      headers: {
        'x-goog-api-client': 'gdcl/4.4.3 gl-node/15.8.0 auth/6.1.6',
        'Accept-Encoding': 'gzip',
        'User-Agent': 'google-api-nodejs-client/4.4.3 (gzip)',
        Authorization: 'Bearer ya29.c.KqMB9gdjqKdaJ6xr5S1m1BLFzi0qPFEtPKcPCKiphIobCRZS0nz7mn8tVMn6SbOEB1hNNZ9UZkF7UVeJnd3Nq-xjASb9030Sn-ubQKdhZvHp2deCB9JEC4K8sf7fmnnr_UBRrhHQcjNAkFucd0GYnTJZIWDugYZw1E3U4qb2WpYmZ-cOx_BPs7JgL6Ku_2QS21dUOAmXYNAJtTqewwWvM4sFNDCLNA',
        'Content-Type': 'application/json',
        Accept: 'application/json'
      },
      params: {},
      validateStatus: [Function (anonymous)],
      retry: true,
      body: '{"name":"cjs_variable_copy","type":"jsm","parameter":[{"type":"template","key":"javascript","value":"function() {return}"}]}',
      responseType: 'json',
      retryConfig: {
        currentRetryAttempt: 0,
        retry: 3,
        httpMethodsToRetry: [ 'GET', 'HEAD', 'PUT', 'OPTIONS', 'DELETE' ],
        noResponseRetries: 2,
        statusCodesToRetry: [ [ 100, 199 ], [ 429, 429 ], [ 500, 599 ] ]
      }
    },
    data: {
      error: {
        code: 404,
        message: 'Not found or permission denied.',
        errors: [
          {
            message: 'Not found or permission denied.',
            domain: 'global',
            reason: 'notFound'
          }
        ],
        status: 'NOT_FOUND'
      }
    },
    headers: {
      'alt-svc': 'h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"',
      'cache-control': 'private',
      connection: 'close',
      'content-encoding': 'gzip',
      'content-type': 'application/json; charset=UTF-8',
      date: 'Wed, 10 Mar 2021 06:08:11 GMT',
      server: 'ESF',
      'transfer-encoding': 'chunked',
      vary: 'Origin, X-Origin, Referer',
      'x-content-type-options': 'nosniff',
      'x-frame-options': 'SAMEORIGIN',
      'x-xss-protection': '0'
    },
    status: 404,
    statusText: 'Not Found',
    request: {
      responseURL: 'https://tagmanager.googleapis.com/tagmanager/v2/accounts/23565264/containers/41163836/workspaces/1/variables/178'
    }
  },
  config: {
    url: 'https://tagmanager.googleapis.com/tagmanager/v2/accounts/23565264/containers/41163836/workspaces/1/variables/178',
    method: 'PUT',
    userAgentDirectives: [
      {
        product: 'google-api-nodejs-client',
        version: '4.4.3',
        comment: 'gzip'
      }
    ],
    paramsSerializer: [Function (anonymous)],
    data: {
      name: 'cjs_variable_copy',
      type: 'jsm',
      parameter: [
        {
          type: 'template',
          key: 'javascript',
          value: 'function() {return}'
        }
      ]
    },
    headers: {
      'x-goog-api-client': 'gdcl/4.4.3 gl-node/15.8.0 auth/6.1.6',
      'Accept-Encoding': 'gzip',
      'User-Agent': 'google-api-nodejs-client/4.4.3 (gzip)',
      Authorization: 'Bearer ya29.c.KqMB9gdjqKdaJ6xr5S1m1BLFzi0qPFEtPKcPCKiphIobCRZS0nz7mn8tVMn6SbOEB1hNNZ9UZkF7UVeJnd3Nq-xjASb9030Sn-ubQKdhZvHp2deCB9JEC4K8sf7fmnnr_UBRrhHQcjNAkFucd0GYnTJZIWDugYZw1E3U4qb2WpYmZ-cOx_BPs7JgL6Ku_2QS21dUOAmXYNAJtTqewwWvM4sFNDCLNA',
      'Content-Type': 'application/json',
      Accept: 'application/json'
    },
    params: {},
    validateStatus: [Function (anonymous)],
    retry: true,
    body: '{"name":"cjs_variable_copy","type":"jsm","parameter":[{"type":"template","key":"javascript","value":"function() {return}"}]}',
    responseType: 'json',
    retryConfig: {
      currentRetryAttempt: 0,
      retry: 3,
      httpMethodsToRetry: [ 'GET', 'HEAD', 'PUT', 'OPTIONS', 'DELETE' ],
      noResponseRetries: 2,
      statusCodesToRetry: [ [ 100, 199 ], [ 429, 429 ], [ 500, 599 ] ]
    }
  },
  code: 404,
  errors: [
    {
      message: 'Not found or permission denied.',
      domain: 'global',
      reason: 'notFound'
    }
  ]
}
sofisl commented 3 years ago

HI @tongdatarunsdeep, do you have this scope set? https://www.googleapis.com/auth/tagmanager.edit.containers

If you're using the oAuth flow, you may need to revoke and regrant access to your app for those scopes: https://myaccount.google.com/permissions

tongdatarunsdeep commented 3 years ago

Hi @sofisl,

Yes, I have the scope https://www.googleapis.com/auth/tagmanager.edit.containers. I have also tried to add other different scopes but was getting the same error. If the issue is caused by the scope, it shouldn't work for other types of variables?

Currently I'm using:

  const auth = new google.auth.GoogleAuth({
      scopes: ['https://www.googleapis.com/auth/tagmanager.edit.containers','https://www.googleapis.com/auth/tagmanager.readonly','https://www.googleapis.com/auth/tagmanager.publish']
  });
  const authClient = await auth.getClient();
  const tagmanager = google.tagmanager({
      version: 'v2',
      auth: authClient
  });

I'm not using OAuth flow but a service account auth (the function will run in google cloud function, so I can't use OAuth). I can send you the credentials.json file if needed.

evanrosa commented 3 years ago

I'm having a similar issue with the GTM API using a service account where I can't authenticate at all. I'm currently getting 401 errors (GaxiosError: Login Required). Code below.

const {google} = require('googleapis');

const scopes = [
    'https://www.googleapis.com/auth/tagmanager.readonly',
    'https://www.googleapis.com/auth/tagmanager.manage.accounts',
    'https://www.googleapis.com/auth/tagmanager.edit.containers',
    'https://www.googleapis.com/auth/tagmanager.delete.containers',
    'https://www.googleapis.com/auth/tagmanager.edit.containerversions',
    'https://www.googleapis.com/auth/tagmanager.manage.users',
    'https://www.googleapis.com/auth/tagmanager.publish'
];

export async function gtmAuthenticate() {
  try {
    const auth = new google.auth.GoogleAuth({
      scopes: scopes,
    });

    const authClient = await auth.getClient();

    google.tagmanager({
      version: 'v2',
      auth: authClient
    });

  } catch (err) {
      console.log(err);
  }
}
tongdatarunsdeep commented 3 years ago

I'm having a similar issue with the GTM API using a service account where I can't authenticate at all. I'm currently getting 401 errors (GaxiosError: Login Required). Code below.

const {google} = require('googleapis');

const scopes = [
    'https://www.googleapis.com/auth/tagmanager.readonly',
    'https://www.googleapis.com/auth/tagmanager.manage.accounts',
    'https://www.googleapis.com/auth/tagmanager.edit.containers',
    'https://www.googleapis.com/auth/tagmanager.delete.containers',
    'https://www.googleapis.com/auth/tagmanager.edit.containerversions',
    'https://www.googleapis.com/auth/tagmanager.manage.users',
    'https://www.googleapis.com/auth/tagmanager.publish'
];

export async function gtmAuthenticate() {
  try {
    const auth = new google.auth.GoogleAuth({
      scopes: scopes,
    });

    const authClient = await auth.getClient();

    google.tagmanager({
      version: 'v2',
      auth: authClient
    });

  } catch (err) {
      console.log(err);
  }
}

Hi @evan-rosa, I had that issue at the beginning as well. Have you tried to run

gcloud auth application-default login

in the console?

evanrosa commented 3 years ago

@tongdatarunsdeep - Yes, I authenticated with gcloud but still the same error when running the script.

evanrosa commented 3 years ago

@tongdatarunsdeep - Just following up, any specific reason I'm still getting a 401 error? Any suggestions?

evanrosa commented 3 years ago

Following up on my issue with 401 errors, I was able to resolve the issue by implementing my auth on each GTM component rather than trying to authenticate globally for all of my code.

JustinBeckwith commented 3 years ago

@tongdatarunsdeep I would be surprised if this was specifically an issue with the npm module itself, rather something missing on the service account or permission set. Honestly, we're not experts specifically on tagmanager, and I suspect you'd have better luck on Stack Overflow for this part: https://stackoverflow.com/questions/tagged/google-tag-manager

If you have a support contract with Google that can get you in front of support folks who can specifically look at your service account, that's also a great approach.