Meteor: Accounts.sendVerificationEmail customising behavior - javascript

Can someone please provide the correct method to send an email verification upon user creation? This is the important part...
a) I would like the user to have immediate access upon signing up. But if the user has not yet clicked clicked on the verification link within 48 hours, I would like to deny them logging in until they have clicked on the link.
My code so far sends an email verification but the user has continuos access to the application with or without clicking on the verification link (so my code is of course incomplete).
client.js
Template.join.events({
'submit #join-form': function(e,t){
e.preventDefault();
var firstName= t.find('#join-firstName').value,
lastName= t.find('#join-lastName').value,
email = t.find('#join-email').value,
password = t.find('#join-password').value,
username = firstName.substring(0) + '.' + lastName.substring(0),
profile = {
fullname: firstName + ' ' + lastName
};
Accounts.createUser({
email: email,
username: username,
password: password,
userType: // 'reader' or 'publisher'
createdAt: new Date(),
profile: profile
}, function(error) {
if (error) {
alert(error);
} else {
Router.go('home');
}
});
}
});
server.js
Meteor.startup(function () {
process.env.MAIL_URL = 'smtp://postmaster.....';
Accounts.emailTemplates.from = "no-reply#mydomain.com";
Accounts.emailTemplates.sitename = "My SIte Name";
Accounts.emailTemplates.verifyEmail.subject = function(user) {
return 'Please confirm tour Email address' ;
},
Accounts.emailTemplates.verifyEmail.text = function(user, url) {
return 'Click on the link below to verify your address: ' + url;
}
Accounts.config({
sendVerificationEmail: true
});
My attempt have been made through own readings on meteor docs and looking at other code on SO. I am stuck guys. Thanks for the support.

I think the basic idea is to have some validation code eg in Accounts.validateLoginAttempt which you want to check every time before user logs in. What you can do is to store the date&time when user signs up in user.profile.joinDate. If a user tries to login
Check if the email address has been verified or
check if the user is logging within the grace period of 48 hrs
isWithinGracePeriod = function(user) {
** TBD returning true or false.
This can be tricky when you
have multiple instances in
different time-zones.
** }
and
Accounts.validateLoginAttempt(function(attempt){
if (attempt.user && attempt.user.emails && !attempt.user.emails[0].verified ) {
console.log('No verification action received yet.');
return isWithinGracePeriod(attempt.user);
}
return true;
});
Further, here is the HTML/spacebars stuff:
<body>
{{ > start }}
</body>
<template name="start">
{{#if currentUser}}{{>showUserProfile}}{{else}}{{> login}}{{/if}}
</template>
<template name="login">
## Grab username/password here
</template>
If the login template is created, we can try to capture the verification code after the user clicked the verification link. Note that, if no user is logged in, then login will be rendered, so we attach to login via
Template.login.created = function() {
if (Accounts._verifyEmailToken) {
Accounts.verifyEmail(Accounts._verifyEmailToken, function(err) {
if (err != null) {
if (err.message = 'Verify email link expired [403]') {
var message ='Sorry this verification link has expired.';
console.log(message);
alertBox = Blaze.renderWithData(Template.Alert, {message: message}, $("body").get(0));
}
} else {
var message = "Thank you! Your email address has been confirmed.";
console.log(message);
alertBox = Blaze.renderWithData(Template.Alert, {message: message}, $("body").get(0));
}
});
}
};
The verification link is send in "hook" to Accounts.createUser:
Accounts.onCreateUser(function(options, user) {
user.profile = {};
Meteor.setTimeout(function() {
Accounts.sendVerificationEmail(user._id);
}, 2 * 3000);
return user;
});

Related

How to setup AWS Cognito TOTP MFA?

I am trying to setup MFA authentication using AWS Cognito as a small proof of concept for a work project. I have managed to get username & password with a MFA code sent via SMS working fine.
Struggling to get the TOTP method which is shown in use case 27 working with my small login app - https://www.npmjs.com/package/amazon-cognito-identity-js
i have modified the associateSecretCode so that it should show me a the secret code to then enter into my authenticator app but this doesnt display when i attempt to login with a valid user.
What am i doing wrong?
Heres my code:
<body>
<form>
<ul class="form-style-1">
<li><label>UserID <span class="required">*</span></label><input type="text" name="username" class="field-divided" placeholder="UID" /></li>
<li><label>Password <span class="required">*</span></label><input type="password" name="password" class="field-divided" placeholder="Password" /></li>
<li>
<input type="submit" value="Submit" />
</li>
</ul>
</form>
<div id="results" class="form-style-1"></div>
</body>
</html>
<script type="text/javascript">
//var dataResult;
$(document).ready(function() {
$('form').submit(function(event) {
//-------------------user pool
AWSCognito.config.region = 'eu-west-2';
var poolData = {
UserPoolId : 'user pool id here',
ClientId : 'app client id here'
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
//------------------Authentication-------------------------
var userData = {
Username : $('input[name=username]').val(), // your username here
Pool : userPool
};
var authenticationData = {
Username : $('input[name=username]').val(), // your username here
Password : $('input[name=password]').val(), // your password here
};
var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function(result) {
console.log('OnSuccess')
var accessToken = result.getAccessToken().getJwtToken();
cognitoUser.associateSoftwareToken(this);
},
onFailure: function(err) {
console.log('onFailure')
alert(err.message || JSON.stringify(err));
},
mfaSetup: function(challengeName, challengeParameters) {
console.log('mfaSetup')
cognitoUser.associateSoftwareToken(this);
},
associateSecretCode: async secretCode => {
console.log("SECRET CODE: ", secretCode);
$('#results').html(secretCode);
setTimeout(() => {
const challengeAnswer = prompt("Please input the TOTP code.", "");
cognitoUser.verifySoftwareToken(challengeAnswer, "My TOTP device", {
onSuccess: session => console.log("SUCCESS TOTP: ", session),
onFailure: err => console.error("ERROR TOTP: ", err)
});
}, 2000);
},
selectMFAType: function(challengeName, challengeParameters) {
console.log('selectMFAType')
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) {
console.log('totpRequired')
var challengeAnswer = prompt('Please input the TOTP code.', '');
cognitoUser.sendMFACode(challengeAnswer, this, 'SOFTWARE_TOKEN_MFA');
},
mfaRequired: function(codeDeliveryDetails) {
console.log('mfaRequired')
var verificationCode = prompt('Please input verification code', '');
cognitoUser.sendMFACode(verificationCode, this);
}
});
});
});
</script>
The best solution I've found is to use Amplify UI components. You don't need to take all the rest of Amplify, you can just grab the two relevant JS libraries, import, configure, and then wrap the page you need in the withAuthenticator HOC. The default setup handles both registration and challenge with sofware and SMS TOTP, as well as forgot password and create account flows. All the error handling you'd expect is included, and even language/theme customization and localization. It's very comprehensive.
While it was painful to figure out, the steps are actually not at all complicated, assuming you already have a Cognito User Pool set up. (Note: Amplify requires a User Pool client which does not use a client secret.)
You can find sample code in my answer to a similar StackOverflow question.

Why Facebook Login API don't ask email access?

Guys I have a custom button which trigger an FB.login() event but I can't get email info, facebook don't ask an email access to user and return name, id fields
FB.login(function(response) {
console.log(response)
if (response.authResponse) {
FB.api('/me?fields=id,name,email', function(data) {
console.log(data)
if ((typeof data.email === 'undefined') ||(data.email == '') || (data.email == null)) {
M.toast({html: "You dont't give an email access, we are can't register you without email. Sorry :("});
return false;
}
window.flags.signin = {
status: true,
name_surname: data.name,
email: data.email,
login_type: 'facebook',
id: data.id
}
// back-end request func
window.flags.submitFunc('veriler='+JSON.stringify(window.flags.signin));
}, { scope: 'id,name,email'});
}
else {
M.toast({html: 'User cancelled login or did not fully authorize.'});
}
});
Repair it,
I change this line
{ scope: 'id,name,email'}
to
{ scope: 'email'}
and it's started ask to email access

How to send submitted forms in ionic apps to email?

I have an Ionic app and contact form page (with Name, Email, and Phone).
After the user clicks the Submit button, I want this form data to be sent to my E-mail. How do I do this?
You'd need to setup some kind of REST api, so that when the user clicks on the submit button, the data in the contact form is sent to the REST api you've set up, which will trigger it to send an email to you with the contents of the user's message.
Since you've tagged this with Node.JS, I would suggest that you have the contact form's action be send to something like 'http://yoursite.com/sendemail/' and then your API would handle the call with something like:
router.route('/sendemail/')
.post(function(req, res) {
var userInput = req.body;
var message = {
text: userInput.message,
from: userInput.name + ' <' + userInput.email + '>',
to: 'youremail#email.com',
subject: userInput.subject,
attachment:
[
{data: this.text, alternative:true},
]
};
server.send(message, function(err, message) {
if(err) {
res.status(400);
console.log(err);
} else {
res.status(200)
}
});
});
(you'll need to change some variables to fit your code)
Hope this helps!

Meteor.loginWithFacebook not storing email address

I'm using
accounts-password
accounts-facebook
service-configuration
On the server:
ServiceConfiguration.configurations.remove({
service: 'facebook'
});
ServiceConfiguration.configurations.upsert(
{ service: 'facebook' },
{ $set: {
appId: 'xxxxxxxxxxxxxxxx',
secret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
}
}
);
On the client:
Meteor.loginWithFacebook({requestPermissions: ['email']}, function(error){
if (error) {
throwError('Could not log in');
} else {
// success
}
});
This configuration prompts the user for Facebook verification with access to email and returns no errors. A new user is stores with the correct name and ID. But this e-mail is not stored in the user object.
This is what I get when i fetch a user from the shell.
{ _id: 'xxxxxxxxxxxxxxxxx',
createdAt: Mon Jul 13 2015 13:36:21 GMT+0200 (CEST),
services:
{ facebook:
{ accessToken: 'xxxxxxxxxxxxxxxxxxxxx...',
expiresAt: 1441971380621,
id: 'xxxxxxxxxxxxxxxxx',
name: 'xxxx xxxxxx' },
resume: { loginTokens: [Object] } },
profile: { name: 'xxxx xxxxxx' } }
Why is the email address from Facebook not being stored?
While I have reported the issue to Meteor I've found a quick fix for the time being.
On the server run this:
Accounts.onCreateUser(function(options, user) {
if (user.hasOwnProperty('services') && user.services.hasOwnProperty('facebook') ) {
var fb = user.services.facebook;
var result = Meteor.http.get('https://graph.facebook.com/v2.4/' + fb.id + '?access_token=' + fb.accessToken + '&fields=name,email');
if (!result.error) {
_.extend(user, {
"emails": [{"address": result.data.email, "verified": false}],
"profile": {"name": result.data.name}
});
}
}
return user;
});
[EDIT]
The previous code works, but since it causes problems with other login methods I went with another approach:
In the client I call a function on the server when the user authenticates with Facebook:
Meteor.loginWithFacebook({requestPermissions: ['email']}, function(error){
if (error) {
//error
} else {
Meteor.call('fbAddEmail');
}
});
And then on the server:
Meteor.startup(function () {
Meteor.methods({
fbAddEmail: function() {
var user = Meteor.user();
if (user.hasOwnProperty('services') && user.services.hasOwnProperty('facebook') ) {
var fb = user.services.facebook;
var result = Meteor.http.get('https://graph.facebook.com/v2.4/' + fb.id + '?access_token=' + fb.accessToken + '&fields=name,email');
if (!result.error) {
Meteor.users.update({_id: user._id}, {
$addToSet: { "emails": {
'address': result.data.email,
'verified': false
}}
});
}
}
}
});
});
Facebook API may not return the email address for some users even if you asked for the "email" permission. The official API docs state that:
[email] field will not be returned if no valid email address is available.
One of the reason may be an unconfirmed email address and another one a user who registered with a mobile phone number only.

How to show error state for wrong password on Firebase Simple Login email password

I'm using Firebase's Simple Login as an admin login for a blog format site. The correct email and pw combo gives write access to the db on Firebase. Following the documentation I have created separate chunks.
The auth var:
var chatRef = new Firebase('https://fiery-fire-291.firebaseio.com/');
var auth = new FirebaseSimpleLogin(chatRef, function(error, user) {
if (error) {
// an error occurred while attempting login
console.log(error);
} else if (user) {
// user authenticated with Firebase
console.log('User ID: ' + user.id + ', Provider: ' + user.provider);
} else {
// user is logged out
}
});
The auth login, which I've wrapped in a login controller:
app.controller('LoginCtrl', function ($scope) {
$scope.login = function() {
auth.login('password', {
email: $scope.loginEmail,
password: $scope.loginPassword,
debug: true
});
};
});
Which gets the data from the login form:
<div ng-controller="LoginCtrl">
<form ng-submit="login()">
<fieldset ng-class="">
<input type="email" ng-model="loginEmail">
<input type="password" ng-model="loginPassword">
</fieldset>
<button type="submit" href="#">Login</button>
</form>
</div>
What would be the correct/best way of setting the ng-class to error for the login form to show the user when their Firebase login has errored?
I feel like I shouldn't set the CSS in Angular (which I could easily do in the error callback). I've tried setting a global var in the callbacks which would be picked up by
ng-class="{error: !userAuthenticated}"
but apart from not working, I feel this is also wrong.
The simplest method here is to write the error to $scope within the callback. You'll need to alert Angular to start a compile by calling $apply or $timeout:
var auth = new FirebaseSimpleLogin(chatRef, function(error, user) {
$timeout(function() {
if (error) {
$scope.error = error;
} else if (user) {
$scope.error = null;
} else {
$scope.error = 'user is logged out';
}
});
});
Display it in your page:
<p class="error" ng-show="error">{{error}}</p>
You can save yourself a good deal of effort by utilizing the in-place tools for Angular + Firebase development: Angular+Firebase Quick Start, angularFire library, angularfire-seed
The angularFire-seed contains a complete, functional login example, including error handling, which you can reference as well.
I am not all that familiar with Angular at all but having looked at the Firebase documentation I believe the callback is exactly were you are meant to do this, it's what it's there for. From the docs:
auth.createUser(email, password, function(error, user) {
if (!error) {
console.log('User Id: ' + user.id + ', Email: ' + user.email);
}
});
If you want to keep out the CSS itself from the controllers then I advise having a look at this snippet on Coderwall that goes into how the ng-class can be set not just via a classname but by an object + expression. You could then set the given boolean controlling the ng-class as required in the error callback and your angular template will update accordingly yet you've not "mixed in" the class setting to your controller code.

Categories