Random "User does not have sufficient permissions for this profile." error from Google Analytics API v3 - google-analytics-api

I am receiving this error randomly when I am trying to send a request to Google Analytics API v3:
"User does not have sufficient permissions for this profile."
From every 8-10 times that I try a same request (same parameters, authentication, etc.), I receive this error only once and the other times I receive the correct response in the other times. The other strange part is that we are handling many clients and I only have seen this error for only handful of our clients.
For more background, we are using googleapis NPM package to send our Google Analytics API requests.
This is parameters that I am sending to the API:
{
params: {
auth: OAuth2Client {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
transporter: DefaultTransporter {},
credentials: [Object],
eagerRefreshThresholdMillis: 300000,
forceRefreshOnFailure: false,
certificateCache: {},
certificateExpiry: null,
certificateCacheFormat: 'PEM',
refreshTokenPromises: Map {},
_clientId: 'XXXXX,
_clientSecret: 'XXXX',
redirectUri: 'postmessage'
},
ids: 'ga:XXXX',
metrics: 'ga:sessions,ga:bounces,ga:transactions,ga:transactionRevenue,ga:goalCompletionsAll',
dimensions: 'ga:date',
'start-date': '2021-10-01',
'end-date': '2021-10-20',
samplingLevel: 'HIGHER_PRECISION',
quotaUser: 'XXX'
}
}
new Promise((resolve, reject) => {
return google
.analytics({ version: "v3"})
.data.ga.get(params, (error, { data: response } = {}) => {
if (error) {
return reject(new Error(`Google API sent the following error: ${error}`));
}
return resolve(response);
});
})
Authentication:
const OAuth2 = google.auth.OAuth2;
const oauth2Client = new OAuth2(process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, "postmessage");
oauth2Client.setCredentials(tokens);
await oauth2Client.getRequestHeaders().catch((error) => {
throw error;
});
And then passing oauth2Client in the params as auth.

I resolved the issue. In my case I was using the same object of oauth2Client for multiple API requests but was calling these lines before each request:
oauth2Client.setCredentials(tokens);
await oauth2Client.getRequestHeaders();
This could potentially change the token that I was passing in the request parameters, params, before it was being sent.
So in other words if you are sending multiple requests to the API at the same time, it is better to generate the token once and use the same token for all those requests.

Related

How to use the Zoho Desk Invoke API (Proxy) to call the Zoho Desk Push-Data to Desk (aka Import) endpoint?

I am trying to use the Zoho Desk Invoke API (Proxy) to call the Zoho Desk Push-Data to Desk (aka Import) endpoint.
This is what my code looks like:
// Build the payload
const invokeApiRequestPayload = {
"securityContext":"edbsnc50b85a3cd964126073f50499ae29a3d6ed3c31123e535e901cdda1b2a312dc0a66c638e2beb2724fffc355faebabf1acd65c3883227c2d329d0c9f62cbbdf26ba4553375b5b11cab90c57590c6b48a3",
"requestURL":"https://desk.zoho.com.au/api/v1/channels/{{installationId}}/import",
"headers":{
"Content-Type":"application/json"
},
"postBody":{
"data":{
"tickets":[
{
"actor":{
"email":"test#gmail.com",
"name":"Tom Billy",
"extId":"NjPk2E6J83g41uDKsD6DznLzz323"
},
"subject":"testing new ticket",
"createdTime":"2022-07-24T01:13:44.419Z",
"status":"Open",
"extId":"YCdHTnu93pQbyNoyZzId"
}
],
"threads":[
{
"contentType":"text/html",
"createdTime":"2022-07-24T01:13:44.419Z",
"extId":"YGrfU6quGoDESjX5HEZM",
"extParentId":"YCdHTnu93pQbyNoyZzId",
"actor":{
"extId":"NjPk2E6J83g41uDKsD6DznLzz323",
"name":"Tom Billy",
"email":"test#gmail.com"
},
"canReply":true,
"content":"testing one two three<br>"
}
]
}
},
"connectionLinkName":"zohodesk",
"requestType":"POST",
"queryParams":{
"orgId":"7002257443"
}
};
// Generate the Hmac
const stringToHash = 'requestURL=https://desk.zoho.com.au/api/v1/channels/{{installationId}}/import&requestType=POST&queryParams={"orgId":"7002257443"}&postBody={"data":{"tickets":[{"extId":"YCdHTnu93pQbyNoyZzId","status":"Open","subject":"testing new ticket","createdTime":"2022-07-24T01:13:44.419Z","actor":{"extId":"NjPk2E6J83g41uDKsD6DznLzz323","name":"Tom Billy","email":"test#gmail.com"}}],"threads":[{"extId":"YGrfU6quGoDESjX5HEZM","extParentId":"YCdHTnu93pQbyNoyZzId","actor":{"extId":"NjPk2E6J83g41uDKsD6DznLzz323","name":"Tom Billy","email":"test#gmail.com"},"content":"testing one two three<br>","createdTime":"2022-07-24T01:13:44.419Z","canReply":true,"contentType":"text/html"}]}}&headers={"Content-Type":"application/json"}&connectionLinkName=zohodesk';
const hmac = crypto
.createHmac('sha256', 'mysecret123')
.update(stringToHash)
.digest('hex');
// The hmac created from the above code is '2aa21d41882223e2e23ad7004cfdc5a0317db5192fdff84431f0515d4f4e004b'
// Make the Invoke API request using Axios
const axiosOptions = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
hash: hmac,
},
params: {
orgId: '7002257443',
},
};
return await axios
.post(
'https://desk.zoho.com.au/api/v1/invoke',
invokeApiRequestPayload,
axiosOptions
)
.catch((error) => {
functions.logger.error(error);
throw new functions.https.HttpsError('unknown', error.message, error);
});
But I always get back Error 422 UNPROCESSABLE_ENTITY:
{
"errorCode":"UNPROCESSABLE_ENTITY",
"message":"`Extra query parameter '{\"securityContext\":\"edbsnc50b85a3cd964126073f50499ae29a3d6ed3c31123e535e901cdda1b2a312dc0a66c638e2beb2724fffc355faebabf1acd65c3883227c2d329d0c9f62cbbdf26ba4553375b5b11cab90c57590c6b48a3\",\"requestURL\":\"https://desk.zoho.com.au/api/v1/channels/{{installationId}}/import\",\"requestType\":\"POST\",\"postBody\":{\"data\":{\"tickets\":[{\"extId\":\"YCdHTnu93pQbyNoyZzId\",\"status\":\"Open\",\"subject\":\"testing new ticket\",\"createdTime\":\"2022-07-24T01:13:44.419Z\",\"actor\":{\"extId\":\"NjPk2E6J83g41uDKsD6DznLzz323\",\"name\":\"Tom Billy\",\"email\":\"test#gmail.com\"}}],\"threads\":[{\"extId\":\"YGrfU6quGoDESjX5HEZM\",\"extParentId\":\"YCdHTnu93pQbyNoyZzId\",\"actor\":{\"extId\":\"NjPk2E6J83g41uDKsD6DznLzz323\",\"name\":\"Tom Billy\",\"email\":\"test#gmail.com\"},\"content\":\"testing one two three<br>\",\"createdTime\":\"2022-07-24T01:13:44.419Z\",\"canReply\":true,\"contentType\":\"text/html\"}]}},\"headers\":{\"Content-Type\":\"application/json\"},\"queryParams\":{\"orgId\":\"7002257443\"},\"connectionLinkName\":\"zohodesk\"}' is present in the input.`"
}
The Zoho Desk docs have this explanation for the UNPROCESSABLE_ENTITY error code:
This errorCode value appears if the input does not fulfil the
conditions necessary for successfully executing the API.
And looking at the returned error message details it seems to be complaining that I have an "Extra query parameter". That does not make sense to me, because I included it as the payload required by the Zoho Desk Invoke API (Proxy).
Can anyone see what I am doing wrong?

Google Books OAuth returns 401 even though I'm passing an access_token

I'm trying to add a book to a bookshelf using the google books API by sending an axios POST request in my express server. I need to send an access token to authorize the POST request according to the docs, and I'm getting that access token from the token client model from Google Identity Services. I have the token and I have my API key, but I can't get google to authorize the request.
Here's the call from my front end:
axios.get(
'http://localhost:5000/to-read',
{
params: {
bookId: bookId,
shelfId: shelfId,
token: token
}
})
and here's the back end:
.get((req, res) => {
const headers = {
'Authorization': req.query.token,
'Content-Type': 'application/json',
}
axios.post(
`https://www.googleapis.com/books/v1/mylibrary/bookshelves/${req.query.shelfId}/addVolume?volumeId=${req.query.bookId}&key=${process.env.REACT_APP_API_KEY}`,
{},
{headers: headers}
).then((response) => {
console.log(response);
}).catch((error) => {
console.log(error.response.data)
})
When I send the request with the API key, I get this error:
error: {
code: 401,
message: 'API keys are not supported by this API. Expected OAuth2 access token or other authentication credentials that assert a principal. See https://cloud.google.com/docs/authentication',
errors: [ [Object] ],
status: 'UNAUTHENTICATED',
details: [ [Object] ]
}
and when I remove the API key (it's optional for this call according to the docs) I get this error:
error: {
code: 401,
message: 'Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.',
errors: [ [Object] ],
status: 'UNAUTHENTICATED',
details: [ [Object] ]
}
and https://developers.google.com/identity/sign-in/web/devconsole-project leads to a 404

How could i pass cookies in Axios

I am in a next-js app and my auth token is stored in cookies.
For some raisons i use Swr and Api route to fetch my secured api backend.
i am trying to find a way to put my auth token in all api request.
During login cookie is set
res.setHeader(
'Set-Cookie',
cookie.serialize('token', data.access_token, {
httpOnly: true,
secure: process.env.NODE_ENV !== 'development',
maxAge: data.expires_in, // 1 week
sameSite: 'strict',
path: '/',
}),
);
This is an example of a page using swr fetch
//page/test.ts - example of my test route
const { data, error } = useFetchContent(id);
if (error) {
showError('error');
replace('/');
}
return <DisplayContent content={data} />
This is a swrFetchHook
// fetchContentHook
function useFetchContent(id: string): ContentDetail {
return useSWR<any>(`/api/content/${id}`, fetcherApiRoute);
}
const fetcherApiRoute = (url: string): Promise<any> => {
return axios(url)
.then((r) => r.data)
.catch((err) => {
console.info('error is ', err)
throw err
});
};
export default useFetchContent;
inside api route
export default async (req, res): Promise<ContentDetail> => {
const { id } = req.query;
if (req.method === 'GET') {
const fetchRealApi = await apiAxios(url);
if(fetchRealApi) {
// here depending on result of fetchRealApi i add some other fetch ...
return res.status(200).json({ ...fetchRealApi, complement: comp1 });
}
return res.status(500)
}
return res.status(500).json({ message: 'Unsupported method only GET is allowed' });
};
and finally api axios configuration
const apiAxios = axios.create({
baseURL: '/myBase',
});
apiAxios.interceptors.request.use(
async (req) => {
// HERE i am trying to get token from cookies
// and also HERE if token is expired i am trying to refresh token
config.headers.Authorization = token;
req.headers['Content-type'] = 'application/x-www-form-urlencoded';
return req;
},
(error) => {
return Promise.reject(error);
},
);
export default apiAxios;
I am stuck here because i cant find token during apiAxios.interceptors.request.use...
Did you know what i am doing wrong, and am i on a correct way to handle this behavior ?
To allow sending server cookie to every subsequent request, you need to set withCredentials to true. here is the code.
const apiAxios = axios.create({
baseURL: '/myBase',
withCredentials: true,
});
Nilesh's answer is right if your API is able to authorize requests based on cookies. Also it needs the API to be in the same domain as your frontend app. If you need to send tokens to the API (the one which is in the cookie), then you will need a small backend component often called BFF or Token Handler. It can extract the token from the cookie and put in an Authorization header.
At Curity we've created a sample implementation of such a Token Handler, of which you can inspire: https://github.com/curityio/kong-bff-plugin/ You can also have a look at an overview article of the Token Handler pattern.

Error trying to get authenticated user email with googleapis and node.js

I'm implementing auth on my website using googleapis. The function plus.people.get doesn't work. I have seen it is deprecated on some forums but it's still documented at google which has me confused. The error I get is "Legacy People API has not been used in project 328985958128 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/legacypeople.googleapis.com then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry." The webpage doesn't even load. My code is
if (!req.body.token) return res.status(500).json({ type: 'error', message: 'No access token provided.' })
const OAuth2 = google.auth.OAuth2
const oauth2Client = new google.auth.OAuth2(keys.client_id, keys.client_secret)
google.options({ auth: oauth2Client });
const plus = google.plus('v1')
oauth2Client.setCredentials({
access_token: req.body.token
})
plus.people.get({
userId: 'me',
auth: oauth2Client
}, (error, response) => {
if (error)
console.log(error)
return res.status(500).json({ type: 'error',error })
const emails = (response.data || {}).emails
You are using google.plus('v1'), which has been deprecated
Instead you should use
const service = google.people({version: 'v1', auth: oauth2Client})
to create a service object.
To perform a request an additional auhtorization is not required anymore, so:
service.people.get({
userId: 'me'
}, (error, response) => {
...
})
Further information:
Creating a service account client with node.js
People API quide for node.js

Automatic request signing with API Gateway REST API and Amplify

This https://aws-amplify.github.io/docs/js/api#signing-request-with-iam says AWS Amplify provides the ability to sign requests automatically ..is this the same with API gateway REST requests that are restricted by Cognito?
auth.currentSession().then(token => {
console.log('>>>>', token.getIdToken().getJwtToken());
authToken = token.getIdToken().getJwtToken();
const myInit = { // OPTIONAL
headers: {
Authorization: authToken
},
response: true,
};
api.get(apiName, path, myInit).then(response => {
// Add your code here
console.log(response);
}).catch(error => {
console.log(error.response);
});
}
);
but I get Authorization header requires 'Credential' parameter. Authorization header requires 'Signature'
But in angular this does not work as Auth.currentSession() does not compile
endpoints: [
{
name: 'test',
endpoint: 'https://xyz.execute-api.us-west-2.amazonaws.com/test',
custom_header: async () => {
// Alternatively, with Cognito User Pools use this:
return {Authorization: (await Auth.currentSession()).idToken.jwtToken};
}
}
]
}
Resolved had typo with the request url it had to be /items/:test where test was the partition name in dynamo, also the
headers: {
Authorization: token
},
is not required:
https://github.com/aws-amplify/amplify-js/issues/2810#issuecomment-470213459