Skip to content
This repository was archived by the owner on Jul 1, 2024. It is now read-only.

Commit 3e8088f

Browse files
authored
porcelain: allow optional headers and low-level SDKOptions (#26)
Including a test for each of them. Signed-off-by: Stephan Renatus <stephan@styra.com>
1 parent d81d213 commit 3e8088f

File tree

2 files changed

+76
-3
lines changed

2 files changed

+76
-3
lines changed

src/porcelain/porcelain.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
ExecutePolicyWithInputResponse,
55
ExecutePolicyResponse,
66
} from "../models/operations";
7+
import { SDKOptions } from "../lib/config";
8+
import { HTTPClient } from "../lib/http";
79

810
export type { Input, Result };
911

@@ -20,15 +22,38 @@ function implementsToInput(object: any): object is ToInput {
2022
return u.toInput !== undefined && typeof u.toInput == "function";
2123
}
2224

25+
/** Extra options for using the high-level SDK.
26+
*/
27+
export type Options = {
28+
headers?: Record<string, string>;
29+
sdk?: SDKOptions;
30+
};
31+
2332
/** OPAClient is the starting point for using the high-level API.
2433
*
2534
* Use {@link Opa} if you need some low-level customization.
2635
*/
2736
export class OPAClient {
2837
private opa: Opa;
2938

30-
constructor(serverURL: string) {
31-
this.opa = new Opa({ serverURL });
39+
/** Create a new `OPA` instance.
40+
* @param serverURL - The OPA URL, e.g. `https://opa.internal.corp:8443/`.
41+
* @param opts - Extra options, ncluding low-level `SDKOptions`.
42+
*/
43+
constructor(serverURL: string, opts?: Options) {
44+
const sdk = { serverURL, ...opts?.sdk };
45+
if (opts?.headers) {
46+
const hdrs = opts.headers;
47+
const client = opts?.sdk?.httpClient ?? new HTTPClient();
48+
client.addHook("beforeRequest", (req) => {
49+
for (const k in hdrs) {
50+
req.headers.set(k, hdrs[k] as string);
51+
}
52+
return req;
53+
});
54+
sdk.httpClient = client;
55+
}
56+
this.opa = new Opa(sdk);
3257
}
3358

3459
/** `authorize` is used to evaluate the policy at the specified.

tests/authorizer.test.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, before, after, it } from "node:test";
22
import assert from "node:assert";
33
import { GenericContainer, StartedTestContainer, Wait } from "testcontainers";
44
import { OPAClient, ToInput, Input, Result } from "../src/porcelain";
5+
import { HTTPClient } from "../src/lib/http";
56

67
// Run these locally, with debug output from testcontainers, like this:
78
// DEBUG='testcontainers*' node --require ts-node/register --test tests/**/*.ts
@@ -24,9 +25,25 @@ compound_result.allowed := true
2425
slash: `package has["weird/package"].but
2526
import rego.v1
2627
27-
it_is := true
28+
it_is := true`,
29+
token: `package token
30+
import rego.v1
31+
p := true
2832
`,
2933
};
34+
const authzPolicy = `package system.authz
35+
import rego.v1
36+
37+
default allow := false
38+
allow if input.method == "PUT"
39+
allow if input.path[0] == "health"
40+
allow if input.path[2] == "test"
41+
allow if input.path[2] == "has"
42+
allow if {
43+
input.path[2] = "token"
44+
input.identity = "opensesame"
45+
}
46+
`;
3047

3148
let container: StartedTestContainer;
3249
let serverURL: string;
@@ -37,9 +54,18 @@ it_is := true
3754
"--server",
3855
"--disable-telemetry",
3956
"--log-level=debug",
57+
"--authentication=token",
58+
"--authorization=basic",
59+
"/authz.rego",
4060
])
4161
.withExposedPorts(8181)
4262
.withWaitStrategy(Wait.forHttp("/health", 8181).forStatusCode(200))
63+
.withCopyContentToContainer([
64+
{
65+
content: authzPolicy,
66+
target: "/authz.rego",
67+
},
68+
])
4369
.start();
4470
serverURL = `http://${container.getHost()}:${container.getMappedPort(8181)}`;
4571

@@ -160,5 +186,27 @@ it_is := true
160186
assert.deepStrictEqual(res, true);
161187
});
162188

189+
it("allows custom low-level SDKOptions' HTTPClient", async () => {
190+
const httpClient = new HTTPClient({});
191+
let called = false;
192+
httpClient.addHook("beforeRequest", (req) => {
193+
called = true;
194+
return req;
195+
});
196+
const res = await new OPAClient(serverURL, {
197+
sdk: { httpClient },
198+
}).authorize("test/p_bool");
199+
assert.strictEqual(res, true);
200+
assert.strictEqual(called, true);
201+
});
202+
203+
it("allows custom headers", async () => {
204+
const authorization = "Bearer opensesame";
205+
const res = await new OPAClient(serverURL, {
206+
headers: { authorization },
207+
}).authorize("token/p");
208+
assert.strictEqual(res, true);
209+
});
210+
163211
after(async () => await container.stop());
164212
});

0 commit comments

Comments
 (0)