sms-panel-manager 0.0.0-stage → 0.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/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +142 -2
- package/SECURITY.md +7 -0
- package/dist/encryption-BUH96uQn.d.cts +119 -0
- package/dist/encryption-Ckv6RYE0.d.ts +119 -0
- package/dist/index.cjs +565 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +546 -0
- package/dist/index.js.map +1 -0
- package/dist/manager-B3b12wET.d.cts +109 -0
- package/dist/manager-B3b12wET.d.ts +109 -0
- package/dist/mongoose.cjs +363 -0
- package/dist/mongoose.cjs.map +1 -0
- package/dist/mongoose.d.cts +132 -0
- package/dist/mongoose.d.ts +132 -0
- package/dist/mongoose.js +357 -0
- package/dist/mongoose.js.map +1 -0
- package/dist/nestjs.cjs +151 -0
- package/dist/nestjs.cjs.map +1 -0
- package/dist/nestjs.d.cts +38 -0
- package/dist/nestjs.d.ts +38 -0
- package/dist/nestjs.js +149 -0
- package/dist/nestjs.js.map +1 -0
- package/dist/provider-QoH6qlAe.d.cts +70 -0
- package/dist/provider-QoH6qlAe.d.ts +70 -0
- package/dist/providers.cjs +433 -0
- package/dist/providers.cjs.map +1 -0
- package/dist/providers.d.cts +139 -0
- package/dist/providers.d.ts +139 -0
- package/dist/providers.js +419 -0
- package/dist/providers.js.map +1 -0
- package/package.json +113 -4
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
var crypto = require('crypto');
|
|
5
|
+
|
|
6
|
+
// src/core/actions.ts
|
|
7
|
+
var RESERVED_PARAMETER_KEYS = /* @__PURE__ */ new Set([
|
|
8
|
+
"recipient",
|
|
9
|
+
"idempotencyKey",
|
|
10
|
+
"signal",
|
|
11
|
+
"correlationId",
|
|
12
|
+
"metadata"
|
|
13
|
+
]);
|
|
14
|
+
function defineSmsActions(actions) {
|
|
15
|
+
for (const [actionKey, action] of Object.entries(actions)) {
|
|
16
|
+
if (!/^[a-z][a-z0-9_]*$/.test(actionKey)) {
|
|
17
|
+
throw new TypeError(
|
|
18
|
+
`Invalid SMS action key "${actionKey}". Use lower snake_case.`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
for (const parameterKey of Object.keys(action.params)) {
|
|
22
|
+
if (RESERVED_PARAMETER_KEYS.has(parameterKey)) {
|
|
23
|
+
throw new TypeError(
|
|
24
|
+
`SMS action "${actionKey}" uses reserved parameter "${parameterKey}".`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return actions;
|
|
30
|
+
}
|
|
31
|
+
function createActionPayloadSchema(action) {
|
|
32
|
+
const shape = {
|
|
33
|
+
recipient: zod.z.string().min(1)
|
|
34
|
+
};
|
|
35
|
+
for (const [key, parameter] of Object.entries(action.params)) {
|
|
36
|
+
shape[key] = parameter.schema;
|
|
37
|
+
}
|
|
38
|
+
return zod.z.object(shape).strict();
|
|
39
|
+
}
|
|
40
|
+
function serializeSmsActions(actions) {
|
|
41
|
+
return Object.entries(actions).map(([key, action]) => ({
|
|
42
|
+
key,
|
|
43
|
+
title: action.title,
|
|
44
|
+
...action.description === void 0 ? {} : { description: action.description },
|
|
45
|
+
parameters: Object.entries(action.params).map(
|
|
46
|
+
([parameterKey, parameter]) => ({
|
|
47
|
+
key: parameterKey,
|
|
48
|
+
title: parameter.metadata.title,
|
|
49
|
+
...parameter.metadata.description === void 0 ? {} : { description: parameter.metadata.description },
|
|
50
|
+
type: parameter.type,
|
|
51
|
+
required: !parameter.isOptional
|
|
52
|
+
})
|
|
53
|
+
)
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/core/bindings.ts
|
|
58
|
+
var InvalidParameterBindingError = class extends TypeError {
|
|
59
|
+
code = "template_binding_invalid";
|
|
60
|
+
constructor(message) {
|
|
61
|
+
super(message);
|
|
62
|
+
this.name = "InvalidParameterBindingError";
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
function assertSafeKey(value, label) {
|
|
66
|
+
if (value.length === 0 || value === "__proto__" || value === "prototype" || value === "constructor") {
|
|
67
|
+
throw new InvalidParameterBindingError(`Unsafe or empty ${label}.`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function applyParameterBindings(parameters, bindings) {
|
|
71
|
+
if (bindings.length === 0) {
|
|
72
|
+
throw new InvalidParameterBindingError(
|
|
73
|
+
"At least one parameter binding is required."
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
const kind = bindings[0]?.kind;
|
|
77
|
+
if (bindings.some((binding) => binding.kind !== kind)) {
|
|
78
|
+
throw new InvalidParameterBindingError(
|
|
79
|
+
"Parameter binding kinds cannot be mixed."
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const sources = /* @__PURE__ */ new Set();
|
|
83
|
+
const destinations = /* @__PURE__ */ new Set();
|
|
84
|
+
for (const binding of bindings) {
|
|
85
|
+
assertSafeKey(binding.sourceParameter, "source parameter");
|
|
86
|
+
if (sources.has(binding.sourceParameter)) {
|
|
87
|
+
throw new InvalidParameterBindingError(
|
|
88
|
+
`Source parameter "${binding.sourceParameter}" is mapped more than once.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
sources.add(binding.sourceParameter);
|
|
92
|
+
const destination = binding.kind === "named" ? binding.providerParameter : binding.kind === "fixed-slot" ? binding.slot : binding.position;
|
|
93
|
+
if (typeof destination === "string")
|
|
94
|
+
assertSafeKey(destination, "destination");
|
|
95
|
+
if (destinations.has(destination)) {
|
|
96
|
+
throw new InvalidParameterBindingError(
|
|
97
|
+
`Provider destination "${String(destination)}" is mapped more than once.`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
destinations.add(destination);
|
|
101
|
+
}
|
|
102
|
+
if (Object.keys(parameters).some((parameter) => !sources.has(parameter))) {
|
|
103
|
+
throw new InvalidParameterBindingError(
|
|
104
|
+
"Every supplied action parameter must be mapped exactly once."
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
if (kind === "ordered") {
|
|
108
|
+
const ordered = bindings.map((binding) => {
|
|
109
|
+
if (binding.kind !== "ordered")
|
|
110
|
+
throw new InvalidParameterBindingError("Invalid binding.");
|
|
111
|
+
if (!Number.isSafeInteger(binding.position) || binding.position < 1) {
|
|
112
|
+
throw new InvalidParameterBindingError(
|
|
113
|
+
"Ordered positions must be positive integers."
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return binding;
|
|
117
|
+
}).sort((left, right) => left.position - right.position);
|
|
118
|
+
if (ordered.some((binding, index) => binding.position !== index + 1)) {
|
|
119
|
+
throw new InvalidParameterBindingError(
|
|
120
|
+
"Ordered positions must be contiguous from one."
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const values = ordered.map(
|
|
124
|
+
(binding) => parameters[binding.sourceParameter]
|
|
125
|
+
);
|
|
126
|
+
while (values.at(-1) === void 0) values.pop();
|
|
127
|
+
if (values.some((value) => value === void 0)) {
|
|
128
|
+
throw new InvalidParameterBindingError(
|
|
129
|
+
"An omitted ordered parameter cannot precede a supplied parameter."
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return values;
|
|
133
|
+
}
|
|
134
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
135
|
+
for (const binding of bindings) {
|
|
136
|
+
if (binding.kind === "ordered") {
|
|
137
|
+
throw new InvalidParameterBindingError("Invalid ordered binding state.");
|
|
138
|
+
}
|
|
139
|
+
const destination = binding.kind === "named" ? binding.providerParameter : binding.slot;
|
|
140
|
+
if (binding.sourceParameter in parameters) {
|
|
141
|
+
result[destination] = parameters[binding.sourceParameter];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/core/phone.ts
|
|
148
|
+
var IRAN_MOBILE_LOCAL = /^09\d{9}$/;
|
|
149
|
+
var InvalidIranianMobileNumberError = class extends TypeError {
|
|
150
|
+
code = "invalid_recipient";
|
|
151
|
+
constructor() {
|
|
152
|
+
super("Recipient must be a valid Iranian mobile number.");
|
|
153
|
+
this.name = "InvalidIranianMobileNumberError";
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
function normalizeIranianMobile(input) {
|
|
157
|
+
const normalized = input.trim().replace(/^0098/, "0").replace(/^\+98/, "0").replace(/^98/, "0");
|
|
158
|
+
if (!IRAN_MOBILE_LOCAL.test(normalized)) {
|
|
159
|
+
throw new InvalidIranianMobileNumberError();
|
|
160
|
+
}
|
|
161
|
+
return `+98${normalized.slice(1)}`;
|
|
162
|
+
}
|
|
163
|
+
function toIranianLocalMobile(input) {
|
|
164
|
+
const canonical = normalizeIranianMobile(input);
|
|
165
|
+
return `0${canonical.slice(3)}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/core/manager.ts
|
|
169
|
+
function createSmsManager(options) {
|
|
170
|
+
return {
|
|
171
|
+
async send(actionKey, payload, sendOptions = {}) {
|
|
172
|
+
const action = options.actions[actionKey];
|
|
173
|
+
if (action === void 0) {
|
|
174
|
+
throw new TypeError(`Unknown SMS action "${actionKey}".`);
|
|
175
|
+
}
|
|
176
|
+
const validated = createActionPayloadSchema(action).parse(payload);
|
|
177
|
+
const { recipient, ...parameters } = validated;
|
|
178
|
+
return options.executor.execute({
|
|
179
|
+
actionKey,
|
|
180
|
+
recipient: normalizeIranianMobile(recipient),
|
|
181
|
+
parameters,
|
|
182
|
+
options: sendOptions
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function canonicalize(value) {
|
|
188
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
if (typeof value === "number") {
|
|
192
|
+
if (!Number.isFinite(value)) {
|
|
193
|
+
throw new TypeError("Cannot fingerprint a non-finite number.");
|
|
194
|
+
}
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
197
|
+
if (value instanceof Date) {
|
|
198
|
+
if (Number.isNaN(value.getTime())) {
|
|
199
|
+
throw new TypeError("Cannot fingerprint an invalid date.");
|
|
200
|
+
}
|
|
201
|
+
return { $date: value.toISOString() };
|
|
202
|
+
}
|
|
203
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
204
|
+
if (typeof value === "object") {
|
|
205
|
+
const prototype = Object.getPrototypeOf(value);
|
|
206
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
207
|
+
throw new TypeError("Only plain objects can be fingerprinted.");
|
|
208
|
+
}
|
|
209
|
+
const result = {};
|
|
210
|
+
for (const key of Object.keys(value).sort()) {
|
|
211
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
212
|
+
throw new TypeError("Unsafe object key cannot be fingerprinted.");
|
|
213
|
+
}
|
|
214
|
+
const item = value[key];
|
|
215
|
+
if (item === void 0) {
|
|
216
|
+
throw new TypeError("Undefined values cannot be fingerprinted.");
|
|
217
|
+
}
|
|
218
|
+
result[key] = canonicalize(item);
|
|
219
|
+
}
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
throw new TypeError(`Unsupported fingerprint value: ${typeof value}.`);
|
|
223
|
+
}
|
|
224
|
+
function createPrivacyFingerprint(secret, namespace, value) {
|
|
225
|
+
if (secret.byteLength < 32) {
|
|
226
|
+
throw new TypeError("Fingerprint secret must contain at least 32 bytes.");
|
|
227
|
+
}
|
|
228
|
+
const canonical = JSON.stringify(canonicalize(value));
|
|
229
|
+
return crypto.createHmac("sha256", secret).update(namespace).update("\0").update(canonical).digest("base64url");
|
|
230
|
+
}
|
|
231
|
+
function fingerprintsEqual(left, right) {
|
|
232
|
+
const leftBuffer = Buffer.from(left);
|
|
233
|
+
const rightBuffer = Buffer.from(right);
|
|
234
|
+
return leftBuffer.byteLength === rightBuffer.byteLength && crypto.timingSafeEqual(leftBuffer, rightBuffer);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/core/orchestrator.ts
|
|
238
|
+
function publicResult(operation) {
|
|
239
|
+
const status = operation.state === "reserved" || operation.state === "processing" ? "pending" : operation.state;
|
|
240
|
+
return {
|
|
241
|
+
operationId: operation.id,
|
|
242
|
+
status,
|
|
243
|
+
...operation.providerMessageId === void 0 ? {} : { providerMessageId: operation.providerMessageId }
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
var SmsOrchestrator = class {
|
|
247
|
+
constructor(options) {
|
|
248
|
+
this.options = options;
|
|
249
|
+
this.scopeKey = options.scopeKey ?? "global";
|
|
250
|
+
this.retentionMs = options.operationRetentionMs ?? 30 * 24 * 60 * 60 * 1e3;
|
|
251
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
252
|
+
if (this.retentionMs <= 0 || !Number.isSafeInteger(this.retentionMs)) {
|
|
253
|
+
throw new TypeError(
|
|
254
|
+
"Operation retention must be a positive integer of milliseconds."
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
options;
|
|
259
|
+
scopeKey;
|
|
260
|
+
retentionMs;
|
|
261
|
+
now;
|
|
262
|
+
async execute(command) {
|
|
263
|
+
if (command.options.idempotencyKey !== void 0 && (command.options.idempotencyKey.length < 1 || command.options.idempotencyKey.length > 200)) {
|
|
264
|
+
throw new TypeError(
|
|
265
|
+
"Idempotency key must contain between 1 and 200 characters."
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
const configuration = await this.options.configurations.resolve(
|
|
269
|
+
this.scopeKey,
|
|
270
|
+
command.actionKey
|
|
271
|
+
);
|
|
272
|
+
const providerParameters = applyParameterBindings(
|
|
273
|
+
command.parameters,
|
|
274
|
+
configuration.bindings
|
|
275
|
+
);
|
|
276
|
+
const idempotencyKey = command.options.idempotencyKey ?? crypto.randomUUID();
|
|
277
|
+
const expiresAt = new Date(this.now().getTime() + this.retentionMs);
|
|
278
|
+
const requestFingerprint = createPrivacyFingerprint(
|
|
279
|
+
this.options.fingerprintSecret,
|
|
280
|
+
"sms-send-request",
|
|
281
|
+
{
|
|
282
|
+
actionKey: command.actionKey,
|
|
283
|
+
recipient: command.recipient,
|
|
284
|
+
parameters: command.parameters,
|
|
285
|
+
actionDefinitionVersion: this.options.actionDefinitionVersion,
|
|
286
|
+
configurationId: configuration.id,
|
|
287
|
+
configurationVersion: configuration.version
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
const recipientFingerprint = createPrivacyFingerprint(
|
|
291
|
+
this.options.fingerprintSecret,
|
|
292
|
+
"sms-recipient",
|
|
293
|
+
command.recipient
|
|
294
|
+
);
|
|
295
|
+
const reservation = await this.options.operations.reserve({
|
|
296
|
+
scopeKey: this.scopeKey,
|
|
297
|
+
actionKey: command.actionKey,
|
|
298
|
+
actionDefinitionVersion: this.options.actionDefinitionVersion,
|
|
299
|
+
configurationVersion: configuration.version,
|
|
300
|
+
idempotencyKey,
|
|
301
|
+
requestFingerprint,
|
|
302
|
+
recipientFingerprint,
|
|
303
|
+
expiresAt
|
|
304
|
+
});
|
|
305
|
+
if (!reservation.created) return publicResult(reservation.operation);
|
|
306
|
+
await this.options.operations.beginAttempt({
|
|
307
|
+
operationId: reservation.operation.id,
|
|
308
|
+
providerConfigurationId: configuration.id,
|
|
309
|
+
adapterId: configuration.adapter.id,
|
|
310
|
+
expiresAt
|
|
311
|
+
});
|
|
312
|
+
let result;
|
|
313
|
+
try {
|
|
314
|
+
result = await configuration.adapter.sendTemplate(
|
|
315
|
+
configuration.credentials,
|
|
316
|
+
{
|
|
317
|
+
recipient: command.recipient,
|
|
318
|
+
template: configuration.template,
|
|
319
|
+
parameters: providerParameters,
|
|
320
|
+
...command.options.idempotencyKey === void 0 ? {} : { clientReferenceId: command.options.idempotencyKey }
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
...command.options.signal === void 0 ? {} : { signal: command.options.signal },
|
|
324
|
+
...command.options.correlationId === void 0 ? {} : { correlationId: command.options.correlationId }
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
} catch {
|
|
328
|
+
result = { status: "unknown", code: "unknown_outcome" };
|
|
329
|
+
}
|
|
330
|
+
return publicResult(
|
|
331
|
+
await this.options.operations.completeAttempt(
|
|
332
|
+
reservation.operation.id,
|
|
333
|
+
result
|
|
334
|
+
)
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
function descriptor(type, schema, metadata) {
|
|
339
|
+
return {
|
|
340
|
+
type,
|
|
341
|
+
schema,
|
|
342
|
+
metadata,
|
|
343
|
+
isOptional: false,
|
|
344
|
+
optional() {
|
|
345
|
+
return optionalDescriptor(type, schema.optional(), metadata);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function optionalDescriptor(type, schema, metadata) {
|
|
350
|
+
return {
|
|
351
|
+
type,
|
|
352
|
+
schema,
|
|
353
|
+
metadata,
|
|
354
|
+
isOptional: true,
|
|
355
|
+
optional() {
|
|
356
|
+
return optionalDescriptor(type, schema.optional(), metadata);
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function stringParameter(options) {
|
|
361
|
+
let schema = zod.z.string();
|
|
362
|
+
if (options.minLength !== void 0) schema = schema.min(options.minLength);
|
|
363
|
+
if (options.maxLength !== void 0) schema = schema.max(options.maxLength);
|
|
364
|
+
if (options.pattern !== void 0) schema = schema.regex(options.pattern);
|
|
365
|
+
return descriptor("string", schema, options);
|
|
366
|
+
}
|
|
367
|
+
function numberParameter(options) {
|
|
368
|
+
let schema = zod.z.number();
|
|
369
|
+
if (options.min !== void 0) schema = schema.min(options.min);
|
|
370
|
+
if (options.max !== void 0) schema = schema.max(options.max);
|
|
371
|
+
return descriptor("number", schema, options);
|
|
372
|
+
}
|
|
373
|
+
function integerParameter(options) {
|
|
374
|
+
let schema = zod.z.number().int();
|
|
375
|
+
if (options.min !== void 0) schema = schema.min(options.min);
|
|
376
|
+
if (options.max !== void 0) schema = schema.max(options.max);
|
|
377
|
+
return descriptor("integer", schema, options);
|
|
378
|
+
}
|
|
379
|
+
function booleanParameter(options) {
|
|
380
|
+
return descriptor("boolean", zod.z.boolean(), options);
|
|
381
|
+
}
|
|
382
|
+
function dateParameter(options) {
|
|
383
|
+
let schema = zod.z.date();
|
|
384
|
+
if (options.min !== void 0) schema = schema.min(options.min);
|
|
385
|
+
if (options.max !== void 0) schema = schema.max(options.max);
|
|
386
|
+
return descriptor("date", schema, options);
|
|
387
|
+
}
|
|
388
|
+
function enumParameter(values, options) {
|
|
389
|
+
return descriptor("enum", zod.z.enum(values), options);
|
|
390
|
+
}
|
|
391
|
+
var sms = {
|
|
392
|
+
string: stringParameter,
|
|
393
|
+
number: numberParameter,
|
|
394
|
+
integer: integerParameter,
|
|
395
|
+
boolean: booleanParameter,
|
|
396
|
+
date: dateParameter,
|
|
397
|
+
enum: enumParameter
|
|
398
|
+
};
|
|
399
|
+
var ALGORITHM = "aes-256-gcm";
|
|
400
|
+
var NONCE_LENGTH = 12;
|
|
401
|
+
var AUTH_TAG_LENGTH = 16;
|
|
402
|
+
var KEY_LENGTH = 32;
|
|
403
|
+
var CredentialEncryptionError = class extends Error {
|
|
404
|
+
code = "credential_encryption_failed";
|
|
405
|
+
constructor(message, options) {
|
|
406
|
+
super(message, options);
|
|
407
|
+
this.name = "CredentialEncryptionError";
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
function associatedData(context, keyId) {
|
|
411
|
+
return Buffer.from(
|
|
412
|
+
JSON.stringify({
|
|
413
|
+
type: "sms-provider-credentials",
|
|
414
|
+
configurationId: context.configurationId,
|
|
415
|
+
adapterId: context.adapterId,
|
|
416
|
+
scopeKey: context.scopeKey,
|
|
417
|
+
credentialRevision: context.credentialRevision,
|
|
418
|
+
schemaVersion: context.schemaVersion,
|
|
419
|
+
keyId
|
|
420
|
+
}),
|
|
421
|
+
"utf8"
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
function validKey(key, keyId) {
|
|
425
|
+
if (key === void 0) {
|
|
426
|
+
throw new CredentialEncryptionError(
|
|
427
|
+
`Encryption key "${keyId}" is unavailable.`
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
if (key.byteLength !== KEY_LENGTH) {
|
|
431
|
+
throw new CredentialEncryptionError(
|
|
432
|
+
`Encryption key "${keyId}" must be exactly 32 bytes.`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
return Buffer.from(key);
|
|
436
|
+
}
|
|
437
|
+
async function encryptCredentials(credentials, context, keyring) {
|
|
438
|
+
const keyId = keyring.activeKeyId;
|
|
439
|
+
const key = validKey(await keyring.getKey(keyId), keyId);
|
|
440
|
+
const nonce = crypto.randomBytes(NONCE_LENGTH);
|
|
441
|
+
try {
|
|
442
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, nonce, {
|
|
443
|
+
authTagLength: AUTH_TAG_LENGTH
|
|
444
|
+
});
|
|
445
|
+
cipher.setAAD(associatedData(context, keyId));
|
|
446
|
+
const plaintext = Buffer.from(JSON.stringify(credentials), "utf8");
|
|
447
|
+
const ciphertext = Buffer.concat([
|
|
448
|
+
cipher.update(plaintext),
|
|
449
|
+
cipher.final()
|
|
450
|
+
]);
|
|
451
|
+
return {
|
|
452
|
+
algorithm: ALGORITHM,
|
|
453
|
+
keyId,
|
|
454
|
+
configurationId: context.configurationId,
|
|
455
|
+
adapterId: context.adapterId,
|
|
456
|
+
scopeKey: context.scopeKey,
|
|
457
|
+
credentialRevision: context.credentialRevision,
|
|
458
|
+
nonce: nonce.toString("base64"),
|
|
459
|
+
ciphertext: ciphertext.toString("base64"),
|
|
460
|
+
authTag: cipher.getAuthTag().toString("base64"),
|
|
461
|
+
schemaVersion: context.schemaVersion
|
|
462
|
+
};
|
|
463
|
+
} catch (cause) {
|
|
464
|
+
throw new CredentialEncryptionError(
|
|
465
|
+
"Could not encrypt provider credentials.",
|
|
466
|
+
{
|
|
467
|
+
cause
|
|
468
|
+
}
|
|
469
|
+
);
|
|
470
|
+
} finally {
|
|
471
|
+
key.fill(0);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
async function decryptCredentials(envelope, context, keyring) {
|
|
475
|
+
if (envelope.algorithm !== ALGORITHM) {
|
|
476
|
+
throw new CredentialEncryptionError(
|
|
477
|
+
"Unsupported credential encryption algorithm."
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
if (envelope.schemaVersion !== context.schemaVersion) {
|
|
481
|
+
throw new CredentialEncryptionError("Credential schema version mismatch.");
|
|
482
|
+
}
|
|
483
|
+
if (envelope.configurationId !== context.configurationId || envelope.adapterId !== context.adapterId || envelope.scopeKey !== context.scopeKey || envelope.credentialRevision !== context.credentialRevision) {
|
|
484
|
+
throw new CredentialEncryptionError(
|
|
485
|
+
"Credential encryption context mismatch."
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
const nonce = Buffer.from(envelope.nonce, "base64");
|
|
489
|
+
const authTag = Buffer.from(envelope.authTag, "base64");
|
|
490
|
+
if (nonce.byteLength !== NONCE_LENGTH || authTag.byteLength !== AUTH_TAG_LENGTH) {
|
|
491
|
+
throw new CredentialEncryptionError(
|
|
492
|
+
"Credential envelope has invalid nonce or tag length."
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
const key = validKey(await keyring.getKey(envelope.keyId), envelope.keyId);
|
|
496
|
+
try {
|
|
497
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, nonce, {
|
|
498
|
+
authTagLength: AUTH_TAG_LENGTH
|
|
499
|
+
});
|
|
500
|
+
decipher.setAAD(associatedData(context, envelope.keyId));
|
|
501
|
+
decipher.setAuthTag(authTag);
|
|
502
|
+
const plaintext = Buffer.concat([
|
|
503
|
+
decipher.update(Buffer.from(envelope.ciphertext, "base64")),
|
|
504
|
+
decipher.final()
|
|
505
|
+
]);
|
|
506
|
+
return JSON.parse(plaintext.toString("utf8"));
|
|
507
|
+
} catch (cause) {
|
|
508
|
+
throw new CredentialEncryptionError(
|
|
509
|
+
"Provider credentials could not be authenticated or decrypted.",
|
|
510
|
+
{ cause }
|
|
511
|
+
);
|
|
512
|
+
} finally {
|
|
513
|
+
key.fill(0);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function createStaticKeyring(options) {
|
|
517
|
+
if (!(options.activeKeyId in options.keys)) {
|
|
518
|
+
throw new CredentialEncryptionError(
|
|
519
|
+
`Active encryption key "${options.activeKeyId}" is unavailable.`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
const keys = new Map(
|
|
523
|
+
Object.entries(options.keys).map(([id, key]) => [id, validKey(key, id)])
|
|
524
|
+
);
|
|
525
|
+
return {
|
|
526
|
+
activeKeyId: options.activeKeyId,
|
|
527
|
+
getKey(keyId) {
|
|
528
|
+
const key = keys.get(keyId);
|
|
529
|
+
return key === void 0 ? void 0 : Buffer.from(key);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function createEnvironmentKeyring(options) {
|
|
534
|
+
const encoding = options.encoding ?? "base64";
|
|
535
|
+
return createStaticKeyring({
|
|
536
|
+
activeKeyId: options.activeKeyId,
|
|
537
|
+
keys: Object.fromEntries(
|
|
538
|
+
Object.entries(options.keys).map(([keyId, value]) => [
|
|
539
|
+
keyId,
|
|
540
|
+
Buffer.from(value, encoding)
|
|
541
|
+
])
|
|
542
|
+
)
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
exports.CredentialEncryptionError = CredentialEncryptionError;
|
|
547
|
+
exports.InvalidIranianMobileNumberError = InvalidIranianMobileNumberError;
|
|
548
|
+
exports.InvalidParameterBindingError = InvalidParameterBindingError;
|
|
549
|
+
exports.SmsOrchestrator = SmsOrchestrator;
|
|
550
|
+
exports.applyParameterBindings = applyParameterBindings;
|
|
551
|
+
exports.createActionPayloadSchema = createActionPayloadSchema;
|
|
552
|
+
exports.createEnvironmentKeyring = createEnvironmentKeyring;
|
|
553
|
+
exports.createPrivacyFingerprint = createPrivacyFingerprint;
|
|
554
|
+
exports.createSmsManager = createSmsManager;
|
|
555
|
+
exports.createStaticKeyring = createStaticKeyring;
|
|
556
|
+
exports.decryptCredentials = decryptCredentials;
|
|
557
|
+
exports.defineSmsActions = defineSmsActions;
|
|
558
|
+
exports.encryptCredentials = encryptCredentials;
|
|
559
|
+
exports.fingerprintsEqual = fingerprintsEqual;
|
|
560
|
+
exports.normalizeIranianMobile = normalizeIranianMobile;
|
|
561
|
+
exports.serializeSmsActions = serializeSmsActions;
|
|
562
|
+
exports.sms = sms;
|
|
563
|
+
exports.toIranianLocalMobile = toIranianLocalMobile;
|
|
564
|
+
//# sourceMappingURL=index.cjs.map
|
|
565
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/actions.ts","../src/core/bindings.ts","../src/core/phone.ts","../src/core/manager.ts","../src/security/fingerprint.ts","../src/core/orchestrator.ts","../src/core/parameters.ts","../src/security/encryption.ts"],"names":["z","createHmac","timingSafeEqual","randomUUID","randomBytes","createCipheriv","createDecipheriv"],"mappings":"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","file":"index.cjs","sourcesContent":["import { z } from \"zod\";\n\nimport type { ParameterDescriptor } from \"./parameters.js\";\n\nexport type SmsParameterMap = Readonly<Record<string, ParameterDescriptor>>;\n\nexport interface SmsActionDefinition<\n TParams extends SmsParameterMap = SmsParameterMap,\n> {\n readonly title: string;\n readonly description?: string;\n readonly params: TParams;\n}\n\nexport type SmsActionMap = Readonly<Record<string, SmsActionDefinition>>;\n\nconst RESERVED_PARAMETER_KEYS = new Set([\n \"recipient\",\n \"idempotencyKey\",\n \"signal\",\n \"correlationId\",\n \"metadata\",\n]);\n\nexport function defineSmsActions<const TActions extends SmsActionMap>(\n actions: TActions,\n): TActions {\n for (const [actionKey, action] of Object.entries(actions)) {\n if (!/^[a-z][a-z0-9_]*$/.test(actionKey)) {\n throw new TypeError(\n `Invalid SMS action key \"${actionKey}\". Use lower snake_case.`,\n );\n }\n\n for (const parameterKey of Object.keys(action.params)) {\n if (RESERVED_PARAMETER_KEYS.has(parameterKey)) {\n throw new TypeError(\n `SMS action \"${actionKey}\" uses reserved parameter \"${parameterKey}\".`,\n );\n }\n }\n }\n\n return actions;\n}\n\ntype RequiredParameterKeys<TParams extends SmsParameterMap> = {\n [K in keyof TParams]-?: TParams[K][\"isOptional\"] extends true ? never : K;\n}[keyof TParams];\n\ntype OptionalParameterKeys<TParams extends SmsParameterMap> = {\n [K in keyof TParams]-?: TParams[K][\"isOptional\"] extends true ? K : never;\n}[keyof TParams];\n\ntype InferDescriptor<TDescriptor extends ParameterDescriptor> = z.output<\n TDescriptor[\"schema\"]\n>;\n\nexport type InferActionPayload<TAction extends SmsActionDefinition> = {\n recipient: string;\n} & {\n [K in RequiredParameterKeys<TAction[\"params\"]>]: InferDescriptor<\n TAction[\"params\"][K]\n >;\n} & {\n [K in OptionalParameterKeys<TAction[\"params\"]>]?: InferDescriptor<\n TAction[\"params\"][K]\n > extends infer TValue\n ? Exclude<TValue, undefined>\n : never;\n};\n\nexport function createActionPayloadSchema(\n action: SmsActionDefinition,\n): z.ZodObject<z.ZodRawShape> {\n const shape: Record<string, z.ZodType> = {\n recipient: z.string().min(1),\n };\n\n for (const [key, parameter] of Object.entries(action.params)) {\n shape[key] = parameter.schema;\n }\n\n return z.object(shape).strict();\n}\n\nexport interface SerializableActionMetadata {\n readonly key: string;\n readonly title: string;\n readonly description?: string;\n readonly parameters: readonly {\n readonly key: string;\n readonly title: string;\n readonly description?: string;\n readonly type: ParameterDescriptor[\"type\"];\n readonly required: boolean;\n }[];\n}\n\nexport function serializeSmsActions(\n actions: SmsActionMap,\n): readonly SerializableActionMetadata[] {\n return Object.entries(actions).map(([key, action]) => ({\n key,\n title: action.title,\n ...(action.description === undefined\n ? {}\n : { description: action.description }),\n parameters: Object.entries(action.params).map(\n ([parameterKey, parameter]) => ({\n key: parameterKey,\n title: parameter.metadata.title,\n ...(parameter.metadata.description === undefined\n ? {}\n : { description: parameter.metadata.description }),\n type: parameter.type,\n required: !parameter.isOptional,\n }),\n ),\n }));\n}\n","export type ParameterBinding =\n | {\n readonly kind: \"named\";\n readonly providerParameter: string;\n readonly sourceParameter: string;\n }\n | {\n readonly kind: \"ordered\";\n readonly position: number;\n readonly sourceParameter: string;\n }\n | {\n readonly kind: \"fixed-slot\";\n readonly slot: string;\n readonly sourceParameter: string;\n };\n\nexport class InvalidParameterBindingError extends TypeError {\n readonly code = \"template_binding_invalid\";\n\n constructor(message: string) {\n super(message);\n this.name = \"InvalidParameterBindingError\";\n }\n}\n\nfunction assertSafeKey(value: string, label: string): void {\n if (\n value.length === 0 ||\n value === \"__proto__\" ||\n value === \"prototype\" ||\n value === \"constructor\"\n ) {\n throw new InvalidParameterBindingError(`Unsafe or empty ${label}.`);\n }\n}\n\nexport function applyParameterBindings(\n parameters: Readonly<Record<string, unknown>>,\n bindings: readonly ParameterBinding[],\n): Readonly<Record<string, unknown>> | readonly unknown[] {\n if (bindings.length === 0) {\n throw new InvalidParameterBindingError(\n \"At least one parameter binding is required.\",\n );\n }\n\n const kind = bindings[0]?.kind;\n if (bindings.some((binding) => binding.kind !== kind)) {\n throw new InvalidParameterBindingError(\n \"Parameter binding kinds cannot be mixed.\",\n );\n }\n\n const sources = new Set<string>();\n const destinations = new Set<string | number>();\n for (const binding of bindings) {\n assertSafeKey(binding.sourceParameter, \"source parameter\");\n if (sources.has(binding.sourceParameter)) {\n throw new InvalidParameterBindingError(\n `Source parameter \"${binding.sourceParameter}\" is mapped more than once.`,\n );\n }\n sources.add(binding.sourceParameter);\n\n const destination =\n binding.kind === \"named\"\n ? binding.providerParameter\n : binding.kind === \"fixed-slot\"\n ? binding.slot\n : binding.position;\n if (typeof destination === \"string\")\n assertSafeKey(destination, \"destination\");\n if (destinations.has(destination)) {\n throw new InvalidParameterBindingError(\n `Provider destination \"${String(destination)}\" is mapped more than once.`,\n );\n }\n destinations.add(destination);\n }\n\n if (Object.keys(parameters).some((parameter) => !sources.has(parameter))) {\n throw new InvalidParameterBindingError(\n \"Every supplied action parameter must be mapped exactly once.\",\n );\n }\n\n if (kind === \"ordered\") {\n const ordered = bindings\n .map((binding) => {\n if (binding.kind !== \"ordered\")\n throw new InvalidParameterBindingError(\"Invalid binding.\");\n if (!Number.isSafeInteger(binding.position) || binding.position < 1) {\n throw new InvalidParameterBindingError(\n \"Ordered positions must be positive integers.\",\n );\n }\n return binding;\n })\n .sort((left, right) => left.position - right.position);\n if (ordered.some((binding, index) => binding.position !== index + 1)) {\n throw new InvalidParameterBindingError(\n \"Ordered positions must be contiguous from one.\",\n );\n }\n const values = ordered.map(\n (binding) => parameters[binding.sourceParameter],\n );\n while (values.at(-1) === undefined) values.pop();\n if (values.some((value) => value === undefined)) {\n throw new InvalidParameterBindingError(\n \"An omitted ordered parameter cannot precede a supplied parameter.\",\n );\n }\n return values;\n }\n\n const result: Record<string, unknown> = Object.create(null) as Record<\n string,\n unknown\n >;\n for (const binding of bindings) {\n if (binding.kind === \"ordered\") {\n throw new InvalidParameterBindingError(\"Invalid ordered binding state.\");\n }\n const destination =\n binding.kind === \"named\" ? binding.providerParameter : binding.slot;\n if (binding.sourceParameter in parameters) {\n result[destination] = parameters[binding.sourceParameter];\n }\n }\n return result;\n}\n","const IRAN_MOBILE_LOCAL = /^09\\d{9}$/;\n\nexport class InvalidIranianMobileNumberError extends TypeError {\n readonly code = \"invalid_recipient\";\n\n constructor() {\n super(\"Recipient must be a valid Iranian mobile number.\");\n this.name = \"InvalidIranianMobileNumberError\";\n }\n}\n\nexport function normalizeIranianMobile(input: string): `+98${string}` {\n const normalized = input\n .trim()\n .replace(/^0098/, \"0\")\n .replace(/^\\+98/, \"0\")\n .replace(/^98/, \"0\");\n\n if (!IRAN_MOBILE_LOCAL.test(normalized)) {\n throw new InvalidIranianMobileNumberError();\n }\n\n return `+98${normalized.slice(1)}`;\n}\n\nexport function toIranianLocalMobile(input: string): `09${string}` {\n const canonical = normalizeIranianMobile(input);\n return `0${canonical.slice(3)}` as `09${string}`;\n}\n","import {\n createActionPayloadSchema,\n type InferActionPayload,\n type SmsActionMap,\n} from \"./actions.js\";\nimport { normalizeIranianMobile } from \"./phone.js\";\n\nexport interface SmsSendOptions {\n readonly idempotencyKey?: string;\n readonly signal?: AbortSignal;\n readonly correlationId?: string;\n readonly metadata?: Readonly<\n Record<string, string | number | boolean | null>\n >;\n}\n\nexport interface SmsSendResult {\n readonly operationId: string;\n readonly status: \"pending\" | \"submitted\" | \"delivered\" | \"failed\" | \"unknown\";\n readonly providerMessageId?: string;\n}\n\nexport interface SmsSendCommand {\n readonly actionKey: string;\n readonly recipient: `+98${string}`;\n readonly parameters: Readonly<Record<string, unknown>>;\n readonly options: SmsSendOptions;\n}\n\nexport interface SmsSendExecutor {\n execute(command: SmsSendCommand): Promise<SmsSendResult>;\n}\n\nexport interface SmsManager<TActions extends SmsActionMap> {\n send<TKey extends keyof TActions & string>(\n action: TKey,\n payload: InferActionPayload<TActions[TKey]>,\n options?: SmsSendOptions,\n ): Promise<SmsSendResult>;\n}\n\nexport function createSmsManager<TActions extends SmsActionMap>(options: {\n readonly actions: TActions;\n readonly executor: SmsSendExecutor;\n}): SmsManager<TActions> {\n return {\n async send(actionKey, payload, sendOptions = {}) {\n const action = options.actions[actionKey];\n if (action === undefined) {\n throw new TypeError(`Unknown SMS action \"${actionKey}\".`);\n }\n const validated = createActionPayloadSchema(action).parse(payload);\n const { recipient, ...parameters } = validated;\n\n return options.executor.execute({\n actionKey,\n recipient: normalizeIranianMobile(recipient as string),\n parameters,\n options: sendOptions,\n });\n },\n };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\ntype CanonicalValue =\n | null\n | boolean\n | number\n | string\n | readonly CanonicalValue[]\n | { readonly [key: string]: CanonicalValue };\n\nfunction canonicalize(value: unknown): CanonicalValue {\n if (\n value === null ||\n typeof value === \"string\" ||\n typeof value === \"boolean\"\n ) {\n return value;\n }\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) {\n throw new TypeError(\"Cannot fingerprint a non-finite number.\");\n }\n return value;\n }\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) {\n throw new TypeError(\"Cannot fingerprint an invalid date.\");\n }\n return { $date: value.toISOString() };\n }\n if (Array.isArray(value)) return value.map(canonicalize);\n if (typeof value === \"object\") {\n const prototype = Object.getPrototypeOf(value) as object | null;\n if (prototype !== Object.prototype && prototype !== null) {\n throw new TypeError(\"Only plain objects can be fingerprinted.\");\n }\n const result: Record<string, CanonicalValue> = {};\n for (const key of Object.keys(value).sort()) {\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\") {\n throw new TypeError(\"Unsafe object key cannot be fingerprinted.\");\n }\n const item = (value as Record<string, unknown>)[key];\n if (item === undefined) {\n throw new TypeError(\"Undefined values cannot be fingerprinted.\");\n }\n result[key] = canonicalize(item);\n }\n return result;\n }\n throw new TypeError(`Unsupported fingerprint value: ${typeof value}.`);\n}\n\nexport function createPrivacyFingerprint(\n secret: Uint8Array,\n namespace: string,\n value: unknown,\n): string {\n if (secret.byteLength < 32) {\n throw new TypeError(\"Fingerprint secret must contain at least 32 bytes.\");\n }\n const canonical = JSON.stringify(canonicalize(value));\n return createHmac(\"sha256\", secret)\n .update(namespace)\n .update(\"\\0\")\n .update(canonical)\n .digest(\"base64url\");\n}\n\nexport function fingerprintsEqual(left: string, right: string): boolean {\n const leftBuffer = Buffer.from(left);\n const rightBuffer = Buffer.from(right);\n return (\n leftBuffer.byteLength === rightBuffer.byteLength &&\n timingSafeEqual(leftBuffer, rightBuffer)\n );\n}\n","import { randomUUID } from \"node:crypto\";\n\nimport { applyParameterBindings, type ParameterBinding } from \"./bindings.js\";\nimport type {\n SmsSendCommand,\n SmsSendExecutor,\n SmsSendResult,\n} from \"./manager.js\";\nimport type {\n ProviderSubmissionResult,\n SmsProviderAdapter,\n} from \"./provider.js\";\nimport { createPrivacyFingerprint } from \"../security/fingerprint.js\";\n\nexport interface ResolvedSendConfiguration {\n readonly id: string;\n readonly version: number;\n readonly adapter: SmsProviderAdapter<Record<string, unknown>>;\n readonly credentials: Record<string, unknown>;\n readonly template: unknown;\n readonly bindings: readonly ParameterBinding[];\n}\n\nexport interface SendConfigurationResolver {\n resolve(\n scopeKey: string,\n actionKey: string,\n ): Promise<ResolvedSendConfiguration>;\n}\n\nexport interface OperationSnapshot {\n readonly id: string;\n readonly state:\n | \"reserved\"\n | \"processing\"\n | \"submitted\"\n | \"delivered\"\n | \"failed\"\n | \"unknown\";\n readonly providerMessageId?: string;\n}\n\nexport interface SendOperationStore {\n reserve(input: {\n readonly scopeKey: string;\n readonly actionKey: string;\n readonly actionDefinitionVersion: string;\n readonly configurationVersion: number;\n readonly idempotencyKey: string;\n readonly requestFingerprint: string;\n readonly recipientFingerprint: string;\n readonly expiresAt: Date;\n }): Promise<{\n readonly created: boolean;\n readonly operation: OperationSnapshot;\n }>;\n\n beginAttempt(input: {\n readonly operationId: string;\n readonly providerConfigurationId: string;\n readonly adapterId: string;\n readonly expiresAt: Date;\n }): Promise<void>;\n\n completeAttempt(\n operationId: string,\n result: ProviderSubmissionResult,\n ): Promise<OperationSnapshot>;\n}\n\nfunction publicResult(operation: OperationSnapshot): SmsSendResult {\n const status =\n operation.state === \"reserved\" || operation.state === \"processing\"\n ? \"pending\"\n : operation.state;\n return {\n operationId: operation.id,\n status,\n ...(operation.providerMessageId === undefined\n ? {}\n : { providerMessageId: operation.providerMessageId }),\n };\n}\n\nexport interface SmsOrchestratorOptions {\n readonly scopeKey?: string;\n readonly actionDefinitionVersion: string;\n readonly fingerprintSecret: Uint8Array;\n readonly operationRetentionMs?: number;\n readonly configurations: SendConfigurationResolver;\n readonly operations: SendOperationStore;\n readonly now?: () => Date;\n}\n\nexport class SmsOrchestrator implements SmsSendExecutor {\n private readonly scopeKey: string;\n private readonly retentionMs: number;\n private readonly now: () => Date;\n\n constructor(private readonly options: SmsOrchestratorOptions) {\n this.scopeKey = options.scopeKey ?? \"global\";\n this.retentionMs =\n options.operationRetentionMs ?? 30 * 24 * 60 * 60 * 1_000;\n this.now = options.now ?? (() => new Date());\n if (this.retentionMs <= 0 || !Number.isSafeInteger(this.retentionMs)) {\n throw new TypeError(\n \"Operation retention must be a positive integer of milliseconds.\",\n );\n }\n }\n\n async execute(command: SmsSendCommand): Promise<SmsSendResult> {\n if (\n command.options.idempotencyKey !== undefined &&\n (command.options.idempotencyKey.length < 1 ||\n command.options.idempotencyKey.length > 200)\n ) {\n throw new TypeError(\n \"Idempotency key must contain between 1 and 200 characters.\",\n );\n }\n const configuration = await this.options.configurations.resolve(\n this.scopeKey,\n command.actionKey,\n );\n const providerParameters = applyParameterBindings(\n command.parameters,\n configuration.bindings,\n );\n const idempotencyKey = command.options.idempotencyKey ?? randomUUID();\n const expiresAt = new Date(this.now().getTime() + this.retentionMs);\n const requestFingerprint = createPrivacyFingerprint(\n this.options.fingerprintSecret,\n \"sms-send-request\",\n {\n actionKey: command.actionKey,\n recipient: command.recipient,\n parameters: command.parameters,\n actionDefinitionVersion: this.options.actionDefinitionVersion,\n configurationId: configuration.id,\n configurationVersion: configuration.version,\n },\n );\n const recipientFingerprint = createPrivacyFingerprint(\n this.options.fingerprintSecret,\n \"sms-recipient\",\n command.recipient,\n );\n\n const reservation = await this.options.operations.reserve({\n scopeKey: this.scopeKey,\n actionKey: command.actionKey,\n actionDefinitionVersion: this.options.actionDefinitionVersion,\n configurationVersion: configuration.version,\n idempotencyKey,\n requestFingerprint,\n recipientFingerprint,\n expiresAt,\n });\n if (!reservation.created) return publicResult(reservation.operation);\n\n await this.options.operations.beginAttempt({\n operationId: reservation.operation.id,\n providerConfigurationId: configuration.id,\n adapterId: configuration.adapter.id,\n expiresAt,\n });\n\n let result: ProviderSubmissionResult;\n try {\n result = await configuration.adapter.sendTemplate(\n configuration.credentials,\n {\n recipient: command.recipient,\n template: configuration.template,\n parameters: providerParameters,\n ...(command.options.idempotencyKey === undefined\n ? {}\n : { clientReferenceId: command.options.idempotencyKey }),\n },\n {\n ...(command.options.signal === undefined\n ? {}\n : { signal: command.options.signal }),\n ...(command.options.correlationId === undefined\n ? {}\n : { correlationId: command.options.correlationId }),\n },\n );\n } catch {\n result = { status: \"unknown\", code: \"unknown_outcome\" };\n }\n\n return publicResult(\n await this.options.operations.completeAttempt(\n reservation.operation.id,\n result,\n ),\n );\n }\n}\n","import { z } from \"zod\";\n\nexport type SmsParameterType =\n \"string\" | \"number\" | \"integer\" | \"boolean\" | \"date\" | \"enum\";\n\nexport interface ParameterMetadata {\n readonly title: string;\n readonly description?: string;\n}\n\nexport interface ParameterDescriptor<\n TSchema extends z.ZodType = z.ZodType,\n TOptional extends boolean = boolean,\n> {\n readonly type: SmsParameterType;\n readonly schema: TSchema;\n readonly metadata: ParameterMetadata;\n readonly isOptional: TOptional;\n optional(): ParameterDescriptor<z.ZodOptional<TSchema>, true>;\n}\n\nfunction descriptor<TSchema extends z.ZodType>(\n type: SmsParameterType,\n schema: TSchema,\n metadata: ParameterMetadata,\n): ParameterDescriptor<TSchema, false> {\n return {\n type,\n schema,\n metadata,\n isOptional: false,\n optional() {\n return optionalDescriptor(type, schema.optional(), metadata);\n },\n };\n}\n\nfunction optionalDescriptor<TSchema extends z.ZodType>(\n type: SmsParameterType,\n schema: TSchema,\n metadata: ParameterMetadata,\n): ParameterDescriptor<TSchema, true> {\n return {\n type,\n schema,\n metadata,\n isOptional: true,\n optional() {\n return optionalDescriptor(type, schema.optional(), metadata);\n },\n };\n}\n\nexport interface StringParameterOptions extends ParameterMetadata {\n readonly minLength?: number;\n readonly maxLength?: number;\n readonly pattern?: RegExp;\n}\n\nexport interface NumberParameterOptions extends ParameterMetadata {\n readonly min?: number;\n readonly max?: number;\n}\n\nexport interface DateParameterOptions extends ParameterMetadata {\n readonly min?: Date;\n readonly max?: Date;\n}\n\nfunction stringParameter(options: StringParameterOptions) {\n let schema = z.string();\n if (options.minLength !== undefined) schema = schema.min(options.minLength);\n if (options.maxLength !== undefined) schema = schema.max(options.maxLength);\n if (options.pattern !== undefined) schema = schema.regex(options.pattern);\n return descriptor(\"string\", schema, options);\n}\n\nfunction numberParameter(options: NumberParameterOptions) {\n let schema = z.number();\n if (options.min !== undefined) schema = schema.min(options.min);\n if (options.max !== undefined) schema = schema.max(options.max);\n return descriptor(\"number\", schema, options);\n}\n\nfunction integerParameter(options: NumberParameterOptions) {\n let schema = z.number().int();\n if (options.min !== undefined) schema = schema.min(options.min);\n if (options.max !== undefined) schema = schema.max(options.max);\n return descriptor(\"integer\", schema, options);\n}\n\nfunction booleanParameter(options: ParameterMetadata) {\n return descriptor(\"boolean\", z.boolean(), options);\n}\n\nfunction dateParameter(options: DateParameterOptions) {\n let schema = z.date();\n if (options.min !== undefined) schema = schema.min(options.min);\n if (options.max !== undefined) schema = schema.max(options.max);\n return descriptor(\"date\", schema, options);\n}\n\nfunction enumParameter<const TValues extends readonly [string, ...string[]]>(\n values: TValues,\n options: ParameterMetadata,\n) {\n return descriptor(\"enum\", z.enum(values), options);\n}\n\nexport const sms = {\n string: stringParameter,\n number: numberParameter,\n integer: integerParameter,\n boolean: booleanParameter,\n date: dateParameter,\n enum: enumParameter,\n} as const;\n","import { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\";\n\nconst ALGORITHM = \"aes-256-gcm\";\nconst NONCE_LENGTH = 12;\nconst AUTH_TAG_LENGTH = 16;\nconst KEY_LENGTH = 32;\n\nexport interface EncryptionKeyring {\n readonly activeKeyId: string;\n getKey(\n keyId: string,\n ): Promise<Uint8Array | undefined> | Uint8Array | undefined;\n}\n\nexport interface CredentialEncryptionContext {\n readonly configurationId: string;\n readonly adapterId: string;\n readonly scopeKey: string;\n readonly credentialRevision: number;\n readonly schemaVersion: number;\n}\n\nexport interface EncryptedCredentialEnvelope {\n readonly algorithm: string;\n readonly keyId: string;\n readonly configurationId: string;\n readonly adapterId: string;\n readonly scopeKey: string;\n readonly credentialRevision: number;\n readonly nonce: string;\n readonly ciphertext: string;\n readonly authTag: string;\n readonly schemaVersion: number;\n}\n\nexport class CredentialEncryptionError extends Error {\n readonly code = \"credential_encryption_failed\";\n\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"CredentialEncryptionError\";\n }\n}\n\nfunction associatedData(\n context: CredentialEncryptionContext,\n keyId: string,\n): Buffer {\n return Buffer.from(\n JSON.stringify({\n type: \"sms-provider-credentials\",\n configurationId: context.configurationId,\n adapterId: context.adapterId,\n scopeKey: context.scopeKey,\n credentialRevision: context.credentialRevision,\n schemaVersion: context.schemaVersion,\n keyId,\n }),\n \"utf8\",\n );\n}\n\nfunction validKey(key: Uint8Array | undefined, keyId: string): Buffer {\n if (key === undefined) {\n throw new CredentialEncryptionError(\n `Encryption key \"${keyId}\" is unavailable.`,\n );\n }\n if (key.byteLength !== KEY_LENGTH) {\n throw new CredentialEncryptionError(\n `Encryption key \"${keyId}\" must be exactly 32 bytes.`,\n );\n }\n return Buffer.from(key);\n}\n\nexport async function encryptCredentials(\n credentials: Readonly<Record<string, unknown>>,\n context: CredentialEncryptionContext,\n keyring: EncryptionKeyring,\n): Promise<EncryptedCredentialEnvelope> {\n const keyId = keyring.activeKeyId;\n const key = validKey(await keyring.getKey(keyId), keyId);\n const nonce = randomBytes(NONCE_LENGTH);\n\n try {\n const cipher = createCipheriv(ALGORITHM, key, nonce, {\n authTagLength: AUTH_TAG_LENGTH,\n });\n cipher.setAAD(associatedData(context, keyId));\n const plaintext = Buffer.from(JSON.stringify(credentials), \"utf8\");\n const ciphertext = Buffer.concat([\n cipher.update(plaintext),\n cipher.final(),\n ]);\n\n return {\n algorithm: ALGORITHM,\n keyId,\n configurationId: context.configurationId,\n adapterId: context.adapterId,\n scopeKey: context.scopeKey,\n credentialRevision: context.credentialRevision,\n nonce: nonce.toString(\"base64\"),\n ciphertext: ciphertext.toString(\"base64\"),\n authTag: cipher.getAuthTag().toString(\"base64\"),\n schemaVersion: context.schemaVersion,\n };\n } catch (cause) {\n throw new CredentialEncryptionError(\n \"Could not encrypt provider credentials.\",\n {\n cause,\n },\n );\n } finally {\n key.fill(0);\n }\n}\n\nexport async function decryptCredentials<T extends Record<string, unknown>>(\n envelope: EncryptedCredentialEnvelope,\n context: CredentialEncryptionContext,\n keyring: EncryptionKeyring,\n): Promise<T> {\n if (envelope.algorithm !== ALGORITHM) {\n throw new CredentialEncryptionError(\n \"Unsupported credential encryption algorithm.\",\n );\n }\n if (envelope.schemaVersion !== context.schemaVersion) {\n throw new CredentialEncryptionError(\"Credential schema version mismatch.\");\n }\n if (\n envelope.configurationId !== context.configurationId ||\n envelope.adapterId !== context.adapterId ||\n envelope.scopeKey !== context.scopeKey ||\n envelope.credentialRevision !== context.credentialRevision\n ) {\n throw new CredentialEncryptionError(\n \"Credential encryption context mismatch.\",\n );\n }\n const nonce = Buffer.from(envelope.nonce, \"base64\");\n const authTag = Buffer.from(envelope.authTag, \"base64\");\n if (\n nonce.byteLength !== NONCE_LENGTH ||\n authTag.byteLength !== AUTH_TAG_LENGTH\n ) {\n throw new CredentialEncryptionError(\n \"Credential envelope has invalid nonce or tag length.\",\n );\n }\n\n const key = validKey(await keyring.getKey(envelope.keyId), envelope.keyId);\n\n try {\n const decipher = createDecipheriv(ALGORITHM, key, nonce, {\n authTagLength: AUTH_TAG_LENGTH,\n });\n decipher.setAAD(associatedData(context, envelope.keyId));\n decipher.setAuthTag(authTag);\n const plaintext = Buffer.concat([\n decipher.update(Buffer.from(envelope.ciphertext, \"base64\")),\n decipher.final(),\n ]);\n return JSON.parse(plaintext.toString(\"utf8\")) as T;\n } catch (cause) {\n throw new CredentialEncryptionError(\n \"Provider credentials could not be authenticated or decrypted.\",\n { cause },\n );\n } finally {\n key.fill(0);\n }\n}\n\nexport function createStaticKeyring(options: {\n readonly activeKeyId: string;\n readonly keys: Readonly<Record<string, Uint8Array>>;\n}): EncryptionKeyring {\n if (!(options.activeKeyId in options.keys)) {\n throw new CredentialEncryptionError(\n `Active encryption key \"${options.activeKeyId}\" is unavailable.`,\n );\n }\n const keys = new Map(\n Object.entries(options.keys).map(([id, key]) => [id, validKey(key, id)]),\n );\n\n return {\n activeKeyId: options.activeKeyId,\n getKey(keyId) {\n const key = keys.get(keyId);\n return key === undefined ? undefined : Buffer.from(key);\n },\n };\n}\n\nexport function createEnvironmentKeyring(options: {\n readonly activeKeyId: string;\n readonly keys: Readonly<Record<string, string>>;\n readonly encoding?: \"base64\" | \"hex\";\n}): EncryptionKeyring {\n const encoding = options.encoding ?? \"base64\";\n return createStaticKeyring({\n activeKeyId: options.activeKeyId,\n keys: Object.fromEntries(\n Object.entries(options.keys).map(([keyId, value]) => [\n keyId,\n Buffer.from(value, encoding),\n ]),\n ),\n });\n}\n"]}
|