You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Corrected. The original version of this issue claimed the Node runtime fails and that the bug was in @cipherstash/stack. Both were wrong — see the correction at the bottom. The runtime is fine; this is a types-only bug, and it lives in protect-ffi.
Summary
Assigning an @cipherstash/auth 0.41 strategy (OidcFederationStrategy, AccessKeyStrategy) to config.authStrategydoes not typecheck, on either entry:
error TS2322: Type 'OidcFederationStrategy' is not assignable to type 'AuthStrategy'.
The types returned by 'getToken()' are incompatible between these types.
Type 'Promise<Result<TokenResult, AuthFailure>>' is not assignable to type 'Promise<{ token: string; }>'.
Property 'token' is missing in type 'Failure<AuthFailure>' but required in type '{ token: string; }'.
(Reproduced under packages/stack/tsconfig.json, which sets customConditions: ["node"].)
It works at runtime. Only the type is wrong.
Root cause — protect-ffi's exported type is narrower than its runtime
But protect-ffi 0.28's runtime accepts both shapes, on Node and WASM alike. From its CHANGELOG under [0.28.0]:
Support for @cipherstash/auth0.41's @byteslice/resultResult-shaped getToken() — { data: { token, … } } on success, { failure: { type, error, … } } on error — on both the Node (Neon) and WASM auth paths. The bare { token } shape … is still accepted, so this is backward compatible.
The Neon implementation is at crates/protect-ffi/src/lib.rs:537-575: it reads failure, unwraps data when present, and otherwise falls back to a top-level token.
The TypeScript declaration simply wasn't widened with it. protect-ffi's own integration tests pin @cipherstash/auth ^0.39.0 (integration-tests/package.json:13) — a pre-Result version with a different 3-arg create() — so oidc-federation.test.ts's const asStrategy: AuthStrategy = strategy still compiles there and nothing exercises the 0.41 shape against the type.
Impact
@cipherstash/stack re-exports the auth strategies and types ClientConfig.authStrategy as protect-ffi's AuthStrategy. Any user following the documented identity-aware encryption path gets a type error, even though the code runs correctly.
Interim workaround: authStrategy: strategy.data as unknown as AuthStrategy.
Fix
Upstream in protect-ffi — widen the exported type to the contract the runtime already implements, e.g.:
Also worth fixing there: the WASM doc comment (crates/protect-ffi/src/wasm.rs:259) still says getToken(): Promise<{ token: string, ... }>, which flows into dist/wasm/protect_ffi.d.ts.
Once released, @cipherstash/stack just needs the version bump — no adapter, no code change — and the caveat in skills/stash-encryption/SKILL.md comes out.
packages/stack/__tests__/init-strategy.test.ts hand-rolls a { token } stub, so it stands in for the FFI's declared contract rather than for a real auth strategy. It cannot see the mismatch.
Vitest doesn't typecheck, and pnpm --filter @cipherstash/stack build is already red on an unrelated pre-existing dts error (Type 'EncryptionError' does not satisfy the constraint 'FailureOption'), so tsc output isn't being watched.
Correction to the original report
The first version of this issue asserted that the Node runtime fails with missing 'token' field, and that the bug was Stack's for forwarding the strategy unadapted.
That was inferred from strings on the 0.28 NAPI binary, where missing 'token' field and 'token' field is not a string are still present. That inference was unsound: those strings persist for the legacy bare-{token} path. Diffing 0.27 against 0.28 for strings unique to the new code path gives the opposite answer:
string
0.27
0.28
reading 'failure' field threw
absent
present
reading 'data' field threw
absent
present
protect-ffi is open source; the Rust was one clone away and says plainly that both shapes are handled. Reading it would have been faster and correct.
Context
Found while refreshing the stash-encryption skill (#598), which documents config.authStrategy as the current identity-aware path.
Summary
Assigning an
@cipherstash/auth0.41 strategy (OidcFederationStrategy,AccessKeyStrategy) toconfig.authStrategydoes not typecheck, on either entry:(Reproduced under
packages/stack/tsconfig.json, which setscustomConditions: ["node"].)It works at runtime. Only the type is wrong.
Root cause — protect-ffi's exported type is narrower than its runtime
@cipherstash/auth0.41 (index.d.ts:134):@cipherstash/protect-ffi0.28 (src/index.cts:461):But protect-ffi 0.28's runtime accepts both shapes, on Node and WASM alike. From its CHANGELOG under
[0.28.0]:The Neon implementation is at
crates/protect-ffi/src/lib.rs:537-575: it readsfailure, unwrapsdatawhen present, and otherwise falls back to a top-leveltoken.The TypeScript declaration simply wasn't widened with it. protect-ffi's own integration tests pin
@cipherstash/auth ^0.39.0(integration-tests/package.json:13) — a pre-Resultversion with a different 3-argcreate()— sooidc-federation.test.ts'sconst asStrategy: AuthStrategy = strategystill compiles there and nothing exercises the 0.41 shape against the type.Impact
@cipherstash/stackre-exports the auth strategies and typesClientConfig.authStrategyas protect-ffi'sAuthStrategy. Any user following the documented identity-aware encryption path gets a type error, even though the code runs correctly.Interim workaround:
authStrategy: strategy.data as unknown as AuthStrategy.Fix
Upstream in protect-ffi — widen the exported type to the contract the runtime already implements, e.g.:
Also worth fixing there: the WASM doc comment (
crates/protect-ffi/src/wasm.rs:259) still saysgetToken(): Promise<{ token: string, ... }>, which flows intodist/wasm/protect_ffi.d.ts.Once released,
@cipherstash/stackjust needs the version bump — no adapter, no code change — and the caveat inskills/stash-encryption/SKILL.mdcomes out.Why nothing caught it
packages/stack/__tests__/lock-context.test.ts— the only test that builds a realOidcFederationStrategy— early-returned whenUSER_JWTwas absent, reporting four passing assertions that never executed. Fixed in docs(stack): refresh the stash-encryption skill; fix the identity-aware auth docs and a vacuously-passing test #598 (skipIf).packages/stack/__tests__/init-strategy.test.tshand-rolls a{ token }stub, so it stands in for the FFI's declared contract rather than for a real auth strategy. It cannot see the mismatch.pnpm --filter @cipherstash/stack buildis already red on an unrelated pre-existing dts error (Type 'EncryptionError' does not satisfy the constraint 'FailureOption'), sotscoutput isn't being watched.Correction to the original report
The first version of this issue asserted that the Node runtime fails with
missing 'token' field, and that the bug was Stack's for forwarding the strategy unadapted.That was inferred from
stringson the 0.28 NAPI binary, wheremissing 'token' fieldand'token' field is not a stringare still present. That inference was unsound: those strings persist for the legacy bare-{token}path. Diffing 0.27 against 0.28 for strings unique to the new code path gives the opposite answer:reading 'failure' field threwreading 'data' field threwprotect-ffi is open source; the Rust was one clone away and says plainly that both shapes are handled. Reading it would have been faster and correct.
Context
Found while refreshing the
stash-encryptionskill (#598), which documentsconfig.authStrategyas the current identity-aware path.