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
Copy file name to clipboardExpand all lines: CHANGELOG.md
+35Lines changed: 35 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,3 +1,38 @@
1
+
## Unreleased (6.0.0-rc.13)
2
+
3
+
### Features
4
+
5
+
***rotation:** Add versioned key rotation via `rotateKeys()` and `getKeyVersion()` with lazy re-encryption on read. New `useKeyRotation` hook exposes the same flow declaratively.
6
+
***security hardening:** Defense-in-depth pass — non-breaking, applied transparently to new writes and via lazy upgrade on rotation:
7
+
- HMAC-SHA256 integrity tag bound to every entry's metadata + ciphertext, surfaced on `StorageMetadata.integrityTag`. Tampering with SharedPreferences/Keychain attributes now raises `IntegrityViolationError` (`E_INTEGRITY_VIOLATION`) before any biometric prompt is shown.
8
+
- AES-GCM AAD on Android binds ciphertext to `service|key|v<version>`, defeating cross-entry swap attacks.
9
+
-`setUnlockedDeviceRequired(true)` on every Android Keystore key (API 28+), mirroring iOS's `kSecAttrAccessibleWhenUnlocked` semantics.
10
+
- Plaintext byte buffers are zeroized after use on both platforms.
- Backwards compatible: entries written by earlier versions decode without verification and are upgraded on the next write or rotation.
13
+
***errors:** New typed error classes (`SensitiveInfoError`, `NotFoundError`, `AuthenticationCanceledError`, `IntegrityViolationError`, `KeyInvalidatedError`, `RotationFailedError`) with `code` discriminants and `instanceof` predicates. Importable from the `react-native-sensitive-info/errors` subpath.
14
+
***tree-shaking:**`"sideEffects": false` everywhere; the package now publishes three focused subpath entries (`.`, `/hooks`, `/errors`). The default export has been removed — import only the helpers you use.
15
+
***nitro 0.35:** Regenerated against `nitrogen@0.35.5` and `react-native-nitro-modules@0.35.5`.
16
+
***tooling:** Migrated linting/formatting from ESLint + Prettier to **Biome 2**. Single config at `biome.json`, faster CI runs.
17
+
18
+
### Refactor (KISS · DRY · SRP)
19
+
20
+
* Introduced `useAsyncQuery` (read-only hooks) and `useMutation` (mutation hooks) primitives. `useHasSecret`, `useSecretItem`, `useSecureOperation`, `useKeyRotation`, and `useSecureStorage` now compose the same lifecycle/abort/error-handling pipeline — no duplicated state machines.
21
+
*`useSecureStorage` shrunk from ~230 LOC to ~180 LOC and reuses the shared abort + auth-cancel semantics; behaviour is unchanged.
22
+
* Test fixtures consolidated in `src/__tests__/__mocks__/fixtures.ts` (`buildTestItem`, `buildTestMetadata`).
23
+
* Removed redundant re-exports from `src/internal/errors.ts`.
24
+
25
+
### Breaking changes
26
+
27
+
* The default export is gone. Use named imports: `import { setItem } from 'react-native-sensitive-info'`.
28
+
* React hooks are no longer re-exported from the package root — import them from `react-native-sensitive-info/hooks`.
29
+
30
+
### Notes
31
+
32
+
***iOS rotation** updates the Keychain metadata via `SecItemUpdate`, preserving the original access-control attributes while bumping `keyVersion`.
33
+
***Android rotation** mints a fresh per-entry Keystore alias (`SensitiveInfo_<hash>_v<version>`) during lazy or eager re-encryption and deletes the stale alias after a successful rewrite.
34
+
* Version state lives in a non-secret registry (`SharedPreferences` on Android, `UserDefaults` on iOS). Delete the app's data to reset.
|`useKeyRotation()`| Rotate the master key for a service |`{ lastResult, error, isRotating, rotate, readVersion }`|
196
200
197
201
### Best practices
198
202
@@ -220,6 +224,18 @@ function YourComponent() {
220
224
221
225
For comprehensive examples and advanced patterns, see [`HOOKS.md`](./HOOKS.md).
222
226
227
+
### 🧱 Hook architecture (DRY · KISS · SRP)
228
+
229
+
Every hook in this package is a thin choreography layer over three internal primitives, so adding or auditing a hook stays a single-file change:
230
+
231
+
| Primitive | Responsibility |
232
+
| --- | --- |
233
+
|`useAsyncLifecycle`| Mount tracking + `AbortController` plumbing — _one job, no React state of its own_. |
234
+
|`useAsync` / `useAsyncQuery`| The shared "stable options → strip `skip` → memoize → fetch" recipe used by every read-only hook (`useHasSecret`, `useSecretItem`, `useSecret`, `useSecureStorage`, `useSecurityAvailability`). |
235
+
|`useMutation`| The imperative state machine (loading + error + auth-cancel handling) reused by every mutation-style hook (`useSecureOperation`, `useKeyRotation`, plus the `saveSecret`/`removeSecret`/`clearAll` helpers in `useSecureStorage`). |
236
+
237
+
Net effect: the data-fetching hooks are 25–35 lines each, mutations are ~10 lines, and the abort/cancel/error contract is identical across the surface — there is no place where a bug fix has to be repeated.
238
+
223
239
## ❗ Error handling
224
240
225
241
Every public hook returns failures as `HookError` instances. Besides `message`, each error carries:
@@ -232,7 +248,7 @@ Biometric or device-credential prompts cancelled by the user now surface as a fr
> When using the imperative API, look for the `[E_AUTH_CANCELED]` marker in the thrown error message to detect cancellations.
263
279
280
+
## 🔁 Key rotation
281
+
282
+
The library supports **versioned master keys** with lazy re-encryption. Each stored entry is tagged with the `keyVersion` that produced its ciphertext. Calling `rotateKeys()` bumps the active version; subsequent reads transparently re-encrypt entries that were stored under older versions.
| Error classification | Typed `SensitiveInfoError` subclasses via `/errors` subpath | Same |
329
+
330
+
> **Tamper detection:** every read recomputes the HMAC over the persisted `(service, key, version, accessControl, securityLevel, timestamp, ciphertext, iv)` tuple. A mismatch raises `IntegrityViolationError` (`E_INTEGRITY_VIOLATION`) **before** any biometric prompt fires, so spoofed entries can never trigger user authentication. Entries written by older library versions (no `integrityTag`) are accepted on first read and upgraded on the next write or rotation.
331
+
332
+
Typed errors can be imported from the `/errors` subpath for tree-shakeable error handling:
333
+
334
+
```tsx
335
+
import {
336
+
isNotFoundError,
337
+
isAuthenticationCanceledError,
338
+
isIntegrityViolationError,
339
+
isKeyInvalidatedError,
340
+
} from'react-native-sensitive-info/errors'
341
+
342
+
try {
343
+
awaitgetItem('token', { service: 'auth' })
344
+
} catch (error) {
345
+
if (isAuthenticationCanceledError(error)) return
346
+
if (isKeyInvalidatedError(error)) {
347
+
// The hardware key was invalidated (e.g. biometrics re-enrolled).
348
+
// Delete the affected entry and ask the user to re-enter.
349
+
awaitdeleteItem('token', { service: 'auth' })
350
+
}
351
+
throwerror
352
+
}
353
+
```
354
+
355
+
## 🌳 Tree-shaking
356
+
357
+
Every entry point is side-effect-free (`"sideEffects": false`) and split into focused subpaths:
0 commit comments