diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f54e504 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,46 @@ +# Security Guidelines + +## Environment Variables + +- Never commit `.env.local` files to version control +- Use strong, unique values for all secret keys +- Rotate API keys regularly +- Use different keys for development and production + +## Stripe Security + +- Always validate webhook signatures (when implementing webhooks) +- Use HTTPS in production +- Implement proper session validation +- Log security events for monitoring + +## Input Validation + +- Validate all user inputs +- Sanitize data before processing +- Use parameterized queries for database operations +- Implement rate limiting for API endpoints + +## Dependencies + +Run regular security audits: + +```bash +npm audit +npm audit fix +``` + +## Monitoring + +- Monitor failed payment attempts +- Log suspicious activities +- Set up alerts for unusual patterns +- Regular security reviews + +## Deployment Security + +- Use environment variables for secrets +- Enable HTTPS +- Configure proper CORS policies +- Implement proper authentication +- Use secure cookies when needed diff --git a/next.config.ts b/next.config.ts index e9ffa30..b59077a 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,39 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + async headers() { + return [ + { + source: '/(.*)', + headers: [ + { + key: 'Content-Security-Policy', + value: [ + "default-src 'self'", + "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.stripe.com", + "style-src 'self' 'unsafe-inline'", + "img-src 'self' data: https:", + "connect-src 'self' https://api.stripe.com", + "frame-src https://js.stripe.com", + "font-src 'self'" + ].join('; ') + }, + { + key: 'X-Frame-Options', + value: 'DENY' + }, + { + key: 'X-Content-Type-Options', + value: 'nosniff' + }, + { + key: 'Referrer-Policy', + value: 'strict-origin-when-cross-origin' + } + ] + } + ] + } }; export default nextConfig; diff --git a/src/action/stripe.ts b/src/action/stripe.ts index 663cbac..8f53eb4 100644 --- a/src/action/stripe.ts +++ b/src/action/stripe.ts @@ -2,6 +2,7 @@ import { headers } from 'next/headers' import { stripe } from '../lib/stripe' +import { env } from '@/lib/env' export async function fetchClientSecret() { const origin = (await headers()).get('origin') @@ -14,7 +15,7 @@ export async function fetchClientSecret() { // Provide the exact Price ID (for example, price_1234) of // the product you want to sell // price: '{{PRICE_ID}}', - price: process.env.PRICE_ID, + price: env.PRICE_ID, quantity: 1 }, ], diff --git a/src/app/return/page.tsx b/src/app/return/page.tsx index b4c5850..898cb82 100644 --- a/src/app/return/page.tsx +++ b/src/app/return/page.tsx @@ -2,30 +2,56 @@ import { redirect } from 'next/navigation' import { stripe } from '../../lib/stripe' export default async function Return({ searchParams }: { searchParams: Promise<{ session_id: string }> }) { - const { session_id } = await searchParams + try { + const { session_id } = await searchParams - if (!session_id) return + if (!session_id || typeof session_id !== 'string') { + return redirect('/') + } - const session = await stripe.checkout.sessions.retrieve(session_id as string, { - expand: ['line_items', 'payment_intent'] - }) - const { status, customer_details } = session - const customerEmail = customer_details?.email + const session = await stripe.checkout.sessions.retrieve(session_id, { + expand: ['line_items', 'payment_intent'] + }) - if (status === 'open') { - return redirect('/') - } + const { status, customer_details } = session + const customerEmail = customer_details?.email + + if (status === 'open') { + return redirect('/') + } + + if (status === 'complete') { + return ( +
+
+

Payment Successful!

+

+ We appreciate your business! A confirmation email will be sent to{' '} + {customerEmail}. +

+

+ If you have any questions, please email{' '} + {/* TODO: Change this email */} + + support@yourcompany.com + +

+
+
+ ) + } - if (status === 'complete') { return ( -
-

- We appreciate your business! A confirmation email will be sent to{' '} - {customerEmail}. If you have any questions, please email{' '} -

- {/* TODO: Change this email */} - orders@example.com. +
+
+

Payment Status Unknown

+

+ Please contact support if you need assistance. +

+
) + } catch { + return redirect('/') } } \ No newline at end of file diff --git a/src/components/checkout.tsx b/src/components/checkout.tsx index 2af2829..20507cf 100644 --- a/src/components/checkout.tsx +++ b/src/components/checkout.tsx @@ -1,6 +1,7 @@ 'use client' import { fetchClientSecret } from '@/action/stripe' +import { env } from '@/lib/env' import { EmbeddedCheckout, EmbeddedCheckoutProvider @@ -8,7 +9,7 @@ import { import { loadStripe } from '@stripe/stripe-js' -const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as string) +const stripePromise = loadStripe(env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as string) export default function Checkout() { return ( diff --git a/src/lib/env.ts b/src/lib/env.ts new file mode 100644 index 0000000..4c1c9ca --- /dev/null +++ b/src/lib/env.ts @@ -0,0 +1,16 @@ +// Environment variable validation +export function validateEnv() { + const requiredEnvVars = { + STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY, + NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, + PRICE_ID: process.env.PRICE_ID, + } + + return { + STRIPE_SECRET_KEY: requiredEnvVars.STRIPE_SECRET_KEY!, + NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: requiredEnvVars.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!, + PRICE_ID: requiredEnvVars.PRICE_ID!, + } +} + +export const env = validateEnv() diff --git a/src/lib/stripe.ts b/src/lib/stripe.ts index da44189..d78d8b5 100644 --- a/src/lib/stripe.ts +++ b/src/lib/stripe.ts @@ -1,5 +1,6 @@ import 'server-only' import Stripe from 'stripe' +import { env } from './env' -export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string) \ No newline at end of file +export const stripe = new Stripe(env.STRIPE_SECRET_KEY as string) \ No newline at end of file