Skip to content

Commit b156115

Browse files
Restricted allowed origins for API calls
1 parent a5f79f3 commit b156115

File tree

4 files changed

+173
-8
lines changed

4 files changed

+173
-8
lines changed

docs-v2/components/ConnectCodeSnippets.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export default function Home() {
4343
pd.connectAccount({
4444
app: "${appSlug}",
4545
token: "${tokenData?.token
46-
? tokenData.token.substring(0, 10) + "..."
46+
? tokenData.token
4747
: "{connect_token}"}",
4848
onSuccess: (account) => {
4949
// Handle successful connection

docs-v2/components/GlobalConnectProvider.jsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ export function GlobalConnectProvider({ children }) {
6565
// Get client code snippet wrapper function
6666
const getClientSnippet = () => getClientCodeSnippet(appSlug, tokenData);
6767

68+
/**
69+
* Generate a request token based on the browser environment
70+
* This creates a token that matches what the API will generate
71+
*/
72+
function generateRequestToken() {
73+
const baseString = `${navigator.userAgent}:${window.location.host}:connect-demo`;
74+
return btoa(baseString);
75+
}
76+
6877
// Generate token async function
6978
async function generateToken() {
7079
setTokenLoading(true);
@@ -73,10 +82,12 @@ export function GlobalConnectProvider({ children }) {
7382
setConnectedAccount(null);
7483

7584
try {
85+
const requestToken = generateRequestToken();
7686
const response = await fetch("/docs/api-demo-connect/token", {
7787
method: "POST",
7888
headers: {
7989
"Content-Type": "application/json",
90+
"X-Request-Token": requestToken,
8091
},
8192
body: JSON.stringify({
8293
external_user_id: externalUserId,
@@ -100,11 +111,13 @@ export function GlobalConnectProvider({ children }) {
100111
// Fetch account details from API
101112
async function fetchAccountDetails(accountId) {
102113
try {
103-
// Use the same token credentials to fetch account details
114+
// Fetch the account details from our API endpoint
115+
const requestToken = generateRequestToken();
104116
const response = await fetch(`/docs/api-demo-connect/accounts/${accountId}`, {
105117
method: "GET",
106118
headers: {
107119
"Content-Type": "application/json",
120+
"X-Request-Token": requestToken,
108121
},
109122
});
110123

docs-v2/pages/api/demo-connect/accounts/[id].js

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,85 @@
1-
// API route to fetch account details from Pipedream API
1+
/**
2+
* API route to fetch account details from Pipedream API
3+
* Retrieves information about connected accounts for the interactive demo
4+
*/
25
import { createBackendClient } from "@pipedream/sdk/server";
36

7+
// Allowed origins for CORS security
8+
const ALLOWED_ORIGINS = [
9+
"https://pipedream.com",
10+
"https://www.pipedream.com",
11+
"http://localhost:3000", // For local development
12+
];
13+
14+
/**
15+
* Generate a browser-specific token based on request properties
16+
* Used to verify requests are coming from our frontend
17+
*/
18+
function generateRequestToken(req) {
19+
const baseString = `${req.headers["user-agent"]}:${req.headers["host"]}:connect-demo`;
20+
return Buffer.from(baseString).toString("base64");
21+
}
22+
23+
/**
24+
* Security middleware to validate requests
25+
* Ensures requests only come from our documentation site
26+
*/
27+
function validateRequest(req, res) {
28+
const origin = req.headers.origin;
29+
const referer = req.headers.referer;
30+
const requestToken = req.headers["x-request-token"];
31+
32+
// Origin validation
33+
if (origin && !ALLOWED_ORIGINS.includes(origin)) {
34+
return res.status(403).json({
35+
error: "Access denied",
36+
});
37+
}
38+
39+
// Referer validation
40+
if (referer && !ALLOWED_ORIGINS.some((allowed) => referer.startsWith(allowed)) &&
41+
!referer.includes("/docs/connect/")) {
42+
return res.status(403).json({
43+
error: "Access denied",
44+
});
45+
}
46+
47+
// Request token validation to prevent API automation
48+
const expectedToken = generateRequestToken(req);
49+
if (!requestToken || requestToken !== expectedToken) {
50+
return res.status(403).json({
51+
error: "Access denied",
52+
});
53+
}
54+
55+
// Method validation
56+
if (req.method !== "GET") {
57+
return res.status(405).json({
58+
error: "Method not allowed",
59+
});
60+
}
61+
62+
// All security checks passed
63+
return null;
64+
}
65+
466
export default async function handler(req, res) {
67+
// Set CORS headers
68+
res.setHeader("Access-Control-Allow-Origin", ALLOWED_ORIGINS.includes(req.headers.origin)
69+
? req.headers.origin
70+
: "");
71+
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
72+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Request-Token");
73+
74+
// Handle preflight requests
75+
if (req.method === "OPTIONS") {
76+
return res.status(200).end();
77+
}
78+
79+
// Validate the request
80+
const validationError = validateRequest(req, res);
81+
if (validationError) return validationError;
82+
583
const { id } = req.query;
684

785
if (!id) {

docs-v2/pages/api/demo-connect/token.js

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,84 @@
1-
// API route for generating Connect tokens for demonstration purposes
2-
export default async function handler(req, res) {
1+
/**
2+
* API route for generating Connect tokens for the interactive demo
3+
* This endpoint creates short-lived tokens for testing the Pipedream Connect auth flow
4+
*/
5+
6+
// Allowed origins for CORS security
7+
const ALLOWED_ORIGINS = [
8+
"https://pipedream.com",
9+
"https://www.pipedream.com",
10+
"http://localhost:3000", // For local development
11+
];
12+
13+
/**
14+
* Generate a browser-specific token based on request properties
15+
* Used to verify requests are coming from our frontend
16+
*/
17+
function generateRequestToken(req) {
18+
const baseString = `${req.headers["user-agent"]}:${req.headers["host"]}:connect-demo`;
19+
return Buffer.from(baseString).toString("base64");
20+
}
21+
22+
/**
23+
* Security middleware to validate requests
24+
* Ensures requests only come from our documentation site
25+
*/
26+
function validateRequest(req, res) {
27+
const origin = req.headers.origin;
28+
const referer = req.headers.referer;
29+
const requestToken = req.headers["x-request-token"];
30+
31+
// Origin validation
32+
if (origin && !ALLOWED_ORIGINS.includes(origin)) {
33+
return res.status(403).json({
34+
error: "Access denied",
35+
});
36+
}
37+
38+
// Referer validation
39+
if (referer && !ALLOWED_ORIGINS.some((allowed) => referer.startsWith(allowed)) &&
40+
!referer.includes("/docs/connect/")) {
41+
return res.status(403).json({
42+
error: "Access denied",
43+
});
44+
}
45+
46+
// Request token validation to prevent API automation
47+
const expectedToken = generateRequestToken(req);
48+
if (!requestToken || requestToken !== expectedToken) {
49+
return res.status(403).json({
50+
error: "Access denied",
51+
});
52+
}
53+
54+
// Method validation
355
if (req.method !== "POST") {
456
return res.status(405).json({
557
error: "Method not allowed",
658
});
759
}
860

61+
// All security checks passed
62+
return null;
63+
}
64+
65+
export default async function handler(req, res) {
66+
// Set CORS headers
67+
res.setHeader("Access-Control-Allow-Origin", ALLOWED_ORIGINS.includes(req.headers.origin)
68+
? req.headers.origin
69+
: "");
70+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
71+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Request-Token");
72+
73+
// Handle preflight requests
74+
if (req.method === "OPTIONS") {
75+
return res.status(200).end();
76+
}
77+
78+
// Validate the request
79+
const validationError = validateRequest(req, res);
80+
if (validationError) return validationError;
81+
982
try {
1083
const { external_user_id } = req.body;
1184

@@ -48,9 +121,10 @@ export default async function handler(req, res) {
48121
},
49122
body: JSON.stringify({
50123
external_user_id,
51-
allowed_origins: [
52-
req.headers.origin || "https://pipedream.com",
53-
],
124+
allowed_origins: ALLOWED_ORIGINS,
125+
webhook_uri: process.env.PIPEDREAM_CONNECT_TOKEN_WEBHOOK_URI,
126+
success_redirect_uri: process.env.PIPEDREAM_CONNECT_SUCCESS_REDIRECT_URI,
127+
error_redirect_uri: process.env.PIPEDREAM_CONNECT_ERROR_REDIRECT_URI,
54128
}),
55129
});
56130

0 commit comments

Comments
 (0)