Skip to content

Commit 25f40bf

Browse files
committed
Secure Stripe endpoints
1 parent 6c918f2 commit 25f40bf

File tree

5 files changed

+45
-10
lines changed

5 files changed

+45
-10
lines changed

.github/actions/heroku-deploy/action.yml

+7-3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ inputs:
5050
stripe-secret-key:
5151
description: "Stripe secret key for checkout flow"
5252
required: true
53+
stripe-endpoint-key:
54+
description: "Stripe endpoint key for verifying webhook calls"
55+
required: true
5356
client-url:
5457
description: "Client base URL for Stripe checkout result redirection"
5558
required: true
@@ -96,9 +99,10 @@ runs:
9699
heroku config:set MAILER_REFRESH_TOKEN="${{ inputs.mailer-refresh-token }}" -a $HEROKU_APP_NAME && \
97100
heroku config:set STRIPE_PUBLISHABLE_TEST_KEY="${{ inputs.stripe-publishable-key }}" -a $HEROKU_APP_NAME && \
98101
heroku config:set STRIPE_SECRET_TEST_KEY="${{ inputs.stripe-secret-key }}" -a $HEROKU_APP_NAME && \
99-
heroku config:set PREVIEW_DEPLOY=$([ "${{inputs.deploy-branch}}" = "main" ] && echo "false" || echo "true") -a $HEROKU_APP_NAME
100-
heroku config:set NODE_ENV="${{ inputs.node-env }}"
101-
heroku config:set CLIENT_URL="${{ inputs.client-url }}"
102+
heroku config:set STRIPE_ENDPOINT_KEY="${{ inputs.stripe-endpoint-key }}" -a $HEROKU_APP_NAME && \
103+
heroku config:set PREVIEW_DEPLOY=$([ "${{inputs.deploy-branch}}" = "main" ] && echo "false" || echo "true") -a $HEROKU_APP_NAME && \
104+
heroku config:set NODE_ENV="${{ inputs.node-env }}" -a $HEROKU_APP_NAME && \
105+
heroku config:set CLIENT_URL="${{ inputs.client-url }}" -a $HEROKU_APP_NAME
102106
env:
103107
HEROKU_APP_NAME: "${{ inputs.heroku-app-name }}"
104108
shell: bash

.github/workflows/deploy-prod.yml

+1
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,6 @@ jobs:
4343
mailer-refresh-token: "${{ secrets.PRODUCTION_MAILER_REFRESH_TOKEN }}"
4444
stripe-publishable-key: "${{ secrets.STRIPE_PUBLISHABLE_TEST_KEY }}"
4545
stripe-secret-key: "${{ secrets.STRIPE_SECRET_TEST_KEY }}"
46+
stripe-endpoint-key: "${{ secrets.STRIPE_ENDPOINT_KEY }}"
4647
client-url: "${{ secrets.CLIENT_URL }}"
4748
deploy-branch: "main"

.github/workflows/heroku-deploy-dev-ts.yml

+1
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,6 @@ jobs:
3535
mailer-refresh-token: "${{ secrets.MAILER_REFRESH_TOKEN }}"
3636
stripe-publishable-key: "${{ secrets.STRIPE_PUBLISHABLE_TEST_KEY }}"
3737
stripe-secret-key: "${{ secrets.STRIPE_SECRET_TEST_KEY }}"
38+
stripe-endpoint-key: "${{ secrets.STRIPE_ENDPOINT_KEY }}"
3839
client-url: "${{ secrets.CLIENT_URL }}"
3940
deploy-branch: "dev"

backend/typescript/rest/camperRoutes.ts

+15-7
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@ import {
1818
WaitlistedCamperDTO,
1919
} from "../types";
2020
import { createWaitlistedCampersDtoValidator } from "../middlewares/validators/waitlistedCampersValidators";
21+
import {
22+
stripeKey,
23+
verifyStripeWebhooksRequest,
24+
} from "../utilities/stripeUtils";
2125

2226
const camperRouter: Router = Router();
2327

2428
const camperService: ICamperService = new CamperService();
2529

26-
// TODO: secure stripe keys
27-
const STRIPE_ENDPOINT_KEY = process.env.STRIPE_ENDPOINT_SECRET || "";
28-
2930
// ROLES: Leaving unprotected as the registration flow probs needs this endpoint to register @dhruv
3031
/* Create a camper */
3132
camperRouter.post("/register", createCampersDtoValidator, async (req, res) => {
@@ -121,12 +122,19 @@ camperRouter.get("/:chargeId/:sessionId", async (req, res) => {
121122
/* Initiated by Stripe webhook. On successful payment, mark camper as paid. */
122123
camperRouter.post("/confirm-payment", async (req, res) => {
123124
try {
124-
const { body } = req;
125+
const event = verifyStripeWebhooksRequest(
126+
req.headers["stripe-signature"],
127+
req.body,
128+
);
129+
130+
if (!event) {
131+
res.status(400).send("Webhook signature verification failed");
132+
}
125133

126-
if (body.type === "checkout.session.completed") {
127-
const chargeId = body.data.object.id;
134+
if (event.type === "checkout.session.completed") {
135+
const chargeId = event.data.object.id;
128136

129-
if (body.data.object.payment_status === "paid") {
137+
if (event.data.object.payment_status === "paid") {
130138
await camperService.confirmCamperPayment(
131139
(chargeId as unknown) as string,
132140
);

backend/typescript/utilities/stripeUtils.ts

+21
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const stripe = new Stripe(process.env.STRIPE_SECRET_TEST_KEY ?? "", {
88
apiVersion: "2020-08-27",
99
});
1010

11+
const STRIPE_ENDPOINT_KEY = process.env.STRIPE_ENDPOINT_SECRET || "";
12+
1113
const dropoffProductName = "Early Drop Off Fees";
1214
const pickupProductName = "Late Pick Up Fees";
1315

@@ -146,3 +148,22 @@ export async function createStripeCheckoutSession(
146148

147149
return checkoutSession;
148150
}
151+
152+
// Similar to Stripe example:
153+
// https://github.com/stripe/stripe-node/blob/master/examples/webhook-signing/typescript-node-express/express-ts.ts
154+
export const verifyStripeWebhooksRequest = (
155+
signature: any,
156+
body: any,
157+
): Stripe.Event | undefined => {
158+
try {
159+
const event: Stripe.Event = stripe.webhooks.constructEvent(
160+
body,
161+
signature,
162+
STRIPE_ENDPOINT_KEY,
163+
);
164+
return event;
165+
} catch (err: any) {
166+
console.log(`❌ Error message: ${err.message}`);
167+
return undefined;
168+
}
169+
};

0 commit comments

Comments
 (0)