Add Zod validation, fix mass assignment, remove test endpoints and dead code

- Add centralized Zod schemas (server/utils/schemas.js) and validateBody
  utility for all API endpoints
- Fix critical mass assignment in member creation: raw body no longer
  passed to new Member(), only validated fields (email, name, circle,
  contributionTier) are accepted
- Apply Zod validation to login, profile patch, event registration,
  updates, verify-payment, and admin event creation endpoints
- Fix logout cookie flags to match login (httpOnly: true, secure
  conditional on NODE_ENV)
- Delete unauthenticated test/debug endpoints (test-connection,
  test-subscription, test-bot)
- Remove sensitive console.log statements from Helcim and member
  endpoints
- Remove unused bcryptjs dependency
- Add 10MB file size limit on image uploads
- Use runtime config for JWT secret across all endpoints
- Add 38 validation tests (117 total, all passing)
This commit is contained in:
Jennie Robinson Faber 2026-03-01 14:02:46 +00:00
parent 26c300c357
commit b7279f57d1
41 changed files with 467 additions and 321 deletions

View file

@ -16,7 +16,6 @@ export default defineEventHandler(async (event) => {
const helcimToken = config.public.helcimToken || process.env.NUXT_PUBLIC_HELCIM_TOKEN
console.log('Creating payment plan:', body.name)
const response = await fetch(`${HELCIM_API_BASE}/payment-plans`, {
method: 'POST',
@ -44,7 +43,6 @@ export default defineEventHandler(async (event) => {
}
const planData = await response.json()
console.log('Payment plan created:', planData)
return {
success: true,

View file

@ -21,7 +21,7 @@ export default defineEventHandler(async (event) => {
// Decode JWT token
let decoded
try {
decoded = jwt.verify(token, process.env.JWT_SECRET)
decoded = jwt.verify(token, useRuntimeConfig().jwtSecret)
} catch (err) {
throw createError({
statusCode: 401,

View file

@ -105,7 +105,7 @@ export default defineEventHandler(async (event) => {
email: body.email,
helcimCustomerId: customerData.id
},
process.env.JWT_SECRET,
config.jwtSecret,
{ expiresIn: '24h' }
)

View file

@ -21,7 +21,7 @@ export default defineEventHandler(async (event) => {
// Decode JWT token
let decoded
try {
decoded = jwt.verify(token, process.env.JWT_SECRET)
decoded = jwt.verify(token, useRuntimeConfig().jwtSecret)
} catch (err) {
throw createError({
statusCode: 401,
@ -59,7 +59,6 @@ export default defineEventHandler(async (event) => {
const existingCustomer = searchData.customers.find(c => c.email === member.email)
if (existingCustomer) {
console.log('Found existing Helcim customer:', existingCustomer.id)
// Update member record with customer ID if not already set
if (!member.helcimCustomerId) {
@ -77,12 +76,11 @@ export default defineEventHandler(async (event) => {
}
}
} catch (searchError) {
console.log('Error searching for customer:', searchError)
console.error('Error searching for customer:', searchError)
// Continue to create new customer
}
// No existing customer found, create new one
console.log('Creating new Helcim customer for:', member.email)
const createResponse = await fetch(`${HELCIM_API_BASE}/customers`, {
method: 'POST',
headers: {
@ -107,7 +105,6 @@ export default defineEventHandler(async (event) => {
}
const customerData = await createResponse.json()
console.log('Created Helcim customer:', customerData.id)
// Update member record with customer ID
member.helcimCustomerId = customerData.id

View file

@ -6,8 +6,6 @@ export default defineEventHandler(async (event) => {
const config = useRuntimeConfig(event)
const helcimToken = config.public.helcimToken || process.env.NUXT_PUBLIC_HELCIM_TOKEN
console.log('Fetching payment plans from Helcim...')
const response = await fetch(`${HELCIM_API_BASE}/payment-plans`, {
method: 'GET',
headers: {
@ -18,17 +16,13 @@ export default defineEventHandler(async (event) => {
if (!response.ok) {
console.error('Failed to fetch payment plans:', response.status, response.statusText)
const errorText = await response.text()
console.error('Response body:', errorText)
throw createError({
statusCode: response.status,
statusMessage: `Failed to fetch payment plans: ${errorText}`
statusMessage: 'Failed to fetch payment plans'
})
}
const plansData = await response.json()
console.log('Payment plans retrieved:', JSON.stringify(plansData, null, 2))
return {
success: true,

View file

@ -6,8 +6,6 @@ export default defineEventHandler(async (event) => {
const config = useRuntimeConfig(event)
const helcimToken = config.public.helcimToken || process.env.NUXT_PUBLIC_HELCIM_TOKEN
console.log('Fetching existing subscriptions from Helcim...')
const response = await fetch(`${HELCIM_API_BASE}/subscriptions`, {
method: 'GET',
headers: {
@ -18,17 +16,13 @@ export default defineEventHandler(async (event) => {
if (!response.ok) {
console.error('Failed to fetch subscriptions:', response.status, response.statusText)
const errorText = await response.text()
console.error('Response body:', errorText)
throw createError({
statusCode: response.status,
statusMessage: `Failed to fetch subscriptions: ${errorText}`
statusMessage: 'Failed to fetch subscriptions'
})
}
const subscriptionsData = await response.json()
console.log('Existing subscriptions:', JSON.stringify(subscriptionsData, null, 2))
return {
success: true,

View file

@ -1,46 +0,0 @@
// Test Helcim API connection
const HELCIM_API_BASE = 'https://api.helcim.com/v2'
export default defineEventHandler(async (event) => {
try {
const config = useRuntimeConfig(event)
// Log token info (safely)
const tokenInfo = {
hasToken: !!config.public.helcimToken,
tokenLength: config.public.helcimToken ? config.public.helcimToken.length : 0,
tokenPrefix: config.public.helcimToken ? config.public.helcimToken.substring(0, 10) : null
}
console.log('Helcim Token Info:', tokenInfo)
const helcimToken = config.public.helcimToken || process.env.NUXT_PUBLIC_HELCIM_TOKEN
// Try connection test endpoint
const response = await $fetch(`${HELCIM_API_BASE}/connection-test`, {
method: 'GET',
headers: {
'accept': 'application/json',
'api-token': helcimToken
}
})
return {
success: true,
message: 'Helcim API connection successful',
tokenInfo,
connectionResponse: response
}
} catch (error) {
console.error('Helcim test error:', error)
return {
success: false,
message: error.message || 'Failed to connect to Helcim API',
statusCode: error.statusCode,
tokenInfo: {
hasToken: !!useRuntimeConfig().public.helcimToken,
tokenLength: useRuntimeConfig().public.helcimToken ? useRuntimeConfig().public.helcimToken.length : 0
}
}
}
})

View file

@ -1,77 +0,0 @@
// Test minimal subscription creation to understand required fields
const HELCIM_API_BASE = 'https://api.helcim.com/v2'
export default defineEventHandler(async (event) => {
try {
const config = useRuntimeConfig(event)
const helcimToken = config.public.helcimToken || process.env.NUXT_PUBLIC_HELCIM_TOKEN
// Generate a 25-character idempotency key
const idempotencyKey = `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`.substring(0, 25)
// Test with minimal fields first
const testRequest1 = {
customerCode: 'CST1020', // Use a recent customer code
planId: 20162
}
console.log('Testing subscription with minimal fields:', testRequest1)
try {
const response1 = await fetch(`${HELCIM_API_BASE}/subscriptions`, {
method: 'POST',
headers: {
'accept': 'application/json',
'content-type': 'application/json',
'api-token': helcimToken,
'idempotency-key': idempotencyKey + 'a'
},
body: JSON.stringify(testRequest1)
})
const result1 = await response1.text()
console.log('Test 1 - Status:', response1.status)
console.log('Test 1 - Response:', result1)
if (!response1.ok) {
// Try with paymentPlanId instead
const testRequest2 = {
customerCode: 'CST1020',
paymentPlanId: 20162
}
console.log('Testing subscription with paymentPlanId:', testRequest2)
const response2 = await fetch(`${HELCIM_API_BASE}/subscriptions`, {
method: 'POST',
headers: {
'accept': 'application/json',
'content-type': 'application/json',
'api-token': helcimToken,
'idempotency-key': idempotencyKey + 'b'
},
body: JSON.stringify(testRequest2)
})
const result2 = await response2.text()
console.log('Test 2 - Status:', response2.status)
console.log('Test 2 - Response:', result2)
}
} catch (error) {
console.error('Test error:', error)
}
return {
success: true,
message: 'Check server logs for test results'
}
} catch (error) {
console.error('Error in test endpoint:', error)
throw createError({
statusCode: 500,
statusMessage: error.message
})
}
})

View file

@ -1,5 +1,7 @@
// Verify payment token from HelcimPay.js
import { requireAuth } from '../../utils/auth.js'
import { validateBody } from '../../utils/validateBody.js'
import { paymentVerifySchema } from '../../utils/schemas.js'
const HELCIM_API_BASE = 'https://api.helcim.com/v2'
@ -7,15 +9,7 @@ export default defineEventHandler(async (event) => {
try {
await requireAuth(event)
const config = useRuntimeConfig(event)
const body = await readBody(event)
// Validate required fields
if (!body.cardToken || !body.customerId) {
throw createError({
statusCode: 400,
statusMessage: 'Card token and customer ID are required'
})
}
const body = await validateBody(event, paymentVerifySchema)
const helcimToken = config.public.helcimToken || process.env.NUXT_PUBLIC_HELCIM_TOKEN
@ -48,13 +42,13 @@ export default defineEventHandler(async (event) => {
// Verify the card token exists for this customer
const cardExists = Array.isArray(cards) && cards.some(card =>
card.cardToken === body.cardToken || card.id
card.cardToken === body.cardToken
)
if (!cardExists && Array.isArray(cards) && cards.length === 0) {
if (!cardExists) {
throw createError({
statusCode: 400,
statusMessage: 'No payment method found for this customer'
statusMessage: 'Payment method not found or does not belong to this customer'
})
}