@alikhalilll/nuxt-crypto 1.0.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/README.md +306 -0
- package/dist/chunk-EFZPSZWO.mjs +1 -0
- package/dist/chunk-EFZPSZWO.mjs.map +1 -0
- package/dist/chunk-HNTEUBQ7.mjs +199 -0
- package/dist/chunk-HNTEUBQ7.mjs.map +1 -0
- package/dist/core/index.d.ts +50 -0
- package/dist/core/index.mjs +23 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/module.d.ts +7 -0
- package/dist/module.mjs +89 -0
- package/dist/module.mjs.map +1 -0
- package/dist/payload-Cht5Ar0K.d.ts +85 -0
- package/dist/runtime/plugin.mjs +29 -0
- package/dist/runtime/plugin.mjs.map +1 -0
- package/dist/types/index.d.ts +30 -0
- package/dist/types/index.mjs +2 -0
- package/dist/types/index.mjs.map +1 -0
- package/package.json +75 -0
package/README.md
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# @alikhalilll/nuxt-crypto
|
|
2
|
+
|
|
3
|
+
Symmetric encryption for Nuxt 3 / 4, built on the native **Web Crypto API**. Defaults to **AES-256-GCM** with **PBKDF2-SHA256** key derivation.
|
|
4
|
+
|
|
5
|
+
- **Framework-agnostic core** — `createCryptoService()` works anywhere `SubtleCrypto` exists (browser, Deno, Bun, Node 20+).
|
|
6
|
+
- **Key caching** — derived keys are cached per salt, so bulk decrypt stays fast even at 100k+ PBKDF2 iterations.
|
|
7
|
+
- **Pluggable algorithms** — swap the default AES-GCM implementation for your own `CryptoAlgorithm` without touching the payload envelope.
|
|
8
|
+
- **Versioned payload format** — `v1.{salt}.{iv}.{cipher}` with clean forward compatibility.
|
|
9
|
+
- **Server-only mode** — opt into registering the plugin only on the server so the passphrase never ships to the browser bundle.
|
|
10
|
+
- **Strong types** — `CryptoService`, `CryptoServiceConfig`, and `CryptoAlgorithm` all exported.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Table of contents
|
|
15
|
+
|
|
16
|
+
1. [Install](#install)
|
|
17
|
+
2. [Register the module](#register-the-module)
|
|
18
|
+
3. [Usage](#usage)
|
|
19
|
+
- [Basic round-trip](#basic-round-trip)
|
|
20
|
+
- [Encrypt / decrypt a JSON object](#encrypt--decrypt-a-json-object)
|
|
21
|
+
- [Encrypt / decrypt an arbitrary value](#encrypt--decrypt-an-arbitrary-value)
|
|
22
|
+
- [Persist a ciphertext in a cookie](#persist-a-ciphertext-in-a-cookie)
|
|
23
|
+
- [Bulk encrypt / decrypt (key cache benefits)](#bulk-encrypt--decrypt-key-cache-benefits)
|
|
24
|
+
- [Clear the key cache](#clear-the-key-cache)
|
|
25
|
+
- [Error handling](#error-handling)
|
|
26
|
+
4. [Server-only mode](#server-only-mode)
|
|
27
|
+
5. [Nitro / API routes](#nitro--api-routes)
|
|
28
|
+
6. [Framework-agnostic core](#framework-agnostic-core)
|
|
29
|
+
7. [Custom algorithm](#custom-algorithm)
|
|
30
|
+
8. [Rotating the passphrase (re-encryption)](#rotating-the-passphrase-re-encryption)
|
|
31
|
+
9. [Payload format](#payload-format)
|
|
32
|
+
10. [Module options reference](#module-options-reference)
|
|
33
|
+
11. [Exported types](#exported-types)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm add @alikhalilll/nuxt-crypto
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Register the module
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
// nuxt.config.ts
|
|
47
|
+
export default defineNuxtConfig({
|
|
48
|
+
modules: ['@alikhalilll/nuxt-crypto'],
|
|
49
|
+
crypto: {
|
|
50
|
+
// Always wire this from an env var. Never commit the passphrase.
|
|
51
|
+
passphrase: process.env.NUXT_ENCRYPTION_PASSPHRASE ?? '',
|
|
52
|
+
provideName: '$crypto', // leading "$" optional
|
|
53
|
+
iterations: 100_000,
|
|
54
|
+
keyCacheSize: 64,
|
|
55
|
+
serverOnly: false,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`.env` (not committed):
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
NUXT_ENCRYPTION_PASSPHRASE=replace-me-with-a-long-random-secret
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
### Basic round-trip
|
|
69
|
+
|
|
70
|
+
```vue
|
|
71
|
+
<script setup lang="ts">
|
|
72
|
+
const { $crypto } = useNuxtApp();
|
|
73
|
+
|
|
74
|
+
const payload = await $crypto.encrypt('super-secret');
|
|
75
|
+
// payload: "v1.<saltB64>.<ivB64>.<cipherB64>"
|
|
76
|
+
|
|
77
|
+
const plain = await $crypto.decrypt(payload);
|
|
78
|
+
</script>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`$crypto` is typed as `CryptoService` via auto-generated module augmentation.
|
|
82
|
+
|
|
83
|
+
### Encrypt / decrypt a JSON object
|
|
84
|
+
|
|
85
|
+
`encrypt` takes a string, so stringify structured data first:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
const session = { userId: 42, roles: ['admin'], issuedAt: Date.now() };
|
|
89
|
+
|
|
90
|
+
const payload = await $crypto.encrypt(JSON.stringify(session));
|
|
91
|
+
|
|
92
|
+
const restored = JSON.parse(await $crypto.decrypt(payload)) as typeof session;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
A tiny helper makes this ergonomic:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// composables/useEncrypted.ts
|
|
99
|
+
export function useEncrypted() {
|
|
100
|
+
const { $crypto } = useNuxtApp();
|
|
101
|
+
return {
|
|
102
|
+
encode: async <T>(value: T) => $crypto.encrypt(JSON.stringify(value)),
|
|
103
|
+
decode: async <T>(payload: string) => JSON.parse(await $crypto.decrypt(payload)) as T,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Encrypt / decrypt an arbitrary value
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const { encode, decode } = useEncrypted();
|
|
112
|
+
|
|
113
|
+
const token = await encode({ refresh: 'abc123', exp: Date.now() + 3_600_000 });
|
|
114
|
+
const { refresh, exp } = await decode<{ refresh: string; exp: number }>(token);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Persist a ciphertext in a cookie
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
const cookie = useCookie<string | null>('session');
|
|
121
|
+
|
|
122
|
+
// Write
|
|
123
|
+
cookie.value = await $crypto.encrypt(JSON.stringify({ userId: 42 }));
|
|
124
|
+
|
|
125
|
+
// Read
|
|
126
|
+
if (cookie.value) {
|
|
127
|
+
const { userId } = JSON.parse(await $crypto.decrypt(cookie.value));
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Bulk encrypt / decrypt (key cache benefits)
|
|
132
|
+
|
|
133
|
+
PBKDF2 is deliberately slow. The built-in key cache (default size 64) keys derived material by salt, so a second decrypt of any payload you've already touched is essentially free:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
const items = Array.from({ length: 100 }, (_, i) => `item-${i}`);
|
|
137
|
+
|
|
138
|
+
const payloads = await Promise.all(items.map((i) => $crypto.encrypt(i)));
|
|
139
|
+
|
|
140
|
+
// Fast — each salt was just derived during encrypt, so decrypt hits the cache.
|
|
141
|
+
const plain = await Promise.all(payloads.map((p) => $crypto.decrypt(p)));
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Clear the key cache
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
$crypto.clearKeyCache();
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Useful when:
|
|
151
|
+
- You rotate the passphrase (the cache would still hold old keys).
|
|
152
|
+
- You want to force re-derivation in a security-audit test.
|
|
153
|
+
|
|
154
|
+
### Error handling
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
try {
|
|
158
|
+
await $crypto.decrypt('not-a-real-payload');
|
|
159
|
+
} catch (e) {
|
|
160
|
+
// '[nuxt-crypto] Invalid payload format — expected 4 dot-separated segments.'
|
|
161
|
+
console.error((e as Error).message);
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Errors you can see:
|
|
166
|
+
|
|
167
|
+
| Scenario | Message |
|
|
168
|
+
|-----------------------------------------------|---------|
|
|
169
|
+
| Payload isn't `a.b.c.d` | `Invalid payload format — expected 4 dot-separated segments.` |
|
|
170
|
+
| A segment is empty | `Invalid payload format — one or more segments were empty.` |
|
|
171
|
+
| Algorithm version mismatch | `Unsupported payload version: v2 (algorithm expects v1).` |
|
|
172
|
+
| Wrong passphrase / tampered ciphertext | Native `OperationError` from Web Crypto. |
|
|
173
|
+
| Passphrase not set in module config | `[nuxt-crypto] passphrase is required.` |
|
|
174
|
+
|
|
175
|
+
## Server-only mode
|
|
176
|
+
|
|
177
|
+
Set `serverOnly: true` in module options to skip the client plugin entirely — the passphrase never ends up in the browser bundle.
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
crypto: {
|
|
181
|
+
passphrase: process.env.NUXT_ENCRYPTION_PASSPHRASE ?? '',
|
|
182
|
+
serverOnly: true,
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
With this enabled, `$crypto` is `undefined` on the client. Use it only in:
|
|
187
|
+
- Nitro server routes (`server/api/*.ts`)
|
|
188
|
+
- Server-only plugins
|
|
189
|
+
- `<script setup>` blocks guarded by `import.meta.server`
|
|
190
|
+
|
|
191
|
+
## Nitro / API routes
|
|
192
|
+
|
|
193
|
+
Encrypt data before sending it to the browser, decrypt it when it comes back:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
// server/api/session/encode.post.ts
|
|
197
|
+
export default defineEventHandler(async (event) => {
|
|
198
|
+
const body = await readBody(event);
|
|
199
|
+
const { $crypto } = useNuxtApp();
|
|
200
|
+
return { token: await $crypto.encrypt(JSON.stringify(body)) };
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// server/api/session/decode.post.ts
|
|
204
|
+
export default defineEventHandler(async (event) => {
|
|
205
|
+
const { token } = await readBody(event);
|
|
206
|
+
const { $crypto } = useNuxtApp();
|
|
207
|
+
return JSON.parse(await $crypto.decrypt(token));
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Framework-agnostic core
|
|
212
|
+
|
|
213
|
+
Everything the Nuxt plugin wraps is available as a plain factory:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { createCryptoService } from '@alikhalilll/nuxt-crypto/core';
|
|
217
|
+
|
|
218
|
+
const service = await createCryptoService({
|
|
219
|
+
passphrase: process.env.ENC_PASS!,
|
|
220
|
+
iterations: 100_000,
|
|
221
|
+
keyCacheSize: 64,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const payload = await service.encrypt('hi');
|
|
225
|
+
const clear = await service.decrypt(payload);
|
|
226
|
+
service.clearKeyCache();
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Works in Node 20+, Bun, Deno, or any browser. Passing a custom `subtle: SubtleCrypto` is supported for testing.
|
|
230
|
+
|
|
231
|
+
## Custom algorithm
|
|
232
|
+
|
|
233
|
+
Replace AES-GCM with any cipher by implementing `CryptoAlgorithm`. The payload envelope (`{version}.{salt}.{iv}.{cipher}`) is preserved, and the version tag routes decrypt to the right algorithm.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
import { createCryptoService } from '@alikhalilll/nuxt-crypto/core';
|
|
237
|
+
import type { CryptoAlgorithm } from '@alikhalilll/nuxt-crypto/types';
|
|
238
|
+
|
|
239
|
+
const myAlgo: CryptoAlgorithm = {
|
|
240
|
+
version: 'v2',
|
|
241
|
+
async deriveKey({ subtle, passphrase, salt, iterations }) { /* ... */ },
|
|
242
|
+
async encrypt({ subtle, key, plainText }) { /* ... */ },
|
|
243
|
+
async decrypt({ subtle, key, cipher, iv }) { /* ... */ },
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const service = await createCryptoService({
|
|
247
|
+
passphrase: 'p4ss',
|
|
248
|
+
algorithm: myAlgo,
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Old v1 payloads will cleanly fail decrypt under a v2 service (the version check throws). Build a version router yourself if you need mixed-version reads during a rotation.
|
|
253
|
+
|
|
254
|
+
## Rotating the passphrase (re-encryption)
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import { createCryptoService } from '@alikhalilll/nuxt-crypto/core';
|
|
258
|
+
|
|
259
|
+
const oldService = await createCryptoService({ passphrase: OLD_PASS });
|
|
260
|
+
const newService = await createCryptoService({ passphrase: NEW_PASS });
|
|
261
|
+
|
|
262
|
+
async function rotate(payload: string): Promise<string> {
|
|
263
|
+
const plain = await oldService.decrypt(payload);
|
|
264
|
+
return newService.encrypt(plain);
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Run `rotate` over your stored ciphertexts during a background migration. Clear the key cache on whichever service goes out of use.
|
|
269
|
+
|
|
270
|
+
## Payload format
|
|
271
|
+
|
|
272
|
+
`v1.{saltB64}.{ivB64}.{cipherB64}` — four dot-separated segments, each standard base64:
|
|
273
|
+
|
|
274
|
+
| Segment | Bytes | Notes |
|
|
275
|
+
|---------|-------|-------|
|
|
276
|
+
| `v1` | — | Algorithm / version tag. |
|
|
277
|
+
| salt | 16 | Per-encryption PBKDF2 salt. |
|
|
278
|
+
| iv | 12 | AES-GCM initialization vector. |
|
|
279
|
+
| cipher | N | Ciphertext + 16-byte GCM auth tag. |
|
|
280
|
+
|
|
281
|
+
## Module options reference
|
|
282
|
+
|
|
283
|
+
| Option | Type | Default | Purpose |
|
|
284
|
+
|----------------|----------|------------|----------------------------------------------------------------------|
|
|
285
|
+
| `passphrase` | `string` | `''` | Passphrase to derive the AES key from. Throws at use if empty. |
|
|
286
|
+
| `provideName` | `string` | `'$crypto'`| Injected under `$<name>`. Leading `$` is stripped. |
|
|
287
|
+
| `iterations` | `number` | `100_000` | PBKDF2 iteration count. |
|
|
288
|
+
| `keyCacheSize` | `number` | `64` | Max derived keys kept in memory. Set to 0 to disable caching. |
|
|
289
|
+
| `serverOnly` | `boolean`| `false` | When true, plugin runs only on the server. |
|
|
290
|
+
|
|
291
|
+
## Exported types
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
import type {
|
|
295
|
+
CryptoService,
|
|
296
|
+
CryptoServiceConfig,
|
|
297
|
+
CryptoAlgorithm,
|
|
298
|
+
CryptoModuleOptions,
|
|
299
|
+
Bytes,
|
|
300
|
+
ParsedPayload,
|
|
301
|
+
} from '@alikhalilll/nuxt-crypto/types';
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## License
|
|
305
|
+
|
|
306
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-EFZPSZWO.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// src/core/algorithms/aes-gcm.ts
|
|
2
|
+
var aesGcmPbkdf2 = {
|
|
3
|
+
version: "v1",
|
|
4
|
+
async deriveKey({ subtle, passphrase, salt, iterations }) {
|
|
5
|
+
const keyMaterial = await subtle.importKey(
|
|
6
|
+
"raw",
|
|
7
|
+
new TextEncoder().encode(passphrase),
|
|
8
|
+
{ name: "PBKDF2" },
|
|
9
|
+
false,
|
|
10
|
+
["deriveKey"]
|
|
11
|
+
);
|
|
12
|
+
return subtle.deriveKey(
|
|
13
|
+
{ name: "PBKDF2", salt, iterations, hash: "SHA-256" },
|
|
14
|
+
keyMaterial,
|
|
15
|
+
{ name: "AES-GCM", length: 256 },
|
|
16
|
+
false,
|
|
17
|
+
["encrypt", "decrypt"]
|
|
18
|
+
);
|
|
19
|
+
},
|
|
20
|
+
async encrypt({ subtle, key, plainText }) {
|
|
21
|
+
const iv = new Uint8Array(new ArrayBuffer(12));
|
|
22
|
+
globalThis.crypto.getRandomValues(iv);
|
|
23
|
+
const buf = await subtle.encrypt({ name: "AES-GCM", iv }, key, plainText);
|
|
24
|
+
const cipher = new Uint8Array(new ArrayBuffer(buf.byteLength));
|
|
25
|
+
cipher.set(new Uint8Array(buf));
|
|
26
|
+
return { cipher, iv };
|
|
27
|
+
},
|
|
28
|
+
async decrypt({ subtle, key, cipher, iv }) {
|
|
29
|
+
const buf = await subtle.decrypt({ name: "AES-GCM", iv }, key, cipher);
|
|
30
|
+
const out = new Uint8Array(new ArrayBuffer(buf.byteLength));
|
|
31
|
+
out.set(new Uint8Array(buf));
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// src/core/base64.ts
|
|
37
|
+
function toBase64(data) {
|
|
38
|
+
if (typeof btoa === "function") {
|
|
39
|
+
let binary = "";
|
|
40
|
+
const CHUNK = 32768;
|
|
41
|
+
for (let i = 0; i < data.length; i += CHUNK) {
|
|
42
|
+
binary += String.fromCharCode(...data.subarray(i, i + CHUNK));
|
|
43
|
+
}
|
|
44
|
+
return btoa(binary);
|
|
45
|
+
}
|
|
46
|
+
return Buffer.from(data).toString("base64");
|
|
47
|
+
}
|
|
48
|
+
function fromBase64(input) {
|
|
49
|
+
if (typeof atob === "function") {
|
|
50
|
+
const binary = atob(input);
|
|
51
|
+
const out2 = new Uint8Array(binary.length);
|
|
52
|
+
for (let i = 0; i < binary.length; i++) out2[i] = binary.charCodeAt(i);
|
|
53
|
+
return out2;
|
|
54
|
+
}
|
|
55
|
+
const buf = Buffer.from(input, "base64");
|
|
56
|
+
const out = new Uint8Array(buf.length);
|
|
57
|
+
out.set(buf);
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/core/key-cache.ts
|
|
62
|
+
var KeyCache = class {
|
|
63
|
+
constructor(capacity) {
|
|
64
|
+
this.capacity = capacity;
|
|
65
|
+
}
|
|
66
|
+
capacity;
|
|
67
|
+
map = /* @__PURE__ */ new Map();
|
|
68
|
+
static key(salt, iterations) {
|
|
69
|
+
return `${iterations}.${toBase64(salt)}`;
|
|
70
|
+
}
|
|
71
|
+
get(key) {
|
|
72
|
+
const value = this.map.get(key);
|
|
73
|
+
if (!value) return void 0;
|
|
74
|
+
this.map.delete(key);
|
|
75
|
+
this.map.set(key, value);
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
set(key, value) {
|
|
79
|
+
if (this.capacity <= 0) return;
|
|
80
|
+
this.map.set(key, value);
|
|
81
|
+
while (this.map.size > this.capacity) {
|
|
82
|
+
const oldest = this.map.keys().next().value;
|
|
83
|
+
if (oldest === void 0) break;
|
|
84
|
+
this.map.delete(oldest);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
clear() {
|
|
88
|
+
this.map.clear();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// src/core/payload.ts
|
|
93
|
+
function encodePayload(parts) {
|
|
94
|
+
return [parts.version, toBase64(parts.salt), toBase64(parts.iv), toBase64(parts.cipher)].join(
|
|
95
|
+
"."
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
function parsePayload(payload) {
|
|
99
|
+
const segments = payload.split(".");
|
|
100
|
+
if (segments.length !== 4) {
|
|
101
|
+
throw new Error("[nuxt-crypto] Invalid payload format \u2014 expected 4 dot-separated segments.");
|
|
102
|
+
}
|
|
103
|
+
const [version, saltB64, ivB64, cipherB64] = segments;
|
|
104
|
+
if (!version || !saltB64 || !ivB64 || !cipherB64) {
|
|
105
|
+
throw new Error("[nuxt-crypto] Invalid payload format \u2014 one or more segments were empty.");
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
version,
|
|
109
|
+
salt: fromBase64(saltB64),
|
|
110
|
+
iv: fromBase64(ivB64),
|
|
111
|
+
cipher: fromBase64(cipherB64)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/core/subtle.ts
|
|
116
|
+
async function getSubtle() {
|
|
117
|
+
if (globalThis.crypto?.subtle) return globalThis.crypto.subtle;
|
|
118
|
+
const { webcrypto } = await import("crypto");
|
|
119
|
+
return webcrypto.subtle;
|
|
120
|
+
}
|
|
121
|
+
function getRandomBytes(length) {
|
|
122
|
+
const out = new Uint8Array(length);
|
|
123
|
+
const c = globalThis.crypto;
|
|
124
|
+
if (c?.getRandomValues) {
|
|
125
|
+
c.getRandomValues(out);
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
throw new Error(
|
|
129
|
+
"[nuxt-crypto] globalThis.crypto.getRandomValues is not available. Upgrade your runtime."
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/core/create-service.ts
|
|
134
|
+
var DEFAULT_ITERATIONS = 1e5;
|
|
135
|
+
var DEFAULT_CACHE = 64;
|
|
136
|
+
var SALT_BYTES = 16;
|
|
137
|
+
async function createCryptoService(config) {
|
|
138
|
+
if (!config.passphrase) {
|
|
139
|
+
throw new Error("[nuxt-crypto] passphrase is required.");
|
|
140
|
+
}
|
|
141
|
+
const iterations = config.iterations ?? DEFAULT_ITERATIONS;
|
|
142
|
+
const algorithm = config.algorithm ?? aesGcmPbkdf2;
|
|
143
|
+
const subtle = config.subtle ?? await getSubtle();
|
|
144
|
+
const cache = new KeyCache(config.keyCacheSize ?? DEFAULT_CACHE);
|
|
145
|
+
const passphrase = String(config.passphrase).trim();
|
|
146
|
+
const encoder = new TextEncoder();
|
|
147
|
+
const decoder = new TextDecoder();
|
|
148
|
+
async function getDerivedKey(salt) {
|
|
149
|
+
const key = KeyCache.key(salt, iterations);
|
|
150
|
+
const cached = cache.get(key);
|
|
151
|
+
if (cached) return cached;
|
|
152
|
+
const pending = algorithm.deriveKey({ subtle, passphrase, salt, iterations });
|
|
153
|
+
cache.set(key, pending);
|
|
154
|
+
return pending;
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
async encrypt(plainText) {
|
|
158
|
+
const salt = getRandomBytes(SALT_BYTES);
|
|
159
|
+
const key = await getDerivedKey(salt);
|
|
160
|
+
const encoded = encoder.encode(plainText);
|
|
161
|
+
const plainBytes = new Uint8Array(new ArrayBuffer(encoded.byteLength));
|
|
162
|
+
plainBytes.set(encoded);
|
|
163
|
+
const { cipher, iv } = await algorithm.encrypt({ subtle, key, plainText: plainBytes });
|
|
164
|
+
return encodePayload({ version: algorithm.version, salt, iv, cipher });
|
|
165
|
+
},
|
|
166
|
+
async decrypt(payload) {
|
|
167
|
+
const parsed = parsePayload(payload);
|
|
168
|
+
if (parsed.version !== algorithm.version) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`[nuxt-crypto] Unsupported payload version: ${parsed.version} (algorithm expects ${algorithm.version}).`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const key = await getDerivedKey(parsed.salt);
|
|
174
|
+
const plainBytes = await algorithm.decrypt({
|
|
175
|
+
subtle,
|
|
176
|
+
key,
|
|
177
|
+
cipher: parsed.cipher,
|
|
178
|
+
iv: parsed.iv
|
|
179
|
+
});
|
|
180
|
+
return decoder.decode(plainBytes);
|
|
181
|
+
},
|
|
182
|
+
clearKeyCache() {
|
|
183
|
+
cache.clear();
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export {
|
|
189
|
+
aesGcmPbkdf2,
|
|
190
|
+
toBase64,
|
|
191
|
+
fromBase64,
|
|
192
|
+
KeyCache,
|
|
193
|
+
encodePayload,
|
|
194
|
+
parsePayload,
|
|
195
|
+
getSubtle,
|
|
196
|
+
getRandomBytes,
|
|
197
|
+
createCryptoService
|
|
198
|
+
};
|
|
199
|
+
//# sourceMappingURL=chunk-HNTEUBQ7.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/algorithms/aes-gcm.ts","../src/core/base64.ts","../src/core/key-cache.ts","../src/core/payload.ts","../src/core/subtle.ts","../src/core/create-service.ts"],"sourcesContent":["import type { CryptoAlgorithm } from '../types';\n\n/**\n * AES-256-GCM with PBKDF2-SHA256 key derivation. This is the default and\n * currently the only built-in algorithm; pluggable via `CryptoServiceConfig.algorithm`.\n */\nexport const aesGcmPbkdf2: CryptoAlgorithm = {\n version: 'v1',\n\n async deriveKey({ subtle, passphrase, salt, iterations }) {\n const keyMaterial = await subtle.importKey(\n 'raw',\n new TextEncoder().encode(passphrase),\n { name: 'PBKDF2' },\n false,\n ['deriveKey']\n );\n\n return subtle.deriveKey(\n { name: 'PBKDF2', salt, iterations, hash: 'SHA-256' },\n keyMaterial,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt']\n );\n },\n\n async encrypt({ subtle, key, plainText }) {\n const iv = new Uint8Array(new ArrayBuffer(12));\n globalThis.crypto.getRandomValues(iv);\n const buf = await subtle.encrypt({ name: 'AES-GCM', iv }, key, plainText);\n const cipher = new Uint8Array(new ArrayBuffer(buf.byteLength));\n cipher.set(new Uint8Array(buf));\n return { cipher, iv };\n },\n\n async decrypt({ subtle, key, cipher, iv }) {\n const buf = await subtle.decrypt({ name: 'AES-GCM', iv }, key, cipher);\n const out = new Uint8Array(new ArrayBuffer(buf.byteLength));\n out.set(new Uint8Array(buf));\n return out;\n },\n};\n","import type { Bytes } from './types';\n\n/** Encode bytes to standard base64. Works in browser and Node. */\nexport function toBase64(data: Bytes): string {\n if (typeof btoa === 'function') {\n let binary = '';\n const CHUNK = 0x8000;\n for (let i = 0; i < data.length; i += CHUNK) {\n binary += String.fromCharCode(...data.subarray(i, i + CHUNK));\n }\n return btoa(binary);\n }\n // Node fallback — Buffer is globally available in Node.\n return Buffer.from(data).toString('base64');\n}\n\n/** Decode a base64 string back to bytes. Works in browser and Node. */\nexport function fromBase64(input: string): Bytes {\n if (typeof atob === 'function') {\n const binary = atob(input);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n }\n const buf = Buffer.from(input, 'base64');\n const out = new Uint8Array(buf.length);\n out.set(buf);\n return out;\n}\n","import type { Bytes } from './types';\nimport { toBase64 } from './base64';\n\n/**\n * A tiny LRU-ish cache for derived `CryptoKey` values, keyed on the salt.\n * When capacity is exceeded the oldest entry is evicted. Derived key\n * material is expensive (PBKDF2 is designed to be slow), so caching has\n * a big impact when decrypting many payloads in a row.\n */\nexport class KeyCache {\n private readonly map = new Map<string, Promise<CryptoKey>>();\n constructor(private readonly capacity: number) {}\n\n static key(salt: Bytes, iterations: number): string {\n return `${iterations}.${toBase64(salt)}`;\n }\n\n get(key: string): Promise<CryptoKey> | undefined {\n const value = this.map.get(key);\n if (!value) return undefined;\n // Refresh recency by re-inserting.\n this.map.delete(key);\n this.map.set(key, value);\n return value;\n }\n\n set(key: string, value: Promise<CryptoKey>): void {\n if (this.capacity <= 0) return;\n this.map.set(key, value);\n while (this.map.size > this.capacity) {\n const oldest = this.map.keys().next().value;\n if (oldest === undefined) break;\n this.map.delete(oldest);\n }\n }\n\n clear(): void {\n this.map.clear();\n }\n}\n","import { fromBase64, toBase64 } from './base64';\nimport type { Bytes } from './types';\n\n/**\n * Payload serialisation format (v1): `${version}.${saltB64}.${ivB64}.${cipherB64}`.\n *\n * Each part is URL-safe base64, all joined with `.`. The version tag is\n * always the first segment so consumers can route to the correct algorithm\n * during decrypt.\n */\nexport interface ParsedPayload {\n version: string;\n salt: Bytes;\n iv: Bytes;\n cipher: Bytes;\n}\n\nexport function encodePayload(parts: ParsedPayload): string {\n return [parts.version, toBase64(parts.salt), toBase64(parts.iv), toBase64(parts.cipher)].join(\n '.'\n );\n}\n\nexport function parsePayload(payload: string): ParsedPayload {\n const segments = payload.split('.');\n if (segments.length !== 4) {\n throw new Error('[nuxt-crypto] Invalid payload format — expected 4 dot-separated segments.');\n }\n const [version, saltB64, ivB64, cipherB64] = segments;\n if (!version || !saltB64 || !ivB64 || !cipherB64) {\n throw new Error('[nuxt-crypto] Invalid payload format — one or more segments were empty.');\n }\n return {\n version,\n salt: fromBase64(saltB64),\n iv: fromBase64(ivB64),\n cipher: fromBase64(cipherB64),\n };\n}\n","/**\n * Resolve a SubtleCrypto implementation across environments:\n *\n * - Browser / Deno / Bun / Node 20+: `globalThis.crypto.subtle` is available.\n * - Older Node: fall back to `node:crypto`'s `webcrypto`.\n */\nexport async function getSubtle(): Promise<SubtleCrypto> {\n if (globalThis.crypto?.subtle) return globalThis.crypto.subtle;\n const { webcrypto } = await import('node:crypto');\n return webcrypto.subtle as unknown as SubtleCrypto;\n}\n\n/** Fill the given buffer with cryptographically strong random bytes. */\nexport function getRandomBytes(length: number): Uint8Array<ArrayBuffer> {\n const out = new Uint8Array(length);\n const c = globalThis.crypto;\n if (c?.getRandomValues) {\n c.getRandomValues(out);\n return out;\n }\n throw new Error(\n '[nuxt-crypto] globalThis.crypto.getRandomValues is not available. Upgrade your runtime.'\n );\n}\n","import { aesGcmPbkdf2 } from './algorithms/aes-gcm';\nimport { KeyCache } from './key-cache';\nimport { encodePayload, parsePayload } from './payload';\nimport { getRandomBytes, getSubtle } from './subtle';\nimport type { CryptoAlgorithm, CryptoService, CryptoServiceConfig } from './types';\n\nconst DEFAULT_ITERATIONS = 100_000;\nconst DEFAULT_CACHE = 64;\nconst SALT_BYTES = 16;\n\n/**\n * Create a framework-agnostic `CryptoService` from a passphrase.\n *\n * Uses AES-256-GCM with PBKDF2-SHA256 (100k iterations by default). Pass a\n * different `algorithm` to plug in another cipher while preserving the same\n * versioned payload envelope.\n */\nexport async function createCryptoService(config: CryptoServiceConfig): Promise<CryptoService> {\n if (!config.passphrase) {\n throw new Error('[nuxt-crypto] passphrase is required.');\n }\n\n const iterations = config.iterations ?? DEFAULT_ITERATIONS;\n const algorithm: CryptoAlgorithm = config.algorithm ?? aesGcmPbkdf2;\n const subtle = config.subtle ?? (await getSubtle());\n const cache = new KeyCache(config.keyCacheSize ?? DEFAULT_CACHE);\n const passphrase = String(config.passphrase).trim();\n const encoder = new TextEncoder();\n const decoder = new TextDecoder();\n\n async function getDerivedKey(salt: Uint8Array<ArrayBuffer>): Promise<CryptoKey> {\n const key = KeyCache.key(salt, iterations);\n const cached = cache.get(key);\n if (cached) return cached;\n const pending = algorithm.deriveKey({ subtle, passphrase, salt, iterations });\n cache.set(key, pending);\n return pending;\n }\n\n return {\n async encrypt(plainText: string): Promise<string> {\n const salt = getRandomBytes(SALT_BYTES);\n const key = await getDerivedKey(salt);\n const encoded = encoder.encode(plainText);\n const plainBytes = new Uint8Array(new ArrayBuffer(encoded.byteLength));\n plainBytes.set(encoded);\n const { cipher, iv } = await algorithm.encrypt({ subtle, key, plainText: plainBytes });\n return encodePayload({ version: algorithm.version, salt, iv, cipher });\n },\n\n async decrypt(payload: string): Promise<string> {\n const parsed = parsePayload(payload);\n if (parsed.version !== algorithm.version) {\n throw new Error(\n `[nuxt-crypto] Unsupported payload version: ${parsed.version} (algorithm expects ${algorithm.version}).`\n );\n }\n const key = await getDerivedKey(parsed.salt);\n const plainBytes = await algorithm.decrypt({\n subtle,\n key,\n cipher: parsed.cipher,\n iv: parsed.iv,\n });\n return decoder.decode(plainBytes);\n },\n\n clearKeyCache(): void {\n cache.clear();\n },\n };\n}\n"],"mappings":"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","names":["out"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { b as CryptoServiceConfig, a as CryptoService, C as CryptoAlgorithm, B as Bytes } from '../payload-Cht5Ar0K.js';
|
|
2
|
+
export { P as ParsedPayload, e as encodePayload, p as parsePayload } from '../payload-Cht5Ar0K.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a framework-agnostic `CryptoService` from a passphrase.
|
|
6
|
+
*
|
|
7
|
+
* Uses AES-256-GCM with PBKDF2-SHA256 (100k iterations by default). Pass a
|
|
8
|
+
* different `algorithm` to plug in another cipher while preserving the same
|
|
9
|
+
* versioned payload envelope.
|
|
10
|
+
*/
|
|
11
|
+
declare function createCryptoService(config: CryptoServiceConfig): Promise<CryptoService>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* AES-256-GCM with PBKDF2-SHA256 key derivation. This is the default and
|
|
15
|
+
* currently the only built-in algorithm; pluggable via `CryptoServiceConfig.algorithm`.
|
|
16
|
+
*/
|
|
17
|
+
declare const aesGcmPbkdf2: CryptoAlgorithm;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a SubtleCrypto implementation across environments:
|
|
21
|
+
*
|
|
22
|
+
* - Browser / Deno / Bun / Node 20+: `globalThis.crypto.subtle` is available.
|
|
23
|
+
* - Older Node: fall back to `node:crypto`'s `webcrypto`.
|
|
24
|
+
*/
|
|
25
|
+
declare function getSubtle(): Promise<SubtleCrypto>;
|
|
26
|
+
/** Fill the given buffer with cryptographically strong random bytes. */
|
|
27
|
+
declare function getRandomBytes(length: number): Uint8Array<ArrayBuffer>;
|
|
28
|
+
|
|
29
|
+
/** Encode bytes to standard base64. Works in browser and Node. */
|
|
30
|
+
declare function toBase64(data: Bytes): string;
|
|
31
|
+
/** Decode a base64 string back to bytes. Works in browser and Node. */
|
|
32
|
+
declare function fromBase64(input: string): Bytes;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A tiny LRU-ish cache for derived `CryptoKey` values, keyed on the salt.
|
|
36
|
+
* When capacity is exceeded the oldest entry is evicted. Derived key
|
|
37
|
+
* material is expensive (PBKDF2 is designed to be slow), so caching has
|
|
38
|
+
* a big impact when decrypting many payloads in a row.
|
|
39
|
+
*/
|
|
40
|
+
declare class KeyCache {
|
|
41
|
+
private readonly capacity;
|
|
42
|
+
private readonly map;
|
|
43
|
+
constructor(capacity: number);
|
|
44
|
+
static key(salt: Bytes, iterations: number): string;
|
|
45
|
+
get(key: string): Promise<CryptoKey> | undefined;
|
|
46
|
+
set(key: string, value: Promise<CryptoKey>): void;
|
|
47
|
+
clear(): void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { Bytes, CryptoAlgorithm, CryptoService, CryptoServiceConfig, KeyCache, aesGcmPbkdf2, createCryptoService, fromBase64, getRandomBytes, getSubtle, toBase64 };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
KeyCache,
|
|
3
|
+
aesGcmPbkdf2,
|
|
4
|
+
createCryptoService,
|
|
5
|
+
encodePayload,
|
|
6
|
+
fromBase64,
|
|
7
|
+
getRandomBytes,
|
|
8
|
+
getSubtle,
|
|
9
|
+
parsePayload,
|
|
10
|
+
toBase64
|
|
11
|
+
} from "../chunk-HNTEUBQ7.mjs";
|
|
12
|
+
export {
|
|
13
|
+
KeyCache,
|
|
14
|
+
aesGcmPbkdf2,
|
|
15
|
+
createCryptoService,
|
|
16
|
+
encodePayload,
|
|
17
|
+
fromBase64,
|
|
18
|
+
getRandomBytes,
|
|
19
|
+
getSubtle,
|
|
20
|
+
parsePayload,
|
|
21
|
+
toBase64
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { NuxtModule } from '@nuxt/schema';
|
|
2
|
+
import { CryptoModuleOptions } from './types/index.js';
|
|
3
|
+
export { B as Bytes, C as CryptoAlgorithm, a as CryptoService, b as CryptoServiceConfig, P as ParsedPayload } from './payload-Cht5Ar0K.js';
|
|
4
|
+
|
|
5
|
+
declare const cryptoModule: NuxtModule<CryptoModuleOptions>;
|
|
6
|
+
|
|
7
|
+
export { CryptoModuleOptions, cryptoModule as default };
|
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import "./chunk-EFZPSZWO.mjs";
|
|
2
|
+
|
|
3
|
+
// src/module.ts
|
|
4
|
+
import { addPlugin, addTemplate, createResolver, defineNuxtModule } from "@nuxt/kit";
|
|
5
|
+
|
|
6
|
+
// src/templates/config.template.ts
|
|
7
|
+
function createConfigTemplateContents(params) {
|
|
8
|
+
const { options, normalizedProvideName } = params;
|
|
9
|
+
const cfg = {
|
|
10
|
+
passphrase: options.passphrase,
|
|
11
|
+
iterations: options.iterations ?? 1e5,
|
|
12
|
+
keyCacheSize: options.keyCacheSize ?? 64,
|
|
13
|
+
provideName: normalizedProvideName
|
|
14
|
+
};
|
|
15
|
+
return `export default ${JSON.stringify(cfg)};
|
|
16
|
+
`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/templates/types.template.ts
|
|
20
|
+
function createTypesTemplateContents(params) {
|
|
21
|
+
const { typesEntryPath, normalizedProvideName } = params;
|
|
22
|
+
return `// Auto-generated by @alikhalilll/nuxt-crypto
|
|
23
|
+
import type { CryptoService } from '${typesEntryPath}';
|
|
24
|
+
|
|
25
|
+
declare module '#app' {
|
|
26
|
+
interface NuxtApp {
|
|
27
|
+
$${normalizedProvideName}: CryptoService;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare module 'vue' {
|
|
32
|
+
interface ComponentCustomProperties {
|
|
33
|
+
$${normalizedProvideName}: CryptoService;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export {};
|
|
38
|
+
`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/utils/normalize-provide-name.ts
|
|
42
|
+
function normalizeProvideName(provideName, fallback) {
|
|
43
|
+
const name = (provideName ?? fallback).trim();
|
|
44
|
+
const stripped = name.startsWith("$") ? name.slice(1) : name;
|
|
45
|
+
return stripped || fallback;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/module.ts
|
|
49
|
+
var cryptoModule = defineNuxtModule({
|
|
50
|
+
meta: {
|
|
51
|
+
name: "@alikhalilll/nuxt-crypto",
|
|
52
|
+
configKey: "crypto",
|
|
53
|
+
compatibility: { nuxt: ">=3.0.0" }
|
|
54
|
+
},
|
|
55
|
+
defaults: {
|
|
56
|
+
passphrase: "",
|
|
57
|
+
provideName: "$crypto",
|
|
58
|
+
iterations: 1e5,
|
|
59
|
+
keyCacheSize: 64,
|
|
60
|
+
serverOnly: false
|
|
61
|
+
},
|
|
62
|
+
setup(options, nuxt) {
|
|
63
|
+
const resolver = createResolver(import.meta.url);
|
|
64
|
+
const normalizedProvideName = normalizeProvideName(options.provideName, "crypto");
|
|
65
|
+
addTemplate({
|
|
66
|
+
filename: "crypto-config.mjs",
|
|
67
|
+
getContents: () => createConfigTemplateContents({ options, normalizedProvideName })
|
|
68
|
+
});
|
|
69
|
+
const typesTemplate = addTemplate({
|
|
70
|
+
filename: "types/crypto.d.ts",
|
|
71
|
+
getContents: () => createTypesTemplateContents({
|
|
72
|
+
typesEntryPath: resolver.resolve("./types/index"),
|
|
73
|
+
normalizedProvideName
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
addPlugin({
|
|
77
|
+
mode: options.serverOnly ? "server" : "all",
|
|
78
|
+
src: resolver.resolve("./runtime/plugin")
|
|
79
|
+
});
|
|
80
|
+
nuxt.hook("prepare:types", ({ references }) => {
|
|
81
|
+
references.push({ path: typesTemplate.dst });
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
var module_default = cryptoModule;
|
|
86
|
+
export {
|
|
87
|
+
module_default as default
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=module.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/module.ts","../src/templates/config.template.ts","../src/templates/types.template.ts","../src/utils/normalize-provide-name.ts"],"sourcesContent":["import { addPlugin, addTemplate, createResolver, defineNuxtModule } from '@nuxt/kit';\nimport type { NuxtModule } from '@nuxt/schema';\n\nimport { createConfigTemplateContents } from './templates/config.template';\nimport { createTypesTemplateContents } from './templates/types.template';\nimport type { CryptoModuleOptions } from './types/module-options';\nimport { normalizeProvideName } from './utils/normalize-provide-name';\n\nexport * from './types';\n\nconst cryptoModule: NuxtModule<CryptoModuleOptions> = defineNuxtModule<CryptoModuleOptions>({\n meta: {\n name: '@alikhalilll/nuxt-crypto',\n configKey: 'crypto',\n compatibility: { nuxt: '>=3.0.0' },\n },\n defaults: {\n passphrase: '',\n provideName: '$crypto',\n iterations: 100_000,\n keyCacheSize: 64,\n serverOnly: false,\n },\n setup(options, nuxt) {\n const resolver = createResolver(import.meta.url);\n const normalizedProvideName = normalizeProvideName(options.provideName, 'crypto');\n\n addTemplate({\n filename: 'crypto-config.mjs',\n getContents: () => createConfigTemplateContents({ options, normalizedProvideName }),\n });\n\n const typesTemplate = addTemplate({\n filename: 'types/crypto.d.ts',\n getContents: () =>\n createTypesTemplateContents({\n typesEntryPath: resolver.resolve('./types/index'),\n normalizedProvideName,\n }),\n });\n\n addPlugin({\n mode: options.serverOnly ? 'server' : 'all',\n src: resolver.resolve('./runtime/plugin'),\n });\n\n nuxt.hook('prepare:types', ({ references }) => {\n references.push({ path: typesTemplate.dst });\n });\n },\n});\n\nexport default cryptoModule;\n","import type { CryptoModuleOptions } from '../types/module-options';\n\nexport interface ResolvedCryptoConfig {\n passphrase: string;\n iterations: number;\n keyCacheSize: number;\n provideName: string;\n}\n\nexport function createConfigTemplateContents(params: {\n options: CryptoModuleOptions;\n normalizedProvideName: string;\n}): string {\n const { options, normalizedProvideName } = params;\n const cfg: ResolvedCryptoConfig = {\n passphrase: options.passphrase,\n iterations: options.iterations ?? 100_000,\n keyCacheSize: options.keyCacheSize ?? 64,\n provideName: normalizedProvideName,\n };\n return `export default ${JSON.stringify(cfg)};\\n`;\n}\n","export function createTypesTemplateContents(params: {\n typesEntryPath: string;\n normalizedProvideName: string;\n}): string {\n const { typesEntryPath, normalizedProvideName } = params;\n\n return `// Auto-generated by @alikhalilll/nuxt-crypto\nimport type { CryptoService } from '${typesEntryPath}';\n\ndeclare module '#app' {\n interface NuxtApp {\n $${normalizedProvideName}: CryptoService;\n }\n}\n\ndeclare module 'vue' {\n interface ComponentCustomProperties {\n $${normalizedProvideName}: CryptoService;\n }\n}\n\nexport {};\n`;\n}\n","export function normalizeProvideName(provideName: string | undefined, fallback: string): string {\n const name = (provideName ?? fallback).trim();\n const stripped = name.startsWith('$') ? name.slice(1) : name;\n return stripped || fallback;\n}\n"],"mappings":";;;AAAA,SAAS,WAAW,aAAa,gBAAgB,wBAAwB;;;ACSlE,SAAS,6BAA6B,QAGlC;AACT,QAAM,EAAE,SAAS,sBAAsB,IAAI;AAC3C,QAAM,MAA4B;AAAA,IAChC,YAAY,QAAQ;AAAA,IACpB,YAAY,QAAQ,cAAc;AAAA,IAClC,cAAc,QAAQ,gBAAgB;AAAA,IACtC,aAAa;AAAA,EACf;AACA,SAAO,kBAAkB,KAAK,UAAU,GAAG,CAAC;AAAA;AAC9C;;;ACrBO,SAAS,4BAA4B,QAGjC;AACT,QAAM,EAAE,gBAAgB,sBAAsB,IAAI;AAElD,SAAO;AAAA,sCAC6B,cAAc;AAAA;AAAA;AAAA;AAAA,OAI7C,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAMrB,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAM5B;;;ACvBO,SAAS,qBAAqB,aAAiC,UAA0B;AAC9F,QAAM,QAAQ,eAAe,UAAU,KAAK;AAC5C,QAAM,WAAW,KAAK,WAAW,GAAG,IAAI,KAAK,MAAM,CAAC,IAAI;AACxD,SAAO,YAAY;AACrB;;;AHMA,IAAM,eAAgD,iBAAsC;AAAA,EAC1F,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,WAAW;AAAA,IACX,eAAe,EAAE,MAAM,UAAU;AAAA,EACnC;AAAA,EACA,UAAU;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,YAAY;AAAA,EACd;AAAA,EACA,MAAM,SAAS,MAAM;AACnB,UAAM,WAAW,eAAe,YAAY,GAAG;AAC/C,UAAM,wBAAwB,qBAAqB,QAAQ,aAAa,QAAQ;AAEhF,gBAAY;AAAA,MACV,UAAU;AAAA,MACV,aAAa,MAAM,6BAA6B,EAAE,SAAS,sBAAsB,CAAC;AAAA,IACpF,CAAC;AAED,UAAM,gBAAgB,YAAY;AAAA,MAChC,UAAU;AAAA,MACV,aAAa,MACX,4BAA4B;AAAA,QAC1B,gBAAgB,SAAS,QAAQ,eAAe;AAAA,QAChD;AAAA,MACF,CAAC;AAAA,IACL,CAAC;AAED,cAAU;AAAA,MACR,MAAM,QAAQ,aAAa,WAAW;AAAA,MACtC,KAAK,SAAS,QAAQ,kBAAkB;AAAA,IAC1C,CAAC;AAED,SAAK,KAAK,iBAAiB,CAAC,EAAE,WAAW,MAAM;AAC7C,iBAAW,KAAK,EAAE,MAAM,cAAc,IAAI,CAAC;AAAA,IAC7C,CAAC;AAAA,EACH;AACF,CAAC;AAED,IAAO,iBAAQ;","names":[]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** Shorthand for the `Uint8Array` we use everywhere in the crypto package. */
|
|
2
|
+
type Bytes = Uint8Array<ArrayBuffer>;
|
|
3
|
+
/**
|
|
4
|
+
* A CryptoService encrypts plaintext to a serialisable string payload and
|
|
5
|
+
* decrypts it back. Every operation is async because it hits the Web Crypto
|
|
6
|
+
* subtle API.
|
|
7
|
+
*/
|
|
8
|
+
interface CryptoService {
|
|
9
|
+
/** Encrypt a UTF-8 string to a versioned, base64-encoded payload. */
|
|
10
|
+
encrypt(plainText: string): Promise<string>;
|
|
11
|
+
/** Decrypt a payload produced by `encrypt`. */
|
|
12
|
+
decrypt(payload: string): Promise<string>;
|
|
13
|
+
/** Clear any cached derived keys. Safe to call at any time. */
|
|
14
|
+
clearKeyCache(): void;
|
|
15
|
+
}
|
|
16
|
+
/** Config for `createCryptoService`. */
|
|
17
|
+
interface CryptoServiceConfig {
|
|
18
|
+
/** Passphrase used to derive the symmetric key. */
|
|
19
|
+
passphrase: string;
|
|
20
|
+
/** PBKDF2 iteration count. Default 100_000. */
|
|
21
|
+
iterations?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Algorithm implementation. Defaults to AES-256-GCM with PBKDF2-SHA256 key
|
|
24
|
+
* derivation. Replace to plug in a different cipher.
|
|
25
|
+
*/
|
|
26
|
+
algorithm?: CryptoAlgorithm;
|
|
27
|
+
/**
|
|
28
|
+
* Max number of derived keys held in memory. Derived keys are expensive —
|
|
29
|
+
* caching them by salt dramatically speeds up bulk decrypt. Default 64.
|
|
30
|
+
* Set to 0 to disable caching.
|
|
31
|
+
*/
|
|
32
|
+
keyCacheSize?: number;
|
|
33
|
+
/** Override the SubtleCrypto implementation. Useful for testing. */
|
|
34
|
+
subtle?: SubtleCrypto;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A pluggable symmetric-encryption algorithm. Each implementation owns the
|
|
38
|
+
* payload format (via its version tag), key derivation, and the actual
|
|
39
|
+
* encrypt/decrypt work.
|
|
40
|
+
*/
|
|
41
|
+
interface CryptoAlgorithm {
|
|
42
|
+
/** Short identifier stored as the first segment of every payload (e.g. `'v1'`). */
|
|
43
|
+
readonly version: string;
|
|
44
|
+
/** Derive a CryptoKey from a passphrase + salt pair. Expected to be idempotent. */
|
|
45
|
+
deriveKey(args: {
|
|
46
|
+
subtle: SubtleCrypto;
|
|
47
|
+
passphrase: string;
|
|
48
|
+
salt: Bytes;
|
|
49
|
+
iterations: number;
|
|
50
|
+
}): Promise<CryptoKey>;
|
|
51
|
+
/** Encrypt plaintext bytes using a pre-derived key. Returns cipher bytes + any per-call params (e.g. IV). */
|
|
52
|
+
encrypt(args: {
|
|
53
|
+
subtle: SubtleCrypto;
|
|
54
|
+
key: CryptoKey;
|
|
55
|
+
plainText: Bytes;
|
|
56
|
+
}): Promise<{
|
|
57
|
+
cipher: Bytes;
|
|
58
|
+
iv: Bytes;
|
|
59
|
+
}>;
|
|
60
|
+
/** Decrypt cipher bytes using a pre-derived key and the IV produced during encrypt. */
|
|
61
|
+
decrypt(args: {
|
|
62
|
+
subtle: SubtleCrypto;
|
|
63
|
+
key: CryptoKey;
|
|
64
|
+
cipher: Bytes;
|
|
65
|
+
iv: Bytes;
|
|
66
|
+
}): Promise<Bytes>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Payload serialisation format (v1): `${version}.${saltB64}.${ivB64}.${cipherB64}`.
|
|
71
|
+
*
|
|
72
|
+
* Each part is URL-safe base64, all joined with `.`. The version tag is
|
|
73
|
+
* always the first segment so consumers can route to the correct algorithm
|
|
74
|
+
* during decrypt.
|
|
75
|
+
*/
|
|
76
|
+
interface ParsedPayload {
|
|
77
|
+
version: string;
|
|
78
|
+
salt: Bytes;
|
|
79
|
+
iv: Bytes;
|
|
80
|
+
cipher: Bytes;
|
|
81
|
+
}
|
|
82
|
+
declare function encodePayload(parts: ParsedPayload): string;
|
|
83
|
+
declare function parsePayload(payload: string): ParsedPayload;
|
|
84
|
+
|
|
85
|
+
export { type Bytes as B, type CryptoAlgorithm as C, type ParsedPayload as P, type CryptoService as a, type CryptoServiceConfig as b, encodePayload as e, parsePayload as p };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createCryptoService
|
|
3
|
+
} from "../chunk-HNTEUBQ7.mjs";
|
|
4
|
+
|
|
5
|
+
// src/runtime/plugin.ts
|
|
6
|
+
import { defineNuxtPlugin } from "#app";
|
|
7
|
+
import config from "#build/crypto-config.mjs";
|
|
8
|
+
var plugin_default = defineNuxtPlugin(async () => {
|
|
9
|
+
const { passphrase, iterations, keyCacheSize, provideName } = config;
|
|
10
|
+
if (!passphrase && import.meta.dev) {
|
|
11
|
+
console.warn(
|
|
12
|
+
`[@alikhalilll/nuxt-crypto] Missing passphrase \u2014 $${provideName} will throw when used.`
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
const service = await createCryptoService({
|
|
16
|
+
passphrase: String(passphrase).trim(),
|
|
17
|
+
iterations,
|
|
18
|
+
keyCacheSize
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
provide: {
|
|
22
|
+
[provideName]: service
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
export {
|
|
27
|
+
plugin_default as default
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=plugin.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/runtime/plugin.ts"],"sourcesContent":["import { defineNuxtPlugin } from '#app';\nimport config from '#build/crypto-config.mjs';\n\nimport { createCryptoService } from '../core/create-service';\n\nexport default defineNuxtPlugin(async () => {\n const { passphrase, iterations, keyCacheSize, provideName } = config;\n\n if (!passphrase && (import.meta as ImportMeta & { dev?: boolean }).dev) {\n // eslint-disable-next-line no-console\n console.warn(\n `[@alikhalilll/nuxt-crypto] Missing passphrase — $${provideName} will throw when used.`\n );\n }\n\n const service = await createCryptoService({\n passphrase: String(passphrase).trim(),\n iterations,\n keyCacheSize,\n });\n\n return {\n provide: {\n [provideName]: service,\n },\n };\n});\n"],"mappings":";;;;;AAAA,SAAS,wBAAwB;AACjC,OAAO,YAAY;AAInB,IAAO,iBAAQ,iBAAiB,YAAY;AAC1C,QAAM,EAAE,YAAY,YAAY,cAAc,YAAY,IAAI;AAE9D,MAAI,CAAC,cAAe,YAA+C,KAAK;AAEtE,YAAQ;AAAA,MACN,yDAAoD,WAAW;AAAA,IACjE;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,oBAAoB;AAAA,IACxC,YAAY,OAAO,UAAU,EAAE,KAAK;AAAA,IACpC;AAAA,IACA;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL,SAAS;AAAA,MACP,CAAC,WAAW,GAAG;AAAA,IACjB;AAAA,EACF;AACF,CAAC;","names":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export { B as Bytes, C as CryptoAlgorithm, a as CryptoService, b as CryptoServiceConfig, P as ParsedPayload } from '../payload-Cht5Ar0K.js';
|
|
2
|
+
|
|
3
|
+
interface CryptoModuleOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Passphrase used to derive the AES key. Prefer wiring this from a
|
|
6
|
+
* runtime config env var (e.g. `NUXT_ENCRYPTION_PASSPHRASE`) rather than
|
|
7
|
+
* committing it.
|
|
8
|
+
*/
|
|
9
|
+
passphrase: string;
|
|
10
|
+
/**
|
|
11
|
+
* Name under which the service is provided. Accepts `$crypto` or `crypto`
|
|
12
|
+
* — the leading `$` is stripped. Default: `$crypto`.
|
|
13
|
+
*/
|
|
14
|
+
provideName?: string;
|
|
15
|
+
/** PBKDF2 iterations. Default 100_000. */
|
|
16
|
+
iterations?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Max number of derived keys held in memory. Default 64. Set to 0 to
|
|
19
|
+
* disable caching.
|
|
20
|
+
*/
|
|
21
|
+
keyCacheSize?: number;
|
|
22
|
+
/**
|
|
23
|
+
* When `true`, the runtime plugin is only registered on the server. Useful
|
|
24
|
+
* when you do not want the plaintext passphrase shipped to the browser.
|
|
25
|
+
* Default: `false`.
|
|
26
|
+
*/
|
|
27
|
+
serverOnly?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type { CryptoModuleOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alikhalilll/nuxt-crypto",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Symmetric Web Crypto service for Nuxt 3/4. AES-256-GCM + PBKDF2 with key caching and pluggable algorithms.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "alikhalilll",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"nuxt",
|
|
14
|
+
"nuxt-module",
|
|
15
|
+
"crypto",
|
|
16
|
+
"encryption",
|
|
17
|
+
"aes-gcm",
|
|
18
|
+
"web-crypto",
|
|
19
|
+
"pbkdf2"
|
|
20
|
+
],
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/module.d.ts",
|
|
24
|
+
"import": "./dist/module.mjs"
|
|
25
|
+
},
|
|
26
|
+
"./core": {
|
|
27
|
+
"types": "./dist/core/index.d.ts",
|
|
28
|
+
"import": "./dist/core/index.mjs"
|
|
29
|
+
},
|
|
30
|
+
"./types": {
|
|
31
|
+
"types": "./dist/types/index.d.ts",
|
|
32
|
+
"import": "./dist/types/index.mjs"
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"types": "./dist/module.d.ts",
|
|
37
|
+
"typesVersions": {
|
|
38
|
+
"*": {
|
|
39
|
+
".": [
|
|
40
|
+
"./dist/module.d.ts"
|
|
41
|
+
],
|
|
42
|
+
"core": [
|
|
43
|
+
"./dist/core/index.d.ts"
|
|
44
|
+
],
|
|
45
|
+
"types": [
|
|
46
|
+
"./dist/types/index.d.ts"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"README.md"
|
|
53
|
+
],
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@nuxt/kit": "^3.0.0 || ^4.0.0"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"@nuxt/kit": {
|
|
59
|
+
"optional": true
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@nuxt/kit": "^4.0.0",
|
|
64
|
+
"@nuxt/schema": "^4.0.0",
|
|
65
|
+
"@types/node": "^22.13.0",
|
|
66
|
+
"rimraf": "^6.0.1",
|
|
67
|
+
"tsup": "^8.5.0",
|
|
68
|
+
"typescript": "^5.9.2"
|
|
69
|
+
},
|
|
70
|
+
"scripts": {
|
|
71
|
+
"clean": "rimraf dist",
|
|
72
|
+
"build": "pnpm clean && tsup",
|
|
73
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
74
|
+
}
|
|
75
|
+
}
|