@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 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,6 @@
1
+ export declare enum EnvelopeKind {
2
+ Wrap = "wrap",
3
+ Token = "token"
4
+ }
5
+ export declare const DEFAULT_TTL: number;
6
+ //# sourceMappingURL=consts.d.ts.map
@@ -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"}
@@ -0,0 +1,7 @@
1
+ export var EnvelopeKind;
2
+ (function (EnvelopeKind) {
3
+ EnvelopeKind["Wrap"] = "wrap";
4
+ EnvelopeKind["Token"] = "token";
5
+ })(EnvelopeKind || (EnvelopeKind = {}));
6
+ export const DEFAULT_TTL = 5 * 60 * 1000;
7
+ //# sourceMappingURL=consts.js.map
@@ -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":""}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=helper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ export * from './types.js';
2
+ export * from './model.js';
3
+ export * from './consts.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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,4 @@
1
+ export * from './types.js';
2
+ export * from './model.js';
3
+ export * from './consts.js';
4
+ //# sourceMappingURL=index.js.map
@@ -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"}
@@ -0,0 +1,4 @@
1
+ import { EnvelopeKind } from './consts.js';
2
+ import type { EnvelopeModel } from './types.js';
3
+ export declare const makeEnvelopeModel: <T extends {} | string = string>(type: string, kind?: EnvelopeKind) => EnvelopeModel<T>;
4
+ //# sourceMappingURL=model.d.ts.map
@@ -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":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAEvD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAErE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,IAAY,EAAE,IAAmB,EACf,EAAE;IACpB,MAAM,KAAK,GAAkB;QAC3B,QAAQ,EAAE,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC;YACvB,CAAC,EAAE,IAAI;YACP,GAAG,EAAE,EAAE;YACP,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE;YACxB,GAAG,EAAE,WAAW;SACjB,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,IAAI;YAC5B,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;QAEnC,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACjB,KAAK,CAAC,QAAQ,CAAC,GAAG,GAAG,OAAO,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YACnG,IAAI,OAAO,GAAG,KAAK,WAAW,EAAE,CAAC;gBAC/B,KAAK,CAAC,QAAQ,CAAC,GAAG,GAAG,GAAG,CAAA;YAC1B,CAAC;YAED,OAAO,KAAK,CAAA;QACd,CAAC;QAED,OAAO,EAAE,CAAI,QAAkB,EAAE,EAAE;YACjC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACtB,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAQ,CAAA;YAChC,CAAC;YACD,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAM,CAAA;YACxE,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAQ,CAAA;YAChC,CAAC;QACH,CAAC;QAED,IAAI,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAE5B,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;QAEhC,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC;QAExC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YACxB,KAAK,CAAC,QAAQ,CAAC,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;YACnD,QAAQ,IAAI,EAAE,CAAC;gBACb,KAAK,YAAY,CAAC,IAAI;oBACpB,OAAO,KAAK,CAAC,IAAI,EAAE,CAAA;gBACrB,KAAK,YAAY,CAAC,KAAK;oBACrB,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAA;gBACzB;oBACE,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAA;YAC7B,CAAC;QACH,CAAC;QAED,MAAM,EAAE,KAAK,EAAC,GAAG,EAAC,EAAE;YAClB,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAA;YACpC,IAAI,SAAS,IAAI,IAAI,EAAE,CAAC;gBACtB,OAAO,KAAK,CAAA;YACd,CAAC;YACD,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,IAAI,IAAI;mBACzB,KAAK,CAAC,QAAQ,CAAC,EAAE,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC;gBACnE,OAAO,KAAK,CAAA;YACd,CAAC;YAED,MAAM,IAAI,GAAG,EAAE,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAA;YAClC,OAAO,IAAI,CAAC,GAAG,CAAA;YAEf,OAAO,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;QAC1C,CAAC;KACF,CAAA;IAED,OAAO,KAAK,CAAA;AACd,CAAC,CAAA"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export * from './model.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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,2 @@
1
+ export * from './model.js';
2
+ //# sourceMappingURL=index.js.map
@@ -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
@@ -0,0 +1,8 @@
1
+
2
+ export enum EnvelopeKind {
3
+ Wrap = 'wrap',
4
+ Token = 'token'
5
+ }
6
+
7
+ export const DEFAULT_TTL = 5 * 60 * 1000
8
+
package/src/helper.ts ADDED
File without changes
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+
2
+ export * from './types.js'
3
+ export * from './model.js'
4
+ export * from './consts.js'
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,2 @@
1
+
2
+ export * from './model.js'
@@ -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"}