1
0
Эх сурвалжийг харах

dev: add notes and some updates to email logic

KernelDeimos 7 сар өмнө
parent
commit
822f3b8c45

+ 69 - 0
notes.md

@@ -0,0 +1,69 @@
+## 2024-10-03
+
+### Plan (constantly changing as per what's below)
+
+- `signup.js` only says "email already used" if the one that's
+  already been used is confirmed.
+- "change email" needs to follow the same logic; show an error when
+  an email already exists on an account with a confirmed email.
+  Then, upon confirming the update, Ensure that in the meanwhile no
+  new account came up with that email set.
+  
+NEXT: figure out where email change is handled on backend
+(prior to confirmation) and see what checks are performed.
+
+### Email duplicate check on confirmation
+
+- signup.js:149 -> this is where email dupe is currently checked
+- signup.js:290 -> This is where we send the confirmation email.
+    There is also a branch that sends a "confirm token".
+    I don't recall what this is for.
+
+### Investigating the "confirm token"
+
+- email template is `email_verification_code`
+    instead of `email_verification_link`
+- This happens when either:
+  - user.requires_email_confirmation is TRUE
+  - send_confirmation_code is TRUE in REQUEST
+
+### Figuring out when `requires_email_confirmation` is TRUE
+
+I'm mostly curious about this state on a user.
+It's strange that `signup.js` would do anything on EXISTING users.
+
+1. `pseudo_user` may be populated if `req.body.email` exists
+   AND a user with no password exists with that email
+2. `uuid_user` may be populated if a user exists with the specified
+   UUID, but it has no usefulness unless `uuid_user` has the same
+   id as `pseudo_user`.
+    
+`uuid_user` is only used to set `email_confirmation_required` to 0
+  IFF `pseudo_user` has same id as `uuid_user`
+  AND `psuedo_user` has an email
+
+When does `pseudo_user` have an email?
+
+### Figuring out when a pseudo user can have an email
+- asking NJ, I'm at a loss on this one for the moment
+
+### Figuring out if account takeover is possible on signup.js with a uuid
+- Nope, looks like `uuid_user` is only used to set
+  `email_confirmation_required = 0`
+
+### Figuring out when `send_confirmation_code` is TRUE in REQUEST
+- IFF `require_email_verification_to_publish_website` is TRUE
+  - it's not currently, but we need this to be possible to enable
+- ^ That seems to be the ONLY place when this matters
+
+### Current Thoughts
+
+- `email_verification_code` will be difficult to test because there is
+  nothing currently in the system that's using it. However, I could try
+  enabling `require_email_verification_to_publish_website` locally and
+  see if this behavior begins to work as expected.
+
+- `email_verification_link` where we can confirm an email. If another email
+  was already confirmed since the time the link was sent, we need to display
+  an error message to the user.
+

+ 44 - 24
src/backend/src/routers/_default.js

@@ -226,30 +226,50 @@ router.all('*', async function(req, res, next) {
                     h += '<p style="text-align:center; color:red;">invalid token.</p>';
                 // mark user as confirmed
                 else{
-                    // If other users have the same unconfirmed email, revoke it
-                    await db.write(
-                        'UPDATE `user` SET `unconfirmed_change_email` = NULL, `change_email_confirm_token` = NULL WHERE `unconfirmed_change_email` = ?',
-                        [user.email],
-                    );
-
-                    // update user
-                    await db.write(
-                        "UPDATE `user` SET `email_confirmed` = 1, `requires_email_confirmation` = 0 WHERE id = ?",
-                        [user.id]
-                    );
-                    invalidate_cached_user(user);
-
-                    // send realtime success msg to client
-                    let socketio = require('../socketio.js').getio();
-                    if(socketio){
-                        socketio.to(user.id).emit('user.email_confirmed', {})
-                    }
-
-                    // return results
-                    h += `<p style="text-align:center; color:green;">Your email has been successfully confirmed.</p>`;
-
-                    const svc_referralCode = Context.get('services').get('referral-code');
-                    svc_referralCode.on_verified(user);
+                    // This IIFE is here to return early on conditions, and
+                    // avoid further nested branching. This is a temporary
+                    // solution; next time this code should be refactored.
+                    await (async () => {
+                        // If other users have the same CONFIRMED email, display an error
+                        const maybe_rows = await db.read(
+                            `SELECT EXISTS(
+                                SELECT 1 FROM user WHERE email=?
+                                AND email_confirmed=1
+                                AND password IS NOT NULL
+                            ) AS email_exists`
+                        );
+                        if ( maybe_rows[0]?.email_exists ) {
+                            // TODO: maybe display the username of that account
+                            h += '<p style="text-align:center; color:red;">' +
+                                'This email was confirmed on a different account.</p>';
+                            return;
+                        }
+
+                        // If other users have the same unconfirmed email, revoke it
+                        await db.write(
+                            'UPDATE `user` SET `unconfirmed_change_email` = NULL, `change_email_confirm_token` = NULL WHERE `unconfirmed_change_email` = ?',
+                            [user.email],
+                        );
+
+                        // update user
+                        await db.write(
+                            "UPDATE `user` SET `email_confirmed` = 1, `requires_email_confirmation` = 0 WHERE id = ?",
+                            [user.id]
+                        );
+                        invalidate_cached_user(user);
+
+                        // send realtime success msg to client
+                        let socketio = require('../socketio.js').getio();
+                        if(socketio){
+                            socketio.to(user.id).emit('user.email_confirmed', {})
+                        }
+
+                        // return results
+                        h += `<p style="text-align:center; color:green;">Your email has been successfully confirmed.</p>`;
+
+                        const svc_referralCode = Context.get('services').get('referral-code');
+                        svc_referralCode.on_verified(user);
+                    })();
                 }
             }
 

+ 3 - 1
src/backend/src/routers/signup.js

@@ -146,8 +146,9 @@ module.exports = eggspress(['/signup'], {
     // duplicate username check
     if(await username_exists(req.body.username))
         return res.status(400).send('This username already exists in our database. Please use another one.');
+    // Email check is here :: Add condition for email_confirmed=1
     // duplicate email check (pseudo-users don't count)
-    let rows2 = await db.read(`SELECT EXISTS(SELECT 1 FROM user WHERE email=? AND password IS NOT NULL) AS email_exists`, [req.body.email]);
+    let rows2 = await db.read(`SELECT EXISTS(SELECT 1 FROM user WHERE email=? AND email_confirmed=1 AND password IS NOT NULL) AS email_exists`, [req.body.email]);
     if(rows2[0].email_exists)
         return res.status(400).send('This email already exists in our database. Please use another one.');
     // get pseudo user, if exists
@@ -286,6 +287,7 @@ module.exports = eggspress(['/signup'], {
     //-------------------------------------------------------------
     // email confirmation
     //-------------------------------------------------------------
+    // Email confirmation from signup is sent here
     if((!req.body.is_temp && email_confirmation_required) || user.requires_email_confirmation){
         if(req.body.send_confirmation_code || user.requires_email_confirmation)
             send_email_verification_code(email_confirm_code, user.email);