Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,16 @@ E2E_ABLY_ABLY_ACCESS_TOKEN=your_control_api_access_token
# Set this to skip E2E tests even when API key is present (for CI or local dev when you don't want to run E2E tests)
# SKIP_E2E_TESTS=true

# CI bypass secret for rate limit bypass in parallel tests
# This should match the CI_BYPASS_SECRET on the terminal server
CI_BYPASS_SECRET=your_ci_bypass_secret
# Terminal server signing secret
# MUST match the live server's SIGNING_SECRET configuration at wss://web-cli.ably.com
# Used to:
# 1. Sign credentials for HMAC authentication (signedConfig + signature)
# 2. Bypass rate limiting in tests (bypassRateLimit flag in signed config)
# Contact platform team for the actual secret
TERMINAL_SERVER_SIGNING_SECRET=your_signing_secret

# Legacy: CI_BYPASS_SECRET (still supported as fallback)
# CI_BYPASS_SECRET=your_ci_bypass_secret

# Terminal server URL for local testing (defaults to production)
# TERMINAL_SERVER_URL=ws://localhost:8080
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
nodejs 22.14.0
pnpm 10.28.0
48 changes: 48 additions & 0 deletions examples/web-cli/api/sign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { VercelRequest, VercelResponse } from "@vercel/node";
import { signCredentials, getSigningSecret } from "../server/sign-handler.js";

/**
* Vercel Serverless Function: Sign credentials for terminal authentication
*
* This endpoint signs API keys with HMAC-SHA256 to create signed configs
* that can be validated by the terminal server.
*
* Environment Variables Required:
* - SIGNING_SECRET or TERMINAL_SERVER_SIGNING_SECRET
*
* Request Body:
* - apiKey: string (required) - Ably API key in format "appId.keyId:secret"
* - bypassRateLimit: boolean (optional) - Set to true for CI/testing
*
* Response:
* - signedConfig: string - JSON-encoded config that was signed
* - signature: string - HMAC-SHA256 hex signature
*/
export default async function handler(
req: VercelRequest,
res: VercelResponse,
) {
// Only accept POST requests
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}

// Get signing secret from environment
const secret = getSigningSecret();

if (!secret) {
console.error("[/api/sign] Signing secret not configured");
return res.status(500).json({ error: "Signing secret not configured" });
}

const { apiKey, bypassRateLimit } = req.body;

if (!apiKey) {
return res.status(400).json({ error: "apiKey is required" });
}

// Use shared signing logic
const result = signCredentials({ apiKey, bypassRateLimit }, secret);

res.status(200).json(result);
}
1 change: 1 addition & 0 deletions examples/web-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@tailwindcss/vite": "^4.1.5",
"@types/react": "^18.3.20",
"@types/react-dom": "^18.3.5",
"@vercel/node": "^5.5.17",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.21",
"eslint": "^9.21.0",
Expand Down
62 changes: 62 additions & 0 deletions examples/web-cli/server/sign-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import crypto from "crypto";

/**
* Shared signing logic for credential authentication
* Used by: Vercel function, Vite middleware, and preview server
*/

export interface SignRequest {
apiKey: string;
bypassRateLimit?: boolean;
}

export interface SignResponse {
signedConfig: string;
signature: string;
}

/**
* Sign credentials using HMAC-SHA256
* @param request - Request containing apiKey and optional flags
* @param secret - Signing secret from environment
* @returns Signed config and signature
*/
export function signCredentials(
request: SignRequest,
secret: string,
): SignResponse {
const { apiKey, bypassRateLimit } = request;

// Build config object (matches terminal server expectations)
const config = {
apiKey,
timestamp: Date.now(),
bypassRateLimit: bypassRateLimit || false,
};

// Serialize to JSON - this exact string is what gets signed
const configString = JSON.stringify(config);

// Generate HMAC-SHA256 signature
const hmac = crypto.createHmac("sha256", secret);
hmac.update(configString);
const signature = hmac.digest("hex");

return {
signedConfig: configString,
signature,
};
}

/**
* Get signing secret from environment variables
* Checks multiple variable names for compatibility
*/
export function getSigningSecret(): string | null {
return (
process.env.TERMINAL_SERVER_SIGNING_SECRET ||
process.env.SIGNING_SECRET ||
process.env.CI_BYPASS_SECRET ||
null
);
}
Loading