@tgoliveira/vault-core 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +77 -0
- package/API_REFERENCE.md +196 -26
- package/ARCHITECTURE.md +5 -0
- package/CHANGELOG.md +51 -0
- package/MIGRATION_FROM_LIQSENSE.md +3 -1
- package/PASSKEY_PRF_ENVELOPES.md +2 -1
- package/PASSWORD_ENVELOPES.md +3 -1
- package/README.md +42 -2
- package/RECOVERY_PHRASE.md +2 -1
- package/SECURITY.md +22 -2
- package/dist/browser.d.ts +12 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +46 -18
- package/dist/browser.js.map +1 -1
- package/dist/envelopes/passkey-prf.d.ts +3 -3
- package/dist/envelopes/passkey-prf.d.ts.map +1 -1
- package/dist/envelopes/passkey-prf.js +7 -5
- package/dist/envelopes/passkey-prf.js.map +1 -1
- package/dist/envelopes/password.d.ts +1 -1
- package/dist/envelopes/password.d.ts.map +1 -1
- package/dist/envelopes/password.js +3 -1
- package/dist/envelopes/password.js.map +1 -1
- package/dist/envelopes/recovery.d.ts +2 -2
- package/dist/envelopes/recovery.d.ts.map +1 -1
- package/dist/envelopes/recovery.js +15 -6
- package/dist/envelopes/recovery.js.map +1 -1
- package/dist/kdf/argon2id.d.ts.map +1 -1
- package/dist/kdf/argon2id.js +15 -2
- package/dist/kdf/argon2id.js.map +1 -1
- package/dist/kdf/params.d.ts +24 -0
- package/dist/kdf/params.d.ts.map +1 -1
- package/dist/kdf/params.js +22 -0
- package/dist/kdf/params.js.map +1 -1
- package/dist/payload/encrypted-payload.d.ts +4 -2
- package/dist/payload/encrypted-payload.d.ts.map +1 -1
- package/dist/payload/encrypted-payload.js +3 -1
- package/dist/payload/encrypted-payload.js.map +1 -1
- package/dist/react/session/use-vault-session.d.ts +1 -0
- package/dist/react/session/use-vault-session.d.ts.map +1 -1
- package/dist/react/session/use-vault-session.js +7 -2
- package/dist/react/session/use-vault-session.js.map +1 -1
- package/dist/react/session/vault-session-provider.d.ts +2 -1
- package/dist/react/session/vault-session-provider.d.ts.map +1 -1
- package/dist/react/session/vault-session-provider.js +7 -2
- package/dist/react/session/vault-session-provider.js.map +1 -1
- package/dist/session/auto-lock.d.ts +2 -1
- package/dist/session/auto-lock.d.ts.map +1 -1
- package/dist/session/auto-lock.js +15 -1
- package/dist/session/auto-lock.js.map +1 -1
- package/dist/validation/aad-assert.d.ts +5 -3
- package/dist/validation/aad-assert.d.ts.map +1 -1
- package/dist/validation/aad-assert.js +15 -8
- package/dist/validation/aad-assert.js.map +1 -1
- package/dist/validation/plaintext-reject.d.ts.map +1 -1
- package/dist/validation/plaintext-reject.js +18 -4
- package/dist/validation/plaintext-reject.js.map +1 -1
- package/dist/validation/schemas.d.ts +148 -56
- package/dist/validation/schemas.d.ts.map +1 -1
- package/dist/validation/schemas.js +29 -10
- package/dist/validation/schemas.js.map +1 -1
- package/docs/ADOPTING_VAULT_CORE_IN_EXISTING_APPS.md +575 -0
- package/docs/IMPLEMENTATION_GUIDE.md +577 -0
- package/docs/README.md +30 -0
- package/docs/RELEASING.md +102 -0
- package/package.json +10 -3
package/AGENTS.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Agent and Contributor Guide
|
|
2
|
+
|
|
3
|
+
This file is the operational entry point for AI agents and human contributors working on
|
|
4
|
+
`@tgoliveira/vault-core`.
|
|
5
|
+
|
|
6
|
+
## Language
|
|
7
|
+
|
|
8
|
+
- Source code, identifiers, error messages, tests, comments, and documentation must be written in
|
|
9
|
+
English.
|
|
10
|
+
- Consumer-facing API examples must be complete TypeScript that reflects the current signatures.
|
|
11
|
+
|
|
12
|
+
## Read first
|
|
13
|
+
|
|
14
|
+
1. `README.md` for package scope and the shortest working example.
|
|
15
|
+
2. `docs/IMPLEMENTATION_GUIDE.md` for the complete consumer workflow.
|
|
16
|
+
3. `SECURITY.md` before changing crypto, persistence, validation, or session behavior.
|
|
17
|
+
4. `API_REFERENCE.md` for public entry points and security preconditions.
|
|
18
|
+
5. `CHANGELOG.md` before modifying a public contract.
|
|
19
|
+
6. `docs/RELEASING.md` before changing the package version or publishing.
|
|
20
|
+
|
|
21
|
+
## Public package boundaries
|
|
22
|
+
|
|
23
|
+
| Import | Responsibility |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `@tgoliveira/vault-core` | Crypto, envelopes, recovery, schemas, AAD, and validation |
|
|
26
|
+
| `@tgoliveira/vault-core/browser` | Browser session lifecycle, storage inspection, PRF salt, recovery kit DOM helpers |
|
|
27
|
+
| `@tgoliveira/vault-core/react` | React session provider, hooks, and client status derivation |
|
|
28
|
+
| `@tgoliveira/vault-core/testing` | Plaintext sentinels and leak-detection helpers |
|
|
29
|
+
|
|
30
|
+
Do not add framework, persistence, database, route, authentication, or product payload concerns to
|
|
31
|
+
the core entry.
|
|
32
|
+
|
|
33
|
+
## Non-negotiable security invariants
|
|
34
|
+
|
|
35
|
+
- Vault passwords, recovery phrases, UVKs, PRF output, and decrypted payloads never go to the server.
|
|
36
|
+
- Decrypted vault state is never persisted to localStorage or IndexedDB.
|
|
37
|
+
- High-level decrypt and unlock calls always receive and validate the expected scope and profile.
|
|
38
|
+
- Persisted KDF metadata is untrusted and bounded before work begins.
|
|
39
|
+
- Session key changes use lifecycle-aware lock and unlock APIs; public direct setters are forbidden.
|
|
40
|
+
- Account authentication and vault unlock remain separate security domains.
|
|
41
|
+
|
|
42
|
+
## Change protocol
|
|
43
|
+
|
|
44
|
+
For every user-visible change:
|
|
45
|
+
|
|
46
|
+
1. Update implementation and tests together.
|
|
47
|
+
2. Add an entry under the appropriate `CHANGELOG.md` `Unreleased` heading.
|
|
48
|
+
3. Update every affected example and signature in consumer documentation.
|
|
49
|
+
4. Preserve deprecated aliases only when the migration path remains safe and explicit.
|
|
50
|
+
5. Run `npm run validate`.
|
|
51
|
+
6. Run `npm pack --dry-run` when exports, package files, or documentation change.
|
|
52
|
+
|
|
53
|
+
The test suite enforces that the current `package.json` version has a released changelog entry. A
|
|
54
|
+
version bump without a changelog release section must fail validation.
|
|
55
|
+
|
|
56
|
+
Do not bump versions, create release tags, or publish manually. Start the `Publish package to npmjs`
|
|
57
|
+
workflow on `main`; it owns version selection, release metadata, npm publication, and tag creation.
|
|
58
|
+
|
|
59
|
+
## Required validation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm ci
|
|
63
|
+
npm run validate
|
|
64
|
+
npm pack --dry-run
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Coverage thresholds are enforced per production file at 90% for statements, branches, functions,
|
|
68
|
+
and lines. Do not lower or bypass thresholds to merge a change.
|
|
69
|
+
|
|
70
|
+
## Definition of done
|
|
71
|
+
|
|
72
|
+
- Public types and runtime schemas agree.
|
|
73
|
+
- Security failures have regression tests.
|
|
74
|
+
- All code and documentation are in English.
|
|
75
|
+
- `CHANGELOG.md` describes the consumer-visible effect.
|
|
76
|
+
- Documentation examples typecheck conceptually against current APIs.
|
|
77
|
+
- `npm run validate` and package dry-run pass.
|
package/API_REFERENCE.md
CHANGED
|
@@ -1,36 +1,206 @@
|
|
|
1
1
|
# API Reference
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The package exposes four supported entry points. Internal `dist/*` paths are not public APIs.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- `@tgoliveira/vault-core/browser`
|
|
7
|
-
- `@tgoliveira/vault-core/testing`
|
|
8
|
-
- `@tgoliveira/vault-core/react`
|
|
5
|
+
For complete workflows, use [`docs/IMPLEMENTATION_GUIDE.md`](docs/IMPLEMENTATION_GUIDE.md).
|
|
9
6
|
|
|
10
|
-
## Core
|
|
7
|
+
## Core: `@tgoliveira/vault-core`
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
- `EncryptedVaultPayload`, `VaultEnvelope`, `PasswordEnvelope`, `RecoveryPhraseEnvelope`, `PasskeyPrfEnvelope`
|
|
14
|
-
- `RecoveryPhraseWordCount` (`12 | 24`)
|
|
15
|
-
- `VaultUnlockResult<TPayload>`, `VaultCoreError`
|
|
9
|
+
### Protocol constants and profile
|
|
16
10
|
|
|
17
|
-
|
|
11
|
+
| Export | Purpose |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `ENCRYPTION_VERSION` | Stored payload version, currently `enc-v1` |
|
|
14
|
+
| `ENCRYPTION_ALG` | Stored algorithm identifier, currently `AES-GCM` |
|
|
15
|
+
| `VAULT_CRYPTO_VERSION` | Vault protocol version, currently `vault-v1` |
|
|
16
|
+
| `DEFAULT_VAULT_AUTO_LOCK_MINUTES` | Default browser inactivity timeout |
|
|
17
|
+
| `VaultCryptoProfile` | Stable application AAD contexts |
|
|
18
|
+
| `VaultAadScope`, `VaultAadField` | Authenticated user/resource/field scope |
|
|
19
|
+
| `RecoveryPhraseWordCount` | `12 | 24` |
|
|
20
|
+
| `resolveAadContext(scope, profile)` | Resolves explicit or profile-derived AAD context |
|
|
18
21
|
|
|
19
|
-
-
|
|
20
|
-
- `encryptVaultPayload<T>(payload, key, scope, profile)`
|
|
21
|
-
- `decryptVaultPayload<T>(encrypted, key)`
|
|
22
|
-
- `createPasswordEnvelope` / `unlockWithPasswordEnvelope`
|
|
23
|
-
- `createRecoveryPhrase` / `createRecoveryEnvelope` / `unlockWithRecoveryEnvelope`
|
|
24
|
-
- `createPasskeyPrfEnvelope` / `unlockWithPasskeyPrfEnvelope`
|
|
25
|
-
- `createRecoveryKitText(...)`
|
|
26
|
-
- `assertVaultKeyAad` / `assertVaultPayloadAad`
|
|
27
|
-
- `assertNoVaultPlaintextFields` / `validateNoPlaintextLeak`
|
|
22
|
+
### User Vault Key and AES-GCM
|
|
28
23
|
|
|
29
|
-
|
|
24
|
+
| Export | Purpose |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `createUserVaultKey()` | Generates an extractable 256-bit AES-GCM UVK |
|
|
27
|
+
| `importUserVaultKey(bytes)` | Imports raw UVK bytes |
|
|
28
|
+
| `exportUserVaultKey(key)` | Exports raw UVK bytes; keep client-only |
|
|
29
|
+
| `generateAesKey()`, `importAesKey()`, `exportAesKey()` | Low-level AES key primitives |
|
|
30
|
+
| `encryptVaultPayload(payload, key, scope, profile)` | Serializes and encrypts generic JSON |
|
|
31
|
+
| `decryptVaultPayload(encrypted, key, expectedScope, profile)` | Validates expected AAD, decrypts, and parses JSON |
|
|
32
|
+
| `encryptField(plaintext, key, aad, profile)` | Low-level string encryption |
|
|
33
|
+
| `decryptField(encrypted, key)` | Low-level compatibility decrypt without expected-scope validation |
|
|
34
|
+
| `canonicalAadString(aad)` | Produces canonical AAD JSON |
|
|
35
|
+
| `aadByteCandidates(aad)` | Produces canonical and legacy AAD byte candidates |
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
Use the high-level payload APIs for application data. `decryptField()` is appropriate only when the
|
|
38
|
+
caller separately validates expected AAD, such as a bounded legacy migration.
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- `
|
|
36
|
-
- `
|
|
40
|
+
### Encoding, random, and serialization utilities
|
|
41
|
+
|
|
42
|
+
- `bytesToBase64Url(bytes)` / `base64UrlToBytes(value)`
|
|
43
|
+
- `stringToBytes(value)` / `bytesToString(bytes)`
|
|
44
|
+
- `toBufferSource(bytes)`
|
|
45
|
+
- `randomBytes(length)`
|
|
46
|
+
- `serializeVaultPayload(payload)` / `parseVaultPayload<T>(json)`
|
|
47
|
+
|
|
48
|
+
### Argon2id
|
|
49
|
+
|
|
50
|
+
| Export | Purpose |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `DEFAULT_ARGON2ID_PARAMS` | Creation defaults |
|
|
53
|
+
| `ARGON2ID_LIMITS` | Accepted persisted resource bounds |
|
|
54
|
+
| `assertSafeArgon2idParams(params)` | Validates memory, iteration, and parallelism bounds |
|
|
55
|
+
| `assertSafeArgon2idSalt(salt)` | Validates salt size |
|
|
56
|
+
| `serializeArgon2idMetadata(salt, params?)` | Builds persisted metadata |
|
|
57
|
+
| `parseArgon2idMetadata(metadata)` | Decodes and validates persisted metadata |
|
|
58
|
+
| `deriveArgon2idAesKey(...)` | Low-level byte-based derivation |
|
|
59
|
+
| `deriveArgon2idAesKeyFromMetadata(...)` | Low-level derivation from stored metadata |
|
|
60
|
+
| `deriveVaultPasswordKey(password, salt?)` | NFKC-normalized password derivation |
|
|
61
|
+
| `deriveVaultPasswordKeyFromMetadata(password, metadata)` | Password derivation from stored metadata |
|
|
62
|
+
|
|
63
|
+
Applications normally use envelope APIs instead of direct derivation functions.
|
|
64
|
+
|
|
65
|
+
### Password envelopes
|
|
66
|
+
|
|
67
|
+
- `createPasswordEnvelope(vaultKey, password, scope, profile, salt?)`
|
|
68
|
+
- `unlockWithPasswordEnvelope(password, envelope, expectedScope, profile)`
|
|
69
|
+
|
|
70
|
+
### Recovery phrases and envelopes
|
|
71
|
+
|
|
72
|
+
- `createRecoveryPhrase({ wordCount })`
|
|
73
|
+
- `normalizeRecoveryPhrase(phrase)`
|
|
74
|
+
- `validateRecoveryPhraseFormat(phrase)`
|
|
75
|
+
- `getRecoveryPhraseWordCount(phrase)`
|
|
76
|
+
- `parseRecoveryPhraseWordCount(publicMetadata)`
|
|
77
|
+
- `assertRecoveryPhraseUnlockInput(phrase, expectedWordCount?)`
|
|
78
|
+
- `getRecoveryConfirmationPromptCount(wordCount)`
|
|
79
|
+
- `pickRecoveryConfirmationIndices(wordCount, count?)`
|
|
80
|
+
- `assertRecoveryPhraseConfirmation(original, confirmation)`
|
|
81
|
+
- `assertRecoveryPhraseWordConfirmation(phrase, answers, requiredIndices?)`
|
|
82
|
+
- `deriveRecoveryPhraseKey(...)` / `deriveRecoveryPhraseKeyFromMetadata(...)`
|
|
83
|
+
- `createRecoveryEnvelope(vaultKey, phrase, scope, profile, publicMetadata?, salt?)`
|
|
84
|
+
- `unlockWithRecoveryEnvelope(phrase, envelope, expectedScope, profile, options?)`
|
|
85
|
+
- `createRecoveryKitText(input)`
|
|
86
|
+
- `RECOVERY_PHRASE_WORDLIST_SOURCE`, `DEFAULT_RECOVERY_PHRASE_WORD_COUNT`
|
|
87
|
+
|
|
88
|
+
Word confirmation requires all deterministic default indices unless explicit required indices are
|
|
89
|
+
provided.
|
|
90
|
+
|
|
91
|
+
### Passkey PRF envelopes
|
|
92
|
+
|
|
93
|
+
- `createPasskeyPrfEnvelope(vaultKey, prfOutput, scope, profile, publicMetadata?)`
|
|
94
|
+
- `unlockWithPasskeyPrfEnvelope(envelope, prfOutput, expectedScope, profile, options?)`
|
|
95
|
+
- `unwrapVaultKeyFromPasskey(encryptedVaultKey, prfOutput, expectedScope, profile)`
|
|
96
|
+
- `extractPasskeyPrfOutput(extensionResults)`
|
|
97
|
+
- `isPasskeySupported()` / `isPrfExtensionSupported()`
|
|
98
|
+
|
|
99
|
+
The application owns WebAuthn ceremonies. Capability probes are preliminary; the actual ceremony may
|
|
100
|
+
still return no PRF output. PRF output must remain client-only.
|
|
101
|
+
|
|
102
|
+
### Runtime schemas and types
|
|
103
|
+
|
|
104
|
+
| Export | Runtime contract |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| `encryptedPayloadSchema` | `enc-v1` AES-GCM payload with UUID AAD identifiers |
|
|
107
|
+
| `argon2idKdfMetadataSchema` / `kdfMetadataSchema` | Bounded `kdf-v1` Argon2id metadata |
|
|
108
|
+
| `passwordEnvelopeSchema` | Password method plus required Argon2id metadata |
|
|
109
|
+
| `recoveryPhraseEnvelopeSchema` | Recovery method plus required Argon2id metadata |
|
|
110
|
+
| `passkeyPrfEnvelopeSchema` | Passkey PRF method plus null KDF metadata |
|
|
111
|
+
| `storedEnvelopeSchema` | Method-discriminated union of all envelopes |
|
|
112
|
+
| `vaultSetupEnvelopeFieldsSchema` | Complete encrypted setup record |
|
|
113
|
+
|
|
114
|
+
Associated inferred types include `EncryptedVaultPayload`, `Argon2idKdfMetadata`, `VaultEnvelope`,
|
|
115
|
+
`PasswordEnvelope`, `RecoveryPhraseEnvelope`, `PasskeyPrfEnvelope`, and `VaultEnvelopeMethod`.
|
|
116
|
+
|
|
117
|
+
### AAD and plaintext validation
|
|
118
|
+
|
|
119
|
+
- `assertVaultKeyAad(expectedScope, payload, profile)`
|
|
120
|
+
- `assertVaultPayloadAad(expectedScope, payload, profile)`
|
|
121
|
+
- `rejectVaultPlaintextFields(body)`
|
|
122
|
+
- `assertNoVaultPlaintextFields(body)`
|
|
123
|
+
- `validateNoPlaintextLeak(data)`
|
|
124
|
+
- `scanForSentinels(data, sentinels?)`
|
|
125
|
+
- `containsSentinel(value, sentinels?)`
|
|
126
|
+
- `PLAINTEXT_FORBIDDEN_VAULT_FIELDS`, `ALL_SENTINELS`, and named `SENTINEL_*` constants
|
|
127
|
+
|
|
128
|
+
The plaintext field guard is recursive and cycle-safe. It is defense in depth; closed API schemas are
|
|
129
|
+
still required.
|
|
130
|
+
|
|
131
|
+
### Errors
|
|
132
|
+
|
|
133
|
+
- `VaultPlaintextRejectionError`
|
|
134
|
+
- `VaultConflictError`
|
|
135
|
+
- `VaultNotFoundError`
|
|
136
|
+
- `PasskeyPrfRequiredError`
|
|
137
|
+
- `PasskeyUnlockError`
|
|
138
|
+
- `RecoveryPhraseConfirmationError`
|
|
139
|
+
- `VaultCoreError`
|
|
140
|
+
|
|
141
|
+
### Deprecated migration aliases
|
|
142
|
+
|
|
143
|
+
- `generateUserVaultKey`
|
|
144
|
+
- `generateRecoveryPhrase`
|
|
145
|
+
- `wrapVaultKeyForPassword` / `unwrapVaultKeyFromPassword`
|
|
146
|
+
- `wrapVaultKeyForRecoveryPhrase` / `unwrapVaultKeyFromRecoveryPhrase`
|
|
147
|
+
- `wrapVaultKeyForPasskey` / `unlockVaultFromPasskeyEnvelope`
|
|
148
|
+
- `buildRecoveryKitContent`
|
|
149
|
+
- `EncryptedPayload`, `StoredEnvelope`
|
|
150
|
+
|
|
151
|
+
New code should use the canonical APIs. Deprecated unlock aliases use the current secure signatures.
|
|
152
|
+
|
|
153
|
+
## Browser: `@tgoliveira/vault-core/browser`
|
|
154
|
+
|
|
155
|
+
### Session lifecycle
|
|
156
|
+
|
|
157
|
+
- `configureVaultSession(config)`
|
|
158
|
+
- `unlockVaultSession(vaultKey)` / `lockVaultSession()`
|
|
159
|
+
- `lockVaultSessionManually()` / `isVaultManuallyLocked()`
|
|
160
|
+
- `touchVaultSession()` / `scheduleVaultAutoLock()` / `clearVaultAutoLockTimer()`
|
|
161
|
+
- `getVaultAutoLockRemainingMs()`
|
|
162
|
+
- `getSessionVaultKey()` / `isVaultUnlocked()`
|
|
163
|
+
- `subscribeVaultSession(listener)`
|
|
164
|
+
- `registerVaultActivityGuard(events?)`
|
|
165
|
+
- `registerVaultUnloadGuard()`
|
|
166
|
+
- `resetVaultSessionLockState()`
|
|
167
|
+
- `VaultSessionConfig`
|
|
168
|
+
|
|
169
|
+
Direct session-key setters are intentionally not exported.
|
|
170
|
+
|
|
171
|
+
### Storage inspection
|
|
172
|
+
|
|
173
|
+
- `VaultStorageInspectionResult`: `"clear" | "found" | "unavailable"`
|
|
174
|
+
- `inspectLocalStoragePrefix(prefix)`
|
|
175
|
+
- `inspectIndexedDBPrefix(prefix)`
|
|
176
|
+
- `persistVaultRecordLocally()` always throws to prevent accidental plaintext persistence
|
|
177
|
+
|
|
178
|
+
Namespace inspection does not inspect record contents. Treat `"unavailable"` as a failed security
|
|
179
|
+
check. `assertNoDecryptedVaultInLocalStorage` and `assertNoDecryptedVaultInIndexedDB` are deprecated
|
|
180
|
+
boolean aliases that fail closed.
|
|
181
|
+
|
|
182
|
+
### Browser UX and passkey helpers
|
|
183
|
+
|
|
184
|
+
- `buildPrfSaltBytes(prefix, userId)`
|
|
185
|
+
- `createRecoveryKitDownload(content, filename)`
|
|
186
|
+
- `printRecoveryKitContent(content)`
|
|
187
|
+
- `extractPasskeyPrfOutput`, `isPasskeySupported`, `isPrfExtensionSupported`
|
|
188
|
+
- `createRecoveryKitText`, `buildRecoveryKitContent`
|
|
189
|
+
|
|
190
|
+
## React: `@tgoliveira/vault-core/react`
|
|
191
|
+
|
|
192
|
+
- `VaultSessionProvider` / `VaultSessionProviderProps`
|
|
193
|
+
- `useVaultSession(options)` / `UseVaultSessionOptions`
|
|
194
|
+
- `useVaultUnlocked()` / `useVaultLockState()`
|
|
195
|
+
- `resolveVaultClientStatus(status, unlocked, prfSupported)`
|
|
196
|
+
- `useVaultClientStatus(serverStatus, prfSupported)`
|
|
197
|
+
- `VaultClientStatus` / `VaultServerStatusSnapshot`
|
|
198
|
+
|
|
199
|
+
Provider and session hook guard options are `registerActivityGuard` and `registerUnloadGuard`, both
|
|
200
|
+
defaulting to `true`.
|
|
201
|
+
|
|
202
|
+
## Testing: `@tgoliveira/vault-core/testing`
|
|
203
|
+
|
|
204
|
+
This entry exports the plaintext validation functions, forbidden field list, `ALL_SENTINELS`, and all
|
|
205
|
+
named `SENTINEL_*` values. Use it in network, persistence, logging, and fixture tests. It does not
|
|
206
|
+
export internal LiqSense compatibility fixtures.
|
package/ARCHITECTURE.md
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
| Passkey PRF | PRF output → AES key | UVK |
|
|
14
14
|
|
|
15
15
|
Envelope AAD field: `vault_key` with app `aadContextEnvelope`.
|
|
16
|
+
Persisted envelopes are validated as a method-discriminated union, so password and recovery
|
|
17
|
+
envelopes require Argon2id metadata while passkey PRF envelopes require `null` KDF metadata.
|
|
16
18
|
|
|
17
19
|
## Encrypted payload
|
|
18
20
|
|
|
@@ -30,3 +32,6 @@ Format: `enc-v1` / `AES-GCM` / `kdf-v1`.
|
|
|
30
32
|
```
|
|
31
33
|
|
|
32
34
|
Apps own: persistence, routes, product UI, product payload schema, WebAuthn ceremony.
|
|
35
|
+
|
|
36
|
+
Browser and React session layers keep the UVK in memory, renew auto-lock on activity, and clear it on
|
|
37
|
+
lock or `pagehide`. Direct key mutation is not part of the public browser entry.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
|
|
6
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). Because the package is pre-1.0, breaking
|
|
7
|
+
API changes increment the minor version.
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
## [0.2.0] - 2026-06-19
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Per-file coverage enforcement at 90% for statements, branches, functions, and lines.
|
|
16
|
+
- Method-specific Zod schemas for password, recovery phrase, and passkey PRF envelopes.
|
|
17
|
+
- Explicit `clear`, `found`, and `unavailable` storage namespace inspection results.
|
|
18
|
+
- Automatic browser activity tracking for React session hooks and providers.
|
|
19
|
+
- Complete implementation, adoption, security, and release documentation for humans and agents.
|
|
20
|
+
- Continuous validation for pull requests and pushes, including the per-file coverage gate.
|
|
21
|
+
- Manually dispatched npm publication with changelog-based automatic versioning, provenance, release
|
|
22
|
+
commits, generated Git tags, and GitHub release notes.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **Breaking:** high-level payload decrypt and envelope unlock APIs now require the expected AAD
|
|
27
|
+
scope and crypto profile.
|
|
28
|
+
- **Breaking:** direct session-key mutation helpers are no longer exported from the browser entry.
|
|
29
|
+
- **Breaking:** recovery word confirmation now requires every expected answer.
|
|
30
|
+
- Deprecated boolean storage checks now fail closed when inspection is unavailable.
|
|
31
|
+
- The published package now includes the complete `docs` directory and this changelog.
|
|
32
|
+
|
|
33
|
+
### Security
|
|
34
|
+
|
|
35
|
+
- Plaintext request guards now inspect nested objects and arrays with cycle protection.
|
|
36
|
+
- Persisted Argon2id parameters, salt sizes, and hash length are bounded before derivation.
|
|
37
|
+
- High-level decrypt and unlock operations reject AAD belonging to another user, resource, field,
|
|
38
|
+
or application context.
|
|
39
|
+
- Envelope schemas reject invalid method and KDF metadata combinations.
|
|
40
|
+
|
|
41
|
+
## [0.1.1] - 2026-06-18
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- Initial public release of `@tgoliveira/vault-core`.
|
|
46
|
+
- AES-GCM payload encryption with canonical AAD.
|
|
47
|
+
- Password and BIP39 recovery phrase envelopes using Argon2id.
|
|
48
|
+
- Passkey PRF envelope primitives.
|
|
49
|
+
- Browser in-memory session and React integration helpers.
|
|
50
|
+
- Plaintext rejection and sentinel-based testing utilities.
|
|
51
|
+
- LiqSense compatibility fixtures and migration aliases.
|
|
@@ -17,11 +17,13 @@ export const LIQSENSE_PRF_SALT_PREFIX = "liqsense-passkey-prf-v1:";
|
|
|
17
17
|
| --- | --- |
|
|
18
18
|
| `generateUserVaultKey` | `createUserVaultKey` |
|
|
19
19
|
| `wrapVaultKeyForPassword` | `createPasswordEnvelope` + profile |
|
|
20
|
-
| `unwrapVaultKeyFromPassword` | `unlockWithPasswordEnvelope` |
|
|
20
|
+
| `unwrapVaultKeyFromPassword` | `unlockWithPasswordEnvelope(password, envelope, expectedScope, profile)` |
|
|
21
21
|
| `wrapVaultKeyForRecoveryPhrase` | `createRecoveryEnvelope` + profile |
|
|
22
22
|
| `generateRecoveryPhrase` | `createRecoveryPhrase` |
|
|
23
23
|
|
|
24
24
|
LiqSense keeps thin wrappers in `src/modules/vault/core/` binding `LIQSENSE_VAULT_PROFILE`.
|
|
25
|
+
High-level decrypt and unlock calls must also bind the expected scope so authenticated AAD cannot be
|
|
26
|
+
replayed under another user or resource.
|
|
25
27
|
|
|
26
28
|
## Local dev
|
|
27
29
|
|
package/PASSKEY_PRF_ENVELOPES.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
- Separate from account passkey login
|
|
4
4
|
- App provides PRF output bytes (≥ 32 bytes) from WebAuthn ceremony
|
|
5
5
|
- Package wraps UVK with PRF-derived AES key
|
|
6
|
-
- API: `createPasskeyPrfEnvelope` / `unlockWithPasskeyPrfEnvelope`
|
|
6
|
+
- API: `createPasskeyPrfEnvelope(vaultKey, prfOutput, scope, profile)` / `unlockWithPasskeyPrfEnvelope(envelope, prfOutput, expectedScope, profile)`
|
|
7
|
+
- Unlock rejects envelopes whose authenticated AAD does not match the expected scope and profile
|
|
7
8
|
- Browser helpers: `buildPrfSaltBytes(prefix, userId)`, capability probes
|
|
8
9
|
|
|
9
10
|
PRF output never sent to server. WebAuthn ceremony stays in the app.
|
package/PASSWORD_ENVELOPES.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
- Vault password normalized with NFKC before Argon2id
|
|
4
4
|
- Default params: memory 65536 KiB, iterations 3, parallelism 1, 32-byte hash, 16-byte salt
|
|
5
|
-
-
|
|
5
|
+
- Persisted Argon2id parameters are bounded before derivation to prevent client-side resource exhaustion
|
|
6
|
+
- API: `createPasswordEnvelope(vaultKey, password, scope, profile)` / `unlockWithPasswordEnvelope(password, envelope, expectedScope, profile)`
|
|
7
|
+
- Unlock rejects envelopes whose authenticated AAD does not match the expected scope and profile
|
|
6
8
|
|
|
7
9
|
Vault password never sent to server.
|
package/README.md
CHANGED
|
@@ -29,6 +29,26 @@ Build vault-core before consuming:
|
|
|
29
29
|
cd ../vault-core && npm run validate
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## Testing
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm test
|
|
36
|
+
npm run test:coverage
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Coverage is enforced per production file at 90% for statements, branches, functions, and lines.
|
|
40
|
+
`npm run validate` includes the coverage gate.
|
|
41
|
+
|
|
42
|
+
## Documentation
|
|
43
|
+
|
|
44
|
+
- [Complete implementation guide](docs/IMPLEMENTATION_GUIDE.md)
|
|
45
|
+
- [Documentation index](docs/README.md)
|
|
46
|
+
- [API reference](API_REFERENCE.md)
|
|
47
|
+
- [Security model](SECURITY.md)
|
|
48
|
+
- [Changelog](CHANGELOG.md)
|
|
49
|
+
- [Release process](docs/RELEASING.md)
|
|
50
|
+
- [Agent and contributor guide](AGENTS.md)
|
|
51
|
+
|
|
32
52
|
## Quick start
|
|
33
53
|
|
|
34
54
|
```ts
|
|
@@ -57,14 +77,33 @@ const { envelope } = await createPasswordEnvelope(
|
|
|
57
77
|
scope,
|
|
58
78
|
profile
|
|
59
79
|
);
|
|
80
|
+
|
|
81
|
+
const encryptedPayload = await encryptVaultPayload(
|
|
82
|
+
{ version: 1, entries: [] },
|
|
83
|
+
vaultKey,
|
|
84
|
+
scope,
|
|
85
|
+
profile
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const unlockedKey = await unlockWithPasswordEnvelope(
|
|
89
|
+
userVaultPassword,
|
|
90
|
+
envelope,
|
|
91
|
+
scope,
|
|
92
|
+
profile
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const payload = await decryptVaultPayload(encryptedPayload, unlockedKey, scope, profile);
|
|
60
96
|
```
|
|
61
97
|
|
|
98
|
+
High-level decrypt and unlock APIs require the expected scope and profile. This binds authenticated
|
|
99
|
+
AAD to the user, resource, field, and application context expected by the caller.
|
|
100
|
+
|
|
62
101
|
## Exports
|
|
63
102
|
|
|
64
103
|
| Entry | Purpose |
|
|
65
104
|
| --- | --- |
|
|
66
105
|
| `@tgoliveira/vault-core` | Core crypto, envelopes, payload, validation |
|
|
67
|
-
| `@tgoliveira/vault-core/browser` | In-memory session, auto-lock, PRF salt, recovery kit DOM helpers |
|
|
106
|
+
| `@tgoliveira/vault-core/browser` | In-memory session, activity-aware auto-lock, storage inspection, PRF salt, recovery kit DOM helpers |
|
|
68
107
|
| `@tgoliveira/vault-core/testing` | Sentinels and plaintext scan helpers |
|
|
69
108
|
| `@tgoliveira/vault-core/react` | Headless React session/status hooks (optional peer: `react`) |
|
|
70
109
|
|
|
@@ -74,5 +113,6 @@ const { envelope } = await createPasswordEnvelope(
|
|
|
74
113
|
- Does **not** require React, Next.js, or product payload schemas on the default entry
|
|
75
114
|
- `./react` is optional and requires `react >= 18`
|
|
76
115
|
- Vault password, recovery phrase, UVK, PRF output, and decrypted payload must stay client-side
|
|
116
|
+
- Persisted envelope schemas enforce method-specific KDF metadata at runtime
|
|
77
117
|
|
|
78
|
-
See `SECURITY.md`, `ARCHITECTURE.md`, and `
|
|
118
|
+
See `SECURITY.md`, `ARCHITECTURE.md`, `MIGRATION_FROM_LIQSENSE.md`, and [`docs/ADOPTING_VAULT_CORE_IN_EXISTING_APPS.md`](docs/ADOPTING_VAULT_CORE_IN_EXISTING_APPS.md) for migrating other apps (including [letter-to-god](https://github.com/tgoliveira11/letter-to-god)).
|
package/RECOVERY_PHRASE.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
- BIP39 English wordlist via `@scure/bip39`
|
|
4
4
|
- Supported lengths: **12 words** (128-bit) and **24 words** (256-bit, default)
|
|
5
5
|
- Normalization: trim, lowercase, single-space separated
|
|
6
|
-
- Confirmation helpers: deterministic word indices (3 for 12 words, 4 for 24)
|
|
6
|
+
- Confirmation helpers: deterministic word indices (3 for 12 words, 4 for 24); every requested answer is required
|
|
7
7
|
- Recovery kit text via `createRecoveryKitText({ productName, ... })`
|
|
8
|
+
- Envelope unlock requires the expected scope and profile and validates authenticated AAD before KDF work
|
|
8
9
|
|
|
9
10
|
Recovery phrase never sent to server.
|
package/SECURITY.md
CHANGED
|
@@ -14,18 +14,38 @@ Vault unlock requires a separate vault password, recovery phrase, or passkey PRF
|
|
|
14
14
|
- PRF output
|
|
15
15
|
- Decrypted vault payload
|
|
16
16
|
|
|
17
|
-
Use `assertNoVaultPlaintextFields()` on API request bodies.
|
|
17
|
+
Use `assertNoVaultPlaintextFields()` on API request bodies. The guard recursively checks nested
|
|
18
|
+
objects and arrays and safely handles cyclic in-memory objects.
|
|
18
19
|
|
|
19
20
|
## Client must never persist
|
|
20
21
|
|
|
21
22
|
- Decrypted vault payload in localStorage or IndexedDB
|
|
22
23
|
|
|
23
|
-
Browser session helpers clear UVK on lock and `pagehide`.
|
|
24
|
+
Browser session helpers clear UVK on lock and `pagehide`. React session helpers also renew the
|
|
25
|
+
inactivity timer on pointer, keyboard, touch, and focus activity by default. Public browser exports
|
|
26
|
+
do not expose direct session-key setters; use `unlockVaultSession()` and `lockVaultSession()` so
|
|
27
|
+
timers and subscribers remain consistent.
|
|
28
|
+
|
|
29
|
+
`inspectLocalStoragePrefix()` and `inspectIndexedDBPrefix()` are namespace inspections, not content
|
|
30
|
+
scanners. They return `"unavailable"` when inspection is blocked or unsupported. Treat that result
|
|
31
|
+
as a failed security check. IndexedDB inspection checks database names and cannot prove that records
|
|
32
|
+
inside an unrelated database contain no plaintext.
|
|
24
33
|
|
|
25
34
|
## Crypto constants (per app profile)
|
|
26
35
|
|
|
27
36
|
Apps define `VaultCryptoProfile` with stable AAD contexts. Existing ciphertext breaks if contexts change.
|
|
28
37
|
|
|
38
|
+
High-level decrypt and envelope-unlock APIs require the expected scope and profile. They reject a
|
|
39
|
+
valid ciphertext when its authenticated AAD belongs to a different user, resource, field, or app
|
|
40
|
+
context. Treat `decryptField()` as a low-level compatibility primitive: callers that use it directly
|
|
41
|
+
must validate the expected AAD separately.
|
|
42
|
+
|
|
43
|
+
## Untrusted persisted data
|
|
44
|
+
|
|
45
|
+
Treat encrypted payloads, envelopes, AAD, and KDF metadata loaded from a server or local storage as
|
|
46
|
+
untrusted. Argon2id metadata is bounded before derivation to prevent excessive client memory or CPU
|
|
47
|
+
consumption. Do not bypass the high-level APIs or their runtime validation for persisted data.
|
|
48
|
+
|
|
29
49
|
## Logging
|
|
30
50
|
|
|
31
51
|
Never log vault secrets, request bodies containing envelopes, or decrypted payloads.
|
package/dist/browser.d.ts
CHANGED
|
@@ -2,10 +2,21 @@ import { extractPasskeyPrfOutput, isPasskeySupported, isPrfExtensionSupported }
|
|
|
2
2
|
export declare function buildPrfSaltBytes(prefix: string, userId: string): Promise<ArrayBuffer>;
|
|
3
3
|
export declare function createRecoveryKitDownload(content: string, filename: string): void;
|
|
4
4
|
export declare function printRecoveryKitContent(content: string): void;
|
|
5
|
+
export type VaultStorageInspectionResult = "clear" | "found" | "unavailable";
|
|
6
|
+
export declare function inspectLocalStoragePrefix(storagePrefix: string): VaultStorageInspectionResult;
|
|
7
|
+
export declare function inspectIndexedDBPrefix(storagePrefix: string): Promise<VaultStorageInspectionResult>;
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated This is a namespace-level, fail-closed check. Use inspectLocalStoragePrefix
|
|
10
|
+
* and handle all three result states explicitly.
|
|
11
|
+
*/
|
|
5
12
|
export declare function assertNoDecryptedVaultInLocalStorage(storagePrefix: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated This checks database names, not record contents. Use inspectIndexedDBPrefix
|
|
15
|
+
* and handle all three result states explicitly.
|
|
16
|
+
*/
|
|
6
17
|
export declare function assertNoDecryptedVaultInIndexedDB(storagePrefix: string): Promise<boolean>;
|
|
7
18
|
export declare function persistVaultRecordLocally(): never;
|
|
8
19
|
export { extractPasskeyPrfOutput, isPasskeySupported, isPrfExtensionSupported, };
|
|
9
|
-
export { configureVaultSession, subscribeVaultSession, isVaultManuallyLocked, clearVaultAutoLockTimer, scheduleVaultAutoLock, touchVaultSession, unlockVaultSession, lockVaultSession, lockVaultSessionManually, resetVaultSessionLockState, registerVaultUnloadGuard, getVaultAutoLockRemainingMs, getSessionVaultKey,
|
|
20
|
+
export { configureVaultSession, subscribeVaultSession, isVaultManuallyLocked, clearVaultAutoLockTimer, scheduleVaultAutoLock, touchVaultSession, unlockVaultSession, lockVaultSession, lockVaultSessionManually, resetVaultSessionLockState, registerVaultUnloadGuard, registerVaultActivityGuard, getVaultAutoLockRemainingMs, getSessionVaultKey, isVaultUnlocked, type VaultSessionConfig, } from "./session/auto-lock.js";
|
|
10
21
|
export { createRecoveryKitText, buildRecoveryKitContent } from "./recovery/kit.js";
|
|
11
22
|
//# sourceMappingURL=browser.d.ts.map
|
package/dist/browser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AACA,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,4BAA4B,CAAC;AAEpC,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAG5F;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,IAAI,CASN;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAU7D;AASD,wBAAgB,oCAAoC,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AACA,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,4BAA4B,CAAC;AAEpC,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAG5F;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,IAAI,CASN;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAU7D;AASD,MAAM,MAAM,4BAA4B,GAAG,OAAO,GAAG,OAAO,GAAG,aAAa,CAAC;AAE7E,wBAAgB,yBAAyB,CACvC,aAAa,EAAE,MAAM,GACpB,4BAA4B,CAiB9B;AAED,wBAAsB,sBAAsB,CAC1C,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,4BAA4B,CAAC,CAwBvC;AAED;;;GAGG;AACH,wBAAgB,oCAAoC,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAEnF;AAED;;;GAGG;AACH,wBAAsB,iCAAiC,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAE/F;AAED,wBAAgB,yBAAyB,IAAI,KAAK,CAEjD;AAED,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,uBAAuB,GACxB,CAAC;AAEF,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,0BAA0B,EAC1B,wBAAwB,EACxB,0BAA0B,EAC1B,2BAA2B,EAC3B,kBAAkB,EAClB,eAAe,EACf,KAAK,kBAAkB,GACxB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/browser.js
CHANGED
|
@@ -32,40 +32,68 @@ function escapeHtml(value) {
|
|
|
32
32
|
.replace(/</g, "<")
|
|
33
33
|
.replace(/>/g, ">");
|
|
34
34
|
}
|
|
35
|
-
export function
|
|
36
|
-
if (typeof window === "undefined")
|
|
37
|
-
return
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
export function inspectLocalStoragePrefix(storagePrefix) {
|
|
36
|
+
if (typeof window === "undefined" || typeof localStorage === "undefined") {
|
|
37
|
+
return "unavailable";
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
41
|
+
const key = localStorage.key(i);
|
|
42
|
+
if (!key)
|
|
43
|
+
continue;
|
|
44
|
+
if (key.startsWith(storagePrefix)) {
|
|
45
|
+
return "found";
|
|
46
|
+
}
|
|
44
47
|
}
|
|
48
|
+
return "clear";
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return "unavailable";
|
|
45
52
|
}
|
|
46
|
-
return true;
|
|
47
53
|
}
|
|
48
|
-
export async function
|
|
49
|
-
if (typeof window === "undefined" || typeof indexedDB === "undefined")
|
|
50
|
-
return
|
|
54
|
+
export async function inspectIndexedDBPrefix(storagePrefix) {
|
|
55
|
+
if (typeof window === "undefined" || typeof indexedDB === "undefined") {
|
|
56
|
+
return "unavailable";
|
|
57
|
+
}
|
|
51
58
|
return new Promise((resolve) => {
|
|
52
|
-
|
|
59
|
+
let request;
|
|
60
|
+
try {
|
|
61
|
+
request = indexedDB.databases?.();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
resolve("unavailable");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
53
67
|
if (!request) {
|
|
54
|
-
resolve(
|
|
68
|
+
resolve("unavailable");
|
|
55
69
|
return;
|
|
56
70
|
}
|
|
57
71
|
void request
|
|
58
72
|
.then((databases) => {
|
|
59
73
|
const hasVaultDb = databases.some((db) => db.name?.startsWith(storagePrefix));
|
|
60
|
-
resolve(
|
|
74
|
+
resolve(hasVaultDb ? "found" : "clear");
|
|
61
75
|
})
|
|
62
|
-
.catch(() => resolve(
|
|
76
|
+
.catch(() => resolve("unavailable"));
|
|
63
77
|
});
|
|
64
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* @deprecated This is a namespace-level, fail-closed check. Use inspectLocalStoragePrefix
|
|
81
|
+
* and handle all three result states explicitly.
|
|
82
|
+
*/
|
|
83
|
+
export function assertNoDecryptedVaultInLocalStorage(storagePrefix) {
|
|
84
|
+
return inspectLocalStoragePrefix(storagePrefix) === "clear";
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* @deprecated This checks database names, not record contents. Use inspectIndexedDBPrefix
|
|
88
|
+
* and handle all three result states explicitly.
|
|
89
|
+
*/
|
|
90
|
+
export async function assertNoDecryptedVaultInIndexedDB(storagePrefix) {
|
|
91
|
+
return (await inspectIndexedDBPrefix(storagePrefix)) === "clear";
|
|
92
|
+
}
|
|
65
93
|
export function persistVaultRecordLocally() {
|
|
66
94
|
throw new Error("Decrypted vault state must not be persisted to localStorage or IndexedDB");
|
|
67
95
|
}
|
|
68
96
|
export { extractPasskeyPrfOutput, isPasskeySupported, isPrfExtensionSupported, };
|
|
69
|
-
export { configureVaultSession, subscribeVaultSession, isVaultManuallyLocked, clearVaultAutoLockTimer, scheduleVaultAutoLock, touchVaultSession, unlockVaultSession, lockVaultSession, lockVaultSessionManually, resetVaultSessionLockState, registerVaultUnloadGuard, getVaultAutoLockRemainingMs, getSessionVaultKey,
|
|
97
|
+
export { configureVaultSession, subscribeVaultSession, isVaultManuallyLocked, clearVaultAutoLockTimer, scheduleVaultAutoLock, touchVaultSession, unlockVaultSession, lockVaultSession, lockVaultSessionManually, resetVaultSessionLockState, registerVaultUnloadGuard, registerVaultActivityGuard, getVaultAutoLockRemainingMs, getSessionVaultKey, isVaultUnlocked, } from "./session/auto-lock.js";
|
|
70
98
|
export { createRecoveryKitText, buildRecoveryKitContent } from "./recovery/kit.js";
|
|
71
99
|
//# sourceMappingURL=browser.js.map
|