fix: use file-based secrets for Stripe API keys (refs #55)

This commit is contained in:
Eric Gullickson
2026-01-18 18:02:10 -06:00
parent 1cf4b78075
commit 1718e8d41b
4 changed files with 39 additions and 17 deletions

View File

@@ -126,6 +126,9 @@ const secretsSchema = z.object({
auth0_management_client_secret: z.string(),
google_maps_api_key: z.string(),
resend_api_key: z.string(),
// Stripe secrets (API keys only - price IDs are config, not secrets)
stripe_secret_key: z.string(),
stripe_webhook_secret: z.string(),
});
type Config = z.infer<typeof configSchema>;
@@ -140,6 +143,10 @@ export interface AppConfiguration {
getRedisUrl(): string;
getAuth0Config(): { domain: string; audience: string; clientSecret: string };
getAuth0ManagementConfig(): { domain: string; clientId: string; clientSecret: string };
getStripeConfig(): {
secretKey: string;
webhookSecret: string;
};
}
class ConfigurationLoader {
@@ -178,6 +185,8 @@ class ConfigurationLoader {
'auth0-management-client-secret',
'google-maps-api-key',
'resend-api-key',
'stripe-secret-key',
'stripe-webhook-secret',
];
for (const secretFile of secretFiles) {
@@ -240,6 +249,13 @@ class ConfigurationLoader {
clientSecret: secrets.auth0_management_client_secret,
};
},
getStripeConfig() {
return {
secretKey: secrets.stripe_secret_key,
webhookSecret: secrets.stripe_webhook_secret,
};
},
};
// Set RESEND_API_KEY in environment for EmailService

View File

@@ -45,10 +45,13 @@ Stripe payment integration for subscription tiers and donations.
### Webhooks (Public)
- POST /api/webhooks/stripe - Stripe webhook (signature verified)
## Environment Variables
## Configuration
- STRIPE_SECRET_KEY
- STRIPE_WEBHOOK_SECRET
### Secrets (files via config-loader)
- `/run/secrets/stripe-secret-key` - Stripe API secret key
- `/run/secrets/stripe-webhook-secret` - Stripe webhook signing secret
### Environment Variables (docker-compose)
- STRIPE_PRO_MONTHLY_PRICE_ID
- STRIPE_PRO_YEARLY_PRICE_ID
- STRIPE_ENTERPRISE_MONTHLY_PRICE_ID

View File

@@ -151,11 +151,18 @@ When user downgrades to a tier with fewer vehicle allowance:
5. Selections saved to tier_vehicle_selections table
6. On upgrade, all vehicles become accessible again
## Environment Variables
## Configuration
### Secrets (loaded from files via config-loader)
Secrets are loaded from `/run/secrets/` (or `SECRETS_DIR` env var):
```
/run/secrets/stripe-secret-key # Stripe API secret key
/run/secrets/stripe-webhook-secret # Stripe webhook signing secret
```
### Environment Variables (docker-compose)
Price IDs are not secrets and are configured via environment variables:
```
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRO_MONTHLY_PRICE_ID=price_...
STRIPE_PRO_YEARLY_PRICE_ID=price_...
STRIPE_ENTERPRISE_MONTHLY_PRICE_ID=price_...

View File

@@ -5,6 +5,7 @@
import Stripe from 'stripe';
import { logger } from '../../../../core/logging/logger';
import { appConfig } from '../../../../core/config/config-loader';
import {
StripeCustomer,
StripeSubscription,
@@ -14,18 +15,18 @@ import {
export class StripeClient {
private stripe: Stripe;
private webhookSecret: string;
constructor() {
const apiKey = process.env.STRIPE_SECRET_KEY;
if (!apiKey) {
throw new Error('STRIPE_SECRET_KEY environment variable is required');
}
const stripeConfig = appConfig.getStripeConfig();
this.stripe = new Stripe(apiKey, {
this.stripe = new Stripe(stripeConfig.secretKey, {
apiVersion: '2025-12-15.clover',
typescript: true,
});
this.webhookSecret = stripeConfig.webhookSecret;
logger.info('Stripe client initialized');
}
@@ -237,15 +238,10 @@ export class StripeClient {
*/
constructWebhookEvent(payload: Buffer, signature: string): StripeWebhookEvent {
try {
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
if (!webhookSecret) {
throw new Error('STRIPE_WEBHOOK_SECRET environment variable is required');
}
const event = this.stripe.webhooks.constructEvent(
payload,
signature,
webhookSecret
this.webhookSecret
);
logger.info('Stripe webhook event verified', { eventId: event.id, type: event.type });