Getting 400 Bad Request when calling users.watch Gmail API from GCP Cloud Function
I'm trying to automate the call to watch() on a users GSuite email account. I have already given domain-wide access to the default service account of the Cloud Function by following the steps outlined in the link below
https://developers.google.com/admin-sdk/reports/v1/guids/delegation
I have authorized the following scopes:
profile,email, https://mail.google.com/,https://www.googleapis.com/auth/gmail.metadata, https://www.googleapis.com/auth/gmail.modify, https://www.googleapis.com/auth/gmail.readonly
Deployed Cloud Function code:
exports.watchEmail = async () => {
console.info('authenticating');
const auth = await google.auth.getClient({
scopes: ['profile',
'email',
'https://mail.google.com/',
'https://www.googleapis.com/auth/gmail.metadata',
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/gmail.readonly'],
});
console.info('<-- authenticated');
console.info('watch on email', 'no-reply#telico.ca');
const api = google.gmail({ version: 'v1', auth });
const response = await api.users.watch({
userId: '<USER_EMAIL>',
requestBody: {
topicName: 'projects/<PROJECT_ID>/topics/processEmail',
labelIds: ["INBOX"]
}
});
console.info('<-- watch on file', JSON.stringify(response));
};
When executing the CloudFunction, I am seeing Error: Bad Request at Gaxios.request printed in the logs.
Related
My project is react native app that uses aws amplify and a rest api through api gateway. i am trying to add an item into my dynamoDB table by calling an API request like so :
const createItemDB = async () => {
API.post("nutritionAPI", "/items", {
body: {
userID: "authenticated user 1 ",
dateID: "1-28-2023",
},
headers: {
Authorization: `Bearer ${(await Auth.currentSession())
.getIdToken()
.getJwtToken()}`,
},
})
};
But this gets the error "request failed with 403 at node_modules\axios\lib\helpers\cookies.js:null in write" Prior to calling createItemDB, i authenticated myself as an "end-user" by Auth.signIn and each time i restart the app, I am still authenticated through my function below
const loadApp = async () =>{
await Auth.currentAuthenticatedUser()
.then(user=>{
console.log("successfully authd" + user)
})
.catch(()=>{
console.log("error signing in")
})
}
So I know i am authenticated. Im new to aws services, and im unsure how to configure the api gateway correctly. the only way to add an item to dynamodb is if I remove the header in my createItemDB call which means any unauthenticated user can add to my dynamodb? I tried to enable CORS inside api gateway but it still doesnt work
I am struggle with AWS amplify to get the users list of a specific cognito group.
I always get this issue : localhost/:1 Access to XMLHttpRequest at 'https://ixa37ulou3.execute-api.eu-central-1.amazonaws.com/dev/users?groupName=xxx' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
I tried a lot of things, see my last try below :
First Step: creatation of my Lambda function using "amplify add function":
const AWS = require('aws-sdk');
const cognito = new AWS.CognitoIdentityServiceProvider({region:"eu-central-1"});
exports.handler = async (event) => {
const params = {
GroupName: event.groupName,
UserPoolId: 'eu-central-1_xqIZx0wkT',
};
const cognitoResponse = await cognito.listUsersInGroup(params).promise()
const response = {
statusCode:200,
headers:{
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Headers":"Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Credentials": true
},
body: JSON.stringify(cognitoResponse),
};
return response;
};
Second Step: Creation of my REST Api using "amplify add api" (pathname: /users)
Third Step: create in API Gateway a new authorizer "using cognito type and link to my user pool, and for the token source : Authorization. And for /users - ANY - Method Request => i added my authorizer in Authorization field.
Fourth Step: creation of my Calling API Function:
async function callApi(){
const user = await Auth.currentAuthenticatedUser()
const token = user.signInUserSession.idToken.jwtToken
console.log({token})
const requestInfo = {
headers:{
Authorization: token
},
queryStringParameters:{
groupName:"MyuserGroup"
}
}
const data = await API.get('apilistusers','/users',requestInfo)
console.log({data})
}
I would very much appreciate any help on this topic, thank you very much.
the current version of amplify has an option to create a lambda function for administrating cognito users, see "amplify add auth" and this page https://docs.amplify.aws/cli/auth/admin/
I have this code:
import {v2beta3} from "#google-cloud/tasks";
const project = 'xxxxxxx'
const location = 'yyyyyyy'
const queue = 'zzzzzzzzz'
const client = new v2beta3.CloudTasksClient()
const parent = client.queuePath(project, location, queue)
const payload = {eventId: "fred"}
const convertedPayload = JSON.stringify(payload)
const body = Buffer.from(convertedPayload).toString('base64');
const task = {
httpRequest: {
httpMethod: "POST",
url: "https://webhook.site/9sssssssssss",
oidcToken: {
serviceAccountEmail: "aaaaaaaaaa#appspot.gserviceaccount.com",
},
headers: {
'Content-Type': 'application/json',
},
body,
},
};
(async function() {
try {
const [response] = await client.createTask({parent, task});
console.log(`Created task ${response.name}`);
} catch (error) {
console.log(error)
}
}());
When I run it from my laptop, it just works, which seems unauthenticated to me. Anyone can now enqueue a task on my queue.
What is the right way to authenticate to the GCP Cloud Tasks enqueue API?
As John Hanley pointed out in the comments, my local app was using Application Default Credentials to authenticate itself. When I switched to a different gcloud account by doing this:
gcloud auth application-default login
I get this error message when I try to run the code:
Error: 7 PERMISSION_DENIED: The principal (user or service account) lacks IAM permission "cloudtasks.tasks.create" for the resource "projects/yyyyyyy/locations/europe-west1/queues/default-xxxxxx" (or the resource may not exist).
I am using the AWS Amplify library with MobileHub.
I have a Cognito User Pool connected, and an API Gateway (which communicates with Lambda functions). I'd like my users to sign before accessing resources, so I've enabled "mandatory sign-in" in MobileHub User Sign-In page, and the Cloud Logic page.
Authentication works fine, but when I send a GET request to my API, I receive this error:
"[WARN] 46:22.756 API - ensure credentials error": "cannot get guest credentials when mandatory signin enabled"
I understand that Amplify generates guest credentials, and has put these in my GET request. Since I've enabled "mandatory signin", this doesn't work.
But why is it use guest credentials? I've signed in -- shouldn't it use those credentials? How do I use the authenticated user's information?
Cheers.
EDIT: Here is the code from the Lambda function:
lambda function:
import { success, failure } from '../lib/response';
import * as dynamoDb from '../lib/dynamodb';
export const main = async (event, context, callback) => {
const params = {
TableName: 'chatrooms',
Key: {
user_id: 'user-abc', //event.pathParameters.user_id,
chatroom_id: 'chatroom-abc',
}
};
try {
const result = await dynamoDb.call('get', params);
if (result.Item) {
return callback(null, success(result.Item, 'Item found'));
} else {
return callback(null, failure({ status: false }, 'Item not found.'));
}
} catch (err) {
console.log(err);
return callback(null, failure({ status: false }), err);
}
}
And these small helper functions:
response.js:
export const success = (body, message) => buildResponse(200, body, message)
export const failure = (body, message) => buildResponse(500, body, message)
const buildResponse = (statusCode, body, message=null) => ({
statusCode: statusCode,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true
},
body: JSON.stringify({
...body,
message: message
})
});
dynamodb.js:
import AWS from 'aws-sdk';
AWS.config.update({ region: 'ap-southeast-2' });
export const call = (action, params) => {
const dynamoDb = new AWS.DynamoDB.DocumentClient();
return dynamoDb[action](params).promise();
}
I'm following the guide "serverless-stack" and was prompt with the same warning message, I was logging in correctly and logging out correctly and did not understand why the warning message.
In my case, in the Amplify.configure I skip to add the identity pool id, and that was the problem, User pools and federated identities are not the same.
(English is not my native language)
Have you tried checking why your SignIn request is being rejected/error prone?
Auth.signIn(username, password)
.then(user => console.log(user))
.catch(err => console.log(err));
// If MFA is enabled, confirm user signing
// `user` : Return object from Auth.signIn()
// `code` : Confirmation code
// `mfaType` : MFA Type e.g. SMS, TOTP.
Auth.confirmSignIn(user, code, mfaType)
.then(data => console.log(data))
.catch(err => console.log(err));
You can try this, then it would be easier for you to debug.
From the suggestions on the aws-amplify issues tracker, add an anonymous user to your cognito user pool and hard code the password in your app. Seems like there are other options but this is the simplest in my opinion.
You have to use your credentials at each request to use AWS Services :
(sample code angular)
SignIn :
import Amplify from 'aws-amplify';
import Auth from '#aws-amplify/auth';
Amplify.configure({
Auth: {
region: ****,
userPoolId: *****,
userPoolWebClientId: ******,
}
});
//sign in
Auth.signIn(email, password)
Request
import Auth from '#aws-amplify/auth';
from(Auth.currentCredentials())
.pipe(
map(credentials => {
const documentClient = new AWS.DynamoDB.DocumentClient({
apiVersion: '2012-08-10',
region: *****,
credentials: Auth.essentialCredentials(credentials)
});
return documentClient.query(params).promise()
}),
flatMap(data => {
return data
})
)
I'm working on an AWS API Gateway implementation with a Lambda backend. I use the API Gateway integration with the Cognito Userpools (fairly new) instead of building a custom authorizer using Lambda (which was the recommended way before it was integrated).
I've created a proof of concept (javascript) that authenticates a user with Cognito and then makes a call to the API Gateway with those credentials. So, basically, the end call to the API Gateway is with the JWT token that I received from Cognito (result.idToken.jwtToken) in the Authorization header. This all works and I can validate that only with this token you can access the API.
All working fine, but now I want to get access to the Cognito identity in my Lambda; for instance the identy id or the name or email. I have read how to map all the parameters, but I'm actually just using the standard 'Method Request Passthrough' template in the integration request. I log all the parameters in the lambda and all the 'cognito' parameters are empty.
I've looked through many similar questions and they all propose to enable the 'Invoke with caller credentials' checkbox on the integration request. That makes perfect sense.
However, this checkbox can only be enabled if you are using AWS_IAM as authorization and not if you have selected your cognito UserPool. So it is just not possible to select it and is actually disabled.
Does anybody know what to do in this case? Is this still work in progress, or is there a reason why you can't enable this and get the cognito credentials in your Lambda?
Many thanks.
If you need to log the user information in your backend, you can use $context.authorizer.claims.sub and $context.authorizer.claims.email to get the sub and email for your Cognito user pool.
Here is the documentation about Use Amazon Cognito Your User Pool in API Gateway
For anyone else still struggling to obtain the IdentityId in a Lambda Function invoked via API-Gateway with a Cognito User Pool Authorizer, I finally was able to use the jwtToken passed into the Authorization header to get the IdentityId by using the following code in my JavaScript Lambda Function:
const IDENTITY_POOL_ID = "us-west-2:7y812k8a-1w26-8dk4-84iw-2kdi849sku72"
const USER_POOL_ID = "cognito-idp.us-west-2.amazonaws.com/us-west-2_an976DxVk"
const { CognitoIdentityClient } = require("#aws-sdk/client-cognito-identity");
const { fromCognitoIdentityPool } = require("#aws-sdk/credential-provider-cognito-identity");
exports.handler = async (event,context) => {
const cognitoidentity = new CognitoIdentityClient({
credentials: fromCognitoIdentityPool({
client: new CognitoIdentityClient(),
identityPoolId: IDENTITY_POOL_ID,
logins: {
[USER_POOL_ID]:event.headers.Authorization
}
}),
});
var credentials = await cognitoidentity.config.credentials()
var identity_ID = credentials.identityId
console.log( identity_ID)
const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods" : "OPTIONS,POST,GET,PUT"
},
body:JSON.stringify(identity_ID)
};
return response;
}
After a Cognito User has signed in to my application, I can use the Auth directive of aws-amplify and fetch() in my React-Native app to invoke the lambda function shown above by sending a request to my API-Gateway trigger (authenticated with a Cognito User Pool Authorizer) by calling the following code:
import { Auth } from 'aws-amplify';
var APIGatewayEndpointURL = 'https://5lstgsolr2.execute-api.us-west-2.amazonaws.com/default/-'
var response = {}
async function getIdentityId () {
var session = await Auth.currentSession()
var IdToken = await session.getIdToken()
var jwtToken = await IdToken.getJwtToken()
var payload = {}
await fetch(APIGatewayEndpointURL, {method:"POST", body:JSON.stringify(payload), headers:{Authorization:jwtToken}})
.then(async(result) => {
response = await result.json()
console.log(response)
})
}
More info on how to Authenticate using aws-amplify can be found here https://docs.amplify.aws/ui/auth/authenticator/q/framework/react-native/#using-withauthenticator-hoc