Skip to content

Bump @cipherstash/auth to 0.41 and migrate to Result API#568

Open
coderdan wants to merge 10 commits into
mainfrom
chore/bump-auth-0.41
Open

Bump @cipherstash/auth to 0.41 and migrate to Result API#568
coderdan wants to merge 10 commits into
mainfrom
chore/bump-auth-0.41

Conversation

@coderdan

@coderdan coderdan commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Closes #567.

What

Bumps @cipherstash/auth (and its 6 per-platform native bindings) from 0.40.0 → 0.41.0 and migrates @cipherstash/stack, the stash CLI, and @cipherstash/wizard to its new API.

Why it's not just a version bump

@cipherstash/auth 0.41 is a breaking release: every fallible auth operation now returns a @byteslice/result Result<T, AuthFailure> ({ data } on success, { failure } on error) instead of throwing, and the AuthError type is renamed to AuthFailure — a discriminated union keyed by .type ("NOT_AUTHENTICATED", "WORKSPACE_MISMATCH", …) that replaces the old error.code string. Consumers branch on if (result.failure) / failure.type rather than try/catch.

Changes

@cipherstash/stack (breaking type surface — minor)

  • Public re-export AuthErrorAuthFailure (AuthErrorCode / TokenResult unchanged)
  • WASM-inline resolveStrategy unwraps the Result from AccessKeyStrategy.create; a construction failure throws a descriptive [encryption] error naming the AuthFailure.type

stash (CLI) (patch)

  • auth login device-code flow + bindClientDevice, and the init existing-auth check, unwrap Result

@cipherstash/wizard (patch)

  • Gateway token fetch, agent access-token helper, and the credential prerequisite check (error.codefailure.type)

Catalog + tests

  • Bump auth + 6 bindings 0.40.00.41.0 in pnpm-workspace.yaml and root package.json; lockfile updated
  • Auth mocks/stubs updated to the Result shape
  • Changeset added

Verification

  • turbo build green for @cipherstash/stack, stash, @cipherstash/wizard
  • stash 334/334, @cipherstash/wizard 140/140, stack offline auth tests 40/40
  • Remaining stack integration-test failures require a live ZeroKMS workspace profile (~/.cipherstash/...) and are unrelated to this change

Note: the AuthErrorAuthFailure rename is a breaking change to @cipherstash/stack's public types, graded minor per the repo's 0.x convention.

Summary by CodeRabbit

  • New Features

    • Added support for the updated authentication configuration name, preferring the new field while keeping the legacy name as a deprecated alias.
    • Expanded authentication guidance covering token handling and multi-tenant key isolation.
  • Bug Fixes

    • Improved login, setup, and wizard authentication flows to reliably handle non-throwing authentication failures and present clearer failure messages.
  • Chores

    • Updated package versions and WASM-inline auth/protection interfaces to stay aligned, plus refreshed tests/mocks accordingly.

coderdan added 6 commits July 6, 2026 15:17
…keysets

Rename the encryption client's auth strategy field from `strategy` to
`authStrategy`. The old `strategy` field is retained as a deprecated alias:
passing it still works and forwards to the client, but logs a one-time runtime
deprecation warning. `authStrategy` wins when both are set.

Also rewrites the `Encryption()` TypeDoc to walk through authentication:
- the default `auto` strategy (env vars, then local dev profile which also
  supplies the client key)
- `npx stash auth login` for local development
- the four `CS_*` env vars for production/CI (table + dashboard link)
- custom strategies via `authStrategy` (AccessKeyStrategy, OidcFederationStrategy)
- lock context as an identity-bound capability on top of OidcFederationStrategy
- keysets for multi-tenant isolation (one client per tenant)

Tests cover the new `authStrategy` field, the deprecated `strategy` alias
(forwarding + warning), and precedence when both are supplied.
- Warn whenever the deprecated `config.strategy` is present, even when
  `config.authStrategy` is also set (matches the ClientConfig TSDoc, which
  states passing `strategy` always warns). `authStrategy` still wins.
- Harden the deprecation test: reset the once-per-process warning latch and
  spy console.warn in beforeEach, restore in afterEach (no leak on assertion
  throw), drop the order-dependent guard comment, and add a negative test that
  authStrategy alone does not warn.
- Add an @internal reset hook (not re-exported from the package entry) so the
  test controls the warning latch deterministically.
- TypeDoc: note that the auth/keyset snippets reuse the `users` schema and
  placeholder workspaceCrn/accessKey credentials.
- Add changeset (minor).
Keep the Node and WASM interfaces in sync: the `@cipherstash/stack/wasm-inline`
entry now uses `config.authStrategy` as the documented field, with
`config.strategy` retained as a deprecated alias.

- `WasmClientConfig` gains an `authStrategy` arm; the deprecated `strategy` is
  kept as an alias (mutually exclusive with `accessKey`, as before).
- `resolveStrategy` resolves `authStrategy ?? strategy`, guards the auth-strategy
  vs `accessKey` mutual exclusion against both fields, and emits a one-time
  runtime deprecation warning when `strategy` is used (mirroring the Node entry,
  with its own `@internal` reset hook for deterministic tests).
- Update wasm-inline docs/examples and the offline resolveStrategy /
  new-client tests to cover authStrategy, the deprecated alias, and the warning.

Closes #564.
When the deprecated `config.strategy` is set alongside `accessKey`, the
mutual-exclusion error now says `config.strategy` (not the resolved
`config.authStrategy`), so it names the field the caller actually set. Tests
assert the precise message for both the `authStrategy` and deprecated
`strategy` cases.
…ocess

Cover the once-per-process latch on both the Node and WASM entries: two
successive Encryption()/resolveStrategy() calls in one test (no beforeEach
reset between them) must warn exactly once. A regression dropping the
`if (warnedStrategyDeprecated) return` guard would warn twice and fail these.
`@cipherstash/auth` 0.41 switches every fallible auth operation to return
a `@byteslice/result` `Result<T, AuthFailure>` (`{ data }` / `{ failure }`)
instead of throwing, and renames the `AuthError` type to `AuthFailure`
(a `.type`-keyed discriminated union replacing `error.code`).

- catalog: bump `@cipherstash/auth` + 6 platform bindings 0.40.0 -> 0.41.0
  in pnpm-workspace.yaml and root package.json; update lockfile
- stack: re-export `AuthFailure` (was `AuthError`); unwrap the Result from
  `AccessKeyStrategy.create` in the wasm-inline `resolveStrategy`
- cli: unwrap Result in `stash auth login` device-code flow, `bindClientDevice`,
  and the `init` existing-auth check
- wizard: unwrap Result in gateway token fetch, agent token helper, and the
  credential prerequisite check (`error.code` -> `failure.type`)
- tests: update auth mocks/stubs to the Result shape
- changeset: stack minor (breaking type surface), stash + wizard patch

Refs #567
@coderdan coderdan requested a review from a team as a code owner July 7, 2026 02:52
@changeset-bot

changeset-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6fdb103

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 8 packages
Name Type
@cipherstash/stack Minor
@cipherstash/wizard Patch
stash Patch
@cipherstash/bench Patch
@cipherstash/prisma-next Patch
@cipherstash/basic-example Patch
@cipherstash/prisma-next-example Patch
@cipherstash/e2e Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@coderdan, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 3 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 61910947-cd25-40fa-801e-1cd57e118cbe

📥 Commits

Reviewing files that changed from the base of the PR and between 6eef4f6 and 6fdb103.

📒 Files selected for processing (1)
  • e2e/wasm/deno.json
📝 Walkthrough

Walkthrough

Renames Encryption() auth configuration from strategy to authStrategy with a deprecated alias, and migrates CLI, wizard, and stack auth call sites to @cipherstash/auth 0.41 Result values while updating related docs, types, tests, and package versions.

Changes

strategy → authStrategy rename

Layer / File(s) Summary
Changeset docs
.changeset/rename-strategy-to-auth-strategy.md
Documents the field rename, deprecated alias with warning, and precedence rules.
Encryption client resolution
packages/stack/src/encryption/index.ts
init config gains authStrategy, deprecation warning latch + test reset hook added, Encryption() resolves effective strategy with precedence and forwards it to client.init/newClient.
wasm-inline config and resolveStrategy
packages/stack/src/wasm-inline.ts
WasmClientConfig reworked around authStrategy with strategy deprecated; resolveStrategy() prefers authStrategy, guards mutual exclusion with accessKey, and unwraps AccessKeyStrategy.create's Result.
Docs and re-exports
packages/stack/src/identity/index.ts, packages/stack/src/index.ts, packages/stack/src/types.ts
Updates JSDoc/TSDoc examples to authStrategy, expands keyset documentation, and renames the AuthError re-export to AuthFailure.
Tests
packages/stack/__tests__/init-strategy.test.ts, .../lock-context.test.ts, .../wasm-inline-new-client.test.ts, .../wasm-inline-strategy.test.ts, .../helpers/stub-auth-wasm-inline.ts
Validates authStrategy forwarding, deprecated alias warnings, precedence, and mutual-exclusion guard errors.

@cipherstash/auth 0.41 Result API migration

Layer / File(s) Summary
Version bump
package.json, pnpm-workspace.yaml, .changeset/stack-auth-0-41-result-api.md
Bumps @cipherstash/auth to 0.41.0 across catalogs and documents the Result-based migration.
CLI Result handling
packages/cli/src/commands/auth/login.ts, packages/cli/src/commands/init/steps/authenticate.ts
Login device-code flow, bindDevice(), and checkExistingAuth() now check .failure/.data instead of relying on thrown errors.
Wizard Result handling
packages/wizard/src/agent/fetch-prompt.ts, packages/wizard/src/agent/interface.ts, packages/wizard/src/lib/prerequisites.ts, packages/wizard/src/__tests__/prerequisites.test.ts
Token acquisition and credential checks branch on failure.type/data instead of try/catch; test mocks updated to the Result shape.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: calvinbrewer, tobyhede

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately states the main change: bumping @cipherstash/auth to 0.41 and migrating to the Result API.
Linked Issues check ✅ Passed The PR covers the requested auth 0.41 migration across stack, CLI, wizard, tests, and version pins, including AuthError→AuthFailure.
Out of Scope Changes check ✅ Passed The added docs, tests, changeset, and e2e import-map update are supporting work for the auth 0.41 migration, not unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/bump-auth-0.41

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The Deno WASM e2e import map pinned `@cipherstash/auth@0.40.0`, but stack's
built `dist/wasm-inline.js` now unwraps the `Result` returned by auth 0.41's
`AccessKeyStrategy.create`. Under the old pin, `create` returned the strategy
directly, so `result.data` was `undefined` and protect-ffi's `newClient`
rejected with "opts.strategy is required". Point the e2e at the versions stack
actually ships (auth 0.41.0, protect-ffi 0.27.0).

Refs #567

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/commands/init/steps/authenticate.ts (1)

17-36: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Failure-type handling is less granular than the sibling implementation.

checkExistingAuth() treats every detected.failure / result.failure as "not authenticated" and silently falls through to undefined, whereas hasCredentials() in packages/wizard/src/lib/prerequisites.ts (same migration cohort) explicitly checks failure.type for NOT_AUTHENTICATED/MISSING_WORKSPACE_CRN and rethrows anything else. Here, a failure like WORKSPACE_MISMATCH gets silently swallowed and init re-runs the full device-code login flow instead of surfacing the real problem to the user.

This preserves prior try/catch-everything behavior so it isn't a regression, but it's an inconsistency worth aligning now that the Result API exposes failure.type for this purpose.

🔧 Suggested alignment with prerequisites.ts
+  const notAuthenticated = (failure: { type: string }): boolean =>
+    failure.type === 'NOT_AUTHENTICATED' ||
+    failure.type === 'MISSING_WORKSPACE_CRN'
+
   const detected = AutoStrategy.detect()
-  if (detected.failure) return undefined
+  if (detected.failure) {
+    if (notAuthenticated(detected.failure)) return undefined
+    throw detected.failure.error
+  }

   const result = await detected.data.getToken()
-  if (result.failure) return undefined
+  if (result.failure) {
+    if (notAuthenticated(result.failure)) return undefined
+    throw result.failure.error
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/init/steps/authenticate.ts` around lines 17 - 36,
checkExistingAuth() is swallowing all Result failures and treating them as “not
authenticated,” unlike the sibling hasCredentials() flow that distinguishes
specific failure types. Update checkExistingAuth() to inspect
detected.failure.type and result.failure.type, returning undefined only for
NOT_AUTHENTICATED/MISSING_WORKSPACE_CRN and rethrowing or surfacing other
failure types like WORKSPACE_MISMATCH. Keep the behavior aligned with
packages/wizard/src/lib/prerequisites.ts and preserve the existing
AutoStrategy.detect()/getToken() handling in authenticate.ts.
🧹 Nitpick comments (3)
packages/cli/src/commands/auth/login.ts (1)

39-81: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the repeated failure-handling into a helper.

The if (x.failure) { log; process.exit(1) } pattern is repeated three times (Lines 40-43, 57-61, 76-80) with only the message/spinner text varying. A small exitOnFailure(result, onFailure?) helper would remove duplication while keeping the same error-message/exit-code behavior.

♻️ Example helper
+function exitOnAuthFailure<T>(
+  result: { failure?: { error: Error } } | { data: T },
+  onFailure?: () => void,
+): asserts result is { data: T } {
+  if ('failure' in result && result.failure) {
+    onFailure?.()
+    p.log.error(result.failure.error.message)
+    process.exit(1)
+  }
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/commands/auth/login.ts` around lines 39 - 81, The
failure-handling logic in the login and bind flows is duplicated across the
`beginDeviceCodeFlow`, `flow.pollForToken`, and `bindClientDevice` result
checks. Extract the repeated `if (result.failure) { ... process.exit(1) }`
pattern into a small helper such as `exitOnFailure` that accepts a `Result` and
optional spinner/log messages, then reuse it in `login` and `bindDevice` so the
error logging and exit behavior stay identical while removing repetition.
packages/wizard/src/__tests__/prerequisites.test.ts (1)

11-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering the rethrow and detect()-failure branches.

The mock only drives the getToken()NOT_AUTHENTICATED path. The new hasCredentials logic also has a detect().failure branch and a rethrow branch for unexpected failure types (Lines 50-58 in prerequisites.ts), including the MISSING_WORKSPACE_CRN case. Adding cases for a detect() failure and a non-auth failure that rethrows would guard the discriminant logic against regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/wizard/src/__tests__/prerequisites.test.ts` around lines 11 - 23,
The current prerequisites tests only cover the getToken() NOT_AUTHENTICATED
path, so add test cases in prerequisites.test.ts for the hasCredentials()
branches where AutoStrategy.detect() returns a failure and where getToken()
returns a non-auth failure that should be rethrown. Use the existing
hasCredentials and prerequisite logic in prerequisites.ts as the target, and
explicitly cover the MISSING_WORKSPACE_CRN case plus one unexpected failure type
to verify the discriminant/rethrow behavior stays correct.
packages/stack/__tests__/wasm-inline-strategy.test.ts (1)

139-169: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a test for the AccessKeyStrategy.create failure path.

The mock at Line 22 only returns { data: ... }. There's no test asserting resolveStrategy throws the descriptive error built at wasm-inline.ts Lines 472-476 when AccessKeyStrategy.create returns { failure }. Given this is new, non-trivial error-surfacing logic introduced by the 0.41 migration, a dedicated failure-path test would guard against regressions.

✅ Suggested additional test
it('surfaces a descriptive error when AccessKeyStrategy.create fails', () => {
  vi.mocked(AccessKeyStrategy.create).mockReturnValueOnce({
    failure: { type: 'InvalidCrn', error: { message: 'bad crn' } },
  } as any)

  expect(() =>
    resolveStrategy({ workspaceCrn: CRN, accessKey: 'CSAK.test' } as any),
  ).toThrowError(/failed to construct `AccessKeyStrategy`/)
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/stack/__tests__/wasm-inline-strategy.test.ts` around lines 139 -
169, Add a dedicated test in wasm-inline-strategy.test.ts for the
AccessKeyStrategy.create failure path: mock AccessKeyStrategy.create to return a
failure object, call resolveStrategy with workspaceCrn and accessKey, and assert
it throws the descriptive error message constructed in wasm-inline.ts. Use the
existing resolveStrategy and AccessKeyStrategy.create symbols so the test covers
the new error-surfacing behavior introduced by the migration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/stack/src/wasm-inline.ts`:
- Around line 435-478: The failure handling in resolveStrategy is reading the
auth construction error from the wrong shape, which can itself throw while
formatting the message. Update the AccessKeyStrategy.create failure branch in
resolveStrategy to use the message exposed directly on result.failure, and keep
the thrown error informative without dereferencing a nested error object.
Preserve the existing runtime checks around config.authStrategy,
config.strategy, and config.accessKey.

---

Outside diff comments:
In `@packages/cli/src/commands/init/steps/authenticate.ts`:
- Around line 17-36: checkExistingAuth() is swallowing all Result failures and
treating them as “not authenticated,” unlike the sibling hasCredentials() flow
that distinguishes specific failure types. Update checkExistingAuth() to inspect
detected.failure.type and result.failure.type, returning undefined only for
NOT_AUTHENTICATED/MISSING_WORKSPACE_CRN and rethrowing or surfacing other
failure types like WORKSPACE_MISMATCH. Keep the behavior aligned with
packages/wizard/src/lib/prerequisites.ts and preserve the existing
AutoStrategy.detect()/getToken() handling in authenticate.ts.

---

Nitpick comments:
In `@packages/cli/src/commands/auth/login.ts`:
- Around line 39-81: The failure-handling logic in the login and bind flows is
duplicated across the `beginDeviceCodeFlow`, `flow.pollForToken`, and
`bindClientDevice` result checks. Extract the repeated `if (result.failure) {
... process.exit(1) }` pattern into a small helper such as `exitOnFailure` that
accepts a `Result` and optional spinner/log messages, then reuse it in `login`
and `bindDevice` so the error logging and exit behavior stay identical while
removing repetition.

In `@packages/stack/__tests__/wasm-inline-strategy.test.ts`:
- Around line 139-169: Add a dedicated test in wasm-inline-strategy.test.ts for
the AccessKeyStrategy.create failure path: mock AccessKeyStrategy.create to
return a failure object, call resolveStrategy with workspaceCrn and accessKey,
and assert it throws the descriptive error message constructed in
wasm-inline.ts. Use the existing resolveStrategy and AccessKeyStrategy.create
symbols so the test covers the new error-surfacing behavior introduced by the
migration.

In `@packages/wizard/src/__tests__/prerequisites.test.ts`:
- Around line 11-23: The current prerequisites tests only cover the getToken()
NOT_AUTHENTICATED path, so add test cases in prerequisites.test.ts for the
hasCredentials() branches where AutoStrategy.detect() returns a failure and
where getToken() returns a non-auth failure that should be rethrown. Use the
existing hasCredentials and prerequisite logic in prerequisites.ts as the
target, and explicitly cover the MISSING_WORKSPACE_CRN case plus one unexpected
failure type to verify the discriminant/rethrow behavior stays correct.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 213c9ca1-2d4e-41cc-91ac-42c58d17a1d4

📥 Commits

Reviewing files that changed from the base of the PR and between 7f67e11 and 4f10d6d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (20)
  • .changeset/rename-strategy-to-auth-strategy.md
  • .changeset/stack-auth-0-41-result-api.md
  • package.json
  • packages/cli/src/commands/auth/login.ts
  • packages/cli/src/commands/init/steps/authenticate.ts
  • packages/stack/__tests__/helpers/stub-auth-wasm-inline.ts
  • packages/stack/__tests__/init-strategy.test.ts
  • packages/stack/__tests__/lock-context.test.ts
  • packages/stack/__tests__/wasm-inline-new-client.test.ts
  • packages/stack/__tests__/wasm-inline-strategy.test.ts
  • packages/stack/src/encryption/index.ts
  • packages/stack/src/identity/index.ts
  • packages/stack/src/index.ts
  • packages/stack/src/types.ts
  • packages/stack/src/wasm-inline.ts
  • packages/wizard/src/__tests__/prerequisites.test.ts
  • packages/wizard/src/agent/fetch-prompt.ts
  • packages/wizard/src/agent/interface.ts
  • packages/wizard/src/lib/prerequisites.ts
  • pnpm-workspace.yaml

Comment on lines 435 to 478
export function resolveStrategy(cfg: WasmClientConfig): WasmAuthStrategy {
// The discriminated union rejects `accessKey` + `strategy` together at
// Honour the deprecated `strategy` alias; `authStrategy` wins when both are
// set. Warn whenever the deprecated field is present at all so the leftover
// field gets cleaned up (mirrors the Node entry).
if (cfg.strategy) {
warnStrategyDeprecated()
}
const authStrategy = cfg.authStrategy ?? cfg.strategy
// The discriminated union rejects an auth strategy + `accessKey` together at
// compile time, but JS callers (Deno / plain JS) bypass that — guard at
// runtime so a conflicting config fails loudly instead of silently
// preferring one.
if (cfg.strategy && cfg.accessKey) {
if (authStrategy && cfg.accessKey) {
// Name the field the caller actually set — `strategy` when only the
// deprecated alias was used — so the message isn't misleading.
const field = cfg.authStrategy ? 'authStrategy' : 'strategy'
throw new Error(
'[encryption]: `config.strategy` and `config.accessKey` are mutually exclusive — pass exactly one.',
`[encryption]: \`config.${field}\` and \`config.accessKey\` are mutually exclusive — pass exactly one.`,
)
}
if (cfg.strategy) return cfg.strategy
// No strategy → the access-key arm, where `workspaceCrn` and `accessKey`
if (authStrategy) return authStrategy
// No auth strategy → the access-key arm, where `workspaceCrn` and `accessKey`
// are both required (and so present at runtime); the union widens their
// static types to `string | undefined`, hence the casts. Guard at runtime
// so plain JS / Deno callers that bypass the compile-time union fail loudly
// instead of forwarding `undefined` into `AccessKeyStrategy.create`.
if (!cfg.workspaceCrn || !cfg.accessKey) {
throw new Error(
'[encryption]: `config.workspaceCrn` and `config.accessKey` are required when `config.strategy` is not provided.',
'[encryption]: `config.workspaceCrn` and `config.accessKey` are required when no auth strategy is provided.',
)
}
// `AccessKeyStrategy.create` takes the full workspace CRN — the region is
// derived from it inside `@cipherstash/auth`, so the CRN stays the single
// source of truth with no manual region split.
return AccessKeyStrategy.create(cfg.workspaceCrn, cfg.accessKey)
// source of truth with no manual region split. As of `@cipherstash/auth`
// `0.41` `create` returns a `Result<AccessKeyStrategy, AuthFailure>` rather
// than throwing — unwrap it and surface a construction failure loudly.
const result = AccessKeyStrategy.create(cfg.workspaceCrn, cfg.accessKey)
if (result.failure) {
throw new Error(
`[encryption]: failed to construct \`AccessKeyStrategy\` from \`config.workspaceCrn\` / \`config.accessKey\` (${result.failure.type}): ${result.failure.error.message}`,
)
}
return result.data
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for local AuthFailure type references (may confirm .error.message shape)
rg -n 'AuthFailure' --type=ts -C3

Repository: cipherstash/stack

Length of output: 155


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate package manifests and any lockfile references to `@cipherstash/auth`
git ls-files '**/package.json' '**/pnpm-lock.yaml' '**/package-lock.json' '**/yarn.lock' | sed -n '1,200p'
printf '\n--- package refs ---\n'
rg -n '"`@cipherstash/auth`"|`@cipherstash/auth`' --glob 'package.json' --glob 'pnpm-lock.yaml' --glob 'package-lock.json' --glob 'yarn.lock' -C 2

Repository: cipherstash/stack

Length of output: 621


🌐 Web query:

@cipherstash/auth 0.41 AuthFailure error message type

💡 Result:

The @cipherstash/auth package does not define an error type named AuthFailure [1][2]. In the CipherStash ecosystem, error handling typically follows one of two patterns depending on the context: 1. JavaScript/TypeScript Package (@cipherstash/auth): Errors thrown by this package are instances of Error that include a machine-readable.code property (e.g., INVALID_ACCESS_KEY, ACCESS_DENIED, EXPIRED_TOKEN, INVALID_TOKEN, SERVER_ERROR, REQUEST_ERROR) [1][2]. 2. Rust Client (cipherstash_client): Authentication-related errors are represented by the AuthError enum, which includes variants such as NotAuthenticated, TokenExpired, AccessDenied, and InvalidAccessKey [3]. If you are encountering an error message referring to "AuthFailure," it may be a custom error generated by your application code, a generic error message returned by an upstream service, or a misinterpretation of a different error code [1][4]. It is recommended to inspect the.code property of the thrown error object in your code to determine the specific machine-readable cause [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the dependency version and any local type declarations for cipherstash/auth
rg -n '"`@cipherstash/auth`"|cipherstash/auth' --glob 'package.json' --glob 'pnpm-lock.yaml' --glob 'package-lock.json' --glob 'yarn.lock' -C 2
printf '\n--- type files ---\n'
fd -a 'auth' .

Repository: cipherstash/stack

Length of output: 155


🌐 Web query:

@cipherstash/auth AuthFailure type error message 0.41

💡 Result:

In the context of @cipherstash/auth, there is no single error message explicitly named "AuthFailure" [1][2]. Instead, the package uses a structured error-handling pattern where errors thrown extend the standard Error class and include a machine-readable code property [1][2]. Common error codes returned by @cipherstash/auth include: - INVALID_ACCESS_KEY [1] - ACCESS_DENIED [1] - EXPIRED_TOKEN [1] - INVALID_TOKEN [1] - INVALID_REGION [1] - SERVER_ERROR [1] - REQUEST_ERROR [1] If you are encountering an authentication-related error, it is recommended to catch the error and inspect the.code and.message properties to determine the specific cause [1][2]. Example pattern for error handling: try { await strategy.getToken; } catch (err) { console.error(err.code); // e.g., "EXPIRED_TOKEN" console.error(err.message); // Human-readable description } For broader @cipherstash/stack operations, errors are often categorized into a union type (e.g., ClientInitError, CtsTokenError, LockContextError) [3]. If you are working with a specific version like 0.41, ensure your implementation aligns with the expected error structure outlined in the official documentation [1][3]. If you continue to see an unexpected "AuthFailure" message, it may be a custom error generated by your application code or a specific wrapper around the library, rather than a native library error.

Citations:


🌐 Web query:

site:github.com "@cipherstash/auth" "AccessKeyStrategy.create" Result AuthFailure

💡 Result:

In the context of @cipherstash/auth (now re-exported via @cipherstash/stack) [1], an AuthFailure resulting from AccessKeyStrategy.create indicates that the authentication attempt using the provided access key has failed [1][2]. CipherStash operations, including those involving encryption and decryption, typically return a Result object rather than throwing errors [1][3][4]. When an operation such as AccessKeyStrategy.create fails, the returned Result object will contain a failure property [1][4]. To address an AuthFailure in this context, consider the following troubleshooting steps: 1. Verify Credentials: Ensure that your CS_CLIENT_ACCESS_KEY and CS_CLIENT_KEY environment variables are correctly set and valid for your workspace [2]. 2. Check Workspace Configuration: Confirm that your CS_WORKSPACE_CRN is correct and that the credentials provided are authorized for that specific workspace [2]. 3. Dependency Management: Ensure you are using the latest version of @cipherstash/stack. Recent updates have re-exported authentication strategies from @cipherstash/stack directly to simplify dependency management and resolve native binding issues [1]. 4. Examine Failure Details: If the Result object provides additional error metadata within the failure property, inspect it to determine if the failure is due to invalid credentials, network issues, or insufficient permissions [1][3]. Note: Ensure you are not confusing this with API key errors from other libraries like better-auth, which also feature similar naming patterns (e.g., authClient.apiKey.create) but serve different purposes [5][6][7].

Citations:


🌐 Web query:

@cipherstash/auth AuthFailure failure.error.message type Result create

💡 Result:

In the @cipherstash/auth library (specifically the Node.js/JavaScript SDK), error handling is typically implemented through thrown exceptions rather than a returned Result object, although other parts of the CipherStash stack use a Result pattern [1][2][3][4]. When using @cipherstash/auth, errors are thrown as objects that extend the standard Error class [3][4]. These objects include a machine-readable.code property and a human-readable.message property [3][4]. Example pattern for catching these errors: try { await strategy.getToken; } catch (err) { console.error(err.code); // e.g., "EXPIRED_TOKEN" console.error(err.message); // Human-readable description } Commonly documented error codes in @cipherstash/auth include: - INVALID_ACCESS_KEY - ACCESS_DENIED - EXPIRED_TOKEN - INVALID_REGION - INVALID_TOKEN - SERVER_ERROR - REQUEST_ERROR In contrast, other parts of the CipherStash ecosystem (such as the encryption stack) return a Result object which contains a failure property [1][2]. In those cases, the failure structure follows this format: if (result.failure) { // result.failure.type: string (e.g., "EncryptionError") // result.failure.message: string (human-readable description) console.error(result.failure.type, result.failure.message); } If you are encountering an AuthFailure type in your specific context, it is likely you are interacting with a component that returns a Result object rather than the standard @cipherstash/auth package which throws errors [1][2]. Ensure you are checking the result object's failure property as shown above [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- packages/stack/package.json ---'
cat -n packages/stack/package.json

printf '\n%s\n' '--- wasm-inline.ts imports/context ---'
sed -n '1,120p' packages/stack/src/wasm-inline.ts

printf '\n%s\n' '--- nearby AccessKeyStrategy.create usage ---'
rg -n 'AccessKeyStrategy\.create|failure\.error\.message|AuthFailure|Result<' packages/stack/src -C 3

Repository: cipherstash/stack

Length of output: 40918


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- packages/stack/src/errors/index.ts ---'
sed -n '1,220p' packages/stack/src/errors/index.ts

printf '\n%s\n' '--- packages/stack/src/types.ts: Result-related definitions ---'
rg -n 'type Result|interface Result|failure:|error:' packages/stack/src/types.ts packages/stack/src/errors/index.ts -C 3

Repository: cipherstash/stack

Length of output: 5095


🌐 Web query:

"@cipherstash/auth" "failure.error.message" AuthFailure

💡 Result:

The term AuthFailure is not a standard error constant or class directly exported by the @cipherstash/auth package [1][2]. Instead, the @cipherstash/auth package handles errors by throwing objects that extend the standard Error class, which contain a machine-readable.code property and a human-readable.message property [1][2]. Common error codes generated by the library include INVALID_ACCESS_KEY, ACCESS_DENIED, EXPIRED_TOKEN, INVALID_REGION, INVALID_TOKEN, SERVER_ERROR, and REQUEST_ERROR [1]. In related Rust components (stack-auth/cipherstash-client), authentication errors are typically represented by the AuthError enum, which includes variants such as NotAuthenticated, TokenExpired, and AccessDenied, rather than a single AuthFailure variant [3][4]. If you are encountering a failure message or object, it is recommended to inspect the error's code and message properties directly [1][2][5]: try { await strategy.getToken; } catch (err) { console.error(err.code); // Machine-readable code (e.g., "EXPIRED_TOKEN") console.error(err.message); // Human-readable description } For complex operations, CipherStash libraries often return a result object containing a failure field, which itself includes a type (e.g., "EncryptionError") and a message [5]. You should check the documentation for the specific module you are using to ensure you are catching the appropriate error union types [5].

Citations:


🌐 Web query:

site:github.com/cipherstash/auth "AuthFailure" "failure.error.message"

💡 Result:

No exact match found for site:github.com/cipherstash/auth "AuthFailure" "failure.error.message".

Closest relevant results I found were CipherStash docs and the @cipherstash/auth package summary, but not that exact string in the public GitHub results. (socket.dev)

If you want, I can try a broader code-search style query for the repo or search issues/PRs specifically.

Citations:


result.failure.error.message uses the wrong shape. failure should expose the message directly; otherwise an auth-construction failure can throw a secondary TypeError while building the error string.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/stack/src/wasm-inline.ts` around lines 435 - 478, The failure
handling in resolveStrategy is reading the auth construction error from the
wrong shape, which can itself throw while formatting the message. Update the
AccessKeyStrategy.create failure branch in resolveStrategy to use the message
exposed directly on result.failure, and keep the thrown error informative
without dereferencing a nested error object. Preserve the existing runtime
checks around config.authStrategy, config.strategy, and config.accessKey.

coderdan added 3 commits July 7, 2026 13:37
Deno 2.9 applies a 24h `install.minimumDependencyAge` cooldown to npm
resolution by default. The WASM smoke test resolves @cipherstash/auth and
@cipherstash/protect-ffi through Deno's own npm resolver, so a freshly
published first-party version (e.g. auth 0.41.0, <24h old) is rejected with
"newer than the specified minimum dependency date" — turning CI red for ~24h
after every bump. In the 1.0 lead-up these are bumped frequently.

Mirror pnpm-workspace.yaml's minimumReleaseAge (7d) + minimumReleaseAgeExclude:
set minimumDependencyAge to P7D and exclude the first-party @cipherstash
packages, so third-party deps keep the cooldown while our own lockstep bumps
are testable immediately.

Refs #567
The declarative `install.minimumDependencyAge` exclude added in the previous
commit does not apply to `deno test`'s implicit npm resolution — Deno 2.9.1
still enforced the default 24h cooldown and rejected the freshly-published
auth 0.41.0. Use the `--minimum-dependency-age=0` flag on the test command
instead (confirmed supported by `deno test`).

Safe: the smoke test resolves only first-party @cipherstash packages (auth,
protect-ffi) plus JSR std — no third-party npm — and pnpm's 7d cooldown still
governs every real install. This unblocks lockstep first-party bumps in the
1.0 lead-up without waiting 24h per version.

Refs #567
Deno 2.9's default 24h npm freshness cooldown kept rejecting freshly
published first-party versions (auth 0.41.0), and neither the
`install.minimumDependencyAge` exclude nor the `--minimum-dependency-age=0`
flag suppressed it for `deno test` in 2.9.1. Rather than fight Deno's
cooldown knobs, stop resolving these against the registry at all.

Point the import map at the WASM-inline entries pnpm already installed
(reached via stack's own node_modules symlink), instead of `npm:@x@version`
specifiers. Both entries import only their own relative wasm bindings, so
there are no transitive npm deps. This:

- removes all `npm:` resolution from the smoke test, so Deno's cooldown never
  applies (the real gate stays pnpm's minimumReleaseAge, which already
  exempts first-party @cipherstash packages);
- keeps auth/protect-ffi in lockstep with the catalog automatically — a
  version bump no longer needs a matching edit here, which matters in the
  1.0 lead-up where these are bumped often.

Refs #567
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update @cipherstash/stack to @cipherstash/auth 0.41 (Result-returning API)

1 participant