@memberjunction/credentials 3.4.0 → 4.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/README.md CHANGED
@@ -1,17 +1,33 @@
1
1
  # @memberjunction/credentials
2
2
 
3
- Secure credential management engine for MemberJunction. Provides centralized storage, retrieval, and audit logging of credentials with automatic field-level encryption.
4
-
5
- ## Features
6
-
7
- - **Centralized Credential Storage** - Store all credentials in one place with consistent access patterns
8
- - **Field-Level Encryption** - Credentials are automatically encrypted at rest using MJ's encryption engine
9
- - **Audit Logging** - Every credential access is logged for compliance and security monitoring
10
- - **Type-Safe Values** - Pre-defined interfaces for common credential types (API keys, OAuth, AWS, Azure, etc.)
11
- - **Cached Lookups** - Credential metadata is cached for fast resolution
12
- - **Per-Request Overrides** - Support for test/development credential injection
13
- - **Validation Support** - Optional endpoint-based credential validation
14
- - **API Key Management** - Fast hash-based lookup for API key authentication
3
+ Secure credential management engine for MemberJunction. Provides centralized storage, retrieval, validation, and audit logging of credentials with automatic field-level encryption and JSON Schema validation.
4
+
5
+ ## Overview
6
+
7
+ The `@memberjunction/credentials` package manages the full credential lifecycle: storing encrypted values, resolving credentials by name or ID, validating against JSON Schema constraints, and logging every access for audit compliance.
8
+
9
+ ```mermaid
10
+ graph TD
11
+ A["CredentialEngine<br/>(Singleton)"] --> B["Credential Types<br/>(Schema Definitions)"]
12
+ A --> C["Credentials<br/>(Encrypted Values)"]
13
+ A --> D["Credential Categories<br/>(Organization)"]
14
+ A --> E["Audit Log<br/>(Access Tracking)"]
15
+ A --> F["Ajv Validator<br/>(JSON Schema)"]
16
+
17
+ G["Consumer Code"] --> A
18
+ G -->|"getCredential()"| H["ResolvedCredential<T>"]
19
+ G -->|"storeCredential()"| C
20
+ G -->|"validateCredential()"| I["ValidationResult"]
21
+
22
+ style A fill:#2d6a9f,stroke:#1a4971,color:#fff
23
+ style B fill:#7c5295,stroke:#563a6b,color:#fff
24
+ style C fill:#2d8659,stroke:#1a5c3a,color:#fff
25
+ style D fill:#b8762f,stroke:#8a5722,color:#fff
26
+ style E fill:#b8762f,stroke:#8a5722,color:#fff
27
+ style F fill:#7c5295,stroke:#563a6b,color:#fff
28
+ style H fill:#2d8659,stroke:#1a5c3a,color:#fff
29
+ style I fill:#2d8659,stroke:#1a5c3a,color:#fff
30
+ ```
15
31
 
16
32
  ## Installation
17
33
 
@@ -21,411 +37,106 @@ npm install @memberjunction/credentials
21
37
 
22
38
  ## Quick Start
23
39
 
24
- ### 1. Initialize the Engine
25
-
26
40
  ```typescript
27
- import { CredentialEngine } from '@memberjunction/credentials';
41
+ import { CredentialEngine, APIKeyCredentialValues } from '@memberjunction/credentials';
28
42
 
29
43
  // Initialize at application startup
30
44
  await CredentialEngine.Instance.Config(false, contextUser);
31
- ```
32
-
33
- ### 2. Retrieve a Credential
34
45
 
35
- ```typescript
36
- import { CredentialEngine, APIKeyCredentialValues } from '@memberjunction/credentials';
37
-
38
- // Get a credential with typed values
46
+ // Retrieve a credential with typed values
39
47
  const cred = await CredentialEngine.Instance.getCredential<APIKeyCredentialValues>(
40
- 'OpenAI',
41
- {
42
- contextUser,
43
- subsystem: 'AIService'
44
- }
48
+ 'OpenAI',
49
+ { contextUser, subsystem: 'AIService' }
45
50
  );
46
51
 
47
52
  // Use the decrypted values
48
- openai.setApiKey(cred.values.apiKey);
53
+ console.log(cred.values.apiKey); // Strongly typed as string
49
54
  ```
50
55
 
51
- ### 3. Store a New Credential
52
-
53
- ```typescript
54
- const credential = await CredentialEngine.Instance.storeCredential(
55
- 'API Key', // Credential type name
56
- 'OpenAI Production', // Credential name
57
- { apiKey: 'sk-...' }, // Values (will be encrypted)
58
- {
59
- isDefault: true,
60
- description: 'Production OpenAI API key'
61
- },
62
- contextUser
63
- );
56
+ ## Credential Resolution
57
+
58
+ ```mermaid
59
+ flowchart TD
60
+ A["getCredential(name, options)"] --> B{directValues<br/>provided?}
61
+ B -->|Yes| C["Return direct values<br/>source: request"]
62
+ B -->|No| D{credentialId<br/>provided?}
63
+ D -->|Yes| E["Lookup by ID"]
64
+ D -->|No| F["Lookup by name"]
65
+ E --> G["Parse & return values<br/>source: database"]
66
+ F --> G
67
+ G --> H["Log access to Audit Log"]
68
+ H --> I["Update LastUsedAt"]
69
+
70
+ style A fill:#2d6a9f,stroke:#1a4971,color:#fff
71
+ style C fill:#2d8659,stroke:#1a5c3a,color:#fff
72
+ style G fill:#2d8659,stroke:#1a5c3a,color:#fff
73
+ style H fill:#b8762f,stroke:#8a5722,color:#fff
64
74
  ```
65
75
 
66
- ## Credential Types
67
-
68
- MemberJunction includes pre-configured credential types with JSON schemas:
76
+ Resolution priority:
77
+ 1. **Direct values** -- `directValues` in options (bypasses database, useful for testing)
78
+ 2. **By ID** -- `credentialId` in options (specific credential lookup)
79
+ 3. **By name** -- The `credentialName` parameter (most common usage)
69
80
 
70
- | Type | Fields | Use Case |
71
- |------|--------|----------|
72
- | API Key | `apiKey` | OpenAI, Anthropic, SendGrid, etc. |
73
- | API Key with Endpoint | `apiKey`, `endpoint` | Azure OpenAI, custom APIs |
74
- | OAuth2 Client Credentials | `clientId`, `clientSecret`, `tokenUrl`, `scope` | OAuth integrations |
75
- | Basic Auth | `username`, `password` | Legacy systems |
76
- | Azure Service Principal | `tenantId`, `clientId`, `clientSecret` | Microsoft Graph, Azure services |
77
- | AWS IAM | `accessKeyId`, `secretAccessKey`, `region` | S3, SES, Lambda, etc. |
78
- | Database Connection | `host`, `port`, `database`, `username`, `password` | External databases |
79
- | Twilio | `accountSid`, `authToken` | SMS/Voice services |
81
+ ## Pre-defined Credential Types
80
82
 
81
- ### Type-Safe Value Interfaces
83
+ | Type | Interface | Fields |
84
+ |------|-----------|--------|
85
+ | API Key | `APIKeyCredentialValues` | `apiKey` |
86
+ | API Key with Endpoint | `APIKeyWithEndpointCredentialValues` | `apiKey`, `endpoint` |
87
+ | OAuth2 Client Credentials | `OAuth2ClientCredentialValues` | `clientId`, `clientSecret`, `tokenUrl`, `scope` |
88
+ | Basic Auth | `BasicAuthCredentialValues` | `username`, `password` |
89
+ | Azure Service Principal | `AzureServicePrincipalCredentialValues` | `tenantId`, `clientId`, `clientSecret` |
90
+ | AWS IAM | `AWSIAMCredentialValues` | `accessKeyId`, `secretAccessKey`, `region` |
91
+ | Database Connection | `DatabaseConnectionCredentialValues` | `host`, `port`, `database`, `username`, `password` |
92
+ | Twilio | `TwilioCredentialValues` | `accountSid`, `authToken` |
82
93
 
83
- Use the provided interfaces for compile-time type safety:
94
+ ## Storing Credentials
84
95
 
85
96
  ```typescript
86
- import {
87
- CredentialEngine,
88
- APIKeyCredentialValues,
89
- AWSIAMCredentialValues,
90
- AzureServicePrincipalCredentialValues
91
- } from '@memberjunction/credentials';
92
-
93
- // OpenAI/Anthropic/etc.
94
- const ai = await CredentialEngine.Instance.getCredential<APIKeyCredentialValues>(
95
- 'OpenAI',
96
- { contextUser }
97
- );
98
- console.log(ai.values.apiKey); // Typed as string
99
-
100
- // AWS Services
101
- const aws = await CredentialEngine.Instance.getCredential<AWSIAMCredentialValues>(
102
- 'AWS Production',
103
- { contextUser }
104
- );
105
- console.log(aws.values.accessKeyId, aws.values.secretAccessKey, aws.values.region);
106
-
107
- // Azure Services
108
- const azure = await CredentialEngine.Instance.getCredential<AzureServicePrincipalCredentialValues>(
109
- 'Microsoft Graph',
110
- { contextUser }
97
+ const credential = await CredentialEngine.Instance.storeCredential(
98
+ 'API Key', // Credential type name
99
+ 'OpenAI Production', // Credential name
100
+ { apiKey: 'sk-...' }, // Values (encrypted on save)
101
+ {
102
+ isDefault: true,
103
+ description: 'Production OpenAI API key',
104
+ expiresAt: new Date('2025-12-31')
105
+ },
106
+ contextUser
111
107
  );
112
- console.log(azure.values.tenantId, azure.values.clientId);
113
- ```
114
-
115
- ## Resolution Priority
116
-
117
- When you call `getCredential()`, credentials are resolved in this order:
118
-
119
- 1. **Direct Values** - If `directValues` is provided in options, use those (useful for testing)
120
- 2. **By ID** - If `credentialId` is provided, look up that specific credential
121
- 3. **By Name** - Look up by the credential name passed to `getCredential()`
122
-
123
- ```typescript
124
- // Priority 1: Direct values (bypasses database)
125
- const cred = await engine.getCredential('OpenAI', {
126
- directValues: { apiKey: 'test-key' },
127
- contextUser
128
- });
129
-
130
- // Priority 2: Specific credential by ID
131
- const cred = await engine.getCredential('OpenAI', {
132
- credentialId: 'specific-credential-uuid',
133
- contextUser
134
- });
135
-
136
- // Priority 3: By name (most common)
137
- const cred = await engine.getCredential('OpenAI', { contextUser });
138
108
  ```
139
109
 
140
- ## API Key Lookup
141
-
142
- The CredentialEngine caches API keys for fast hash-based lookup, used by authentication systems:
143
-
144
- ```typescript
145
- // Look up API key by hash (O(1) from cache)
146
- const apiKey = CredentialEngine.Instance.getAPIKeyByHash(keyHash);
110
+ ## JSON Schema Validation
147
111
 
148
- if (apiKey && apiKey.Status === 'Active') {
149
- // Key is valid
150
- console.log('User ID:', apiKey.UserID);
151
- }
152
- ```
112
+ The engine validates credential values against the `FieldSchema` defined on each Credential Type using Ajv. Supported constraints include `required`, `const`, `enum`, `format`, `pattern`, `minLength`/`maxLength`, and `minimum`/`maximum`.
153
113
 
154
- This is used internally by `EncryptionEngine.ValidateAPIKey()` for fast API authentication.
114
+ Default and const values are auto-populated before validation, and validation errors produce clear, human-readable messages.
155
115
 
156
116
  ## Audit Logging
157
117
 
158
- Every credential access is automatically logged to the `Audit Logs` entity:
159
-
160
- ```typescript
161
- const cred = await engine.getCredential('OpenAI', {
162
- contextUser,
163
- subsystem: 'AIService' // Logged for tracking
164
- });
165
- ```
166
-
167
- Audit log entry includes:
168
- - User who accessed the credential
169
- - Operation type (Decrypt, Create, Update, Validate)
118
+ Every credential operation (Decrypt, Create, Update, Validate) is logged to the Audit Logs entity with:
119
+ - User who performed the operation
170
120
  - Subsystem that requested access
171
- - Success/failure status
121
+ - Success or failure status
172
122
  - Duration in milliseconds
173
123
 
174
- ## JSON Schema Validation
175
-
176
- The CredentialEngine uses [Ajv JSON Schema validator](https://ajv.js.org/) to validate credential values against the `FieldSchema` defined in each Credential Type. This ensures data integrity and security by enforcing constraints at save time.
177
-
178
- ### Supported Constraints
179
-
180
- All JSON Schema Draft 7 validation keywords are supported:
181
-
182
- | Constraint | Description | Example Use Case |
183
- |-----------|-------------|------------------|
184
- | `required` | Mandatory fields | API keys must include `apiKey` field |
185
- | `const` | Fixed immutable values | OAuth token URLs that must never change |
186
- | `enum` | Limited set of allowed values | Region selection, account types |
187
- | `format` | Value format validation | `uri`, `email`, `date`, `uuid` |
188
- | `pattern` | Regex pattern matching | API key format validation |
189
- | `minLength`/`maxLength` | String length bounds | Password length requirements |
190
- | `minimum`/`maximum` | Numeric range validation | Port numbers, timeout values |
191
- | `default` | Auto-populated values | Default regions, endpoints |
192
-
193
- ### Schema Validation Flow
194
-
195
- 1. **Create/Update**: When `storeCredential()` or `updateCredential()` is called
196
- 2. **Apply Defaults**: Fields with `default` or `const` values are auto-populated
197
- 3. **Validate**: The complete credential values are validated against the schema
198
- 4. **Throw on Error**: If validation fails, a clear error message is thrown listing all violations
199
-
200
- ```typescript
201
- // This credential type schema
202
- {
203
- "type": "object",
204
- "properties": {
205
- "apiKey": {
206
- "type": "string",
207
- "pattern": "^sk-[a-zA-Z0-9]{32}$",
208
- "description": "OpenAI API key"
209
- },
210
- "endpoint": {
211
- "type": "string",
212
- "format": "uri",
213
- "default": "https://api.openai.com/v1"
214
- }
215
- },
216
- "required": ["apiKey"]
217
- }
218
-
219
- // Will reject this (invalid pattern)
220
- await engine.storeCredential('OpenAI', 'Production', {
221
- apiKey: 'invalid-format' // ❌ Fails pattern validation
222
- }, {}, contextUser);
223
- // Error: Field "apiKey" does not match required pattern
224
-
225
- // Will reject this (invalid URI format)
226
- await engine.storeCredential('OpenAI', 'Production', {
227
- apiKey: 'sk-abcdefghijklmnopqrstuvwxyz123456',
228
- endpoint: 'not-a-url' // ❌ Fails format validation
229
- }, {}, contextUser);
230
- // Error: Field "endpoint" must be a valid uri
231
-
232
- // Will accept and auto-populate default
233
- await engine.storeCredential('OpenAI', 'Production', {
234
- apiKey: 'sk-abcdefghijklmnopqrstuvwxyz123456'
235
- // endpoint auto-populated with default: https://api.openai.com/v1
236
- }, {}, contextUser);
237
- // ✅ Success
238
- ```
239
-
240
- ### Error Messages
241
-
242
- Validation errors are formatted for clarity:
243
-
244
- - **Required**: `Missing required field: apiKey`
245
- - **Const**: `Field "tokenUrl" must be "https://api.box.com/oauth2/token"`
246
- - **Enum**: `Field "region" must be one of: us-east-1, us-west-2, eu-west-1`
247
- - **Format**: `Field "endpoint" must be a valid uri`
248
- - **Pattern**: `Field "apiKey" does not match required pattern`
249
- - **Length**: `Field "password" must be at least 8 characters`
250
- - **Range**: `Field "port" must be at least 1024`
251
-
252
- ### Const Fields for Security
253
-
254
- Use `const` in schemas to enforce fixed values that must never change:
255
-
256
- ```json
257
- {
258
- "tokenUrl": {
259
- "type": "string",
260
- "const": "https://api.box.com/oauth2/token",
261
- "description": "Box.com OAuth token endpoint"
262
- }
263
- }
264
- ```
265
-
266
- This prevents:
267
- - Users from accidentally changing critical endpoints
268
- - Malicious redirection of credentials to external servers
269
- - Configuration drift across environments
270
-
271
- Const values are:
272
- - **Auto-populated** when creating credentials (users don't need to enter them)
273
- - **Immutable** - validation rejects any attempt to change them
274
- - **Visible** in UI as read-only fields
275
-
276
- ### Format Validation
277
-
278
- The `format` keyword validates common data types:
279
-
280
- - **uri** / **url** - Valid HTTP/HTTPS URLs
281
- - **email** - Valid email addresses
282
- - **date** - ISO 8601 dates
283
- - **date-time** - ISO 8601 timestamps
284
- - **uuid** - RFC 4122 UUIDs
285
- - **ipv4** / **ipv6** - IP addresses
286
- - **hostname** - Valid DNS hostnames
287
-
288
- ### Default Values
289
-
290
- Use `default` to pre-populate fields with sensible values:
291
-
292
- ```json
293
- {
294
- "region": {
295
- "type": "string",
296
- "default": "us-east-1",
297
- "enum": ["us-east-1", "us-west-2", "eu-west-1"]
298
- }
299
- }
300
- ```
301
-
302
- Users can override defaults, but they provide good starting points and reduce configuration errors.
303
-
304
- ## Credential Validation
305
-
306
- Validate credentials against provider endpoints:
307
-
308
- ```typescript
309
- const result = await engine.validateCredential(credentialId, contextUser);
310
-
311
- if (!result.isValid) {
312
- console.error('Credential validation failed:', result.errors);
313
- }
314
-
315
- // Result structure
316
- interface CredentialValidationResult {
317
- isValid: boolean;
318
- errors: string[];
319
- warnings: string[];
320
- validatedAt: Date;
321
- }
322
- ```
323
-
324
- ## Updating Credentials
325
-
326
- Update credential values (automatically re-encrypted):
327
-
328
- ```typescript
329
- await engine.updateCredential(
330
- credentialId,
331
- { apiKey: 'new-sk-...' }, // New values
332
- contextUser
333
- );
334
- ```
335
-
336
- ## Cached Data Access
337
-
338
- Access cached credential metadata without database calls:
339
-
340
- ```typescript
341
- // All credentials
342
- const credentials = engine.Credentials;
343
-
344
- // All credential types
345
- const types = engine.CredentialTypes;
346
-
347
- // All credential categories
348
- const categories = engine.CredentialCategories;
349
-
350
- // Lookup helpers
351
- const type = engine.getCredentialTypeByName('API Key');
352
- const defaultCred = engine.getDefaultCredentialForType('API Key');
353
- const credById = engine.getCredentialById('uuid-here');
354
- const credByName = engine.getCredentialByName('API Key', 'OpenAI');
355
- ```
356
-
357
- ## Database Schema
358
-
359
- ### MJ: Credential Types
360
-
361
- Defines the shape of credential values:
362
-
363
- | Column | Description |
364
- |--------|-------------|
365
- | `Name` | Type name (e.g., 'API Key') |
366
- | `Description` | Human-readable description |
367
- | `FieldSchema` | JSON Schema for validation |
368
- | `ValidationEndpoint` | Optional URL for validation |
369
-
370
- ### MJ: Credentials
371
-
372
- Stores credential instances:
373
-
374
- | Column | Description |
375
- |--------|-------------|
376
- | `Name` | Credential name (e.g., 'OpenAI Production') |
377
- | `CredentialTypeID` | Foreign key to type |
378
- | `Values` | Encrypted JSON blob |
379
- | `IsDefault` | Default for this type |
380
- | `IsActive` | Active status |
381
- | `ExpiresAt` | Optional expiration |
382
- | `LastUsedAt` | Updated on each access |
383
- | `LastValidatedAt` | Updated on validation |
384
-
385
- ### MJ: Credential Categories
386
-
387
- Optional organization:
388
-
389
- | Column | Description |
390
- |--------|-------------|
391
- | `Name` | Category name |
392
- | `Description` | Description |
393
- | `ParentCategoryID` | For hierarchy |
394
-
395
- ## Security Considerations
396
-
397
- 1. **Encryption at Rest**
398
- - The `Values` field uses MJ field-level encryption
399
- - Only decrypted when explicitly accessed via `getCredential()`
400
-
401
- 2. **Audit Trail**
402
- - Every access is logged with user, timestamp, and subsystem
403
- - Failed access attempts are also logged
404
-
405
- 3. **Access Control**
406
- - Credentials inherit MJ's entity-level permissions
407
- - Use `contextUser` to enforce authorization
408
-
409
- 4. **Expiration**
410
- - Set `ExpiresAt` on credentials to enforce rotation
411
- - Check `expiresAt` in the resolved credential for warnings
412
-
413
- 5. **Per-Request Override**
414
- - Use `directValues` for testing without database access
415
- - Values are marked with `source: 'request'` for audit clarity
416
-
417
- ## Integration with EncryptionEngine
124
+ ## Security
418
125
 
419
- The Credentials package works alongside the Encryption package:
126
+ - **Encryption at rest** -- The `Values` field uses MJ field-level encryption
127
+ - **Audit trail** -- All access logged including failed attempts
128
+ - **Access control** -- Entity-level permissions enforced via `contextUser`
129
+ - **Expiration support** -- `ExpiresAt` field enforces credential rotation
420
130
 
421
- - **CredentialEngine** - Manages credential storage and retrieval
422
- - **EncryptionEngine** - Handles API key generation, validation, and field encryption
131
+ ## Dependencies
423
132
 
424
- API key authentication flow:
425
- 1. `EncryptionEngine.ValidateAPIKey()` hashes the incoming key
426
- 2. `CredentialEngine.getAPIKeyByHash()` looks up the cached key entity
427
- 3. Validation checks status, expiration, and user account
428
- 4. User context is returned for authorized operations
133
+ | Package | Purpose |
134
+ |---------|---------|
135
+ | `@memberjunction/core` | Base engine, metadata, entity system |
136
+ | `@memberjunction/global` | Global state management |
137
+ | `@memberjunction/core-entities` | Credential entity types |
138
+ | `ajv` | JSON Schema validation |
139
+ | `ajv-formats` | Format validators (uri, email, date) |
429
140
 
430
141
  ## License
431
142
 
@@ -1,6 +1,6 @@
1
1
  import { BaseEngine, IMetadataProvider, UserInfo } from "@memberjunction/core";
2
2
  import { CredentialCategoryEntity, CredentialEntity, CredentialTypeEntity } from "@memberjunction/core-entities";
3
- import { CredentialResolutionOptions, ResolvedCredential, StoreCredentialOptions, CredentialValidationResult } from "./types";
3
+ import { CredentialResolutionOptions, ResolvedCredential, StoreCredentialOptions, CredentialValidationResult } from "./types.js";
4
4
  export declare class CredentialEngine extends BaseEngine<CredentialEngine> {
5
5
  private _credentials;
6
6
  private _credentialTypes;