fix: Remove username from email verification and password reset process (#8488)
BREAKING CHANGE: This removes the username from the email verification and password reset process to prevent storing personally identifiable information (PII) in server and infrastructure logs. Customized HTML pages or emails related to email verification and password reset may need to be adapted accordingly. See the new templates that come bundled with Parse Server and the [migration guide](https://github.com/parse-community/parse-server/blob/alpha/8.0.0.md) for more details.
This commit is contained in:
@@ -83,30 +83,24 @@ export class PagesRouter extends PromiseRouter {
|
||||
|
||||
verifyEmail(req) {
|
||||
const config = req.config;
|
||||
const { username, token: rawToken } = req.query;
|
||||
const { token: rawToken } = req.query;
|
||||
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||
|
||||
if (!config) {
|
||||
this.invalidRequest();
|
||||
}
|
||||
|
||||
if (!token || !username) {
|
||||
if (!token) {
|
||||
return this.goToPage(req, pages.emailVerificationLinkInvalid);
|
||||
}
|
||||
|
||||
const userController = config.userController;
|
||||
return userController.verifyEmail(username, token).then(
|
||||
return userController.verifyEmail(token).then(
|
||||
() => {
|
||||
const params = {
|
||||
[pageParams.username]: username,
|
||||
};
|
||||
return this.goToPage(req, pages.emailVerificationSuccess, params);
|
||||
return this.goToPage(req, pages.emailVerificationSuccess);
|
||||
},
|
||||
() => {
|
||||
const params = {
|
||||
[pageParams.username]: username,
|
||||
};
|
||||
return this.goToPage(req, pages.emailVerificationLinkExpired, params);
|
||||
return this.goToPage(req, pages.emailVerificationLinkInvalid);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -114,18 +108,19 @@ export class PagesRouter extends PromiseRouter {
|
||||
resendVerificationEmail(req) {
|
||||
const config = req.config;
|
||||
const username = req.body.username;
|
||||
const token = req.body.token;
|
||||
|
||||
if (!config) {
|
||||
this.invalidRequest();
|
||||
}
|
||||
|
||||
if (!username) {
|
||||
if (!username && !token) {
|
||||
return this.goToPage(req, pages.emailVerificationLinkInvalid);
|
||||
}
|
||||
|
||||
const userController = config.userController;
|
||||
|
||||
return userController.resendVerificationEmail(username, req).then(
|
||||
return userController.resendVerificationEmail(username, req, token).then(
|
||||
() => {
|
||||
return this.goToPage(req, pages.emailVerificationSendSuccess);
|
||||
},
|
||||
@@ -154,28 +149,24 @@ export class PagesRouter extends PromiseRouter {
|
||||
this.invalidRequest();
|
||||
}
|
||||
|
||||
const { username, token: rawToken } = req.query;
|
||||
const { token: rawToken } = req.query;
|
||||
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||
|
||||
if (!username || !token) {
|
||||
if (!token) {
|
||||
return this.goToPage(req, pages.passwordResetLinkInvalid);
|
||||
}
|
||||
|
||||
return config.userController.checkResetTokenValidity(username, token).then(
|
||||
return config.userController.checkResetTokenValidity(token).then(
|
||||
() => {
|
||||
const params = {
|
||||
[pageParams.token]: token,
|
||||
[pageParams.username]: username,
|
||||
[pageParams.appId]: config.applicationId,
|
||||
[pageParams.appName]: config.appName,
|
||||
};
|
||||
return this.goToPage(req, pages.passwordReset, params);
|
||||
},
|
||||
() => {
|
||||
const params = {
|
||||
[pageParams.username]: username,
|
||||
};
|
||||
return this.goToPage(req, pages.passwordResetLinkInvalid, params);
|
||||
return this.goToPage(req, pages.passwordResetLinkInvalid);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -187,17 +178,13 @@ export class PagesRouter extends PromiseRouter {
|
||||
this.invalidRequest();
|
||||
}
|
||||
|
||||
const { username, new_password, token: rawToken } = req.body;
|
||||
const { new_password, token: rawToken } = req.body;
|
||||
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||
|
||||
if ((!username || !token || !new_password) && req.xhr === false) {
|
||||
if ((!token || !new_password) && req.xhr === false) {
|
||||
return this.goToPage(req, pages.passwordResetLinkInvalid);
|
||||
}
|
||||
|
||||
if (!username) {
|
||||
throw new Parse.Error(Parse.Error.USERNAME_MISSING, 'Missing username');
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'Missing token');
|
||||
}
|
||||
@@ -207,7 +194,7 @@ export class PagesRouter extends PromiseRouter {
|
||||
}
|
||||
|
||||
return config.userController
|
||||
.updatePassword(username, token, new_password)
|
||||
.updatePassword(token, new_password)
|
||||
.then(
|
||||
() => {
|
||||
return Promise.resolve({
|
||||
@@ -235,16 +222,18 @@ export class PagesRouter extends PromiseRouter {
|
||||
}
|
||||
|
||||
const query = result.success
|
||||
? {
|
||||
[pageParams.username]: username,
|
||||
}
|
||||
? {}
|
||||
: {
|
||||
[pageParams.username]: username,
|
||||
[pageParams.token]: token,
|
||||
[pageParams.appId]: config.applicationId,
|
||||
[pageParams.error]: result.err,
|
||||
[pageParams.appName]: config.appName,
|
||||
};
|
||||
|
||||
if (result?.err === 'The password reset link has expired') {
|
||||
delete query[pageParams.token];
|
||||
query[pageParams.token] = token;
|
||||
}
|
||||
const page = result.success ? pages.passwordResetSuccess : pages.passwordReset;
|
||||
|
||||
return this.goToPage(req, page, query, false);
|
||||
|
||||
@@ -19,7 +19,7 @@ export class PublicAPIRouter extends PromiseRouter {
|
||||
});
|
||||
}
|
||||
verifyEmail(req) {
|
||||
const { username, token: rawToken } = req.query;
|
||||
const { token: rawToken } = req.query;
|
||||
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||
|
||||
const appId = req.params.appId;
|
||||
@@ -33,21 +33,20 @@ export class PublicAPIRouter extends PromiseRouter {
|
||||
return this.missingPublicServerURL();
|
||||
}
|
||||
|
||||
if (!token || !username) {
|
||||
if (!token) {
|
||||
return this.invalidLink(req);
|
||||
}
|
||||
|
||||
const userController = config.userController;
|
||||
return userController.verifyEmail(username, token).then(
|
||||
return userController.verifyEmail(token).then(
|
||||
() => {
|
||||
const params = qs.stringify({ username });
|
||||
return Promise.resolve({
|
||||
status: 302,
|
||||
location: `${config.verifyEmailSuccessURL}?${params}`,
|
||||
location: `${config.verifyEmailSuccessURL}`,
|
||||
});
|
||||
},
|
||||
() => {
|
||||
return this.invalidVerificationLink(req);
|
||||
return this.invalidVerificationLink(req, token);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -65,13 +64,15 @@ export class PublicAPIRouter extends PromiseRouter {
|
||||
return this.missingPublicServerURL();
|
||||
}
|
||||
|
||||
if (!username) {
|
||||
const token = req.body.token;
|
||||
|
||||
if (!username && !token) {
|
||||
return this.invalidLink(req);
|
||||
}
|
||||
|
||||
const userController = config.userController;
|
||||
|
||||
return userController.resendVerificationEmail(username, req).then(
|
||||
return userController.resendVerificationEmail(username, req, token).then(
|
||||
() => {
|
||||
return Promise.resolve({
|
||||
status: 302,
|
||||
@@ -125,19 +126,18 @@ export class PublicAPIRouter extends PromiseRouter {
|
||||
return this.missingPublicServerURL();
|
||||
}
|
||||
|
||||
const { username, token: rawToken } = req.query;
|
||||
const { token: rawToken } = req.query;
|
||||
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||
|
||||
if (!username || !token) {
|
||||
if (!token) {
|
||||
return this.invalidLink(req);
|
||||
}
|
||||
|
||||
return config.userController.checkResetTokenValidity(username, token).then(
|
||||
return config.userController.checkResetTokenValidity(token).then(
|
||||
() => {
|
||||
const params = qs.stringify({
|
||||
token,
|
||||
id: config.applicationId,
|
||||
username,
|
||||
app: config.appName,
|
||||
});
|
||||
return Promise.resolve({
|
||||
@@ -162,17 +162,13 @@ export class PublicAPIRouter extends PromiseRouter {
|
||||
return this.missingPublicServerURL();
|
||||
}
|
||||
|
||||
const { username, new_password, token: rawToken } = req.body;
|
||||
const { new_password, token: rawToken } = req.body;
|
||||
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
|
||||
|
||||
if ((!username || !token || !new_password) && req.xhr === false) {
|
||||
if ((!token || !new_password) && req.xhr === false) {
|
||||
return this.invalidLink(req);
|
||||
}
|
||||
|
||||
if (!username) {
|
||||
throw new Parse.Error(Parse.Error.USERNAME_MISSING, 'Missing username');
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'Missing token');
|
||||
}
|
||||
@@ -182,7 +178,7 @@ export class PublicAPIRouter extends PromiseRouter {
|
||||
}
|
||||
|
||||
return config.userController
|
||||
.updatePassword(username, token, new_password)
|
||||
.updatePassword(token, new_password)
|
||||
.then(
|
||||
() => {
|
||||
return Promise.resolve({
|
||||
@@ -197,13 +193,18 @@ export class PublicAPIRouter extends PromiseRouter {
|
||||
}
|
||||
)
|
||||
.then(result => {
|
||||
const params = qs.stringify({
|
||||
username: username,
|
||||
const queryString = {
|
||||
token: token,
|
||||
id: config.applicationId,
|
||||
error: result.err,
|
||||
app: config.appName,
|
||||
});
|
||||
};
|
||||
|
||||
if (result?.err === 'The password reset link has expired') {
|
||||
delete queryString.token;
|
||||
queryString.token = token;
|
||||
}
|
||||
const params = qs.stringify(queryString);
|
||||
|
||||
if (req.xhr) {
|
||||
if (result.success) {
|
||||
@@ -217,9 +218,8 @@ export class PublicAPIRouter extends PromiseRouter {
|
||||
}
|
||||
}
|
||||
|
||||
const encodedUsername = encodeURIComponent(username);
|
||||
const location = result.success
|
||||
? `${config.passwordResetSuccessURL}?username=${encodedUsername}`
|
||||
? `${config.passwordResetSuccessURL}`
|
||||
: `${config.choosePasswordURL}?${params}`;
|
||||
|
||||
return Promise.resolve({
|
||||
@@ -236,12 +236,12 @@ export class PublicAPIRouter extends PromiseRouter {
|
||||
});
|
||||
}
|
||||
|
||||
invalidVerificationLink(req) {
|
||||
invalidVerificationLink(req, token) {
|
||||
const config = req.config;
|
||||
if (req.query.username && req.params.appId) {
|
||||
if (req.params.appId) {
|
||||
const params = qs.stringify({
|
||||
username: req.query.username,
|
||||
appId: req.params.appId,
|
||||
token,
|
||||
});
|
||||
return Promise.resolve({
|
||||
status: 302,
|
||||
|
||||
@@ -438,10 +438,20 @@ export class UsersRouter extends ClassesRouter {
|
||||
async handleResetRequest(req) {
|
||||
this._throwOnBadEmailConfig(req);
|
||||
|
||||
const { email } = req.body;
|
||||
if (!email) {
|
||||
let email = req.body.email;
|
||||
const token = req.body.token;
|
||||
if (!email && !token) {
|
||||
throw new Parse.Error(Parse.Error.EMAIL_MISSING, 'you must provide an email');
|
||||
}
|
||||
if (token) {
|
||||
const results = await req.config.database.find('_User', {
|
||||
_perishable_token: token,
|
||||
_perishable_token_expires_at: { $lt: Parse._encode(new Date()) },
|
||||
});
|
||||
if (results && results[0] && results[0].email) {
|
||||
email = results[0].email;
|
||||
}
|
||||
}
|
||||
if (typeof email !== 'string') {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_EMAIL_ADDRESS,
|
||||
|
||||
Reference in New Issue
Block a user