Files
kami-parse-server/src/Controllers/UserController.js
Danaru 84896dbeec Set objectId into query for Email Validation (#6930)
* Retrieve user concerned by email verification and ser objectId into query

* Linter ok

* Testing live query fired when email validation done

* Setting objectId into query if user exists

* Setting objectId into query if user exists
2020-10-09 08:21:34 -07:00

325 lines
8.5 KiB
JavaScript

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';
var RestQuery = require('../RestQuery');
var Auth = require('../Auth');
export class UserController extends AdaptableController {
constructor(adapter, appId, options = {}) {
super(adapter, appId, options);
}
validateAdapter(adapter) {
// Allow no adapter
if (!adapter && !this.shouldVerifyEmails) {
return;
}
super.validateAdapter(adapter);
}
expectedAdapterType() {
return MailAdapter;
}
get shouldVerifyEmails() {
return this.options.verifyUserEmails;
}
setEmailVerifyToken(user) {
if (this.shouldVerifyEmails) {
user._email_verify_token = randomString(25);
user.emailVerified = false;
if (this.config.emailVerifyTokenValidityDuration) {
user._email_verify_token_expires_at = Parse._encode(
this.config.generateEmailVerifyTokenExpiresAt()
);
}
}
}
verifyEmail(username, token) {
if (!this.shouldVerifyEmails) {
// Trying to verify email when not enabled
// TODO: Better error here.
throw undefined;
}
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
if (this.config.emailVerifyTokenValidityDuration) {
query.emailVerified = false;
query._email_verify_token_expires_at = { $gt: Parse._encode(new Date()) };
updateFields._email_verify_token_expires_at = { __op: 'Delete' };
}
const masterAuth = Auth.master(this.config);
var findUserForEmailVerification = new RestQuery(
this.config,
Auth.master(this.config),
'_User',
{ username: username }
);
return findUserForEmailVerification.execute().then(result => {
if (result.results.length && result.results[0].emailVerified) {
return Promise.resolve(result.results.length[0]);
} else if (result.results.length) {
query.objectId = result.results[0].objectId;
}
return rest.update(this.config, masterAuth, '_User', query, updateFields);
});
}
checkResetTokenValidity(username, token) {
return this.config.database
.find(
'_User',
{
username: username,
_perishable_token: token,
},
{ limit: 1 }
)
.then(results => {
if (results.length != 1) {
throw 'Failed to reset password: username / email / token is invalid';
}
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) {
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) {
throw undefined;
}
return result.results[0];
});
}
sendVerificationEmail(user) {
if (!this.shouldVerifyEmails) {
return;
}
const token = encodeURIComponent(user._email_verify_token);
// We may need to fetch the user in case of update email
this.getUserIfNeeded(user).then(user => {
const username = encodeURIComponent(user.username);
const link = buildEmailLink(
this.config.verifyEmailURL,
username,
token,
this.config
);
const options = {
appName: this.config.appName,
link: link,
user: inflate('_User', user),
};
if (this.adapter.sendVerificationEmail) {
this.adapter.sendVerificationEmail(options);
} else {
this.adapter.sendMail(this.defaultVerificationEmail(options));
}
});
}
/**
* Regenerates the given user's email verification token
*
* @param user
* @returns {*}
*/
regenerateEmailVerifyToken(user) {
this.setEmailVerifyToken(user);
return this.config.database.update(
'_User',
{ username: user.username },
user
);
}
resendVerificationEmail(username) {
return this.getUserIfNeeded({ username: username }).then(aUser => {
if (!aUser || aUser.emailVerified) {
throw undefined;
}
return this.regenerateEmailVerifyToken(aUser).then(() => {
this.sendVerificationEmail(aUser);
});
});
}
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()
);
}
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';
// TODO: No adapter?
}
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),
};
if (this.adapter.sendPasswordResetEmail) {
this.adapter.sendPasswordResetEmail(options);
} else {
this.adapter.sendMail(this.defaultResetPasswordEmail(options));
}
return Promise.resolve(user);
});
}
updatePassword(username, token, password) {
return this.checkResetTokenValidity(username, token)
.then(user => updateUserPassword(user.objectId, password, this.config))
.catch(error => {
if (error && 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');
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;
return { text, to, subject };
}
}
// Mark this private
function updateUserPassword(userId, password, config) {
return rest.update(
config,
Auth.master(config),
'_User',
{ objectId: userId },
{
password: password,
}
);
}
function buildEmailLink(destination, username, token, config) {
const usernameAndToken = `token=${token}&username=${username}`;
if (config.parseFrameURL) {
const destinationWithoutHost = destination.replace(
config.publicServerURL,
''
);
return `${config.parseFrameURL}?link=${encodeURIComponent(
destinationWithoutHost
)}&${usernameAndToken}`;
} else {
return `${destination}?${usernameAndToken}`;
}
}
export default UserController;