Failed sending mail with googleapis' service account and JWT auth in nodejs - javascript

I'm trying to send an email using a service account and JWT authentication and keep getting and error with a very unhelpful message: { code: 500, message: null }
This code snippet is from the following StackOverflow link: Failed sending mail through google api in nodejs
It seems like the solution there was to change the key in the parameters to resource instead of message but it's not working for me. This is strange because in the JS example in the docs (https://developers.google.com/gmail/api/v1/reference/users/messages/send) it claims the key is still message
I'm authenticating with
var jwtClient = new google.auth.JWT(config.SERVICE_EMAIL, config.SERVICE_KEY_PATH, null, config.ALLOWED_SCOPES);
then sending an email with
jwtClient.authorize(function(err, res) {
if (err) return console.log('err', err);
var email_lines = [];
email_lines.push("From: \"Some Name Here\" <rootyadaim#gmail.com>");
email_lines.push("To: hanochg#gmail.com");
email_lines.push('Content-type: text/html;charset=iso-8859-1');
email_lines.push('MIME-Version: 1.0');
email_lines.push("Subject: New future subject here");
email_lines.push("");
email_lines.push("And the body text goes here");
email_lines.push("<b>And the bold text goes here</b>");
var email = email_lines.join("\r\n").trim();
var base64EncodedEmailSafe = new Buffer(email).toString('base64').replace(/\+/g, '-').replace(/\//g, '_');
var params = {
auth: jwtClient,
userId: "myaddress#gmail.com",
resource: {
raw: base64EncodedEmailSafe
}
};
gmail.users.messages.send(params, function(err, res) {
if (err) console.log('error sending mail', err);
else console.log('great success', res);
});
}
The comments in the library seem to say that resource is the correct property as well (https://github.com/google/google-api-nodejs-client/blob/master/apis/gmail/v1.js)
What am I missing?

According to #ryanseys on github
You cannot authorize Gmail API requests with JWT, you must use OAuth 2.0 because it needs to be auth'd to a specific user. Or else you'd be able to do some really shady things like send messages impersonating someone else. The Google APIs Explorer is authenticated with OAuth 2.0 that's why it works. See https://developers.google.com/gmail/api/auth/about-auth for more information.
As you can see in Failed sending mail through google api in nodejs, auth: OAuth2Client, they are using the OAuth2 client to authenticate. There is currently no way for you to send messages using the GMail API without authenticating as a specific GMail user. Service accounts do not have access to GMail the same way that regular users do.
Hopefully this helps someone else out there trying to use a service account to send mail!

Related

Gmail with Heroku to send mail

I want to add to my web app that after order I'm sending a mail.
I choose Nodemailer because it's the most famous npm to use.
I coded my request and in the local environment, it's working.
I uploaded the code to Heroku and I get an Error.
Error: Invalid login: 534-5.7.14 <https://accounts.google.com/signin/continue?sarp=1&scc=1&plt=AKgnsbs
I checked people told me to disable the captcha wish I did here: UnlockCaptcha
And now I still get the same error, and I get a mail that google blocked the connection what can I do?
const nodemailer = require('nodemailer');
const { sendLog } = require('../middleware/sendLog');
const { coupons, actions } = require('../constant/actionCoupon');
var simple = function () {
var textMultiple = {
text1: 'text1',
text2: 'text2',
};
return textMultiple;
};
// send mail system for the (REQUEST ACCEPTED SYSTEM)
const sendMail = (mail, action) => {
let mailTransporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.MAIL,
pass: process.env.PASSWORD,
},
});
let mailDetails = {
from: process.env.MAIL,
to: mail,
subject: `Thank you for your purchase. with love FameGoal`,
text: "for any probleme please reply on this message",
};
mailTransporter.sendMail(mailDetails, function (err, data) {
if (err) {
console.log(err);
console.log(`error sent mail to ${mail}`, 'error');
} else {
console.log('succeed');
console.log(`succesfully sent mail to ${mail}`, 'info');
}
});
};
exports.sendMail = sendMail;
Using Gmail as an SMTP relay isn't the most ideal because Google servers may reject basic username/password authentication at times.
There are some workarounds. The most ideal is to use OAuth2 to send emails.
OAuth2
OAuth2 uses access tokens to perform authentication instead of a password.
I won't go over the steps to set up OAuth2 because it can take some time but if you're interested, this answer: https://stackoverflow.com/a/51933602/10237430 goes over all of the steps.
App passwords
If the Google account you're trying to send emails from has two step verification enabled, using a password to send emails will not work. You instead need to generate a app-specific password on Google's site and pass that in the password field.
More info on that here: https://support.google.com/accounts/answer/185833?hl=en
Enabling less secure apps
If you still want to use your current setup, you have to make sure you enable less secure apps on the Google account you're sending emails from. This will let you authenticate with Google using just an email and a password.
More info on that here: https://support.google.com/accounts/answer/6010255?hl=en
Basic password authentication will not work until you enable less secure apps.

How to get nodemailer to work with 2LO for service accounts?

I have followed the instructions on the nodemailer site to the letter, but OAuth2 for google service accounts simply does not work for me.
Either I get ECONN timeouts when setting "host" to mail.google.com or some combination of "401, user/pwd not accepted, can not create access token" errors when using smtp.gmail.com as the host.
I've used this as my template:
http://nodemailer.com/smtp/oauth2/#example-4
I've set up a service account: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
I've enabled the gmail api.
I've created tokens that validate:
https://www.googleapis.com/oauth2/v1/tokeninfo
I have also tried the xoauth2 npm package to generate tokens and failed...
I also tried the answer in this question, but still no luck...
There seems to be an endless supply of answers for 3LO, but none that I've tried for 2LO that work. Now, having said all that.
var nodemailer = require("nodemailer");
var { google } = require("googleapis");
var accessToken;
var expires;
var key = require(“path/to/secrets.json");
var privateKey = key.private_key;
var jwtClient = new google.auth.JWT(key.client_email, null, key.private_key, ["https://mail.google.com/"], null);
jwtClient.authorize(function(err, tokens) {
if (err) {
return;
} else {
token = tokens
accessToken = tokens.access_token //this **IS** a valid token
expires = tokens.expiry_date
}
var transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
type: "OAuth2",
user: key.client_email, //I've also used my email here
serviceClient: key.client_id,
privateKey: privateKey,
accessToken: accessToken,
expires: expires,
},
});
var mailOptions = {
from: “me#here.com”
to: “me#there.com",
subject: "Ready",
text: “Really Ready"
}
transporter.sendMail(mailOptions, function(error, info) {
if (error) {
return;
}
console.log("Message %s sent: %s", info.messageId, info.response);
});
});
which generated the error:
535-5.7.8 Username and Password not accepted.
But as I mentioned, I've tried differing configurations and settings and gotten just as many different errors...
SO... Has anyone had success in using service accounts for 2LO using nodemailer?
I'm using node 9.5.0 and nodemailer ^4.6.0
I got it working (2021!), these were the steps:
Log in to console.- https://console.cloud.google.com/
Create a service account under the project.
Click on the new service account, go to permissions and add a member. You will use this member's email address when sending the request.
Create keys for the service account. - keys -> add key. https://console.cloud.google.com/iam-admin/serviceaccounts
Download your key file. You will get something like service-account-name-accountid.json. It will have all the information you need to get the code below running.
Delegate authority to your service account https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority. Addhttps://mail.google.com/ as the scope.
Write some code like below:
const nodemailer = require('nodemailer');
const json = require('./service-account-name-accountid.json');
const sendEmail = async (email, subject, text) => {
try {
const transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
type: 'OAuth2',
user: email, //your permissioned service account member e-mail address
serviceClient: json.client_id,
privateKey: json.private_key
}
});
await transporter.verify();
await transporter.sendMail({
from: json.service_email,
to: email, //you can change this to any other e-mail address and it should work!
subject,
text
});
console.log('success!');
return {
status : 200
}
} catch (error) {
console.log(error);
return {
status : 500,
error
}
}
}
sendEmail('your_permissioned_service_account_email_address#some_place.com, 'testing 123', 'woohoo!');
Why? Because one the default scopes on your credentials for OAuth 2.0 client IDs are these:
email <- Can only view email address
profile
openid
If you want to send email using node mailer it should include this scope:
https://mail.google.com/
and which is a sensitive scope, so google needs to verify it first else you will receive some delegation error messages.
Follow this verification process.
And add scope in the consent page
Or
Make sure you're an admin of the gsuite then give your service account access to sending email or an admin can give your service account access to sending email.
This guide will help. It's in Japanese just translate it to english.
Old thread, but I've got this working just now (after a lot of trying) so a few suggestions for anyone interested:
Enable the Gmail API in the Cloud Console
Use your 'normal' email address as the user (not the Service Account email)
Go here https://admin.google.com/ac/owl/domainwidedelegation and add your service client to the list. Here you will have to type in your client_id for the Client Name part and https://mail.google.com/ as the API scope. This last part was hard to find, but made it work for me in the end.
Also, I found this guide very helpful: Here you will have to type in your client_id for the Client Name part and https://mail.google.com/ as the API scope before pressing the Authorise.

Google account gets suspended when using Gmail API from Node.js Nodemailer

I have an Express web application on a Node.js server. It supports sending email notifications for registration, password reset, etc. For this, I use Nodemailer and XOAuth2 to connect to Google API using a normal Google account. The connection works well and emails are being sent as expected. But after a while (1-3 days), Google sends me a message saying my account "has been suspended because of a violation of our Terms of Service" (screenshot). So I have to manually recover the account using the link in the notification and then the emails that were blocked during suspension are sent.
After getting 3 of those notifications, I noticed that they follow only if I triggered my email sending function (see below)
I tried to log XOAuth2's token refresh and seems like it works
I tried to renew the refreshToken from the playground several times but the problem remains
IMHO, it might be that I'm using a free account and Google wants me to pay for using its API, it thinks I do some kind spams, or it just doesn't want me to use its API. I'm I wrong?
So my questions are:
Why does my account get suspended?
What can I do to fix this problem?
Code details (approximate):
var bluebird = require('bluebird');
var nodemailer = require('nodemailer');
var xoauth2 = require('xoauth2');
// clientId, clientSecret taken from: https://console.developers.google.com/
// Create credentials > OAuth client ID > Web application >
// Name = Nodemailer,
// Authorised redirect URIs = https://developers.google.com/oauthplayground
//
// refreshToken taken from: https://developers.google.com/oauthplayground/
// Access token location: Autorization header w/ Bearer prefix
// Access type: Offline
// Force prompt: Consent Screen
// Use your own OAuth credentials: yes
// Authorized APIs: everything inside Gmail API v1
// Auto refresh the token after it expires: yes
xoauth2Gen = xoauth2.createXOAuth2Generator({
user: 'example#gmail.com', // my real google account
clientId: '84037...t.com', // my real clientId
clientSecret: 'c3Yo...KP', // my real clientSecret
refreshToken: '1/ex...Wk' // my real refreshToken
// nothing more here
});
var mail_transport: {
service: 'Gmail',
auth: { xoauth2: xoauth2Gen }
};
var mailer = bluebird.promisifyAll(nodemailer.createTransport(mail_transport));
mailer.sendMail({
from : '"Example User" <example#gmail.com>',
to : 'recipient#gmail.com',
subject : 'Example subject',
text : 'Example\nplain\ntext',
// could it be bad html?
html : 'Example<br><strong>html</strong><br>version'
}).then(function (info) { console.log(info); });

Creating users in tenant using Microsoft Office 365 API and link it to chrome Id

I manually created a user in Azure active directory for my project and I am able to get the users. I made a chrome extension and GCM provides me a ID which I want to be linked with the microsoft account.
So for each user, I want a GCM id (got this part) and an Azure AD Id linked together.
I was doing the following:
router.route('/users')
// create a user accessed at POST http://localhost:8080/api/users)
.post(function(req, res) {
// Get an access token for the app.
auth.getAccessToken().then(function (token) {
console.log(token)
var user = new User({
officeId: token,
name : req.body.name,
email :req.body.email,
chromeId : req.body.chromeId
});
user.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'User created!' });
});
});
});
However, what this does is take the auth token id, chromeId, name and email and just adds it to my mongoose database.
What can I do differently in order to get what I want to achieve? My teammate says what I am doing is correct but I checked the Azure AD and I don't see my user authorized there.
Btw, in the front-end, I ask a user to give their microsoft email and name.
Also, I merged my code with the code found here https://github.com/OfficeDev/O365-Nodejs-Microsoft-Graph-App-only
// #name getAccessToken
// #desc Makes a request for a token using client credentials.
auth.getAccessToken = function () {
var deferred = Q.defer();
// These are the parameters necessary for the OAuth 2.0 Client Credentials Grant Flow.
// For more information, see Service to Service Calls Using Client Credentials (https://msdn.microsoft.com/library/azure/dn645543.aspx).
var requestParams = {
'grant_type': 'client_credentials',
'client_id': config.clientId,
'client_secret': config.clientSecret,
'resource': 'https://graph.microsoft.com'
};
// Make a request to the token issuing endpoint.
request.post({url: config.tokenEndpoint, form: requestParams}, function (err, response, body) {
var parsedBody = JSON.parse(body);
if (err) {
deferred.reject(err);
} else if (parsedBody.error) {
deferred.reject(parsedBody.error_description);
} else {
// If successful, return the access token.
deferred.resolve(parsedBody.access_token);
}
});
return deferred.promise;
};
If you want to create use in your AAD, you can leverage the Microsoft Graph API: Create User, which is not implemented in your code or the graph.js code at github repository.
You need to implement the function yourself like:
Additionally, it seems that we have to generate the access token in Authorization Code Grant Flow to complete the operation. As in my test, I got the Authorization_RequestDenied error when I use the app-only flow access token to authorize the operation, and the graph server returned me the message:
"message": "Insufficient privileges to complete the operation."
you can refer to https://github.com/OfficeDev/O365-Nodejs-Microsoft-Graph-Connect/ for the sample.

AWS Cognito - Developer Authenticated Identities in JavaScript(Browser)

I have trouble getting credentials in a browser script.
The authentication server returns cognito_identityId and cognito_token.
Then I set a Cookie:
$.cookie('cognito_identityId')
$.cookie('cognito_token')
I tried to get credentials in 4 ways on the browser, and all Failed:
CognitoIdentityCredentials
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:xxxxxxxxxxxx'
IdentityId: $.cookie('cognito_identityId'),
Logins: {
'myauth': $.cookie('cognito_token')
}
});
// => Error: Missing required key 'IdentityId' in params
assumeRoleWithWebIdentity
var params = {
RoleArn: 'arn:aws:iam::xxxxxxxxxxxx:role/Cognito_xxxxxxxAuth_Role',
RoleSessionName: 'xxxxxxxxxxx',
WebIdentityToken: $.cookie('cognito_token'),
DurationSeconds: 900,
ProviderId: 'myauth'
};
var sts = new AWS.STS({apiVersion: '2011-06-15'});
sts.assumeRoleWithWebIdentity(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
// => AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity
PolicyDocument
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "us-east-1:xxxxxxxxxxxxx"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}
GetCredentialsForIdentity
var params = {
IdentityId: $.cookie('cognito_identityId'),
Logins: {
"myauth": $.cookie('oauth.io_token')
}
};
var cognitoidentity = new AWS.CognitoIdentity({apiVersion: '2014-06-30'});
cognitoidentity.getCredentialsForIdentity(params, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
}
else {
console.log(data); // successful response
}
});
// => InvalidParameterException: Please provide a valid public provider
WebIdentityCredentials
AWS.config.credentials = new AWS.WebIdentityCredentials({
RoleArn: 'arn:aws:iam::xxxxxxxx:role/Cognito_xxxxxxxxxxAuth_Role',
WebIdentityToken: $.cookie('cognito_token')
});
// => Error: There were 2 validation errors:
// * MissingRequiredParameter: Missing required key 'IdentityPoolId' in params
// * MissingRequiredParameter: Missing required key 'IdentityId' in params
Questions:
What am I doing wrong?
What is the correct way to use this?
Thank you.
Thank you for your kindness.
I tyied your advice, but did not change.
Error messages.
POST https://cognito-identity.us-east-1.amazonaws.com/ 400 (Bad Request)
POST https://cognito-identity.us-east-1.amazonaws.com/ 400 (Bad Request)
Error: Missing required key 'IdentityId' in params
at fail (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:2163:37)
at validateStructure (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:2084:14)
at validateMember (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:2110:21)
at validate (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:2059:10)
at Request.VALIDATE_PARAMETERS (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:800:32)
at Request.callListeners (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:3913:20)
at callNextListener (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:3903:12)
at chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:787:9
at finish (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:126:7)
at chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:142:9
There are source code below link.
https://github.com/bisque33/my-custom-dictionary
and server side is a AWS Lambda Function.
var aws = require('aws-sdk');
aws.config.region = 'us-east-1';
var cognitoidentity = new aws.CognitoIdentity();
var identityPoolId = 'us-east-1:0dccff0d-5fd7-4d14-b38f-d27204feaecc';
console.log('Loading function');
exports.handler = function(event, context) {
console.log('token: %s', event.token);
var params = {
IdentityPoolId: identityPoolId,
Logins: {
'oauth.io': event.token
}
};
cognitoidentity.getOpenIdTokenForDeveloperIdentity(params,function(err,data){
if(err){
console.log(err);
context.fail('Something went wrong');
}else{
context.succeed(data);
}
});
};
This program is Google-Chrome-Extension.
AWS Lambda Function returns token by getOpenIdTokenForDeveloperIdentity.
app/scripts/popup.js calls Lambda Function and set cookies.
app/scripts/background.js calls AWS.config.credentials.get, and returns error.
Am I using it wrong?
Update for Additional Information
Thank you for the additional information.
Error appears on 104 line on background.js
AWS.config.credentials.get(function(){
and 115 line on background.js
dataset.synchronize(
And, My explaination was not enough. Facebook authentication needs the domain(ex. http :// example.com). However, Google-Chrome-Ext does not have domain. It has a domain 'chrome-extension://xxxxxxxxxxxxxxxxxxxx'. Then, I use https://oauth.io. It proxies any authentication and accepts chrome-extension domain.
Popup.js does Facebook authentication through oauth.io sdk. It gets a facebook token, and gives to getOpenIdTokenForDeveloperIdentity. I think facebook token.substr(0,14) is unique. But, If it is wrong, I use another unique identifier(ex. email-address.)
Sorry, I was wrong. AWS.config.credentials.get gives an Error:
Error: Invalid login token.
And, dataset.synchronize shows this Error:
Error: Missing required key 'IdentityId' in params
The first approach you have, using CognitoIdentityCredentials, is most likely the best approach for you to take. I can't spot exactly what's causing the error for you but lets try a couple things:
When using Developer Authenticated Identities, you do need to specify the IdentityId when initializing CognitoIdentityCredentials. You need to get the IdentityId value from the call to GetOpenIdTokenForDeveloperIdentity. However, you shouldn't need to preserve the IdentityId value in a cookie as CognitoIdentityCredentials will cache the id by default in the browser's local storage.
As for your Logins map: It looks like you're trying to use Developer Authenticated Identities. With the JavaScript SDK, use the key 'cognito-identity.amazonaws.com' and make sure the value is the token returned from your backend's call to getOpenIdTokenForDeveloperIdentity.
If you continue to have problem using the CognitoIdentityCredentials approach, please reply here with some more info such as the exact method/code you're calling when you receive the error message, and the traced output (i.e. with console.log('%o',..)) of the params input just before your call to the CognitoIdentityCredentials constructor.
Update Based on Additional Information Provided
I still need to know exactly which line of code you receive the error on, but based on the information provided I think I can still help...
Based on what I see in background.js, it looks like you're trying to initialize CognitoIdentityCredentials using a Developer Authenticated Identities provider. This is where I'm guessing that you're receiving the error.
However, in Popup.js, it looks like you're trying to authenticate the user with Facebook. If you're authenticating your users with Facebook, you should just pass the facebook access token into your Logins map when using Cognito. Just use graph.facebook.com as the key in the Logins map and the access token from Facebook. More detail on how to do this is in the Facebook Integration topic of the Amazon Cognito developer guide.
Facebook vs Developer Authenticated Identities
We can get Developer Authenticated Identities to work for you, but in this case, it doesn't look like the right solution for you since you're not actually doing any additional authentication on the identity in your Lambda function and the unique user identifier that you're passing into the getOpenIdTokenForDeveloperIdentity operation appears to be the facebook token, which is not good by the way since the token itself will change between user sessions even for the same user. Usually a good unique identifier is an email address or a user id used by an internal system.
Facebook Login & Redirects
Since you're ultimately trying to use Facebook for login and Amazon Cognito has built-in integration for Facebook, the best thing for you to do is get an access token from Facebook and pass in the Facebook token to Cognito's login map directly. I'm not sure if this will work with Auth.io or not (I'm just not familiar with it), but as long as Auth.io gives your JavaScript code a bonefide facebook token and you add the same Facebook App ID to both Auth.io and Amazon Cognito's Console, it should work. However, you mentioned you want to use Auth.io to avoid Facebook doing a redirect to a landing page. I could be mistaken, but I'm pretty sure if you're using Facebook's JavaScript SDK you won't need a redirect page. You should only need the redirect page if you're doing Facebook's Manually Build a Login Flow.

Categories

Resources