My nodejs program fails to send messages using the Gmail api.
The solution from Gmail API for sending mails in Node.js does not work for me.
I encode an email with
var {google} = require('googleapis');
// to and from = "some name <blaw.blaw.com"
function makeBody(to, from, subject, message) {
var str = ["Content-Type: text/plain; charset=\"UTF-8\"\r\n",
"MIME-Version: 1.0\r\n",
"Content-Transfer-Encoding: 7bit\r\n",
"to: ", to, "\r\n",
"from: ", from, "\r\n",
"subject: ", subject, "\r\n\r\n",
message
].join('');
encodedMail = new Buffer(str).toString("base64").replace(/\+/g, '-').replace(/\//g, '_');
return encodedMail;
}
Then go to the Google API explorer
https://developers.google.com/apis-explorer/#p/
enter gmail.users.messages.send and the string generated from the above make_body.
An email will be successfully sent. So I know the above encoding is
ok.
When my program tried to send using the following, it fails with error
Error: 'raw' RFC822 payload message string or uploading message via
/upload/* URL required
function sendMessage(auth) {
var gmail = google.gmail('v1');
var raw = makeBody('john g <asdfasdf#hotmail.com>', 'john g<asfasdgf#gmail.com>', 'test subject', 'test message #2');
gmail.users.messages.send({
auth: auth,
userId: 'me',
resource: {
raw: raw
}
}, function(err, response) {
console.log(err || response)
});
}
The auth token is good since I can call gmail.users.labels.list and I use the same authorization when using the API explorer.
Q1: Does anyone know why the above does not work?
Q2: Gmail API for sending mails in Node.js does not explain why the raw email message is wrapped inside a resource field. I tried simply raw and it did not help.
This fails.
gmail.users.messages.send({
auth: auth,
userId: 'me',
resource: {
raw: raw
}
}, function(err, response) {
console.log(err || response)
});
and so does
gmail.users.messages.send({
auth: auth,
userId: 'me',
raw: raw
}, function(err, response) {
console.log(err || response)
});
and so does this GMAIL API for sending Email with attachment
gmail.users.messages.send({
auth: auth,
userId: 'me',
data: raw
}, function(err, response) {
console.log(err || response)
});
Does anyone know where its documented how to pass the "requested body" the api explorer is asking for?
Q3: Why does the google api need substitutions in the base64 encoding?
I tried encoding using
const Base64 = require("js-base64").Base64
var encodedMail = Base64.encode(str);
When I feed this into the API explorer, I get the error
"message": "Invalid value for ByteString:
Ohai! For others that stumble here, a few things. First - we have a complete end to end sample of sending mail now here:
https://github.com/google/google-api-nodejs-client/blob/master/samples/gmail/send.js
Second, the answer above is mostly right :) Instead of installing the latest version of google-auth-library... just remove it from your package.json all together. The getting started guide was very, very wrong (it has since been fixed). googelapis brings in it's own compatible version of google-auth-library, so you really don't want to mess with that by installing your own version :)
The quickstart specifies:
npm install google-auth-library#0.* --save
When I changed this to
npm install google-auth-library -- save
it pulled in version 1.3.1 vs 0.12.0. Everything started working once I changed the code to account for the breaking changes. The latest version of googleapis also has breaking changes. Here is my tweaks to the quickstart:
package.json
....
"dependencies": {
"google-auth-library": "^1.3.1",
"googleapis": "^26.0.1"
}
quickstart.js
var fs = require('fs');
var readline = require('readline');
var {google} = require('googleapis');
const {GoogleAuth, JWT, OAuth2Client} = require('google-auth-library');
var SCOPES = [
'https://mail.google.com/',
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/gmail.compose',
'https://www.googleapis.com/auth/gmail.send'
];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'gmail-nodejs-quickstart.json';
function authorize(credentials, callback) {
var clientSecret = credentials.installed.client_secret;
var clientId = credentials.installed.client_id;
var redirectUrl = credentials.installed.redirect_uris[0];
var auth = new GoogleAuth();
var oauth2Client = new OAuth2Client(clientId, clientSecret, redirectUrl);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function (err, token) {
if (err) {
getNewToken(oauth2Client, callback);
} else {
oauth2Client.credentials = JSON.parse(token);
callback(oauth2Client);
}
});
}
function getNewToken(oauth2Client, callback) {
var authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
console.log('Authorize this app by visiting this url: ', authUrl);
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Enter the code from that page here: ', function (code) {
rl.close();
oauth2Client.getToken(code, function (err, token) {
if (err) {
console.log('Error while trying to retrieve access token', err);
return;
}
oauth2Client.credentials = token;
storeToken(token);
callback(oauth2Client);
});
});
}
function makeBody(to, from, subject, message) {
var str = ["Content-Type: text/plain; charset=\"UTF-8\"\n",
"MIME-Version: 1.0\n",
"Content-Transfer-Encoding: 7bit\n",
"to: ", to, "\n",
"from: ", from, "\n",
"subject: ", subject, "\n\n",
message
].join('');
var encodedMail = new Buffer(str).toString("base64").replace(/\+/g, '-').replace(/\//g, '_');
return encodedMail;
}
function sendMessage(auth) {
var gmail = google.gmail('v1');
var raw = makeBody('xxxxxxxx#hotmail.com', 'xxxxxxx#gmail.com', 'test subject', 'test message');
gmail.users.messages.send({
auth: auth,
userId: 'me',
resource: {
raw: raw
}
}, function(err, response) {
console.log(err || response)
});
}
const secretlocation = 'client_secret.json'
fs.readFile(secretlocation, function processClientSecrets(err, content) {
if (err) {
console.log('Error loading client secret file: ' + err);
return;
}
// Authorize a client with the loaded credentials, then call the
// Gmail API.
authorize(JSON.parse(content), sendMessage);
});
Now when I run, I get the response
Object {status: 200, statusText: "OK", headers: Object, config: Object, request: ClientRequest, …}
Related
Please refer to the following image.
I was able to successfully integrate the MFA with my Cognito pool and I am getting the OTP as well. Once the OTP is submitted I get the following error message as a 400 bad request. It seems MFA integration is working since once I entered the wrong code as the OTP then it shows the error message.
Once entered the correct OTP that I received into my mobile only I get this error message. Could someone please help me to sort out this issue?
I am expecting to get rid of this 400 Bad request error message which is returned by cognito endpoint call. Please find the below code for my cognito integration with the ReactJs project.
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function(result) {
console.log("RESULT SUCCESS: ", result);
const accessToken = result.getAccessToken().getJwtToken();
console.log("ACCESS TOKEN: ", accessToken);
cognitoUser.getSession(function(err, session) {
if (err) {
console.log(err);
return;
}
// console.log('session validity: ' + session.isValid());
const totpMfaSettings = {
PreferredMfa: true,
Enabled: true
};
cognitoUser.setUserMfaPreference(null, totpMfaSettings, function(
err,
result
) {
if (err) {
console.log(err);
}
console.log("call result " + result);
});
});
},
onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
mfaSetup: function(challengeName, challengeParameters) {
console.log("MFA SETUP");
cognitoUser.associateSoftwareToken(this);
},
associateSecretCode: function(secretCode) {
const challengeAnswer = prompt("Please input the TOTP code.", "");
cognitoUser.verifySoftwareToken(
challengeAnswer,
"My TOTP device",
this
);
},
selectMFAType: function(challengeName, challengeParameters) {
var mfaType = prompt("Please select the MFA method.", ""); // valid values for mfaType is "SMS_MFA", "SOFTWARE_TOKEN_MFA"
cognitoUser.sendMFASelectionAnswer(mfaType, this);
},
totpRequired: function(secretCode) {
var challengeAnswer = prompt("Please input the TOTP code.", "");
cognitoUser.sendMFACode(challengeAnswer, this, "SOFTWARE_TOKEN_MFA");
},
mfaRequired: function(codeDeliveryDetails) {
var verificationCode = prompt("Please input verification code", "");
cognitoUser.sendMFACode(verificationCode, this);
},
newPasswordRequired: userAttributes => {
// XXXXXXXX
// This is somethign we need to fetch from an input
cognitoUser.completeNewPasswordChallenge("XXXXXXXX", {}, {
onSuccess: result => {
// login
console.log(result)
}
});
// };
}
});
I have an application running on http://localhost:8181/ which has React integrated with Spring Boot. (both run on the same port).
I send this POST request to http://localhost:8181/public/authenticate using axios:
The axios instance:
export const axios_register_login_user = axios.create({
baseURL: '/public',
withCredentials: true,
headers: { "content-type": "application/json" },
method: 'POST'
})
The login request:
export async function login(username, password, callback) {
axios_register_login_user.post("/authenticate", {
'username': username,
'password': password
}).then(response => {
console.log("success", response);
callback(response.data);
}).catch(error => {
console.log("failed", error);
callback("Failed");
})
}
The login is successful and I can see a cookie being returned in the response
However, this cookie is not set in the Application->Cookies tab
Here's my API code:
#RequestMapping(value = "/authenticate", method = RequestMethod.POST, produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<?> authenticateUser(#RequestBody AuthenticationRequest request, HttpServletResponse response) {
try {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword());
authenticationManager.authenticate(usernamePasswordAuthenticationToken);
} catch (BadCredentialsException e) {
throw new BadCredentialsException("Invalid details");
}
UserDetailsImpl userDetails = (UserDetailsImpl) userDetailsService.loadUserByUsername(request.getUsername());
String name = userDetails.getUser().getName();
String generatedToken = jwtUtil.generateToken(userDetails);
Cookie cookie = new Cookie("jwt", generatedToken);
cookie.setMaxAge(60 * 60 * 10);//like JWT, the max age is 10 hours
// cookie.setSecure(false);
cookie.setHttpOnly(true);
response.addCookie(cookie);
return new ResponseEntity<>(new AuthenticationResponse(name + " " + generatedToken), HttpStatus.OK);
}
I tried adding
#CrossOrigin(allowCredentials = "true", origins = "{http://localhost:3000,http://localhost:8181}")
to the above method but it didn't help.
Please help me out with this. I've been stuck here for 2 days now :(
I was missing cookie.setPath("/") in my code.
Additionally, I also did cookie.setSecure(false);
After these changes, the cookie was added to the browser
My task is pretty simple, all i have to do is to create records in the lead section of the salesforce. I have created a free account and i am not able to figure it out that, what is https://yourinstance.saleforce.com in the below rest api:
https://yourinstance.salesforce.com/services/data/v39.0/sobjects/Lead
Body JSON:
{
"body": {
"Salutation": "Mr.",
"FirstName": "H",
"LastName": "Sam",
"Company": "Samosto"
}
}
Header:
Authorization: Bearer 00D0o0000015jPn!ARgAQPiIGhuYGUG_c0HDKNR0hxTX9zS82Fv1lIuqn4rapFJHPR422gLyi10rF8Auukb._hj9pj532DP7IajQV36lyKpUNEXdxvL
Content-Type: application/json
Sforce-Auto-Assign: TRUE
Any help will be highly appreciated!
This is the URL you have for the organization you want to login to. Since most of the orgs are using their own Domain names in guides or examples you will see this "https://yourinstance.saleforce.com" being used.
You can simply take it from the URL while logged in Salesforce or go to Setup -> quick search "My Domain" and you will see the domain name.
It is a good thing to check it from here as the generic URL can also be blocked as a login option.
fwiw i think the API has changed to Account. This is working for me
// to run:
// node create_new_account.js --config ./config_na150_scan_email_app.json
//
// links:
// https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_create.htm
const axios = require("axios");
const qs = require("qs");
const yargs = require("yargs");
const argv = yargs
.command('create_account', 'test creating salesforce leads', {
config: {
description: 'config',
alias: 'c',
type: 'string',
}
})
.help()
.alias('help', 'h')
.argv;
let { salesforce, scanResultsURL } = require(argv.config);
const auth_data = qs.stringify({"username": salesforce.username,
"password": salesforce.password + salesforce.security_token,
"grant_type": "password",
"client_id": salesforce.consumer_key,
"client_secret": salesforce.customer_secret,
"redirect_uri": salesforce.redirect_uri});
console.log("auth data", auth_data)
const auth_config = {
method: "post",
url: salesforce.oauth2_uri,
data: auth_data,
headers: {"Content-Type": "application/x-www-form-urlencoded"}
}
const action_url = "https://na<TBD>.salesforce.com/services/data/v51.0/sobjects/Account"
console.log('action url', action_url)
data = {
"Name" : "test Salesforce account API"
}
async function createAccout() {
// "get" token
axios(auth_config).then(function (response) {
auth_header = {"Authorization": "Bearer " + response["data"]["access_token"]}
action_config = {
method: "post",
url: action_url,
headers: auth_header,
data: data
}
// use valid token to send email
axios(action_config).then(function (response) {
console.log("action response", response["data"]); // TODO rm
}).catch(function (error) {
console.log("authenticated passed, action failed")
console.log("action error", error); // TODO something
})
}).catch(function (error) {
console.log("action token error", error); // TODO something
})
}
In my serverless app, I want to create pdf which is generated dynamically and then upload that created pdf into aws s3. My problem is, when a url is returned to client-side code from server, uploaded url doesn't working. My code is given below:
Client-side javascript code (angular.js)
$scope.downloadAsPDF = function() {
// first I have to sent all html data into server
var html = angular.element('html').html(); // get all page data
var service = API.getService();
service.downloadPdf({}, { html : html }, // api call with html data
function(res) {
console.log("res : ", res);
window.open(res.url); // open uploaded pdf file
// err: The server replies that you don't have permissions to download this file
// HTTP/1.1 403 Forbidden
}, function(err) {
console.log("err : ", err);
});
};
Serverless Code
var fs = require('fs');
var pdf = require('html-pdf');
var AWS = require("aws-sdk");
var s3 = new AWS.S3();
module.exports.handler = function(event, context) {
if (event.html) { // client html data
AWS.config.update({
accessKeyId: 'xxxxxxxxxxxxxxxxx',
secretAccessKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
region: 'xxxxxxxxxxxxxxxxxxxx'
});
var awsInfo = {
bucket: 'xxxxx-xxxxxx'
};
var baseUrl = 'https://s3-my-region.amazonaws.com/s3-upload-directory';
var folderRoot = 'development/pdf';
// unique file name
var output_filename = Math.random().toString(36).slice(2) + '.pdf';
// file created directory
var output = '/tmp/' + output_filename;
pdf.create(event.html, options).toStream(function(err, stream) {
if( err ) {
console.log('pdf err : ', err);
} else {
writeStream =fs.createWriteStream(output);
s3.putObject({
Bucket : awsInfo.bucket,
Key : folderRoot + '/' + output_filename,
Body : fs.createReadStream(output),
ContentType : "application/pdf"
},
function(error, data) {
if (error != null) {
console.log("error: " + error);
} else {
// upload data: { ETag: '"d41d8cd98f00b204e9800998ecf8427e"' }
console.log('upload data : ', data);
return cb(null, {
// return actual aws link, but no file
// ex: 'https://s3-my-region.amazonaws.com/s3-upload-directory/output_filename.pdf
url: baseUrl + '/' + output_filename
});
}
});
}
}
};
I've solve my problem. I was trying to upload pdf before I generate pdf. I have solve this problem using the following code:
pdf.create(event.html, options).toStream(function(err, stream) {
if (err) {
console.log('pdf err : ', err);
} else {
var stream = stream.pipe(fs.createWriteStream(output));
stream.on('finish', function () {
s3.putObject({
Bucket : awsInfo.bucket,
Key : folderRoot + '/' + output_filename,
Body : fs.createReadStream(output),
ContentType : "application/pdf"
},
function(error, data) {
if (error != null) {
console.log("error: " + error);
return cb(null, {
err: error
});
} else {
var url = baseUrl + '/' + output_filename
return cb(null, {
url: url
});
}
});
});
}
});
I have done similar kind of thing before. I want a few clarifications from you and then I will be able to help you better.
1) In your code (server side), you have mentioned in the callback function that actual aws link is getting returned.
Are you sure that your file is getting uploaded to Amazon s3. I mean did you check your bucket for the file or not?
2) Have you set any custom bucket policy on Amazon s3. Bucket policy play an important role in what can be downloaded from S3.
3) Did you check the logs to see exactly which part of code is causing the error?
Please provide me this information and I think the I should be able to help you.
if we don't want to upload at s3 just return generated file from aws-lambda.
I am trying to update some Dialog profile variables via the watson-developer-cloud library. The client_id variable seems to be ignored, and is being set to a random number. As a result, the profile variables are not being set. This is the endpoint in my API that is supposed to do the update:
app.put('/update', function(req, res)
{
setHeaders(req,res); //set up headers for CORS
var parms=req.body;
parms.dialog_id=dialogId;
console.log("Processing PUT /update...");
console.log("Setting: ",parms);
dialog.updateProfile(parms,function(err, results)
{
if (err)
{
console.log(err);
res.status(500);
res.send(err);
}
else
{
console.log('Update returning: ',results);
res.send(results);
}
});
});
and this is a sample value for parms (shown here after JSON.stringify(parms):
{"client_id":12345,"dialog_id":"4a6e3699-10ab-4703-86bb-0b74384aaf94","conversation_id":245895,"name_values":[{"name":"CPE_Name","value":"Tracey Moon"},{"name":"CPE_Name","value":"Kellogg"},{"name":"CPE_StateTerritory","value":"California"}]}
Is this a bug in the watson-developer-cloud library?enter code here
Going off the swagger doc here:
https://watson-api-explorer.mybluemix.net/swagger.html?url=/listings/dialog-v1.json#/
I would hit the following endpoint:
https://gateway.watsonplatform.net/dialog/api/v1/dialogs/7eb167d5-f581-40d3-b91a-ad9c142282ad/profile
with the following in the body:
{
"client_id": 155351,
"name_values": [
{
"name": "Name",
"value": "Mitch"
}
]
}
Here's a code snippet which does the getConversation to get a client id and then an updateProfile to set a profile var:
var watson = require('watson-developer-cloud');
var dialogid = <dialog_id>;
var clientid = '';
var dialog = watson.dialog({
username: <username>,
password: <password>,
version: 'v1'
});
dialog.conversation({dialog_id: dialogid}, function (err, dialogs) {
if (err)
console.log('error:', err);
else{
console.log('Started conversation with dialog id ' + dialogid + '.');
console.log(JSON.stringify(dialogs, null, 2));
clientid = dialogs.client_id;
}
});
dialog.updateProfile({ client_id: clientid, dialog_id: dialogid, name_values: [{"name":"Topic","value":"Education"}]}, function (err, dialogs) {
if (err)
console.log('error:', err);
else{
console.log('Set profile variable "Topic" to "Education".');
console.log(JSON.stringify(dialogs, null, 2));
}
});
The above code works for me. Hope it helps!
The problem is apparently a bug in an earlier version of the watson-developer-cloud package. Running
npm install watson-developer-cloud#latest as described in this post seems to have fixed the problem.