Bump @cipherstash/auth to 0.41 and migrate to Result API#568
Conversation
…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
🦋 Changeset detectedLatest commit: 6fdb103 The changes in this PR will be included in the next version bump. This PR includes changesets to release 8 packages
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 |
|
Warning Review limit reached
Next review available in: 3 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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. 📝 WalkthroughWalkthroughRenames Changesstrategy → authStrategy rename
Estimated code review effort: 3 (Moderate) | ~30 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
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
There was a problem hiding this comment.
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 winFailure-type handling is less granular than the sibling implementation.
checkExistingAuth()treats everydetected.failure/result.failureas "not authenticated" and silently falls through toundefined, whereashasCredentials()inpackages/wizard/src/lib/prerequisites.ts(same migration cohort) explicitly checksfailure.typeforNOT_AUTHENTICATED/MISSING_WORKSPACE_CRNand rethrows anything else. Here, a failure likeWORKSPACE_MISMATCHgets silently swallowed andinitre-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.typefor 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 winConsider 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 smallexitOnFailure(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 winConsider covering the rethrow and
detect()-failure branches.The mock only drives the
getToken()→NOT_AUTHENTICATEDpath. The newhasCredentialslogic also has adetect().failurebranch and a rethrow branch for unexpected failure types (Lines 50-58 inprerequisites.ts), including theMISSING_WORKSPACE_CRNcase. Adding cases for adetect()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 winConsider adding a test for the
AccessKeyStrategy.createfailure path.The mock at Line 22 only returns
{ data: ... }. There's no test assertingresolveStrategythrows the descriptive error built atwasm-inline.tsLines 472-476 whenAccessKeyStrategy.createreturns{ 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (20)
.changeset/rename-strategy-to-auth-strategy.md.changeset/stack-auth-0-41-result-api.mdpackage.jsonpackages/cli/src/commands/auth/login.tspackages/cli/src/commands/init/steps/authenticate.tspackages/stack/__tests__/helpers/stub-auth-wasm-inline.tspackages/stack/__tests__/init-strategy.test.tspackages/stack/__tests__/lock-context.test.tspackages/stack/__tests__/wasm-inline-new-client.test.tspackages/stack/__tests__/wasm-inline-strategy.test.tspackages/stack/src/encryption/index.tspackages/stack/src/identity/index.tspackages/stack/src/index.tspackages/stack/src/types.tspackages/stack/src/wasm-inline.tspackages/wizard/src/__tests__/prerequisites.test.tspackages/wizard/src/agent/fetch-prompt.tspackages/wizard/src/agent/interface.tspackages/wizard/src/lib/prerequisites.tspnpm-workspace.yaml
| 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 | ||
| } |
There was a problem hiding this comment.
🗄️ 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 -C3Repository: 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 2Repository: 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:
- 1: https://npmx.dev/package/@cipherstash/auth
- 2: https://npmx.dev/package/@cipherstash/auth/v/0.36.0
- 3: https://docs.rs/cipherstash-client/latest/cipherstash_client/enum.AuthError.html
- 4: https://cipherstash.com/docs/stack/reference/error-handling
🏁 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:
- 1: https://npmx.dev/package/@cipherstash/auth
- 2: https://npmx.dev/package/@cipherstash/auth/v/0.36.0
- 3: https://cipherstash.com/docs/stack/reference/error-handling
🌐 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:
- 1: feat(stack): protect-ffi 0.26.0 + auth 0.39 OidcFederationStrategy (stacked on #496) #497
- 2: https://github.com/cipherstash/proxy/blob/main/docs/reference/index.md
- 3: https://github.com/cipherstash/stack/
- 4: https://github.com/cipherstash/stack
- 5: better-auth/better-auth@2e23681f
- 6: https://github.com/better-auth/better-auth/blob/938efee3/packages/api-key/src/api-key.test.ts
- 7: [Feature Request] Allow authenticated users to set permissions when creating their own API keys better-auth/better-auth#7333
🌐 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:
- 1: https://cipherstash.com/docs/stack/reference/error-handling
- 2: https://github.com/cipherstash/docs/blob/main/content/stack/cipherstash/encryption/models.mdx
- 3: https://npmx.dev/package/@cipherstash/auth
- 4: https://npmx.dev/package/@cipherstash/auth/v/0.36.0
🏁 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 3Repository: 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 3Repository: 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:
- 1: https://npmx.dev/package/@cipherstash/auth
- 2: https://npmx.dev/package/@cipherstash/auth/v/0.36.0
- 3: https://docs.rs/cipherstash-client/latest/cipherstash_client/enum.AuthError.html
- 4: https://docs.rs/crate/stack-auth/latest
- 5: https://cipherstash.com/docs/stack/reference/error-handling
🌐 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.
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
Closes #567.
What
Bumps
@cipherstash/auth(and its 6 per-platform native bindings) from 0.40.0 → 0.41.0 and migrates@cipherstash/stack, thestashCLI, and@cipherstash/wizardto its new API.Why it's not just a version bump
@cipherstash/auth0.41 is a breaking release: every fallible auth operation now returns a@byteslice/resultResult<T, AuthFailure>({ data }on success,{ failure }on error) instead of throwing, and theAuthErrortype is renamed toAuthFailure— a discriminated union keyed by.type("NOT_AUTHENTICATED","WORKSPACE_MISMATCH", …) that replaces the olderror.codestring. Consumers branch onif (result.failure)/failure.typerather thantry/catch.Changes
@cipherstash/stack(breaking type surface —minor)AuthError→AuthFailure(AuthErrorCode/TokenResultunchanged)resolveStrategyunwraps theResultfromAccessKeyStrategy.create; a construction failure throws a descriptive[encryption]error naming theAuthFailure.typestash(CLI) (patch)auth logindevice-code flow +bindClientDevice, and theinitexisting-auth check, unwrapResult@cipherstash/wizard(patch)error.code→failure.type)Catalog + tests
0.40.0→0.41.0inpnpm-workspace.yamland rootpackage.json; lockfile updatedResultshapeVerification
turbo buildgreen for@cipherstash/stack,stash,@cipherstash/wizardstash334/334,@cipherstash/wizard140/140, stack offline auth tests 40/40~/.cipherstash/...) and are unrelated to this changeSummary by CodeRabbit
New Features
Bug Fixes
Chores