Skip to content
Merged
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
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,30 @@ Temporal.io Worker Interceptor for Predicate Authority Zero-Trust authorization.

This package provides a pre-execution security gate for all Temporal Activities, enforcing cryptographic authorization mandates before any activity code runs.

## Prerequisites

This package requires the **Predicate Authority Sidecar** daemon to be running. The sidecar is a lightweight Rust binary that handles policy evaluation and mandate signing.

| Resource | Link |
|----------|------|
| Sidecar Repository | [github.com/PredicateSystems/predicate-authority-sidecar](https://github.com/PredicateSystems/predicate-authority-sidecar) |
| Download Binaries | [Latest Releases](https://github.com/PredicateSystems/predicate-authority-sidecar/releases) |
| License | MIT / Apache 2.0 |

### Quick Sidecar Setup

```bash
# Download the latest release for your platform
# Linux x64, macOS x64/ARM64, Windows x64 available

# Extract and run
tar -xzf predicate-authorityd-*.tar.gz
chmod +x predicate-authorityd

# Start with a policy file
./predicate-authorityd --port 8787 --policy-file policy.json
```

## Installation

```bash
Expand Down
89 changes: 89 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Predicate Temporal TypeScript Examples

This directory contains examples demonstrating how to use `@predicatesystems/temporal` to secure Temporal activities.

## Prerequisites

1. Install dependencies:
```bash
npm install @temporalio/client @temporalio/worker @temporalio/workflow
npm install @predicatesystems/authority @predicatesystems/temporal
```

2. Start the Predicate Authority daemon:
```bash
# Download from https://github.com/PredicateSystems/predicate-authority-sidecar/releases
./predicate-authorityd --port 8787 --policy-file policy.json
```

3. Start a local Temporal server:
```bash
temporal server start-dev
```

## Examples

### Basic Example

A minimal example showing:
- Setting up a worker with Predicate interceptor
- Defining secured activities
- Running workflows

```bash
npx ts-node basic-worker.ts
```

### E-commerce Example

A realistic e-commerce scenario with:
- Order processing activities
- Payment handling
- Inventory management
- Policy-based access control

```bash
npx ts-node ecommerce-worker.ts
```

## Policy File

The `policy.json` file defines which activities are allowed:

```json
{
"rules": [
{
"name": "allow-order-processing",
"effect": "allow",
"principals": ["temporal-worker"],
"actions": ["processOrder", "checkInventory", "sendConfirmation"],
"resources": ["*"]
},
{
"name": "deny-dangerous-activities",
"effect": "deny",
"principals": ["*"],
"actions": ["delete*", "admin*"],
"resources": ["*"]
}
]
}
```

## Running the Examples

1. Compile TypeScript:
```bash
npx tsc
```

2. Run the worker:
```bash
node dist/examples/basic-worker.js
```

Or use ts-node for development:
```bash
npx ts-node examples/basic-worker.ts
```
166 changes: 166 additions & 0 deletions examples/activities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/**
* Activity definitions for Temporal examples.
*
* These activities will be secured by Predicate Authority.
*/

// ============================================================================
// Basic Activities (allowed by policy)
// ============================================================================

export async function greet(name: string): Promise<string> {
return `Hello, ${name}!`;
}

export async function fetchData(dataId: string): Promise<Record<string, unknown>> {
// Simulate fetching data
await sleep(100);
return {
id: dataId,
value: "sample_data",
status: "active",
timestamp: new Date().toISOString(),
};
}

export async function processData(data: Record<string, unknown>): Promise<Record<string, unknown>> {
// Simulate processing
await sleep(50);
return {
...data,
processed: true,
processedBy: "temporal-worker",
processedAt: new Date().toISOString(),
};
}

// ============================================================================
// E-commerce Activities (allowed by policy)
// ============================================================================

export interface OrderItem {
productId: string;
quantity: number;
price: number;
}

export interface Order {
orderId: string;
customerEmail: string;
items: OrderItem[];
}

export async function checkInventory(
items: OrderItem[]
): Promise<{ available: boolean; checkedItems: string[] }> {
console.log(`[Activity] Checking inventory for ${items.length} items`);
await sleep(100);

return {
available: true,
checkedItems: items.map((item) => item.productId),
};
}

export async function reserveInventory(
items: OrderItem[]
): Promise<{ reserved: boolean; reservationId: string }> {
console.log(`[Activity] Reserving ${items.length} items`);
await sleep(100);

return {
reserved: true,
reservationId: `res-${randomId()}`,
};
}

export async function chargePayment(
orderId: string,
amount: number,
email: string
): Promise<{ success: boolean; transactionId: string; amount: number }> {
console.log(`[Activity] Charging $${amount.toFixed(2)} for order ${orderId}`);
await sleep(200);

return {
success: true,
transactionId: `txn-${randomId()}`,
amount,
};
}

export async function refundPayment(
transactionId: string,
amount: number
): Promise<{ refunded: boolean; refundId: string }> {
console.log(`[Activity] Refunding $${amount.toFixed(2)} for transaction ${transactionId}`);
await sleep(100);

return {
refunded: true,
refundId: `ref-${randomId()}`,
};
}

export async function sendConfirmation(
email: string,
orderId: string,
transactionId: string
): Promise<{ sent: boolean; email: string; orderId: string }> {
console.log(`[Activity] Sending confirmation to ${email} for order ${orderId}`);
await sleep(100);

return {
sent: true,
email,
orderId,
};
}

export async function processOrder(
order: Order
): Promise<{ processed: boolean; orderId: string }> {
console.log(`[Activity] Processing order ${order.orderId}`);
await sleep(150);

return {
processed: true,
orderId: order.orderId,
};
}

// ============================================================================
// Dangerous Activities (BLOCKED by policy)
// ============================================================================

export async function deleteOrder(orderId: string): Promise<{ deleted: boolean; orderId: string }> {
// This will NEVER execute due to Predicate authorization
console.log(`[Activity] DANGER: Deleting order ${orderId}`);
return { deleted: true, orderId };
}

export async function adminOverridePayment(
orderId: string
): Promise<{ overridden: boolean; orderId: string }> {
// This will NEVER execute due to Predicate authorization
console.log(`[Activity] DANGER: Admin override for ${orderId}`);
return { overridden: true, orderId };
}

export async function dropDatabase(): Promise<{ dropped: boolean }> {
// This will NEVER execute due to Predicate authorization
console.log("[Activity] DANGER: Dropping database!");
return { dropped: true };
}

// ============================================================================
// Helpers
// ============================================================================

function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

function randomId(): string {
return Math.random().toString(36).substring(2, 8);
}
104 changes: 104 additions & 0 deletions examples/basic-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Basic example demonstrating Predicate Temporal interceptor.
*
* This example shows:
* 1. Setting up a Temporal worker with Predicate authorization
* 2. Defining activities that will be secured
* 3. Running a workflow that executes those activities
*
* Prerequisites:
* - Temporal server running locally (temporal server start-dev)
* - Predicate Authority daemon running (./predicate-authorityd --port 8787 --policy-file policy.json)
*/

import { Client, Connection } from "@temporalio/client";
import { Worker } from "@temporalio/worker";
import { AuthorityClient } from "@predicatesystems/authority";
import { createPredicateInterceptors } from "@predicatesystems/temporal";

import * as activities from "./activities";

async function main() {
// Connect to Temporal
const connection = await Connection.connect({ address: "localhost:7233" });
const client = new Client({ connection });

// Initialize Predicate Authority client
const authorityClient = new AuthorityClient({
baseUrl: "http://127.0.0.1:8787",
timeoutMs: 5000,
});

// Create the Predicate interceptors
const interceptors = createPredicateInterceptors({
authorityClient,
principal: "temporal-worker",
});

// Create worker with the interceptor
const worker = await Worker.create({
connection,
namespace: "default",
taskQueue: "predicate-demo-queue",
workflowsPath: require.resolve("./workflows"),
activities,
interceptors,
});

console.log("Starting worker with Predicate authorization...");
console.log("=".repeat(60));

// Run worker
const workerPromise = worker.run();

// Give worker time to start
await sleep(1000);

try {
// Execute the basic workflow - should succeed
console.log("\n[1] Running basicWorkflow (should succeed)...");
try {
const result = await client.workflow.execute("basicWorkflow", {
args: [{ name: "Alice", dataId: "data-123" }],
workflowId: "basic-workflow-1",
taskQueue: "predicate-demo-queue",
});
console.log(` Greeting: ${result.greeting}`);
console.log(` Processed data: ${JSON.stringify(result.processedData, null, 2)}`);
console.log(" Status: SUCCESS");
} catch (error) {
console.log(` Status: FAILED - ${error}`);
}

// Execute the malicious workflow - activities should be blocked
console.log("\n[2] Running maliciousWorkflow (activities should be blocked)...");
try {
const result = await client.workflow.execute("maliciousWorkflow", {
args: ["ORD-12345"],
workflowId: "malicious-workflow-1",
taskQueue: "predicate-demo-queue",
});
console.log(` Blocked activities: ${result.blocked.join(", ")}`);
console.log(" (These activities were denied by Predicate Authority)");
} catch (error) {
console.log(` Error: ${error}`);
}

console.log("\n" + "=".repeat(60));
console.log("Demo complete!");
} finally {
// Shutdown
worker.shutdown();
await workerPromise;
await connection.close();
}
}

function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

main().catch((err) => {
console.error("Error:", err);
process.exit(1);
});
Loading