diff --git a/create-a-container/public/style.css b/create-a-container/public/style.css index 52201bd5..f88134b3 100644 --- a/create-a-container/public/style.css +++ b/create-a-container/public/style.css @@ -368,3 +368,39 @@ main { .navbar { border-bottom: 2px solid #1a252f; } + +/* Alert/Message boxes */ +.alert { + margin-bottom: 1rem; + padding: 0.75rem; + border: 1px solid transparent; + border-radius: 4px; +} + +.alert-success { + color: #155724; + background-color: #d4edda; + border-color: #c3e6cb; +} + +.alert-error { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; +} + +.alert-info { + color: #004085; + background-color: #cce5ff; + border-color: #b8daff; +} + +.alert-info strong { + display: block; + margin-bottom: 0.5rem; +} + +.alert-info p { + margin: 0.5rem 0 0 0; + font-size: 0.9em; +} diff --git a/create-a-container/routers/apikeys.js b/create-a-container/routers/apikeys.js index b14d4808..2918f301 100644 --- a/create-a-container/routers/apikeys.js +++ b/create-a-container/routers/apikeys.js @@ -11,7 +11,7 @@ router.use(requireAuth); router.get('/', async (req, res) => { const user = await User.findOne({ where: { uid: req.session.user } }); if (!user) { - req.flash('error', 'User not found'); + await req.flash('error', 'User not found'); return res.redirect('/login'); } @@ -46,7 +46,7 @@ router.get('/new', (req, res) => { router.post('/', async (req, res) => { const user = await User.findOne({ where: { uid: req.session.user } }); if (!user) { - req.flash('error', 'User not found'); + await req.flash('error', 'User not found'); return res.redirect('/login'); } @@ -79,7 +79,7 @@ router.post('/', async (req, res) => { }); } - req.flash('success', 'API key created successfully. This is the only time it will be shown!'); + await req.flash('success', 'API key created successfully. This is the only time it will be shown!'); return res.render('apikeys/created', { plainKey: apiKeyData.plainKey, apiKey, @@ -91,7 +91,7 @@ router.post('/', async (req, res) => { router.get('/:id', async (req, res) => { const user = await User.findOne({ where: { uid: req.session.user } }); if (!user) { - req.flash('error', 'User not found'); + await req.flash('error', 'User not found'); return res.redirect('/login'); } @@ -113,7 +113,7 @@ router.get('/:id', async (req, res) => { return res.status(404).json({ error: 'API key not found' }); } - req.flash('error', 'API key not found'); + await req.flash('error', 'API key not found'); return res.redirect('/apikeys'); } @@ -135,7 +135,7 @@ router.get('/:id', async (req, res) => { router.delete('/:id', async (req, res) => { const user = await User.findOne({ where: { uid: req.session.user } }); if (!user) { - req.flash('error', 'User not found'); + await req.flash('error', 'User not found'); return res.redirect('/login'); } @@ -156,7 +156,7 @@ router.delete('/:id', async (req, res) => { return res.status(404).json({ error: 'API key not found' }); } - req.flash('error', 'API key not found'); + await req.flash('error', 'API key not found'); return res.redirect('/apikeys'); } @@ -170,7 +170,7 @@ router.delete('/:id', async (req, res) => { return res.status(204).send(); } - req.flash('success', 'API key deleted successfully'); + await req.flash('success', 'API key deleted successfully'); return res.redirect('/apikeys'); }); diff --git a/create-a-container/routers/containers.js b/create-a-container/routers/containers.js index d7e4bcdd..43e3ca1f 100644 --- a/create-a-container/routers/containers.js +++ b/create-a-container/routers/containers.js @@ -67,7 +67,7 @@ router.get('/new', requireAuth, async (req, res) => { const siteId = parseInt(req.params.siteId, 10); const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -121,7 +121,7 @@ router.get('/', requireAuth, async (req, res) => { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -186,7 +186,7 @@ router.get('/:id/edit', requireAuth, async (req, res) => { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -228,7 +228,7 @@ router.get('/:id/edit', requireAuth, async (req, res) => { }); if (!container) { - req.flash('error', 'Container not found'); + await req.flash('error', 'Container not found'); return res.redirect(`/sites/${siteId}/containers`); } @@ -255,7 +255,7 @@ router.post('/', async (req, res) => { // Validate site exists const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -422,7 +422,7 @@ router.post('/', async (req, res) => { // Commit the transaction await t.commit(); - req.flash('success', `Container "${hostname}" is being created. Check back shortly for status updates.`); + await req.flash('success', `Container "${hostname}" is being created. Check back shortly for status updates.`); return res.redirect(`/jobs/${job.id}`); } catch (err) { // Rollback the transaction @@ -444,7 +444,7 @@ router.post('/', async (req, res) => { errorMessage += err.message; } - req.flash('error', errorMessage); + await req.flash('error', errorMessage); return res.redirect(`/sites/${siteId}/containers/new`); } }); @@ -456,7 +456,7 @@ router.put('/:id', requireAuth, async (req, res) => { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -477,7 +477,7 @@ router.put('/:id', requireAuth, async (req, res) => { }); if (!container) { - req.flash('error', 'Container not found'); + await req.flash('error', 'Container not found'); return res.redirect(`/sites/${siteId}/containers`); } @@ -624,15 +624,15 @@ router.put('/:id', requireAuth, async (req, res) => { }); if (restartJob) { - req.flash('success', 'Container configuration updated. Restarting container...'); + await req.flash('success', 'Container configuration updated. Restarting container...'); return res.redirect(`/jobs/${restartJob.id}`); } else { - req.flash('success', 'Container services updated successfully'); + await req.flash('success', 'Container services updated successfully'); } return res.redirect(`/sites/${siteId}/containers`); } catch (err) { console.error('Error updating container:', err); - req.flash('error', 'Failed to update container: ' + err.message); + await req.flash('error', 'Failed to update container: ' + err.message); return res.redirect(`/sites/${siteId}/containers/${containerId}/edit`); } }); @@ -645,7 +645,7 @@ router.delete('/:id', requireAuth, async (req, res) => { // Validate site exists const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -663,24 +663,24 @@ router.delete('/:id', requireAuth, async (req, res) => { }); if (!container) { - req.flash('error', 'Container not found'); + await req.flash('error', 'Container not found'); return res.redirect(`/sites/${siteId}/containers`); } // Verify the container's node belongs to this site if (!container.node || container.node.siteId !== siteId) { - req.flash('error', 'Container does not belong to this site'); + await req.flash('error', 'Container does not belong to this site'); return res.redirect(`/sites/${siteId}/containers`); } const node = container.node; if (!node.apiUrl) { - req.flash('error', 'Node API URL not configured'); + await req.flash('error', 'Node API URL not configured'); return res.redirect(`/sites/${siteId}/containers`); } if (!node.tokenId || !node.secret) { - req.flash('error', 'Node API token not configured'); + await req.flash('error', 'Node API token not configured'); return res.redirect(`/sites/${siteId}/containers`); } @@ -696,7 +696,7 @@ router.delete('/:id', requireAuth, async (req, res) => { if (proxmoxHostname && proxmoxHostname !== container.hostname) { console.error(`Hostname mismatch: DB has "${container.hostname}", Proxmox has "${proxmoxHostname}" for VMID ${container.containerId}`); - req.flash('error', `Safety check failed: Proxmox container hostname "${proxmoxHostname}" does not match database hostname "${container.hostname}". Manual intervention required.`); + await req.flash('error', `Safety check failed: Proxmox container hostname "${proxmoxHostname}" does not match database hostname "${container.hostname}". Manual intervention required.`); return res.redirect(`/sites/${siteId}/containers`); } @@ -721,11 +721,11 @@ router.delete('/:id', requireAuth, async (req, res) => { await container.destroy(); } catch (error) { console.error(error); - req.flash('error', `Failed to delete container: ${error.message}`); + await req.flash('error', `Failed to delete container: ${error.message}`); return res.redirect(`/sites/${siteId}/containers`); } - req.flash('success', `Container ${container.hostname} deleted successfully`); + await req.flash('success', `Container ${container.hostname} deleted successfully`); return res.redirect(`/sites/${siteId}/containers`); }); diff --git a/create-a-container/routers/external-domains.js b/create-a-container/routers/external-domains.js index a6d5b2e1..76c3e3a8 100644 --- a/create-a-container/routers/external-domains.js +++ b/create-a-container/routers/external-domains.js @@ -15,7 +15,7 @@ router.get('/', async (req, res) => { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -45,7 +45,7 @@ router.get('/new', requireAdmin, async (req, res) => { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -64,7 +64,7 @@ router.get('/:id/edit', requireAdmin, async (req, res) => { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -73,7 +73,7 @@ router.get('/:id/edit', requireAdmin, async (req, res) => { }); if (!externalDomain) { - req.flash('error', 'External domain not found'); + await req.flash('error', 'External domain not found'); return res.redirect(`/sites/${siteId}/external-domains`); } @@ -91,7 +91,7 @@ router.post('/', requireAdmin, async (req, res) => { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -159,19 +159,19 @@ router.post('/', requireAdmin, async (req, res) => { const { stdout, stderr } = await run('lego', legoArgs, { env }); console.log(`Certificate provisioned for ${externalDomain.name}`); - req.flash('success', `External domain ${name} created and certificate provisioned successfully`); + await req.flash('success', `External domain ${name} created and certificate provisioned successfully`); } catch (certError) { console.error('Certificate provisioning error:', certError); - req.flash('warning', `External domain ${name} created, but certificate provisioning failed: ${certError.message}`); + await req.flash('warning', `External domain ${name} created, but certificate provisioning failed: ${certError.message}`); } } else { - req.flash('success', `External domain ${name} created successfully (certificate provisioning skipped - missing required fields)`); + await req.flash('success', `External domain ${name} created successfully (certificate provisioning skipped - missing required fields)`); } return res.redirect(`/sites/${siteId}/external-domains`); } catch (error) { console.error('Error creating external domain:', error); - req.flash('error', 'Failed to create external domain: ' + error.message); + await req.flash('error', 'Failed to create external domain: ' + error.message); return res.redirect(`/sites/${siteId}/external-domains/new`); } }); @@ -183,7 +183,7 @@ router.put('/:id', requireAdmin, async (req, res) => { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -193,7 +193,7 @@ router.put('/:id', requireAdmin, async (req, res) => { }); if (!externalDomain) { - req.flash('error', 'External domain not found'); + await req.flash('error', 'External domain not found'); return res.redirect(`/sites/${siteId}/external-domains`); } @@ -213,11 +213,11 @@ router.put('/:id', requireAdmin, async (req, res) => { await externalDomain.update(updateData); - req.flash('success', `External domain ${name} updated successfully`); + await req.flash('success', `External domain ${name} updated successfully`); return res.redirect(`/sites/${siteId}/external-domains`); } catch (error) { console.error('Error updating external domain:', error); - req.flash('error', 'Failed to update external domain: ' + error.message); + await req.flash('error', 'Failed to update external domain: ' + error.message); return res.redirect(`/sites/${siteId}/external-domains/${domainId}/edit`); } }); @@ -229,7 +229,7 @@ router.delete('/:id', requireAdmin, async (req, res) => { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -239,18 +239,18 @@ router.delete('/:id', requireAdmin, async (req, res) => { }); if (!externalDomain) { - req.flash('error', 'External domain not found'); + await req.flash('error', 'External domain not found'); return res.redirect(`/sites/${siteId}/external-domains`); } const domainName = externalDomain.name; await externalDomain.destroy(); - req.flash('success', `External domain ${domainName} deleted successfully`); + await req.flash('success', `External domain ${domainName} deleted successfully`); return res.redirect(`/sites/${siteId}/external-domains`); } catch (error) { console.error('Error deleting external domain:', error); - req.flash('error', 'Failed to delete external domain: ' + error.message); + await req.flash('error', 'Failed to delete external domain: ' + error.message); return res.redirect(`/sites/${siteId}/external-domains`); } }); diff --git a/create-a-container/routers/groups.js b/create-a-container/routers/groups.js index 8f74b0bc..e34c17f8 100644 --- a/create-a-container/routers/groups.js +++ b/create-a-container/routers/groups.js @@ -46,7 +46,7 @@ router.get('/:id/edit', async (req, res) => { const group = await Group.findByPk(req.params.id); if (!group) { - req.flash('error', 'Group not found'); + await req.flash('error', 'Group not found'); return res.redirect('/groups'); } @@ -68,11 +68,11 @@ router.post('/', async (req, res) => { isAdmin: isAdmin === 'on' || isAdmin === 'true' }); - req.flash('success', `Group ${cn} created successfully`); + await req.flash('success', `Group ${cn} created successfully`); return res.redirect('/groups'); } catch (error) { console.error('Error creating group:', error); - req.flash('error', 'Failed to create group: ' + error.message); + await req.flash('error', 'Failed to create group: ' + error.message); return res.redirect('/groups/new'); } }); @@ -83,7 +83,7 @@ router.put('/:id', async (req, res) => { const group = await Group.findByPk(req.params.id); if (!group) { - req.flash('error', 'Group not found'); + await req.flash('error', 'Group not found'); return res.redirect('/groups'); } @@ -94,11 +94,11 @@ router.put('/:id', async (req, res) => { isAdmin: isAdmin === 'on' || isAdmin === 'true' }); - req.flash('success', `Group ${cn} updated successfully`); + await req.flash('success', `Group ${cn} updated successfully`); return res.redirect('/groups'); } catch (error) { console.error('Error updating group:', error); - req.flash('error', 'Failed to update group: ' + error.message); + await req.flash('error', 'Failed to update group: ' + error.message); return res.redirect(`/groups/${req.params.id}/edit`); } }); @@ -109,18 +109,18 @@ router.delete('/:id', async (req, res) => { const group = await Group.findByPk(req.params.id); if (!group) { - req.flash('error', 'Group not found'); + await req.flash('error', 'Group not found'); return res.redirect('/groups'); } const groupName = group.cn; await group.destroy(); - req.flash('success', `Group ${groupName} deleted successfully`); + await req.flash('success', `Group ${groupName} deleted successfully`); return res.redirect('/groups'); } catch (error) { console.error('Error deleting group:', error); - req.flash('error', 'Failed to delete group: ' + error.message); + await req.flash('error', 'Failed to delete group: ' + error.message); return res.redirect('/groups'); } }); diff --git a/create-a-container/routers/jobs.js b/create-a-container/routers/jobs.js index 874790a7..e9913ac0 100644 --- a/create-a-container/routers/jobs.js +++ b/create-a-container/routers/jobs.js @@ -44,7 +44,7 @@ router.get('/:id', async (req, res) => { const job = await Job.findByPk(id); if (!job) { if (req.accepts('html')) { - req.flash('error', 'Job not found'); + await req.flash('error', 'Job not found'); return res.redirect('/'); } return res.status(404).json({ error: 'Job not found' }); @@ -53,7 +53,7 @@ router.get('/:id', async (req, res) => { // Authorization: only owner or admin can view if (!await canAccessJob(job, req)) { if (req.accepts('html')) { - req.flash('error', 'Job not found'); + await req.flash('error', 'Job not found'); return res.redirect('/'); } return res.status(404).json({ error: 'Job not found' }); @@ -94,7 +94,7 @@ router.get('/:id', async (req, res) => { } catch (err) { console.error('Failed to fetch job:', err); if (req.accepts('html')) { - req.flash('error', 'Failed to load job'); + await req.flash('error', 'Failed to load job'); return res.redirect('/'); } return res.status(500).json({ error: 'Failed to fetch job' }); diff --git a/create-a-container/routers/login.js b/create-a-container/routers/login.js index 955964e9..0a06e7f0 100644 --- a/create-a-container/routers/login.js +++ b/create-a-container/routers/login.js @@ -20,18 +20,18 @@ router.post('/', async (req, res) => { include: [{ association: 'groups' }] }); if (!user) { - req.flash('error', 'Invalid username or password'); + await req.flash('error', 'Invalid username or password'); return res.redirect('/login'); } const isValidPassword = await user.validatePassword(password); if (!isValidPassword) { - req.flash('error', 'Invalid username or password'); + await req.flash('error', 'Invalid username or password'); return res.redirect('/login'); } if (user.status !== 'active') { - req.flash('error', 'Account is not active. Please contact the administrator.'); + await req.flash('error', 'Account is not active. Please contact the administrator.'); return res.redirect('/login'); } @@ -65,22 +65,29 @@ router.post('/', async (req, res) => { // Check for no device found error if (result.success === false && result.error?.includes('No device found with this Username')) { const registrationUrl = pushNotificationUrl; - req.flash('error', `No device found with this username. Please register your device at: ${registrationUrl}`); + await req.flash('error', `No device found with this username. Please register your device at: ${registrationUrl}`); return res.redirect('/login'); } if (!response.ok) { - req.flash('error', 'Failed to send push notification. Please contact support.'); + await req.flash('error', 'Failed to send push notification. Please contact support.'); return res.redirect('/login'); } - if (result.action?.toUpperCase() !== 'APPROVE') { - req.flash('error', 'Authentication request was denied'); + if (result.action !== 'approve') { + // Distinguish between different failure scenarios + if (result.action === 'reject') { + await req.flash('error', 'Second factor push notification was denied.'); + } else if (result.action === 'timeout') { + await req.flash('error', 'Second factor push notification timed out. Please try again.'); + } else { + await req.flash('error', `Second factor push notification failed: ${result.action}. Please contact support.`); + } return res.redirect('/login'); } } catch (error) { console.error('Push notification error:', error); - req.flash('error', 'Failed to send push notification. Please contact support.'); + await req.flash('error', 'Failed to send push notification. Please contact support.'); return res.redirect('/login'); } } diff --git a/create-a-container/routers/nodes.js b/create-a-container/routers/nodes.js index 921949ec..8185d001 100644 --- a/create-a-container/routers/nodes.js +++ b/create-a-container/routers/nodes.js @@ -16,7 +16,7 @@ router.get('/', async (req, res) => { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -52,7 +52,7 @@ router.get('/new', async (req, res) => { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -70,7 +70,7 @@ router.get('/import', async (req, res) => { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -84,7 +84,7 @@ router.get('/:id/edit', async (req, res) => { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -94,7 +94,7 @@ router.get('/:id/edit', async (req, res) => { }); if (!node) { - req.flash('error', 'Node not found'); + await req.flash('error', 'Node not found'); return res.redirect(`/sites/${siteId}/nodes`); } @@ -113,7 +113,7 @@ router.post('/', async (req, res) => { try { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -130,11 +130,11 @@ router.post('/', async (req, res) => { siteId }); - req.flash('success', `Node ${name} created successfully`); + await req.flash('success', `Node ${name} created successfully`); return res.redirect(`/sites/${siteId}/nodes`); } catch (err) { console.error('Error creating node:', err); - req.flash('error', `Failed to create node: ${err.message}`); + await req.flash('error', `Failed to create node: ${err.message}`); return res.redirect(`/sites/${siteId}/nodes/new`); } }); @@ -144,7 +144,7 @@ router.post('/import', async (req, res) => { const siteId = parseInt(req.params.siteId, 10); const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -223,7 +223,7 @@ router.post('/import', async (req, res) => { res.redirect(`/sites/${siteId}/nodes`); } catch (err) { console.log(err); - req.flash('error', `Failed to import nodes: ${err.message}`); + await req.flash('error', `Failed to import nodes: ${err.message}`); return res.redirect(`/sites/${siteId}/nodes/import`); } }); @@ -236,7 +236,7 @@ router.put('/:id', async (req, res) => { try { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -245,7 +245,7 @@ router.put('/:id', async (req, res) => { }); if (!node) { - req.flash('error', 'Node not found'); + await req.flash('error', 'Node not found'); return res.redirect(`/sites/${siteId}/nodes`); } @@ -267,11 +267,11 @@ router.put('/:id', async (req, res) => { await node.update(updateData); - req.flash('success', `Node ${name} updated successfully`); + await req.flash('success', `Node ${name} updated successfully`); return res.redirect(`/sites/${siteId}/nodes`); } catch (err) { console.error('Error updating node:', err); - req.flash('error', `Failed to update node: ${err.message}`); + await req.flash('error', `Failed to update node: ${err.message}`); return res.redirect(`/sites/${siteId}/nodes/${nodeId}/edit`); } }); @@ -312,7 +312,7 @@ router.delete('/:id', async (req, res) => { try { const site = await Site.findByPk(siteId); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -322,23 +322,23 @@ router.delete('/:id', async (req, res) => { }); if (!node) { - req.flash('error', 'Node not found'); + await req.flash('error', 'Node not found'); return res.redirect(`/sites/${siteId}/nodes`); } // Check if node has containers if (node.containers && node.containers.length > 0) { - req.flash('error', `Cannot delete node ${node.name}: ${node.containers.length} container(s) still reference this node`); + await req.flash('error', `Cannot delete node ${node.name}: ${node.containers.length} container(s) still reference this node`); return res.redirect(`/sites/${siteId}/nodes`); } await node.destroy(); - req.flash('success', `Node ${node.name} deleted successfully`); + await req.flash('success', `Node ${node.name} deleted successfully`); return res.redirect(`/sites/${siteId}/nodes`); } catch (err) { console.error('Error deleting node:', err); - req.flash('error', `Failed to delete node: ${err.message}`); + await req.flash('error', `Failed to delete node: ${err.message}`); return res.redirect(`/sites/${siteId}/nodes`); } }); diff --git a/create-a-container/routers/register.js b/create-a-container/routers/register.js index 67c93b5e..bebc0eec 100644 --- a/create-a-container/routers/register.js +++ b/create-a-container/routers/register.js @@ -26,11 +26,24 @@ router.post('/', async (req, res) => { try { await User.create(userParams); - req.flash('success', 'Account registered successfully. You will be notified via email once approved.'); + await req.flash('success', 'Account registered successfully. You will be notified via email once approved.'); return res.redirect('/login'); } catch (err) { console.error('Registration error:', err); - req.flash('error', 'Registration failed: ' + err.message); + + // Handle Sequelize unique constraint errors with user-friendly messages + if (err.name === 'SequelizeUniqueConstraintError' && err.errors && err.errors.length > 0) { + const field = err.errors[0]?.path; + if (field === 'uid') { + await req.flash('error', 'This username is already registered. Please choose a different username or login with your existing account.'); + } else if (field === 'mail') { + await req.flash('error', 'This email address is already registered. Please use a different email or login with your existing account.'); + } else { + await req.flash('error', 'A user with these details is already registered. Please login with your existing account.'); + } + } else { + await req.flash('error', 'Registration failed: ' + err.message); + } return res.redirect('/register'); } }); diff --git a/create-a-container/routers/reset-password.js b/create-a-container/routers/reset-password.js index 84ae3aea..2832949e 100644 --- a/create-a-container/routers/reset-password.js +++ b/create-a-container/routers/reset-password.js @@ -16,7 +16,7 @@ router.post('/', async (req, res) => { const { usernameOrEmail } = req.body; if (!usernameOrEmail || usernameOrEmail.trim() === '') { - req.flash('error', 'Please enter your username or email address'); + await req.flash('error', 'Please enter your username or email address'); return res.redirect('/reset-password'); } @@ -32,7 +32,7 @@ router.post('/', async (req, res) => { }); if (!user) { - req.flash('error', 'User not found'); + await req.flash('error', 'User not found'); return res.redirect('/reset-password'); } @@ -45,16 +45,16 @@ router.post('/', async (req, res) => { // Send email try { await sendPasswordResetEmail(user.mail, user.uid, resetUrl); - req.flash('success', 'Password reset instructions have been sent to your email address'); + await req.flash('success', 'Password reset instructions have been sent to your email address'); return res.redirect('/login'); } catch (emailError) { console.error('Failed to send password reset email:', emailError); - req.flash('error', 'Password reset failed, please contact an administrator'); + await req.flash('error', 'Password reset failed, please contact an administrator'); return res.redirect('/reset-password'); } } catch (error) { console.error('Password reset error:', error); - req.flash('error', 'Password reset failed, please contact an administrator'); + await req.flash('error', 'Password reset failed, please contact an administrator'); return res.redirect('/reset-password'); } }); @@ -67,7 +67,7 @@ router.get('/:token', async (req, res) => { const resetToken = await PasswordResetToken.validateToken(token); if (!resetToken) { - req.flash('error', 'Invalid or expired password reset link'); + await req.flash('error', 'Invalid or expired password reset link'); return res.redirect('/login'); } @@ -79,7 +79,7 @@ router.get('/:token', async (req, res) => { }); } catch (error) { console.error('Password reset token validation error:', error); - req.flash('error', 'Password reset failed, please contact an administrator'); + await req.flash('error', 'Password reset failed, please contact an administrator'); return res.redirect('/login'); } }); @@ -91,17 +91,17 @@ router.post('/:token', async (req, res) => { // Validate passwords if (!password || !confirmPassword) { - req.flash('error', 'Please enter and confirm your new password'); + await req.flash('error', 'Please enter and confirm your new password'); return res.redirect(`/reset-password/${token}`); } if (password !== confirmPassword) { - req.flash('error', 'Passwords do not match'); + await req.flash('error', 'Passwords do not match'); return res.redirect(`/reset-password/${token}`); } if (password.length < 8) { - req.flash('error', 'Password must be at least 8 characters long'); + await req.flash('error', 'Password must be at least 8 characters long'); return res.redirect(`/reset-password/${token}`); } @@ -109,7 +109,7 @@ router.post('/:token', async (req, res) => { const resetToken = await PasswordResetToken.validateToken(token); if (!resetToken) { - req.flash('error', 'Invalid or expired password reset link'); + await req.flash('error', 'Invalid or expired password reset link'); return res.redirect('/login'); } @@ -121,11 +121,11 @@ router.post('/:token', async (req, res) => { // Mark token as used await resetToken.markAsUsed(); - req.flash('success', 'Your password has been reset successfully. Please log in with your new password.'); + await req.flash('success', 'Your password has been reset successfully. Please log in with your new password.'); return res.redirect('/login'); } catch (error) { console.error('Password reset error:', error); - req.flash('error', 'Password reset failed, please contact an administrator'); + await req.flash('error', 'Password reset failed, please contact an administrator'); return res.redirect(`/reset-password/${token}`); } }); diff --git a/create-a-container/routers/settings.js b/create-a-container/routers/settings.js index a5ef7555..37d308a6 100644 --- a/create-a-container/routers/settings.js +++ b/create-a-container/routers/settings.js @@ -34,7 +34,7 @@ router.post('/', async (req, res) => { const enabled = push_notification_enabled === 'on'; if (enabled && (!push_notification_url || push_notification_url.trim() === '')) { - req.flash('error', 'Push notification URL is required when push notifications are enabled'); + await req.flash('error', 'Push notification URL is required when push notifications are enabled'); return res.redirect('/settings'); } @@ -43,7 +43,7 @@ router.post('/', async (req, res) => { await Setting.set('smtp_url', smtp_url || ''); await Setting.set('smtp_noreply_address', smtp_noreply_address || ''); - req.flash('success', 'Settings saved successfully'); + await req.flash('success', 'Settings saved successfully'); return res.redirect('/settings'); }); diff --git a/create-a-container/routers/sites.js b/create-a-container/routers/sites.js index 17fc23a0..0824deae 100644 --- a/create-a-container/routers/sites.js +++ b/create-a-container/routers/sites.js @@ -253,7 +253,7 @@ router.get('/:id/edit', requireAdmin, async (req, res) => { const site = await Site.findByPk(req.params.id); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -278,11 +278,11 @@ router.post('/', requireAdmin, async (req, res) => { dnsForwarders }); - req.flash('success', `Site ${name} created successfully`); + await req.flash('success', `Site ${name} created successfully`); return res.redirect('/sites'); } catch (error) { console.error('Error creating site:', error); - req.flash('error', 'Failed to create site: ' + error.message); + await req.flash('error', 'Failed to create site: ' + error.message); return res.redirect('/sites/new'); } }); @@ -293,7 +293,7 @@ router.put('/:id', requireAdmin, async (req, res) => { const site = await Site.findByPk(req.params.id); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } @@ -308,11 +308,11 @@ router.put('/:id', requireAdmin, async (req, res) => { dnsForwarders }); - req.flash('success', `Site ${name} updated successfully`); + await req.flash('success', `Site ${name} updated successfully`); return res.redirect('/sites'); } catch (error) { console.error('Error updating site:', error); - req.flash('error', 'Failed to update site: ' + error.message); + await req.flash('error', 'Failed to update site: ' + error.message); return res.redirect(`/sites/${req.params.id}/edit`); } }); @@ -325,23 +325,23 @@ router.delete('/:id', requireAdmin, async (req, res) => { }); if (!site) { - req.flash('error', 'Site not found'); + await req.flash('error', 'Site not found'); return res.redirect('/sites'); } if (site.nodes && site.nodes.length > 0) { - req.flash('error', 'Cannot delete site with associated nodes'); + await req.flash('error', 'Cannot delete site with associated nodes'); return res.redirect('/sites'); } const siteName = site.name; await site.destroy(); - req.flash('success', `Site ${siteName} deleted successfully`); + await req.flash('success', `Site ${siteName} deleted successfully`); return res.redirect('/sites'); } catch (error) { console.error('Error deleting site:', error); - req.flash('error', 'Failed to delete site: ' + error.message); + await req.flash('error', 'Failed to delete site: ' + error.message); return res.redirect('/sites'); } }); diff --git a/create-a-container/routers/users.js b/create-a-container/routers/users.js index f75f9d5c..7a91cf4d 100644 --- a/create-a-container/routers/users.js +++ b/create-a-container/routers/users.js @@ -56,7 +56,7 @@ router.get('/:id/edit', async (req, res) => { }); if (!user) { - req.flash('error', 'User not found'); + await req.flash('error', 'User not found'); return res.redirect('/users'); } @@ -100,11 +100,11 @@ router.post('/', async (req, res) => { await user.setGroups(groups); } - req.flash('success', `User ${uid} created successfully`); + await req.flash('success', `User ${uid} created successfully`); return res.redirect('/users'); } catch (error) { console.error('Error creating user:', error); - req.flash('error', 'Failed to create user: ' + error.message); + await req.flash('error', 'Failed to create user: ' + error.message); return res.redirect('/users/new'); } }); @@ -117,7 +117,7 @@ router.put('/:id', async (req, res) => { const user = await User.findByPk(uidNumber); if (!user) { - req.flash('error', 'User not found'); + await req.flash('error', 'User not found'); return res.redirect('/users'); } @@ -151,11 +151,11 @@ router.put('/:id', async (req, res) => { await user.setGroups([]); } - req.flash('success', `User ${uid} updated successfully`); + await req.flash('success', `User ${uid} updated successfully`); return res.redirect('/users'); } catch (error) { console.error('Error updating user:', error); - req.flash('error', 'Failed to update user: ' + error.message); + await req.flash('error', 'Failed to update user: ' + error.message); return res.redirect(`/users/${uidNumber}/edit`); } }); @@ -168,18 +168,18 @@ router.delete('/:id', async (req, res) => { const user = await User.findByPk(uidNumber); if (!user) { - req.flash('error', 'User not found'); + await req.flash('error', 'User not found'); return res.redirect('/users'); } const username = user.uid; await user.destroy(); - req.flash('success', `User ${username} deleted successfully`); + await req.flash('success', `User ${username} deleted successfully`); return res.redirect('/users'); } catch (error) { console.error('Error deleting user:', error); - req.flash('error', 'Failed to delete user: ' + error.message); + await req.flash('error', 'Failed to delete user: ' + error.message); return res.redirect('/users'); } }); diff --git a/create-a-container/server.js b/create-a-container/server.js index 21b1d2e7..6083c2f9 100644 --- a/create-a-container/server.js +++ b/create-a-container/server.js @@ -69,6 +69,23 @@ async function main() { })); app.use(flash()); + // fix flash with postgres + app.use((req, res, next) => { + const _flash = req.flash; + req.flash = function(type, msg) { + const result = _flash.apply(this, arguments); + if (type && msg) { + return new Promise((resolve, reject) => { + this.session.save((err) => { + if (err) return reject(err); + resolve(result); + }); + }); + } + return result; + } + next(); + }); app.use(express.static('public')); app.use(RateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes diff --git a/create-a-container/views/login.ejs b/create-a-container/views/login.ejs index 284484c0..097ff5ca 100644 --- a/create-a-container/views/login.ejs +++ b/create-a-container/views/login.ejs @@ -13,7 +13,7 @@ <% if (successMessages && successMessages.length > 0) { %> <% successMessages.forEach(function(message) { %> -