@owlmeans/basic-envelope 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 +21 -0
- package/README.md +616 -0
- package/build/.gitkeep +0 -0
- package/build/consts.d.ts +6 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +7 -0
- package/build/consts.js.map +1 -0
- package/build/helper.d.ts +1 -0
- package/build/helper.d.ts.map +1 -0
- package/build/helper.js +2 -0
- package/build/helper.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -0
- package/build/model.d.ts +4 -0
- package/build/model.d.ts.map +1 -0
- package/build/model.js +61 -0
- package/build/model.js.map +1 -0
- package/build/types.d.ts +20 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/utils/index.d.ts +2 -0
- package/build/utils/index.d.ts.map +1 -0
- package/build/utils/index.js +2 -0
- package/build/utils/index.js.map +1 -0
- package/build/utils/model.d.ts +6 -0
- package/build/utils/model.d.ts.map +1 -0
- package/build/utils/model.js +6 -0
- package/build/utils/model.js.map +1 -0
- package/package.json +39 -0
- package/src/consts.ts +8 -0
- package/src/helper.ts +0 -0
- package/src/index.ts +4 -0
- package/src/model.ts +74 -0
- package/src/types.ts +21 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/model.ts +14 -0
- package/tsconfig.json +14 -0
- package/tsconfig.tsbuildinfo +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 OwlMeans Common — Fullstack typescript framework
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
# @owlmeans/basic-envelope
|
|
2
|
+
|
|
3
|
+
A lightweight cryptographic message envelope library for OwlMeans Common applications. This package provides secure message wrapping, signing, and verification capabilities using Ed25519 cryptographic signatures, designed as an alternative to JWT/JWE tokens with enhanced security and simplicity.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `@owlmeans/basic-envelope` package enables secure message transmission and storage by providing:
|
|
8
|
+
|
|
9
|
+
- **Message Envelopes**: Lightweight containers for messages with built-in expiration and type identification
|
|
10
|
+
- **Cryptographic Signing**: Ed25519 signature support for message authentication and integrity
|
|
11
|
+
- **Flexible Serialization**: Multiple output formats including wrapped strings and tokenized representations
|
|
12
|
+
- **TTL Support**: Time-to-live functionality for automatic message expiration
|
|
13
|
+
- **Type Safety**: Full TypeScript support with generic message types
|
|
14
|
+
|
|
15
|
+
This package is particularly useful for creating secure authentication tokens, signed API requests, and encrypted inter-service communication.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @owlmeans/basic-envelope
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Core Concepts
|
|
24
|
+
|
|
25
|
+
### Envelope Structure
|
|
26
|
+
|
|
27
|
+
An envelope is a container that wraps a message with metadata including:
|
|
28
|
+
- **Type identifier**: Categorizes the message purpose
|
|
29
|
+
- **Message payload**: The actual data (string or object)
|
|
30
|
+
- **Signature**: Optional cryptographic signature for verification
|
|
31
|
+
- **Timestamp**: Creation time for ordering and TTL calculations
|
|
32
|
+
- **TTL**: Time-to-live for automatic expiration
|
|
33
|
+
|
|
34
|
+
### Envelope Kinds
|
|
35
|
+
|
|
36
|
+
The package supports different serialization formats:
|
|
37
|
+
- **Wrap**: Base64-encoded JSON representation
|
|
38
|
+
- **Token**: Compact tokenized format for transmission
|
|
39
|
+
|
|
40
|
+
## API Reference
|
|
41
|
+
|
|
42
|
+
### Factory Functions
|
|
43
|
+
|
|
44
|
+
The package uses factory functions for object creation following OwlMeans Common conventions:
|
|
45
|
+
|
|
46
|
+
#### `makeEnvelopeModel<T>(type, kind?): EnvelopeModel<T>`
|
|
47
|
+
|
|
48
|
+
Creates an envelope model for wrapping and managing messages with cryptographic capabilities.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { makeEnvelopeModel, EnvelopeKind } from '@owlmeans/basic-envelope'
|
|
52
|
+
|
|
53
|
+
// Create a new envelope
|
|
54
|
+
const envelope = makeEnvelopeModel<UserData>('user-session')
|
|
55
|
+
|
|
56
|
+
// Create from existing wrapped envelope
|
|
57
|
+
const wrappedEnvelope = makeEnvelopeModel('eyJ0IjoiZXhhbXBsZSIsLi4u', EnvelopeKind.Wrap)
|
|
58
|
+
|
|
59
|
+
// Create from tokenized envelope
|
|
60
|
+
const tokenizedEnvelope = makeEnvelopeModel('example:msg:signature', EnvelopeKind.Token)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Parameters:**
|
|
64
|
+
- `type`: string - Type identifier for new envelopes, or serialized envelope for reconstruction
|
|
65
|
+
- `kind`: EnvelopeKind (optional) - Specifies format when reconstructing from serialized data
|
|
66
|
+
|
|
67
|
+
**Returns:** EnvelopeModel<T> instance with methods for message management and cryptographic operations
|
|
68
|
+
|
|
69
|
+
**Generic Type Parameter:**
|
|
70
|
+
- `T`: Type of the message payload (defaults to string)
|
|
71
|
+
|
|
72
|
+
### Core Interfaces
|
|
73
|
+
|
|
74
|
+
#### `Envelope`
|
|
75
|
+
Core envelope structure containing message and metadata.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
interface Envelope {
|
|
79
|
+
t: string // Type identifier for categorizing messages
|
|
80
|
+
msg: string // Base64-encoded message content
|
|
81
|
+
sig?: string // Optional cryptographic signature
|
|
82
|
+
dt: number // Creation timestamp (milliseconds)
|
|
83
|
+
ttl: number | null // Time-to-live in milliseconds (null = no expiration)
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Properties:**
|
|
88
|
+
- `t`: Message type identifier used for categorization and routing
|
|
89
|
+
- `msg`: Base64-encoded message payload (automatically encoded by envelope methods)
|
|
90
|
+
- `sig`: Optional Ed25519 signature for message authentication and integrity verification
|
|
91
|
+
- `dt`: Creation timestamp used for ordering and TTL calculations (milliseconds since epoch)
|
|
92
|
+
- `ttl`: Time-to-live for automatic expiration, null indicates no expiration
|
|
93
|
+
|
|
94
|
+
#### `EnvelopeModel<T>`
|
|
95
|
+
Factory-created envelope manager providing comprehensive message operations and cryptographic functions.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
interface EnvelopeModel<T extends {} | string = string> {
|
|
99
|
+
envelope: Envelope
|
|
100
|
+
|
|
101
|
+
// Message management methods
|
|
102
|
+
send<M extends T>(msg: M, ttl?: number | null): EnvelopeModel
|
|
103
|
+
message<M extends T>(preserve?: boolean): M
|
|
104
|
+
type(): string
|
|
105
|
+
|
|
106
|
+
// Serialization methods
|
|
107
|
+
wrap(): string
|
|
108
|
+
tokenize(): string
|
|
109
|
+
|
|
110
|
+
// Cryptographic methods
|
|
111
|
+
sign(key: KeyPairModel, kind?: EnvelopeKind): Promise<string>
|
|
112
|
+
verify(key: KeyPairModel): Promise<boolean>
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### EnvelopeModel Methods Reference
|
|
117
|
+
|
|
118
|
+
#### Message Management Methods
|
|
119
|
+
|
|
120
|
+
**`send<M extends T>(msg: M, ttl?: number | null): EnvelopeModel`**
|
|
121
|
+
|
|
122
|
+
Sets the message content and optional TTL, with automatic encoding based on message type.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// Send a string message
|
|
126
|
+
envelope.send('Hello, World!')
|
|
127
|
+
|
|
128
|
+
// Send an object with 1-hour TTL
|
|
129
|
+
envelope.send({ userId: '123', action: 'login' }, 3600000)
|
|
130
|
+
|
|
131
|
+
// Send with no expiration
|
|
132
|
+
envelope.send(messageData, null)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- **Purpose**: Encodes and stores message data in the envelope with optional expiration
|
|
136
|
+
- **Behavior**:
|
|
137
|
+
- String messages are stored directly
|
|
138
|
+
- Objects are JSON-serialized then Base64-encoded automatically
|
|
139
|
+
- Updates TTL if provided, maintains existing TTL if not specified
|
|
140
|
+
- **Parameters**:
|
|
141
|
+
- `msg: M`: Message payload extending type T
|
|
142
|
+
- `ttl?: number | null`: Time-to-live in milliseconds, null for no expiration
|
|
143
|
+
- **Returns**: The same EnvelopeModel instance for method chaining
|
|
144
|
+
- **Throws**: No exceptions - handles all message types gracefully
|
|
145
|
+
|
|
146
|
+
**`message<M extends T>(preserve?: boolean): M`**
|
|
147
|
+
|
|
148
|
+
Retrieves and decodes the message content from the envelope.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// Get decoded object message
|
|
152
|
+
const userData = envelope.message<UserData>()
|
|
153
|
+
|
|
154
|
+
// Get raw string message without decoding
|
|
155
|
+
const rawMessage = envelope.message<string>(true)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
- **Purpose**: Extracts the original message from the envelope with configurable decoding
|
|
159
|
+
- **Behavior**:
|
|
160
|
+
- Default (`preserve` false): Attempts JSON parsing followed by Base64 decoding
|
|
161
|
+
- Preserve mode (`preserve` true): Returns raw message string without processing
|
|
162
|
+
- Gracefully falls back to string if JSON parsing fails
|
|
163
|
+
- **Parameters**:
|
|
164
|
+
- `preserve?: boolean`: If true, returns raw message without decoding
|
|
165
|
+
- **Returns**: Decoded message of type M
|
|
166
|
+
- **Error Handling**: Silent fallback to string type on decode errors
|
|
167
|
+
|
|
168
|
+
**`type(): string`**
|
|
169
|
+
|
|
170
|
+
Returns the type identifier of the envelope for message categorization.
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
const messageType = envelope.type() // e.g., 'user-session'
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- **Purpose**: Provides access to the envelope's type identifier
|
|
177
|
+
- **Returns**: String identifier used for message routing and categorization
|
|
178
|
+
- **Use Cases**: Message dispatching, route determination, debugging
|
|
179
|
+
|
|
180
|
+
#### Serialization Methods
|
|
181
|
+
|
|
182
|
+
**`wrap(): string`**
|
|
183
|
+
|
|
184
|
+
Serializes the envelope to a Base64-encoded JSON string for secure transmission.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
const wrappedEnvelope = envelope.wrap()
|
|
188
|
+
// Returns: "eyJ0IjoiZXhhbXBsZSIsIm1zZyI6IkhlbGxvIiwiZHQiOjE2Mzk..."
|
|
189
|
+
|
|
190
|
+
// Can be reconstructed later
|
|
191
|
+
const restored = makeEnvelopeModel(wrappedEnvelope, EnvelopeKind.Wrap)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- **Purpose**: Creates a wrapped representation suitable for storage or secure transmission
|
|
195
|
+
- **Behavior**: JSON-serializes the complete envelope then Base64-encodes the result
|
|
196
|
+
- **Returns**: Base64-encoded envelope string
|
|
197
|
+
- **Use Cases**: Database storage, HTTP headers, secure URL parameters
|
|
198
|
+
- **Security**: Preserves all envelope data including signatures
|
|
199
|
+
|
|
200
|
+
**`tokenize(): string`**
|
|
201
|
+
|
|
202
|
+
Creates a compact token format for efficient transmission and parsing.
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const token = envelope.tokenize()
|
|
206
|
+
// Returns: "user-session:SGVsbG8gV29ybGQ:signature_if_present"
|
|
207
|
+
|
|
208
|
+
// Reconstruct from token
|
|
209
|
+
const restored = makeEnvelopeModel(token, EnvelopeKind.Token)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
- **Purpose**: Creates a human-readable, compact token representation
|
|
213
|
+
- **Behavior**: Combines type, message, and signature with colon separators
|
|
214
|
+
- **Format**: `type:base64_message:signature` (signature omitted if not present)
|
|
215
|
+
- **Returns**: Compact token string
|
|
216
|
+
- **Use Cases**: API tokens, authentication headers, URL fragments, debugging
|
|
217
|
+
- **Advantages**: Easily parseable, more compact than wrapped format
|
|
218
|
+
|
|
219
|
+
#### Cryptographic Methods
|
|
220
|
+
|
|
221
|
+
**`sign(key: KeyPairModel, kind?: EnvelopeKind): Promise<string>`**
|
|
222
|
+
|
|
223
|
+
Signs the envelope with Ed25519 cryptographic signature for authentication and integrity.
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import { makeKeyPairModel } from '@owlmeans/basic-keys'
|
|
227
|
+
|
|
228
|
+
const keyPair = makeKeyPairModel()
|
|
229
|
+
await keyPair.generate()
|
|
230
|
+
|
|
231
|
+
// Sign and get signature only
|
|
232
|
+
const signature = await envelope.sign(keyPair)
|
|
233
|
+
|
|
234
|
+
// Sign and get wrapped envelope
|
|
235
|
+
const signedWrapped = await envelope.sign(keyPair, EnvelopeKind.Wrap)
|
|
236
|
+
|
|
237
|
+
// Sign and get tokenized envelope
|
|
238
|
+
const signedToken = await envelope.sign(keyPair, EnvelopeKind.Token)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
- **Purpose**: Provides cryptographic authentication and tamper-detection
|
|
242
|
+
- **Behavior**:
|
|
243
|
+
- Creates Ed25519 signature of envelope data (excluding existing signature field)
|
|
244
|
+
- Stores signature in `envelope.sig` property for future verification
|
|
245
|
+
- Returns formatted output based on `kind` parameter
|
|
246
|
+
- **Parameters**:
|
|
247
|
+
- `key: KeyPairModel`: Ed25519 key pair instance for signing
|
|
248
|
+
- `kind?: EnvelopeKind`: Output format - Wrap, Token, or raw signature (default)
|
|
249
|
+
- **Returns**: Promise resolving to signature string or formatted envelope
|
|
250
|
+
- **Security**: Uses industry-standard Ed25519 signatures for cryptographic authenticity
|
|
251
|
+
- **Error Handling**: Propagates key pair errors (e.g., missing private key)
|
|
252
|
+
|
|
253
|
+
**`verify(key: KeyPairModel): Promise<boolean>`**
|
|
254
|
+
|
|
255
|
+
Verifies envelope signature and validates TTL expiration for complete authenticity check.
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// Verify signature and TTL
|
|
259
|
+
const isValid = await envelope.verify(keyPair)
|
|
260
|
+
|
|
261
|
+
if (isValid) {
|
|
262
|
+
console.log('Envelope is authentic and not expired')
|
|
263
|
+
// Safe to process message
|
|
264
|
+
} else {
|
|
265
|
+
console.log('Envelope is invalid or expired - reject')
|
|
266
|
+
// Handle invalid envelope
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
- **Purpose**: Validates both cryptographic authenticity and temporal freshness
|
|
271
|
+
- **Behavior**:
|
|
272
|
+
- Returns `false` immediately if no signature is present
|
|
273
|
+
- Checks TTL expiration if TTL is set (compares `dt + ttl` with current time)
|
|
274
|
+
- Verifies Ed25519 signature against envelope data (excluding signature field)
|
|
275
|
+
- All conditions must pass for `true` result
|
|
276
|
+
- **Parameters**:
|
|
277
|
+
- `key: KeyPairModel`: Key pair instance with public key for verification
|
|
278
|
+
- **Returns**: Promise resolving to boolean validation result
|
|
279
|
+
- **Security**: Combines cryptographic verification with temporal validation
|
|
280
|
+
- **Use Cases**: Authentication token validation, message integrity checking
|
|
281
|
+
|
|
282
|
+
### Factory Functions
|
|
283
|
+
|
|
284
|
+
#### `makeEnvelopeModel<T>(type: string, kind?: EnvelopeKind): EnvelopeModel<T>`
|
|
285
|
+
|
|
286
|
+
Creates a new envelope model instance.
|
|
287
|
+
|
|
288
|
+
**Parameters:**
|
|
289
|
+
- `type`: Type identifier for the envelope
|
|
290
|
+
- `kind`: Optional kind for deserializing existing envelopes
|
|
291
|
+
|
|
292
|
+
**Returns:** EnvelopeModel instance
|
|
293
|
+
|
|
294
|
+
**Example:**
|
|
295
|
+
```typescript
|
|
296
|
+
import { makeEnvelopeModel } from '@owlmeans/basic-envelope'
|
|
297
|
+
|
|
298
|
+
// Create new envelope
|
|
299
|
+
const envelope = makeEnvelopeModel<UserData>('user-profile')
|
|
300
|
+
|
|
301
|
+
// Send message
|
|
302
|
+
envelope.send({ userId: '123', name: 'John' }, 300000) // 5 minutes TTL
|
|
303
|
+
|
|
304
|
+
// Get wrapped representation
|
|
305
|
+
const wrapped = envelope.wrap()
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Constants and Enums
|
|
309
|
+
|
|
310
|
+
#### `EnvelopeKind`
|
|
311
|
+
Enumeration defining available envelope serialization formats.
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
enum EnvelopeKind {
|
|
315
|
+
Wrap = 'wrap', // Base64-encoded JSON format - secure, complete data preservation
|
|
316
|
+
Token = 'token' // Compact colon-separated format - human-readable, efficient
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Usage Guidelines:**
|
|
321
|
+
- Use `Wrap` for secure storage, HTTP headers, or when preserving all metadata is critical
|
|
322
|
+
- Use `Token` for API tokens, URLs, or when human readability and compactness are important
|
|
323
|
+
|
|
324
|
+
#### `DEFAULT_TTL`
|
|
325
|
+
Default time-to-live value applied to new envelopes.
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
const DEFAULT_TTL = 5 * 60 * 1000 // 5 minutes in milliseconds
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Behavior**: Applied automatically when creating new envelopes unless explicitly overridden
|
|
332
|
+
|
|
333
|
+
## Error Handling
|
|
334
|
+
|
|
335
|
+
The basic-envelope package follows OwlMeans Common error handling patterns and integrates with cryptographic operations that may throw errors.
|
|
336
|
+
|
|
337
|
+
### Common Error Scenarios
|
|
338
|
+
|
|
339
|
+
#### Key-Related Errors
|
|
340
|
+
```typescript
|
|
341
|
+
import { makeKeyPairModel } from '@owlmeans/basic-keys'
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
const keyPair = makeKeyPairModel()
|
|
345
|
+
// Error: Attempting to sign without generating keys
|
|
346
|
+
await envelope.sign(keyPair)
|
|
347
|
+
} catch (error) {
|
|
348
|
+
console.error('Key operation failed:', error.message)
|
|
349
|
+
// Handle missing private key or key generation errors
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### TTL Validation Errors
|
|
354
|
+
```typescript
|
|
355
|
+
// Expired envelope verification
|
|
356
|
+
const expiredEnvelope = makeEnvelopeModel('expired-message')
|
|
357
|
+
expiredEnvelope.send('data', 1) // 1ms TTL
|
|
358
|
+
await new Promise(resolve => setTimeout(resolve, 10)) // Wait for expiration
|
|
359
|
+
|
|
360
|
+
const isValid = await expiredEnvelope.verify(keyPair)
|
|
361
|
+
// Returns false - envelope has expired
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
#### Malformed Data Errors
|
|
365
|
+
```typescript
|
|
366
|
+
try {
|
|
367
|
+
// Invalid wrapped data
|
|
368
|
+
const malformed = makeEnvelopeModel('invalid-base64', EnvelopeKind.Wrap)
|
|
369
|
+
} catch (error) {
|
|
370
|
+
console.error('Failed to parse envelope:', error.message)
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Error Types and Handling
|
|
375
|
+
|
|
376
|
+
The package gracefully handles various error conditions:
|
|
377
|
+
|
|
378
|
+
1. **Silent Fallbacks**: Message decoding errors fall back to string representation
|
|
379
|
+
2. **Validation Failures**: Verification methods return `false` rather than throwing
|
|
380
|
+
3. **Key Errors**: Cryptographic errors are propagated from the key pair implementation
|
|
381
|
+
4. **Parse Errors**: Malformed envelope data results in constructor exceptions
|
|
382
|
+
|
|
383
|
+
## Advanced Usage Examples
|
|
384
|
+
|
|
385
|
+
### Authentication Token System
|
|
386
|
+
```typescript
|
|
387
|
+
import { makeEnvelopeModel, EnvelopeKind } from '@owlmeans/basic-envelope'
|
|
388
|
+
import { makeKeyPairModel } from '@owlmeans/basic-keys'
|
|
389
|
+
|
|
390
|
+
// Setup authentication system
|
|
391
|
+
const authKeys = makeKeyPairModel()
|
|
392
|
+
await authKeys.generate()
|
|
393
|
+
|
|
394
|
+
// Create authentication token
|
|
395
|
+
const sessionEnvelope = makeEnvelopeModel<SessionData>('user-session')
|
|
396
|
+
const sessionData = {
|
|
397
|
+
userId: 'user123',
|
|
398
|
+
permissions: ['read', 'write'],
|
|
399
|
+
loginTime: Date.now()
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Sign and create token with 1-hour expiration
|
|
403
|
+
const authToken = await sessionEnvelope
|
|
404
|
+
.send(sessionData, 3600000)
|
|
405
|
+
.sign(authKeys, EnvelopeKind.Token)
|
|
406
|
+
|
|
407
|
+
console.log('Auth token:', authToken)
|
|
408
|
+
// user-session:eyJ1c2VySWQ...base64...:signature
|
|
409
|
+
|
|
410
|
+
// Later: verify token
|
|
411
|
+
const receivedEnvelope = makeEnvelopeModel(authToken, EnvelopeKind.Token)
|
|
412
|
+
const isValidToken = await receivedEnvelope.verify(authKeys)
|
|
413
|
+
|
|
414
|
+
if (isValidToken) {
|
|
415
|
+
const session = receivedEnvelope.message<SessionData>()
|
|
416
|
+
console.log('Valid session for user:', session.userId)
|
|
417
|
+
} else {
|
|
418
|
+
console.log('Invalid or expired token')
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Secure Message Transmission
|
|
423
|
+
```typescript
|
|
424
|
+
// Sender side
|
|
425
|
+
const messageEnvelope = makeEnvelopeModel<ApiRequest>('api-request')
|
|
426
|
+
const apiRequest = {
|
|
427
|
+
method: 'POST',
|
|
428
|
+
endpoint: '/users',
|
|
429
|
+
data: { name: 'John Doe', email: 'john@example.com' }
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Create signed message with 30-second TTL
|
|
433
|
+
const signedMessage = await messageEnvelope
|
|
434
|
+
.send(apiRequest, 30000)
|
|
435
|
+
.sign(senderKeys, EnvelopeKind.Wrap)
|
|
436
|
+
|
|
437
|
+
// Transmit wrapped message
|
|
438
|
+
await sendToServer(signedMessage)
|
|
439
|
+
|
|
440
|
+
// Receiver side
|
|
441
|
+
const receivedEnvelope = makeEnvelopeModel(signedMessage, EnvelopeKind.Wrap)
|
|
442
|
+
|
|
443
|
+
// Verify authenticity and freshness
|
|
444
|
+
if (await receivedEnvelope.verify(senderKeys)) {
|
|
445
|
+
const request = receivedEnvelope.message<ApiRequest>()
|
|
446
|
+
// Process authenticated API request
|
|
447
|
+
console.log('Processing request:', request.method, request.endpoint)
|
|
448
|
+
} else {
|
|
449
|
+
// Reject unauthenticated or expired request
|
|
450
|
+
throw new Error('Invalid request signature or expired')
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Multi-Format Message Exchange
|
|
455
|
+
```typescript
|
|
456
|
+
// Create message once
|
|
457
|
+
const dataEnvelope = makeEnvelopeModel<UserProfile>('user-profile')
|
|
458
|
+
dataEnvelope.send({
|
|
459
|
+
id: '123',
|
|
460
|
+
name: 'Alice Smith',
|
|
461
|
+
preferences: { theme: 'dark', notifications: true }
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
// Generate multiple formats for different use cases
|
|
465
|
+
const forDatabase = dataEnvelope.wrap() // Secure storage
|
|
466
|
+
const forUrl = dataEnvelope.tokenize() // URL transmission
|
|
467
|
+
const forHeaders = await dataEnvelope.sign(keys, EnvelopeKind.Wrap) // Signed secure
|
|
468
|
+
|
|
469
|
+
// Each format can be reconstructed to the same data
|
|
470
|
+
const fromDb = makeEnvelopeModel(forDatabase, EnvelopeKind.Wrap)
|
|
471
|
+
const fromUrl = makeEnvelopeModel(forUrl, EnvelopeKind.Token)
|
|
472
|
+
const fromHeaders = makeEnvelopeModel(forHeaders, EnvelopeKind.Wrap)
|
|
473
|
+
|
|
474
|
+
// All contain the same user profile data
|
|
475
|
+
console.log('Same data:',
|
|
476
|
+
fromDb.message<UserProfile>().id ===
|
|
477
|
+
fromUrl.message<UserProfile>().id &&
|
|
478
|
+
fromHeaders.message<UserProfile>().id
|
|
479
|
+
)
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## Integration Patterns
|
|
483
|
+
|
|
484
|
+
### Express.js Middleware
|
|
485
|
+
```typescript
|
|
486
|
+
import express from 'express'
|
|
487
|
+
import { makeEnvelopeModel, EnvelopeKind } from '@owlmeans/basic-envelope'
|
|
488
|
+
|
|
489
|
+
// Authentication middleware using envelope tokens
|
|
490
|
+
const authenticateToken = (serverKeys) => async (req, res, next) => {
|
|
491
|
+
const authHeader = req.headers.authorization
|
|
492
|
+
if (!authHeader?.startsWith('Bearer ')) {
|
|
493
|
+
return res.status(401).json({ error: 'Missing auth token' })
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
const token = authHeader.substring(7)
|
|
498
|
+
const envelope = makeEnvelopeModel(token, EnvelopeKind.Token)
|
|
499
|
+
|
|
500
|
+
if (await envelope.verify(serverKeys)) {
|
|
501
|
+
req.user = envelope.message()
|
|
502
|
+
next()
|
|
503
|
+
} else {
|
|
504
|
+
res.status(401).json({ error: 'Invalid or expired token' })
|
|
505
|
+
}
|
|
506
|
+
} catch (error) {
|
|
507
|
+
res.status(400).json({ error: 'Malformed token' })
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const app = express()
|
|
512
|
+
app.use('/api', authenticateToken(serverKeys))
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### React Hook Integration
|
|
516
|
+
```typescript
|
|
517
|
+
import { useState, useEffect } from 'react'
|
|
518
|
+
import { makeEnvelopeModel, EnvelopeKind } from '@owlmeans/basic-envelope'
|
|
519
|
+
|
|
520
|
+
// Custom hook for secure message handling
|
|
521
|
+
function useSecureMessage<T>(initialMessage?: T) {
|
|
522
|
+
const [envelope] = useState(() => makeEnvelopeModel<T>('react-state'))
|
|
523
|
+
const [message, setMessage] = useState<T | null>(initialMessage || null)
|
|
524
|
+
|
|
525
|
+
const updateMessage = (newMessage: T, ttl?: number) => {
|
|
526
|
+
envelope.send(newMessage, ttl)
|
|
527
|
+
setMessage(newMessage)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const getSecureToken = async (keys) => {
|
|
531
|
+
return await envelope.sign(keys, EnvelopeKind.Token)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const verifyMessage = async (token: string, keys) => {
|
|
535
|
+
try {
|
|
536
|
+
const received = makeEnvelopeModel(token, EnvelopeKind.Token)
|
|
537
|
+
if (await received.verify(keys)) {
|
|
538
|
+
const data = received.message<T>()
|
|
539
|
+
setMessage(data)
|
|
540
|
+
return true
|
|
541
|
+
}
|
|
542
|
+
} catch (error) {
|
|
543
|
+
console.error('Message verification failed:', error)
|
|
544
|
+
}
|
|
545
|
+
return false
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return { message, updateMessage, getSecureToken, verifyMessage }
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
## Security Considerations
|
|
553
|
+
|
|
554
|
+
### Cryptographic Security
|
|
555
|
+
1. **Key Management**: Use `@owlmeans/basic-keys` for proper Ed25519 key generation and management
|
|
556
|
+
2. **Signature Verification**: Always verify signatures before processing messages in production
|
|
557
|
+
3. **TTL Usage**: Implement appropriate TTL values to prevent replay attacks
|
|
558
|
+
4. **Key Rotation**: Regularly rotate signing keys and update verification keys
|
|
559
|
+
|
|
560
|
+
### Data Protection
|
|
561
|
+
1. **Sensitive Data**: Avoid including sensitive data in message types or identifiers
|
|
562
|
+
2. **Message Encoding**: Automatic Base64 encoding provides basic obfuscation but not encryption
|
|
563
|
+
3. **Transmission Security**: Use HTTPS/TLS for envelope transmission over networks
|
|
564
|
+
4. **Storage Security**: Encrypt wrapped envelopes before database storage
|
|
565
|
+
|
|
566
|
+
### Validation Best Practices
|
|
567
|
+
1. **Input Validation**: Validate envelope data before processing messages
|
|
568
|
+
2. **Type Safety**: Use TypeScript generics for compile-time message type checking
|
|
569
|
+
3. **Error Handling**: Implement proper error handling for verification failures
|
|
570
|
+
4. **Audit Logging**: Log envelope verification results for security monitoring
|
|
571
|
+
|
|
572
|
+
## Performance Considerations
|
|
573
|
+
|
|
574
|
+
### Memory Usage
|
|
575
|
+
- **Large Messages**: Consider message size impact on memory usage
|
|
576
|
+
- **Batch Operations**: Process multiple envelopes efficiently rather than individually
|
|
577
|
+
- **Key Caching**: Cache key pairs to avoid repeated generation operations
|
|
578
|
+
|
|
579
|
+
### Serialization Performance
|
|
580
|
+
- **Format Selection**: Choose appropriate format (wrap vs token) based on use case
|
|
581
|
+
- **Compression**: Consider compression for large messages before envelope wrapping
|
|
582
|
+
- **JSON Parsing**: Message decoding involves JSON parsing - optimize for large objects
|
|
583
|
+
|
|
584
|
+
## Best Practices
|
|
585
|
+
|
|
586
|
+
1. **TTL Management**: Set appropriate TTL values based on use case security requirements
|
|
587
|
+
2. **Type Safety**: Always use TypeScript generics for compile-time message validation
|
|
588
|
+
3. **Error Handling**: Implement comprehensive error handling for all envelope operations
|
|
589
|
+
4. **Key Management**: Use proper key generation and storage practices from `@owlmeans/basic-keys`
|
|
590
|
+
5. **Format Selection**: Choose envelope format (wrap vs token) based on transmission requirements
|
|
591
|
+
6. **Verification**: Always verify envelopes in production environments before processing
|
|
592
|
+
7. **Logging**: Implement appropriate logging for debugging without exposing sensitive data
|
|
593
|
+
|
|
594
|
+
## Package Structure
|
|
595
|
+
|
|
596
|
+
Following OwlMeans Common library structure:
|
|
597
|
+
|
|
598
|
+
- **types**: TypeScript interfaces (`Envelope`, `EnvelopeModel`)
|
|
599
|
+
- **model**: Factory functions (`makeEnvelopeModel`)
|
|
600
|
+
- **consts**: Constants and enumerations (`EnvelopeKind`, `DEFAULT_TTL`)
|
|
601
|
+
- **helper**: Utility functions for envelope operations
|
|
602
|
+
- **utils**: Internal utilities for serialization and parsing
|
|
603
|
+
|
|
604
|
+
## Dependencies
|
|
605
|
+
|
|
606
|
+
This package depends on:
|
|
607
|
+
- `@owlmeans/basic-keys` - Ed25519 key pair management for cryptographic operations
|
|
608
|
+
- `@scure/base` - Base64 and UTF-8 encoding utilities
|
|
609
|
+
- `uuid` - UUID generation (if needed for message identification)
|
|
610
|
+
|
|
611
|
+
## Related Packages
|
|
612
|
+
|
|
613
|
+
- [`@owlmeans/basic-keys`](../basic-keys) - Cryptographic key management
|
|
614
|
+
- [`@owlmeans/auth`](../auth) - Authentication system integration
|
|
615
|
+
- [`@owlmeans/error`](../error) - Error handling system
|
|
616
|
+
|
package/build/.gitkeep
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,oBAAY,YAAY;IACtB,IAAI,SAAS;IACb,KAAK,UAAU;CAChB;AAED,eAAO,MAAM,WAAW,QAAgB,CAAA"}
|
package/build/consts.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAN,IAAY,YAGX;AAHD,WAAY,YAAY;IACtB,6BAAa,CAAA;IACb,+BAAe,CAAA;AACjB,CAAC,EAHW,YAAY,KAAZ,YAAY,QAGvB;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=helper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":""}
|
package/build/helper.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":""}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA"}
|
package/build/model.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,YAAY,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAG/C,eAAO,MAAM,iBAAiB,GAAI,CAAC,SAAS,EAAE,GAAG,MAAM,iBAC/C,MAAM,SAAS,YAAY,KAChC,aAAa,CAAC,CAAC,CAkEjB,CAAA"}
|
package/build/model.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { base64, utf8 } from '@scure/base';
|
|
2
|
+
import { DEFAULT_TTL, EnvelopeKind } from './consts.js';
|
|
3
|
+
import { tokenize, untokenize, unwrap, wrap } from './utils/model.js';
|
|
4
|
+
export const makeEnvelopeModel = (type, kind) => {
|
|
5
|
+
const model = {
|
|
6
|
+
envelope: kind == null ? {
|
|
7
|
+
t: type,
|
|
8
|
+
msg: '',
|
|
9
|
+
dt: new Date().getTime(),
|
|
10
|
+
ttl: DEFAULT_TTL
|
|
11
|
+
} : kind === EnvelopeKind.Wrap
|
|
12
|
+
? unwrap(type) : untokenize(type),
|
|
13
|
+
send: (msg, ttl) => {
|
|
14
|
+
model.envelope.msg = typeof msg == 'string' ? msg : base64.encode(utf8.decode(JSON.stringify(msg)));
|
|
15
|
+
if (typeof ttl !== 'undefined') {
|
|
16
|
+
model.envelope.ttl = ttl;
|
|
17
|
+
}
|
|
18
|
+
return model;
|
|
19
|
+
},
|
|
20
|
+
message: (preserve) => {
|
|
21
|
+
if (preserve === true) {
|
|
22
|
+
return model.envelope.msg;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(utf8.encode(base64.decode(model.envelope.msg)));
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
return model.envelope.msg;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
type: () => model.envelope.t,
|
|
32
|
+
wrap: () => wrap(model.envelope),
|
|
33
|
+
tokenize: () => tokenize(model.envelope),
|
|
34
|
+
sign: async (key, kind) => {
|
|
35
|
+
model.envelope.sig = await key.sign(model.envelope);
|
|
36
|
+
switch (kind) {
|
|
37
|
+
case EnvelopeKind.Wrap:
|
|
38
|
+
return model.wrap();
|
|
39
|
+
case EnvelopeKind.Token:
|
|
40
|
+
return model.tokenize();
|
|
41
|
+
default:
|
|
42
|
+
return model.envelope.sig;
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
verify: async (key) => {
|
|
46
|
+
const signature = model.envelope.sig;
|
|
47
|
+
if (signature == null) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (model.envelope.ttl != null
|
|
51
|
+
&& model.envelope.dt + model.envelope.ttl < new Date().getTime()) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const data = { ...model.envelope };
|
|
55
|
+
delete data.sig;
|
|
56
|
+
return await key.verify(data, signature);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
return model;
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { KeyPairModel } from '@owlmeans/basic-keys';
|
|
2
|
+
import type { EnvelopeKind } from './consts';
|
|
3
|
+
export interface Envelope {
|
|
4
|
+
t: string;
|
|
5
|
+
msg: string;
|
|
6
|
+
sig?: string;
|
|
7
|
+
dt: number;
|
|
8
|
+
ttl: number | null;
|
|
9
|
+
}
|
|
10
|
+
export interface EnvelopeModel<T extends {} | string = string> {
|
|
11
|
+
envelope: Envelope;
|
|
12
|
+
send: <M extends T>(msg: M, ttl?: number | null) => EnvelopeModel;
|
|
13
|
+
wrap: () => string;
|
|
14
|
+
tokenize: () => string;
|
|
15
|
+
message: <M extends T>(preserve?: boolean) => M;
|
|
16
|
+
type: () => string;
|
|
17
|
+
sign: (key: KeyPairModel, kind?: EnvelopeKind) => Promise<string>;
|
|
18
|
+
verify: (key: KeyPairModel) => Promise<boolean>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE5C,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,MAAM,CAAA;IACT,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CACnB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,EAAE,GAAG,MAAM,GAAG,MAAM;IAC3D,QAAQ,EAAE,QAAQ,CAAA;IAClB,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,aAAa,CAAA;IACjE,IAAI,EAAE,MAAM,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,MAAM,CAAA;IACtB,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,CAAC,CAAA;IAC/C,IAAI,EAAE,MAAM,MAAM,CAAA;IAClB,IAAI,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACjE,MAAM,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAChD"}
|
package/build/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Envelope } from '../types.js';
|
|
2
|
+
export declare const wrap: (object: Envelope) => string;
|
|
3
|
+
export declare const tokenize: (object: Envelope) => string;
|
|
4
|
+
export declare const untokenize: (token: string) => Envelope;
|
|
5
|
+
export declare const unwrap: (envelope: string) => Envelope;
|
|
6
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/utils/model.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,eAAO,MAAM,IAAI,WAAY,QAAQ,KAAG,MACY,CAAA;AAEpD,eAAO,MAAM,QAAQ,WAAY,QAAQ,KAAG,MACgB,CAAA;AAE5D,eAAO,MAAM,UAAU,UAAW,MAAM,KAAG,QACY,CAAA;AAEvD,eAAO,MAAM,MAAM,aAAc,MAAM,KAAG,QACQ,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { base64, base64urlnopad, utf8 } from '@scure/base';
|
|
2
|
+
export const wrap = (object) => base64.encode(utf8.decode(JSON.stringify(object)));
|
|
3
|
+
export const tokenize = (object) => base64urlnopad.encode(utf8.decode(JSON.stringify(object)));
|
|
4
|
+
export const untokenize = (token) => JSON.parse(utf8.encode(base64urlnopad.decode(token)));
|
|
5
|
+
export const unwrap = (envelope) => JSON.parse(utf8.encode(base64.decode(envelope)));
|
|
6
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/utils/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAG1D,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,MAAgB,EAAU,EAAE,CAC/C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;AAEpD,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,MAAgB,EAAU,EAAE,CACnD,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;AAE5D,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAa,EAAY,EAAE,CACpD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAEvD,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,QAAgB,EAAY,EAAE,CACnD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@owlmeans/basic-envelope",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "tsc -b",
|
|
7
|
+
"dev": "sleep 36 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
|
|
8
|
+
"watch": "tsc -b -w --preserveWatchOutput --pretty"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"owlkeys": "./build/bin.js"
|
|
12
|
+
},
|
|
13
|
+
"main": "build/index.js",
|
|
14
|
+
"module": "build/index.js",
|
|
15
|
+
"types": "build/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"import": "./build/index.js",
|
|
19
|
+
"require": "./build/index.js",
|
|
20
|
+
"default": "./build/index.js",
|
|
21
|
+
"module": "./build/index.js",
|
|
22
|
+
"types": "./build/index.d.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"nodemon": "^3.1.7",
|
|
27
|
+
"typescript": "^5.6.3"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@noble/curves": "^1.6.0",
|
|
31
|
+
"@noble/hashes": "^1.5.0",
|
|
32
|
+
"@owlmeans/basic-keys": "^0.1.0",
|
|
33
|
+
"@scure/base": "^1.1.9"
|
|
34
|
+
},
|
|
35
|
+
"private": false,
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/consts.ts
ADDED
package/src/helper.ts
ADDED
|
File without changes
|
package/src/index.ts
ADDED
package/src/model.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { base64, utf8 } from '@scure/base'
|
|
2
|
+
import { DEFAULT_TTL, EnvelopeKind } from './consts.js'
|
|
3
|
+
import type { EnvelopeModel } from './types.js'
|
|
4
|
+
import { tokenize, untokenize, unwrap, wrap } from './utils/model.js'
|
|
5
|
+
|
|
6
|
+
export const makeEnvelopeModel = <T extends {} | string = string>(
|
|
7
|
+
type: string, kind?: EnvelopeKind
|
|
8
|
+
): EnvelopeModel<T> => {
|
|
9
|
+
const model: EnvelopeModel = {
|
|
10
|
+
envelope: kind == null ? {
|
|
11
|
+
t: type,
|
|
12
|
+
msg: '',
|
|
13
|
+
dt: new Date().getTime(),
|
|
14
|
+
ttl: DEFAULT_TTL
|
|
15
|
+
} : kind === EnvelopeKind.Wrap
|
|
16
|
+
? unwrap(type) : untokenize(type),
|
|
17
|
+
|
|
18
|
+
send: (msg, ttl) => {
|
|
19
|
+
model.envelope.msg = typeof msg == 'string' ? msg : base64.encode(utf8.decode(JSON.stringify(msg)))
|
|
20
|
+
if (typeof ttl !== 'undefined') {
|
|
21
|
+
model.envelope.ttl = ttl
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return model
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
message: <T>(preserve?: boolean) => {
|
|
28
|
+
if (preserve === true) {
|
|
29
|
+
return model.envelope.msg as T
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(utf8.encode(base64.decode(model.envelope.msg))) as T
|
|
33
|
+
} catch (e) {
|
|
34
|
+
return model.envelope.msg as T
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
type: () => model.envelope.t,
|
|
39
|
+
|
|
40
|
+
wrap: () => wrap(model.envelope),
|
|
41
|
+
|
|
42
|
+
tokenize: () => tokenize(model.envelope),
|
|
43
|
+
|
|
44
|
+
sign: async (key, kind) => {
|
|
45
|
+
model.envelope.sig = await key.sign(model.envelope)
|
|
46
|
+
switch (kind) {
|
|
47
|
+
case EnvelopeKind.Wrap:
|
|
48
|
+
return model.wrap()
|
|
49
|
+
case EnvelopeKind.Token:
|
|
50
|
+
return model.tokenize()
|
|
51
|
+
default:
|
|
52
|
+
return model.envelope.sig
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
verify: async key => {
|
|
57
|
+
const signature = model.envelope.sig
|
|
58
|
+
if (signature == null) {
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
if (model.envelope.ttl != null
|
|
62
|
+
&& model.envelope.dt + model.envelope.ttl < new Date().getTime()) {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const data = { ...model.envelope }
|
|
67
|
+
delete data.sig
|
|
68
|
+
|
|
69
|
+
return await key.verify(data, signature)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return model
|
|
74
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { KeyPairModel } from '@owlmeans/basic-keys'
|
|
2
|
+
import type { EnvelopeKind } from './consts'
|
|
3
|
+
|
|
4
|
+
export interface Envelope {
|
|
5
|
+
t: string
|
|
6
|
+
msg: string
|
|
7
|
+
sig?: string
|
|
8
|
+
dt: number
|
|
9
|
+
ttl: number | null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface EnvelopeModel<T extends {} | string = string> {
|
|
13
|
+
envelope: Envelope
|
|
14
|
+
send: <M extends T>(msg: M, ttl?: number | null) => EnvelopeModel
|
|
15
|
+
wrap: () => string
|
|
16
|
+
tokenize: () => string
|
|
17
|
+
message: <M extends T>(preserve?: boolean) => M
|
|
18
|
+
type: () => string
|
|
19
|
+
sign: (key: KeyPairModel, kind?: EnvelopeKind) => Promise<string>
|
|
20
|
+
verify: (key: KeyPairModel) => Promise<boolean>
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { base64, base64urlnopad, utf8 } from '@scure/base'
|
|
2
|
+
import type { Envelope } from '../types.js'
|
|
3
|
+
|
|
4
|
+
export const wrap = (object: Envelope): string =>
|
|
5
|
+
base64.encode(utf8.decode(JSON.stringify(object)))
|
|
6
|
+
|
|
7
|
+
export const tokenize = (object: Envelope): string =>
|
|
8
|
+
base64urlnopad.encode(utf8.decode(JSON.stringify(object)))
|
|
9
|
+
|
|
10
|
+
export const untokenize = (token: string): Envelope =>
|
|
11
|
+
JSON.parse(utf8.encode(base64urlnopad.decode(token)))
|
|
12
|
+
|
|
13
|
+
export const unwrap = (envelope: string): Envelope =>
|
|
14
|
+
JSON.parse(utf8.encode(base64.decode(envelope)))
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"../tsconfig.default.json",
|
|
4
|
+
],
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"rootDir": "./src/", /* Specify the root folder within your source files. */
|
|
7
|
+
"outDir": "./build/", /* Specify an output folder for all emitted files. */
|
|
8
|
+
},
|
|
9
|
+
"exclude": [
|
|
10
|
+
"./dist/**/*",
|
|
11
|
+
"./build/**/*",
|
|
12
|
+
"./*.ts"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/consts.ts","./src/helper.ts","./src/index.ts","./src/model.ts","./src/types.ts","./src/utils/index.ts","./src/utils/model.ts"],"version":"5.6.3"}
|