release: v2.11.2
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 2m0s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 31s

This commit is contained in:
2026-09-05 18:14:38 +01:00
parent 7fec2154e0
commit c4ae69d25f
40 changed files with 314 additions and 352 deletions
+9 -7
View File
@@ -1,8 +1,8 @@
// Admin user route registration and user-role management.
const { fetchAppSettings } = require('#src/data/app-settings');
const { renderAccountEmailTemplate } = require('#src/data/account-email-templates');
const { buildAuditChanges } = require('#src/data/audit-log');
const { formatAccountEmailExpiry, renderAccountEmailTemplate } = require('#src/data/account-email-templates');
const { buildAuditChanges, formatUserAgentLabel } = require('#src/data/audit-log');
module.exports = function registerUsersRoutes(app, deps) {
const pool = deps.pool;
@@ -187,8 +187,9 @@
const invitation = rows[0];
const token = createOneTimeToken();
const invitationUrl = getRequestOrigin(req) + '/accept-invite?token=' + encodeURIComponent(token);
await sendAccountEmail(settings, Object.assign({ to: invitation.email }, renderAccountEmailTemplate(settings['email.invitation_subject'], settings['email.invitation_body'], { url: invitationUrl, username: '', display_name: invitation.name || 'there', email: invitation.email, action_alignment: settings['email.invitation_button_alignment'], action_label: settings['email.invitation_button_text'] })));
await pool.query('UPDATE a_user_invitations SET token_hash = ?, expires_at = DATE_ADD(NOW(), INTERVAL 24 HOUR), created_at = NOW(), created_by = ? WHERE id = ? AND used_at IS NULL', [hashSessionToken(token), getAuditUserId(req), invitationId]);
const expiryHours = Number(settings['email.invitation_expiry_hours']);
await sendAccountEmail(settings, Object.assign({ to: invitation.email }, renderAccountEmailTemplate(settings['email.invitation_subject'], settings['email.invitation_body'], { url: invitationUrl, username: '', display_name: invitation.name || 'there', email: invitation.email, expiry_time: formatAccountEmailExpiry(expiryHours, 'hours'), action_alignment: settings['email.invitation_button_alignment'], action_label: settings['email.invitation_button_text'] })));
await pool.query('UPDATE a_user_invitations SET token_hash = ?, expires_at = DATE_ADD(NOW(), INTERVAL ' + expiryHours + ' HOUR), created_at = NOW(), created_by = ? WHERE id = ? AND used_at IS NULL', [hashSessionToken(token), getAuditUserId(req), invitationId]);
if (typeof recordRequestAuditEvent === 'function') await recordRequestAuditEvent(pool, req, { category: 'users', eventType: 'user.invitation_resent', actorUserId: getAuditUserId(req), targetType: 'invitation', targetId: invitationId, targetLabel: invitation.email });
res.redirect('/settings/invitations?message=' + encodeURIComponent('Invitation resent.'));
} catch (error) {
@@ -236,10 +237,11 @@
if (Number(recentInvites[0] && recentInvites[0].invite_count) >= 25) return renderInviteError('Invitation sending is temporarily limited. Try again later.');
const token = createOneTimeToken();
await pool.query('UPDATE a_user_invitations SET used_at = NOW() WHERE email = ? AND used_at IS NULL', [email]);
await pool.query('INSERT INTO a_user_invitations (email, name, role_ids_json, token_hash, expires_at, created_by) VALUES (?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL 24 HOUR), ?)', [email, name || null, JSON.stringify(roleCheck.roleIds), hashSessionToken(token), getAuditUserId(req)]);
const expiryHours = Number(settings['email.invitation_expiry_hours']);
await pool.query('INSERT INTO a_user_invitations (email, name, role_ids_json, token_hash, expires_at, created_by) VALUES (?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL ' + expiryHours + ' HOUR), ?)', [email, name || null, JSON.stringify(roleCheck.roleIds), hashSessionToken(token), getAuditUserId(req)]);
const invitationUrl = getRequestOrigin(req) + '/accept-invite?token=' + encodeURIComponent(token);
try {
await sendAccountEmail(settings, Object.assign({ to: email }, renderAccountEmailTemplate(settings['email.invitation_subject'], settings['email.invitation_body'], { url: invitationUrl, username: '', display_name: name || 'there', email: email, action_alignment: settings['email.invitation_button_alignment'], action_label: settings['email.invitation_button_text'] })));
await sendAccountEmail(settings, Object.assign({ to: email }, renderAccountEmailTemplate(settings['email.invitation_subject'], settings['email.invitation_body'], { url: invitationUrl, username: '', display_name: name || 'there', email: email, expiry_time: formatAccountEmailExpiry(expiryHours, 'hours'), action_alignment: settings['email.invitation_button_alignment'], action_label: settings['email.invitation_button_text'] })));
} catch (mailError) {
await pool.query('DELETE FROM a_user_invitations WHERE token_hash = ?', [hashSessionToken(token)]);
throw mailError;
@@ -307,7 +309,7 @@
return {
id: Number(session.id),
ipAddress: String(session.ip_address || 'Unknown'),
userAgent: String(session.user_agent || 'Unknown browser'),
userAgent: formatUserAgentLabel(session.user_agent) || 'Unknown browser',
createdAtLabel: formatDashboardDate(session.created_at),
lastUsedAtLabel: formatDashboardDate(session.last_used_at),
expiresAtLabel: formatDashboardDate(session.expires_at)