Allows very simple mail adapters
- Fix nasty bug when updating users email and sending verification
This commit is contained in:
@@ -150,10 +150,67 @@ describe("Email Verification", () => {
|
|||||||
user.set("email", "cool_guy@parse.com");
|
user.set("email", "cool_guy@parse.com");
|
||||||
return user.save();
|
return user.save();
|
||||||
}).then((user) => {
|
}).then((user) => {
|
||||||
expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled();
|
|
||||||
return user.fetch();
|
return user.fetch();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
expect(user.get('emailVerified')).toEqual(false);
|
expect(user.get('emailVerified')).toEqual(false);
|
||||||
|
// Wait as on update emai, we need to fetch the username
|
||||||
|
setTimeout(function(){
|
||||||
|
expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled();
|
||||||
|
done();
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function(userAgain, error) {
|
||||||
|
fail('Failed to save user');
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does send with a simple adapter', done => {
|
||||||
|
var calls = 0;
|
||||||
|
var emailAdapter = {
|
||||||
|
sendMail: function(options){
|
||||||
|
expect(options.to).toBe('cool_guy@parse.com');
|
||||||
|
if (calls == 0) {
|
||||||
|
expect(options.subject).toEqual('Please verify your e-mail for My Cool App');
|
||||||
|
expect(options.text.match(/verify_email/)).not.toBe(null);
|
||||||
|
} else if (calls == 1) {
|
||||||
|
expect(options.subject).toEqual('Password Reset for My Cool App');
|
||||||
|
expect(options.text.match(/request_password_reset/)).not.toBe(null);
|
||||||
|
}
|
||||||
|
calls++;
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setServerConfiguration({
|
||||||
|
serverURL: 'http://localhost:8378/1',
|
||||||
|
appId: 'test',
|
||||||
|
appName: 'My Cool App',
|
||||||
|
javascriptKey: 'test',
|
||||||
|
dotNetKey: 'windows',
|
||||||
|
clientKey: 'client',
|
||||||
|
restAPIKey: 'rest',
|
||||||
|
masterKey: 'test',
|
||||||
|
collectionPrefix: 'test_',
|
||||||
|
fileKey: 'test',
|
||||||
|
verifyUserEmails: true,
|
||||||
|
emailAdapter: emailAdapter,
|
||||||
|
});
|
||||||
|
var user = new Parse.User();
|
||||||
|
user.setPassword("asdf");
|
||||||
|
user.setUsername("zxcv");
|
||||||
|
user.set("email", "cool_guy@parse.com");
|
||||||
|
user.signUp(null, {
|
||||||
|
success: function(user) {
|
||||||
|
expect(calls).toBe(1);
|
||||||
|
user.fetch()
|
||||||
|
.then((user) => {
|
||||||
|
return user.save();
|
||||||
|
}).then((user) => {
|
||||||
|
return Parse.User.requestPasswordReset("cool_guy@parse.com");
|
||||||
|
}).then(() => {
|
||||||
|
expect(calls).toBe(2);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,23 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
Mail Adapter prototype
|
||||||
|
A MailAdapter should implement at least sendMail()
|
||||||
|
*/
|
||||||
export class MailAdapter {
|
export class MailAdapter {
|
||||||
sendVerificationEmail(options) {}
|
/*
|
||||||
sendPasswordResetEmail(options) {}
|
* A method for sending mail
|
||||||
|
* @param options would have the parameters
|
||||||
|
* - to: the recipient
|
||||||
|
* - text: the raw text of the message
|
||||||
|
* - subject: the subject of the email
|
||||||
|
*/
|
||||||
sendMail(options) {}
|
sendMail(options) {}
|
||||||
|
|
||||||
|
/* You can implement those methods if you want
|
||||||
|
* to provide HTML templates etc...
|
||||||
|
*/
|
||||||
|
// sendVerificationEmail({ link, appName, user }) {}
|
||||||
|
// sendPasswordResetEmail({ link, appName, user }) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MailAdapter;
|
export default MailAdapter;
|
||||||
|
|||||||
@@ -25,31 +25,6 @@ let SimpleMailgunAdapter = mailgunOptions => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Object.freeze({
|
return Object.freeze({
|
||||||
sendVerificationEmail: ({ link, user, appName, }) => {
|
|
||||||
let verifyMessage =
|
|
||||||
"Hi,\n\n" +
|
|
||||||
"You are being asked to confirm the e-mail address " + user.email + " with " + appName + "\n\n" +
|
|
||||||
"" +
|
|
||||||
"Click here to confirm it:\n" + link;
|
|
||||||
return sendMail({
|
|
||||||
to:user.email,
|
|
||||||
subject: 'Please verify your e-mail for ' + appName,
|
|
||||||
text: verifyMessage
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
sendPasswordResetEmail: ({link,user, appName}) => {
|
|
||||||
let message =
|
|
||||||
"Hi,\n\n" +
|
|
||||||
"You requested to reset your password for " + appName + ".\n\n" +
|
|
||||||
"" +
|
|
||||||
"Click here to reset it:\n" + link;
|
|
||||||
return sendMail({
|
|
||||||
to:user.email,
|
|
||||||
subject: 'Password Reset for ' + appName,
|
|
||||||
text: message
|
|
||||||
});
|
|
||||||
},
|
|
||||||
sendMail: sendMail
|
sendMail: sendMail
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import MailAdapter from '../Adapters/Email/MailAdapter';
|
|||||||
|
|
||||||
var DatabaseAdapter = require('../DatabaseAdapter');
|
var DatabaseAdapter = require('../DatabaseAdapter');
|
||||||
var RestWrite = require('../RestWrite');
|
var RestWrite = require('../RestWrite');
|
||||||
|
var RestQuery = require('../RestQuery');
|
||||||
var hash = require('../password').hash;
|
var hash = require('../password').hash;
|
||||||
var Auth = require('../Auth');
|
var Auth = require('../Auth');
|
||||||
|
|
||||||
@@ -84,20 +85,47 @@ export class UserController extends AdaptableController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUserIfNeeded(user) {
|
||||||
|
if (user.username && user.email) {
|
||||||
|
return Promise.resolve(user);
|
||||||
|
}
|
||||||
|
var where = {};
|
||||||
|
if (user.username) {
|
||||||
|
where.username = user.username;
|
||||||
|
}
|
||||||
|
if (user.email) {
|
||||||
|
where.email = user.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = new RestQuery(this.config, Auth.master(this.config), '_User', where);
|
||||||
|
return query.execute().then(function(result){
|
||||||
|
if (result.results.length != 1) {
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
return result.results[0];
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
sendVerificationEmail(user) {
|
sendVerificationEmail(user) {
|
||||||
if (!this.shouldVerifyEmails) {
|
if (!this.shouldVerifyEmails) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// We may need to fetch the user in case of update email
|
||||||
const token = encodeURIComponent(user._email_verify_token);
|
this.getUserIfNeeded(user).then((user) => {
|
||||||
const username = encodeURIComponent(user.username);
|
const token = encodeURIComponent(user._email_verify_token);
|
||||||
|
const username = encodeURIComponent(user.username);
|
||||||
let link = `${this.config.verifyEmailURL}?token=${token}&username=${username}`;
|
let link = `${this.config.verifyEmailURL}?token=${token}&username=${username}`;
|
||||||
this.adapter.sendVerificationEmail({
|
let options = {
|
||||||
appName: this.config.appName,
|
appName: this.config.appName,
|
||||||
link: link,
|
link: link,
|
||||||
user: inflate('_User', user),
|
user: inflate('_User', user),
|
||||||
|
};
|
||||||
|
if (this.adapter.sendVerificationEmail) {
|
||||||
|
this.adapter.sendVerificationEmail(options);
|
||||||
|
} else {
|
||||||
|
this.adapter.sendMail(this.defaultVerificationEmail(options));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,11 +162,23 @@ export class UserController extends AdaptableController {
|
|||||||
const token = encodeURIComponent(user._perishable_token);
|
const token = encodeURIComponent(user._perishable_token);
|
||||||
const username = encodeURIComponent(user.username);
|
const username = encodeURIComponent(user.username);
|
||||||
let link = `${this.config.requestResetPasswordURL}?token=${token}&username=${username}`
|
let link = `${this.config.requestResetPasswordURL}?token=${token}&username=${username}`
|
||||||
this.adapter.sendPasswordResetEmail({
|
|
||||||
appName: this.config.appName,
|
if (!user.username) {
|
||||||
link: link,
|
console.log('No username...');
|
||||||
user: inflate('_User', user),
|
}
|
||||||
});
|
|
||||||
|
let options = {
|
||||||
|
appName: this.config.appName,
|
||||||
|
link: link,
|
||||||
|
user: inflate('_User', user),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.adapter.sendPasswordResetEmail) {
|
||||||
|
this.adapter.sendPasswordResetEmail(options);
|
||||||
|
} else {
|
||||||
|
this.adapter.sendMail(this.defaultResetPasswordEmail(options));
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.resolve(user);
|
return Promise.resolve(user);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -148,6 +188,26 @@ export class UserController extends AdaptableController {
|
|||||||
return updateUserPassword(username, token, password, this.config);
|
return updateUserPassword(username, token, password, this.config);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defaultVerificationEmail({link, user, appName, }) {
|
||||||
|
let text = "Hi,\n\n" +
|
||||||
|
"You are being asked to confirm the e-mail address " + user.email + " with " + appName + "\n\n" +
|
||||||
|
"" +
|
||||||
|
"Click here to confirm it:\n" + link;
|
||||||
|
let to = user.get("email");
|
||||||
|
let subject = 'Please verify your e-mail for ' + appName;
|
||||||
|
return { text, to, subject };
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultResetPasswordEmail({link, user, appName, }) {
|
||||||
|
let text = "Hi,\n\n" +
|
||||||
|
"You requested to reset your password for " + appName + ".\n\n" +
|
||||||
|
"" +
|
||||||
|
"Click here to reset it:\n" + link;
|
||||||
|
let to = user.get("email");
|
||||||
|
let subject = 'Password Reset for ' + appName;
|
||||||
|
return { text, to, subject };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark this private
|
// Mark this private
|
||||||
|
|||||||
Reference in New Issue
Block a user