fix: bad amount of sql params (#1880)

This commit is contained in:
Daniel Salazar
2025-11-01 17:52:43 -07:00
committed by GitHub
parent 98cf4bbb08
commit 89a3a3228f

View File

@@ -16,22 +16,22 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
"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,
}
})
},
});
});