@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 +5 -0
- package/README.md +303 -0
- package/dist/decryption-stream.cjs +2 -0
- package/dist/decryption-stream.cjs.map +1 -0
- package/dist/decryption-stream.d.cts +6 -0
- package/dist/decryption-stream.d.mts +6 -0
- package/dist/decryption-stream.mjs +2 -0
- package/dist/decryption-stream.mjs.map +1 -0
- package/dist/encryption.cjs +2 -0
- package/dist/encryption.cjs.map +1 -0
- package/dist/encryption.d.cts +5 -0
- package/dist/encryption.d.mts +5 -0
- package/dist/encryption.mjs +2 -0
- package/dist/encryption.mjs.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/dist/signed-urls.cjs +2 -0
- package/dist/signed-urls.cjs.map +1 -0
- package/dist/signed-urls.d.cts +72 -0
- package/dist/signed-urls.d.mts +72 -0
- package/dist/signed-urls.mjs +2 -0
- package/dist/signed-urls.mjs.map +1 -0
- package/package.json +82 -0
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":""}
|
package/dist/index.d.cts
ADDED
|
@@ -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';
|
package/dist/index.d.mts
ADDED
|
@@ -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
|
+
}
|