tududi/backend/modules/auth/registrationService.js
Chris 542be2c1e9
Fix bug 366 (#764)
* Optimize DB

* Clean up names

* fixup! Clean up names

* fixup! fixup! Clean up names
2026-01-07 18:18:07 +02:00

238 lines
6.6 KiB
JavaScript

const crypto = require('crypto');
const { User, Setting } = require('../../models');
const { getConfig } = require('../../config/config');
const { logError, logInfo } = require('../../services/logService');
const { sendEmail } = require('../../services/emailService');
const { validateEmail, validatePassword } = require('../users/userService');
const isRegistrationEnabled = async () => {
const setting = await Setting.findOne({
where: { key: 'registration_enabled' },
});
// Default to false if setting doesn't exist
return setting ? setting.value === 'true' : false;
};
const setRegistrationEnabled = async (enabled) => {
await Setting.upsert({
key: 'registration_enabled',
value: String(enabled),
});
logInfo(`Registration ${enabled ? 'enabled' : 'disabled'} by admin`);
};
const generateVerificationToken = () => {
return crypto.randomBytes(32).toString('hex');
};
const getTokenExpirationDate = () => {
const config = getConfig();
const hours = config.registrationConfig.tokenExpiryHours;
const expirationDate = new Date();
expirationDate.setHours(expirationDate.getHours() + hours);
return expirationDate;
};
const createUnverifiedUser = async (email, password, transaction = null) => {
if (!validateEmail(email)) {
throw new Error('Invalid email format');
}
if (!validatePassword(password)) {
throw new Error('Password must be at least 6 characters long');
}
const existingUser = await User.findOne({
where: { email },
transaction,
});
if (existingUser) {
throw new Error('Email already registered');
}
const verificationToken = generateVerificationToken();
const tokenExpiresAt = getTokenExpirationDate();
const user = await User.create(
{
email,
password,
email_verified: false,
email_verification_token: verificationToken,
email_verification_token_expires_at: tokenExpiresAt,
},
{ transaction }
);
return {
user,
verificationToken,
tokenExpiresAt,
};
};
const isVerificationTokenValid = (token, expiresAt) => {
if (!token || !expiresAt) {
return false;
}
const now = new Date();
const expiration = new Date(expiresAt);
return now <= expiration;
};
const verifyUserEmail = async (token) => {
if (!token) {
throw new Error('Verification token is required');
}
const user = await User.findOne({
where: { email_verification_token: token },
});
if (!user) {
throw new Error('Invalid verification token');
}
if (user.email_verified) {
throw new Error('Email already verified');
}
if (
!isVerificationTokenValid(
token,
user.email_verification_token_expires_at
)
) {
throw new Error('Verification token has expired');
}
user.email_verified = true;
user.email_verification_token = null;
user.email_verification_token_expires_at = null;
await user.save();
return user;
};
const sendVerificationEmail = async (user, verificationToken) => {
const config = getConfig();
const { isEmailEnabled } = require('../../services/emailService');
if (!isEmailEnabled()) {
logInfo(
`Email service is disabled. Verification email for ${user.email} not sent. User must be verified manually.`
);
return { success: false, reason: 'Email service is disabled' };
}
const verificationUrl = `${config.backendUrl}/api/verify-email?token=${verificationToken}`;
const tokenExpiryHours = config.registrationConfig.tokenExpiryHours;
const subject = 'Welcome to Tududi - Verify your email';
const text = `Welcome to Tududi!
Thank you for registering. To complete your registration and start using Tududi, please verify your email address by clicking the link below:
${verificationUrl}
This verification link will expire in ${tokenExpiryHours} hours.
After verification, you'll be able to:
- Create and organize tasks with ease
- Manage projects and areas
- Set up recurring tasks
- Track your productivity
If you didn't create an account with Tududi, you can safely ignore this email.
Best regards,
The Tududi Team`;
const html = `
<p>Welcome to Tududi!</p>
<p>Thank you for registering. To complete your registration and start using Tududi, please verify your email address by clicking the button below:</p>
<p style="text-align: center; margin: 30px 0;">
<a href="${verificationUrl}" style="background-color: #3b82f6; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">Verify Email Address</a>
</p>
<p>Or copy and paste this link into your browser:</p>
<p style="word-break: break-all; color: #666;">${verificationUrl}</p>
<p><strong>This verification link will expire in ${tokenExpiryHours} hours.</strong></p>
<p>After verification, you'll be able to:</p>
<ul>
<li>Create and organize tasks with ease</li>
<li>Manage projects and areas</li>
<li>Set up recurring tasks</li>
<li>Track your productivity</li>
</ul>
<p>If you didn't create an account with Tududi, you can safely ignore this email.</p>
<p>Best regards,<br>The Tududi Team</p>
`;
const result = await sendEmail({
to: user.email,
subject,
text,
html,
});
if (result.success) {
logInfo(`Verification email sent to ${user.email}`);
return result;
} else {
logError(
new Error(result.reason),
`Failed to send verification email to ${user.email}`
);
return result;
}
};
const cleanupExpiredTokens = async () => {
const now = new Date();
try {
const result = await User.update(
{
email_verification_token: null,
email_verification_token_expires_at: null,
},
{
where: {
email_verified: false,
email_verification_token_expires_at: {
[require('sequelize').Op.lt]: now,
},
},
}
);
if (result[0] > 0) {
logInfo(`Cleaned up ${result[0]} expired verification tokens`);
}
return result[0];
} catch (error) {
logError(error, 'Failed to cleanup expired tokens');
return 0;
}
};
module.exports = {
isRegistrationEnabled,
setRegistrationEnabled,
generateVerificationToken,
createUnverifiedUser,
verifyUserEmail,
sendVerificationEmail,
isVerificationTokenValid,
cleanupExpiredTokens,
};