@hkdigital/lib-core 0.4.56 → 0.4.58

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.
@@ -0,0 +1,275 @@
1
+ # JWT Utilities
2
+
3
+ A comprehensive JSON Web Token (JWT) library for signing, verifying, and
4
+ decoding JWT tokens with proper error handling and type safety.
5
+
6
+ ## Features
7
+
8
+ - **Sign tokens** with customizable algorithms and expiration
9
+ - **Verify tokens** with signature validation and expiration checks
10
+ - **Decode payloads** without verification for inspection purposes
11
+ - **Generate secret keys** for HMAC algorithms
12
+ - **Error handling** with specific error types for different failure modes
13
+ - **Type safety** with JSDoc type annotations
14
+
15
+ ## Quick Start
16
+
17
+ ```javascript
18
+ import { sign, verify, decodePayload, expiresAtUTC } from '$lib/auth/jwt.js';
19
+
20
+ // Create a token
21
+ const payload = { userId: 123, username: 'john' };
22
+ const secret = 'your-secret-key';
23
+ const token = sign(payload, secret);
24
+
25
+ // Verify and decode
26
+ const decoded = verify(token, secret);
27
+ console.log(decoded.userId); // 123
28
+
29
+ // Decode without verification (for inspection)
30
+ const payload = decodePayload(token);
31
+ console.log(payload.exp); // expiration timestamp
32
+
33
+ // Get human-readable expiration
34
+ const expiresAt = expiresAtUTC(payload);
35
+ console.log(expiresAt); // "Sun, 01 Jan 2023 00:00:00 GMT"
36
+ ```
37
+
38
+ ## API Reference
39
+
40
+ ### sign(claims, secretOrPrivateKey, options?)
41
+
42
+ Creates a JSON Web Token with the specified claims.
43
+
44
+ **Parameters:**
45
+ - `claims` (object) - JWT payload/claims
46
+ - `secretOrPrivateKey` (string|Buffer) - Secret for HMAC or private key
47
+ - `options` (object, optional) - Signing options
48
+
49
+ **Options:**
50
+ - `algorithm` (string) - Algorithm to use (default: 'HS512')
51
+ - `expiresIn` (string|number) - Token expiration (default: '24h')
52
+ - `issuer` (string) - Token issuer
53
+ - `audience` (string) - Token audience
54
+ - `subject` (string) - Token subject
55
+
56
+ **Returns:** `string` - JWT token
57
+
58
+ **Example:**
59
+ ```javascript
60
+ const token = sign(
61
+ { userId: 123, role: 'admin' },
62
+ 'secret-key',
63
+ {
64
+ algorithm: 'HS256',
65
+ expiresIn: '1h',
66
+ issuer: 'my-app'
67
+ }
68
+ );
69
+ ```
70
+
71
+ ### verify(token, secretOrPrivateKey, options?)
72
+
73
+ Verifies and decodes a JWT token.
74
+
75
+ **Parameters:**
76
+ - `token` (string) - JWT token to verify
77
+ - `secretOrPrivateKey` (string|Buffer) - Secret for HMAC or public key
78
+ - `options` (object, optional) - Verification options
79
+
80
+ **Options:**
81
+ - `algorithms` (string[]) - Allowed algorithms (default: ['HS512'])
82
+ - `issuer` (string) - Expected issuer
83
+ - `audience` (string) - Expected audience
84
+ - `ignoreExpiration` (boolean) - Skip expiration validation
85
+
86
+ **Returns:** `object` - Decoded JWT payload
87
+
88
+ **Throws:**
89
+ - `TokenExpiredError` - Token has expired
90
+ - `InvalidSignatureError` - Invalid signature
91
+ - `JsonWebTokenError` - Other JWT validation errors
92
+ - `NotBeforeError` - Token not yet valid
93
+
94
+ **Example:**
95
+ ```javascript
96
+ try {
97
+ const decoded = verify(token, 'secret-key');
98
+ console.log('User ID:', decoded.userId);
99
+ } catch (error) {
100
+ if (error instanceof TokenExpiredError) {
101
+ console.log('Token expired at:', error.expiredAt);
102
+ }
103
+ }
104
+ ```
105
+
106
+ ### decodePayload(token)
107
+
108
+ Decodes JWT payload without verification. Useful for inspecting token claims
109
+ before verification or when verification is not required.
110
+
111
+ **Parameters:**
112
+ - `token` (string) - JWT token to decode
113
+
114
+ **Returns:** `object` - Decoded JWT payload
115
+
116
+ **Throws:**
117
+ - `Error` - Invalid token format or malformed payload
118
+
119
+ **Example:**
120
+ ```javascript
121
+ // Inspect token without verification
122
+ const payload = decodePayload(token);
123
+ console.log('Token expires:', payload.exp);
124
+ console.log('Issued at:', payload.iat);
125
+
126
+ // Check if token is expired before verification
127
+ if (payload.exp && payload.exp < Date.now() / 1000) {
128
+ console.log('Token is expired');
129
+ }
130
+ ```
131
+
132
+ ### expiresAtUTC(token)
133
+
134
+ Converts the `exp` claim of a decoded token to a human-readable UTC string.
135
+
136
+ **Parameters:**
137
+ - `token` (object) - Decoded JWT payload with optional `exp` claim
138
+
139
+ **Returns:** `string|null` - UTC string or null if no expiration
140
+
141
+ **Example:**
142
+ ```javascript
143
+ const payload = decodePayload(token);
144
+ const expiresAt = expiresAtUTC(payload);
145
+
146
+ if (expiresAt) {
147
+ console.log('Token expires at:', expiresAt);
148
+ // "Sun, 01 Jan 2023 00:00:00 GMT"
149
+ } else {
150
+ console.log('Token never expires');
151
+ }
152
+ ```
153
+
154
+ ## Error Types
155
+
156
+ The library provides specific error types for different failure modes:
157
+
158
+ ### TokenExpiredError
159
+ Thrown when a token has expired.
160
+ - `message` - Error description
161
+ - `expiredAt` - Date when token expired
162
+
163
+ ### InvalidSignatureError
164
+ Thrown when token signature is invalid.
165
+ - `message` - Error description
166
+
167
+ ### JsonWebTokenError
168
+ Thrown for general JWT validation errors.
169
+ - `message` - Error description
170
+
171
+ ### NotBeforeError
172
+ Thrown when token is not yet valid (nbf claim).
173
+ - `message` - Error description
174
+ - `date` - Date when token becomes valid
175
+
176
+ ## Security Best Practices
177
+
178
+ ### Secret Key Management
179
+ - Use cryptographically strong secrets (256+ bits for HMAC)
180
+ - Store secrets securely (environment variables, key management services)
181
+ - Rotate secrets regularly
182
+ - Never commit secrets to version control
183
+
184
+ ```javascript
185
+ import { generateSecretKeyForHmacBase58 } from '$lib/auth/jwt.js';
186
+
187
+ // Generate a secure secret
188
+ const secret = generateSecretKeyForHmacBase58();
189
+ ```
190
+
191
+ ### Token Validation
192
+ - Always verify tokens before trusting claims
193
+ - Use specific algorithm allowlists in verification
194
+ - Validate issuer, audience, and other claims as needed
195
+ - Handle expiration appropriately for your use case
196
+
197
+ ```javascript
198
+ const decoded = verify(token, secret, {
199
+ algorithms: ['HS256'], // Only allow specific algorithms
200
+ issuer: 'trusted-issuer',
201
+ audience: 'my-app'
202
+ });
203
+ ```
204
+
205
+ ### Token Storage
206
+ - Store tokens securely (httpOnly cookies, secure storage)
207
+ - Use short expiration times when possible
208
+ - Implement token refresh mechanisms
209
+ - Clear tokens on logout
210
+
211
+ ## Common Patterns
212
+
213
+ ### Token Inspection Middleware
214
+ ```javascript
215
+ function inspectToken(token) {
216
+ try {
217
+ const payload = decodePayload(token);
218
+ const expiresAt = expiresAtUTC(payload);
219
+
220
+ return {
221
+ isExpired: payload.exp && payload.exp < Date.now() / 1000,
222
+ expiresAt,
223
+ userId: payload.userId,
224
+ role: payload.role
225
+ };
226
+ } catch (error) {
227
+ return { isValid: false, error: error.message };
228
+ }
229
+ }
230
+ ```
231
+
232
+ ### Token Refresh Check
233
+ ```javascript
234
+ function shouldRefreshToken(token) {
235
+ const payload = decodePayload(token);
236
+ if (!payload.exp) return false;
237
+
238
+ const now = Date.now() / 1000;
239
+ const timeUntilExpiry = payload.exp - now;
240
+ const refreshThreshold = 5 * 60; // 5 minutes
241
+
242
+ return timeUntilExpiry < refreshThreshold && timeUntilExpiry > 0;
243
+ }
244
+ ```
245
+
246
+ ### Safe Token Verification
247
+ ```javascript
248
+ function safeVerifyToken(token, secret) {
249
+ try {
250
+ return { success: true, payload: verify(token, secret) };
251
+ } catch (error) {
252
+ return {
253
+ success: false,
254
+ error: error.constructor.name,
255
+ message: error.message
256
+ };
257
+ }
258
+ }
259
+ ```
260
+
261
+ ## Testing
262
+
263
+ Run the JWT tests:
264
+
265
+ ```bash
266
+ pnpm test:file src/lib/auth/jwt/
267
+ ```
268
+
269
+ The test suite covers:
270
+ - Token signing with various options
271
+ - Token verification with different scenarios
272
+ - Error handling for expired/invalid tokens
273
+ - Payload decoding without verification
274
+ - UTC time conversion
275
+ - Edge cases and error conditions
@@ -42,3 +42,19 @@ export function verify(token: string, secretOrPrivateKey: import("./typedef.js")
42
42
  * @returns {Error} - The corresponding internal error
43
43
  */
44
44
  export function castJwtError(error: Error): Error;
45
+ /**
46
+ * Decodes the payload of a JSON Web Token without verification
47
+ *
48
+ * @param {string} token - A JSON Web Token
49
+ *
50
+ * @returns {object} claims - The decoded JWT payload
51
+ */
52
+ export function decodePayload(token: string): object;
53
+ /**
54
+ * Returns the "exp" (expiresAt) property of a token as UTC string
55
+ *
56
+ * @param {object} token - Decoded JWT payload/claims
57
+ *
58
+ * @returns {string|null} "expires at" as UTC string or null if no exp claim
59
+ */
60
+ export function expiresAtUTC(token: object): string | null;
@@ -144,3 +144,51 @@ export function castJwtError(error) {
144
144
  // Return original error if not a known JWT error
145
145
  return error;
146
146
  }
147
+
148
+ /**
149
+ * Decodes the payload of a JSON Web Token without verification
150
+ *
151
+ * @param {string} token - A JSON Web Token
152
+ *
153
+ * @returns {object} claims - The decoded JWT payload
154
+ */
155
+ export function decodePayload(token) {
156
+ expect.notEmptyString(token);
157
+
158
+ const firstDot = token.indexOf('.');
159
+
160
+ if (firstDot === -1) {
161
+ throw new Error(
162
+ 'Invalid token, missing [.] token as payload start indicator'
163
+ );
164
+ }
165
+
166
+ const lastDot = token.lastIndexOf('.');
167
+
168
+ if (lastDot === firstDot) {
169
+ throw new Error(
170
+ 'Invalid token, missing second [.] token as payload end indicator'
171
+ );
172
+ }
173
+
174
+ const payload = token.slice(firstDot + 1, lastDot);
175
+
176
+ return JSON.parse(atob(payload));
177
+ }
178
+
179
+ /**
180
+ * Returns the "exp" (expiresAt) property of a token as UTC string
181
+ *
182
+ * @param {object} token - Decoded JWT payload/claims
183
+ *
184
+ * @returns {string|null} "expires at" as UTC string or null if no exp claim
185
+ */
186
+ export function expiresAtUTC(token) {
187
+ expect.object(token);
188
+
189
+ if ('exp' in token) {
190
+ return new Date(1000 * token.exp).toUTCString();
191
+ }
192
+
193
+ return null;
194
+ }
package/dist/auth/jwt.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @description
5
5
  * This module provides a clean API for JWT operations including signing,
6
- * verifying tokens, and generating secret keys.
6
+ * verifying tokens, decoding payloads, and generating secret keys.
7
7
  */
8
8
 
9
9
  export * from './jwt/util.js';
@@ -1,3 +1,4 @@
1
+ export * from "./util/typedef.js";
1
2
  declare const _default: {};
2
3
  export default _default;
3
4
  export type IterableTreeOptions = {
@@ -6,4 +6,6 @@
6
6
  * @property {boolean} outputIntermediateNodes
7
7
  */
8
8
 
9
+ export * from './util/typedef.js';
10
+
9
11
  export default {};
@@ -0,0 +1,252 @@
1
+ # Flat Tree Utilities
2
+
3
+ Tree serialization and deserialization utilities that convert between
4
+ hierarchical tree structures and flat tree format (ft1).
5
+
6
+ ## Overview
7
+
8
+ The flat tree utilities provide bidirectional conversion between:
9
+ - **Hierarchical Trees**: Standard nested object structures with children
10
+ - **Flat Tree Format (ft1)**: Compact serialized format with separate
11
+ nodes and edges
12
+
13
+ ## Key Features
14
+
15
+ - **Format Versioning**: ft1 format with forward compatibility
16
+ - **Property Preservation**: Maintains original property names
17
+ (`children`, `items`, etc.)
18
+ - **Shared Reference Handling**: Uses WeakMap to track objects that
19
+ appear multiple times
20
+ - **ID Collision Avoidance**: No conflicts between user IDs and tree
21
+ structure IDs
22
+ - **Efficient Serialization**: Compact flat edge array format
23
+
24
+ ## API Reference
25
+
26
+ ### `buildTree(flatTree, options)`
27
+
28
+ Converts ft1 flat tree format to hierarchical tree structure.
29
+
30
+ **Parameters:**
31
+ - `flatTree` - FlatTree object in ft1 format
32
+ - `options` - Optional configuration object
33
+
34
+ **Returns:** Reconstructed hierarchical tree or null
35
+
36
+ **Example:**
37
+ ```javascript
38
+ import { buildTree } from '$lib/generic/data/util/flat-tree.js';
39
+
40
+ const flatTree = {
41
+ format: 'ft1',
42
+ properties: ['children'],
43
+ nodes: [
44
+ { id: 'root', name: 'Root' },
45
+ { id: 'child1', name: 'Child 1' }
46
+ ],
47
+ edges: [0, 0, 1] // root->children->child1
48
+ };
49
+
50
+ const hierarchical = buildTree(flatTree);
51
+ // Result: { id: 'root', name: 'Root', children: [{ id: 'child1', name: 'Child 1' }] }
52
+ ```
53
+
54
+ ### `flattenTree(hierarchicalTree, options)`
55
+
56
+ Converts hierarchical tree to ft1 flat tree format.
57
+
58
+ **Parameters:**
59
+ - `hierarchicalTree` - Nested object structure with children
60
+ - `options` - Optional configuration object
61
+ - `childrenKey` (default: 'children') - Primary children property name
62
+
63
+ **Returns:** FlatTree object in ft1 format
64
+
65
+ **Example:**
66
+ ```javascript
67
+ import { flattenTree } from '$lib/generic/data/util/flat-tree.js';
68
+
69
+ const hierarchical = {
70
+ id: 'root',
71
+ name: 'Root',
72
+ children: [
73
+ { id: 'child1', name: 'Child 1' }
74
+ ]
75
+ };
76
+
77
+ const flatTree = flattenTree(hierarchical);
78
+ // Result: { format: 'ft1', properties: ['children'], nodes: [...], edges: [...] }
79
+ ```
80
+
81
+ ## ft1 Format Specification
82
+
83
+ The ft1 format consists of four main components:
84
+
85
+ ### Format Structure
86
+
87
+ ```javascript
88
+ {
89
+ format: 'ft1', // Format version identifier
90
+ properties: string[], // Array of property names used in edges
91
+ nodes: object[], // Array of node objects (index 0 = root)
92
+ edges: number[] // Flat array: [from, prop, to, from, prop, to, ...]
93
+ }
94
+ ```
95
+
96
+ ### Properties Array
97
+
98
+ Maps property indices to names for edge decoding:
99
+ ```javascript
100
+ properties: ['children', 'items', 'metadata']
101
+ // index: 0 1 2
102
+ ```
103
+
104
+ ### Nodes Array
105
+
106
+ Contains node data with children properties removed:
107
+ ```javascript
108
+ nodes: [
109
+ { id: 'root', name: 'Root' }, // index 0 (always root)
110
+ { id: 'child1', name: 'Child 1' }, // index 1
111
+ { id: 'item1', name: 'Item 1' } // index 2
112
+ ]
113
+ ```
114
+
115
+ ### Edges Array
116
+
117
+ Flat array in groups of 3: `[from_index, property_index, to_index]`
118
+ ```javascript
119
+ edges: [
120
+ 0, 0, 1, // root(0) -children(0)-> child1(1)
121
+ 0, 1, 2 // root(0) -items(1)-> item1(2)
122
+ ]
123
+ ```
124
+
125
+ ## Shared Object References
126
+
127
+ Objects that appear multiple times in the tree are automatically
128
+ handled using WeakMap tracking:
129
+
130
+ ```javascript
131
+ const sharedNode = { id: 'shared', name: 'Shared' };
132
+
133
+ const hierarchical = {
134
+ id: 'root',
135
+ children: [
136
+ { id: 'left', children: [sharedNode] },
137
+ { id: 'right', children: [sharedNode] } // Same object reference
138
+ ]
139
+ };
140
+
141
+ const flattened = flattenTree(hierarchical);
142
+ const rebuilt = buildTree(flattened);
143
+
144
+ // Shared reference is preserved
145
+ console.log(rebuilt.children[0].children[0] === rebuilt.children[1].children[0]); // true
146
+ ```
147
+
148
+ ## Property Detection
149
+
150
+ The flattening process automatically detects child-containing properties:
151
+
152
+ ```javascript
153
+ const tree = {
154
+ id: 'root',
155
+ children: [{ id: 'child1' }], // Detected as child property
156
+ items: [{ id: 'item1' }], // Detected as child property
157
+ metadata: 'string value' // Not a child property (ignored)
158
+ };
159
+ ```
160
+
161
+ ## Roundtrip Conversion
162
+
163
+ The utilities maintain data integrity across roundtrip conversions:
164
+
165
+ ```javascript
166
+ const original = { /* complex tree */ };
167
+ const flattened = flattenTree(original);
168
+ const rebuilt = buildTree(flattened);
169
+ const reflattened = flattenTree(rebuilt);
170
+
171
+ // Data integrity preserved
172
+ console.log(JSON.stringify(flattened) === JSON.stringify(reflattened)); // true
173
+ ```
174
+
175
+ ## Error Handling
176
+
177
+ The utilities provide clear error messages for invalid data:
178
+
179
+ - **Unsupported format**: When format is not 'ft1'
180
+ - **Invalid edges**: When edges array length is not multiple of 3
181
+ - **Invalid indices**: When node or property indices are out of bounds
182
+ - **Missing data**: When required fields are missing
183
+
184
+ ## Performance Considerations
185
+
186
+ - **WeakMap Usage**: Efficient object reference tracking
187
+ - **Flat Edge Array**: Compact memory usage for large trees
188
+ - **Property Index Mapping**: Fast property name lookup
189
+ - **No Deep Cloning**: Shallow copies preserve performance
190
+
191
+ ## Use Cases
192
+
193
+ ### Configuration Trees
194
+ ```javascript
195
+ const config = {
196
+ server: {
197
+ children: [
198
+ { name: 'port', value: 3000 },
199
+ { name: 'host', value: 'localhost' }
200
+ ]
201
+ }
202
+ };
203
+ ```
204
+
205
+ ### File System Trees
206
+ ```javascript
207
+ const fileTree = {
208
+ path: '/',
209
+ children: [
210
+ { path: '/src', children: [{ path: '/src/index.js' }] },
211
+ { path: '/package.json' }
212
+ ]
213
+ };
214
+ ```
215
+
216
+ ### Menu Structures
217
+ ```javascript
218
+ const menu = {
219
+ title: 'Main',
220
+ items: [
221
+ { title: 'File', items: [{ title: 'New' }, { title: 'Open' }] },
222
+ { title: 'Edit', items: [{ title: 'Copy' }, { title: 'Paste' }] }
223
+ ]
224
+ };
225
+ ```
226
+
227
+ ## Migration Guide
228
+
229
+ ### From Legacy Formats
230
+
231
+ If migrating from older tree formats, ensure your data structure
232
+ follows these patterns:
233
+
234
+ 1. **Root Object**: Single root node with child properties
235
+ 2. **Array Children**: Child properties should be arrays of objects
236
+ 3. **Object Structure**: Each node should be a plain object
237
+ 4. **No Circular References**: Avoid circular references (use shared
238
+ objects instead)
239
+
240
+ ### Type Safety
241
+
242
+ Use JSDoc type annotations for better development experience:
243
+
244
+ ```javascript
245
+ /**
246
+ * @param {import('./typedef.js').FlatTree<MyNodeType>} flatTree
247
+ * @returns {MyNodeType|null}
248
+ */
249
+ function processTree(flatTree) {
250
+ return buildTree(flatTree);
251
+ }
252
+ ```
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Build a hierarchical tree from ft1 flat tree format
3
+ *
4
+ * @template {object} T
5
+ * @param {import('./typedef.js').FlatTree<T>} flatTree - Flat tree data
6
+ *
7
+ * @returns {T|null} reconstructed hierarchical tree or null
8
+ */
9
+ export function buildTree<T extends object>(flatTree: import("./typedef.js").FlatTree<T>): T | null;
10
+ /**
11
+ * Flatten a hierarchical tree into ft1 flat tree format
12
+ *
13
+ * @template {object} T
14
+ * @param {T} hierarchicalTree - Hierarchical tree with nested children
15
+ *
16
+ * @returns {import('./typedef.js').FlatTree<T>} flat tree data
17
+ */
18
+ export function flattenTree<T extends object>(hierarchicalTree: T): import("./typedef.js").FlatTree<T>;