Skip to content

Commit 8a06178

Browse files
committed
feat: add api auth
1 parent 80e1e30 commit 8a06178

File tree

4 files changed

+255
-2
lines changed

4 files changed

+255
-2
lines changed

infra/sandbox/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"start": "tsx watch --clear-screen=false src/index.ts",
2929
"dev": "tsx watch --clear-screen=false src/index.ts",
3030
"dev:mcp": "tsx watch --clear-screen=false src/mcp-server.ts",
31+
"generate-api-key": "node scripts/generate-api-key.cjs",
3132
"test": "jest",
3233
"lint": "eslint src --ext .ts",
3334
"prepack": "npm run build"
@@ -75,4 +76,4 @@
7576
"tsx": "^4.16.5",
7677
"typescript": "^5.3.3"
7778
}
78-
}
79+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* GraphScope Sandbox API Key Generator
5+
*
6+
* Usage:
7+
* node scripts/generate-api-key.cjs
8+
* node scripts/generate-api-key.cjs --count 3
9+
* node scripts/generate-api-key.cjs --prefix custom_prefix
10+
*/
11+
12+
const crypto = require("crypto");
13+
14+
function generateApiKey(prefix = "ai-spider") {
15+
const chars =
16+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
17+
let result = prefix + "_";
18+
19+
for (let i = 0; i < 32; i++) {
20+
result += chars.charAt(Math.floor(Math.random() * chars.length));
21+
}
22+
23+
return result;
24+
}
25+
26+
function main() {
27+
const args = process.argv.slice(2);
28+
29+
let count = 1;
30+
let prefix = "ai-spider";
31+
32+
// Parse command line arguments
33+
for (let i = 0; i < args.length; i++) {
34+
if (args[i] === "--count" && i + 1 < args.length) {
35+
count = parseInt(args[i + 1]);
36+
i++;
37+
} else if (args[i] === "--prefix" && i + 1 < args.length) {
38+
prefix = args[i + 1];
39+
i++;
40+
} else if (args[i] === "--help" || args[i] === "-h") {
41+
console.log(`
42+
GraphScope Sandbox API Key Generator
43+
44+
Usage:
45+
node scripts/generate-api-key.cjs [options]
46+
47+
Options:
48+
--count <number> Number of API Keys to generate (default: 1)
49+
--prefix <string> API Key prefix (default: ai-spider)
50+
--help, -h Show help
51+
52+
Examples:
53+
node scripts/generate-api-key.cjs
54+
node scripts/generate-api-key.cjs --count 3
55+
node scripts/generate-api-key.cjs --prefix my_app
56+
`);
57+
return;
58+
}
59+
}
60+
61+
console.log("🔑 GraphScope Sandbox API Key Generator\n");
62+
63+
if (count === 1) {
64+
const apiKey = generateApiKey(prefix);
65+
console.log(`Generated API Key: ${apiKey}`);
66+
console.log("\n💡 Usage:");
67+
console.log(`export SANDBOX_API_KEY="${apiKey}"`);
68+
console.log(`Or add to your .env file:`);
69+
console.log(`SANDBOX_API_KEY=${apiKey}`);
70+
} else {
71+
const apiKeys = [];
72+
for (let i = 0; i < count; i++) {
73+
apiKeys.push(generateApiKey(prefix));
74+
}
75+
76+
console.log(`Generated ${count} API Keys:`);
77+
apiKeys.forEach((key, index) => {
78+
console.log(`${index + 1}. ${key}`);
79+
});
80+
81+
console.log("\n💡 Usage:");
82+
console.log("Method 1 - Use a single key (pick one):");
83+
console.log(`export SANDBOX_API_KEY="${apiKeys[0]}"`);
84+
85+
console.log("\nMethod 2 - Use all keys together:");
86+
console.log(`export SANDBOX_API_KEYS="${apiKeys.join(",")}"`);
87+
88+
console.log("\nOr add to your .env file:");
89+
console.log(`SANDBOX_API_KEYS=${apiKeys.join(",")}`);
90+
}
91+
92+
console.log("\n⚠️ Security Tips:");
93+
console.log("- Keep your API Keys safe");
94+
console.log("- Do not hardcode API Keys in your code");
95+
console.log("- Rotate API Keys regularly");
96+
console.log("- Use different API Keys for different environments");
97+
}
98+
99+
if (require.main === module) {
100+
main();
101+
}
102+
103+
module.exports = { generateApiKey };
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { Request, Response, NextFunction } from "express";
2+
import logger from "../utils/logger";
3+
4+
interface AuthenticatedRequest extends Request {
5+
isAuthenticated?: boolean;
6+
}
7+
8+
/**
9+
* Simple API Key authentication middleware.
10+
* Supports multiple API keys via environment variables.
11+
*/
12+
export const simpleApiKeyAuth = (
13+
req: AuthenticatedRequest,
14+
res: Response,
15+
next: NextFunction
16+
) => {
17+
try {
18+
const authHeader = req.headers.authorization;
19+
20+
// Check if Authorization header exists
21+
if (!authHeader) {
22+
logger.warn("Missing authorization header", {
23+
ip: req.ip,
24+
path: req.path,
25+
method: req.method
26+
});
27+
28+
return res.status(401).json({
29+
error: {
30+
code: "MISSING_AUTH",
31+
message:
32+
"Authorization header is required. Use: Authorization: Bearer <your-api-key>"
33+
}
34+
});
35+
}
36+
37+
// Check format: Bearer <api-key>
38+
if (!authHeader.startsWith("Bearer ")) {
39+
logger.warn("Invalid authorization format", {
40+
ip: req.ip,
41+
path: req.path,
42+
authHeader: authHeader.substring(0, 20) + "..."
43+
});
44+
45+
return res.status(401).json({
46+
error: {
47+
code: "INVALID_AUTH_FORMAT",
48+
message:
49+
"Authorization header must be in format: Bearer <your-api-key>"
50+
}
51+
});
52+
}
53+
54+
const apiKey = authHeader.replace("Bearer ", "").trim();
55+
56+
// Validate API Key
57+
if (!isValidApiKey(apiKey)) {
58+
logger.warn("Invalid API key attempt", {
59+
ip: req.ip,
60+
path: req.path,
61+
keyPrefix: apiKey.substring(0, 8) + "..."
62+
});
63+
64+
return res.status(401).json({
65+
error: {
66+
code: "INVALID_API_KEY",
67+
message: "Invalid API key"
68+
}
69+
});
70+
}
71+
72+
// Authentication successful
73+
req.isAuthenticated = true;
74+
logger.info("API key authentication successful", {
75+
ip: req.ip,
76+
path: req.path,
77+
method: req.method,
78+
keyPrefix: apiKey.substring(0, 8) + "..."
79+
});
80+
81+
next();
82+
} catch (error) {
83+
logger.error("Authentication error", { error, ip: req.ip, path: req.path });
84+
85+
return res.status(500).json({
86+
error: {
87+
code: "AUTH_ERROR",
88+
message: "Authentication service error"
89+
}
90+
});
91+
}
92+
};
93+
94+
/**
95+
* Validate if the API Key is valid.
96+
* Supports multiple configuration methods:
97+
* 1. Environment variable SANDBOX_API_KEYS (comma separated)
98+
* 2. Environment variable SANDBOX_API_KEY (single key)
99+
*/
100+
function isValidApiKey(apiKey: string): boolean {
101+
if (!apiKey || apiKey.length < 8) {
102+
return false;
103+
}
104+
105+
// Method 1: Multiple API keys, comma separated
106+
const apiKeys = process.env.SANDBOX_API_KEYS;
107+
if (apiKeys) {
108+
const validKeys = apiKeys.split(",").map((key) => key.trim());
109+
return validKeys.includes(apiKey);
110+
}
111+
112+
// Method 2: Single API key
113+
const singleApiKey = process.env.SANDBOX_API_KEY;
114+
if (singleApiKey) {
115+
return apiKey === singleApiKey.trim();
116+
}
117+
118+
// If no API key is configured, reject all requests
119+
logger.error(
120+
"No API keys configured. Please set SANDBOX_API_KEY or SANDBOX_API_KEYS environment variable"
121+
);
122+
return false;
123+
}
124+
125+
/**
126+
* Health check and management endpoints can skip authentication.
127+
*/
128+
export const skipAuthPaths = ["/health"];
129+
130+
/**
131+
* Authentication middleware with path filtering.
132+
*/
133+
export const conditionalAuth = (
134+
req: AuthenticatedRequest,
135+
res: Response,
136+
next: NextFunction
137+
) => {
138+
// Skip authentication for health check and similar paths
139+
if (skipAuthPaths.some((path) => req.path.startsWith(path))) {
140+
return next();
141+
}
142+
143+
// Require authentication for other paths
144+
return simpleApiKeyAuth(req, res, next);
145+
};

infra/sandbox/src/server.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import helmet from "helmet";
44
import morgan from "morgan";
55
import http, { createServer } from "http";
66
import { errorHandler, notFoundHandler } from "./middleware/error-handler";
7+
import { conditionalAuth } from "./middleware/simple-auth";
78
import {
89
createSandboxValidation,
910
execCodeValidation,
@@ -37,11 +38,14 @@ export function createApp(): {
3738
app.use(express.urlencoded({ limit: "100mb", extended: true })); // Parse URL-encoded bodies with 100MB limit
3839
app.use(morgan("dev")); // HTTP request logging
3940

40-
// Health check endpoint
41+
// Health check endpoint (no auth required)
4142
app.get("/health", (req, res) => {
4243
res.status(200).json({ status: "ok" });
4344
});
4445

46+
// Apply API Key authentication to all /api routes
47+
app.use("/api", conditionalAuth);
48+
4549
// API routes
4650
app.post(
4751
"/api/sandbox",

0 commit comments

Comments
 (0)