@@ -1,15 +1,14 @@
|
||||
import { randomString } from '../cryptoUtils';
|
||||
import { inflate } from '../triggers';
|
||||
import { randomString } from '../cryptoUtils';
|
||||
import { inflate } from '../triggers';
|
||||
import AdaptableController from './AdaptableController';
|
||||
import MailAdapter from '../Adapters/Email/MailAdapter';
|
||||
import rest from '../rest';
|
||||
import Parse from 'parse/node';
|
||||
import MailAdapter from '../Adapters/Email/MailAdapter';
|
||||
import rest from '../rest';
|
||||
import Parse from 'parse/node';
|
||||
|
||||
var RestQuery = require('../RestQuery');
|
||||
var Auth = require('../Auth');
|
||||
|
||||
export class UserController extends AdaptableController {
|
||||
|
||||
constructor(adapter, appId, options = {}) {
|
||||
super(adapter, appId, options);
|
||||
}
|
||||
@@ -36,7 +35,9 @@ export class UserController extends AdaptableController {
|
||||
user.emailVerified = false;
|
||||
|
||||
if (this.config.emailVerifyTokenValidityDuration) {
|
||||
user._email_verify_token_expires_at = Parse._encode(this.config.generateEmailVerifyTokenExpiresAt());
|
||||
user._email_verify_token_expires_at = Parse._encode(
|
||||
this.config.generateEmailVerifyTokenExpiresAt()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,8 +49,11 @@ export class UserController extends AdaptableController {
|
||||
throw undefined;
|
||||
}
|
||||
|
||||
const query = {username: username, _email_verify_token: token};
|
||||
const updateFields = { emailVerified: true, _email_verify_token: {__op: 'Delete'}};
|
||||
const query = { username: username, _email_verify_token: token };
|
||||
const updateFields = {
|
||||
emailVerified: true,
|
||||
_email_verify_token: { __op: 'Delete' },
|
||||
};
|
||||
|
||||
// if the email verify token needs to be validated then
|
||||
// add additional query params and additional fields that need to be updated
|
||||
@@ -57,10 +61,15 @@ export class UserController extends AdaptableController {
|
||||
query.emailVerified = false;
|
||||
query._email_verify_token_expires_at = { $gt: Parse._encode(new Date()) };
|
||||
|
||||
updateFields._email_verify_token_expires_at = {__op: 'Delete'};
|
||||
updateFields._email_verify_token_expires_at = { __op: 'Delete' };
|
||||
}
|
||||
const masterAuth = Auth.master(this.config);
|
||||
var checkIfAlreadyVerified = new RestQuery(this.config, Auth.master(this.config), '_User', {username: username, emailVerified: true});
|
||||
var checkIfAlreadyVerified = new RestQuery(
|
||||
this.config,
|
||||
Auth.master(this.config),
|
||||
'_User',
|
||||
{ username: username, emailVerified: true }
|
||||
);
|
||||
return checkIfAlreadyVerified.execute().then(result => {
|
||||
if (result.results.length) {
|
||||
return Promise.resolve(result.results.length[0]);
|
||||
@@ -70,25 +79,34 @@ export class UserController extends AdaptableController {
|
||||
}
|
||||
|
||||
checkResetTokenValidity(username, token) {
|
||||
return this.config.database.find('_User', {
|
||||
username: username,
|
||||
_perishable_token: token
|
||||
}, {limit: 1}).then(results => {
|
||||
if (results.length != 1) {
|
||||
throw undefined;
|
||||
}
|
||||
|
||||
if (this.config.passwordPolicy && this.config.passwordPolicy.resetTokenValidityDuration) {
|
||||
let expiresDate = results[0]._perishable_token_expires_at;
|
||||
if (expiresDate && expiresDate.__type == 'Date') {
|
||||
expiresDate = new Date(expiresDate.iso);
|
||||
return this.config.database
|
||||
.find(
|
||||
'_User',
|
||||
{
|
||||
username: username,
|
||||
_perishable_token: token,
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
.then(results => {
|
||||
if (results.length != 1) {
|
||||
throw undefined;
|
||||
}
|
||||
if (expiresDate < new Date())
|
||||
throw 'The password reset link has expired';
|
||||
}
|
||||
|
||||
return results[0];
|
||||
});
|
||||
if (
|
||||
this.config.passwordPolicy &&
|
||||
this.config.passwordPolicy.resetTokenValidityDuration
|
||||
) {
|
||||
let expiresDate = results[0]._perishable_token_expires_at;
|
||||
if (expiresDate && expiresDate.__type == 'Date') {
|
||||
expiresDate = new Date(expiresDate.iso);
|
||||
}
|
||||
if (expiresDate < new Date())
|
||||
throw 'The password reset link has expired';
|
||||
}
|
||||
|
||||
return results[0];
|
||||
});
|
||||
}
|
||||
|
||||
getUserIfNeeded(user) {
|
||||
@@ -103,13 +121,18 @@ export class UserController extends AdaptableController {
|
||||
where.email = user.email;
|
||||
}
|
||||
|
||||
var query = new RestQuery(this.config, Auth.master(this.config), '_User', where);
|
||||
return query.execute().then(function(result){
|
||||
var query = new RestQuery(
|
||||
this.config,
|
||||
Auth.master(this.config),
|
||||
'_User',
|
||||
where
|
||||
);
|
||||
return query.execute().then(function(result) {
|
||||
if (result.results.length != 1) {
|
||||
throw undefined;
|
||||
}
|
||||
return result.results[0];
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
sendVerificationEmail(user) {
|
||||
@@ -118,10 +141,15 @@ export class UserController extends AdaptableController {
|
||||
}
|
||||
const token = encodeURIComponent(user._email_verify_token);
|
||||
// We may need to fetch the user in case of update email
|
||||
this.getUserIfNeeded(user).then((user) => {
|
||||
this.getUserIfNeeded(user).then(user => {
|
||||
const username = encodeURIComponent(user.username);
|
||||
|
||||
const link = buildEmailLink(this.config.verifyEmailURL, username, token, this.config);
|
||||
const link = buildEmailLink(
|
||||
this.config.verifyEmailURL,
|
||||
username,
|
||||
token,
|
||||
this.config
|
||||
);
|
||||
const options = {
|
||||
appName: this.config.appName,
|
||||
link: link,
|
||||
@@ -143,11 +171,15 @@ export class UserController extends AdaptableController {
|
||||
*/
|
||||
regenerateEmailVerifyToken(user) {
|
||||
this.setEmailVerifyToken(user);
|
||||
return this.config.database.update('_User', { username: user.username }, user);
|
||||
return this.config.database.update(
|
||||
'_User',
|
||||
{ username: user.username },
|
||||
user
|
||||
);
|
||||
}
|
||||
|
||||
resendVerificationEmail(username) {
|
||||
return this.getUserIfNeeded({username: username}).then((aUser) => {
|
||||
return this.getUserIfNeeded({ username: username }).then(aUser => {
|
||||
if (!aUser || aUser.emailVerified) {
|
||||
throw undefined;
|
||||
}
|
||||
@@ -160,93 +192,141 @@ export class UserController extends AdaptableController {
|
||||
setPasswordResetToken(email) {
|
||||
const token = { _perishable_token: randomString(25) };
|
||||
|
||||
if (this.config.passwordPolicy && this.config.passwordPolicy.resetTokenValidityDuration) {
|
||||
token._perishable_token_expires_at = Parse._encode(this.config.generatePasswordResetTokenExpiresAt());
|
||||
if (
|
||||
this.config.passwordPolicy &&
|
||||
this.config.passwordPolicy.resetTokenValidityDuration
|
||||
) {
|
||||
token._perishable_token_expires_at = Parse._encode(
|
||||
this.config.generatePasswordResetTokenExpiresAt()
|
||||
);
|
||||
}
|
||||
|
||||
return this.config.database.update('_User', { $or: [{email}, {username: email, email: {$exists: false}}] }, token, {}, true)
|
||||
return this.config.database.update(
|
||||
'_User',
|
||||
{ $or: [{ email }, { username: email, email: { $exists: false } }] },
|
||||
token,
|
||||
{},
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
sendPasswordResetEmail(email) {
|
||||
if (!this.adapter) {
|
||||
throw "Trying to send a reset password but no adapter is set";
|
||||
throw 'Trying to send a reset password but no adapter is set';
|
||||
// TODO: No adapter?
|
||||
}
|
||||
|
||||
return this.setPasswordResetToken(email)
|
||||
.then(user => {
|
||||
const token = encodeURIComponent(user._perishable_token);
|
||||
const username = encodeURIComponent(user.username);
|
||||
return this.setPasswordResetToken(email).then(user => {
|
||||
const token = encodeURIComponent(user._perishable_token);
|
||||
const username = encodeURIComponent(user.username);
|
||||
|
||||
const link = buildEmailLink(this.config.requestResetPasswordURL, username, token, this.config);
|
||||
const options = {
|
||||
appName: this.config.appName,
|
||||
link: link,
|
||||
user: inflate('_User', user),
|
||||
};
|
||||
const link = buildEmailLink(
|
||||
this.config.requestResetPasswordURL,
|
||||
username,
|
||||
token,
|
||||
this.config
|
||||
);
|
||||
const 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));
|
||||
}
|
||||
if (this.adapter.sendPasswordResetEmail) {
|
||||
this.adapter.sendPasswordResetEmail(options);
|
||||
} else {
|
||||
this.adapter.sendMail(this.defaultResetPasswordEmail(options));
|
||||
}
|
||||
|
||||
return Promise.resolve(user);
|
||||
});
|
||||
return Promise.resolve(user);
|
||||
});
|
||||
}
|
||||
|
||||
updatePassword(username, token, password) {
|
||||
return this.checkResetTokenValidity(username, token)
|
||||
.then(user => updateUserPassword(user.objectId, password, this.config))
|
||||
// clear reset password token
|
||||
.then(() => this.config.database.update('_User', {username}, {
|
||||
_perishable_token: {__op: 'Delete'},
|
||||
_perishable_token_expires_at: {__op: 'Delete'}
|
||||
})).catch((error) => {
|
||||
if (error.message) { // in case of Parse.Error, fail with the error message only
|
||||
return Promise.reject(error.message);
|
||||
} else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
return (
|
||||
this.checkResetTokenValidity(username, token)
|
||||
.then(user => updateUserPassword(user.objectId, password, this.config))
|
||||
// clear reset password token
|
||||
.then(() =>
|
||||
this.config.database.update(
|
||||
'_User',
|
||||
{ username },
|
||||
{
|
||||
_perishable_token: { __op: 'Delete' },
|
||||
_perishable_token_expires_at: { __op: 'Delete' },
|
||||
}
|
||||
)
|
||||
)
|
||||
.catch(error => {
|
||||
if (error.message) {
|
||||
// in case of Parse.Error, fail with the error message only
|
||||
return Promise.reject(error.message);
|
||||
} else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
defaultVerificationEmail({link, user, appName, }) {
|
||||
const text = "Hi,\n\n" +
|
||||
"You are being asked to confirm the e-mail address " + user.get("email") + " with " + appName + "\n\n" +
|
||||
"" +
|
||||
"Click here to confirm it:\n" + link;
|
||||
const to = user.get("email");
|
||||
defaultVerificationEmail({ link, user, appName }) {
|
||||
const text =
|
||||
'Hi,\n\n' +
|
||||
'You are being asked to confirm the e-mail address ' +
|
||||
user.get('email') +
|
||||
' with ' +
|
||||
appName +
|
||||
'\n\n' +
|
||||
'' +
|
||||
'Click here to confirm it:\n' +
|
||||
link;
|
||||
const to = user.get('email');
|
||||
const subject = 'Please verify your e-mail for ' + appName;
|
||||
return { text, to, subject };
|
||||
}
|
||||
|
||||
defaultResetPasswordEmail({link, user, appName, }) {
|
||||
const text = "Hi,\n\n" +
|
||||
"You requested to reset your password for " + appName +
|
||||
(user.get('username') ? (" (your username is '" + user.get('username') + "')") : "") + ".\n\n" +
|
||||
"" +
|
||||
"Click here to reset it:\n" + link;
|
||||
const to = user.get("email") || user.get('username');
|
||||
const subject = 'Password Reset for ' + appName;
|
||||
defaultResetPasswordEmail({ link, user, appName }) {
|
||||
const text =
|
||||
'Hi,\n\n' +
|
||||
'You requested to reset your password for ' +
|
||||
appName +
|
||||
(user.get('username')
|
||||
? " (your username is '" + user.get('username') + "')"
|
||||
: '') +
|
||||
'.\n\n' +
|
||||
'' +
|
||||
'Click here to reset it:\n' +
|
||||
link;
|
||||
const to = user.get('email') || user.get('username');
|
||||
const subject = 'Password Reset for ' + appName;
|
||||
return { text, to, subject };
|
||||
}
|
||||
}
|
||||
|
||||
// Mark this private
|
||||
function updateUserPassword(userId, password, config) {
|
||||
return rest.update(config, Auth.master(config), '_User', { objectId: userId }, {
|
||||
password: password
|
||||
});
|
||||
return rest.update(
|
||||
config,
|
||||
Auth.master(config),
|
||||
'_User',
|
||||
{ objectId: userId },
|
||||
{
|
||||
password: password,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function buildEmailLink(destination, username, token, config) {
|
||||
const usernameAndToken = `token=${token}&username=${username}`
|
||||
const usernameAndToken = `token=${token}&username=${username}`;
|
||||
|
||||
if (config.parseFrameURL) {
|
||||
const destinationWithoutHost = destination.replace(config.publicServerURL, '');
|
||||
const destinationWithoutHost = destination.replace(
|
||||
config.publicServerURL,
|
||||
''
|
||||
);
|
||||
|
||||
return `${config.parseFrameURL}?link=${encodeURIComponent(destinationWithoutHost)}&${usernameAndToken}`;
|
||||
return `${config.parseFrameURL}?link=${encodeURIComponent(
|
||||
destinationWithoutHost
|
||||
)}&${usernameAndToken}`;
|
||||
} else {
|
||||
return `${destination}?${usernameAndToken}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user