@endora-commerce/mod-credentials 0.100.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.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/dist/admin/api/credentials-client.d.ts +11 -0
  4. package/dist/admin/api/credentials-client.d.ts.map +1 -0
  5. package/dist/admin/api/credentials-client.js +38 -0
  6. package/dist/admin/api/credentials-client.js.map +1 -0
  7. package/dist/admin/components/ConfigurationForm.d.ts +22 -0
  8. package/dist/admin/components/ConfigurationForm.d.ts.map +1 -0
  9. package/dist/admin/components/ConfigurationForm.js +89 -0
  10. package/dist/admin/components/ConfigurationForm.js.map +1 -0
  11. package/dist/admin/index.d.ts +47 -0
  12. package/dist/admin/index.d.ts.map +1 -0
  13. package/dist/admin/index.js +65 -0
  14. package/dist/admin/index.js.map +1 -0
  15. package/dist/admin/pages/CredentialsNewPage.d.ts +19 -0
  16. package/dist/admin/pages/CredentialsNewPage.d.ts.map +1 -0
  17. package/dist/admin/pages/CredentialsNewPage.js +22 -0
  18. package/dist/admin/pages/CredentialsNewPage.js.map +1 -0
  19. package/dist/admin/pages/CredentialsPage.d.ts +22 -0
  20. package/dist/admin/pages/CredentialsPage.d.ts.map +1 -0
  21. package/dist/admin/pages/CredentialsPage.js +154 -0
  22. package/dist/admin/pages/CredentialsPage.js.map +1 -0
  23. package/dist/admin-ui/ConfigurationPreviewModal.d.ts +16 -0
  24. package/dist/admin-ui/ConfigurationPreviewModal.d.ts.map +1 -0
  25. package/dist/admin-ui/ConfigurationPreviewModal.js +15 -0
  26. package/dist/admin-ui/ConfigurationPreviewModal.js.map +1 -0
  27. package/dist/admin-ui/index.d.ts +57 -0
  28. package/dist/admin-ui/index.d.ts.map +1 -0
  29. package/dist/admin-ui/index.js +56 -0
  30. package/dist/admin-ui/index.js.map +1 -0
  31. package/dist/backend/commands/create-configuration.command.d.ts +20 -0
  32. package/dist/backend/commands/create-configuration.command.d.ts.map +1 -0
  33. package/dist/backend/commands/create-configuration.command.js +70 -0
  34. package/dist/backend/commands/create-configuration.command.js.map +1 -0
  35. package/dist/backend/commands/delete-configuration.command.d.ts +16 -0
  36. package/dist/backend/commands/delete-configuration.command.d.ts.map +1 -0
  37. package/dist/backend/commands/delete-configuration.command.js +48 -0
  38. package/dist/backend/commands/delete-configuration.command.js.map +1 -0
  39. package/dist/backend/commands/update-configuration.command.d.ts +20 -0
  40. package/dist/backend/commands/update-configuration.command.d.ts.map +1 -0
  41. package/dist/backend/commands/update-configuration.command.js +94 -0
  42. package/dist/backend/commands/update-configuration.command.js.map +1 -0
  43. package/dist/backend/entities/credential-configuration.entity.d.ts +30 -0
  44. package/dist/backend/entities/credential-configuration.entity.d.ts.map +1 -0
  45. package/dist/backend/entities/credential-configuration.entity.js +84 -0
  46. package/dist/backend/entities/credential-configuration.entity.js.map +1 -0
  47. package/dist/backend/index.d.ts +73 -0
  48. package/dist/backend/index.d.ts.map +1 -0
  49. package/dist/backend/index.js +80 -0
  50. package/dist/backend/index.js.map +1 -0
  51. package/dist/backend/plugin.d.ts +43 -0
  52. package/dist/backend/plugin.d.ts.map +1 -0
  53. package/dist/backend/plugin.js +25 -0
  54. package/dist/backend/plugin.js.map +1 -0
  55. package/dist/backend/routes.admin.d.ts +27 -0
  56. package/dist/backend/routes.admin.d.ts.map +1 -0
  57. package/dist/backend/routes.admin.js +50 -0
  58. package/dist/backend/routes.admin.js.map +1 -0
  59. package/dist/backend/services/configuration-type-registry.d.ts +105 -0
  60. package/dist/backend/services/configuration-type-registry.d.ts.map +1 -0
  61. package/dist/backend/services/configuration-type-registry.js +111 -0
  62. package/dist/backend/services/configuration-type-registry.js.map +1 -0
  63. package/dist/backend/services/credentials.service.d.ts +63 -0
  64. package/dist/backend/services/credentials.service.d.ts.map +1 -0
  65. package/dist/backend/services/credentials.service.js +128 -0
  66. package/dist/backend/services/credentials.service.js.map +1 -0
  67. package/dist/backend/services/field-validator.d.ts +65 -0
  68. package/dist/backend/services/field-validator.d.ts.map +1 -0
  69. package/dist/backend/services/field-validator.js +165 -0
  70. package/dist/backend/services/field-validator.js.map +1 -0
  71. package/dist/backend/services/registry-singleton.d.ts +28 -0
  72. package/dist/backend/services/registry-singleton.d.ts.map +1 -0
  73. package/dist/backend/services/registry-singleton.js +29 -0
  74. package/dist/backend/services/registry-singleton.js.map +1 -0
  75. package/dist/backend/services/secret-value-codec.d.ts +55 -0
  76. package/dist/backend/services/secret-value-codec.d.ts.map +1 -0
  77. package/dist/backend/services/secret-value-codec.js +105 -0
  78. package/dist/backend/services/secret-value-codec.js.map +1 -0
  79. package/dist/backend/types/email-adapter.type.d.ts +10 -0
  80. package/dist/backend/types/email-adapter.type.d.ts.map +1 -0
  81. package/dist/backend/types/email-adapter.type.js +49 -0
  82. package/dist/backend/types/email-adapter.type.js.map +1 -0
  83. package/dist/backend/types/llm.type.d.ts +3 -0
  84. package/dist/backend/types/llm.type.d.ts.map +1 -0
  85. package/dist/backend/types/llm.type.js +35 -0
  86. package/dist/backend/types/llm.type.js.map +1 -0
  87. package/dist/manifest.d.ts +176 -0
  88. package/dist/manifest.d.ts.map +1 -0
  89. package/dist/manifest.js +167 -0
  90. package/dist/manifest.js.map +1 -0
  91. package/dist/migrations/20260721T011509_credentials_init.d.ts +17 -0
  92. package/dist/migrations/20260721T011509_credentials_init.d.ts.map +1 -0
  93. package/dist/migrations/20260721T011509_credentials_init.js +36 -0
  94. package/dist/migrations/20260721T011509_credentials_init.js.map +1 -0
  95. package/dist/migrations/index.d.ts +27 -0
  96. package/dist/migrations/index.d.ts.map +1 -0
  97. package/dist/migrations/index.js +29 -0
  98. package/dist/migrations/index.js.map +1 -0
  99. package/docs/credentials.md +202 -0
  100. package/i18n/en.json +47 -0
  101. package/i18n/pl.json +47 -0
  102. package/package.json +98 -0
  103. package/tailwind.css +16 -0
@@ -0,0 +1,128 @@
1
+ import { ERROR_CODES, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { CredentialConfiguration } from '../entities/credential-configuration.entity.js';
4
+ import { maskSecrets } from './field-validator.js';
5
+ import { decryptSecretValue, secretValueIsSet } from './secret-value-codec.js';
6
+ import { makeCreateConfigurationCommand } from '../commands/create-configuration.command.js';
7
+ import { makeUpdateConfigurationCommand } from '../commands/update-configuration.command.js';
8
+ import { makeDeleteConfigurationCommand } from '../commands/delete-configuration.command.js';
9
+ export class CredentialsService {
10
+ deps;
11
+ constructor(deps) {
12
+ this.deps = deps;
13
+ }
14
+ /** Registered configuration types powering the admin type picker + form. */
15
+ describeTypes() {
16
+ return this.deps.registry.describe();
17
+ }
18
+ async list(typeFilter) {
19
+ const em = this.deps.emFactory();
20
+ const where = typeFilter ? { typeCode: typeFilter } : {};
21
+ const rows = await em.find(CredentialConfiguration, where, { orderBy: { name: 'asc' } });
22
+ return rows.map((r) => this.toDto(r));
23
+ }
24
+ async getByCode(code) {
25
+ const em = this.deps.emFactory();
26
+ const row = await em.findOne(CredentialConfiguration, { code });
27
+ return row ? this.toDto(row) : null;
28
+ }
29
+ async create(data) {
30
+ const entity = await this.deps.commandBus.run(makeCreateConfigurationCommand({
31
+ data,
32
+ registry: this.deps.registry,
33
+ secretEncryptionKey: this.deps.secretEncryptionKey,
34
+ }));
35
+ return this.toDto(entity);
36
+ }
37
+ async update(code, data) {
38
+ const entity = await this.deps.commandBus.run(makeUpdateConfigurationCommand({
39
+ code,
40
+ data,
41
+ registry: this.deps.registry,
42
+ secretEncryptionKey: this.deps.secretEncryptionKey,
43
+ }));
44
+ return this.toDto(entity);
45
+ }
46
+ async delete(code) {
47
+ // Delete-integrity guard (FR-012): block if any setting references this
48
+ // configuration, listing the referrers. Read-then-delete — a race is
49
+ // acceptable for this admin-only, low-volume operation.
50
+ if (this.deps.settings) {
51
+ const referencedBy = await this.deps.settings.listReferencesToConfiguration(code);
52
+ if (referencedBy.length > 0) {
53
+ throw new HttpError(409, ERROR_CODES.CREDENTIAL_IN_USE, 'Configuration is referenced by one or more settings.', { referencedBy });
54
+ }
55
+ }
56
+ await this.deps.commandBus.run(makeDeleteConfigurationCommand({ code, registry: this.deps.registry }));
57
+ }
58
+ /**
59
+ * Server-side resolution (feature 058 US2) — the single path that returns
60
+ * DECRYPTED secret values, for a consumer's in-memory use only. Never exposed
61
+ * over HTTP. Fails closed: an unset reference is `not_configured`; a
62
+ * deleted/inert one is `unavailable` — never a foreign configuration
63
+ * (FR-012 / SC-005).
64
+ */
65
+ async resolve(configurationCode) {
66
+ if (!configurationCode || configurationCode.length === 0) {
67
+ return { status: 'not_configured' };
68
+ }
69
+ const em = this.deps.emFactory();
70
+ const entity = await em.findOne(CredentialConfiguration, { code: configurationCode });
71
+ if (!entity)
72
+ return { status: 'unavailable', reason: 'missing' };
73
+ const descriptor = this.deps.registry.get(entity.typeCode);
74
+ const variant = descriptor?.providers.find((p) => p.code === entity.providerCode);
75
+ if (!descriptor || !variant)
76
+ return { status: 'unavailable', reason: 'inert_type' };
77
+ const values = {};
78
+ for (const field of variant.fields) {
79
+ const stored = entity.values[field.key];
80
+ if (stored === undefined)
81
+ continue;
82
+ if (field.secret) {
83
+ values[field.key] = secretValueIsSet(stored)
84
+ ? decryptSecretValue(stored, this.deps.secretEncryptionKey)
85
+ : stored;
86
+ }
87
+ else {
88
+ values[field.key] = stored;
89
+ }
90
+ }
91
+ return {
92
+ status: 'ok',
93
+ typeCode: entity.typeCode,
94
+ providerCode: entity.providerCode,
95
+ values,
96
+ };
97
+ }
98
+ /**
99
+ * Map an entity to a masked DTO, marking unregistered-type rows as inert.
100
+ *
101
+ * The registry's **diagnostic** read (issue #129): a stored configuration
102
+ * whose contributing module is merely switched off keeps its label and its
103
+ * field shape, because deactivation drops no data and an inert row would say
104
+ * the type is gone. `resolve` above stays on the acting read, so nothing
105
+ * decrypted leaves for the same configuration.
106
+ */
107
+ toDto(entity) {
108
+ const descriptor = this.deps.registry.entry(entity.typeCode);
109
+ const variant = descriptor?.providers.find((p) => p.code === entity.providerCode);
110
+ const inert = !descriptor || !variant;
111
+ return {
112
+ id: entity.id,
113
+ code: entity.code,
114
+ name: entity.name,
115
+ typeCode: entity.typeCode,
116
+ typeLabel: descriptor?.label ?? null,
117
+ providerCode: entity.providerCode,
118
+ providerLabel: variant?.label ?? null,
119
+ inert,
120
+ // An inert row has no known field shape — expose no fields rather than
121
+ // risk leaking a value we cannot classify as secret.
122
+ fields: variant ? maskSecrets(entity.values, variant.fields) : [],
123
+ version: entity.version,
124
+ updatedAt: entity.updatedAt.toISOString(),
125
+ };
126
+ }
127
+ }
128
+ //# sourceMappingURL=credentials.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.service.js","sourceRoot":"","sources":["../../../src/backend/services/credentials.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import { z } from 'zod';
2
+ import type { ConfigurationFieldDto, ConfigurationTypeDescriptor, FieldDefinition, ProviderVariant } from '@endora-commerce/contracts';
3
+ /** Sentinel a masked secret is echoed back as; submitting it preserves the stored envelope. */
4
+ export declare const SECRET_MASK = "[redacted]";
5
+ export interface FieldError {
6
+ field: string;
7
+ message: string;
8
+ }
9
+ export interface BuildValuesResult {
10
+ values: Record<string, unknown>;
11
+ errors: FieldError[];
12
+ }
13
+ /**
14
+ * Field validation + secret masking derived from a configuration-type
15
+ * descriptor (feature 058, research §R2). The credentials core stays
16
+ * type-agnostic (Principle XIV): the write-validator is built purely from the
17
+ * selected provider's `FieldDefinition[]` — the same technique custom-fields
18
+ * uses to derive a validator from a stored definition.
19
+ */
20
+ /** Resolve a provider variant within a type descriptor, or throw. */
21
+ export declare function providerVariantFor(descriptor: ConfigurationTypeDescriptor, providerCode: string): ProviderVariant;
22
+ /**
23
+ * Build a Zod object schema validating a values bag against the provider
24
+ * variant's field definitions. Required fields must be present; required
25
+ * string/select fields must be non-empty. Unknown keys are stripped.
26
+ *
27
+ * Note: write-only secret semantics (a blank/`[redacted]` secret on update
28
+ * preserving the stored envelope) are applied by the update command BEFORE
29
+ * validation, not here — this validator sees the effective values to persist.
30
+ */
31
+ export declare function buildFieldValidator(descriptor: ConfigurationTypeDescriptor, providerCode: string): z.ZodType<Record<string, unknown>>;
32
+ /**
33
+ * Compute the effective values bag to persist, validating each field against the
34
+ * provider descriptor and applying write-only secret semantics (FR-015):
35
+ *
36
+ * - a secret field submitted blank / omitted / equal to the `[redacted]` mask
37
+ * keeps the existing envelope; a non-empty secret is re-encrypted;
38
+ * - a non-secret field absent from `submitted` keeps its existing value (so a
39
+ * partial update need not resend every field);
40
+ * - required fields must be present and non-empty (per-field error otherwise).
41
+ *
42
+ * `existing` is `{}` on create. Errors are collected per field (never throws for
43
+ * a validation problem) so the caller can surface a `CREDENTIAL_VALIDATION_FAILED`
44
+ * envelope carrying every offending field at once.
45
+ */
46
+ export declare function buildPersistableValues(opts: {
47
+ fields: FieldDefinition[];
48
+ submitted: Record<string, unknown>;
49
+ existing: Record<string, unknown>;
50
+ secretEncryptionKey: string | undefined;
51
+ }): BuildValuesResult;
52
+ /**
53
+ * Redact secret field values for an audit snapshot: a set secret becomes the
54
+ * `[redacted]` mask, non-secret values pass through (FR-014). Never emits a
55
+ * plaintext or an envelope.
56
+ */
57
+ export declare function redactSecretsForAudit(values: Record<string, unknown>, fields: FieldDefinition[]): Record<string, unknown>;
58
+ /**
59
+ * Mask a stored values bag for a read DTO: secret fields expose only an `isSet`
60
+ * boolean (never a value); non-secret fields expose their plain value. Returned
61
+ * in descriptor field order. A `maskSecrets` guarantee: a secret value is never
62
+ * emitted (SC-003 / FR-005).
63
+ */
64
+ export declare function maskSecrets(values: Record<string, unknown>, fields: FieldDefinition[]): ConfigurationFieldDto[];
65
+ //# sourceMappingURL=field-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-validator.d.ts","sourceRoot":"","sources":["../../../src/backend/services/field-validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EACV,qBAAqB,EACrB,2BAA2B,EAC3B,eAAe,EACf,eAAe,EAChB,MAAM,4BAA4B,CAAC;AAIpC,+FAA+F;AAC/F,eAAO,MAAM,WAAW,eAAe,CAAC;AAExC,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED;;;;;;GAMG;AAEH,qEAAqE;AACrE,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,2BAA2B,EACvC,YAAY,EAAE,MAAM,GACnB,eAAe,CAMjB;AAoBD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,2BAA2B,EACvC,YAAY,EAAE,MAAM,GACnB,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAepC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,mBAAmB,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC,GAAG,iBAAiB,CAqCpB;AAqBD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,eAAe,EAAE,GACxB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUzB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,eAAe,EAAE,GACxB,qBAAqB,EAAE,CAOzB"}
@@ -0,0 +1,165 @@
1
+ import { z } from 'zod';
2
+ import { ConfigurationTypeUnknown } from './configuration-type-registry.js';
3
+ import { encryptSecretValue, secretValueIsSet } from './secret-value-codec.js';
4
+ /** Sentinel a masked secret is echoed back as; submitting it preserves the stored envelope. */
5
+ export const SECRET_MASK = '[redacted]';
6
+ /**
7
+ * Field validation + secret masking derived from a configuration-type
8
+ * descriptor (feature 058, research §R2). The credentials core stays
9
+ * type-agnostic (Principle XIV): the write-validator is built purely from the
10
+ * selected provider's `FieldDefinition[]` — the same technique custom-fields
11
+ * uses to derive a validator from a stored definition.
12
+ */
13
+ /** Resolve a provider variant within a type descriptor, or throw. */
14
+ export function providerVariantFor(descriptor, providerCode) {
15
+ const variant = descriptor.providers.find((p) => p.code === providerCode);
16
+ if (!variant) {
17
+ throw new ConfigurationTypeUnknown(`${descriptor.code}/${providerCode}`);
18
+ }
19
+ return variant;
20
+ }
21
+ function baseSchemaForField(field) {
22
+ switch (field.kind) {
23
+ case 'number':
24
+ return z.number();
25
+ case 'boolean':
26
+ return z.boolean();
27
+ case 'select': {
28
+ const allowed = (field.options ?? []).map((o) => o.value);
29
+ return z
30
+ .string()
31
+ .refine((v) => allowed.includes(v), { message: `must be one of: ${allowed.join(', ')}` });
32
+ }
33
+ case 'string':
34
+ default:
35
+ return z.string();
36
+ }
37
+ }
38
+ /**
39
+ * Build a Zod object schema validating a values bag against the provider
40
+ * variant's field definitions. Required fields must be present; required
41
+ * string/select fields must be non-empty. Unknown keys are stripped.
42
+ *
43
+ * Note: write-only secret semantics (a blank/`[redacted]` secret on update
44
+ * preserving the stored envelope) are applied by the update command BEFORE
45
+ * validation, not here — this validator sees the effective values to persist.
46
+ */
47
+ export function buildFieldValidator(descriptor, providerCode) {
48
+ const variant = providerVariantFor(descriptor, providerCode);
49
+ const shape = {};
50
+ for (const field of variant.fields) {
51
+ let schema = baseSchemaForField(field);
52
+ if (field.required) {
53
+ if (field.kind === 'string' || field.kind === 'select') {
54
+ schema = schema.min(1, { message: 'is required' });
55
+ }
56
+ }
57
+ else {
58
+ schema = schema.optional();
59
+ }
60
+ shape[field.key] = schema;
61
+ }
62
+ return z.object(shape).strip();
63
+ }
64
+ /**
65
+ * Compute the effective values bag to persist, validating each field against the
66
+ * provider descriptor and applying write-only secret semantics (FR-015):
67
+ *
68
+ * - a secret field submitted blank / omitted / equal to the `[redacted]` mask
69
+ * keeps the existing envelope; a non-empty secret is re-encrypted;
70
+ * - a non-secret field absent from `submitted` keeps its existing value (so a
71
+ * partial update need not resend every field);
72
+ * - required fields must be present and non-empty (per-field error otherwise).
73
+ *
74
+ * `existing` is `{}` on create. Errors are collected per field (never throws for
75
+ * a validation problem) so the caller can surface a `CREDENTIAL_VALIDATION_FAILED`
76
+ * envelope carrying every offending field at once.
77
+ */
78
+ export function buildPersistableValues(opts) {
79
+ const { fields, submitted, existing, secretEncryptionKey } = opts;
80
+ const values = {};
81
+ const errors = [];
82
+ for (const field of fields) {
83
+ if (field.secret) {
84
+ const raw = submitted[field.key];
85
+ const providesNew = typeof raw === 'string' && raw.length > 0 && raw !== SECRET_MASK;
86
+ if (providesNew) {
87
+ values[field.key] = encryptSecretValue(raw, secretEncryptionKey);
88
+ }
89
+ else if (secretValueIsSet(existing[field.key])) {
90
+ values[field.key] = existing[field.key];
91
+ }
92
+ if (field.required && !(field.key in values)) {
93
+ errors.push({ field: field.key, message: 'is required' });
94
+ }
95
+ continue;
96
+ }
97
+ const hasSubmitted = Object.prototype.hasOwnProperty.call(submitted, field.key);
98
+ const candidate = hasSubmitted ? submitted[field.key] : existing[field.key];
99
+ if (candidate === undefined || candidate === null) {
100
+ if (field.required)
101
+ errors.push({ field: field.key, message: 'is required' });
102
+ continue;
103
+ }
104
+ const typeError = validateNonSecretValue(field, candidate);
105
+ if (typeError) {
106
+ errors.push({ field: field.key, message: typeError });
107
+ continue;
108
+ }
109
+ values[field.key] = candidate;
110
+ }
111
+ return { values, errors };
112
+ }
113
+ function validateNonSecretValue(field, value) {
114
+ switch (field.kind) {
115
+ case 'number':
116
+ return typeof value === 'number' && Number.isFinite(value) ? null : 'must be a number';
117
+ case 'boolean':
118
+ return typeof value === 'boolean' ? null : 'must be a boolean';
119
+ case 'select': {
120
+ if (typeof value !== 'string')
121
+ return 'must be a string';
122
+ const allowed = (field.options ?? []).map((o) => o.value);
123
+ return allowed.includes(value) ? null : `must be one of: ${allowed.join(', ')}`;
124
+ }
125
+ case 'string':
126
+ default:
127
+ if (typeof value !== 'string')
128
+ return 'must be a string';
129
+ if (field.required && value.length === 0)
130
+ return 'is required';
131
+ return null;
132
+ }
133
+ }
134
+ /**
135
+ * Redact secret field values for an audit snapshot: a set secret becomes the
136
+ * `[redacted]` mask, non-secret values pass through (FR-014). Never emits a
137
+ * plaintext or an envelope.
138
+ */
139
+ export function redactSecretsForAudit(values, fields) {
140
+ const out = {};
141
+ for (const field of fields) {
142
+ if (field.secret) {
143
+ out[field.key] = secretValueIsSet(values[field.key]) ? SECRET_MASK : null;
144
+ }
145
+ else if (Object.prototype.hasOwnProperty.call(values, field.key)) {
146
+ out[field.key] = values[field.key];
147
+ }
148
+ }
149
+ return out;
150
+ }
151
+ /**
152
+ * Mask a stored values bag for a read DTO: secret fields expose only an `isSet`
153
+ * boolean (never a value); non-secret fields expose their plain value. Returned
154
+ * in descriptor field order. A `maskSecrets` guarantee: a secret value is never
155
+ * emitted (SC-003 / FR-005).
156
+ */
157
+ export function maskSecrets(values, fields) {
158
+ return fields.map((field) => {
159
+ if (field.secret) {
160
+ return { key: field.key, secret: true, isSet: secretValueIsSet(values[field.key]) };
161
+ }
162
+ return { key: field.key, secret: false, value: values[field.key] };
163
+ });
164
+ }
165
+ //# sourceMappingURL=field-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-validator.js","sourceRoot":"","sources":["../../../src/backend/services/field-validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { ConfigurationTypeRegistry } from './configuration-type-registry.js';
2
+ /**
3
+ * Process-wide ConfigurationTypeRegistry (feature 058).
4
+ *
5
+ * The module lifecycle install-hook context is fixed (`{ em, redis, log,
6
+ * module }`) and cannot carry services, so an external / overlay module cannot
7
+ * receive the registry through dependency injection. This shared singleton is
8
+ * the explicit cross-module seam a module imports to contribute a configuration
9
+ * type on enable (mirrors `delivery_methods/services/registry-singleton.ts`):
10
+ *
11
+ * import { configurationTypeRegistry } from '.../credentials/services/registry-singleton.js';
12
+ * configurationTypeRegistry.register(myConfigurationType);
13
+ *
14
+ * `composition.ts` populates this same instance with the core types (LLM, email
15
+ * adapter) at boot and passes it to the credentials plugin, so a type registered
16
+ * from an install hook is immediately recognised by the live admin + resolve
17
+ * paths. Registering without editing the shared core registry satisfies
18
+ * Principle XV.
19
+ *
20
+ * The presence probe is wired here rather than in the class: this is the one
21
+ * instance that participates in the platform's lifecycle, and a registry a test
22
+ * builds for itself should keep answering about the types that test registered
23
+ * (issue #129). `presenceOf` rather than `isPresent`, because a type contributed
24
+ * through this seam may name an owner no manifest declares, and collapsing
25
+ * "unknown id" into "absent" would filter the extension point away.
26
+ */
27
+ export declare const configurationTypeRegistry: ConfigurationTypeRegistry;
28
+ //# sourceMappingURL=registry-singleton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-singleton.d.ts","sourceRoot":"","sources":["../../../src/backend/services/registry-singleton.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB,2BAErC,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { effectiveState } from '@endora-commerce/platform/kernel';
2
+ import { ConfigurationTypeRegistry } from './configuration-type-registry.js';
3
+ /**
4
+ * Process-wide ConfigurationTypeRegistry (feature 058).
5
+ *
6
+ * The module lifecycle install-hook context is fixed (`{ em, redis, log,
7
+ * module }`) and cannot carry services, so an external / overlay module cannot
8
+ * receive the registry through dependency injection. This shared singleton is
9
+ * the explicit cross-module seam a module imports to contribute a configuration
10
+ * type on enable (mirrors `delivery_methods/services/registry-singleton.ts`):
11
+ *
12
+ * import { configurationTypeRegistry } from '.../credentials/services/registry-singleton.js';
13
+ * configurationTypeRegistry.register(myConfigurationType);
14
+ *
15
+ * `composition.ts` populates this same instance with the core types (LLM, email
16
+ * adapter) at boot and passes it to the credentials plugin, so a type registered
17
+ * from an install hook is immediately recognised by the live admin + resolve
18
+ * paths. Registering without editing the shared core registry satisfies
19
+ * Principle XV.
20
+ *
21
+ * The presence probe is wired here rather than in the class: this is the one
22
+ * instance that participates in the platform's lifecycle, and a registry a test
23
+ * builds for itself should keep answering about the types that test registered
24
+ * (issue #129). `presenceOf` rather than `isPresent`, because a type contributed
25
+ * through this seam may name an owner no manifest declares, and collapsing
26
+ * "unknown id" into "absent" would filter the extension point away.
27
+ */
28
+ export const configurationTypeRegistry = new ConfigurationTypeRegistry(undefined, (moduleId) => effectiveState.presenceOf(moduleId));
29
+ //# sourceMappingURL=registry-singleton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-singleton.js","sourceRoot":"","sources":["../../../src/backend/services/registry-singleton.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,yBAAyB,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE,CAC7F,cAAc,CAAC,UAAU,CAAC,QAAQ,CAAC,CACpC,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Secret-value codec for credential-configuration secret fields (feature 058,
3
+ * research §R4).
4
+ *
5
+ * This is a verbatim duplication of the codec that used to live at
6
+ * `settings/services/secret-value-codec.ts`. The original justification —
7
+ * importing the settings module's private codec across a module boundary would
8
+ * couple the two modules (Principle I) and break independent removability — no
9
+ * longer holds: feature 072 T018 moved that codec into the kernel
10
+ * (`kernel/settings/secret-value-codec.ts`), and a module may import the kernel.
11
+ * The duplication is therefore now debt, not a trade; it is left in place here
12
+ * because collapsing it is a behaviour-carrying change, not part of the
13
+ * relocation. Both codecs read the SAME
14
+ * env key `SETTINGS_SECRET_ENCRYPTION_KEY` (base64, 32 bytes) — no new secret to
15
+ * provision — and produce self-describing envelopes, so a value written by one
16
+ * decrypts under the other.
17
+ *
18
+ * There is NO plaintext fallback on write: encrypting without a key throws
19
+ * {@link SecretKeyMissing} (fail-closed, the desired behaviour for credentials).
20
+ */
21
+ declare const ALGORITHM = "aes-256-gcm";
22
+ export interface SecretEnvelope {
23
+ v: 1;
24
+ alg: typeof ALGORITHM;
25
+ iv: string;
26
+ ct: string;
27
+ tag: string;
28
+ }
29
+ export declare class SecretKeyMissing extends Error {
30
+ readonly name = "SecretKeyMissing";
31
+ readonly code: "SETTING_SECRET_KEY_MISSING";
32
+ constructor();
33
+ }
34
+ /**
35
+ * The encryption key is present but not a valid AES-256 key (must decode to
36
+ * exactly 32 bytes). Carries the same error code as {@link SecretKeyMissing} so
37
+ * callers surface a clear 5xx instead of a bare "Internal server error".
38
+ */
39
+ export declare class SecretKeyInvalid extends Error {
40
+ readonly name = "SecretKeyInvalid";
41
+ readonly code: "SETTING_SECRET_KEY_MISSING";
42
+ constructor(actualBytes: number);
43
+ }
44
+ export declare function isSecretEnvelope(value: unknown): value is SecretEnvelope;
45
+ export declare function encryptSecretValue(plaintext: string, keyBase64: string | undefined): SecretEnvelope;
46
+ /**
47
+ * Resolve a stored secret value to its plaintext. Plain strings are legacy
48
+ * pre-encryption values and pass through unchanged; envelopes require the key
49
+ * and an intact auth tag.
50
+ */
51
+ export declare function decryptSecretValue(stored: unknown, keyBase64: string | undefined): string;
52
+ /** A secret counts as "set" when it is an envelope or a non-empty legacy string. */
53
+ export declare function secretValueIsSet(stored: unknown): boolean;
54
+ export {};
55
+ //# sourceMappingURL=secret-value-codec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-value-codec.d.ts","sourceRoot":"","sources":["../../../src/backend/services/secret-value-codec.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,QAAA,MAAM,SAAS,gBAAgB,CAAC;AAIhC,MAAM,WAAW,cAAc;IAC7B,CAAC,EAAE,CAAC,CAAC;IACL,GAAG,EAAE,OAAO,SAAS,CAAC;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,SAAkB,IAAI,sBAAsB;IAC5C,QAAQ,CAAC,IAAI,EAAG,4BAA4B,CAAU;;CAMvD;AAED;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,SAAkB,IAAI,sBAAsB;IAC5C,QAAQ,CAAC,IAAI,EAAG,4BAA4B,CAAU;gBAC1C,WAAW,EAAE,MAAM;CAMhC;AAUD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAUxE;AAED,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,cAAc,CAahB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,MAAM,CAcR;AAED,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAGzD"}
@@ -0,0 +1,105 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
2
+ /**
3
+ * Secret-value codec for credential-configuration secret fields (feature 058,
4
+ * research §R4).
5
+ *
6
+ * This is a verbatim duplication of the codec that used to live at
7
+ * `settings/services/secret-value-codec.ts`. The original justification —
8
+ * importing the settings module's private codec across a module boundary would
9
+ * couple the two modules (Principle I) and break independent removability — no
10
+ * longer holds: feature 072 T018 moved that codec into the kernel
11
+ * (`kernel/settings/secret-value-codec.ts`), and a module may import the kernel.
12
+ * The duplication is therefore now debt, not a trade; it is left in place here
13
+ * because collapsing it is a behaviour-carrying change, not part of the
14
+ * relocation. Both codecs read the SAME
15
+ * env key `SETTINGS_SECRET_ENCRYPTION_KEY` (base64, 32 bytes) — no new secret to
16
+ * provision — and produce self-describing envelopes, so a value written by one
17
+ * decrypts under the other.
18
+ *
19
+ * There is NO plaintext fallback on write: encrypting without a key throws
20
+ * {@link SecretKeyMissing} (fail-closed, the desired behaviour for credentials).
21
+ */
22
+ const ALGORITHM = 'aes-256-gcm';
23
+ const IV_LENGTH = 12;
24
+ const KEY_LENGTH = 32;
25
+ export class SecretKeyMissing extends Error {
26
+ name = 'SecretKeyMissing';
27
+ code = 'SETTING_SECRET_KEY_MISSING';
28
+ constructor() {
29
+ super('SETTINGS_SECRET_ENCRYPTION_KEY is not configured — secret credential fields cannot be processed.');
30
+ }
31
+ }
32
+ /**
33
+ * The encryption key is present but not a valid AES-256 key (must decode to
34
+ * exactly 32 bytes). Carries the same error code as {@link SecretKeyMissing} so
35
+ * callers surface a clear 5xx instead of a bare "Internal server error".
36
+ */
37
+ export class SecretKeyInvalid extends Error {
38
+ name = 'SecretKeyInvalid';
39
+ code = 'SETTING_SECRET_KEY_MISSING';
40
+ constructor(actualBytes) {
41
+ super(`SETTINGS_SECRET_ENCRYPTION_KEY is misconfigured — it must decode to ${KEY_LENGTH} bytes ` +
42
+ `(got ${actualBytes}). Set it to 32 random bytes, base64-encoded (e.g. \`openssl rand -base64 32\`).`);
43
+ }
44
+ }
45
+ function keyBuffer(keyBase64) {
46
+ const key = Buffer.from(keyBase64, 'base64');
47
+ if (key.length !== KEY_LENGTH) {
48
+ throw new SecretKeyInvalid(key.length);
49
+ }
50
+ return key;
51
+ }
52
+ export function isSecretEnvelope(value) {
53
+ if (typeof value !== 'object' || value === null)
54
+ return false;
55
+ const v = value;
56
+ return (v['v'] === 1 &&
57
+ v['alg'] === ALGORITHM &&
58
+ typeof v['iv'] === 'string' &&
59
+ typeof v['ct'] === 'string' &&
60
+ typeof v['tag'] === 'string');
61
+ }
62
+ export function encryptSecretValue(plaintext, keyBase64) {
63
+ if (!keyBase64)
64
+ throw new SecretKeyMissing();
65
+ const key = keyBuffer(keyBase64);
66
+ const iv = randomBytes(IV_LENGTH);
67
+ const cipher = createCipheriv(ALGORITHM, key, iv);
68
+ const ct = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
69
+ return {
70
+ v: 1,
71
+ alg: ALGORITHM,
72
+ iv: iv.toString('base64'),
73
+ ct: ct.toString('base64'),
74
+ tag: cipher.getAuthTag().toString('base64'),
75
+ };
76
+ }
77
+ /**
78
+ * Resolve a stored secret value to its plaintext. Plain strings are legacy
79
+ * pre-encryption values and pass through unchanged; envelopes require the key
80
+ * and an intact auth tag.
81
+ */
82
+ export function decryptSecretValue(stored, keyBase64) {
83
+ if (typeof stored === 'string')
84
+ return stored;
85
+ if (!isSecretEnvelope(stored)) {
86
+ throw new Error('Stored secret value is neither a string nor a v1 secret envelope.');
87
+ }
88
+ if (!keyBase64)
89
+ throw new SecretKeyMissing();
90
+ const key = keyBuffer(keyBase64);
91
+ const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(stored.iv, 'base64'));
92
+ decipher.setAuthTag(Buffer.from(stored.tag, 'base64'));
93
+ const plaintext = Buffer.concat([
94
+ decipher.update(Buffer.from(stored.ct, 'base64')),
95
+ decipher.final(),
96
+ ]);
97
+ return plaintext.toString('utf8');
98
+ }
99
+ /** A secret counts as "set" when it is an envelope or a non-empty legacy string. */
100
+ export function secretValueIsSet(stored) {
101
+ if (isSecretEnvelope(stored))
102
+ return true;
103
+ return typeof stored === 'string' && stored.length > 0;
104
+ }
105
+ //# sourceMappingURL=secret-value-codec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-value-codec.js","sourceRoot":"","sources":["../../../src/backend/services/secret-value-codec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { ConfigurationTypeDescriptor } from '@endora-commerce/contracts';
2
+ /**
3
+ * Core `email_adapter` configuration type (feature 058, US1 / research §R2).
4
+ *
5
+ * Mirrors the newsletter SMTP/SES field set. Each provider declares exactly one
6
+ * secret field (`password` / `secretAccessKey` / `apiKey`); the rest are plain.
7
+ * Provider meaning (how mail is actually sent) is owned by the consumer.
8
+ */
9
+ export declare const emailAdapterConfigurationType: ConfigurationTypeDescriptor;
10
+ //# sourceMappingURL=email-adapter.type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-adapter.type.d.ts","sourceRoot":"","sources":["../../../src/backend/types/email-adapter.type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AAE9E;;;;;;GAMG;AAEH,eAAO,MAAM,6BAA6B,EAAE,2BAwC3C,CAAC"}