Updates to sending user details
This commit is contained in:
@@ -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();
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
116
map/app/templates/email/login-details.html
Normal file
116
map/app/templates/email/login-details.html
Normal 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>
|
||||
17
map/app/templates/email/login-details.txt
Normal file
17
map/app/templates/email/login-details.txt
Normal 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.
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user