@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.
- package/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/admin/api/credentials-client.d.ts +11 -0
- package/dist/admin/api/credentials-client.d.ts.map +1 -0
- package/dist/admin/api/credentials-client.js +38 -0
- package/dist/admin/api/credentials-client.js.map +1 -0
- package/dist/admin/components/ConfigurationForm.d.ts +22 -0
- package/dist/admin/components/ConfigurationForm.d.ts.map +1 -0
- package/dist/admin/components/ConfigurationForm.js +89 -0
- package/dist/admin/components/ConfigurationForm.js.map +1 -0
- package/dist/admin/index.d.ts +47 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +65 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/CredentialsNewPage.d.ts +19 -0
- package/dist/admin/pages/CredentialsNewPage.d.ts.map +1 -0
- package/dist/admin/pages/CredentialsNewPage.js +22 -0
- package/dist/admin/pages/CredentialsNewPage.js.map +1 -0
- package/dist/admin/pages/CredentialsPage.d.ts +22 -0
- package/dist/admin/pages/CredentialsPage.d.ts.map +1 -0
- package/dist/admin/pages/CredentialsPage.js +154 -0
- package/dist/admin/pages/CredentialsPage.js.map +1 -0
- package/dist/admin-ui/ConfigurationPreviewModal.d.ts +16 -0
- package/dist/admin-ui/ConfigurationPreviewModal.d.ts.map +1 -0
- package/dist/admin-ui/ConfigurationPreviewModal.js +15 -0
- package/dist/admin-ui/ConfigurationPreviewModal.js.map +1 -0
- package/dist/admin-ui/index.d.ts +57 -0
- package/dist/admin-ui/index.d.ts.map +1 -0
- package/dist/admin-ui/index.js +56 -0
- package/dist/admin-ui/index.js.map +1 -0
- package/dist/backend/commands/create-configuration.command.d.ts +20 -0
- package/dist/backend/commands/create-configuration.command.d.ts.map +1 -0
- package/dist/backend/commands/create-configuration.command.js +70 -0
- package/dist/backend/commands/create-configuration.command.js.map +1 -0
- package/dist/backend/commands/delete-configuration.command.d.ts +16 -0
- package/dist/backend/commands/delete-configuration.command.d.ts.map +1 -0
- package/dist/backend/commands/delete-configuration.command.js +48 -0
- package/dist/backend/commands/delete-configuration.command.js.map +1 -0
- package/dist/backend/commands/update-configuration.command.d.ts +20 -0
- package/dist/backend/commands/update-configuration.command.d.ts.map +1 -0
- package/dist/backend/commands/update-configuration.command.js +94 -0
- package/dist/backend/commands/update-configuration.command.js.map +1 -0
- package/dist/backend/entities/credential-configuration.entity.d.ts +30 -0
- package/dist/backend/entities/credential-configuration.entity.d.ts.map +1 -0
- package/dist/backend/entities/credential-configuration.entity.js +84 -0
- package/dist/backend/entities/credential-configuration.entity.js.map +1 -0
- package/dist/backend/index.d.ts +73 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +80 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +43 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +25 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +27 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +50 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/services/configuration-type-registry.d.ts +105 -0
- package/dist/backend/services/configuration-type-registry.d.ts.map +1 -0
- package/dist/backend/services/configuration-type-registry.js +111 -0
- package/dist/backend/services/configuration-type-registry.js.map +1 -0
- package/dist/backend/services/credentials.service.d.ts +63 -0
- package/dist/backend/services/credentials.service.d.ts.map +1 -0
- package/dist/backend/services/credentials.service.js +128 -0
- package/dist/backend/services/credentials.service.js.map +1 -0
- package/dist/backend/services/field-validator.d.ts +65 -0
- package/dist/backend/services/field-validator.d.ts.map +1 -0
- package/dist/backend/services/field-validator.js +165 -0
- package/dist/backend/services/field-validator.js.map +1 -0
- package/dist/backend/services/registry-singleton.d.ts +28 -0
- package/dist/backend/services/registry-singleton.d.ts.map +1 -0
- package/dist/backend/services/registry-singleton.js +29 -0
- package/dist/backend/services/registry-singleton.js.map +1 -0
- package/dist/backend/services/secret-value-codec.d.ts +55 -0
- package/dist/backend/services/secret-value-codec.d.ts.map +1 -0
- package/dist/backend/services/secret-value-codec.js +105 -0
- package/dist/backend/services/secret-value-codec.js.map +1 -0
- package/dist/backend/types/email-adapter.type.d.ts +10 -0
- package/dist/backend/types/email-adapter.type.d.ts.map +1 -0
- package/dist/backend/types/email-adapter.type.js +49 -0
- package/dist/backend/types/email-adapter.type.js.map +1 -0
- package/dist/backend/types/llm.type.d.ts +3 -0
- package/dist/backend/types/llm.type.d.ts.map +1 -0
- package/dist/backend/types/llm.type.js +35 -0
- package/dist/backend/types/llm.type.js.map +1 -0
- package/dist/manifest.d.ts +176 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +167 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260721T011509_credentials_init.d.ts +17 -0
- package/dist/migrations/20260721T011509_credentials_init.d.ts.map +1 -0
- package/dist/migrations/20260721T011509_credentials_init.js +36 -0
- package/dist/migrations/20260721T011509_credentials_init.js.map +1 -0
- package/dist/migrations/index.d.ts +27 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +29 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/credentials.md +202 -0
- package/i18n/en.json +47 -0
- package/i18n/pl.json +47 -0
- package/package.json +98 -0
- 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"}
|