I'm would like to know how could i create a method, or if there is a method to generate a new token only with the email. I want create a option in my site "Send new verification email", where the user only needs to put the email. Actually i'm using Mandril, so i'm using a custom way to send emails and verify users:
function generateVerificationToken(context, user, callback) {
const { req } = context;
req.app.models.User.generateVerificationToken(user, (error, token) => {
if (error) {
return callback(error, null);
}
callback(null, token);
});
}
User.afterRemote('create', (context, user, next) => {
generateVerificationToken(context, user, (error, token) => {
if (error) {
return next(error);
}
user.verificationToken = token;
user.save((error) => {
if (error) {
return next(error);
}
loopback.Email.send({
to: user.email,
template: {
name: 'signup-confirm',
},
global_merge_vars: [{
name: 'href',
content:`http://localhost:3000/api/accounts/confirm?uid=${user.id}&token=${token}&redirect=http://localhost:4200/login/token-verification&verification=1`
}]
}, (error) => {
next(error);
});
});
});
});
Thanks in advance!
(Note: This question is a bit tricky because it involves several modifications that, although not that hard, might require some refactoring of your code. Also, see the warning note at the end please.)
1. Override the User model
(Note: Although you could do this in another model, I found it better to do it inside User for consistency's sake, even though there's a bit more to do.)
To override the User model, you can do two things. Some people like to add a new user model (in lowercase) and do the overriding there, but I personally prefer to use Spencer Mefford's more elegant way of doing it.
You should check the whole gist because there's a lot more going on, but to summarize a bit, you need to create a new boot script, ideally with a name starting with "0" (boot scripts are executed in alphabetical order and thus you need to have the model ready before the rest of the stuff), for example
server/boot/0-user-model-override.js
Then you add the necessary boilerplate:
module.exports = function (app) {
var User = app.models.User;
var Email = app.models.Email;
var Role = app.models.Role;
var RoleMapping = app.models.RoleMapping;
var ACL = app.models.ACL;
/*
* If this is an initial setup, create the ACL entry,
* otherwise just configure the relationships
* (this needs to be done every time the server is started)
*/
if(process.env.INIT_SETUP == "true"){
ACL.create({
model: 'User',
property: 'customEndpoint1',
accessType: 'EXECUTE',
principalType: 'ROLE',
principalId: '$everyone',
permission: 'ALLOW'
}, function (err, acl) { // Create the acl
if (err) console.error(err);
});
}
RoleMapping.belongsTo(User);
RoleMapping.belongsTo(Role);
User.hasMany(Role, {through: RoleMapping, foreignKey: 'principalId'});
User.hasMany(RoleMapping, {foreignKey: 'principalId'});
Role.hasMany(User, {through: RoleMapping, foreignKey: 'roleId'});
// Add your custom endpoints
User.customEndpoint1 = function(param1, cb) {...};
User.remoteMethod('customEndpoint1',...){...};
};
The boilerplate is basically there because we need to manually add an ACL entry that sets the permissions to allow anyone to request a new verification email. If this is not done, by default the User model denies access by non-authenticated users.
Also, note that we only create the ACL entry in the DB when we are doing the initial setup, i.e. we do it only once (unless you setup a new DB).
However we need to configure the relationships between the User, Role and RoleMapping every time we start the server, otherwise you will get access errors for valid users and other strange behaviors.
(Note: Although this is quite a bit of work, I think being able to somewhat easily add new functionality to the User model will allow you to keep user management where it belongs.)
After this setup you can now do e.g.
POST https://myserver/api/Users/newVerificationEmail
2. Create an endpoint to request the new link
To add an endpoint you do as you would with any other model:
User.newVerificationEmail = function(email, cb) {
console.log("A new verification email was requested for: " + email);
cb(null);
};
User.remoteMethod(
'newVerificationEmail',
{
accepts: [
{arg: 'email', type: 'string'}
],
http: {
verb: 'post'
}
}
);
3. Call the verify method and send the email
To send the verification email you have a few options. You can either:
Re-use the User.verify() method (located in the user.js model file) and the default SMTP emailer
Re-use the User.verify() method but with your own Emailer (to send via API for example)
Do everything by hand, i.e. generate the token yourself, saving it to the User collection and then sending the email, which is basically what User.verify() does. However this requires you to also write the confirmation logic which is yet more work.
User.verify() with default emailer
To re-use the verify method you need to generate the verify link (except the token part, which will be added by the method itself), configure the options, and call the method.
User.newVerificationEmail = function(email, cb) {
console.log("A new verification email was requested");
var userModel = User.constructor;
// Note: To get user.id you need to query the DB
// for the User instance with the requested email
var verifyLink = 'https://' +
hostAddress +
':' +
portNumber +
restApiRoot +
'/Users/confirm' +
'?uid=' +
user.id +
'&redirect=https://' + hostAddress + '/verified?user_id='+user.id;
var options = {
type: 'email',
mailer: Email,
to: user.email,
from: 'sender#example.com',
subject: 'My Email Subject',
template: path.resolve(__dirname, '../views/verify.ejs'),
user: user,
verifyHref: verifyLink,
host: myEmailHost,
port: myEmailPort
};
user.verify(options, function(err, response) {
if (err) {
console.log(err);
}
console.log("Account verification email sent to " + options.to);
cb(null);
});
};
Create the email verification template
The email that will be sent is the one specified in options.template, i.e. server/views/verify.ejs
This file should contain the verification link we generated again. You can add whatever HTML you want, just be sure to add the verifyHref variable:
Please click this link to verify your email
After these changes are done, this should send an email whenever you do a POST request to api/Users/newVerificationLink
User.verify() with custom emailer
I haven't yet finished implementing this solution, but it basically involves creating your own Email connector to use your provider's API (e.g. Mandrill, Mailgun, etc) and passing this model in the options.mailer field.
Warning: I haven't tested this code and there are several variable values that you need to specify yourself (e.g. hostAddress, portNumber, restApiRoot, etc). The code in this answer has been extracted from several pieces of a project I'm working on and although it's almost complete, you need to verify that there are no missing callbacks and other typos and compiler errors as well as provide the code to search the User object corresponding to the provided email (which is very easy to do).
Related
I am currently creating an application where I used MongoDB to store login data , nodejs for creating API and front end JS HTML to display the page. I am Creating user where i pass the email id and password via fetch ajax POST call to the backend node server. The backend api route creating the user in DB. I want after creating the user I want to redirect to a different page. How can i do that? her is my code snippet.
//front end fetch call
async function createUser(email , password) {
return fetch("http://localhost:3000/user",
{
// Adding method type
method: "POST",
// Adding body or contents to send
headers :{
"content-type": "application/json"
},
body: JSON.stringify({
"userId" : email.value,
"password" : password.value
})
});
}
//beckend code
app.post('/user', async (req, res) => {
// res.header("Access-Control-Allow-Origin", "*");
// res.header("Access-Control-Allow-Methods", "POST,OPTIONS");
const {error} = validate(req.body);
if (error) {
return res.status(400).send(error.details[0].message);
}
let user = await User.findOne({userId : req.body.userId});
if(user) return res.status(400).send("User already registered");
let newUser = new User (
{
userId : req.body.userId,
password : req.body.password
}
);
newUser = await newUser.save();
res.redirect('/logged');
//res.send(newUser)
//res.send("<h1>Hi</h1>");
//res.sendFile((path.join(__dirname+'/loggedon.html')));
//res.send("<h1>Hi</h1>");
}
);
app.get('/logged' , function(req, res){
res.send("<h1>Hi</h1>");
// res.setHeader(200 , 'Content-Type' , 'text/html');
// fs.readFile('./loggedon.html', function(err , data){
// res.write("<h1>Hi</h1>");
// })
});
I was checking sending only HI. but this will work fine in local server. but how will i send data to Front end. Please let me know. comment lines are the things I already tried.
res.redirect sends an HTTP redirect response which means "You asked for some data from this URL. You can get it from this URL instead".
Since you made the request with Ajax, the redirect will be followed, the new URL requested, and the result passed to the Ajax handler.
You haven't written a handler (or, if you have, you haven't bothered to include it in the question) so nothing happens.
Aside: It is important to note that it does not mean "Load the new URL in the main browser window".
The new URL will only be loaded in the main browser window if the request was intended to be loaded there in the first place.
Yours was not. It is an Ajax request.
You could have the Ajax handler assign a new value to location when the promise resolves.
Frankly, if you want to load a new page, I'd question the use of Ajax in the first place and just design the system to use a regular form submission with no client-side JS involved at all.
I am working on using sendgrid to send automated emails through my app. When I built out a custom dynamic template to handle various cases for automated emails, line breaks stopped working on the email body, or 'text', I am sending through to the API. Here is my process flow:
When a user signs up for the app, the my /signup api route determines the type of email to send the user:
const msg = await getEmailType('signup', user)
The awaited getEmailType function returns the following JSON object depending on the case - in my example, the case is 'signup':
const getEmailType = async (emailType, user, token, req) => {
switch (emailType) {
case 'signup':
return {
to: user.email,
from: 'no-reply#app-address.com',
templateId: templates.default,
dynamic_template_data: {
subject: 'Welcome to our app!',
name: user.firstName,
text:
' Here is the email body with some line breaks. Confirm your account at the link
below:\r\n' +
' http://app-address.herokuapp.com/\r\n'
}
}
The line breaks in 'text', which is supposed to be my email body, are not being applied properly within the actual email template.
The JSON object is sent through sendgrid as follows and then formatted as an email to send to the user (I've also included some dependencies at the top here for some context in case anyone has used sendgrid):
const sgMail = require('#sendgrid/mail')
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
const sendEmail = async msg => {
try {
const response = await sgMail.send(msg)
console.log('Response', response)
return response
} catch (error) {
console.error(error)
}
}
The template I've created looks nice and works, but for some reason, since the actual email sent to the user is returning HTML generated by the WYSIWYG editor, the line breaks do not work as a result of a deprecated setting in sendgrid's api.
There also doesn't seem to be any updated resources for this issue, so I'm wondering if anyone else has encountered this before and can help.
I'm currently working on a angular + sails project. I'm using json web tokens for auth. It works fine but I wanna set a new token for every validated request that my angular app does.
This is my auth policy
passport.authenticate('jwt', function (error, user, info) {
if (error) return res.serverError(error);
if (!user)
return res.send({
message: info.message,
code: info.code,
tokenError: info.name
});
// The token is ok past this line
// I check the user again
User.findOne({ email: user.email }, function (err, thisUser) {
if (err) { return res.send(err); }
if (!thisUser) {
// send a bad response
}
req.user = user;
// This is the new token that I wanna send to the frontend
var newToken = AuthService.createToken(thisUser);
next();
});
})(req, res);
With this policy I can create the new token, but then I would need a way to include this token in every response, this Is the point where I'm stuck.
I gues I could do it manually in every controller action, but this is want I want to avoid
The best way to standardize your responses in Sails is to use the custom responses feature. In short, instead of calling res.send() or res.json() in your controller actions, call res.ok() instead, and then customize the api/responses/ok.js file that is generated with every new Sails app. This is the same response that Sails blueprints use as well!
In your case, you'd want to save the token onto the request object (e.g. req.token) in your policy code, then use that property in your logic inside of ok.js.
I have a REST endpoint that is being submitted with a JSON object (User) and I just set the corresponding mongo record to that JSON object. This saves me the trouble of updating schema changes in the service method and the endpoint leaving just the Mongoose model to update.
What would be a more secure way of doing this, if any?
Example User JSON
{
'fname': 'Bill',
'lname': 'Williams',
'email': 'bill#billwilliams.com',
'settings': {
'strokeColor': '#FF0000'
}
}
From my Angular service
Update: function(my_user) {
return $http.put('http://api.domain.com/v1/api/users/' + _user.id, {
user: my_user,
token: window.localStorage['token']
});
}
My REST endpoint in Node
api.route('/users/:user_id')
.put(function(req, res) {
User.findById(req.params.user_id, function(err, user) {
userData = req.body.user;
if (user) {
//-- This is potential trouble area?
User.update({'_id': user._id}, {$set: userData});
user.save(function(err) {
res.json({
success: true,
message: 'User updated'
});
}); //-- end findById()
}); //-- end /users/:user_id put() route
Have a look at Jsonwebtoken.
It basically works like this:
Create a REST endpoint that lets users aquire a token with a certain
payload (id for example)
Secure the relevant part of your api with the Jsonwebtoken middleware (if using express as the webserver)
User adds the token to every request header (by using $httpInterceptor)
Token is checked on the server side before the request reaches your API
Tokens may expire after a certain time (useful when users needs to register first) which adds additional security.
I am trying to initialize a customer for the first time. I have a form where they sign up and everything, and they submit it. On the client, the following happens:
var cardValues = AutoForm.getFormValues('credit-card-form').insertDoc;
Stripe.createToken(cardValues, function (err, token) {
if (!err && token) {
Meteor.call('Stripe.initializeCustomer', token);
}
});
On the serverside, I am trying to do something like this:
Meteor.methods({
'Stripe.initializeCustomer': function (token) {
var Stripe = StripeAPI(process.env.STRIPE_KEY);
// some validation here that nobody cares about
Stripe.customers.create({
source: token
}).then(function (customer) {
return Stripe.customers.createCard(customer.id, {
source: token
})
}).catch(function (error) {
// need to do something here
})
}
});
It would seem that the Stripe API doesn't like this
Unhandled rejection Error: You cannot use a Stripe token more than once
Is there a canonical way to make multiple requests to stripe on the server for a single token?
It seems that you're running into this issue because you're accidentally trying to reuse a token to create a new card for a customer when, unbeknownst to you, you've already used that token to create that card for that user. Creating a customer with a stored card is actually much easier than you expect: when you initialize a customer object with a token, the Stripe API goes ahead and stores that card in association with the new customer. That is, you can immediately go ahead and make a charge to your customer upon creation as in:
Stripe.customers.create({
source: token.id
}).then(function (customer) {
Stripe.charge.create({
amount: 1000,
currency: 'usd',
customer: customer.id
});
});
For more information, I'd recommend the Stripe docs at https://support.stripe.com/questions/can-i-save-a-card-and-charge-it-later and https://stripe.com/docs/api/node#create_customer.
Let me know if that solves your problem!