@cratis/chronicle 6.2.0 → 6.3.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/confidentiality/EncryptedNotSupportedOnEventSourceId.ts +21 -0
- package/confidentiality/EncryptionScope.ts +34 -0
- package/confidentiality/PIIAndEncryptedCombinedNotSupported.ts +29 -0
- package/confidentiality/SecurityMetadata.ts +22 -0
- package/confidentiality/SecurityMetadataResolver.ts +54 -0
- package/confidentiality/SecurityMetadataType.ts +56 -0
- package/confidentiality/encrypted.spec.ts +105 -0
- package/confidentiality/encrypted.ts +143 -0
- package/confidentiality/index.ts +10 -0
- package/dist/confidentiality/EncryptedNotSupportedOnEventSourceId.d.ts +11 -0
- package/dist/confidentiality/EncryptedNotSupportedOnEventSourceId.d.ts.map +1 -0
- package/dist/confidentiality/EncryptedNotSupportedOnEventSourceId.js +19 -0
- package/dist/confidentiality/EncryptedNotSupportedOnEventSourceId.js.map +1 -0
- package/dist/confidentiality/EncryptionScope.d.ts +30 -0
- package/dist/confidentiality/EncryptionScope.d.ts.map +1 -0
- package/dist/confidentiality/EncryptionScope.js +33 -0
- package/dist/confidentiality/EncryptionScope.js.map +1 -0
- package/dist/confidentiality/PIIAndEncryptedCombinedNotSupported.d.ts +19 -0
- package/dist/confidentiality/PIIAndEncryptedCombinedNotSupported.d.ts.map +1 -0
- package/dist/confidentiality/PIIAndEncryptedCombinedNotSupported.js +27 -0
- package/dist/confidentiality/PIIAndEncryptedCombinedNotSupported.js.map +1 -0
- package/dist/confidentiality/SecurityMetadata.d.ts +18 -0
- package/dist/confidentiality/SecurityMetadata.d.ts.map +1 -0
- package/dist/confidentiality/SecurityMetadata.js +4 -0
- package/dist/confidentiality/SecurityMetadata.js.map +1 -0
- package/dist/confidentiality/SecurityMetadataResolver.d.ts +30 -0
- package/dist/confidentiality/SecurityMetadataResolver.d.ts.map +1 -0
- package/dist/confidentiality/SecurityMetadataResolver.js +48 -0
- package/dist/confidentiality/SecurityMetadataResolver.js.map +1 -0
- package/dist/confidentiality/SecurityMetadataType.d.ts +42 -0
- package/dist/confidentiality/SecurityMetadataType.d.ts.map +1 -0
- package/dist/confidentiality/SecurityMetadataType.js +50 -0
- package/dist/confidentiality/SecurityMetadataType.js.map +1 -0
- package/dist/confidentiality/encrypted.d.ts +72 -0
- package/dist/confidentiality/encrypted.d.ts.map +1 -0
- package/dist/confidentiality/encrypted.js +130 -0
- package/dist/confidentiality/encrypted.js.map +1 -0
- package/dist/confidentiality/index.d.ts +8 -0
- package/dist/confidentiality/index.d.ts.map +1 -0
- package/dist/confidentiality/index.js +9 -0
- package/dist/confidentiality/index.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/schemas/JsonSchema.d.ts +18 -0
- package/dist/schemas/JsonSchema.d.ts.map +1 -1
- package/dist/schemas/JsonSchemaGenerator.d.ts +41 -0
- package/dist/schemas/JsonSchemaGenerator.d.ts.map +1 -1
- package/dist/schemas/JsonSchemaGenerator.js +102 -9
- package/dist/schemas/JsonSchemaGenerator.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/index.ts +3 -0
- package/package.json +7 -2
- package/schemas/JsonSchema.ts +20 -0
- package/schemas/JsonSchemaGenerator.confidentiality.spec.ts +194 -0
- package/schemas/JsonSchemaGenerator.ts +115 -10
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
import 'reflect-metadata';
|
|
5
5
|
import { ConceptAs, Constructor, Fields, Guid } from '@cratis/fundamentals';
|
|
6
|
-
import { ComplianceSchemaMetadata, JsonSchema } from './JsonSchema';
|
|
6
|
+
import { ComplianceSchemaMetadata, JsonSchema, SecuritySchemaMetadata } from './JsonSchema';
|
|
7
7
|
import { TypeIntrospector } from '../types';
|
|
8
8
|
import { ComplianceMetadata } from '../compliance/ComplianceMetadata';
|
|
9
9
|
import { ComplianceMetadataResolver } from '../compliance/ComplianceMetadataResolver';
|
|
10
|
+
import { SecurityMetadata } from '../confidentiality/SecurityMetadata';
|
|
11
|
+
import { SecurityMetadataResolver } from '../confidentiality/SecurityMetadataResolver';
|
|
12
|
+
import { PIIAndEncryptedCombinedNotSupported } from '../confidentiality/PIIAndEncryptedCombinedNotSupported';
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* Generates JSON schemas for class constructors using reflection metadata.
|
|
@@ -54,11 +57,15 @@ export class JsonSchemaGenerator {
|
|
|
54
57
|
// the runtime type was unavailable (e.g. esbuild/tsx omits design:paramtypes).
|
|
55
58
|
if (Object.keys(propertySchema).length > 0) {
|
|
56
59
|
// An array of concept elements resolves and applies its own item-level compliance
|
|
57
|
-
// inside mapRuntimeTypeToSchema - the general
|
|
58
|
-
// below is skipped for it, mirroring the C#
|
|
60
|
+
// and security metadata inside mapRuntimeTypeToSchema - the general
|
|
61
|
+
// property/type/declaring-class walk below is skipped for it, mirroring the C#
|
|
62
|
+
// generator's enumerable-of-concept branch.
|
|
59
63
|
if (!this.isConceptArrayMember(target, memberName, memberType)) {
|
|
60
|
-
const
|
|
61
|
-
this.
|
|
64
|
+
const complianceMetadata = this.collectComplianceMetadata(prototype, memberName, memberType);
|
|
65
|
+
const securityMetadata = this.collectSecurityMetadata(prototype, memberName, memberType);
|
|
66
|
+
this.throwIfBothCompliantAndSecure(memberName, complianceMetadata, securityMetadata);
|
|
67
|
+
this.addComplianceMetadataToSchema(propertySchema, complianceMetadata);
|
|
68
|
+
this.addSecurityMetadataToSchema(propertySchema, securityMetadata);
|
|
62
69
|
}
|
|
63
70
|
schemaProperties[memberName] = propertySchema;
|
|
64
71
|
}
|
|
@@ -132,13 +139,16 @@ export class JsonSchemaGenerator {
|
|
|
132
139
|
const elementType = this.getArrayElementType(declaringType, propertyName);
|
|
133
140
|
|
|
134
141
|
// An array whose element is a ConceptAs<T> loses its classification the moment it is put in
|
|
135
|
-
// a list unless the element concept's own compliance metadata is carried onto the
|
|
136
|
-
// a value that would be encrypted as a scalar would otherwise be persisted in
|
|
137
|
-
// list element. Mirrors the C# generator's explicit enumerable-of-concept branch.
|
|
142
|
+
// a list unless the element concept's own compliance/security metadata is carried onto the
|
|
143
|
+
// item schema - a value that would be encrypted as a scalar would otherwise be persisted in
|
|
144
|
+
// the clear as a list element. Mirrors the C# generator's explicit enumerable-of-concept branch.
|
|
138
145
|
if (elementType && this.isConceptAs(elementType)) {
|
|
139
146
|
const itemSchema = this.mapRuntimeTypeToSchema(elementType);
|
|
140
|
-
const
|
|
141
|
-
|
|
147
|
+
const complianceMetadata = ComplianceMetadataResolver.getMetadataForType(elementType);
|
|
148
|
+
const securityMetadata = SecurityMetadataResolver.getMetadataForType(elementType);
|
|
149
|
+
this.throwIfBothCompliantAndSecure(elementType.name, complianceMetadata, securityMetadata);
|
|
150
|
+
this.addComplianceMetadataToSchema(itemSchema, complianceMetadata);
|
|
151
|
+
this.addSecurityMetadataToSchema(itemSchema, securityMetadata);
|
|
142
152
|
return { type: 'array', items: itemSchema };
|
|
143
153
|
}
|
|
144
154
|
|
|
@@ -299,4 +309,99 @@ export class JsonSchemaGenerator {
|
|
|
299
309
|
private static hasComplianceMetadataOfType(compliance: ComplianceSchemaMetadata[], metadataType: string): boolean {
|
|
300
310
|
return compliance.some(item => item.metadataType === metadataType);
|
|
301
311
|
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Collects security metadata for a property from every source compliance metadata is resolved
|
|
315
|
+
* from: the property itself, its declaring class, and its own type (the concept case). This is
|
|
316
|
+
* the security counterpart to {@link collectComplianceMetadata} - a deliberately separate walk
|
|
317
|
+
* rather than a shared one, over a completely separate `@encrypted()` decorator vocabulary.
|
|
318
|
+
* @param target - The class prototype the property is declared on.
|
|
319
|
+
* @param propertyKey - The property name.
|
|
320
|
+
* @param propertyType - The property's runtime type, when resolved.
|
|
321
|
+
* @returns The collected security metadata, in property → declaring-class → type order.
|
|
322
|
+
*/
|
|
323
|
+
private static collectSecurityMetadata(target: object, propertyKey: string, propertyType?: Function): SecurityMetadata[] {
|
|
324
|
+
const metadata: SecurityMetadata[] = [];
|
|
325
|
+
|
|
326
|
+
// Property-level security decorator.
|
|
327
|
+
if (SecurityMetadataResolver.hasMetadataFor(target, propertyKey)) {
|
|
328
|
+
metadata.push(...SecurityMetadataResolver.getMetadataFor(target, propertyKey));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Declaring class-level security decorator - a class-level @encrypted() marks every one of
|
|
332
|
+
// its own properties, the same way a class-level @pii() does for compliance.
|
|
333
|
+
const declaringClass = (target as { constructor?: Function }).constructor;
|
|
334
|
+
if (declaringClass) {
|
|
335
|
+
metadata.push(...SecurityMetadataResolver.getMetadataForType(declaringClass));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Type-level security decorator on the property's own type (e.g., @encrypted on a ConceptAs).
|
|
339
|
+
if (propertyType) {
|
|
340
|
+
metadata.push(...SecurityMetadataResolver.getMetadataForType(propertyType));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return metadata;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Adds security metadata to a schema node, descending into an object's properties so that the
|
|
348
|
+
* metadata always lands on the leaves that actually hold a value. This is the security
|
|
349
|
+
* counterpart to {@link addComplianceMetadataToSchema} - see its remarks for why metadata is
|
|
350
|
+
* pushed to leaves and why an array is left as a container.
|
|
351
|
+
* @param schema - The schema node to add to.
|
|
352
|
+
* @param metadata - The security metadata to add.
|
|
353
|
+
*/
|
|
354
|
+
private static addSecurityMetadataToSchema(schema: JsonSchema, metadata: SecurityMetadata[]): void {
|
|
355
|
+
if (metadata.length === 0) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (schema.properties && Object.keys(schema.properties).length > 0) {
|
|
360
|
+
for (const propertySchema of Object.values(schema.properties)) {
|
|
361
|
+
this.addSecurityMetadataToSchema(propertySchema, metadata);
|
|
362
|
+
}
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const security = schema.security ?? [];
|
|
367
|
+
for (const item of metadata) {
|
|
368
|
+
const metadataType = item.metadataType.value.toString();
|
|
369
|
+
if (!this.hasSecurityMetadataOfType(security, metadataType)) {
|
|
370
|
+
security.push({ metadataType, details: item.details });
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (security.length > 0) {
|
|
375
|
+
schema.security = security;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Checks whether a security array already carries metadata of a given type.
|
|
381
|
+
* @param security - The security array to check.
|
|
382
|
+
* @param metadataType - The metadata type to look for.
|
|
383
|
+
* @returns True when the metadata type is already present, false if not.
|
|
384
|
+
*/
|
|
385
|
+
private static hasSecurityMetadataOfType(security: SecuritySchemaMetadata[], metadataType: string): boolean {
|
|
386
|
+
return security.some(item => item.metadataType === metadataType);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Rejects a property or type that resolved both compliance and security metadata.
|
|
391
|
+
* @param name - The property or type name, used in the thrown error.
|
|
392
|
+
* @param complianceMetadata - The compliance metadata collected for the property/type.
|
|
393
|
+
* @param securityMetadata - The security metadata collected for the property/type.
|
|
394
|
+
* @remarks
|
|
395
|
+
* This is not merely redundant - it corrupts the value. The kernel applies every matching
|
|
396
|
+
* handler for a property in sequence, so a value marked both ways is encrypted first under the
|
|
397
|
+
* PII key and then again under the Encrypted key; releasing it decrypts with the wrong key
|
|
398
|
+
* against ciphertext, which fails loudly rather than returning a wrong value. Checked here, at
|
|
399
|
+
* schema-generation time, the same point C#'s `EncryptedMetadataProvider.ThrowIfAlsoPII` checks
|
|
400
|
+
* it - this client has no compile-time analyzer, so this runtime check is the only backstop.
|
|
401
|
+
*/
|
|
402
|
+
private static throwIfBothCompliantAndSecure(name: string, complianceMetadata: ComplianceMetadata[], securityMetadata: SecurityMetadata[]): void {
|
|
403
|
+
if (complianceMetadata.length > 0 && securityMetadata.length > 0) {
|
|
404
|
+
throw new PIIAndEncryptedCombinedNotSupported(name);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
302
407
|
}
|