diff --git a/packages/backend/src/routers/change_email.js b/packages/backend/src/routers/change_email.js
index aa8456ab9..5a6f81edd 100644
--- a/packages/backend/src/routers/change_email.js
+++ b/packages/backend/src/routers/change_email.js
@@ -28,72 +28,6 @@ const config = require('../config.js');
const jwt = require('jsonwebtoken');
const { invalidate_cached_user_by_id } = require('../helpers.js');
-const CHANGE_EMAIL_START = eggspress('/change_email/start', {
- subdomain: 'api',
- auth: true,
- verified: true,
- allowedMethods: ['POST'],
-}, async (req, res, next) => {
- const user = req.user;
- const new_email = req.body.new_email;
-
- // TODO: DRY: signup.js
- // validation
- if( ! new_email ) {
- throw APIError.create('field_missing', null, { key: 'new_email' });
- }
- if ( typeof new_email !== 'string' ) {
- throw APIError.create('field_invalid', null, {
- key: 'new_email', expected: 'a valid email address' });
- }
- if ( ! validator.isEmail(new_email) ) {
- throw APIError.create('field_invalid', null, {
- key: 'new_email', expected: 'a valid email address' });
- }
-
- const svc_edgeRateLimit = req.services.get('edge-rate-limit');
- if ( ! svc_edgeRateLimit.check('change-email-start') ) {
- return res.status(429).send('Too many requests.');
- }
-
- // check if email is already in use
- const db = req.services.get('database').get(DB_WRITE, 'auth');
- const rows = await db.read(
- 'SELECT COUNT(*) AS `count` FROM `user` WHERE `email` = ?',
- [new_email]
- );
- if ( rows[0].count > 0 ) {
- throw APIError.create('email_already_in_use', null, { email: new_email });
- }
-
- // generate confirmation token
- const token = crypto.randomBytes(4).toString('hex');
- const jwt_token = jwt.sign({
- user_id: user.id,
- token,
- }, config.jwt_secret, { expiresIn: '24h' });
-
- // send confirmation email
- const svc_email = req.services.get('email');
- await svc_email.send_email({ email: new_email }, 'email_change_request', {
- confirm_url: `${config.origin}/change_email/confirm?token=${jwt_token}`,
- username: user.username,
- });
- const old_email = user.email;
- // TODO: NotificationService
- await svc_email.send_email({ email: old_email }, 'email_change_notification', {
- new_email: new_email,
- });
-
- // update user
- await db.write(
- 'UPDATE `user` SET `unconfirmed_change_email` = ?, `change_email_confirm_token` = ? WHERE `id` = ?',
- [new_email, token, user.id]
- );
-
- res.send({ success: true });
-});
-
const CHANGE_EMAIL_CONFIRM = eggspress('/change_email/confirm', {
allowedMethods: ['GET'],
}, async (req, res, next) => {
@@ -137,6 +71,5 @@ const CHANGE_EMAIL_CONFIRM = eggspress('/change_email/confirm', {
});
module.exports = app => {
- app.use(CHANGE_EMAIL_START);
app.use(CHANGE_EMAIL_CONFIRM);
}
diff --git a/packages/backend/src/routers/user-protected/change-email.js b/packages/backend/src/routers/user-protected/change-email.js
new file mode 100644
index 000000000..c4195712c
--- /dev/null
+++ b/packages/backend/src/routers/user-protected/change-email.js
@@ -0,0 +1,68 @@
+const APIError = require("../../api/APIError");
+const { DB_WRITE } = require("../../services/database/consts");
+const jwt = require('jsonwebtoken');
+const validator = require('validator');
+const crypto = require('crypto');
+const config = require("../../config");
+
+module.exports = {
+ route: '/change-email',
+ methods: ['POST'],
+ handler: async (req, res, next) => {
+ const user = req.user;
+ const new_email = req.body.new_email;
+
+ console.log('DID REACH HERE');
+
+ // TODO: DRY: signup.js
+ // validation
+ if( ! new_email ) {
+ throw APIError.create('field_missing', null, { key: 'new_email' });
+ }
+ if ( typeof new_email !== 'string' ) {
+ throw APIError.create('field_invalid', null, {
+ key: 'new_email', expected: 'a valid email address' });
+ }
+ if ( ! validator.isEmail(new_email) ) {
+ throw APIError.create('field_invalid', null, {
+ key: 'new_email', expected: 'a valid email address' });
+ }
+
+ // check if email is already in use
+ const db = req.services.get('database').get(DB_WRITE, 'auth');
+ const rows = await db.read(
+ 'SELECT COUNT(*) AS `count` FROM `user` WHERE `email` = ?',
+ [new_email]
+ );
+ if ( rows[0].count > 0 ) {
+ throw APIError.create('email_already_in_use', null, { email: new_email });
+ }
+
+ // generate confirmation token
+ const token = crypto.randomBytes(4).toString('hex');
+ const jwt_token = jwt.sign({
+ user_id: user.id,
+ token,
+ }, config.jwt_secret, { expiresIn: '24h' });
+
+ // send confirmation email
+ const svc_email = req.services.get('email');
+ await svc_email.send_email({ email: new_email }, 'email_change_request', {
+ confirm_url: `${config.origin}/change_email/confirm?token=${jwt_token}`,
+ username: user.username,
+ });
+ const old_email = user.email;
+ // TODO: NotificationService
+ await svc_email.send_email({ email: old_email }, 'email_change_notification', {
+ new_email: new_email,
+ });
+
+ // update user
+ await db.write(
+ 'UPDATE `user` SET `unconfirmed_change_email` = ?, `change_email_confirm_token` = ? WHERE `id` = ?',
+ [new_email, token, user.id]
+ );
+
+ res.send({ success: true });
+ }
+};
diff --git a/packages/backend/src/services/abuse-prevention/EdgeRateLimitService.js b/packages/backend/src/services/abuse-prevention/EdgeRateLimitService.js
index c5852c3aa..7efe8f778 100644
--- a/packages/backend/src/services/abuse-prevention/EdgeRateLimitService.js
+++ b/packages/backend/src/services/abuse-prevention/EdgeRateLimitService.js
@@ -5,6 +5,12 @@ const { quot } = require("../../util/strutil");
const { MINUTE, HOUR } = require('../../util/time.js');
const BaseService = require("../BaseService");
+/* INCREMENTAL CHANGES
+ The first scopes are of the form 'name-of-endpoint', but later it was
+ decided that they're of the form `/path/to/endpoint`. New scopes should
+ follow the latter form.
+*/
+
class EdgeRateLimitService extends BaseService {
_construct () {
this.scopes = {
@@ -60,6 +66,10 @@ class EdgeRateLimitService extends BaseService {
limit: 10,
window: HOUR,
},
+ ['/user-protected/change-email']: {
+ limit: 10,
+ window: HOUR,
+ },
['login-otp']: {
limit: 15,
window: 30 * MINUTE,
diff --git a/packages/backend/src/services/web/UserProtectedEndpointsService.js b/packages/backend/src/services/web/UserProtectedEndpointsService.js
index ae9acd059..d9413b775 100644
--- a/packages/backend/src/services/web/UserProtectedEndpointsService.js
+++ b/packages/backend/src/services/web/UserProtectedEndpointsService.js
@@ -82,7 +82,11 @@ class UserProtectedEndpointsService extends BaseService {
});
Endpoint(
- require('../../routers/user-protected/change-password.js')
+ require('../../routers/user-protected/change-password.js'),
+ ).attach(router);
+
+ Endpoint(
+ require('../../routers/user-protected/change-email.js'),
).attach(router);
}
}
diff --git a/src/UI/Settings/UIWindowChangeEmail.js b/src/UI/Settings/UIWindowChangeEmail.js
index 1b28c0aee..787bcc893 100644
--- a/src/UI/Settings/UIWindowChangeEmail.js
+++ b/src/UI/Settings/UIWindowChangeEmail.js
@@ -17,11 +17,18 @@
* along with this program. If not, see