Updates to sending user details

This commit is contained in:
2025-08-03 13:26:20 -06:00
parent 6b4732db65
commit 86685a13a6
22 changed files with 1669 additions and 156 deletions

View File

@@ -2,6 +2,7 @@ const nocodbService = require('../services/nocodb');
const logger = require('../utils/logger');
const config = require('../config');
const { sanitizeUser, extractId } = require('../utils/helpers');
const { sendLoginDetails } = require('../services/email');
class UsersController {
async getAll(req, res) {
@@ -155,6 +156,47 @@ class UsersController {
});
}
}
async sendLoginDetails(req, res) {
try {
const userId = req.params.id;
if (!config.nocodb.loginSheetId) {
return res.status(500).json({
success: false,
error: 'Login sheet not configured'
});
}
// Get user data from database
const user = await nocodbService.getById(
config.nocodb.loginSheetId,
userId
);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
// Send login details email
await sendLoginDetails(user);
res.json({
success: true,
message: 'Login details sent successfully'
});
} catch (error) {
logger.error('Error sending login details:', error);
res.status(500).json({
success: false,
error: 'Failed to send login details'
});
}
}
}
module.exports = new UsersController();

View File

@@ -346,7 +346,7 @@ function setupEventListeners() {
}
// User form submission
const userForm = document.getElementById('user-form');
const userForm = document.getElementById('create-user-form');
if (userForm) {
userForm.addEventListener('submit', createUser);
}
@@ -1269,6 +1269,9 @@ function displayUsers(users) {
<td data-label="Created">${formattedDate}</td>
<td data-label="Actions">
<div class="user-actions">
<button class="btn btn-secondary send-login-btn" data-user-id="${userId}" data-user-email="${escapeHtml(user.email || user.Email)}">
Send Login Details
</button>
<button class="btn btn-danger delete-user-btn" data-user-id="${userId}" data-user-email="${escapeHtml(user.email || user.Email)}">
Delete
</button>
@@ -1288,22 +1291,27 @@ function displayUsers(users) {
}
function setupUserActionListeners() {
const tableBody = document.getElementById('users-table-body');
if (!tableBody) return;
const container = document.querySelector('.users-list');
if (!container) return;
// Remove existing listeners by cloning the node
const newTableBody = tableBody.cloneNode(true);
tableBody.parentNode.replaceChild(newTableBody, tableBody);
// Remove existing event listeners by cloning the container
const newContainer = container.cloneNode(true);
container.parentNode.replaceChild(newContainer, container);
// Get the updated reference
const updatedTableBody = document.getElementById('users-table-body');
const updatedContainer = document.querySelector('.users-list');
updatedTableBody.addEventListener('click', function(e) {
updatedContainer.addEventListener('click', function(e) {
if (e.target.classList.contains('delete-user-btn')) {
const userId = e.target.getAttribute('data-user-id');
const userEmail = e.target.getAttribute('data-user-email');
console.log('Delete button clicked for user:', userId);
deleteUser(userId, userEmail);
} else if (e.target.classList.contains('send-login-btn')) {
const userId = e.target.getAttribute('data-user-id');
const userEmail = e.target.getAttribute('data-user-email');
console.log('Send login details button clicked for user:', userId);
sendLoginDetailsToUser(userId, userEmail);
}
});
}
@@ -1333,13 +1341,40 @@ async function deleteUser(userId, userEmail) {
}
}
async function sendLoginDetailsToUser(userId, userEmail) {
if (!confirm(`Send login details to "${userEmail}"?`)) {
return;
}
try {
const response = await fetch(`/api/users/${userId}/send-login-details`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (data.success) {
showStatus(`Login details sent to "${userEmail}" successfully`, 'success');
} else {
throw new Error(data.error || 'Failed to send login details');
}
} catch (error) {
console.error('Error sending login details:', error);
showStatus(`Failed to send login details: ${error.message}`, 'error');
}
}
async function createUser(e) {
e.preventDefault();
const email = document.getElementById('user-email').value.trim();
const password = document.getElementById('user-password').value;
const name = document.getElementById('user-name').value.trim();
const admin = document.getElementById('user-admin').checked;
const admin = document.getElementById('user-is-admin').checked;
if (!email || !password) {
showStatus('Email and password are required', 'error');
@@ -1382,7 +1417,7 @@ async function createUser(e) {
}
function clearUserForm() {
const form = document.getElementById('user-form');
const form = document.getElementById('create-user-form');
if (form) {
form.reset();
showStatus('User form cleared', 'info');

View File

@@ -8,6 +8,9 @@ router.get('/', usersController.getAll);
// Create new user
router.post('/', usersController.create);
// Send login details to user
router.post('/:id/send-login-details', usersController.sendLoginDetails);
// Delete user
router.delete('/:id', usersController.delete);

View File

@@ -92,8 +92,41 @@ const sendPasswordRecovery = async (user) => {
}
};
const sendLoginDetails = async (user) => {
try {
const baseUrl = config.isProduction ?
`https://map.${config.domain}` :
`http://localhost:${config.port}`;
const isAdmin = user.admin || user.Admin || false;
const variables = {
APP_NAME: process.env.APP_NAME || 'CMlite Map',
USER_NAME: user.Name || user.name || user.Email || user.email,
USER_EMAIL: user.Email || user.email,
PASSWORD: user.Password || user.password,
USER_ROLE: isAdmin ? 'Administrator' : 'User',
LOGIN_URL: `${baseUrl}/login.html`,
TIMESTAMP: new Date().toLocaleString()
};
const { html, text } = await emailTemplates.render('login-details', variables);
return await sendEmail({
to: user.Email || user.email,
subject: `Your Login Details - ${variables.APP_NAME}`,
text,
html
});
} catch (error) {
logger.error('Failed to send login details email:', error);
throw error;
}
};
module.exports = {
initializeEmailService,
sendEmail,
sendPasswordRecovery
sendPasswordRecovery,
sendLoginDetails
};

View File

@@ -0,0 +1,116 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Your Login Details</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.container {
background-color: #f9f9f9;
border-radius: 8px;
padding: 30px;
border: 1px solid #e0e0e0;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.logo {
color: #a02c8d;
font-size: 24px;
font-weight: bold;
}
.content {
background-color: white;
padding: 20px;
border-radius: 6px;
margin-bottom: 20px;
}
.credentials-box {
background-color: #f0f0f0;
padding: 15px;
border-radius: 4px;
margin: 20px 0;
border: 1px solid #ddd;
}
.credential-item {
margin: 10px 0;
}
.credential-label {
font-weight: bold;
display: inline-block;
width: 100px;
}
.credential-value {
font-family: monospace;
font-size: 16px;
color: #2c3e50;
}
.login-button {
display: inline-block;
background-color: #a02c8d;
color: white;
padding: 12px 24px;
text-decoration: none;
border-radius: 4px;
margin: 20px 0;
}
.footer {
text-align: center;
font-size: 12px;
color: #666;
margin-top: 30px;
}
.info {
color: #3498db;
font-size: 14px;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo">{{APP_NAME}}</div>
</div>
<div class="content">
<h2>Your Login Details</h2>
<p>Hello {{USER_NAME}},</p>
<p>Here are your login credentials for {{APP_NAME}}:</p>
<div class="credentials-box">
<div class="credential-item">
<span class="credential-label">Email:</span>
<span class="credential-value">{{USER_EMAIL}}</span>
</div>
<div class="credential-item">
<span class="credential-label">Password:</span>
<span class="credential-value">{{PASSWORD}}</span>
</div>
<div class="credential-item">
<span class="credential-label">Role:</span>
<span class="credential-value">{{USER_ROLE}}</span>
</div>
</div>
<p>You can log in using the link below:</p>
<p style="text-align: center;">
<a href="{{LOGIN_URL}}" class="login-button">Login to {{APP_NAME}}</a>
</p>
<p class="info">💡 For security reasons, we recommend changing your password after your first login.</p>
</div>
<div class="footer">
<p>This email was sent from {{APP_NAME}} at {{TIMESTAMP}}</p>
<p>If you have any questions, please contact your administrator.</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,17 @@
Login Details - {{APP_NAME}}
Hello {{USER_NAME}},
Here are your login credentials for {{APP_NAME}}:
Email: {{USER_EMAIL}}
Password: {{PASSWORD}}
Role: {{USER_ROLE}}
You can log in at: {{LOGIN_URL}}
For security reasons, we recommend changing your password after your first login.
---
This email was sent from {{APP_NAME}} at {{TIMESTAMP}}
If you have any questions, please contact your administrator.

View File

@@ -62,7 +62,7 @@ Controller for aggregating and calculating dashboard statistics from locations a
# app/controllers/usersController.js
Controller for user management (list, create, delete users).
Controller for user management (list, create, delete users, send login details via email).
# app/middleware/auth.js
@@ -98,7 +98,7 @@ Service for generating QR codes and handling QR-related logic.
# app/services/email.js
Service for sending emails via SMTP, including password recovery emails using nodemailer. Supports multiple SMTP providers and includes connection verification and error handling.
Service for sending emails via SMTP, including password recovery emails and login details using nodemailer. Supports multiple SMTP providers and includes connection verification and error handling.
# app/services/emailTemplates.js
@@ -112,6 +112,14 @@ Plain text email template for password recovery notifications. Contains user-fri
HTML email template for password recovery notifications. Features responsive design with styled password display box and security warnings for better user experience.
# app/templates/email/login-details.txt
Plain text email template for sending login credentials to users. Contains email, password, role, and login URL with security recommendations.
# app/templates/email/login-details.html
HTML email template for sending login credentials to users. Features responsive design with styled credentials display and login button for better user experience.
# app/utils/helpers.js
Utility functions for geographic data, validation, and helpers used across the backend.