@md-oss/security 0.1.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/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Copyright 2026 Mirasaki Development
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
4
+
5
+ THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,303 @@
1
+ # @md-oss/security
2
+
3
+ Cryptographic utilities for encryption, signed URLs, and secure streaming.
4
+
5
+ ## Features
6
+
7
+ - **AES-256-GCM Encryption** - Secure string and buffer encryption with authentication
8
+ - **Signed URLs** - Generate and verify cryptographically signed URLs with optional expiration
9
+ - **Decryption Streams** - Stream-based decryption for large files with memory efficiency
10
+ - **Range Streaming** - Support for partial file reads
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pnpm add @md-oss/security
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ### Encryption & Decryption
21
+
22
+ ```typescript
23
+ import { generateKey, encrypt, decrypt } from '@md-oss/security';
24
+
25
+ // Generate a random encryption key (base64 encoded)
26
+ const key = generateKey(32); // 32 bytes for AES-256
27
+
28
+ // Encrypt a string
29
+ const encrypted = encrypt('Hello, World!', key);
30
+ console.log(encrypted); // Base64 encoded string
31
+
32
+ // Decrypt it back
33
+ const decrypted = decrypt(encrypted, key);
34
+ console.log(decrypted); // 'Hello, World!'
35
+
36
+ // Works with buffers too
37
+ const buffer = Buffer.from('Binary data');
38
+ const encryptedBuffer = encrypt(buffer, key);
39
+ const decryptedBuffer = decrypt(encryptedBuffer, key);
40
+ ```
41
+
42
+ #### How It Works
43
+
44
+ - Uses **AES-256-GCM** (Galois/Counter Mode) for authenticated encryption
45
+ - Generates a random 12-byte IV for each encryption
46
+ - Automatically includes the IV and authentication tag in the encrypted output
47
+ - Returns base64-encoded strings for safe transport
48
+ - Provides authentication to prevent tampering
49
+
50
+ ### Signed URLs
51
+
52
+ #### Generate Signed URLs
53
+
54
+ ```typescript
55
+ import { generateSignedUrl } from '@md-oss/security';
56
+
57
+ const secret = 'your-base64-encoded-secret';
58
+ const path = '/api/files/document.pdf';
59
+ const expires = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours from now
60
+
61
+ const { url, expiresUnixTs, sig } = generateSignedUrl({
62
+ secret,
63
+ path,
64
+ expires
65
+ });
66
+
67
+ console.log(url); // /api/files/document.pdf?sig=abc123&expires=1234567890
68
+
69
+ // URLs without expiration
70
+ const permanentUrl = generateSignedUrl({
71
+ secret,
72
+ path,
73
+ expires: null
74
+ });
75
+ console.log(permanentUrl.url); // /api/files/document.pdf?sig=abc123
76
+ ```
77
+
78
+ #### Verify Signed URLs
79
+
80
+ ```typescript
81
+ import { verifySignedUrl } from '@md-oss/security';
82
+
83
+ const result = verifySignedUrl({
84
+ secret,
85
+ path: '/api/files/document.pdf',
86
+ expires: 1234567890, // UNIX timestamp in seconds
87
+ sig: 'abc123'
88
+ });
89
+
90
+ if (result === 'InvalidSignatureError') {
91
+ console.log('Signature is invalid');
92
+ } else if (result === 'ExpiredSignatureError') {
93
+ console.log('URL has expired');
94
+ } else {
95
+ console.log('Verified:', result); // { path, expires, sig }
96
+ }
97
+ ```
98
+
99
+ #### Verify from Request
100
+
101
+ ```typescript
102
+ import { verifySignedUrlFromRequest } from '@md-oss/security';
103
+
104
+ // In your request handler
105
+ const signedAccess = verifySignedUrlFromRequest({
106
+ secret: process.env.SIGNED_URL_SECRET || '',
107
+ expectedPath: req.path,
108
+ query: req.query
109
+ });
110
+
111
+ if (signedAccess.type === 'verified') {
112
+ // URL signature is valid
113
+ console.log('Access granted');
114
+ } else {
115
+ // Handle invalid/expired signature
116
+ console.log('Access denied:', signedAccess.error);
117
+ // Possible errors: 'MISSING_SIGNATURE' | 'INVALID_SIGNATURE' | 'EXPIRED_SIGNATURE'
118
+ }
119
+ ```
120
+
121
+ #### With Access Control
122
+
123
+ ```typescript
124
+ import { withSignedAccess } from '@md-oss/security';
125
+
126
+ const result = await withSignedAccess(
127
+ signedAccess,
128
+ async () => {
129
+ // This runs if signature is missing or invalid
130
+ // Return true to continue without signature
131
+ // Return error or data to handle differently
132
+ return true; // Allow access without signature
133
+ }
134
+ );
135
+
136
+ if (result instanceof APIError) {
137
+ // Signature validation failed
138
+ res.status(result.statusCode).json(result.body);
139
+ } else if (result === true) {
140
+ // Access granted (either verified or allowed without)
141
+ // Continue processing
142
+ }
143
+ ```
144
+
145
+ ### Decryption Streams
146
+
147
+ #### Basic Decryption Stream
148
+
149
+ ```typescript
150
+ import { createDecryptionStream } from '@md-oss/security';
151
+
152
+ const decryptionKey = 'your-base64-encoded-key';
153
+ const filePath = '/path/to/encrypted/file';
154
+
155
+ const decryptedStream = createDecryptionStream(filePath, decryptionKey);
156
+
157
+ // Pipe to response for download
158
+ res.setHeader('Content-Type', 'application/octet-stream');
159
+ res.setHeader('Content-Disposition', 'attachment; filename="file.pdf"');
160
+ decryptedStream.pipe(res);
161
+
162
+ // Or save to file
163
+ import { createWriteStream } from 'node:fs';
164
+ const writeStream = createWriteStream('/path/to/decrypted/file');
165
+ decryptedStream.pipe(writeStream);
166
+
167
+ // Handle errors
168
+ decryptedStream.on('error', (err) => {
169
+ console.error('Decryption error:', err);
170
+ });
171
+ ```
172
+
173
+ #### Range Streaming
174
+
175
+ ```typescript
176
+ import { streamWithRange } from '@md-oss/security';
177
+
178
+ // Stream specific byte range (e.g., for HTTP Range requests)
179
+ const start = 0;
180
+ const end = 1024 * 1024; // 1MB
181
+
182
+ const rangeStream = streamWithRange(filePath, start, end);
183
+ rangeStream.pipe(res);
184
+ ```
185
+
186
+ ## Security Best Practices
187
+
188
+ ### Key Management
189
+
190
+ ```typescript
191
+ // Generate secure random keys
192
+ const encryptionKey = generateKey(32); // 32 bytes for AES-256
193
+ const signingSecret = generateKey(32);
194
+
195
+ // Store securely (e.g., environment variables, KMS)
196
+ process.env.ENCRYPTION_KEY = encryptionKey;
197
+ process.env.SIGNED_URL_SECRET = signingSecret;
198
+
199
+ // Never hardcode keys in source code
200
+ // Rotate keys periodically
201
+ // Use different keys for different purposes
202
+ ```
203
+
204
+ ### Signed URL Best Practices
205
+
206
+ ```typescript
207
+ // Always set expiration times
208
+ const shortLivedUrl = generateSignedUrl({
209
+ secret,
210
+ path,
211
+ expires: new Date(Date.now() + 30 * 60 * 1000) // 30 minutes
212
+ });
213
+
214
+ // Use different secrets for different purposes
215
+ const downloadSecret = process.env.DOWNLOAD_SIGNED_URL_SECRET;
216
+ const uploadSecret = process.env.UPLOAD_SIGNED_URL_SECRET;
217
+
218
+ // Include path in signature to prevent URL manipulation
219
+ // Verify path matches expected value
220
+ const expectedPath = '/api/files/user-123/document.pdf';
221
+ const userProvidedPath = req.query.path;
222
+
223
+ if (expectedPath !== userProvidedPath) {
224
+ throw new Error('Path mismatch');
225
+ }
226
+ ```
227
+
228
+ ### Encryption Best Practices
229
+
230
+ ```typescript
231
+ // Use strong keys
232
+ const key = generateKey(32); // 32 bytes = 256 bits for AES-256
233
+
234
+ // Each encryption generates a new random IV
235
+ const data1 = encrypt('secret', key); // Different each time
236
+ const data2 = encrypt('secret', key);
237
+ // data1 !== data2, both decrypt to 'secret'
238
+
239
+ // Verify authenticity before decrypting
240
+ // AES-GCM provides authentication automatically
241
+
242
+ // Don't reuse keys across different purposes
243
+ const userDataKey = generateKey(32);
244
+ const sessionDataKey = generateKey(32);
245
+ ```
246
+
247
+ ## Types
248
+
249
+ ```typescript
250
+ import type {
251
+ SignedUrlSchema,
252
+ VerifySignedUrlFromRequestOptions,
253
+ SignedAccessError,
254
+ SignedAccess
255
+ } from '@md-oss/security';
256
+ ```
257
+
258
+ ## Error Handling
259
+
260
+ ```typescript
261
+ import { createDecryptionStream } from '@md-oss/security';
262
+
263
+ const stream = createDecryptionStream(filePath, key);
264
+
265
+ stream.on('error', (err) => {
266
+ if (err.code === 'ENOENT') {
267
+ console.error('File not found');
268
+ } else if (err.code === 'ERR_OSSL_EVP_BAD_DECRYPT') {
269
+ console.error('Decryption failed - wrong key or corrupted data');
270
+ } else {
271
+ console.error('Unknown error:', err);
272
+ }
273
+ });
274
+
275
+ stream.pipe(destination);
276
+ ```
277
+
278
+ ## Performance Considerations
279
+
280
+ - **Encryption/Decryption**: O(n) where n is data size
281
+ - **Stream Processing**: Memory-efficient for large files (64KB chunks by default)
282
+ - **Signed URLs**: O(1) - constant time verification
283
+ - **Timing-safe Comparison**: Uses `crypto.timingSafeEqual` to prevent timing attacks
284
+
285
+ ## API Reference
286
+
287
+ ### Encryption
288
+
289
+ - `generateKey(length?: number): string` - Generate base64-encoded random key
290
+ - `encrypt<T>(input: T, key: string): T` - Encrypt string or buffer
291
+ - `decrypt<T>(input: T, key: string): T` - Decrypt string or buffer
292
+
293
+ ### Signed URLs
294
+
295
+ - `generateSignedUrl(options): { url, expiresUnixTs, sig }`
296
+ - `verifySignedUrl(options): string | { path, expires, sig }`
297
+ - `verifySignedUrlFromRequest(options): SignedAccess`
298
+ - `withSignedAccess(access, callback): Promise`
299
+
300
+ ### Streams
301
+
302
+ - `createDecryptionStream(filePath, key): PassThrough`
303
+ - `streamWithRange(filePath, start, end): PassThrough`
@@ -0,0 +1,2 @@
1
+ "use strict";var d=Object.defineProperty;var i=(n,o)=>d(n,"name",{value:o,configurable:!0});var m=require("node:crypto"),h=require("node:fs"),u=require("node:stream");function p(n,o){const e=h.createReadStream(n,{highWaterMark:65536});let a,r;const t=new u.PassThrough;return e.on("error",s=>{t.emit("error",s)}),e.on("end",()=>{t.end()}),e.once("readable",()=>{const s=e.read(28);if(!s)return;a=s.subarray(0,12),r=s.subarray(12,28);const c=m.createDecipheriv("aes-256-gcm",Buffer.from(o,"base64"),a);c.setAuthTag(r),e.pipe(c).pipe(t)}),t}i(p,"createDecryptionStream");function g(n,o,e){const a=h.createReadStream(n,{start:o,end:e,highWaterMark:65536}),r=new u.PassThrough;return a.on("error",t=>{r.emit("error",t)}),a.on("end",()=>{r.end()}),a.pipe(r),r}i(g,"streamWithRange"),exports.createDecryptionStream=p,exports.streamWithRange=g;
2
+ //# sourceMappingURL=decryption-stream.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decryption-stream.cjs","sources":["../src/decryption-stream.ts"],"sourcesContent":["import { createDecipheriv } from 'node:crypto';\nimport { createReadStream } from 'node:fs';\nimport { PassThrough } from 'node:stream';\n\nexport function createDecryptionStream(\n\tfilePath: string,\n\tdecryptionKey: string\n): PassThrough {\n\tconst readStream = createReadStream(filePath, { highWaterMark: 64 * 1024 });\n\n\tlet iv: Buffer;\n\tlet authTag: Buffer;\n\n\tconst passthrough = new PassThrough();\n\n\treadStream.on('error', (error) => {\n\t\tpassthrough.emit('error', error);\n\t});\n\n\treadStream.on('end', () => {\n\t\tpassthrough.end();\n\t});\n\n\treadStream.once('readable', () => {\n\t\tconst header = readStream.read(28);\n\t\tif (!header) return;\n\n\t\tiv = header.subarray(0, 12);\n\t\tauthTag = header.subarray(12, 28);\n\n\t\tconst decipher = createDecipheriv(\n\t\t\t'aes-256-gcm',\n\t\t\tBuffer.from(decryptionKey, 'base64'),\n\t\t\tiv\n\t\t);\n\t\tdecipher.setAuthTag(authTag);\n\n\t\treadStream.pipe(decipher).pipe(passthrough);\n\t});\n\n\treturn passthrough;\n}\n\nexport function streamWithRange(\n\tfilePath: string,\n\tstart: number,\n\tend: number\n): PassThrough {\n\tconst readStream = createReadStream(filePath, {\n\t\tstart,\n\t\tend,\n\t\thighWaterMark: 64 * 1024,\n\t});\n\tconst passthrough = new PassThrough();\n\n\treadStream.on('error', (error) => {\n\t\tpassthrough.emit('error', error);\n\t});\n\n\treadStream.on('end', () => {\n\t\tpassthrough.end();\n\t});\n\n\treadStream.pipe(passthrough);\n\n\treturn passthrough;\n}\n"],"names":["createDecryptionStream","filePath","decryptionKey","readStream","createReadStream","iv","authTag","passthrough","PassThrough","error","header","decipher","createDecipheriv","__name","streamWithRange","start","end"],"mappings":"uKAIO,SAASA,EACfC,EACAC,EACc,CACd,MAAMC,EAAaC,EAAAA,iBAAiBH,EAAU,CAAE,cAAe,MAAW,EAE1E,IAAII,EACAC,EAEJ,MAAMC,EAAc,IAAIC,cAExB,OAAAL,EAAW,GAAG,QAAUM,GAAU,CACjCF,EAAY,KAAK,QAASE,CAAK,CAChC,CAAC,EAEDN,EAAW,GAAG,MAAO,IAAM,CAC1BI,EAAY,IAAA,CACb,CAAC,EAEDJ,EAAW,KAAK,WAAY,IAAM,CACjC,MAAMO,EAASP,EAAW,KAAK,EAAE,EACjC,GAAI,CAACO,EAAQ,OAEbL,EAAKK,EAAO,SAAS,EAAG,EAAE,EAC1BJ,EAAUI,EAAO,SAAS,GAAI,EAAE,EAEhC,MAAMC,EAAWC,EAAAA,iBAChB,cACA,OAAO,KAAKV,EAAe,QAAQ,EACnCG,CAAA,EAEDM,EAAS,WAAWL,CAAO,EAE3BH,EAAW,KAAKQ,CAAQ,EAAE,KAAKJ,CAAW,CAC3C,CAAC,EAEMA,CACR,CArCgBM,EAAAb,EAAA,0BAuCT,SAASc,EACfb,EACAc,EACAC,EACc,CACd,MAAMb,EAAaC,EAAAA,iBAAiBH,EAAU,CAC7C,MAAAc,EACA,IAAAC,EACA,cAAe,KAAK,CACpB,EACKT,EAAc,IAAIC,cAExB,OAAAL,EAAW,GAAG,QAAUM,GAAU,CACjCF,EAAY,KAAK,QAASE,CAAK,CAChC,CAAC,EAEDN,EAAW,GAAG,MAAO,IAAM,CAC1BI,EAAY,IAAA,CACb,CAAC,EAEDJ,EAAW,KAAKI,CAAW,EAEpBA,CACR,CAvBgBM,EAAAC,EAAA"}
@@ -0,0 +1,6 @@
1
+ import { PassThrough } from 'node:stream';
2
+
3
+ declare function createDecryptionStream(filePath: string, decryptionKey: string): PassThrough;
4
+ declare function streamWithRange(filePath: string, start: number, end: number): PassThrough;
5
+
6
+ export { createDecryptionStream, streamWithRange };
@@ -0,0 +1,6 @@
1
+ import { PassThrough } from 'node:stream';
2
+
3
+ declare function createDecryptionStream(filePath: string, decryptionKey: string): PassThrough;
4
+ declare function streamWithRange(filePath: string, start: number, end: number): PassThrough;
5
+
6
+ export { createDecryptionStream, streamWithRange };
@@ -0,0 +1,2 @@
1
+ var p=Object.defineProperty;var s=(o,n)=>p(o,"name",{value:n,configurable:!0});import{createDecipheriv as u}from"node:crypto";import{createReadStream as h}from"node:fs";import{PassThrough as m}from"node:stream";function d(o,n){const r=h(o,{highWaterMark:65536});let t,e;const a=new m;return r.on("error",i=>{a.emit("error",i)}),r.on("end",()=>{a.end()}),r.once("readable",()=>{const i=r.read(28);if(!i)return;t=i.subarray(0,12),e=i.subarray(12,28);const c=u("aes-256-gcm",Buffer.from(n,"base64"),t);c.setAuthTag(e),r.pipe(c).pipe(a)}),a}s(d,"createDecryptionStream");function f(o,n,r){const t=h(o,{start:n,end:r,highWaterMark:65536}),e=new m;return t.on("error",a=>{e.emit("error",a)}),t.on("end",()=>{e.end()}),t.pipe(e),e}s(f,"streamWithRange");export{d as createDecryptionStream,f as streamWithRange};
2
+ //# sourceMappingURL=decryption-stream.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decryption-stream.mjs","sources":["../src/decryption-stream.ts"],"sourcesContent":["import { createDecipheriv } from 'node:crypto';\nimport { createReadStream } from 'node:fs';\nimport { PassThrough } from 'node:stream';\n\nexport function createDecryptionStream(\n\tfilePath: string,\n\tdecryptionKey: string\n): PassThrough {\n\tconst readStream = createReadStream(filePath, { highWaterMark: 64 * 1024 });\n\n\tlet iv: Buffer;\n\tlet authTag: Buffer;\n\n\tconst passthrough = new PassThrough();\n\n\treadStream.on('error', (error) => {\n\t\tpassthrough.emit('error', error);\n\t});\n\n\treadStream.on('end', () => {\n\t\tpassthrough.end();\n\t});\n\n\treadStream.once('readable', () => {\n\t\tconst header = readStream.read(28);\n\t\tif (!header) return;\n\n\t\tiv = header.subarray(0, 12);\n\t\tauthTag = header.subarray(12, 28);\n\n\t\tconst decipher = createDecipheriv(\n\t\t\t'aes-256-gcm',\n\t\t\tBuffer.from(decryptionKey, 'base64'),\n\t\t\tiv\n\t\t);\n\t\tdecipher.setAuthTag(authTag);\n\n\t\treadStream.pipe(decipher).pipe(passthrough);\n\t});\n\n\treturn passthrough;\n}\n\nexport function streamWithRange(\n\tfilePath: string,\n\tstart: number,\n\tend: number\n): PassThrough {\n\tconst readStream = createReadStream(filePath, {\n\t\tstart,\n\t\tend,\n\t\thighWaterMark: 64 * 1024,\n\t});\n\tconst passthrough = new PassThrough();\n\n\treadStream.on('error', (error) => {\n\t\tpassthrough.emit('error', error);\n\t});\n\n\treadStream.on('end', () => {\n\t\tpassthrough.end();\n\t});\n\n\treadStream.pipe(passthrough);\n\n\treturn passthrough;\n}\n"],"names":["createDecryptionStream","filePath","decryptionKey","readStream","createReadStream","iv","authTag","passthrough","PassThrough","error","header","decipher","createDecipheriv","__name","streamWithRange","start","end"],"mappings":"mNAIO,SAASA,EACfC,EACAC,EACc,CACd,MAAMC,EAAaC,EAAiBH,EAAU,CAAE,cAAe,MAAW,EAE1E,IAAII,EACAC,EAEJ,MAAMC,EAAc,IAAIC,EAExB,OAAAL,EAAW,GAAG,QAAUM,GAAU,CACjCF,EAAY,KAAK,QAASE,CAAK,CAChC,CAAC,EAEDN,EAAW,GAAG,MAAO,IAAM,CAC1BI,EAAY,IAAA,CACb,CAAC,EAEDJ,EAAW,KAAK,WAAY,IAAM,CACjC,MAAMO,EAASP,EAAW,KAAK,EAAE,EACjC,GAAI,CAACO,EAAQ,OAEbL,EAAKK,EAAO,SAAS,EAAG,EAAE,EAC1BJ,EAAUI,EAAO,SAAS,GAAI,EAAE,EAEhC,MAAMC,EAAWC,EAChB,cACA,OAAO,KAAKV,EAAe,QAAQ,EACnCG,CAAA,EAEDM,EAAS,WAAWL,CAAO,EAE3BH,EAAW,KAAKQ,CAAQ,EAAE,KAAKJ,CAAW,CAC3C,CAAC,EAEMA,CACR,CArCgBM,EAAAb,EAAA,0BAuCT,SAASc,EACfb,EACAc,EACAC,EACc,CACd,MAAMb,EAAaC,EAAiBH,EAAU,CAC7C,MAAAc,EACA,IAAAC,EACA,cAAe,KAAK,CACpB,EACKT,EAAc,IAAIC,EAExB,OAAAL,EAAW,GAAG,QAAUM,GAAU,CACjCF,EAAY,KAAK,QAASE,CAAK,CAChC,CAAC,EAEDN,EAAW,GAAG,MAAO,IAAM,CAC1BI,EAAY,IAAA,CACb,CAAC,EAEDJ,EAAW,KAAKI,CAAW,EAEpBA,CACR,CAvBgBM,EAAAC,EAAA"}
@@ -0,0 +1,2 @@
1
+ "use strict";var g=Object.defineProperty;var a=(e,r)=>g(e,"name",{value:r,configurable:!0});var f=require("node:crypto");function i(e=32){return f.randomBytes(e).toString("hex")}a(i,"generateKey");function p(e,r){const t=f.randomBytes(12),s=Buffer.from(r,"base64"),c=f.createCipheriv("aes-256-gcm",s,t),u=typeof e=="string"?Buffer.from(e,"utf8"):e,y=Buffer.concat([c.update(u),c.final()]),n=c.getAuthTag(),o=Buffer.concat([t,n,y]);return typeof e=="string"?o.toString("base64"):o}a(p,"encrypt");function d(e,r){const t=typeof e=="string"?Buffer.from(e,"base64"):e,s=t.subarray(0,12),c=t.subarray(12,28),u=t.subarray(28),y=Buffer.from(r,"base64"),n=f.createDecipheriv("aes-256-gcm",y,s);n.setAuthTag(c);const o=Buffer.concat([n.update(u),n.final()]);return typeof e=="string"?o.toString("utf8"):o}a(d,"decrypt"),exports.decrypt=d,exports.encrypt=p,exports.generateKey=i;
2
+ //# sourceMappingURL=encryption.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryption.cjs","sources":["../src/encryption.ts"],"sourcesContent":["import crypto from 'node:crypto';\n\nexport function generateKey(length = 32): string {\n\treturn crypto.randomBytes(length).toString('hex');\n}\n\nexport function encrypt<T extends string | Buffer>(\n\tinput: T,\n\tencryptionKey: string\n): T {\n\tconst iv = crypto.randomBytes(12);\n\tconst key = Buffer.from(encryptionKey, 'base64');\n\tconst cipher = crypto.createCipheriv('aes-256-gcm', key, iv);\n\n\tconst bufferInput =\n\t\ttypeof input === 'string' ? Buffer.from(input, 'utf8') : input;\n\tconst encrypted = Buffer.concat([cipher.update(bufferInput), cipher.final()]);\n\tconst authTag = cipher.getAuthTag();\n\n\tconst output = Buffer.concat([iv, authTag, encrypted]);\n\n\treturn typeof input === 'string'\n\t\t? (output.toString('base64') as T)\n\t\t: (output as T);\n}\n\nexport function decrypt<T extends string | Buffer>(\n\tinput: T,\n\tdecryptionKey: string\n): T {\n\tconst encryptedBuffer =\n\t\ttypeof input === 'string'\n\t\t\t? Buffer.from(input, 'base64')\n\t\t\t: (input as Buffer);\n\n\tconst iv = encryptedBuffer.subarray(0, 12);\n\tconst authTag = encryptedBuffer.subarray(12, 28);\n\tconst encryptedData = encryptedBuffer.subarray(28);\n\n\tconst key = Buffer.from(decryptionKey, 'base64');\n\tconst decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);\n\tdecipher.setAuthTag(authTag);\n\n\tconst decrypted = Buffer.concat([\n\t\tdecipher.update(encryptedData),\n\t\tdecipher.final(),\n\t]);\n\n\treturn typeof input === 'string'\n\t\t? (decrypted.toString('utf8') as T)\n\t\t: (decrypted as T);\n}\n"],"names":["generateKey","length","crypto","__name","encrypt","input","encryptionKey","iv","key","cipher","bufferInput","encrypted","authTag","output","decrypt","decryptionKey","encryptedBuffer","encryptedData","decipher","decrypted"],"mappings":"yHAEO,SAASA,EAAYC,EAAS,GAAY,CAChD,OAAOC,EAAO,YAAYD,CAAM,EAAE,SAAS,KAAK,CACjD,CAFgBE,EAAAH,EAAA,eAIT,SAASI,EACfC,EACAC,EACI,CACJ,MAAMC,EAAKL,EAAO,YAAY,EAAE,EAC1BM,EAAM,OAAO,KAAKF,EAAe,QAAQ,EACzCG,EAASP,EAAO,eAAe,cAAeM,EAAKD,CAAE,EAErDG,EACL,OAAOL,GAAU,SAAW,OAAO,KAAKA,EAAO,MAAM,EAAIA,EACpDM,EAAY,OAAO,OAAO,CAACF,EAAO,OAAOC,CAAW,EAAGD,EAAO,MAAA,CAAO,CAAC,EACtEG,EAAUH,EAAO,WAAA,EAEjBI,EAAS,OAAO,OAAO,CAACN,EAAIK,EAASD,CAAS,CAAC,EAErD,OAAO,OAAON,GAAU,SACpBQ,EAAO,SAAS,QAAQ,EACxBA,CACL,CAlBgBV,EAAAC,EAAA,WAoBT,SAASU,EACfT,EACAU,EACI,CACJ,MAAMC,EACL,OAAOX,GAAU,SACd,OAAO,KAAKA,EAAO,QAAQ,EAC1BA,EAECE,EAAKS,EAAgB,SAAS,EAAG,EAAE,EACnCJ,EAAUI,EAAgB,SAAS,GAAI,EAAE,EACzCC,EAAgBD,EAAgB,SAAS,EAAE,EAE3CR,EAAM,OAAO,KAAKO,EAAe,QAAQ,EACzCG,EAAWhB,EAAO,iBAAiB,cAAeM,EAAKD,CAAE,EAC/DW,EAAS,WAAWN,CAAO,EAE3B,MAAMO,EAAY,OAAO,OAAO,CAC/BD,EAAS,OAAOD,CAAa,EAC7BC,EAAS,MAAA,CAAM,CACf,EAED,OAAO,OAAOb,GAAU,SACpBc,EAAU,SAAS,MAAM,EACzBA,CACL,CAzBgBhB,EAAAW,EAAA"}
@@ -0,0 +1,5 @@
1
+ declare function generateKey(length?: number): string;
2
+ declare function encrypt<T extends string | Buffer>(input: T, encryptionKey: string): T;
3
+ declare function decrypt<T extends string | Buffer>(input: T, decryptionKey: string): T;
4
+
5
+ export { decrypt, encrypt, generateKey };
@@ -0,0 +1,5 @@
1
+ declare function generateKey(length?: number): string;
2
+ declare function encrypt<T extends string | Buffer>(input: T, encryptionKey: string): T;
3
+ declare function decrypt<T extends string | Buffer>(input: T, decryptionKey: string): T;
4
+
5
+ export { decrypt, encrypt, generateKey };
@@ -0,0 +1,2 @@
1
+ var g=Object.defineProperty;var f=(t,r)=>g(t,"name",{value:r,configurable:!0});import a from"node:crypto";function i(t=32){return a.randomBytes(t).toString("hex")}f(i,"generateKey");function p(t,r){const e=a.randomBytes(12),s=Buffer.from(r,"base64"),o=a.createCipheriv("aes-256-gcm",s,e),u=typeof t=="string"?Buffer.from(t,"utf8"):t,y=Buffer.concat([o.update(u),o.final()]),c=o.getAuthTag(),n=Buffer.concat([e,c,y]);return typeof t=="string"?n.toString("base64"):n}f(p,"encrypt");function d(t,r){const e=typeof t=="string"?Buffer.from(t,"base64"):t,s=e.subarray(0,12),o=e.subarray(12,28),u=e.subarray(28),y=Buffer.from(r,"base64"),c=a.createDecipheriv("aes-256-gcm",y,s);c.setAuthTag(o);const n=Buffer.concat([c.update(u),c.final()]);return typeof t=="string"?n.toString("utf8"):n}f(d,"decrypt");export{d as decrypt,p as encrypt,i as generateKey};
2
+ //# sourceMappingURL=encryption.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryption.mjs","sources":["../src/encryption.ts"],"sourcesContent":["import crypto from 'node:crypto';\n\nexport function generateKey(length = 32): string {\n\treturn crypto.randomBytes(length).toString('hex');\n}\n\nexport function encrypt<T extends string | Buffer>(\n\tinput: T,\n\tencryptionKey: string\n): T {\n\tconst iv = crypto.randomBytes(12);\n\tconst key = Buffer.from(encryptionKey, 'base64');\n\tconst cipher = crypto.createCipheriv('aes-256-gcm', key, iv);\n\n\tconst bufferInput =\n\t\ttypeof input === 'string' ? Buffer.from(input, 'utf8') : input;\n\tconst encrypted = Buffer.concat([cipher.update(bufferInput), cipher.final()]);\n\tconst authTag = cipher.getAuthTag();\n\n\tconst output = Buffer.concat([iv, authTag, encrypted]);\n\n\treturn typeof input === 'string'\n\t\t? (output.toString('base64') as T)\n\t\t: (output as T);\n}\n\nexport function decrypt<T extends string | Buffer>(\n\tinput: T,\n\tdecryptionKey: string\n): T {\n\tconst encryptedBuffer =\n\t\ttypeof input === 'string'\n\t\t\t? Buffer.from(input, 'base64')\n\t\t\t: (input as Buffer);\n\n\tconst iv = encryptedBuffer.subarray(0, 12);\n\tconst authTag = encryptedBuffer.subarray(12, 28);\n\tconst encryptedData = encryptedBuffer.subarray(28);\n\n\tconst key = Buffer.from(decryptionKey, 'base64');\n\tconst decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);\n\tdecipher.setAuthTag(authTag);\n\n\tconst decrypted = Buffer.concat([\n\t\tdecipher.update(encryptedData),\n\t\tdecipher.final(),\n\t]);\n\n\treturn typeof input === 'string'\n\t\t? (decrypted.toString('utf8') as T)\n\t\t: (decrypted as T);\n}\n"],"names":["generateKey","length","crypto","__name","encrypt","input","encryptionKey","iv","key","cipher","bufferInput","encrypted","authTag","output","decrypt","decryptionKey","encryptedBuffer","encryptedData","decipher","decrypted"],"mappings":"0GAEO,SAASA,EAAYC,EAAS,GAAY,CAChD,OAAOC,EAAO,YAAYD,CAAM,EAAE,SAAS,KAAK,CACjD,CAFgBE,EAAAH,EAAA,eAIT,SAASI,EACfC,EACAC,EACI,CACJ,MAAMC,EAAKL,EAAO,YAAY,EAAE,EAC1BM,EAAM,OAAO,KAAKF,EAAe,QAAQ,EACzCG,EAASP,EAAO,eAAe,cAAeM,EAAKD,CAAE,EAErDG,EACL,OAAOL,GAAU,SAAW,OAAO,KAAKA,EAAO,MAAM,EAAIA,EACpDM,EAAY,OAAO,OAAO,CAACF,EAAO,OAAOC,CAAW,EAAGD,EAAO,MAAA,CAAO,CAAC,EACtEG,EAAUH,EAAO,WAAA,EAEjBI,EAAS,OAAO,OAAO,CAACN,EAAIK,EAASD,CAAS,CAAC,EAErD,OAAO,OAAON,GAAU,SACpBQ,EAAO,SAAS,QAAQ,EACxBA,CACL,CAlBgBV,EAAAC,EAAA,WAoBT,SAASU,EACfT,EACAU,EACI,CACJ,MAAMC,EACL,OAAOX,GAAU,SACd,OAAO,KAAKA,EAAO,QAAQ,EAC1BA,EAECE,EAAKS,EAAgB,SAAS,EAAG,EAAE,EACnCJ,EAAUI,EAAgB,SAAS,GAAI,EAAE,EACzCC,EAAgBD,EAAgB,SAAS,EAAE,EAE3CR,EAAM,OAAO,KAAKO,EAAe,QAAQ,EACzCG,EAAWhB,EAAO,iBAAiB,cAAeM,EAAKD,CAAE,EAC/DW,EAAS,WAAWN,CAAO,EAE3B,MAAMO,EAAY,OAAO,OAAO,CAC/BD,EAAS,OAAOD,CAAa,EAC7BC,EAAS,MAAA,CAAM,CACf,EAED,OAAO,OAAOb,GAAU,SACpBc,EAAU,SAAS,MAAM,EACzBA,CACL,CAzBgBhB,EAAAW,EAAA"}
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var i=require("./decryption-stream.cjs"),r=require("./encryption.cjs"),e=require("./signed-urls.cjs");require("node:crypto"),require("node:fs"),require("node:stream"),require("@md-oss/common/api/errors"),require("@md-oss/common/api/status-codes"),exports.createDecryptionStream=i.createDecryptionStream,exports.streamWithRange=i.streamWithRange,exports.decrypt=r.decrypt,exports.encrypt=r.encrypt,exports.generateKey=r.generateKey,exports.generateSignedUrl=e.generateSignedUrl,exports.verifySignedUrl=e.verifySignedUrl,exports.verifySignedUrlFromRequest=e.verifySignedUrlFromRequest,exports.withSignedAccess=e.withSignedAccess;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ export { createDecryptionStream, streamWithRange } from './decryption-stream.cjs';
2
+ export { decrypt, encrypt, generateKey } from './encryption.cjs';
3
+ export { SignedAccess, SignedAccessError, VerifySignedUrlFromRequestOptions, generateSignedUrl, verifySignedUrl, verifySignedUrlFromRequest, withSignedAccess } from './signed-urls.cjs';
4
+ import 'node:stream';
5
+ import '@md-oss/common/api/errors';
@@ -0,0 +1,5 @@
1
+ export { createDecryptionStream, streamWithRange } from './decryption-stream.mjs';
2
+ export { decrypt, encrypt, generateKey } from './encryption.mjs';
3
+ export { SignedAccess, SignedAccessError, VerifySignedUrlFromRequestOptions, generateSignedUrl, verifySignedUrl, verifySignedUrlFromRequest, withSignedAccess } from './signed-urls.mjs';
4
+ import 'node:stream';
5
+ import '@md-oss/common/api/errors';
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import{createDecryptionStream as n,streamWithRange as g}from"./decryption-stream.mjs";import{decrypt as c,encrypt as y,generateKey as d}from"./encryption.mjs";import{generateSignedUrl as S,verifySignedUrl as s,verifySignedUrlFromRequest as l,withSignedAccess as x}from"./signed-urls.mjs";import"node:crypto";import"node:fs";import"node:stream";import"@md-oss/common/api/errors";import"@md-oss/common/api/status-codes";export{n as createDecryptionStream,c as decrypt,y as encrypt,d as generateKey,S as generateSignedUrl,g as streamWithRange,s as verifySignedUrl,l as verifySignedUrlFromRequest,x as withSignedAccess};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ "use strict";var S=Object.defineProperty;var u=(r,e)=>S(r,"name",{value:e,configurable:!0});var a=require("node:crypto"),I=require("@md-oss/common/api/errors"),d=require("@md-oss/common/api/status-codes");const f=u((r,e,t)=>{const i=t===null?e:`${e}:${t}`;return a.createHmac("sha256",Buffer.from(r,"base64")).update(i).digest("hex")},"generateSignature");function g({secret:r,path:e,expires:t}){const i=t?t.valueOf():null,n=i?Math.floor(i/1e3):null,o=f(r,e,n);let l=`${e}?sig=${o}`;return n!==null&&(l+=`&expires=${n}`),{url:l,expiresUnixTs:n,sig:o}}u(g,"generateSignedUrl");function s({secret:r,path:e,expires:t,sig:i}){const n=f(r,e,t),o=Math.floor(Date.now()/1e3),l=typeof i!="string"?a.timingSafeEqual(Buffer.from(JSON.stringify(i)),Buffer.from(n)):i===n;return typeof i!="string"||!l?"InvalidSignatureError":t!==null&&(typeof t!="number"||t<o)?"ExpiredSignatureError":{path:e,expires:typeof t=="number"?t:o,sig:n}}u(s,"verifySignedUrl");function v({secret:r,expectedPath:e,query:t}){const{expires:i,sig:n}=t||{};if(!n)return{type:"invalid",error:"MISSING_SIGNATURE"};let o;try{o=s({secret:r,path:e,expires:i?Number(i):null,sig:n})}catch{return{type:"invalid",error:"INVALID_SIGNATURE"}}return typeof o=="string"?{type:"invalid",error:o==="InvalidSignatureError"?"INVALID_SIGNATURE":"EXPIRED_SIGNATURE"}:{type:"verified",sig:o.sig,expires:o.expires??null}}u(v,"verifySignedUrlFromRequest");function E(r,e){return r.type==="verified"?Promise.resolve(!0):r.error==="MISSING_SIGNATURE"||r.error!=="INVALID_SIGNATURE"&&r.error!=="EXPIRED_SIGNATURE"?e():Promise.resolve(new I.APIError(d.statusCodes.FORBIDDEN,{code:r.error,message:r.error==="INVALID_SIGNATURE"?"The provided signature is invalid.":"The provided signature has expired.",details:{error:r.error}}))}u(E,"withSignedAccess"),exports.generateSignedUrl=g,exports.verifySignedUrl=s,exports.verifySignedUrlFromRequest=v,exports.withSignedAccess=E;
2
+ //# sourceMappingURL=signed-urls.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signed-urls.cjs","sources":["../src/signed-urls.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport { APIError } from '@md-oss/common/api/errors';\nimport { statusCodes } from '@md-oss/common/api/status-codes';\n\ntype SignedUrlSchema = {\n\texpires?: number | undefined;\n\tsig?: string | undefined;\n\tref?: string | undefined;\n};\n\nconst generateSignature = (\n\tsecret: string,\n\tpath: string,\n\texpires: number | null\n): string => {\n\tconst payload = expires === null ? path : `${path}:${expires}`;\n\treturn crypto\n\t\t.createHmac('sha256', Buffer.from(secret, 'base64'))\n\t\t.update(payload)\n\t\t.digest('hex');\n};\n\nexport function generateSignedUrl({\n\tsecret,\n\tpath,\n\texpires,\n}: {\n\tsecret: string;\n\tpath: string;\n\texpires: Date | null;\n}): {\n\t/**\n\t * The signed URL.\n\t */\n\turl: string;\n\t/**\n\t * The expiration timestamp in UNIX format (seconds).\n\t */\n\texpiresUnixTs: number | null;\n\t/**\n\t * The signature used to verify the URL.\n\t */\n\tsig: string;\n} {\n\tconst expiresTs = expires ? expires.valueOf() : null;\n\tconst expiresTsUnix = expiresTs ? Math.floor(expiresTs / 1000) : null;\n\tconst signature = generateSignature(secret, path, expiresTsUnix);\n\n\tlet url = `${path}?sig=${signature}`;\n\n\tif (expiresTsUnix !== null) {\n\t\turl += `&expires=${expiresTsUnix}`;\n\t}\n\n\treturn {\n\t\turl,\n\t\texpiresUnixTs: expiresTsUnix,\n\t\tsig: signature,\n\t};\n}\n\nexport function verifySignedUrl({\n\tsecret,\n\tpath,\n\texpires,\n\tsig,\n}: {\n\t/**\n\t * The secret used to generate the signature.\n\t */\n\tsecret: string;\n\t/**\n\t * The path of the signed URL.\n\t */\n\tpath: string;\n\t/**\n\t * The expiration to verify against, in UNIX timestamp format (seconds).\n\t */\n\texpires: number | null;\n\t/**\n\t * The signature to verify.\n\t */\n\tsig?: unknown;\n}):\n\t| 'InvalidSignatureError'\n\t| 'ExpiredSignatureError'\n\t| {\n\t\t\tpath: string;\n\t\t\texpires: number;\n\t\t\tsig: string;\n\t } {\n\tconst expectedSig = generateSignature(secret, path, expires);\n\tconst now = Math.floor(Date.now() / 1000);\n\tconst match =\n\t\ttypeof sig !== 'string'\n\t\t\t? crypto.timingSafeEqual(\n\t\t\t\t\tBuffer.from(JSON.stringify(sig)),\n\t\t\t\t\tBuffer.from(expectedSig)\n\t\t\t\t)\n\t\t\t: sig === expectedSig;\n\n\tif (typeof sig !== 'string' || !match) {\n\t\treturn 'InvalidSignatureError';\n\t}\n\n\tif (expires !== null && (typeof expires !== 'number' || expires < now)) {\n\t\treturn 'ExpiredSignatureError';\n\t}\n\n\treturn {\n\t\tpath,\n\t\texpires: typeof expires === 'number' ? expires : now,\n\t\tsig: expectedSig,\n\t};\n}\n\nexport type VerifySignedUrlFromRequestOptions = {\n\t/** The expected path to verify (must match what was originally signed) */\n\texpectedPath: string;\n\t/** The query object */\n\tquery: SignedUrlSchema | undefined;\n\t/** The secret used to verify the signature */\n\tsecret: string;\n};\n\nexport type SignedAccessError =\n\t| 'MISSING_SIGNATURE'\n\t| 'INVALID_SIGNATURE'\n\t| 'EXPIRED_SIGNATURE';\nexport type SignedAccess =\n\t| {\n\t\t\ttype: 'verified';\n\t\t\tsig: string;\n\t\t\texpires: number | null;\n\t }\n\t| {\n\t\t\ttype: 'invalid';\n\t\t\terror: SignedAccessError;\n\t };\n\nexport function verifySignedUrlFromRequest({\n\tsecret,\n\texpectedPath,\n\tquery,\n}: VerifySignedUrlFromRequestOptions): SignedAccess {\n\tconst { expires, sig } = query || {};\n\n\tif (!sig) {\n\t\treturn {\n\t\t\ttype: 'invalid',\n\t\t\terror: 'MISSING_SIGNATURE',\n\t\t};\n\t}\n\n\tlet result: ReturnType<typeof verifySignedUrl>;\n\ttry {\n\t\tresult = verifySignedUrl({\n\t\t\tsecret,\n\t\t\tpath: expectedPath,\n\t\t\texpires: expires ? Number(expires) : null,\n\t\t\tsig,\n\t\t});\n\t} catch {\n\t\treturn {\n\t\t\ttype: 'invalid',\n\t\t\terror: 'INVALID_SIGNATURE',\n\t\t};\n\t}\n\n\tif (typeof result === 'string') {\n\t\treturn {\n\t\t\ttype: 'invalid',\n\t\t\terror:\n\t\t\t\tresult === 'InvalidSignatureError'\n\t\t\t\t\t? 'INVALID_SIGNATURE'\n\t\t\t\t\t: 'EXPIRED_SIGNATURE',\n\t\t};\n\t}\n\n\treturn {\n\t\ttype: 'verified',\n\t\tsig: result.sig,\n\t\texpires: result.expires ?? null,\n\t};\n}\n\nexport function withSignedAccess<T>(\n\tsignedAccess:\n\t\t| SignedAccess\n\t\t| {\n\t\t\t\ttype: 'invalid';\n\t\t\t\terror: string;\n\t\t },\n\tnoSignedAccessCheck: () => Promise<true | T>\n): Promise<true | T | APIError> {\n\tif (signedAccess.type === 'verified') {\n\t\treturn Promise.resolve(true);\n\t}\n\n\tif (\n\t\tsignedAccess.error === 'MISSING_SIGNATURE' ||\n\t\t(signedAccess.error !== 'INVALID_SIGNATURE' &&\n\t\t\tsignedAccess.error !== 'EXPIRED_SIGNATURE')\n\t) {\n\t\treturn noSignedAccessCheck();\n\t}\n\n\treturn Promise.resolve(\n\t\tnew APIError(statusCodes.FORBIDDEN, {\n\t\t\tcode: signedAccess.error,\n\t\t\tmessage:\n\t\t\t\tsignedAccess.error === 'INVALID_SIGNATURE'\n\t\t\t\t\t? 'The provided signature is invalid.'\n\t\t\t\t\t: 'The provided signature has expired.',\n\t\t\tdetails: {\n\t\t\t\terror: signedAccess.error,\n\t\t\t},\n\t\t})\n\t);\n}\n"],"names":["generateSignature","__name","secret","path","expires","payload","crypto","generateSignedUrl","expiresTs","expiresTsUnix","signature","url","verifySignedUrl","sig","expectedSig","now","match","verifySignedUrlFromRequest","expectedPath","query","result","withSignedAccess","signedAccess","noSignedAccessCheck","APIError","statusCodes"],"mappings":"6MAUA,MAAMA,EAAoBC,EAAA,CACzBC,EACAC,EACAC,IACY,CACZ,MAAMC,EAAUD,IAAY,KAAOD,EAAO,GAAGA,CAAI,IAAIC,CAAO,GAC5D,OAAOE,EACL,WAAW,SAAU,OAAO,KAAKJ,EAAQ,QAAQ,CAAC,EAClD,OAAOG,CAAO,EACd,OAAO,KAAK,CACf,EAV0B,qBAYnB,SAASE,EAAkB,CACjC,OAAAL,EACA,KAAAC,EACA,QAAAC,CACD,EAiBE,CACD,MAAMI,EAAYJ,EAAUA,EAAQ,QAAA,EAAY,KAC1CK,EAAgBD,EAAY,KAAK,MAAMA,EAAY,GAAI,EAAI,KAC3DE,EAAYV,EAAkBE,EAAQC,EAAMM,CAAa,EAE/D,IAAIE,EAAM,GAAGR,CAAI,QAAQO,CAAS,GAElC,OAAID,IAAkB,OACrBE,GAAO,YAAYF,CAAa,IAG1B,CACN,IAAAE,EACA,cAAeF,EACf,IAAKC,CAAA,CAEP,CArCgBT,EAAAM,EAAA,qBAuCT,SAASK,EAAgB,CAC/B,OAAAV,EACA,KAAAC,EACA,QAAAC,EACA,IAAAS,CACD,EAwBK,CACJ,MAAMC,EAAcd,EAAkBE,EAAQC,EAAMC,CAAO,EACrDW,EAAM,KAAK,MAAM,KAAK,IAAA,EAAQ,GAAI,EAClCC,EACL,OAAOH,GAAQ,SACZP,EAAO,gBACP,OAAO,KAAK,KAAK,UAAUO,CAAG,CAAC,EAC/B,OAAO,KAAKC,CAAW,CAAA,EAEvBD,IAAQC,EAEZ,OAAI,OAAOD,GAAQ,UAAY,CAACG,EACxB,wBAGJZ,IAAY,OAAS,OAAOA,GAAY,UAAYA,EAAUW,GAC1D,wBAGD,CACN,KAAAZ,EACA,QAAS,OAAOC,GAAY,SAAWA,EAAUW,EACjD,IAAKD,CAAA,CAEP,CArDgBb,EAAAW,EAAA,mBA+ET,SAASK,EAA2B,CAC1C,OAAAf,EACA,aAAAgB,EACA,MAAAC,CACD,EAAoD,CACnD,KAAM,CAAE,QAAAf,EAAS,IAAAS,CAAA,EAAQM,GAAS,CAAA,EAElC,GAAI,CAACN,EACJ,MAAO,CACN,KAAM,UACN,MAAO,mBAAA,EAIT,IAAIO,EACJ,GAAI,CACHA,EAASR,EAAgB,CACxB,OAAAV,EACA,KAAMgB,EACN,QAASd,EAAU,OAAOA,CAAO,EAAI,KACrC,IAAAS,CAAA,CACA,CACF,MAAQ,CACP,MAAO,CACN,KAAM,UACN,MAAO,mBAAA,CAET,CAEA,OAAI,OAAOO,GAAW,SACd,CACN,KAAM,UACN,MACCA,IAAW,wBACR,oBACA,mBAAA,EAIC,CACN,KAAM,WACN,IAAKA,EAAO,IACZ,QAASA,EAAO,SAAW,IAAA,CAE7B,CA5CgBnB,EAAAgB,EAAA,8BA8CT,SAASI,EACfC,EAMAC,EAC+B,CAC/B,OAAID,EAAa,OAAS,WAClB,QAAQ,QAAQ,EAAI,EAI3BA,EAAa,QAAU,qBACtBA,EAAa,QAAU,qBACvBA,EAAa,QAAU,oBAEjBC,EAAA,EAGD,QAAQ,QACd,IAAIC,EAAAA,SAASC,EAAAA,YAAY,UAAW,CACnC,KAAMH,EAAa,MACnB,QACCA,EAAa,QAAU,oBACpB,qCACA,sCACJ,QAAS,CACR,MAAOA,EAAa,KAAA,CACrB,CACA,CAAA,CAEH,CAjCgBrB,EAAAoB,EAAA"}
@@ -0,0 +1,72 @@
1
+ import { APIError } from '@md-oss/common/api/errors';
2
+
3
+ type SignedUrlSchema = {
4
+ expires?: number | undefined;
5
+ sig?: string | undefined;
6
+ ref?: string | undefined;
7
+ };
8
+ declare function generateSignedUrl({ secret, path, expires, }: {
9
+ secret: string;
10
+ path: string;
11
+ expires: Date | null;
12
+ }): {
13
+ /**
14
+ * The signed URL.
15
+ */
16
+ url: string;
17
+ /**
18
+ * The expiration timestamp in UNIX format (seconds).
19
+ */
20
+ expiresUnixTs: number | null;
21
+ /**
22
+ * The signature used to verify the URL.
23
+ */
24
+ sig: string;
25
+ };
26
+ declare function verifySignedUrl({ secret, path, expires, sig, }: {
27
+ /**
28
+ * The secret used to generate the signature.
29
+ */
30
+ secret: string;
31
+ /**
32
+ * The path of the signed URL.
33
+ */
34
+ path: string;
35
+ /**
36
+ * The expiration to verify against, in UNIX timestamp format (seconds).
37
+ */
38
+ expires: number | null;
39
+ /**
40
+ * The signature to verify.
41
+ */
42
+ sig?: unknown;
43
+ }): 'InvalidSignatureError' | 'ExpiredSignatureError' | {
44
+ path: string;
45
+ expires: number;
46
+ sig: string;
47
+ };
48
+ type VerifySignedUrlFromRequestOptions = {
49
+ /** The expected path to verify (must match what was originally signed) */
50
+ expectedPath: string;
51
+ /** The query object */
52
+ query: SignedUrlSchema | undefined;
53
+ /** The secret used to verify the signature */
54
+ secret: string;
55
+ };
56
+ type SignedAccessError = 'MISSING_SIGNATURE' | 'INVALID_SIGNATURE' | 'EXPIRED_SIGNATURE';
57
+ type SignedAccess = {
58
+ type: 'verified';
59
+ sig: string;
60
+ expires: number | null;
61
+ } | {
62
+ type: 'invalid';
63
+ error: SignedAccessError;
64
+ };
65
+ declare function verifySignedUrlFromRequest({ secret, expectedPath, query, }: VerifySignedUrlFromRequestOptions): SignedAccess;
66
+ declare function withSignedAccess<T>(signedAccess: SignedAccess | {
67
+ type: 'invalid';
68
+ error: string;
69
+ }, noSignedAccessCheck: () => Promise<true | T>): Promise<true | T | APIError>;
70
+
71
+ export { generateSignedUrl, verifySignedUrl, verifySignedUrlFromRequest, withSignedAccess };
72
+ export type { SignedAccess, SignedAccessError, VerifySignedUrlFromRequestOptions };
@@ -0,0 +1,72 @@
1
+ import { APIError } from '@md-oss/common/api/errors';
2
+
3
+ type SignedUrlSchema = {
4
+ expires?: number | undefined;
5
+ sig?: string | undefined;
6
+ ref?: string | undefined;
7
+ };
8
+ declare function generateSignedUrl({ secret, path, expires, }: {
9
+ secret: string;
10
+ path: string;
11
+ expires: Date | null;
12
+ }): {
13
+ /**
14
+ * The signed URL.
15
+ */
16
+ url: string;
17
+ /**
18
+ * The expiration timestamp in UNIX format (seconds).
19
+ */
20
+ expiresUnixTs: number | null;
21
+ /**
22
+ * The signature used to verify the URL.
23
+ */
24
+ sig: string;
25
+ };
26
+ declare function verifySignedUrl({ secret, path, expires, sig, }: {
27
+ /**
28
+ * The secret used to generate the signature.
29
+ */
30
+ secret: string;
31
+ /**
32
+ * The path of the signed URL.
33
+ */
34
+ path: string;
35
+ /**
36
+ * The expiration to verify against, in UNIX timestamp format (seconds).
37
+ */
38
+ expires: number | null;
39
+ /**
40
+ * The signature to verify.
41
+ */
42
+ sig?: unknown;
43
+ }): 'InvalidSignatureError' | 'ExpiredSignatureError' | {
44
+ path: string;
45
+ expires: number;
46
+ sig: string;
47
+ };
48
+ type VerifySignedUrlFromRequestOptions = {
49
+ /** The expected path to verify (must match what was originally signed) */
50
+ expectedPath: string;
51
+ /** The query object */
52
+ query: SignedUrlSchema | undefined;
53
+ /** The secret used to verify the signature */
54
+ secret: string;
55
+ };
56
+ type SignedAccessError = 'MISSING_SIGNATURE' | 'INVALID_SIGNATURE' | 'EXPIRED_SIGNATURE';
57
+ type SignedAccess = {
58
+ type: 'verified';
59
+ sig: string;
60
+ expires: number | null;
61
+ } | {
62
+ type: 'invalid';
63
+ error: SignedAccessError;
64
+ };
65
+ declare function verifySignedUrlFromRequest({ secret, expectedPath, query, }: VerifySignedUrlFromRequestOptions): SignedAccess;
66
+ declare function withSignedAccess<T>(signedAccess: SignedAccess | {
67
+ type: 'invalid';
68
+ error: string;
69
+ }, noSignedAccessCheck: () => Promise<true | T>): Promise<true | T | APIError>;
70
+
71
+ export { generateSignedUrl, verifySignedUrl, verifySignedUrlFromRequest, withSignedAccess };
72
+ export type { SignedAccess, SignedAccessError, VerifySignedUrlFromRequestOptions };
@@ -0,0 +1,2 @@
1
+ var s=Object.defineProperty;var u=(r,e)=>s(r,"name",{value:e,configurable:!0});import l from"node:crypto";import{APIError as S}from"@md-oss/common/api/errors";import{statusCodes as p}from"@md-oss/common/api/status-codes";const a=u((r,e,t)=>{const n=t===null?e:`${e}:${t}`;return l.createHmac("sha256",Buffer.from(r,"base64")).update(n).digest("hex")},"generateSignature");function d({secret:r,path:e,expires:t}){const n=t?t.valueOf():null,i=n?Math.floor(n/1e3):null,o=a(r,e,i);let f=`${e}?sig=${o}`;return i!==null&&(f+=`&expires=${i}`),{url:f,expiresUnixTs:i,sig:o}}u(d,"generateSignedUrl");function I({secret:r,path:e,expires:t,sig:n}){const i=a(r,e,t),o=Math.floor(Date.now()/1e3),f=typeof n!="string"?l.timingSafeEqual(Buffer.from(JSON.stringify(n)),Buffer.from(i)):n===i;return typeof n!="string"||!f?"InvalidSignatureError":t!==null&&(typeof t!="number"||t<o)?"ExpiredSignatureError":{path:e,expires:typeof t=="number"?t:o,sig:i}}u(I,"verifySignedUrl");function m({secret:r,expectedPath:e,query:t}){const{expires:n,sig:i}=t||{};if(!i)return{type:"invalid",error:"MISSING_SIGNATURE"};let o;try{o=I({secret:r,path:e,expires:n?Number(n):null,sig:i})}catch{return{type:"invalid",error:"INVALID_SIGNATURE"}}return typeof o=="string"?{type:"invalid",error:o==="InvalidSignatureError"?"INVALID_SIGNATURE":"EXPIRED_SIGNATURE"}:{type:"verified",sig:o.sig,expires:o.expires??null}}u(m,"verifySignedUrlFromRequest");function E(r,e){return r.type==="verified"?Promise.resolve(!0):r.error==="MISSING_SIGNATURE"||r.error!=="INVALID_SIGNATURE"&&r.error!=="EXPIRED_SIGNATURE"?e():Promise.resolve(new S(p.FORBIDDEN,{code:r.error,message:r.error==="INVALID_SIGNATURE"?"The provided signature is invalid.":"The provided signature has expired.",details:{error:r.error}}))}u(E,"withSignedAccess");export{d as generateSignedUrl,I as verifySignedUrl,m as verifySignedUrlFromRequest,E as withSignedAccess};
2
+ //# sourceMappingURL=signed-urls.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signed-urls.mjs","sources":["../src/signed-urls.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport { APIError } from '@md-oss/common/api/errors';\nimport { statusCodes } from '@md-oss/common/api/status-codes';\n\ntype SignedUrlSchema = {\n\texpires?: number | undefined;\n\tsig?: string | undefined;\n\tref?: string | undefined;\n};\n\nconst generateSignature = (\n\tsecret: string,\n\tpath: string,\n\texpires: number | null\n): string => {\n\tconst payload = expires === null ? path : `${path}:${expires}`;\n\treturn crypto\n\t\t.createHmac('sha256', Buffer.from(secret, 'base64'))\n\t\t.update(payload)\n\t\t.digest('hex');\n};\n\nexport function generateSignedUrl({\n\tsecret,\n\tpath,\n\texpires,\n}: {\n\tsecret: string;\n\tpath: string;\n\texpires: Date | null;\n}): {\n\t/**\n\t * The signed URL.\n\t */\n\turl: string;\n\t/**\n\t * The expiration timestamp in UNIX format (seconds).\n\t */\n\texpiresUnixTs: number | null;\n\t/**\n\t * The signature used to verify the URL.\n\t */\n\tsig: string;\n} {\n\tconst expiresTs = expires ? expires.valueOf() : null;\n\tconst expiresTsUnix = expiresTs ? Math.floor(expiresTs / 1000) : null;\n\tconst signature = generateSignature(secret, path, expiresTsUnix);\n\n\tlet url = `${path}?sig=${signature}`;\n\n\tif (expiresTsUnix !== null) {\n\t\turl += `&expires=${expiresTsUnix}`;\n\t}\n\n\treturn {\n\t\turl,\n\t\texpiresUnixTs: expiresTsUnix,\n\t\tsig: signature,\n\t};\n}\n\nexport function verifySignedUrl({\n\tsecret,\n\tpath,\n\texpires,\n\tsig,\n}: {\n\t/**\n\t * The secret used to generate the signature.\n\t */\n\tsecret: string;\n\t/**\n\t * The path of the signed URL.\n\t */\n\tpath: string;\n\t/**\n\t * The expiration to verify against, in UNIX timestamp format (seconds).\n\t */\n\texpires: number | null;\n\t/**\n\t * The signature to verify.\n\t */\n\tsig?: unknown;\n}):\n\t| 'InvalidSignatureError'\n\t| 'ExpiredSignatureError'\n\t| {\n\t\t\tpath: string;\n\t\t\texpires: number;\n\t\t\tsig: string;\n\t } {\n\tconst expectedSig = generateSignature(secret, path, expires);\n\tconst now = Math.floor(Date.now() / 1000);\n\tconst match =\n\t\ttypeof sig !== 'string'\n\t\t\t? crypto.timingSafeEqual(\n\t\t\t\t\tBuffer.from(JSON.stringify(sig)),\n\t\t\t\t\tBuffer.from(expectedSig)\n\t\t\t\t)\n\t\t\t: sig === expectedSig;\n\n\tif (typeof sig !== 'string' || !match) {\n\t\treturn 'InvalidSignatureError';\n\t}\n\n\tif (expires !== null && (typeof expires !== 'number' || expires < now)) {\n\t\treturn 'ExpiredSignatureError';\n\t}\n\n\treturn {\n\t\tpath,\n\t\texpires: typeof expires === 'number' ? expires : now,\n\t\tsig: expectedSig,\n\t};\n}\n\nexport type VerifySignedUrlFromRequestOptions = {\n\t/** The expected path to verify (must match what was originally signed) */\n\texpectedPath: string;\n\t/** The query object */\n\tquery: SignedUrlSchema | undefined;\n\t/** The secret used to verify the signature */\n\tsecret: string;\n};\n\nexport type SignedAccessError =\n\t| 'MISSING_SIGNATURE'\n\t| 'INVALID_SIGNATURE'\n\t| 'EXPIRED_SIGNATURE';\nexport type SignedAccess =\n\t| {\n\t\t\ttype: 'verified';\n\t\t\tsig: string;\n\t\t\texpires: number | null;\n\t }\n\t| {\n\t\t\ttype: 'invalid';\n\t\t\terror: SignedAccessError;\n\t };\n\nexport function verifySignedUrlFromRequest({\n\tsecret,\n\texpectedPath,\n\tquery,\n}: VerifySignedUrlFromRequestOptions): SignedAccess {\n\tconst { expires, sig } = query || {};\n\n\tif (!sig) {\n\t\treturn {\n\t\t\ttype: 'invalid',\n\t\t\terror: 'MISSING_SIGNATURE',\n\t\t};\n\t}\n\n\tlet result: ReturnType<typeof verifySignedUrl>;\n\ttry {\n\t\tresult = verifySignedUrl({\n\t\t\tsecret,\n\t\t\tpath: expectedPath,\n\t\t\texpires: expires ? Number(expires) : null,\n\t\t\tsig,\n\t\t});\n\t} catch {\n\t\treturn {\n\t\t\ttype: 'invalid',\n\t\t\terror: 'INVALID_SIGNATURE',\n\t\t};\n\t}\n\n\tif (typeof result === 'string') {\n\t\treturn {\n\t\t\ttype: 'invalid',\n\t\t\terror:\n\t\t\t\tresult === 'InvalidSignatureError'\n\t\t\t\t\t? 'INVALID_SIGNATURE'\n\t\t\t\t\t: 'EXPIRED_SIGNATURE',\n\t\t};\n\t}\n\n\treturn {\n\t\ttype: 'verified',\n\t\tsig: result.sig,\n\t\texpires: result.expires ?? null,\n\t};\n}\n\nexport function withSignedAccess<T>(\n\tsignedAccess:\n\t\t| SignedAccess\n\t\t| {\n\t\t\t\ttype: 'invalid';\n\t\t\t\terror: string;\n\t\t },\n\tnoSignedAccessCheck: () => Promise<true | T>\n): Promise<true | T | APIError> {\n\tif (signedAccess.type === 'verified') {\n\t\treturn Promise.resolve(true);\n\t}\n\n\tif (\n\t\tsignedAccess.error === 'MISSING_SIGNATURE' ||\n\t\t(signedAccess.error !== 'INVALID_SIGNATURE' &&\n\t\t\tsignedAccess.error !== 'EXPIRED_SIGNATURE')\n\t) {\n\t\treturn noSignedAccessCheck();\n\t}\n\n\treturn Promise.resolve(\n\t\tnew APIError(statusCodes.FORBIDDEN, {\n\t\t\tcode: signedAccess.error,\n\t\t\tmessage:\n\t\t\t\tsignedAccess.error === 'INVALID_SIGNATURE'\n\t\t\t\t\t? 'The provided signature is invalid.'\n\t\t\t\t\t: 'The provided signature has expired.',\n\t\t\tdetails: {\n\t\t\t\terror: signedAccess.error,\n\t\t\t},\n\t\t})\n\t);\n}\n"],"names":["generateSignature","__name","secret","path","expires","payload","crypto","generateSignedUrl","expiresTs","expiresTsUnix","signature","url","verifySignedUrl","sig","expectedSig","now","match","verifySignedUrlFromRequest","expectedPath","query","result","withSignedAccess","signedAccess","noSignedAccessCheck","APIError","statusCodes"],"mappings":"6NAUA,MAAMA,EAAoBC,EAAA,CACzBC,EACAC,EACAC,IACY,CACZ,MAAMC,EAAUD,IAAY,KAAOD,EAAO,GAAGA,CAAI,IAAIC,CAAO,GAC5D,OAAOE,EACL,WAAW,SAAU,OAAO,KAAKJ,EAAQ,QAAQ,CAAC,EAClD,OAAOG,CAAO,EACd,OAAO,KAAK,CACf,EAV0B,qBAYnB,SAASE,EAAkB,CACjC,OAAAL,EACA,KAAAC,EACA,QAAAC,CACD,EAiBE,CACD,MAAMI,EAAYJ,EAAUA,EAAQ,QAAA,EAAY,KAC1CK,EAAgBD,EAAY,KAAK,MAAMA,EAAY,GAAI,EAAI,KAC3DE,EAAYV,EAAkBE,EAAQC,EAAMM,CAAa,EAE/D,IAAIE,EAAM,GAAGR,CAAI,QAAQO,CAAS,GAElC,OAAID,IAAkB,OACrBE,GAAO,YAAYF,CAAa,IAG1B,CACN,IAAAE,EACA,cAAeF,EACf,IAAKC,CAAA,CAEP,CArCgBT,EAAAM,EAAA,qBAuCT,SAASK,EAAgB,CAC/B,OAAAV,EACA,KAAAC,EACA,QAAAC,EACA,IAAAS,CACD,EAwBK,CACJ,MAAMC,EAAcd,EAAkBE,EAAQC,EAAMC,CAAO,EACrDW,EAAM,KAAK,MAAM,KAAK,IAAA,EAAQ,GAAI,EAClCC,EACL,OAAOH,GAAQ,SACZP,EAAO,gBACP,OAAO,KAAK,KAAK,UAAUO,CAAG,CAAC,EAC/B,OAAO,KAAKC,CAAW,CAAA,EAEvBD,IAAQC,EAEZ,OAAI,OAAOD,GAAQ,UAAY,CAACG,EACxB,wBAGJZ,IAAY,OAAS,OAAOA,GAAY,UAAYA,EAAUW,GAC1D,wBAGD,CACN,KAAAZ,EACA,QAAS,OAAOC,GAAY,SAAWA,EAAUW,EACjD,IAAKD,CAAA,CAEP,CArDgBb,EAAAW,EAAA,mBA+ET,SAASK,EAA2B,CAC1C,OAAAf,EACA,aAAAgB,EACA,MAAAC,CACD,EAAoD,CACnD,KAAM,CAAE,QAAAf,EAAS,IAAAS,CAAA,EAAQM,GAAS,CAAA,EAElC,GAAI,CAACN,EACJ,MAAO,CACN,KAAM,UACN,MAAO,mBAAA,EAIT,IAAIO,EACJ,GAAI,CACHA,EAASR,EAAgB,CACxB,OAAAV,EACA,KAAMgB,EACN,QAASd,EAAU,OAAOA,CAAO,EAAI,KACrC,IAAAS,CAAA,CACA,CACF,MAAQ,CACP,MAAO,CACN,KAAM,UACN,MAAO,mBAAA,CAET,CAEA,OAAI,OAAOO,GAAW,SACd,CACN,KAAM,UACN,MACCA,IAAW,wBACR,oBACA,mBAAA,EAIC,CACN,KAAM,WACN,IAAKA,EAAO,IACZ,QAASA,EAAO,SAAW,IAAA,CAE7B,CA5CgBnB,EAAAgB,EAAA,8BA8CT,SAASI,EACfC,EAMAC,EAC+B,CAC/B,OAAID,EAAa,OAAS,WAClB,QAAQ,QAAQ,EAAI,EAI3BA,EAAa,QAAU,qBACtBA,EAAa,QAAU,qBACvBA,EAAa,QAAU,oBAEjBC,EAAA,EAGD,QAAQ,QACd,IAAIC,EAASC,EAAY,UAAW,CACnC,KAAMH,EAAa,MACnB,QACCA,EAAa,QAAU,oBACpB,qCACA,sCACJ,QAAS,CACR,MAAOA,EAAa,KAAA,CACrB,CACA,CAAA,CAEH,CAjCgBrB,EAAAoB,EAAA"}
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@md-oss/security",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "publishConfig": {
6
+ "access": "public",
7
+ "registry": "https://registry.npmjs.org/"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+ssh://git@github.com/Mirasaki-OSS/monorepo-template.git",
12
+ "directory": "vendor/security"
13
+ },
14
+ "type": "module",
15
+ "description": "Cryptographic utilities for encryption, signed URLs, and secure streaming",
16
+ "license": "ISC",
17
+ "main": "./dist/index.cjs",
18
+ "module": "./dist/index.mjs",
19
+ "types": "./dist/index.d.cts",
20
+ "files": [
21
+ "dist/**/*",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "exports": {
26
+ ".": {
27
+ "require": {
28
+ "types": "./dist/index.d.cts",
29
+ "default": "./dist/index.cjs"
30
+ },
31
+ "import": {
32
+ "types": "./dist/index.d.mts",
33
+ "default": "./dist/index.mjs"
34
+ }
35
+ },
36
+ "./decryption-stream": {
37
+ "require": {
38
+ "types": "./dist/decryption-stream.d.cts",
39
+ "default": "./dist/decryption-stream.cjs"
40
+ },
41
+ "import": {
42
+ "types": "./dist/decryption-stream.d.mts",
43
+ "default": "./dist/decryption-stream.mjs"
44
+ }
45
+ },
46
+ "./encryption": {
47
+ "require": {
48
+ "types": "./dist/encryption.d.cts",
49
+ "default": "./dist/encryption.cjs"
50
+ },
51
+ "import": {
52
+ "types": "./dist/encryption.d.mts",
53
+ "default": "./dist/encryption.mjs"
54
+ }
55
+ },
56
+ "./signed-urls": {
57
+ "require": {
58
+ "types": "./dist/signed-urls.d.cts",
59
+ "default": "./dist/signed-urls.cjs"
60
+ },
61
+ "import": {
62
+ "types": "./dist/signed-urls.d.mts",
63
+ "default": "./dist/signed-urls.mjs"
64
+ }
65
+ }
66
+ },
67
+ "dependencies": {
68
+ "@md-oss/config": "^0.1.0",
69
+ "@md-oss/common": "^0.1.0"
70
+ },
71
+ "devDependencies": {
72
+ "@types/node": "^25.0.9",
73
+ "pkgroll": "^2.21.5",
74
+ "typescript": "^5.9.3"
75
+ },
76
+ "scripts": {
77
+ "build": "pkgroll --minify --clean-dist --sourcemap --define.process.env.NODE_ENV='\"production\"' --define.DEBUG=false",
78
+ "clean": "git clean -xdf .turbo dist node_modules tsconfig.tsbuildinfo",
79
+ "dev": "tsc --watch",
80
+ "typecheck": "tsc --noEmit"
81
+ }
82
+ }