History

feat: enhance secret management in SDK

- Introduced `SecretRef` class to represent stored secrets without their values. - Updated `SecretStore` to manage `SecretRef` instances, allowing for better handling of secret resolution and status checks. - Added `resolve` and `status` methods to `SecretStore` for retrieving secret values and checking their availability. - Refactored in-memory secret store to support the new structure and added a default in-memory secret provider. - Updated OpenAPI plugin to resolve secret-backed headers during invocation, improving security handling in API requests. - Enhanced tests to cover new secret management features and ensure proper resolution of secrets in headers. All changes are aimed at improving the flexibility and security of secret handling within the SDK.

Rhys Sullivan committed 7 months ago

Commit 8d3cddf1 parent 673821a11 files · +441 −87Browse files

Showing the changes to packages/plugins/openapi/src/plugin.test.ts (1 of 11 files). Show all

Unified · Split

packages/plugins/openapi/src/plugin.test.ts+96−1
@@ -6,11 +6,12 @@ import { · 5 unmodified lines
6 HttpApiEndpoint,6 HttpApiEndpoint,
7 HttpApiGroup,7 HttpApiGroup,
8 HttpClient,8 HttpClient,
9 HttpServerRequest,
9 OpenApi,10 OpenApi,
10} from "@effect/platform";11} from "@effect/platform";
11import { NodeHttpServer } from "@effect/platform-node";12import { NodeHttpServer } from "@effect/platform-node";
12 13
13import { createExecutor, makeTestConfig } from "@executor/sdk";14import { createExecutor, makeTestConfig, SecretId } from "@executor/sdk";
14import { openApiPlugin } from "./plugin";15import { openApiPlugin } from "./plugin";
15 16
16// ---------------------------------------------------------------------------17// ---------------------------------------------------------------------------
@@ -22,6 +23,11 @@ class Item extends Schema.Class<Item>("Item")({ · 5 unmodified lines
22 name: Schema.String,23 name: Schema.String,
23}) {}24}) {}
24 25
26class EchoHeaders extends Schema.Class<EchoHeaders>("EchoHeaders")({
27 authorization: Schema.optional(Schema.String),
28 "x-static": Schema.optional(Schema.String),
29}) {}
30
25const ItemsGroup = HttpApiGroup.make("items")31const ItemsGroup = HttpApiGroup.make("items")
26 .add(32 .add(
27 HttpApiEndpoint.get("listItems", "/items").addSuccess(Schema.Array(Item)),33 HttpApiEndpoint.get("listItems", "/items").addSuccess(Schema.Array(Item)),
@@ -30,6 +36,9 @@ const ItemsGroup = HttpApiGroup.make("items") · 2 unmodified lines
30 HttpApiEndpoint.get("getItem", "/items/:itemId")36 HttpApiEndpoint.get("getItem", "/items/:itemId")
31 .setPath(Schema.Struct({ itemId: Schema.NumberFromString }))37 .setPath(Schema.Struct({ itemId: Schema.NumberFromString }))
32 .addSuccess(Item),38 .addSuccess(Item),
39 )
40 .add(
41 HttpApiEndpoint.get("echoHeaders", "/echo-headers").addSuccess(EchoHeaders),
33 );42 );
34 43
35const TestApi = HttpApi.make("testApi").add(ItemsGroup);44const TestApi = HttpApi.make("testApi").add(ItemsGroup);
@@ -57,6 +66,15 @@ const ItemsGroupLive = HttpApiBuilder.group(TestApi, "items", (handlers) => · 21 unmodified lines
57 name: "Unknown",66 name: "Unknown",
58 },67 },
59 ),68 ),
69 )
70 .handle("echoHeaders", () =>
71 Effect.gen(function* () {
72 const req = yield* HttpServerRequest.HttpServerRequest;
73 return new EchoHeaders({
74 authorization: req.headers["authorization"],
75 "x-static": req.headers["x-static"],
76 });
77 }),
60 ),78 ),
61);79);
62 80
@@ -98,6 +116,83 @@ layer(TestLayer)("OpenAPI Plugin", (it) => { · 35 unmodified lines
98 }),116 }),
99 );117 );
100 118
119 it.effect("resolves secret-backed headers at invocation time", () =>
120 Effect.gen(function* () {
121 const httpClient = yield* HttpClient.HttpClient;
122 const clientLayer = Layer.succeed(HttpClient.HttpClient, httpClient);
123
124 const executor = yield* createExecutor(
125 makeTestConfig({
126 plugins: [
127 openApiPlugin({ httpClientLayer: clientLayer }),
128 ] as const,
129 }),
130 );
131
132 // Store a secret
133 yield* executor.secrets.set({
134 id: SecretId.make("test-api-token"),
135 name: "Test API Token",
136 value: "secret-value-123",
137 });
138
139 // Add spec with secret-backed header
140 yield* executor.openapi.addSpec({
141 spec: specJson,
142 namespace: "authed",
143 baseUrl: "",
144 headers: {
145 "Authorization": { secretId: "test-api-token", prefix: "Bearer " },
146 "X-Static": "hello",
147 },
148 });
149
150 // Invoke the echo endpoint — verifies secret was resolved and sent
151 const result = yield* executor.tools.invoke(
152 "authed.items.echoHeaders",
153 {},
154 );
155
156 expect(result.error).toBeNull();
157 const data = result.data as { authorization?: string; "x-static"?: string };
158 expect(data.authorization).toBe("Bearer secret-value-123");
159 expect(data["x-static"]).toBe("hello");
160 }),
161 );
162
163 it.effect("fails clearly when a secret is missing", () =>
164 Effect.gen(function* () {
165 const httpClient = yield* HttpClient.HttpClient;
166 const clientLayer = Layer.succeed(HttpClient.HttpClient, httpClient);
167
168 const executor = yield* createExecutor(
169 makeTestConfig({
170 plugins: [
171 openApiPlugin({ httpClientLayer: clientLayer }),
172 ] as const,
173 }),
174 );
175
176 // Add spec with secret-backed header but DON'T store the secret
177 yield* executor.openapi.addSpec({
178 spec: specJson,
179 namespace: "noauth",
180 baseUrl: "",
181 headers: {
182 "Authorization": { secretId: "missing-token", prefix: "Bearer " },
183 },
184 });
185
186 // Invoke — should fail with a clear error about the missing secret
187 const error = yield* Effect.flip(
188 executor.tools.invoke("noauth.items.listItems", {}),
189 );
190
191 expect(error._tag).toBe("ToolInvocationError");
192 expect((error as { message: string }).message).toContain("missing-token");
193 }),
194 );
195
101 it.effect("registers tools from an OpenAPI spec", () =>196 it.effect("registers tools from an OpenAPI spec", () =>
102 Effect.gen(function* () {197 Effect.gen(function* () {
103 const httpClient = yield* HttpClient.HttpClient;198 const httpClient = yield* HttpClient.HttpClient;

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t