From 59c08c59d75cc44c058dec39c08596209206e730 Mon Sep 17 00:00:00 2001 From: giancarlo Date: Fri, 29 Mar 2024 18:03:41 +0800 Subject: [PATCH] Update and refine schemas in Stripe and Team Accounts features Refactored both Stripe and Team Accounts features' schemas for better data validation and added specificity to keys. Enhanced form validations with methods to ensure Stripe keys follow appropriate prefixes. Replaced generic string types with UUID for accountId attributes in different services. Also turned off autocomplete for destructive actions for improved security. --- .../schema/create-billing-checkout.schema.ts | 4 +-- .../src/schema/leave-team-account.schema.ts | 2 +- .../src/schema/update-invitation-schema.ts | 4 +-- .../services/leave-team-account.service.ts | 4 +-- packages/mailers/src/schema/mailer.schema.ts | 4 ++- .../components/stripe-embedded-checkout.tsx | 9 ++++--- .../src/schema/stripe-client-env.schema.ts | 16 ++++++++--- .../src/schema/stripe-server-env.schema.ts | 27 ++++++++++++++++--- packages/stripe/src/services/stripe-sdk.ts | 2 +- .../stripe-webhook-handler.service.ts | 8 +++--- 10 files changed, 55 insertions(+), 25 deletions(-) diff --git a/packages/billing/src/schema/create-billing-checkout.schema.ts b/packages/billing/src/schema/create-billing-checkout.schema.ts index be476921a..22a85bdc5 100644 --- a/packages/billing/src/schema/create-billing-checkout.schema.ts +++ b/packages/billing/src/schema/create-billing-checkout.schema.ts @@ -5,7 +5,7 @@ import { LineItemUsageType, PaymentType } from '../create-billing-schema'; export const CreateBillingCheckoutSchema = z .object({ returnUrl: z.string().url(), - accountId: z.string(), + accountId: z.string().uuid(), paymentType: PaymentType, lineItems: z.array( z.object({ @@ -16,7 +16,7 @@ export const CreateBillingCheckoutSchema = z ), trialDays: z.number().optional(), customerId: z.string().optional(), - customerEmail: z.string().optional(), + customerEmail: z.string().email().optional(), }) .refine( (schema) => { diff --git a/packages/features/team-accounts/src/schema/leave-team-account.schema.ts b/packages/features/team-accounts/src/schema/leave-team-account.schema.ts index 1ba4c6ce5..a9168cdae 100644 --- a/packages/features/team-accounts/src/schema/leave-team-account.schema.ts +++ b/packages/features/team-accounts/src/schema/leave-team-account.schema.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; export const LeaveTeamAccountSchema = z.object({ - accountId: z.string(), + accountId: z.string().uuid(), confirmation: z.custom((value) => value === 'LEAVE'), }); diff --git a/packages/features/team-accounts/src/schema/update-invitation-schema.ts b/packages/features/team-accounts/src/schema/update-invitation-schema.ts index f833fdd55..4882695e9 100644 --- a/packages/features/team-accounts/src/schema/update-invitation-schema.ts +++ b/packages/features/team-accounts/src/schema/update-invitation-schema.ts @@ -1,8 +1,6 @@ import { z } from 'zod'; -type Role = string; - export const UpdateInvitationSchema = z.object({ invitationId: z.number(), - role: z.custom(() => z.string().min(1)), + role: z.string().min(1), }); diff --git a/packages/features/team-accounts/src/server/services/leave-team-account.service.ts b/packages/features/team-accounts/src/server/services/leave-team-account.service.ts index 059cdc1d8..a889eb68a 100644 --- a/packages/features/team-accounts/src/server/services/leave-team-account.service.ts +++ b/packages/features/team-accounts/src/server/services/leave-team-account.service.ts @@ -7,8 +7,8 @@ import { Logger } from '@kit/shared/logger'; import { Database } from '@kit/supabase/database'; const Schema = z.object({ - accountId: z.string(), - userId: z.string(), + accountId: z.string().uuid(), + userId: z.string().uuid(), }); export class LeaveTeamAccountService { diff --git a/packages/mailers/src/schema/mailer.schema.ts b/packages/mailers/src/schema/mailer.schema.ts index 4f77c3189..1fd1f5849 100644 --- a/packages/mailers/src/schema/mailer.schema.ts +++ b/packages/mailers/src/schema/mailer.schema.ts @@ -3,7 +3,9 @@ import { z } from 'zod'; export const MailerSchema = z .object({ to: z.string().email(), - from: z.string().email(), + // this is not necessarily formatted + // as an email so we type it loosely + from: z.string().min(1), subject: z.string(), }) .and( diff --git a/packages/stripe/src/components/stripe-embedded-checkout.tsx b/packages/stripe/src/components/stripe-embedded-checkout.tsx index 6fcf3c093..722d256c7 100644 --- a/packages/stripe/src/components/stripe-embedded-checkout.tsx +++ b/packages/stripe/src/components/stripe-embedded-checkout.tsx @@ -2,7 +2,6 @@ import { useState } from 'react'; -import { invariant } from '@epic-web/invariant'; import { EmbeddedCheckout, EmbeddedCheckoutProvider, @@ -16,11 +15,13 @@ import { DialogTitle, } from '@kit/ui/dialog'; -const STRIPE_PUBLISHABLE_KEY = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY; +import { StripeClientEnvSchema } from '../schema/stripe-client-env.schema'; -invariant(STRIPE_PUBLISHABLE_KEY, 'Stripe publishable key is required'); +const { publishableKey } = StripeClientEnvSchema.parse({ + publishableKey: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, +}); -const stripePromise = loadStripe(STRIPE_PUBLISHABLE_KEY); +const stripePromise = loadStripe(publishableKey); export function StripeCheckout({ checkoutToken, diff --git a/packages/stripe/src/schema/stripe-client-env.schema.ts b/packages/stripe/src/schema/stripe-client-env.schema.ts index c0fa90cf2..5cb12ee39 100644 --- a/packages/stripe/src/schema/stripe-client-env.schema.ts +++ b/packages/stripe/src/schema/stripe-client-env.schema.ts @@ -1,5 +1,15 @@ import { z } from 'zod'; -export const StripeClientEnvSchema = z.object({ - NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().min(1), -}); +export const StripeClientEnvSchema = z + .object({ + publishableKey: z.string().min(1), + }) + .refine( + (schema) => { + return schema.publishableKey.startsWith('pk_'); + }, + { + path: ['publishableKey'], + message: `Stripe publishable key must start with 'pk_'`, + }, + ); diff --git a/packages/stripe/src/schema/stripe-server-env.schema.ts b/packages/stripe/src/schema/stripe-server-env.schema.ts index c0bd7c5a7..d5e2d4d1f 100644 --- a/packages/stripe/src/schema/stripe-server-env.schema.ts +++ b/packages/stripe/src/schema/stripe-server-env.schema.ts @@ -1,6 +1,25 @@ import { z } from 'zod'; -export const StripeServerEnvSchema = z.object({ - STRIPE_SECRET_KEY: z.string().min(1), - STRIPE_WEBHOOK_SECRET: z.string().min(1), -}); +export const StripeServerEnvSchema = z + .object({ + secretKey: z.string().min(1), + webhooksSecret: z.string().min(1), + }) + .refine( + (schema) => { + return schema.secretKey.startsWith('sk_'); + }, + { + path: ['STRIPE_SECRET_KEY'], + message: `Stripe secret key must start with 'sk_'`, + }, + ) + .refine( + (schema) => { + return schema.webhooksSecret.startsWith('whsec_'); + }, + { + path: ['STRIPE_WEBHOOK_SECRET'], + message: `Stripe webhook secret must start with 'whsec_'`, + }, + ); diff --git a/packages/stripe/src/services/stripe-sdk.ts b/packages/stripe/src/services/stripe-sdk.ts index ccff536a2..f0d9299cf 100644 --- a/packages/stripe/src/services/stripe-sdk.ts +++ b/packages/stripe/src/services/stripe-sdk.ts @@ -16,7 +16,7 @@ export async function createStripeClient() { STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET, }); - return new Stripe(stripeServerEnv.STRIPE_SECRET_KEY, { + return new Stripe(stripeServerEnv.secretKey, { apiVersion: STRIPE_API_VERSION, }); } diff --git a/packages/stripe/src/services/stripe-webhook-handler.service.ts b/packages/stripe/src/services/stripe-webhook-handler.service.ts index 19d867986..b0d55e8a4 100644 --- a/packages/stripe/src/services/stripe-webhook-handler.service.ts +++ b/packages/stripe/src/services/stripe-webhook-handler.service.ts @@ -32,9 +32,9 @@ export class StripeWebhookHandlerService const signatureKey = `stripe-signature`; const signature = request.headers.get(signatureKey)!; - const { STRIPE_WEBHOOK_SECRET } = StripeServerEnvSchema.parse({ - STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY, - STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET, + const { webhooksSecret } = StripeServerEnvSchema.parse({ + secretKey: process.env.STRIPE_SECRET_KEY, + webhooksSecret: process.env.STRIPE_WEBHOOK_SECRET, }); const stripe = await this.loadStripe(); @@ -42,7 +42,7 @@ export class StripeWebhookHandlerService const event = stripe.webhooks.constructEvent( body, signature, - STRIPE_WEBHOOK_SECRET, + webhooksSecret, ); if (!event) {