This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 7 years ago.
So, I'm trying to send an email confirmation token to an user, and for that, I'm trying to use the crypto module to generate the token. I have this:
var transport = this.NewTransport(), // This generates a nodemailer transport
token;
// Generates the token
require('crypto').randomBytes(48, function(ex, buf) {
token = buf.toString('hex');
});
// Uses nodemailer to send the message, with the token.
var message = {
from: 'test#email.com',
to: 'receiver#email.com',
subject: 'Token',
text: token,
html: token
};
transport.sendMail(message, function(err){
if(err) return res.send({s: 0});
return res.send({s: 1});
});
Well, the token generated by the crypto module isn't getting assigned to the token variable, I assume this is because of the asynchronous nature of the randomBytes function.
How can I actually... save the token somewhere so I can send it through the email? Or do I have to include ALL of the email-sending code inside of the randomBytes callback function? Is this the way it has to be done in node? Is there any other way, so that the token gets generated in time, and actually sent?
Sorry, I'm quite new to node and I'm still confused about callbacks sometimes. Thanks.
You should really wrap your code within functions. It makes it easier to manage callbacks and simultaneously maintain the code. Have a look at how I reworked what you provided... Keep in mind I haven't checked the code so there may be a few bugs.
var crypto = require('crypto'),
transport = this.NewTransport(); // This generates a nodemailer transport
generateToken(sendMail);
function generateToken(callback) {
// Generates the token
var token;
crypto.randomBytes(48, function(ex, buf) {
token = buf.toString('hex');
if (typeof callback === 'function') {
callback(token);
}
});
}
function sendMail(token) {
// Uses nodemailer to send the message, with the token.
var message = {
from: 'test#email.com',
to: 'receiver#email.com',
subject: 'Token',
text: token,
html: token
};
transport.sendMail(message, function(err){
if(err) return res.send({s: 0});
return res.send({s: 1});
});
}
Related
The Nodejs functions return an error from try/catch scope, such as the one below if the user doesn't exist of if a database is not reachable:
router.delete('/delete/:email', async (req, res) => {
var email = req.params.email;
try {
let result = await User.remove({"email": email});
res.status(204).send(email);
} catch (err) {
res.status(400).send(err);
}
});
I can also return the Error from Nodejs server by myself:
return res.status(400).send(new Error(`The user with email ${email} doesn't exist.`));
The first problem is that I can't find the error message that is embedded somewhere deep in the body the returned Error object. It is stored in one of its 100+ attributes. Where should I look for it so I could display in on a screen for the end user to read it?
Then, the err object generated by the try/catch scope has a set of different attributes comparing to the Error object created with new Error("Here is my error message"). Is there a way to normalize the returned Errors so they all have the same or similar attributes?
You don't need to return the whole error object from the server, and arguably shouldn't since error messages can expose internals about your code and infrastructure.
One way you could handle this is to format and return an error message from the server yourself. Assuming you're using express this would look something like:
return res.status(400).json({ message: `The user with email ${email} doesn't exist.` });
Alternatively you could use an error handling middleware like strong-error-handler found here: https://github.com/strongloop/strong-error-handler which automatically builds a json formatted message that's easier to parse, but keep in mind that the content of the message differs depending on whether you set debug mode to true or no.
If you want to develop a secure web application with nice error handling, i will suggest you the following structure.
Step 1. At front end divide your api calls in four main operations for e.g. inset,update,query and filter.
now whenever your page loads and you want to show some data fetched from server then your api call must be like 'https://domainname.tld/server/query' and send some payload with this api call according to need of your data requirements to be fetched.
At backend probably at Server.js handle like this :
app.all("/server/query", function (req, res) {
try {
console.log(a);
// some database or io blocking process
} catch (error) {
// error handling
var err = writeCustomError(error.message || error.errmsg || error.stack);
res.status(417).json(err).end();
}
});
function writeCustomError(message) {
var errorObject = {};
errorObject.message = message;
errorObject.code = 10001; // as you want
errorObject.status = "failed";
return errorObject;
}
in try block you can also handle logical errors using same function i.e writeCustomError
So if you use this approach you can also implement end-to-end encryption and send only eP('encrypted payload') and eK('encryption Key'),by doing this end users and bad end users even can not evaluate your serve API calls.
If you are thinking how will you route different paths at server then simplest solution is send uri in payload from client to server for e.g
User wants to reset password :-
then
call api like this
https://domain.tld/server/execute and send Json object in payload like this {uri:"reset-password",old:"",new:""}.
at backend
use
app.all("/server/execute", function (req, res) {
try {
// decrypt payload
req.url = payload.uri;
next();
} catch (error) {
// error handling
var err = writeCustomError(error.message || error.errmsg || error.stack);
res.status(417).json(err).end();
}
});
app.all("/reset-password", function (req, res) {
try {
// reset logic
} catch (error) {
// error handling
var err = writeCustomError(error.message || error.errmsg || error.stack);
res.status(417).json(err).end();
}
});
so in this way only developer know where password reset logic and how it can called and what parameters are required.
I will also suggest you to create different router files for express like QueryRouter,InsertRouter etc.
Also try to implement end-to-end encryption.Any query regarding post,kindly comment it.
I have the following Node.JS (ran with Express) code :
let app = express();
app.use(cors());
app.get('/callback', function (req, res) {
// your application requests refresh and access tokens
// after checking the state parameter
var code = req.query.code || null;
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
form: {
code: code,
redirect_uri: redirectUri,
grant_type: 'authorization_code'
},
headers: {
'Authorization': 'Basic ' + (new Buffer(clientId + ':' + clientSecret).toString('base64'))
},
json: true
};
request.post(authOptions, function (error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token,
refresh_token = body.refresh_token;
fs.writeFile('test.txt', 'HELLO', function (err) {
if (err) return console.log(err);
console.log('Hello World > helloworld.txt');
});
}
}
)
});
console.log('Listening on 8888');
app.listen(8888);
The route is used as a callback for a request to the Spotify Web API, thus I can get an access token.
Spotify then redirects to the callback function above, you can see it in the URI by looking at "redirect_uri".
If you need more information about the Spotify Authorization Flow, see here.
Here's the URI I'm using to authenticate my app to Spotify.
https://accounts.spotify.com/authorize?client_id=CLIENT_ID&response_type=code&redirect_uri=http://localhost:8888/callback&scope=user-read-private%20user-read-email%20playlist-modify-public&state=PexBrjEzISHepTp7&show_dialog=false
CLIENT_ID is replaced by my real CLIENT_ID in the request I make
My problem is located to the file writing part :
fs.writeFile('test.txt', 'HELLO', function (err) {
if (err) return console.log(err);
console.log('Hello World > helloworld.txt');
});
When the callback route is called by Spotify, I have the string "HELLO" wrote in my text file, so the file writing is functional.
But even if it has finished writing the string, the Chrome page is still running and "pending" on the server. It runs for a few minutes and then crash by saying that the page didn't sent any data. Why ?
I've looked at this page talking about the methods of writing to text files, using writeFile and writeFileAsync, but using both of them didn't solved my problem.
EDIT: I don't really want to stop the Express process! I just want to be able to process another request :)
Any idea ? Thanks in advance :)
You aren't returning anything from your route, try adding res.send({})
In your get route you are not sending response, you must send response irrespective of writing a file was successful or not.
Add below code post writing to file (as well as in if error case)
res.send({YOUR_CHOICE_RESPONSE_DATA})
I have a simple firebase functions script setup (running firebase-admin version 8.0 and firebase-functions version 2.3.1):
const functions = require('firebase-functions');
const cors = require('cors')({
origin: true,
});
//Gets and returns a user's ip address
exports.getIPAddress = functions.https.onRequest((req, res) => {
let ipAddress = req.headers['fastly-client-ip'] || req.connection.remoteAddress;
ipAddress = ipAddress.toString();
console.log('Fetched IP Address: ' + ipAddress);
return cors(req, res, () => {
res.status(200).send(ipAddress);
});
});
The function's goal is simply to return to user's IP address. It logs fine in the functions console, no errors.
Here is the client code:
var getIPAddress = mainFirebase.functions().httpsCallable('getIPAddress');
function testIP() {
getIPAddress().then(function(result) {
console.log(result.data.text)
});
}
However, the console says that 'result' is not a valid JSON object.
I've tried using https.onCall which somebody else on the internet recommended, however, the console says that function doesn't exist.
Any help getting the response to work properly would be greatly appreciated!
Your function is a regular HTTP type function. However, your client code is attempting to call it as if it were a callable type function. That's not going to work. If you want to invoke a callable type function, you'll have to implement the function according to the documentation.
If you need to keep the function as an HTTP type function, you can't use the Firebase client SDK to invoke it. Just invoke it as if it were any other type of HTTP endpoint.
For Callable functions. You need to create a function like:
exports.addMessage = functions.https.onCall(
async (data, context) => {
// context contains the user info.
}
);
And on your front-end you can call them like:
firebase.functions().httpsCallable('addMessage');
addMessage({text: messageText}).then(function(result) {
// Read result of the Cloud Function.
var sanitizedMessage = result.data.text;
}).catch(function(error) {
// Getting the Error details.
var code = error.code;
var message = error.message;
var details = error.details;
// ...
});
As you are calling an https message. You can also use the SDK to call https methods. But make sure you are handling CORS on your server.
In your client. Just use the http client.
this.http.post method with the function url.
I'm adding tests to an application that already (partially) exists. It was written using angular and php/MariaDB in the backend. I'm now working on the http calls to the server. I plan to use jasmine with request.
I was able to make some simple tests, and can login. But I cannot test the pages that require to be logged in. I cannot find a way to add the token to the calls.
If I understand things correctly, on the received message from the sig in I should get a token that I should then use in the following calls. Who wrote the app followed the instructions given by the angular documentation which handles everything, so we are learning toguether how things really work under the hood.
Going through the received answer on the login, the only thing that looks like a token is a cookie set in the header, whose name is 'PHPSESSID'. I read and parse that cookie to get the token and make the next call like this:
request.get(
{
url: 'http://xxx.xxx.com/php/authentication/session.php',
'auth': {
'bearer': mytoken
}
}, function(err, res) {
console.log(res['body']);
done();
})
the response is what I should get if the user is NOT logged in.
Using Postman, everything works. Aparently it saves the token and uses it for the next call. That is, f I make the sign in and then make the get call to session.php I get the correct answer. I just cannot figure out what exact call postman makes or how to use the token in the next call using jasmine and request.
Since the token was passed as a cookie on the response call, what I was supposed to do was set the same cookie in the next call. Here is the code of the whole test just in case somebody needs a hand. First I sign in, then I made a call to an address that should return my email if indeed I'm logged in.
var request = require('request');
describe("login test", function() {
it("should log in", (done) => {
var user = {'email':'test#xxx.de', 'password':'blablabla'};
request.post(
{
url: 'http://xxx/test/public/php/authentication/login.php',
body: JSON.stringify(user)
},
(err, res) => {
var result = JSON.parse(res['body']);
var cookieString = res['headers']['set-cookie'];
expect(result['success']).toBe(true);
request.get(
{
url: 'http://xxx/test/public/php/authentication/session.php',
headers: {
'Cookie': cookieString
}
}, function(err, res) {
var result = JSON.parse(res['body']);
expect(result.user.email).toBe(user.email);
done();
})
});
});
});
I'm seeing strange behavior when trying to add pdf file generation.
The following code, on the if statement, throws:
both\routes.js
Router.onBeforeAction(function () { if (!Meteor.user() || Meteor.loggingIn()) {
this.redirect('welcome.view'); } else {
Meteor.call("userFileDirectory", function (error, result) {
if (error)
throw error;
else
console.log(result);
});
this.next(); } }, { except: ['welcome.view'] });
Error: Meteor.userId can only be invoked in method calls. Use
this.userId in publish functions. at Object.Meteor.userId
(packages/accounts-base/accounts_server.js:19:1) at Object.Meteor.user
(packages/accounts-base/accounts_server.js:24:1) at [object
Object].Router.onBeforeAction.except
(app/both/3-router/routes.js:10:15) at
packages/iron:router/lib/router.js:277:1 at [object
Object]._.extend.withValue (packages/meteor/dynamics_nodejs.js:56:1)
at [object Object].hookWithOptions
(packages/iron:router/lib/router.js:276:1) at boundNext
(packages/iron:middleware-stack/lib/middleware_stack.js:251:1) at
runWithEnvironment (packages/meteor/dynamics_nodejs.js:108:1) at
packages/meteor/dynamics_nodejs.js:121:1 at [object Object].dispatch
(packages/iron:middleware-stack/lib/middleware_stack.js:275:1)
Only when I add this code into the file, and the /pdf route is taken:
Router.route('/pdf', function() {
var filePath = process.env.PWD + "/server/.files/users/test.pdf";
console.log(filePath);
var fs = Npm.require('fs');
var data = fs.readFileSync(filePath);
this.response.write(data);
this.response.end();
}, {
where: 'server'
});
The above code works fine; the pdf is rendered to the screen and no exception is thrown, when I take out the onBeforeAction code.
The opposite is also true, if I take out the server route, there is no route that causes an exception.
This occurs because the route you're using is a server side route. The technique Meteor uses to authenticate a user is done via the DDP protocol, over websockets.
When your browser makes a GET/POST request to the server it doesn't have any information regarding the user's authentication state.
You use Meteor.user() in your Route.onBeforeAction but it has no access to this information.
The solution to this is find an alternative way to authenticate the user. One such method is to use cookie's.
This is known issue with Meteor's authentication system, see: https://github.com/EventedMind/iron-router/issues/649
A better way than cookies could be a named collection of Meteor that stores userId and some sessionId:
You can store current userId on the client side before the call to the server:
var sessionId = Random.id();
col = new Mongo.Collection('session');
col.insert({
sessionId: sid,
userId: Meteor.userId(),
issued: new Date()
});
And then pass sessionId to the server through a GET/POST request and read it on the server:
var sid = this.request.query.sid;
var user = col.findOne({sessionId: sid}); // returns an object
Using a separate parameter is better than using userId itself because you can revoke this sessionId after some time or immediately after the server call.
Proper allow/deny permissions are required to prevent anyone from updating the collection. Also, please note that you can't trust new Date() on the client's side.