fix: use file-based secrets for Stripe API keys (refs #55)
This commit is contained in:
@@ -126,6 +126,9 @@ const secretsSchema = z.object({
|
|||||||
auth0_management_client_secret: z.string(),
|
auth0_management_client_secret: z.string(),
|
||||||
google_maps_api_key: z.string(),
|
google_maps_api_key: z.string(),
|
||||||
resend_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>;
|
type Config = z.infer<typeof configSchema>;
|
||||||
@@ -140,6 +143,10 @@ export interface AppConfiguration {
|
|||||||
getRedisUrl(): string;
|
getRedisUrl(): string;
|
||||||
getAuth0Config(): { domain: string; audience: string; clientSecret: string };
|
getAuth0Config(): { domain: string; audience: string; clientSecret: string };
|
||||||
getAuth0ManagementConfig(): { domain: string; clientId: string; clientSecret: string };
|
getAuth0ManagementConfig(): { domain: string; clientId: string; clientSecret: string };
|
||||||
|
getStripeConfig(): {
|
||||||
|
secretKey: string;
|
||||||
|
webhookSecret: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigurationLoader {
|
class ConfigurationLoader {
|
||||||
@@ -178,6 +185,8 @@ class ConfigurationLoader {
|
|||||||
'auth0-management-client-secret',
|
'auth0-management-client-secret',
|
||||||
'google-maps-api-key',
|
'google-maps-api-key',
|
||||||
'resend-api-key',
|
'resend-api-key',
|
||||||
|
'stripe-secret-key',
|
||||||
|
'stripe-webhook-secret',
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const secretFile of secretFiles) {
|
for (const secretFile of secretFiles) {
|
||||||
@@ -240,6 +249,13 @@ class ConfigurationLoader {
|
|||||||
clientSecret: secrets.auth0_management_client_secret,
|
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
|
// Set RESEND_API_KEY in environment for EmailService
|
||||||
|
|||||||
@@ -45,10 +45,13 @@ Stripe payment integration for subscription tiers and donations.
|
|||||||
### Webhooks (Public)
|
### Webhooks (Public)
|
||||||
- POST /api/webhooks/stripe - Stripe webhook (signature verified)
|
- POST /api/webhooks/stripe - Stripe webhook (signature verified)
|
||||||
|
|
||||||
## Environment Variables
|
## Configuration
|
||||||
|
|
||||||
- STRIPE_SECRET_KEY
|
### Secrets (files via config-loader)
|
||||||
- STRIPE_WEBHOOK_SECRET
|
- `/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_MONTHLY_PRICE_ID
|
||||||
- STRIPE_PRO_YEARLY_PRICE_ID
|
- STRIPE_PRO_YEARLY_PRICE_ID
|
||||||
- STRIPE_ENTERPRISE_MONTHLY_PRICE_ID
|
- STRIPE_ENTERPRISE_MONTHLY_PRICE_ID
|
||||||
|
|||||||
@@ -151,11 +151,18 @@ When user downgrades to a tier with fewer vehicle allowance:
|
|||||||
5. Selections saved to tier_vehicle_selections table
|
5. Selections saved to tier_vehicle_selections table
|
||||||
6. On upgrade, all vehicles become accessible again
|
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_MONTHLY_PRICE_ID=price_...
|
||||||
STRIPE_PRO_YEARLY_PRICE_ID=price_...
|
STRIPE_PRO_YEARLY_PRICE_ID=price_...
|
||||||
STRIPE_ENTERPRISE_MONTHLY_PRICE_ID=price_...
|
STRIPE_ENTERPRISE_MONTHLY_PRICE_ID=price_...
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
import { logger } from '../../../../core/logging/logger';
|
import { logger } from '../../../../core/logging/logger';
|
||||||
|
import { appConfig } from '../../../../core/config/config-loader';
|
||||||
import {
|
import {
|
||||||
StripeCustomer,
|
StripeCustomer,
|
||||||
StripeSubscription,
|
StripeSubscription,
|
||||||
@@ -14,18 +15,18 @@ import {
|
|||||||
|
|
||||||
export class StripeClient {
|
export class StripeClient {
|
||||||
private stripe: Stripe;
|
private stripe: Stripe;
|
||||||
|
private webhookSecret: string;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const apiKey = process.env.STRIPE_SECRET_KEY;
|
const stripeConfig = appConfig.getStripeConfig();
|
||||||
if (!apiKey) {
|
|
||||||
throw new Error('STRIPE_SECRET_KEY environment variable is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.stripe = new Stripe(apiKey, {
|
this.stripe = new Stripe(stripeConfig.secretKey, {
|
||||||
apiVersion: '2025-12-15.clover',
|
apiVersion: '2025-12-15.clover',
|
||||||
typescript: true,
|
typescript: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.webhookSecret = stripeConfig.webhookSecret;
|
||||||
|
|
||||||
logger.info('Stripe client initialized');
|
logger.info('Stripe client initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,15 +238,10 @@ export class StripeClient {
|
|||||||
*/
|
*/
|
||||||
constructWebhookEvent(payload: Buffer, signature: string): StripeWebhookEvent {
|
constructWebhookEvent(payload: Buffer, signature: string): StripeWebhookEvent {
|
||||||
try {
|
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(
|
const event = this.stripe.webhooks.constructEvent(
|
||||||
payload,
|
payload,
|
||||||
signature,
|
signature,
|
||||||
webhookSecret
|
this.webhookSecret
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info('Stripe webhook event verified', { eventId: event.id, type: event.type });
|
logger.info('Stripe webhook event verified', { eventId: event.id, type: event.type });
|
||||||
|
|||||||
Reference in New Issue
Block a user