diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 5153feb..464173b 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -23,7 +23,7 @@ export const handle: Handle = async ({ event, resolve }) => { // Force password change if (event.locals.user?.mustChangePassword) { const path = event.url.pathname; - if (path !== '/change-password' && path !== '/logout' && !path.startsWith('/api/')) { + if (path !== '/change-password' && path !== '/logout' && !path.startsWith('/api/worker/')) { throw redirect(302, '/change-password'); } } diff --git a/src/lib/server/email.ts b/src/lib/server/email.ts index ff327a5..18ae0cf 100644 --- a/src/lib/server/email.ts +++ b/src/lib/server/email.ts @@ -1,5 +1,9 @@ import nodemailer from 'nodemailer'; +function escHtml(s: string): string { + return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); +} + function getTransporter() { if (!process.env.SMTP_USER) return null; @@ -33,8 +37,8 @@ export async function sendApplicationNotification( const subject = `[${orgName}] New ${type} application from ${applicantName}`; const html = `

New ${type} Application

-

Applicant: ${applicantName}

- ${petName ? `

Pet: ${petName}

` : ''} +

Applicant: ${escHtml(applicantName)}

+ ${petName ? `

Pet: ${escHtml(petName)}

` : ''}

Log in to your admin dashboard to review this application.

`; await sendEmail(staffEmail, subject, html); @@ -51,8 +55,8 @@ export async function sendApplicationStatusUpdate( const subject = `[${orgName}] Your ${type} application has been ${status}`; const html = `

Application Update

-

Hi ${applicantName},

-

Your ${type} application has been ${status}.

+

Hi ${escHtml(applicantName)},

+

Your ${escHtml(type)} application has been ${escHtml(status)}.

Visit ${siteUrl} for more information.

`; await sendEmail(to, subject, html); @@ -87,9 +91,9 @@ export async function sendRegistrationNotification( const subject = `[${orgName}] New registration request from ${name}`; const html = `

New Registration Request

-

Name: ${name}

-

Email: ${email}

-

Reason: ${reason}

+

Name: ${escHtml(name)}

+

Email: ${escHtml(email)}

+

Reason: ${escHtml(reason)}

Log in to your admin dashboard to approve or deny this request.

`; await sendEmail(staffEmail, subject, html); @@ -104,7 +108,7 @@ export async function sendRegistrationApproved( const subject = `[${orgName}] Your account has been approved!`; const html = `

Account Approved

-

Hi ${name}, your account at ${orgName} has been approved.

+

Hi ${escHtml(name)}, your account at ${escHtml(orgName)} has been approved.

Log in at: ${loginUrl}

`; await sendEmail(to, subject, html); @@ -118,7 +122,7 @@ export async function sendRegistrationDenied( const subject = `[${orgName}] Registration update`; const html = `

Registration Update

-

Hi ${name}, unfortunately your registration request at ${orgName} was not approved at this time.

+

Hi ${escHtml(name)}, unfortunately your registration request at ${escHtml(orgName)} was not approved at this time.

If you believe this is an error, please contact us.

`; await sendEmail(to, subject, html); @@ -204,9 +208,9 @@ export async function sendContactNotification( const subject = `[${orgName}] Contact form: ${messageSubject || 'New message'}`; const html = `

Contact Form Message

-

From: ${senderName} (${senderEmail})

-

Subject: ${messageSubject || 'N/A'}

-

${message}

+

From: ${escHtml(senderName)} (${escHtml(senderEmail)})

+

Subject: ${escHtml(messageSubject || 'N/A')}

+

${escHtml(message)}

`; await sendEmail(staffEmail, subject, html); } diff --git a/src/lib/server/upload.ts b/src/lib/server/upload.ts index ede3387..2190653 100644 --- a/src/lib/server/upload.ts +++ b/src/lib/server/upload.ts @@ -40,7 +40,10 @@ export async function saveUpload( throw new Error('File content does not match declared type'); } - const ext = file.name.split('.').pop()?.toLowerCase() || 'jpg'; + const mimeToExt: Record = { + 'image/jpeg': 'jpg', 'image/png': 'png', 'image/webp': 'webp', 'image/gif': 'gif' + }; + const ext = mimeToExt[file.type] ?? 'jpg'; const filename = `${crypto.randomUUID()}.${ext}`; const dir = path.join(UPLOAD_DIR, subdir); @@ -57,7 +60,9 @@ export async function saveUpload( export async function deleteUpload(url: string): Promise { if (!url.startsWith('/uploads/')) return; - const filePath = path.join(UPLOAD_DIR, url.replace('/uploads/', '')); + const filePath = path.resolve(path.join(UPLOAD_DIR, url.replace('/uploads/', ''))); + const base = path.resolve(UPLOAD_DIR); + if (!filePath.startsWith(base + path.sep)) return; try { await fs.unlink(filePath); } catch { diff --git a/src/routes/embed/pets.js/+server.ts b/src/routes/embed/pets.js/+server.ts index 3d86f9a..0960a63 100644 --- a/src/routes/embed/pets.js/+server.ts +++ b/src/routes/embed/pets.js/+server.ts @@ -29,6 +29,8 @@ export const GET: RequestHandler = async ({ url }) => { var container = document.getElementById('fosterflow-pets'); if (!container) { console.warn('FosterFlow: #fosterflow-pets not found'); return; } + function esc(s) { return (s || '').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } + var limit = parseInt(container.dataset.limit || '12'); var species = container.dataset.species || ''; var columns = parseInt(container.dataset.columns || '3'); @@ -72,13 +74,13 @@ export const GET: RequestHandler = async ({ url }) => { var card = document.createElement('div'); card.className = 'ff-card'; var photoHtml = pet.photoUrl - ? '' + pet.name + '' + ? '' + esc(pet.name) + '' : '🐾'; - card.innerHTML = '' + + card.innerHTML = '' + '
' + photoHtml + '
' + '
' + - '

' + pet.name + '

' + - '

' + (pet.breed || pet.species) + (pet.age ? ' · ' + pet.age : '') + ' · ' + pet.sex + '

' + + '

' + esc(pet.name) + '

' + + '

' + esc(pet.breed || pet.species) + (pet.age ? ' · ' + esc(pet.age) : '') + ' · ' + esc(pet.sex) + '

' + '
'; grid.appendChild(card); }); diff --git a/src/routes/login/+page.server.ts b/src/routes/login/+page.server.ts index 04116e3..3110026 100644 --- a/src/routes/login/+page.server.ts +++ b/src/routes/login/+page.server.ts @@ -30,20 +30,6 @@ export const actions: Actions = { const user = await authenticateUser(email, password); if (!user) { - // Check if account exists but is inactive (pending approval) - if (email) { - const [existing] = await db - .select({ id: users.id, active: users.active }) - .from(users) - .where(eq(users.email, email.toLowerCase())) - .limit(1); - if (existing && !existing.active) { - return fail(401, { - error: "Your account is pending approval. You'll receive an email when it's been reviewed.", - email - }); - } - } return fail(401, { error: 'Invalid email or password', email }); }