Send activation link immediately for walk-in and manual registration accounts

Accounts created by staff on someone's behalf now get their activation
link (email or WhatsApp) sent right away, instead of only on a first
failed login attempt, matching what the Terms of Use already promised.
This also fixed a real account with a real email being silently
activated with a fixed, undisclosed password (Hope123).

Also fixes the self-service kiosk's "Create an account" password field,
which never actually took effect server-side, and removes the "Guest
(no account)" checkboxes that no longer had any backend effect once
every walk-in account started behaving the same way.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-24 13:29:38 +02:00
co-authored by Claude Sonnet 5
parent 7eed7a01df
commit d6da2c8227
8 changed files with 121 additions and 111 deletions
@@ -1,6 +1,5 @@
const prisma = require('../config/db');
const { v4: uuidv4 } = require('uuid');
const axios = require("axios");
const { generateTicketsForRegistration } = require('../utils/ticketUtils');
const { emailTickets } = require('./ticketController');
const { hashPassword } = require('../config/auth');
@@ -760,7 +759,7 @@ const getRegistrationsByEvent = async (req, res) => {
const createManualRegistration = async (req, res) => {
let userRecord;
try {
const { eventId, options, user, guestOnly, notificationPreference: prefFromBody } = req.body;
const { eventId, options, user, notificationPreference: prefFromBody, skipActivationNotice } = req.body;
if (!eventId || !options || !user || !user.name || (!user.email && !user.phoneNumber)) {
res.status(400);
@@ -844,7 +843,7 @@ const createManualRegistration = async (req, res) => {
? prefFromBody
: (hasValidEmail && phone ? 'both' : phone ? 'whatsapp' : 'email');
// Always search by email AND/OR phone regardless of guestOnly.
// Always search by email AND/OR phone.
// Resolve each channel independently (rather than a single findFirst with an OR
// across both) so that an email belonging to one account and a phone number
// belonging to a *different* account can never be silently collapsed into
@@ -907,42 +906,58 @@ const createManualRegistration = async (req, res) => {
if (Object.keys(updateData).length > 0) {
await prisma.user.update({ where: { id: userId }, data: updateData }).catch(() => {});
}
} else if (!guestOnly && hasValidEmail) {
// Create a real active account (non-guest with email)
try {
const password = 'Hope123';
const response = await axios.post(
`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5000'}/api/users`,
{ name: user.name, email: user.email, password, phoneNumber: phone || null }
);
const createdUser = response.data.user || response.data;
if (!createdUser?.id) { res.status(400); throw new Error('User creation failed: No user ID returned'); }
userId = createdUser.id;
// Set derived preference on the new account
await prisma.user.update({ where: { id: userId }, data: { notificationPreference: derivedPref } }).catch(() => {});
} catch (userErr) {
res.status(400);
throw new Error(`Failed to create user: ${userErr.response?.data?.message || userErr.message}`);
}
} else {
// Guest path: phone-only, guestOnly=true, or no valid email
const placeholderEmail = hasValidEmail
? user.email
: `guest+${uuidv4().slice(0, 8)}@guest.local`;
const hashed = await hashPassword(uuidv4());
const created = await prisma.user.create({
data: {
id: uuidv4(),
name: user.name,
email: placeholderEmail,
password: hashed,
phoneNumber: phone || null,
isActive: false,
notificationPreference: derivedPref,
updatedAt: new Date(),
const suppliedPassword = typeof user.password === 'string' && user.password.trim().length >= 6
? user.password.trim()
: null;
if (hasValidEmail && suppliedPassword) {
// Caller supplied their own password (the self-service kiosk, where the
// visitor sets it themselves on the spot) — activate immediately, since
// there's nothing left for them to do via an activation link.
const hashed = await hashPassword(suppliedPassword);
const created = await prisma.user.create({
data: {
id: uuidv4(),
name: user.name,
email: user.email,
password: hashed,
phoneNumber: phone || null,
isActive: true,
notificationPreference: derivedPref,
updatedAt: new Date(),
}
});
userId = created.id;
} else {
// New account: uses the real email if a valid one was given, otherwise a
// guest.local placeholder (phone-only registration). Always created inactive
// with a random password — the visitor activates it themselves via the link
// sent immediately below (email or WhatsApp), unless the caller explicitly
// opted out of that nudge (e.g. a self-service visitor who declined to
// create an account at all).
const placeholderEmail = hasValidEmail
? user.email
: `guest+${uuidv4().slice(0, 8)}@guest.local`;
const hashed = await hashPassword(uuidv4());
const created = await prisma.user.create({
data: {
id: uuidv4(),
name: user.name,
email: placeholderEmail,
password: hashed,
phoneNumber: phone || null,
isActive: false,
notificationPreference: derivedPref,
updatedAt: new Date(),
}
});
userId = created.id;
if (!skipActivationNotice) {
const { sendActivationLink } = require('./userController');
sendActivationLink(created);
}
});
userId = created.id;
}
}
// Merge into existing non-cancelled registration if one exists, otherwise create new