From 89a3a3228f5bd1fe629bd98cf7464801261fef64 Mon Sep 17 00:00:00 2001 From: Daniel Salazar Date: Sat, 1 Nov 2025 17:52:43 -0700 Subject: [PATCH] fix: bad amount of sql params (#1880) --- src/backend/src/routers/signup.js | 336 ++++++++++++++++++------------ 1 file changed, 197 insertions(+), 139 deletions(-) diff --git a/src/backend/src/routers/signup.js b/src/backend/src/routers/signup.js index c77a569e..51deeb70 100644 --- a/src/backend/src/routers/signup.js +++ b/src/backend/src/routers/signup.js @@ -16,22 +16,22 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -"use strict" -const {get_taskbar_items, send_email_verification_code, send_email_verification_token, username_exists, invalidate_cached_user_by_id, get_user } = require('../helpers'); +'use strict'; +const { get_taskbar_items, send_email_verification_code, send_email_verification_token, username_exists, invalidate_cached_user_by_id, get_user } = require('../helpers'); const config = require('../config'); const eggspress = require('../api/eggspress'); const { Context } = require('../util/context'); const { DB_WRITE } = require('../services/database/consts'); const { generate_identifier } = require('../util/identifier'); -const { is_temp_users_disabled: lazy_temp_users, - is_user_signup_disabled: lazy_user_signup } = require("../helpers") +const { is_temp_users_disabled: lazy_temp_users, + is_user_signup_disabled: lazy_user_signup } = require('../helpers'); const { requireCaptcha } = require('../modules/captcha/middleware/captcha-middleware'); -async function generate_random_username () { +async function generate_random_username() { let username; do { username = generate_identifier(); - } while (await username_exists(username)); + } while ( await username_exists(username) ); return username; } @@ -46,14 +46,16 @@ module.exports = eggspress(['/signup'], { no_bots: true, // puter_origin: false, shadow_ban_responder: (req, res) => { - res.status(400).send(`email username mismatch; please provide a password`); - } + res.status(400).send('email username mismatch; please provide a password'); + }, }, mw: [requireCaptcha({ strictMode: true, eventType: 'signup' })], // Conditionally require captcha for signup }, async (req, res, next) => { // either api. subdomain or no subdomain - if(require('../helpers').subdomain(req) !== 'api' && require('../helpers').subdomain(req) !== '') + if ( require('../helpers').subdomain(req) !== 'api' && require('../helpers').subdomain(req) !== '' ) + { next(); + } const svc_edgeRateLimit = req.services.get('edge-rate-limit'); if ( ! svc_edgeRateLimit.check('signup') ) { @@ -62,31 +64,32 @@ module.exports = eggspress(['/signup'], { // modules const db = req.services.get('database').get(DB_WRITE, 'auth'); - const bcrypt = require('bcrypt') + const bcrypt = require('bcrypt'); const { v4: uuidv4 } = require('uuid'); - const jwt = require('jsonwebtoken') - const validator = require('validator') + const jwt = require('jsonwebtoken'); + const validator = require('validator'); let uuid_user; const svc_auth = Context.get('services').get('auth'); const svc_authAudit = Context.get('services').get('auth-audit'); svc_authAudit.record({ requester: Context.get('requester'), - action: req.body.is_temp ? `signup:temp` : `signup:real`, + action: req.body.is_temp ? 'signup:temp' : 'signup:real', body: req.body, }); // check bot trap, if `p102xyzname` is anything but an empty string it means // that a bot has filled the form // doesn't apply to temp users - if(!req.body.is_temp && req.body.p102xyzname !== '') + if ( !req.body.is_temp && req.body.p102xyzname !== '' ) + { return res.send(); - + } // cloudflare turnstile validation // // ref: https://developers.cloudflare.com/turnstile/get-started/server-side-validation/ - if (config.services?.['cloudflare-turnstile']?.enabled) { + if ( config.services?.['cloudflare-turnstile']?.enabled ) { const formData = new FormData(); formData.append('secret', config.services?.['cloudflare-turnstile']?.secret_key); formData.append('response', req.body['cf-turnstile-response']); @@ -94,12 +97,14 @@ module.exports = eggspress(['/signup'], { const response = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', { method: 'POST', - body: formData + body: formData, }); const result = await response.json(); - if (!result.success) + if ( !result.success ) + { return res.status(400).send('captcha verification failed'); + } } // send event @@ -112,7 +117,7 @@ module.exports = eggspress(['/signup'], { }; const svc_event = Context.get('services').get('event'); - await svc_event.emit('puter.signup', event) + await svc_event.emit('puter.signup', event); if ( ! event.allow ) { return res.status(400).send(event.error ?? 'You are not allowed to sign up.'); @@ -120,9 +125,7 @@ module.exports = eggspress(['/signup'], { // check if user is already logged in if ( req.body.is_temp && req.cookies[config.cookie_name] ) { - const { user, token } = await svc_auth.check_session( - req.cookies[config.cookie_name] - ); + const { user, token } = await svc_auth.check_session(req.cookies[config.cookie_name]); res.cookie(config.cookie_name, token, { sameSite: 'none', secure: true, @@ -141,7 +144,7 @@ module.exports = eggspress(['/signup'], { requires_email_confirmation: user.requires_email_confirmation, is_temp: (user.password === null && user.email === null), taskbar_items: await get_taskbar_items(user), - } + }, }); } } @@ -149,19 +152,19 @@ module.exports = eggspress(['/signup'], { const is_temp_users_disabled = await lazy_temp_users(); const is_user_signup_disabled = await lazy_user_signup(); - if (is_temp_users_disabled && is_user_signup_disabled) { + if ( is_temp_users_disabled && is_user_signup_disabled ) { return res.status(403).send('User signup and Temporary users are disabled.'); } - if (!req.body.is_temp && is_user_signup_disabled) { + if ( !req.body.is_temp && is_user_signup_disabled ) { return res.status(403).send('User signup is disabled.'); - } + } - if (req.body.is_temp && is_temp_users_disabled) { + if ( req.body.is_temp && is_temp_users_disabled ) { return res.status(403).send('Temporary users are disabled.'); } - if (req.body.is_temp && event.no_temp_user) { + if ( req.body.is_temp && event.no_temp_user ) { return res.status(403).send('You must login or signup.'); } @@ -169,74 +172,101 @@ module.exports = eggspress(['/signup'], { req.body.username = req.body.username ?? await generate_random_username(); req.body.email = req.body.email ?? req.body.username + '@gmail.com'; req.body.password = req.body.password ?? 'sadasdfasdfsadfsa'; - + // send_confirmation_code req.body.send_confirmation_code = req.body.send_confirmation_code ?? true; // username is required - if(!req.body.username) - return res.status(400).send('Username is required') + if ( !req.body.username ) + { + return res.status(400).send('Username is required'); + } // username must be a string - else if (typeof req.body.username !== 'string') - return res.status(400).send('username must be a string.') + else if ( typeof req.body.username !== 'string' ) + { + return res.status(400).send('username must be a string.'); + } // check if username is valid - else if(!req.body.username.match(config.username_regex)) - return res.status(400).send('Username can only contain letters, numbers and underscore (_).') + else if ( !req.body.username.match(config.username_regex) ) + { + return res.status(400).send('Username can only contain letters, numbers and underscore (_).'); + } // check if username is of proper length - else if(req.body.username.length > config.username_max_length) - return res.status(400).send(`Username cannot be longer than ${config.username_max_length} characters.`) + else if ( req.body.username.length > config.username_max_length ) + { + return res.status(400).send(`Username cannot be longer than ${config.username_max_length} characters.`); + } // check if username matches any reserved words - else if(config.reserved_words.includes(req.body.username)) - return res.status(400).send({message: 'This username is not available.'}); + else if ( config.reserved_words.includes(req.body.username) ) + { + return res.status(400).send({ message: 'This username is not available.' }); + } // TODO: DRY: change_email.js - else if(!req.body.is_temp && !req.body.email) + else if ( !req.body.is_temp && !req.body.email ) + { return res.status(400).send('Email is required'); + } // email, if present, must be a string - else if (req.body.email && typeof req.body.email !== 'string') - return res.status(400).send('email must be a string.') + else if ( req.body.email && typeof req.body.email !== 'string' ) + { + return res.status(400).send('email must be a string.'); + } // if email is present, validate it - else if(!req.body.is_temp && !validator.isEmail(req.body.email)) - return res.status(400).send('Please enter a valid email address.') - else if(!req.body.is_temp && !req.body.password) + else if ( !req.body.is_temp && !validator.isEmail(req.body.email) ) + { + return res.status(400).send('Please enter a valid email address.'); + } + else if ( !req.body.is_temp && !req.body.password ) + { return res.status(400).send('Password is required'); + } // password, if present, must be a string - else if (req.body.password && typeof req.body.password !== 'string') - return res.status(400).send('password must be a string.') - else if(!req.body.is_temp && req.body.password.length < config.min_pass_length) + else if ( req.body.password && typeof req.body.password !== 'string' ) + { + return res.status(400).send('password must be a string.'); + } + else if ( !req.body.is_temp && req.body.password.length < config.min_pass_length ) + { return res.status(400).send(`Password must be at least ${config.min_pass_length} characters long.`); + } const svc_cleanEmail = req.services.get('clean-email'); const clean_email = svc_cleanEmail.clean(req.body.email); - - if (!req.body.is_temp && ! await svc_cleanEmail.validate(clean_email) ) { + + if ( !req.body.is_temp && ! await svc_cleanEmail.validate(clean_email) ) { return res.status(400).send('This email does not seem to be valid.'); } // duplicate username check - if(await username_exists(req.body.username)) + 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( + let rows2 = await db.read(`SELECT EXISTS( SELECT 1 FROM user WHERE (email=? OR clean_email=?) AND email_confirmed=1 AND password IS NOT NULL ) AS email_exists`, [req.body.email, clean_email]); - if(rows2[0].email_exists) + 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 - let pseudo_user = await db.read(`SELECT * FROM user WHERE email = ? AND password IS NULL`, [req.body.email]); + let pseudo_user = await db.read('SELECT * FROM user WHERE email = ? AND password IS NULL', [req.body.email]); pseudo_user = pseudo_user[0]; // get uuid user, if exists - if(req.body.uuid){ - uuid_user = await db.read(`SELECT * FROM user WHERE uuid = ? LIMIT 1`, [req.body.uuid]); + if ( req.body.uuid ) { + uuid_user = await db.read('SELECT * FROM user WHERE uuid = ? LIMIT 1', [req.body.uuid]); uuid_user = uuid_user[0]; } // email confirmation is required by default unless: // Pseudo user converting and matching uuid is provided let email_confirmation_required = 1; - if(pseudo_user && uuid_user && pseudo_user.id === uuid_user.id) + if ( pseudo_user && uuid_user && pseudo_user.id === uuid_user.id ) + { email_confirmation_required = 0; + } // ----------------------------------- // Get referral user @@ -265,94 +295,120 @@ module.exports = eggspress(['/signup'], { server: config.server_id, }; - if(pseudo_user === undefined){ - insert_res = await db.write( - `INSERT INTO user + if ( pseudo_user === undefined ) { + insert_res = await db.write(`INSERT INTO user ( - username, email, clean_email, password, uuid, referrer, email_confirm_code, email_confirm_token, free_storage, referred_by, audit_metadata, signup_ip, signup_ip_forwarded, signup_user_agent, signup_origin, signup_server, metadata + username, + email, + clean_email, + password, + uuid, + referrer, + email_confirm_code, + email_confirm_token, + free_storage, + referred_by, + audit_metadata, + signup_ip, + signup_ip_forwarded, + signup_user_agent, + signup_origin, + signup_server, + metadata ) VALUES - (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - [ - // username - req.body.username, - // email - req.body.is_temp ? null : req.body.email, - // normalized email - req.body.is_temp ? null : clean_email, - // password - req.body.is_temp ? null : await bcrypt.hash(req.body.password, 8), - // uuid - user_uuid, - // referrer - req.body.referrer ?? null, - // email_confirm_code - '' + email_confirm_code, - // email_confirm_token - email_confirm_token, - // free_storage - config.storage_capacity, - // referred_by - referred_by_user ? referred_by_user.id : null, - // audit_metadata - JSON.stringify(audit_metadata), - // signup_ip - req.connection.remoteAddress ?? null, - // signup_ip_fwd - req.headers['x-forwarded-for'] ?? null, - // signup_user_agent - req.headers['user-agent'] ?? null, - // signup_origin - req.headers['origin'] ?? null, - // signup_server - config.server_id ?? null, - // metadata - {} - ] - ); + (?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?)`, + [ + // username + req.body.username, + // email + req.body.is_temp ? null : req.body.email, + // normalized email + req.body.is_temp ? null : clean_email, + // password + req.body.is_temp ? null : await bcrypt.hash(req.body.password, 8), + // uuid + user_uuid, + // referrer + req.body.referrer ?? null, + // email_confirm_code + '' + email_confirm_code, + // email_confirm_token + email_confirm_token, + // free_storage + config.storage_capacity, + // referred_by + referred_by_user ? referred_by_user.id : null, + // audit_metadata + JSON.stringify(audit_metadata), + // signup_ip + req.connection.remoteAddress ?? null, + // signup_ip_fwd + req.headers['x-forwarded-for'] ?? null, + // signup_user_agent + req.headers['user-agent'] ?? null, + // signup_origin + req.headers['origin'] ?? null, + // signup_server + config.server_id ?? null, + // metadata + JSON.stringify({}), + ]); // record activity - db.write( - 'UPDATE `user` SET `last_activity_ts` = now() WHERE id=? LIMIT 1', - [insert_res.insertId] - ); - + db.write('UPDATE `user` SET `last_activity_ts` = now() WHERE id=? LIMIT 1', + [insert_res.insertId]); + // TODO: cache group id const svc_group = req.services.get('group'); await svc_group.add_users({ uid: req.body.is_temp ? config.default_temp_group : config.default_user_group, - users: [req.body.username] + users: [req.body.username], }); } // ----------------------------------- // Pseudo User converting // ----------------------------------- - else{ - insert_res = await db.write( - `UPDATE user SET + else { + insert_res = await db.write(`UPDATE user SET username = ?, password = ?, uuid = ?, email_confirm_code = ?, email_confirm_token = ?, email_confirmed = ?, requires_email_confirmation = 1, referred_by = ? WHERE id = ?`, - [ - // username - req.body.username, - // password - await bcrypt.hash(req.body.password, 8), - // uuid - user_uuid, - // email_confirm_code - '' + email_confirm_code, - // email_confirm_token - email_confirm_token, - // email_confirmed - !email_confirmation_required, - // id - pseudo_user.id, - // referred_by - referred_by_user ? referred_by_user.id : null, - ] - ); + [ + // username + req.body.username, + // password + await bcrypt.hash(req.body.password, 8), + // uuid + user_uuid, + // email_confirm_code + '' + email_confirm_code, + // email_confirm_token + email_confirm_token, + // email_confirmed + !email_confirmation_required, + // id + pseudo_user.id, + // referred_by + referred_by_user ? referred_by_user.id : null, + ]); // TODO: cache group ids const svc_group = req.services.get('group'); @@ -362,7 +418,7 @@ module.exports = eggspress(['/signup'], { }); await svc_group.add_users({ uid: config.default_user_group, - users: [req.body.username] + users: [req.body.username], }); // record activity @@ -374,10 +430,8 @@ module.exports = eggspress(['/signup'], { // todo if pseudo user, assign directly no need to do another DB lookup const user_id = (pseudo_user === undefined) ? insert_res.insertId : pseudo_user.id; - const [user] = await db.pread( - 'SELECT * FROM `user` WHERE `id` = ? LIMIT 1', - [user_id] - ); + const [user] = await db.pread('SELECT * FROM `user` WHERE `id` = ? LIMIT 1', + [user_id]); // create token for login const { token } = await svc_auth.create_session_token(user, { @@ -389,11 +443,15 @@ 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) + 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); + } else + { send_email_verification_token(user.email_confirm_token, user.email, user.uuid); + } } //------------------------------------------------------------- @@ -419,7 +477,7 @@ module.exports = eggspress(['/signup'], { }); // add to mailchimp - if(!req.body.is_temp){ + if ( !req.body.is_temp ) { const svc_event = Context.get('services').get('event'); svc_event.emit('user.save_account', { user }); } @@ -427,7 +485,7 @@ module.exports = eggspress(['/signup'], { // return results return res.send({ token: token, - user:{ + user: { username: user.username, uuid: user.uuid, email: user.email, @@ -436,6 +494,6 @@ module.exports = eggspress(['/signup'], { is_temp: (user.password === null && user.email === null), taskbar_items: await get_taskbar_items(user), referral_code, - } - }) + }, + }); });