Skip to content

Commit 777c9a6

Browse files
committed
fix(webhooks): robust Event Grid subscription validation; feat(runtime): runtime commit SHA injection
1 parent bc06480 commit 777c9a6

File tree

4 files changed

+61
-19
lines changed

4 files changed

+61
-19
lines changed

inject-env.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ sed -i "s|__VITE_OAUTH_CLIENT_ID__|${VITE_OAUTH_CLIENT_ID:-}|g" "$ENV_CONFIG_FIL
2121
sed -i "s|__VITE_OAUTH_CLIENT_SECRET__|${VITE_OAUTH_CLIENT_SECRET:-}|g" "$ENV_CONFIG_FILE"
2222
sed -i "s|__VITE_GRAPHQL_URL__|${VITE_GRAPHQL_URL:-}|g" "$ENV_CONFIG_FILE"
2323
sed -i "s|__VITE_OAUTH_TOKEN_URL__|${VITE_OAUTH_TOKEN_URL:-}|g" "$ENV_CONFIG_FILE"
24+
sed -i "s|__VITE_APP_COMMIT_SHA__|${VITE_APP_COMMIT_SHA:-}|g" "$ENV_CONFIG_FILE"
2425

2526
echo "✅ Environment variables injected successfully!"
2627
echo "🌐 Backend Base URL: ${VITE_BACKEND_BASE_URL:-'NOT SET'}"

public/env-config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
VITE_OAUTH_WEB_CLIENT_ID: '__VITE_OAUTH_WEB_CLIENT_ID__',
1212
VITE_OAUTH_WEB_CLIENT_SECRET: '__VITE_OAUTH_WEB_CLIENT_SECRET__',
1313
VITE_GRAPHQL_URL: '__VITE_GRAPHQL_URL__',
14-
VITE_OAUTH_TOKEN_URL: '__VITE_OAUTH_TOKEN_URL__'
14+
VITE_OAUTH_TOKEN_URL: '__VITE_OAUTH_TOKEN_URL__',
15+
// Build info (commit sha) can be provided at build time or injected at runtime
16+
VITE_APP_COMMIT_SHA: '__VITE_APP_COMMIT_SHA__'
1517
};
1618

1719

server/src/index.ts

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ app.post("/api/webhook/:userPath", (req: Request, res: Response) => {
5151
}
5252
}
5353

54-
// Event Grid sends an array of events in the body
55-
const events = Array.isArray(req.body) ? req.body : [req.body];
54+
// Event Grid usually sends an array, but some proxies or transports may send a single
55+
// object. Normalize to an array for easier handling.
56+
const events = Array.isArray(req.body) ? req.body : req.body ? [req.body] : [];
5657

5758
// Save last request for diagnostics
5859
try {
@@ -69,26 +70,51 @@ app.post("/api/webhook/:userPath", (req: Request, res: Response) => {
6970
// ignore
7071
}
7172

72-
// Azure Event Grid subscription validation can arrive in two common shapes:
73-
// 1) Standard validation event: an array with eventType === 'Microsoft.EventGrid.SubscriptionValidationEvent'
74-
// 2) A special header 'aeg-event-type: SubscriptionValidation' with the events array
75-
// Handle both cases robustly.
76-
const validationEvent = events.find((e: any) => e && (e.eventType === 'Microsoft.EventGrid.SubscriptionValidationEvent' || e.eventType === 'Microsoft.EventGridSubscriptionValidationEvent'));
73+
// Helper: extract validation code from an event object in a forgiving way.
74+
const extractValidationCode = (ev: any): string | undefined => {
75+
if (!ev) return undefined;
76+
// Common places: ev.data.validationCode (case variants)
77+
const data = ev.data || ev.Data || ev.DATA || ev;
78+
if (data && typeof data === 'object') {
79+
for (const key of Object.keys(data)) {
80+
if (key.toLowerCase() === 'validationcode' && data[key]) return String(data[key]);
81+
}
82+
}
83+
// Some transports may put the code at the top-level
84+
for (const key of Object.keys(ev)) {
85+
if (key.toLowerCase() === 'validationcode' && ev[key]) return String(ev[key]);
86+
}
87+
return undefined;
88+
};
89+
90+
// Event Grid subscription validation can arrive as:
91+
// - an event in the array with eventType matching expected strings
92+
// - header-based flows where 'aeg-event-type' tells us it's a validation attempt
93+
// Try to find any validation code in the incoming payloads.
94+
const validationEvent = events.find((e: any) => {
95+
if (!e) return false;
96+
const et = (e.eventType || e.EventType || '').toString();
97+
return (
98+
et === 'Microsoft.EventGrid.SubscriptionValidationEvent' ||
99+
et === 'Microsoft.EventGridSubscriptionValidationEvent' ||
100+
et === 'Microsoft.EventGrid.SubscriptionValidation' ||
101+
et.toLowerCase().includes('subscriptionvalidation')
102+
);
103+
});
104+
77105
if (validationEvent) {
78-
const data = validationEvent.data || {};
79-
console.log(`EventGrid subscription validation for ${userPath}:`, data);
80-
return res.status(200).json({ validationResponse: data.validationCode });
106+
const code = extractValidationCode(validationEvent) || (validationEvent.data && validationEvent.data.validationCode) || undefined;
107+
console.log(`EventGrid subscription validation (event) for ${userPath}:`, validationEvent?.data || validationEvent, '=> code=', code);
108+
if (code) return res.status(200).json({ validationResponse: code });
81109
}
82110

83-
// Also handle the header-based SubscriptionValidation flow
111+
// Also handle the header-based SubscriptionValidation flow: check aeg header and try first event
84112
const aegHeader = (req.header('aeg-event-type') || '').toString();
85-
if (aegHeader === 'SubscriptionValidation' && Array.isArray(events) && events.length > 0) {
113+
if (aegHeader && aegHeader.toLowerCase().includes('subscriptionvalidation') && events.length > 0) {
86114
const maybe = events[0] as any;
87-
const code = maybe && maybe.data && (maybe.data.validationCode || maybe.data.validationcode || maybe.data.ValidationCode);
88-
if (code) {
89-
console.log(`EventGrid header-based validation for ${userPath}:`, code);
90-
return res.status(200).json({ validationResponse: code });
91-
}
115+
const code = extractValidationCode(maybe);
116+
console.log(`EventGrid header-based validation for ${userPath}: aeg=${aegHeader} =>`, maybe, '=> code=', code);
117+
if (code) return res.status(200).json({ validationResponse: code });
92118
}
93119

94120
// Normal events: log and ack

src/utils/buildInfo.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ export const getBuildInfo = (): BuildInfo => {
2323
const buildTime = import.meta.env.VITE_APP_BUILD_TIME;
2424
const commitSha = import.meta.env.VITE_APP_COMMIT_SHA;
2525

26+
// If the commit SHA isn't available at build time, try the runtime-injected window.env
27+
// (env-config.js injects window.env when running inside the container)
28+
let runtimeCommitSha: string | undefined = undefined;
29+
try {
30+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
31+
const w = window as any;
32+
if (w && w.env && w.env.VITE_APP_COMMIT_SHA) {
33+
runtimeCommitSha = String(w.env.VITE_APP_COMMIT_SHA);
34+
}
35+
} catch (e) {
36+
// ignore (e.g., server-side rendering or restricted env)
37+
}
38+
2639
// Format the build time if available
2740
let timestamp: string;
2841
if (buildTime) {
@@ -35,7 +48,7 @@ export const getBuildInfo = (): BuildInfo => {
3548
return {
3649
version,
3750
timestamp,
38-
commit: commitSha?.slice(0, 7) || undefined,
51+
commit: (commitSha || runtimeCommitSha)?.slice ? (commitSha || runtimeCommitSha)?.slice(0, 7) : undefined,
3952
};
4053
};
4154

0 commit comments

Comments
 (0)