From 684181c34c96903acec38f1b3726a87f4ba0543b Mon Sep 17 00:00:00 2001 From: nmemmert Date: Tue, 8 Sep 2026 14:05:02 -0400 Subject: [PATCH] Use email as host login credential; remove separate username field; v2.0.8 - Login form now asks for email + password (no username) - Admin creates hosts with name + email + password; email is always required and welcome email always sends on creation - Duplicate email check replaces duplicate username check - Admin table shows Email (login) column only; username field removed throughout data model, routes, and UI Co-Authored-By: Claude Sonnet 4.6 --- package.json | 2 +- server/email.js | 2 +- server/routes/nuggets-host-upload.js | 41 ++++++++++++---------------- src/AdminPage.tsx | 22 +++++---------- src/components/NuggetsHostPortal.tsx | 12 ++++---- 5 files changed, 33 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index d79806f..6b5e1d5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "siteforge", "private": true, - "version": "2.0.7", + "version": "2.0.8", "type": "module", "scripts": { "dev": "vite", diff --git a/server/email.js b/server/email.js index e320d9f..62a6d4e 100644 --- a/server/email.js +++ b/server/email.js @@ -778,7 +778,7 @@ export async function sendNuggetsHostWelcomeEmail({ to, name, username, password

- +
Portal URL${escapeHtml(portalUrl)}
Username${escapeHtml(username)}
Email${escapeHtml(username)}
Password${escapeHtml(password)}

When you're ready to submit an episode, fill in the episode name, the Bible passage, a short description, and attach your audio file. Everything uploads in one step.

diff --git a/server/routes/nuggets-host-upload.js b/server/routes/nuggets-host-upload.js index e6423e5..f3b7b30 100644 --- a/server/routes/nuggets-host-upload.js +++ b/server/routes/nuggets-host-upload.js @@ -159,18 +159,18 @@ export function register(app) { }) app.post('/api/nuggets-upload/login', loginRateLimiter, async (req, res) => { - const username = typeof req.body?.username === 'string' ? req.body.username.trim() : '' + const email = typeof req.body?.email === 'string' ? req.body.email.trim().toLowerCase() : '' const password = typeof req.body?.password === 'string' ? req.body.password : '' - if (!username || !password) { - res.status(400).json({ message: 'Username and password are required.' }) + if (!email || !password) { + res.status(400).json({ message: 'Email and password are required.' }) return } const data = await loadUploadHosts() - const host = data.hosts.find(h => h.username === username && h.active) + const host = data.hosts.find(h => h.email === email && h.active) if (!host || !passwordsMatch(password, host.passwordHash)) { - res.status(401).json({ message: 'Invalid username or password.' }) + res.status(401).json({ message: 'Invalid email or password.' }) return } @@ -279,12 +279,11 @@ export function register(app) { app.post('/api/admin/nuggets-upload/hosts', requireAdminAuth, async (req, res) => { const name = typeof req.body?.name === 'string' ? req.body.name.trim() : '' - const username = typeof req.body?.username === 'string' ? req.body.username.trim().toLowerCase() : '' - const password = typeof req.body?.password === 'string' ? req.body.password : '' const email = typeof req.body?.email === 'string' ? req.body.email.trim().toLowerCase() : '' + const password = typeof req.body?.password === 'string' ? req.body.password : '' - if (!name || !username || !password) { - res.status(400).json({ message: 'Name, username, and password are required.' }) + if (!name || !email || !password) { + res.status(400).json({ message: 'Name, email, and password are required.' }) return } if (password.length < 8) { @@ -293,15 +292,14 @@ export function register(app) { } const data = await loadUploadHosts() - if (data.hosts.some(h => h.username === username)) { - res.status(409).json({ message: 'Username already taken.' }) + if (data.hosts.some(h => h.email === email)) { + res.status(409).json({ message: 'An account with that email already exists.' }) return } const host = { id: randomUUID(), name, - username, email, passwordHash: hashPassword(password), active: true, @@ -310,19 +308,16 @@ export function register(app) { data.hosts.push(host) await saveUploadHosts(data) - // Send welcome email if an address was provided - if (email) { - sendNuggetsHostWelcomeEmail({ - to: email, - name, - username, - password, - portalUrl: `${process.env.SITE_URL ?? 'https://versebyversewithnate.us'}/host`, - }).catch(err => console.error('[nuggets-host] welcome email error:', err)) - } + sendNuggetsHostWelcomeEmail({ + to: email, + name, + username: email, + password, + portalUrl: `${process.env.SITE_URL ?? 'https://versebyversewithnate.us'}/host`, + }).catch(err => console.error('[nuggets-host] welcome email error:', err)) const { passwordHash: _ph, ...safeHost } = host - res.json({ ok: true, host: safeHost, emailSent: Boolean(email) }) + res.json({ ok: true, host: safeHost, emailSent: true }) }) app.put('/api/admin/nuggets-upload/hosts/:id', requireAdminAuth, async (req, res) => { diff --git a/src/AdminPage.tsx b/src/AdminPage.tsx index a8028ed..de002a8 100644 --- a/src/AdminPage.tsx +++ b/src/AdminPage.tsx @@ -1100,7 +1100,6 @@ function QrCodeImage({ url, label }: { url: string; label: string }) { interface UploadHost { id: string name: string - username: string email: string active: boolean createdAt: string @@ -1111,7 +1110,6 @@ function NuggetsUploadPortalAdmin() { const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [addName, setAddName] = useState('') - const [addUsername, setAddUsername] = useState('') const [addEmail, setAddEmail] = useState('') const [addPassword, setAddPassword] = useState('') const [addError, setAddError] = useState('') @@ -1142,12 +1140,12 @@ function NuggetsUploadPortalAdmin() { const r = await fetch('/api/admin/nuggets-upload/hosts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: addName, username: addUsername, email: addEmail, password: addPassword }), + body: JSON.stringify({ name: addName, email: addEmail, password: addPassword }), }) const d = await r.json() if (!r.ok) { setAddError(d.message ?? 'Failed.'); return } setAddEmailSent(Boolean(d.emailSent)) - setAddName(''); setAddUsername(''); setAddEmail(''); setAddPassword('') + setAddName(''); setAddEmail(''); setAddPassword('') reload() } finally { setAddSaving(false) @@ -1212,8 +1210,7 @@ function NuggetsUploadPortalAdmin() { Name - Username - Email + Email (login) Status @@ -1222,8 +1219,7 @@ function NuggetsUploadPortalAdmin() { {hosts.map(h => ( {h.name} - {h.username} - {h.email || none} + {h.email} {h.active ? 'Active' : 'Disabled'} @@ -1266,13 +1262,9 @@ function NuggetsUploadPortalAdmin() { setAddName(e.target.value)} required /> -
- - setAddUsername(e.target.value.toLowerCase())} required /> -
-
- - setAddEmail(e.target.value)} /> +
+ + setAddEmail(e.target.value)} required />
diff --git a/src/components/NuggetsHostPortal.tsx b/src/components/NuggetsHostPortal.tsx index 90180d9..b7b3479 100644 --- a/src/components/NuggetsHostPortal.tsx +++ b/src/components/NuggetsHostPortal.tsx @@ -12,7 +12,7 @@ export function NuggetsHostPortal() { const [hostName, setHostName] = useState('') const [seafileConfigured, setSeafileConfigured] = useState(true) - const [username, setUsername] = useState('') + const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [loginError, setLoginError] = useState('') const [loggingIn, setLoggingIn] = useState(false) @@ -48,7 +48,7 @@ export function NuggetsHostPortal() { const r = await fetch('/api/nuggets-upload/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, password }), + body: JSON.stringify({ email, password }), }) const d = await r.json() if (!r.ok) { setLoginError(d.message ?? 'Login failed.'); return } @@ -63,7 +63,7 @@ export function NuggetsHostPortal() { await fetch('/api/nuggets-upload/logout', { method: 'POST' }) setAuthenticated(false) setHostName('') - setUsername('') + setEmail('') setPassword('') setSubmitted([]) resetForm() @@ -166,9 +166,9 @@ export function NuggetsHostPortal() {

Sign in with your host credentials to submit an episode.

- - setUsername(e.target.value)} disabled={loggingIn} required /> + + setEmail(e.target.value)} disabled={loggingIn} required />