Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update /checkout.js with issue #963 from AppSec Hack Pod #972

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 73 additions & 8 deletions server/routes/api/checkout.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,34 @@

const router = express.Router()

const Joi = require('joi') // For input validation

// Validation schema for incoming data
const sessionSchema = Joi.object({
donation: Joi.number()
.positive()
.max(10000)
.required() // Limit donation amount to prevent abuse
.messages({
'number.base': 'Donation must be a number.',
'number.positive': 'Donation must be a positive number.',
'number.max': 'Donation exceeds the allowed limit.',
'any.required': 'Donation is required.'
}),
user: Joi.object({
email: Joi.string().email().required().messages({
'string.email': 'A valid email is required.',
'any.required': 'User email is required.'
})
}).required(),
letter: Joi.string()
.max(300)
.optional() // Limit the size to prevent long data
.messages({
'string.max': 'Letter cannot exceed 300 characters.'
})
}).unknown(false) // Disallow any additional, unexpected fields

class CheckoutError extends Error {
constructor(message) {
super(message)
Expand All @@ -20,25 +48,58 @@
}

router.post('/create-checkout-session', async (req, res) => {
const { donation, user, letter } = req.body
const origin = req.get('origin')
try {
// Validate incoming data against the schema
const { donation, user, letter } = await sessionSchema.validateAsync(

Check failure on line 53 in server/routes/api/checkout.js

View workflow job for this annotation

GitHub Actions / lint

'donation' is assigned a value but never used

Check failure on line 53 in server/routes/api/checkout.js

View workflow job for this annotation

GitHub Actions / lint

'user' is assigned a value but never used

Check failure on line 53 in server/routes/api/checkout.js

View workflow job for this annotation

GitHub Actions / lint

'letter' is assigned a value but never used
req.body
)

// Validate origin to ensure the request is coming from trusted sources
const origin = req.get('origin')
// Trusted origins to be added as a list below, environment variable, config file, or in the DB
const allowedOrigins = [
'https://yourtrusteddomain.com',
'https://anothertrusteddomain.com'
]
if (!allowedOrigins.includes(origin)) {
console.warn(`Untrusted origin attempted to create session: ${origin}`)
return res
.status(403)
.json({ error: 'Forbidden request from untrusted origin.' })
}

// Log the origin without sensitive information
console.log(`Valid origin: ${origin}`)

console.log(`origin: ${origin}`)
// Continue with checkout session creation logic...
// (You can add the session creation logic here)

return res
.status(200)
.json({ message: 'Checkout session created successfully' })
} catch (error) {
// Error handling
if (error.isJoi) {
// Handle validation errors
console.error('Validation error:', error.details)
return res.status(400).json({ error: 'Invalid input data' })
}
}

try {
const presenter = new PaymentPresenter()

// Will throw error if invalid amount is given.
presenter.validatePaymentAmount(donation)

Check failure on line 93 in server/routes/api/checkout.js

View workflow job for this annotation

GitHub Actions / lint

'donation' is not defined

if (donation === 0 && process.env.VUE_APP_EMPTY_TRANSACTIONS === 'on') {

Check failure on line 95 in server/routes/api/checkout.js

View workflow job for this annotation

GitHub Actions / lint

'donation' is not defined
const CHECKOUT_SESSION_ID = uuidv4()
const redirectUrl = `${origin}/complete?session_id=${CHECKOUT_SESSION_ID}`

Check failure on line 97 in server/routes/api/checkout.js

View workflow job for this annotation

GitHub Actions / lint

'origin' is not defined

let constituent
;[constituent] = await Constituent.query().where('email', user.email)

Check failure on line 100 in server/routes/api/checkout.js

View workflow job for this annotation

GitHub Actions / lint

'user' is not defined
if (!constituent) {
constituent = await Constituent.query().insert(user)

Check failure on line 102 in server/routes/api/checkout.js

View workflow job for this annotation

GitHub Actions / lint

'user' is not defined
}

console.log(constituent.id)
Expand All @@ -46,7 +107,7 @@
const transaction = await Transaction.query().insert({
stripeTransactionId: 'no-stripe-' + uuidv4(),
constituentId: constituent.id,
amount: donation,

Check failure on line 110 in server/routes/api/checkout.js

View workflow job for this annotation

GitHub Actions / lint

'donation' is not defined
currency: 'usd',
paymentMethod: 'credit_card',
status: 'succeeded'
Expand All @@ -56,7 +117,7 @@
await Letter.query().insert({
transactionId: transaction.id,
constituentId: constituent.id,
...letter

Check failure on line 120 in server/routes/api/checkout.js

View workflow job for this annotation

GitHub Actions / lint

'letter' is not defined
})

return res
Expand Down Expand Up @@ -159,19 +220,23 @@
)
}

const transaction = await Transaction.query().findOne({ stripe_transaction_id: paymentIntent })
const transaction = await Transaction.query().findOne({
stripe_transaction_id: paymentIntent
})
await transaction.$query().patch({ status: eventOutcome })

const letter = await Letter.query().where({ transaction_id: transaction.id }).first()
const letter = await Letter.query()
.where({ transaction_id: transaction.id })
.first()
letter.trackingNumber = uuidv4()
const letterTemplate = JSON.parse(letter.letterTemplate)

const lobApiKey = process.env.LOB_API_KEY
const lobCredentials = btoa(`${lobApiKey}:`)

console.log(letter.mergeVariables)
const lobResponse = await axios.post(
'https://api.lob.com/v1/letters',
'https://api.lob.com/v1/letters',
{
to: {
name: letter.addressee,
Expand All @@ -197,7 +262,7 @@

if (!lobResponse.statusCode === 200) throw new CheckoutError(lobResponse)

await letter.$query().patch({ sent: true})
await letter.$query().patch({ sent: true })

return res.status(201).end()
} catch (error) {
Expand Down
Loading