@terreno/api 0.3.1 → 0.4.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/dist/api.js +9 -8
- package/dist/betterAuthSetup.js +1 -1
- package/dist/configuration.test.d.ts +1 -0
- package/dist/configuration.test.js +699 -0
- package/dist/configurationApp.d.ts +91 -0
- package/dist/configurationApp.js +407 -0
- package/dist/configurationPlugin.d.ts +102 -0
- package/dist/configurationPlugin.js +285 -0
- package/dist/configurationPlugin.test.d.ts +1 -0
- package/dist/configurationPlugin.test.js +509 -0
- package/dist/example.js +1 -1
- package/dist/expressServer.js +5 -1
- package/dist/githubAuth.js +2 -2
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/openApiCompat.d.ts +23 -0
- package/dist/openApiCompat.js +198 -0
- package/dist/scriptRunner.d.ts +52 -0
- package/dist/scriptRunner.js +231 -0
- package/dist/secretProviders.d.ts +47 -0
- package/dist/secretProviders.js +214 -0
- package/dist/terrenoApp.d.ts +25 -0
- package/dist/terrenoApp.js +49 -2
- package/dist/tests.d.ts +27 -9
- package/dist/tests.js +10 -1
- package/package.json +13 -13
- package/src/api.ts +9 -8
- package/src/betterAuthSetup.ts +2 -2
- package/src/configuration.test.ts +398 -0
- package/src/configurationApp.ts +359 -0
- package/src/configurationPlugin.test.ts +299 -0
- package/src/configurationPlugin.ts +288 -0
- package/src/example.ts +1 -1
- package/src/expressServer.ts +6 -1
- package/src/githubAuth.ts +4 -4
- package/src/index.ts +5 -0
- package/src/openApiCompat.ts +147 -0
- package/src/permissions.ts +1 -1
- package/src/scriptRunner.ts +219 -0
- package/src/secretProviders.ts +109 -0
- package/src/terrenoApp.ts +44 -2
- package/src/tests.ts +12 -1
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var __values = (this && this.__values) || function(o) {
|
|
39
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
40
|
+
if (m) return m.call(o);
|
|
41
|
+
if (o && typeof o.length === "number") return {
|
|
42
|
+
next: function () {
|
|
43
|
+
if (o && i >= o.length) o = void 0;
|
|
44
|
+
return { value: o && o[i++], done: !o };
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
48
|
+
};
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.configurationPlugin = void 0;
|
|
51
|
+
var errors_1 = require("./errors");
|
|
52
|
+
var logger_1 = require("./logger");
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Plugin
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
/**
|
|
57
|
+
* Mongoose schema plugin that adds singleton configuration behavior.
|
|
58
|
+
*
|
|
59
|
+
* Adds:
|
|
60
|
+
* - Pre-save hook enforcing exactly one document
|
|
61
|
+
* - `getConfig()` static: fetches or creates the singleton (full doc or keyed value)
|
|
62
|
+
* - `updateConfig(updates)` static: patches the singleton
|
|
63
|
+
* - `getSecretFields()` static: returns metadata for fields with `secret: true`
|
|
64
|
+
* - `resolveSecrets(provider?)` static: fetches secret values, using the plugin provider by default
|
|
65
|
+
*
|
|
66
|
+
* Mark fields as secrets using schema path options:
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const configSchema = new Schema({
|
|
69
|
+
* apiKey: {
|
|
70
|
+
* type: String,
|
|
71
|
+
* description: "Third-party API key",
|
|
72
|
+
* secret: true,
|
|
73
|
+
* secretName: "my-api-key",
|
|
74
|
+
* },
|
|
75
|
+
* });
|
|
76
|
+
* configSchema.plugin(configurationPlugin, {secretProvider: new EnvSecretProvider()});
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
var configurationPlugin = function (schema, options) {
|
|
80
|
+
var pluginOptions = options !== null && options !== void 0 ? options : {};
|
|
81
|
+
// Add a sentinel field with a unique index to enforce singleton at the DB level.
|
|
82
|
+
// All config documents get _singleton: "config", and the unique index prevents duplicates.
|
|
83
|
+
schema.add({
|
|
84
|
+
_singleton: { default: "config", immutable: true, select: false, type: String },
|
|
85
|
+
});
|
|
86
|
+
schema.index({ _singleton: 1 }, { unique: true });
|
|
87
|
+
// Enforce singleton: only one document allowed (application-level guard)
|
|
88
|
+
schema.pre("save", function () {
|
|
89
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
90
|
+
var existing;
|
|
91
|
+
return __generator(this, function (_a) {
|
|
92
|
+
switch (_a.label) {
|
|
93
|
+
case 0:
|
|
94
|
+
if (!this.isNew) return [3 /*break*/, 2];
|
|
95
|
+
return [4 /*yield*/, this.constructor.findOne({})];
|
|
96
|
+
case 1:
|
|
97
|
+
existing = _a.sent();
|
|
98
|
+
if (existing) {
|
|
99
|
+
throw new errors_1.APIError({
|
|
100
|
+
status: 409,
|
|
101
|
+
title: "Only one configuration document is allowed. Use updateConfig() instead.",
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
_a.label = 2;
|
|
105
|
+
case 2: return [2 /*return*/];
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
// Prevent hard deletion of the singleton (soft deletes via isDeletedPlugin still work)
|
|
111
|
+
var createHardDeleteError = function () {
|
|
112
|
+
return new errors_1.APIError({
|
|
113
|
+
status: 400,
|
|
114
|
+
title: "Cannot hard-delete the configuration document. Use updateConfig() or soft delete instead.",
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
schema.pre("deleteOne", { document: true, query: true }, function () {
|
|
118
|
+
throw createHardDeleteError();
|
|
119
|
+
});
|
|
120
|
+
schema.pre("deleteMany", function () {
|
|
121
|
+
throw createHardDeleteError();
|
|
122
|
+
});
|
|
123
|
+
schema.pre("findOneAndDelete", function () {
|
|
124
|
+
throw createHardDeleteError();
|
|
125
|
+
});
|
|
126
|
+
// Static: get the singleton configuration document or a value at a path (race-safe via upsert)
|
|
127
|
+
schema.statics.getConfig = function (key) {
|
|
128
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
129
|
+
var config, err_1, parts, value, parts_1, parts_1_1, part;
|
|
130
|
+
var e_1, _a;
|
|
131
|
+
return __generator(this, function (_b) {
|
|
132
|
+
switch (_b.label) {
|
|
133
|
+
case 0: return [4 /*yield*/, this.findOne({})];
|
|
134
|
+
case 1:
|
|
135
|
+
config = _b.sent();
|
|
136
|
+
if (!!config) return [3 /*break*/, 8];
|
|
137
|
+
_b.label = 2;
|
|
138
|
+
case 2:
|
|
139
|
+
_b.trys.push([2, 4, , 8]);
|
|
140
|
+
// Use `new` + `save` instead of `create({})` so Mongoose initializes
|
|
141
|
+
// nested subdocument defaults (create({}) skips them).
|
|
142
|
+
config = new this();
|
|
143
|
+
return [4 /*yield*/, config.save()];
|
|
144
|
+
case 3:
|
|
145
|
+
_b.sent();
|
|
146
|
+
return [3 /*break*/, 8];
|
|
147
|
+
case 4:
|
|
148
|
+
err_1 = _b.sent();
|
|
149
|
+
if (!((err_1 === null || err_1 === void 0 ? void 0 : err_1.status) === 409)) return [3 /*break*/, 6];
|
|
150
|
+
return [4 /*yield*/, this.findOne({})];
|
|
151
|
+
case 5:
|
|
152
|
+
config = _b.sent();
|
|
153
|
+
return [3 /*break*/, 7];
|
|
154
|
+
case 6: throw err_1;
|
|
155
|
+
case 7: return [3 /*break*/, 8];
|
|
156
|
+
case 8:
|
|
157
|
+
if (key === undefined) {
|
|
158
|
+
return [2 /*return*/, config];
|
|
159
|
+
}
|
|
160
|
+
parts = key.split(".");
|
|
161
|
+
value = config.toObject();
|
|
162
|
+
try {
|
|
163
|
+
for (parts_1 = __values(parts), parts_1_1 = parts_1.next(); !parts_1_1.done; parts_1_1 = parts_1.next()) {
|
|
164
|
+
part = parts_1_1.value;
|
|
165
|
+
if (value == null || typeof value !== "object") {
|
|
166
|
+
return [2 /*return*/, undefined];
|
|
167
|
+
}
|
|
168
|
+
value = value[part];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
172
|
+
finally {
|
|
173
|
+
try {
|
|
174
|
+
if (parts_1_1 && !parts_1_1.done && (_a = parts_1.return)) _a.call(parts_1);
|
|
175
|
+
}
|
|
176
|
+
finally { if (e_1) throw e_1.error; }
|
|
177
|
+
}
|
|
178
|
+
return [2 /*return*/, value];
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
// Static: update the singleton configuration document (race-safe)
|
|
184
|
+
schema.statics.updateConfig = function (updates) {
|
|
185
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
186
|
+
var config;
|
|
187
|
+
return __generator(this, function (_a) {
|
|
188
|
+
switch (_a.label) {
|
|
189
|
+
case 0: return [4 /*yield*/, this.getConfig()];
|
|
190
|
+
case 1:
|
|
191
|
+
config = _a.sent();
|
|
192
|
+
Object.assign(config, updates);
|
|
193
|
+
return [4 /*yield*/, config.save()];
|
|
194
|
+
case 2:
|
|
195
|
+
_a.sent();
|
|
196
|
+
return [2 /*return*/, config];
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
// Static: discover secret fields from schema options
|
|
202
|
+
schema.statics.getSecretFields = function () {
|
|
203
|
+
var secrets = [];
|
|
204
|
+
var discoverSecrets = function (s, prefix) {
|
|
205
|
+
s.eachPath(function (pathName, schemaType) {
|
|
206
|
+
var _a;
|
|
207
|
+
var opts = schemaType.options;
|
|
208
|
+
if ((opts === null || opts === void 0 ? void 0 : opts.secret) === true) {
|
|
209
|
+
secrets.push({
|
|
210
|
+
path: prefix ? "".concat(prefix, ".").concat(pathName) : pathName,
|
|
211
|
+
secretName: (_a = opts.secretName) !== null && _a !== void 0 ? _a : pathName,
|
|
212
|
+
secretProvider: opts.secretProvider,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
// Recurse into subschemas
|
|
216
|
+
if (schemaType.schema) {
|
|
217
|
+
discoverSecrets(schemaType.schema, prefix ? "".concat(prefix, ".").concat(pathName) : pathName);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
};
|
|
221
|
+
discoverSecrets(this.schema, "");
|
|
222
|
+
return secrets;
|
|
223
|
+
};
|
|
224
|
+
// Static: resolve secret values from a provider
|
|
225
|
+
schema.statics.resolveSecrets = function (provider) {
|
|
226
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
227
|
+
var resolvedProvider, secrets, resolved, results, failCount, results_1, results_1_1, result;
|
|
228
|
+
var e_2, _a;
|
|
229
|
+
var _this = this;
|
|
230
|
+
return __generator(this, function (_b) {
|
|
231
|
+
switch (_b.label) {
|
|
232
|
+
case 0:
|
|
233
|
+
resolvedProvider = provider !== null && provider !== void 0 ? provider : pluginOptions.secretProvider;
|
|
234
|
+
if (!resolvedProvider) {
|
|
235
|
+
logger_1.logger.warn("resolveSecrets called with no provider. Pass a SecretProvider to resolveSecrets() or configurationPlugin options.");
|
|
236
|
+
return [2 /*return*/, new Map()];
|
|
237
|
+
}
|
|
238
|
+
secrets = this.getSecretFields();
|
|
239
|
+
resolved = new Map();
|
|
240
|
+
return [4 /*yield*/, Promise.allSettled(secrets.map(function (meta) { return __awaiter(_this, void 0, void 0, function () {
|
|
241
|
+
var value;
|
|
242
|
+
return __generator(this, function (_a) {
|
|
243
|
+
switch (_a.label) {
|
|
244
|
+
case 0: return [4 /*yield*/, resolvedProvider.getSecret(meta.secretName)];
|
|
245
|
+
case 1:
|
|
246
|
+
value = _a.sent();
|
|
247
|
+
if (value !== null) {
|
|
248
|
+
resolved.set(meta.path, value);
|
|
249
|
+
}
|
|
250
|
+
return [2 /*return*/];
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}); }))];
|
|
254
|
+
case 1:
|
|
255
|
+
results = _b.sent();
|
|
256
|
+
failCount = 0;
|
|
257
|
+
try {
|
|
258
|
+
for (results_1 = __values(results), results_1_1 = results_1.next(); !results_1_1.done; results_1_1 = results_1.next()) {
|
|
259
|
+
result = results_1_1.value;
|
|
260
|
+
if (result.status === "rejected") {
|
|
261
|
+
failCount++;
|
|
262
|
+
logger_1.logger.error("Failed to resolve secret: ".concat(result.reason));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
267
|
+
finally {
|
|
268
|
+
try {
|
|
269
|
+
if (results_1_1 && !results_1_1.done && (_a = results_1.return)) _a.call(results_1);
|
|
270
|
+
}
|
|
271
|
+
finally { if (e_2) throw e_2.error; }
|
|
272
|
+
}
|
|
273
|
+
if (failCount > 0) {
|
|
274
|
+
logger_1.logger.warn("".concat(failCount, "/").concat(secrets.length, " secrets failed to resolve"));
|
|
275
|
+
}
|
|
276
|
+
else if (secrets.length > 0) {
|
|
277
|
+
logger_1.logger.info("Resolved ".concat(resolved.size, "/").concat(secrets.length, " secrets from ").concat(resolvedProvider.name));
|
|
278
|
+
}
|
|
279
|
+
return [2 /*return*/, resolved];
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
};
|
|
285
|
+
exports.configurationPlugin = configurationPlugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|