Skip to content
Closed
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
23 changes: 23 additions & 0 deletions .oxlintrc.jsonc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"plugins": ["react"],
"jsPlugins": ["./scripts/oxlint-plugin-executor/index.js"],
"rules": {
"typescript/no-explicit-any": "error",
"react/forbid-elements": [
Expand All @@ -16,5 +17,27 @@
},
],
},
"overrides": [
{
"files": ["packages/plugins/workos-vault/src/**/*.{ts,tsx}"],
"rules": {
"eslint/no-nested-ternary": "error",
"executor/no-inline-object-type-assertion": "error",
"executor/no-instanceof-tagged-error": "error",
"executor/no-manual-tag-check": "error",
"executor/no-promise-client-surface": "error",
"executor/no-raw-error-throw": "error",
"executor/no-redundant-error-factory": "error",
"executor/no-unknown-shape-probing": "error",
},
},
{
"files": ["packages/plugins/workos-vault/src/**/*.{test,spec}.{ts,tsx}"],
"rules": {
"executor/no-if-in-tests": "error",
"executor/no-vitest-import": "error",
},
},
],
"ignorePatterns": [".astro/", "**/routeTree.gen.ts", ".references/"],
}
39 changes: 31 additions & 8 deletions packages/plugins/workos-vault/src/sdk/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type { WorkOS } from "@workos-inc/node/worker";
import { WorkOS as WorkOSClient } from "@workos-inc/node/worker";
import {
GenericServerException,
NotFoundException,
WorkOS as WorkOSClient,
} from "@workos-inc/node/worker";
import { Data, Effect } from "effect";

export interface WorkOSVaultObjectMetadata {
Expand All @@ -18,16 +22,40 @@ export interface WorkOSVaultObject {

export class WorkOSVaultClientError extends Data.TaggedError("WorkOSVaultClientError")<{
readonly cause: unknown;
readonly message: string;
readonly operation: string;
}> {}
readonly status?: number;
}> {
constructor(options: {
readonly cause: unknown;
readonly message?: string;
readonly operation: string;
readonly status?: number;
}) {
super({
cause: options.cause,
message: options.message ?? messageFromCause(options.cause),
operation: options.operation,
status: options.status ?? statusFromWorkOSCause(options.cause),
});
}
}

const statusFromWorkOSCause = (cause: unknown): number | undefined =>
cause instanceof GenericServerException || cause instanceof NotFoundException
? cause.status
: undefined;

const messageFromCause = (cause: unknown): string =>
cause instanceof Error ? cause.message : String(cause);

export class WorkOSVaultClientInstantiationError extends Data.TaggedError(
"WorkOSVaultClientInstantiationError",
)<{
readonly cause: unknown;
}> {}

export interface WorkOSVaultSdk {
interface WorkOSVaultSdk {
readonly createObject: (options: {
readonly name: string;
readonly value: string;
Expand All @@ -48,10 +76,6 @@ export interface WorkOSVaultCredentials {
}

export interface WorkOSVaultClient {
readonly use: <A>(
operation: string,
fn: (client: WorkOSVaultSdk) => Promise<A>,
) => Effect.Effect<A, WorkOSVaultClientError, never>;
readonly createObject: (options: {
readonly name: string;
readonly value: string;
Expand Down Expand Up @@ -85,7 +109,6 @@ export const makeWorkOSVaultClient = (
}).pipe(Effect.withSpan(`workos_vault.${operation}`));

return {
use,
createObject: (options) => use("create_object", (vault) => vault.createObject(options)),
readObjectByName: (name) => use("read_object_by_name", (vault) => vault.readObjectByName(name)),
updateObject: (options) => use("update_object", (vault) => vault.updateObject(options)),
Expand Down
1 change: 0 additions & 1 deletion packages/plugins/workos-vault/src/sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export {
type WorkOSVaultCredentials,
type WorkOSVaultObject,
type WorkOSVaultObjectMetadata,
type WorkOSVaultSdk,
} from "./client";
export {
workosVaultPlugin,
Expand Down
22 changes: 15 additions & 7 deletions packages/plugins/workos-vault/src/sdk/secret-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ import {
ScopeId,
SecretId,
SetSecretInput,
typedAdapter,
} from "@executor/sdk";

import { type WorkOSVaultClient } from "./client";
import { workosVaultPlugin } from "./plugin";
import {
makeWorkosVaultStore,
type WorkosVaultSchema,
} from "./secret-store";
import { makeTestWorkOSVaultClient } from "./testing";

const makeExecutor = (client: WorkOSVaultClient) =>
Expand Down Expand Up @@ -180,6 +185,9 @@ const makeLayeredExecutors = (client: WorkOSVaultClient) =>
const plugins = [workosVaultPlugin({ client })] as const;
const schema = collectSchemas(plugins);
const adapter = makeMemoryAdapter({ schema });
const metadataStore = makeWorkosVaultStore({
adapter: typedAdapter<WorkosVaultSchema>(adapter),
});
const blobs = makeInMemoryBlobStore();

const outerId = ScopeId.make("org");
Expand Down Expand Up @@ -207,7 +215,7 @@ const makeLayeredExecutors = (client: WorkOSVaultClient) =>
blobs,
plugins,
});
return { execOuter, execInner, outerId, innerId, adapter };
return { execOuter, execInner, outerId, innerId, metadataStore };
});

describe("WorkOS Vault secret provider — multi-scope isolation", () => {
Expand Down Expand Up @@ -274,7 +282,7 @@ describe("WorkOS Vault secret provider — multi-scope isolation", () => {
// just the SDK's defensive shielding.
Effect.gen(function* () {
const client = makeTestWorkOSVaultClient();
const { execOuter, execInner, outerId, innerId, adapter } =
const { execOuter, execInner, outerId, innerId, metadataStore } =
yield* makeLayeredExecutors(client);

yield* execOuter.secrets.set(
Expand All @@ -294,11 +302,11 @@ describe("WorkOS Vault secret provider — multi-scope isolation", () => {
}),
);

const rows = yield* adapter.findMany({
model: "workos_vault_metadata",
where: [{ field: "id", value: "api-token" }],
});
const scopes = rows.map((r) => (r as { scope_id: string }).scope_id).sort();
const rows = yield* metadataStore.list();
const scopes = rows
.filter((row) => row.id === "api-token")
.map((row) => row.scope_id)
.sort();
expect(scopes).toEqual([outerId, innerId].sort());
}),
);
Expand Down
59 changes: 15 additions & 44 deletions packages/plugins/workos-vault/src/sdk/secret-store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Effect } from "effect";
import { GenericServerException, NotFoundException } from "@workos-inc/node/worker";

import {
defineSchema,
Expand Down Expand Up @@ -72,11 +71,9 @@ export interface WorkosVaultStore {
readonly list: () => Effect.Effect<readonly MetadataRow[], StorageFailure>;
}

export const makeWorkosVaultStore = (
deps: StorageDeps<WorkosVaultSchema>,
): WorkosVaultStore => {
const { adapter: db } = deps;

export const makeWorkosVaultStore = ({
adapter: db,
}: Pick<StorageDeps<WorkosVaultSchema>, "adapter">): WorkosVaultStore => {
// Every read/write to a specific row pins BOTH `id` and `scope_id`.
// The store runs behind the SDK's scoped adapter (which auto-injects
// `scope_id IN (stack)`), so a bare `{id}` filter resolves to any
Expand Down Expand Up @@ -155,35 +152,11 @@ export const makeWorkosVaultStore = (
// Vault helpers — scope-prefixed object naming + 409-retry upsert.
// ---------------------------------------------------------------------------

const unwrapVaultError = (error: unknown): unknown =>
error instanceof WorkOSVaultClientError ? error.cause : error;

const isStatusError = (error: unknown, status: number): boolean => {
const cause = unwrapVaultError(error);
return (
((cause instanceof GenericServerException ||
cause instanceof NotFoundException) &&
cause.status === status) ||
(typeof cause === "object" &&
cause !== null &&
"status" in cause &&
typeof (cause as { status: unknown }).status === "number" &&
(cause as { status: number }).status === status)
);
};
const isStatusError = (error: WorkOSVaultClientError, status: number): boolean =>
error.status === status;

const isKekNotReadyError = (error: unknown): boolean => {
const cause = unwrapVaultError(error);
const message =
cause instanceof Error
? cause.message
: typeof cause === "string"
? cause
: typeof cause === "object" && cause !== null && "message" in cause
? String((cause as { message: unknown }).message)
: "";
return message.includes("KEK was created but is not yet ready");
};
const isKekNotReadyError = (error: WorkOSVaultClientError): boolean =>
error.message.includes("KEK was created but is not yet ready");

// Default context builder. Each semantic piece of a scope id lives in
// its own vault-context key so WorkOS's KEK matcher sees individual
Expand Down Expand Up @@ -246,11 +219,12 @@ const loadSecretObject = (
if (legacyName === encodedName) return Effect.succeed(null);

return client.readObjectByName(legacyName).pipe(
Effect.catchAll((legacyError) =>
isStatusError(legacyError, 404) || isStatusError(legacyError, 400)
? Effect.succeed(null)
: Effect.fail(legacyError),
),
Effect.catchAll((legacyError) => {
if (isStatusError(legacyError, 404) || isStatusError(legacyError, 400)) {
return Effect.succeed(null);
}
return Effect.fail(legacyError);
}),
);
}),
);
Expand Down Expand Up @@ -327,11 +301,8 @@ const deleteSecretValue = (
return true;
});

const formatVaultError = (error: unknown): StorageError => {
const cause = unwrapVaultError(error);
const message = cause instanceof Error ? cause.message : String(cause);
return new StorageError({ message, cause });
};
const formatVaultError = (error: WorkOSVaultClientError): StorageError =>
new StorageError({ message: error.message, cause: error.cause });

// ---------------------------------------------------------------------------
// makeWorkOSVaultSecretProvider — builds a SecretProvider backed by
Expand Down
23 changes: 9 additions & 14 deletions packages/plugins/workos-vault/src/sdk/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
type WorkOSVaultClient,
type WorkOSVaultObject,
type WorkOSVaultObjectMetadata,
type WorkOSVaultSdk,
} from "./client";

export class TestWorkOSVaultNotFoundError extends Data.TaggedError("TestWorkOSVaultNotFoundError")<{
Expand Down Expand Up @@ -170,23 +169,19 @@ export const makeTestWorkOSVaultClient = (
effect: Effect.Effect<A, TestWorkOSVaultError>,
): Effect.Effect<A, WorkOSVaultClientError> =>
effect.pipe(
Effect.mapError((cause) => new WorkOSVaultClientError({ cause, operation })),
Effect.mapError(
(cause) =>
new WorkOSVaultClientError({
cause,
message: cause.message,
operation,
status: cause.status,
}),
),
Effect.withSpan(`workos_vault.test.${operation}`),
);

const rawClient: WorkOSVaultSdk = {
createObject: (options) => Effect.runPromise(createObject(options)),
readObjectByName: (name) => Effect.runPromise(readObjectByName(name)),
updateObject: (options) => Effect.runPromise(updateObject(options)),
deleteObject: (options) => Effect.runPromise(deleteObject(options)),
};

return {
use: (operation, fn) =>
Effect.tryPromise({
try: () => fn(rawClient),
catch: (cause) => new WorkOSVaultClientError({ cause, operation }),
}).pipe(Effect.withSpan(`workos_vault.test.${operation}`)),
createObject: (options) => wrap("create_object", createObject(options)),
readObjectByName: (name) => wrap("read_object_by_name", readObjectByName(name)),
updateObject: (options) => wrap("update_object", updateObject(options)),
Expand Down
Loading
Loading