@terreno/api 0.19.0 → 0.20.1
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 +25 -0
- package/dist/auth.js +2 -2
- package/dist/configuration.test.js +289 -10
- package/dist/configurationApp.d.ts +72 -5
- package/dist/configurationApp.js +168 -48
- package/dist/configurationPlugin.d.ts +64 -7
- package/dist/configurationPlugin.js +161 -39
- package/dist/configurationPlugin.test.js +238 -1
- package/dist/secretProviders.d.ts +79 -2
- package/dist/secretProviders.js +178 -9
- package/dist/secretProviders.test.d.ts +1 -0
- package/dist/secretProviders.test.js +391 -0
- package/package.json +1 -1
- package/src/auth.ts +2 -2
- package/src/configuration.test.ts +171 -7
- package/src/configurationApp.ts +213 -30
- package/src/configurationPlugin.test.ts +174 -2
- package/src/configurationPlugin.ts +157 -28
- package/src/secretProviders.test.ts +186 -0
- package/src/secretProviders.ts +147 -5
package/dist/secretProviders.js
CHANGED
|
@@ -84,8 +84,20 @@ var __read = (this && this.__read) || function (o, n) {
|
|
|
84
84
|
}
|
|
85
85
|
return ar;
|
|
86
86
|
};
|
|
87
|
+
var __values = (this && this.__values) || function(o) {
|
|
88
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
89
|
+
if (m) return m.call(o);
|
|
90
|
+
if (o && typeof o.length === "number") return {
|
|
91
|
+
next: function () {
|
|
92
|
+
if (o && i >= o.length) o = void 0;
|
|
93
|
+
return { value: o && o[i++], done: !o };
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
97
|
+
};
|
|
87
98
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
88
|
-
exports.GcpSecretProvider = exports.EnvSecretProvider = void 0;
|
|
99
|
+
exports.CachingSecretProvider = exports.CompositeSecretProvider = exports.GcpSecretProvider = exports.EnvSecretProvider = void 0;
|
|
100
|
+
var luxon_1 = require("luxon");
|
|
89
101
|
var errors_1 = require("./errors");
|
|
90
102
|
var logger_1 = require("./logger");
|
|
91
103
|
/**
|
|
@@ -106,7 +118,11 @@ var EnvSecretProvider = /** @class */ (function () {
|
|
|
106
118
|
function EnvSecretProvider() {
|
|
107
119
|
this.name = "env";
|
|
108
120
|
}
|
|
109
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Resolve a secret from an environment variable. Environment variables have no
|
|
123
|
+
* versions, so the optional `version` parameter is ignored.
|
|
124
|
+
*/
|
|
125
|
+
EnvSecretProvider.prototype.getSecret = function (secretName, _version) {
|
|
110
126
|
return __awaiter(this, void 0, void 0, function () {
|
|
111
127
|
var envKey, value;
|
|
112
128
|
var _a;
|
|
@@ -180,30 +196,42 @@ var GcpSecretProvider = /** @class */ (function () {
|
|
|
180
196
|
});
|
|
181
197
|
});
|
|
182
198
|
};
|
|
183
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Resolve a secret from Google Cloud Secret Manager.
|
|
201
|
+
*
|
|
202
|
+
* @param secretName - A short secret id (e.g. "openai-api-key") or a full
|
|
203
|
+
* resource path (e.g. "projects/p/secrets/s" or
|
|
204
|
+
* "projects/p/secrets/s/versions/3").
|
|
205
|
+
* @param version - Optional version to resolve when `secretName` is a short id
|
|
206
|
+
* (e.g. "3"). Defaults to "latest". Ignored when `secretName` already
|
|
207
|
+
* contains an explicit `/versions/...` suffix.
|
|
208
|
+
*/
|
|
209
|
+
GcpSecretProvider.prototype.getSecret = function (secretName, version) {
|
|
184
210
|
return __awaiter(this, void 0, void 0, function () {
|
|
185
|
-
var client, resourceName, _a,
|
|
211
|
+
var client, resolvedVersion, resourceName, _a, version_1, payload, error_1;
|
|
186
212
|
var _b;
|
|
187
213
|
return __generator(this, function (_c) {
|
|
188
214
|
switch (_c.label) {
|
|
189
215
|
case 0: return [4 /*yield*/, this.getClient()];
|
|
190
216
|
case 1:
|
|
191
217
|
client = _c.sent();
|
|
218
|
+
resolvedVersion = version !== null && version !== void 0 ? version : "latest";
|
|
192
219
|
if (secretName.startsWith("projects/")) {
|
|
193
|
-
|
|
220
|
+
// Honor a full resource path. Only append a version when one is not present.
|
|
221
|
+
resourceName = secretName.includes("/versions/")
|
|
194
222
|
? secretName
|
|
195
|
-
: "".concat(secretName, "/versions/
|
|
223
|
+
: "".concat(secretName, "/versions/").concat(resolvedVersion);
|
|
196
224
|
}
|
|
197
225
|
else {
|
|
198
|
-
resourceName = "projects/".concat(this.projectId, "/secrets/").concat(secretName, "/versions/
|
|
226
|
+
resourceName = "projects/".concat(this.projectId, "/secrets/").concat(secretName, "/versions/").concat(resolvedVersion);
|
|
199
227
|
}
|
|
200
228
|
_c.label = 2;
|
|
201
229
|
case 2:
|
|
202
230
|
_c.trys.push([2, 4, , 5]);
|
|
203
231
|
return [4 /*yield*/, client.accessSecretVersion({ name: resourceName })];
|
|
204
232
|
case 3:
|
|
205
|
-
_a = __read.apply(void 0, [_c.sent(), 1]),
|
|
206
|
-
payload = (_b =
|
|
233
|
+
_a = __read.apply(void 0, [_c.sent(), 1]), version_1 = _a[0];
|
|
234
|
+
payload = (_b = version_1.payload) === null || _b === void 0 ? void 0 : _b.data;
|
|
207
235
|
if (!payload) {
|
|
208
236
|
logger_1.logger.warn("GcpSecretProvider: secret ".concat(secretName, " has no payload"));
|
|
209
237
|
return [2 /*return*/, null];
|
|
@@ -225,3 +253,144 @@ var GcpSecretProvider = /** @class */ (function () {
|
|
|
225
253
|
return GcpSecretProvider;
|
|
226
254
|
}());
|
|
227
255
|
exports.GcpSecretProvider = GcpSecretProvider;
|
|
256
|
+
/**
|
|
257
|
+
* Secret provider that delegates to an ordered list of providers, returning the
|
|
258
|
+
* first non-null result.
|
|
259
|
+
*
|
|
260
|
+
* A provider that throws is warn-logged (secret name only — never the value) and
|
|
261
|
+
* resolution falls through to the next provider. This makes it easy to compose a
|
|
262
|
+
* primary provider with a fallback, e.g. GCP with an environment-variable
|
|
263
|
+
* fallback:
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```typescript
|
|
267
|
+
* const provider = new CompositeSecretProvider([
|
|
268
|
+
* new GcpSecretProvider({projectId: "my-project"}),
|
|
269
|
+
* new EnvSecretProvider(),
|
|
270
|
+
* ]);
|
|
271
|
+
* const key = await provider.getSecret("openai-api-key");
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
var CompositeSecretProvider = /** @class */ (function () {
|
|
275
|
+
function CompositeSecretProvider(providers) {
|
|
276
|
+
if (!providers || providers.length === 0) {
|
|
277
|
+
throw new errors_1.APIError({
|
|
278
|
+
status: 500,
|
|
279
|
+
title: "CompositeSecretProvider requires at least one provider",
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
this.providers = providers;
|
|
283
|
+
this.name = "composite(".concat(providers.map(function (p) { return p.name; }).join(","), ")");
|
|
284
|
+
}
|
|
285
|
+
CompositeSecretProvider.prototype.getSecret = function (secretName, version) {
|
|
286
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
287
|
+
var _a, _b, provider, value, error_2, message, e_1_1;
|
|
288
|
+
var e_1, _c;
|
|
289
|
+
return __generator(this, function (_d) {
|
|
290
|
+
switch (_d.label) {
|
|
291
|
+
case 0:
|
|
292
|
+
_d.trys.push([0, 7, 8, 9]);
|
|
293
|
+
_a = __values(this.providers), _b = _a.next();
|
|
294
|
+
_d.label = 1;
|
|
295
|
+
case 1:
|
|
296
|
+
if (!!_b.done) return [3 /*break*/, 6];
|
|
297
|
+
provider = _b.value;
|
|
298
|
+
_d.label = 2;
|
|
299
|
+
case 2:
|
|
300
|
+
_d.trys.push([2, 4, , 5]);
|
|
301
|
+
return [4 /*yield*/, provider.getSecret(secretName, version)];
|
|
302
|
+
case 3:
|
|
303
|
+
value = _d.sent();
|
|
304
|
+
if (value !== null) {
|
|
305
|
+
return [2 /*return*/, value];
|
|
306
|
+
}
|
|
307
|
+
return [3 /*break*/, 5];
|
|
308
|
+
case 4:
|
|
309
|
+
error_2 = _d.sent();
|
|
310
|
+
message = error_2 instanceof Error ? error_2.message : String(error_2);
|
|
311
|
+
logger_1.logger.warn("CompositeSecretProvider: provider ".concat(provider.name, " failed for secret ").concat(secretName, ": ").concat(message));
|
|
312
|
+
return [3 /*break*/, 5];
|
|
313
|
+
case 5:
|
|
314
|
+
_b = _a.next();
|
|
315
|
+
return [3 /*break*/, 1];
|
|
316
|
+
case 6: return [3 /*break*/, 9];
|
|
317
|
+
case 7:
|
|
318
|
+
e_1_1 = _d.sent();
|
|
319
|
+
e_1 = { error: e_1_1 };
|
|
320
|
+
return [3 /*break*/, 9];
|
|
321
|
+
case 8:
|
|
322
|
+
try {
|
|
323
|
+
if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
|
|
324
|
+
}
|
|
325
|
+
finally { if (e_1) throw e_1.error; }
|
|
326
|
+
return [7 /*endfinally*/];
|
|
327
|
+
case 9: return [2 /*return*/, null];
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
return CompositeSecretProvider;
|
|
333
|
+
}());
|
|
334
|
+
exports.CompositeSecretProvider = CompositeSecretProvider;
|
|
335
|
+
/**
|
|
336
|
+
* Secret provider that wraps any provider with an in-memory TTL cache.
|
|
337
|
+
*
|
|
338
|
+
* Cache entries are keyed by `secretName@version` so that pinned versions are
|
|
339
|
+
* cached independently. `null` results (secret not found) are cached too, to
|
|
340
|
+
* avoid hammering the underlying provider for missing secrets. Secret values are
|
|
341
|
+
* never logged.
|
|
342
|
+
*
|
|
343
|
+
* Use `clear()` to drop the entire cache (e.g. on rotation) or `clearKey()` to
|
|
344
|
+
* invalidate a single secret.
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* ```typescript
|
|
348
|
+
* const provider = new CachingSecretProvider(
|
|
349
|
+
* new CompositeSecretProvider([gcp, env]),
|
|
350
|
+
* {ttlMs: 30_000}
|
|
351
|
+
* );
|
|
352
|
+
* ```
|
|
353
|
+
*/
|
|
354
|
+
var CachingSecretProvider = /** @class */ (function () {
|
|
355
|
+
function CachingSecretProvider(provider, options) {
|
|
356
|
+
var _a;
|
|
357
|
+
this.cache = new Map();
|
|
358
|
+
this.provider = provider;
|
|
359
|
+
this.ttlMs = (_a = options === null || options === void 0 ? void 0 : options.ttlMs) !== null && _a !== void 0 ? _a : 60000;
|
|
360
|
+
this.name = "caching(".concat(provider.name, ")");
|
|
361
|
+
}
|
|
362
|
+
CachingSecretProvider.prototype.cacheKey = function (secretName, version) {
|
|
363
|
+
return "".concat(secretName, "@").concat(version !== null && version !== void 0 ? version : "latest");
|
|
364
|
+
};
|
|
365
|
+
CachingSecretProvider.prototype.getSecret = function (secretName, version) {
|
|
366
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
367
|
+
var key, now, cached, value;
|
|
368
|
+
return __generator(this, function (_a) {
|
|
369
|
+
switch (_a.label) {
|
|
370
|
+
case 0:
|
|
371
|
+
key = this.cacheKey(secretName, version);
|
|
372
|
+
now = luxon_1.DateTime.now().toMillis();
|
|
373
|
+
cached = this.cache.get(key);
|
|
374
|
+
if (cached && cached.expiresAt > now) {
|
|
375
|
+
return [2 /*return*/, cached.value];
|
|
376
|
+
}
|
|
377
|
+
return [4 /*yield*/, this.provider.getSecret(secretName, version)];
|
|
378
|
+
case 1:
|
|
379
|
+
value = _a.sent();
|
|
380
|
+
this.cache.set(key, { expiresAt: now + this.ttlMs, value: value });
|
|
381
|
+
return [2 /*return*/, value];
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
/** Clears the entire cache. Useful on secret rotation and in tests. */
|
|
387
|
+
CachingSecretProvider.prototype.clear = function () {
|
|
388
|
+
this.cache.clear();
|
|
389
|
+
};
|
|
390
|
+
/** Invalidates a single cached secret by name (and optional version). */
|
|
391
|
+
CachingSecretProvider.prototype.clearKey = function (secretName, version) {
|
|
392
|
+
this.cache.delete(this.cacheKey(secretName, version));
|
|
393
|
+
};
|
|
394
|
+
return CachingSecretProvider;
|
|
395
|
+
}());
|
|
396
|
+
exports.CachingSecretProvider = CachingSecretProvider;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,391 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
var bun_test_1 = require("bun:test");
|
|
40
|
+
var secretProviders_1 = require("./secretProviders");
|
|
41
|
+
(0, bun_test_1.describe)("EnvSecretProvider", function () {
|
|
42
|
+
(0, bun_test_1.beforeEach)(function () {
|
|
43
|
+
delete process.env.MY_SECRET_KEY;
|
|
44
|
+
});
|
|
45
|
+
(0, bun_test_1.it)("resolves from a SCREAMING_SNAKE_CASE env var", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
46
|
+
var provider, _a;
|
|
47
|
+
return __generator(this, function (_b) {
|
|
48
|
+
switch (_b.label) {
|
|
49
|
+
case 0:
|
|
50
|
+
process.env.MY_SECRET_KEY = "from-env";
|
|
51
|
+
provider = new secretProviders_1.EnvSecretProvider();
|
|
52
|
+
_a = bun_test_1.expect;
|
|
53
|
+
return [4 /*yield*/, provider.getSecret("my-secret-key")];
|
|
54
|
+
case 1:
|
|
55
|
+
_a.apply(void 0, [_b.sent()]).toBe("from-env");
|
|
56
|
+
return [2 /*return*/];
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}); });
|
|
60
|
+
(0, bun_test_1.it)("returns null when the env var is missing", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
61
|
+
var provider, _a;
|
|
62
|
+
return __generator(this, function (_b) {
|
|
63
|
+
switch (_b.label) {
|
|
64
|
+
case 0:
|
|
65
|
+
provider = new secretProviders_1.EnvSecretProvider();
|
|
66
|
+
_a = bun_test_1.expect;
|
|
67
|
+
return [4 /*yield*/, provider.getSecret("my-secret-key")];
|
|
68
|
+
case 1:
|
|
69
|
+
_a.apply(void 0, [_b.sent()]).toBeNull();
|
|
70
|
+
return [2 /*return*/];
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}); });
|
|
74
|
+
(0, bun_test_1.it)("ignores the version parameter", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
75
|
+
var provider, _a;
|
|
76
|
+
return __generator(this, function (_b) {
|
|
77
|
+
switch (_b.label) {
|
|
78
|
+
case 0:
|
|
79
|
+
process.env.MY_SECRET_KEY = "value";
|
|
80
|
+
provider = new secretProviders_1.EnvSecretProvider();
|
|
81
|
+
_a = bun_test_1.expect;
|
|
82
|
+
return [4 /*yield*/, provider.getSecret("my-secret-key", "5")];
|
|
83
|
+
case 1:
|
|
84
|
+
_a.apply(void 0, [_b.sent()]).toBe("value");
|
|
85
|
+
return [2 /*return*/];
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}); });
|
|
89
|
+
});
|
|
90
|
+
(0, bun_test_1.describe)("CompositeSecretProvider", function () {
|
|
91
|
+
(0, bun_test_1.it)("throws when constructed with no providers", function () {
|
|
92
|
+
(0, bun_test_1.expect)(function () { return new secretProviders_1.CompositeSecretProvider([]); }).toThrow();
|
|
93
|
+
});
|
|
94
|
+
(0, bun_test_1.it)("returns the first non-null result", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
95
|
+
var a, b, c, provider, _a;
|
|
96
|
+
return __generator(this, function (_b) {
|
|
97
|
+
switch (_b.label) {
|
|
98
|
+
case 0:
|
|
99
|
+
a = { getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
100
|
+
return [2 /*return*/, null];
|
|
101
|
+
}); }); }, name: "a" };
|
|
102
|
+
b = { getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
103
|
+
return [2 /*return*/, "from-b"];
|
|
104
|
+
}); }); }, name: "b" };
|
|
105
|
+
c = { getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
106
|
+
return [2 /*return*/, "from-c"];
|
|
107
|
+
}); }); }, name: "c" };
|
|
108
|
+
provider = new secretProviders_1.CompositeSecretProvider([a, b, c]);
|
|
109
|
+
_a = bun_test_1.expect;
|
|
110
|
+
return [4 /*yield*/, provider.getSecret("x")];
|
|
111
|
+
case 1:
|
|
112
|
+
_a.apply(void 0, [_b.sent()]).toBe("from-b");
|
|
113
|
+
return [2 /*return*/];
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}); });
|
|
117
|
+
(0, bun_test_1.it)("falls through to the next provider when one throws", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
118
|
+
var failing, fallback, provider, _a;
|
|
119
|
+
return __generator(this, function (_b) {
|
|
120
|
+
switch (_b.label) {
|
|
121
|
+
case 0:
|
|
122
|
+
failing = {
|
|
123
|
+
getSecret: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
124
|
+
return __generator(this, function (_a) {
|
|
125
|
+
throw new Error("provider down");
|
|
126
|
+
});
|
|
127
|
+
}); },
|
|
128
|
+
name: "failing",
|
|
129
|
+
};
|
|
130
|
+
fallback = { getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
131
|
+
return [2 /*return*/, "from-fallback"];
|
|
132
|
+
}); }); }, name: "fallback" };
|
|
133
|
+
provider = new secretProviders_1.CompositeSecretProvider([failing, fallback]);
|
|
134
|
+
_a = bun_test_1.expect;
|
|
135
|
+
return [4 /*yield*/, provider.getSecret("x")];
|
|
136
|
+
case 1:
|
|
137
|
+
_a.apply(void 0, [_b.sent()]).toBe("from-fallback");
|
|
138
|
+
return [2 /*return*/];
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}); });
|
|
142
|
+
(0, bun_test_1.it)("returns null when every provider yields null", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
143
|
+
var a, b, provider, _a;
|
|
144
|
+
return __generator(this, function (_b) {
|
|
145
|
+
switch (_b.label) {
|
|
146
|
+
case 0:
|
|
147
|
+
a = { getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
148
|
+
return [2 /*return*/, null];
|
|
149
|
+
}); }); }, name: "a" };
|
|
150
|
+
b = { getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
151
|
+
return [2 /*return*/, null];
|
|
152
|
+
}); }); }, name: "b" };
|
|
153
|
+
provider = new secretProviders_1.CompositeSecretProvider([a, b]);
|
|
154
|
+
_a = bun_test_1.expect;
|
|
155
|
+
return [4 /*yield*/, provider.getSecret("x")];
|
|
156
|
+
case 1:
|
|
157
|
+
_a.apply(void 0, [_b.sent()]).toBeNull();
|
|
158
|
+
return [2 /*return*/];
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}); });
|
|
162
|
+
(0, bun_test_1.it)("forwards the version parameter to each provider", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
163
|
+
var seen, a, b, provider;
|
|
164
|
+
return __generator(this, function (_a) {
|
|
165
|
+
switch (_a.label) {
|
|
166
|
+
case 0:
|
|
167
|
+
seen = [];
|
|
168
|
+
a = {
|
|
169
|
+
getSecret: function (_name, version) { return __awaiter(void 0, void 0, void 0, function () {
|
|
170
|
+
return __generator(this, function (_a) {
|
|
171
|
+
seen.push(version);
|
|
172
|
+
return [2 /*return*/, null];
|
|
173
|
+
});
|
|
174
|
+
}); },
|
|
175
|
+
name: "a",
|
|
176
|
+
};
|
|
177
|
+
b = {
|
|
178
|
+
getSecret: function (_name, version) { return __awaiter(void 0, void 0, void 0, function () {
|
|
179
|
+
return __generator(this, function (_a) {
|
|
180
|
+
seen.push(version);
|
|
181
|
+
return [2 /*return*/, "value"];
|
|
182
|
+
});
|
|
183
|
+
}); },
|
|
184
|
+
name: "b",
|
|
185
|
+
};
|
|
186
|
+
provider = new secretProviders_1.CompositeSecretProvider([a, b]);
|
|
187
|
+
return [4 /*yield*/, provider.getSecret("x", "7")];
|
|
188
|
+
case 1:
|
|
189
|
+
_a.sent();
|
|
190
|
+
(0, bun_test_1.expect)(seen).toEqual(["7", "7"]);
|
|
191
|
+
return [2 /*return*/];
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}); });
|
|
195
|
+
(0, bun_test_1.it)("builds a composite name from the underlying providers", function () {
|
|
196
|
+
var provider = new secretProviders_1.CompositeSecretProvider([
|
|
197
|
+
{ getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
198
|
+
return [2 /*return*/, null];
|
|
199
|
+
}); }); }, name: "gcp" },
|
|
200
|
+
{ getSecret: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
201
|
+
return [2 /*return*/, null];
|
|
202
|
+
}); }); }, name: "env" },
|
|
203
|
+
]);
|
|
204
|
+
(0, bun_test_1.expect)(provider.name).toBe("composite(gcp,env)");
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
(0, bun_test_1.describe)("CachingSecretProvider", function () {
|
|
208
|
+
(0, bun_test_1.it)("memoizes a value within the TTL (single underlying call)", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
209
|
+
var calls, underlying, provider, _a, _b;
|
|
210
|
+
return __generator(this, function (_c) {
|
|
211
|
+
switch (_c.label) {
|
|
212
|
+
case 0:
|
|
213
|
+
calls = 0;
|
|
214
|
+
underlying = {
|
|
215
|
+
getSecret: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
216
|
+
return __generator(this, function (_a) {
|
|
217
|
+
calls++;
|
|
218
|
+
return [2 /*return*/, "value"];
|
|
219
|
+
});
|
|
220
|
+
}); },
|
|
221
|
+
name: "underlying",
|
|
222
|
+
};
|
|
223
|
+
provider = new secretProviders_1.CachingSecretProvider(underlying, { ttlMs: 10000 });
|
|
224
|
+
_a = bun_test_1.expect;
|
|
225
|
+
return [4 /*yield*/, provider.getSecret("x")];
|
|
226
|
+
case 1:
|
|
227
|
+
_a.apply(void 0, [_c.sent()]).toBe("value");
|
|
228
|
+
_b = bun_test_1.expect;
|
|
229
|
+
return [4 /*yield*/, provider.getSecret("x")];
|
|
230
|
+
case 2:
|
|
231
|
+
_b.apply(void 0, [_c.sent()]).toBe("value");
|
|
232
|
+
(0, bun_test_1.expect)(calls).toBe(1);
|
|
233
|
+
return [2 /*return*/];
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}); });
|
|
237
|
+
(0, bun_test_1.it)("re-fetches after clear()", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
238
|
+
var calls, underlying, provider, _a, _b;
|
|
239
|
+
return __generator(this, function (_c) {
|
|
240
|
+
switch (_c.label) {
|
|
241
|
+
case 0:
|
|
242
|
+
calls = 0;
|
|
243
|
+
underlying = {
|
|
244
|
+
getSecret: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
245
|
+
return __generator(this, function (_a) {
|
|
246
|
+
calls++;
|
|
247
|
+
return [2 /*return*/, "value-".concat(calls)];
|
|
248
|
+
});
|
|
249
|
+
}); },
|
|
250
|
+
name: "underlying",
|
|
251
|
+
};
|
|
252
|
+
provider = new secretProviders_1.CachingSecretProvider(underlying, { ttlMs: 10000 });
|
|
253
|
+
_a = bun_test_1.expect;
|
|
254
|
+
return [4 /*yield*/, provider.getSecret("x")];
|
|
255
|
+
case 1:
|
|
256
|
+
_a.apply(void 0, [_c.sent()]).toBe("value-1");
|
|
257
|
+
provider.clear();
|
|
258
|
+
_b = bun_test_1.expect;
|
|
259
|
+
return [4 /*yield*/, provider.getSecret("x")];
|
|
260
|
+
case 2:
|
|
261
|
+
_b.apply(void 0, [_c.sent()]).toBe("value-2");
|
|
262
|
+
(0, bun_test_1.expect)(calls).toBe(2);
|
|
263
|
+
return [2 /*return*/];
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}); });
|
|
267
|
+
(0, bun_test_1.it)("re-fetches after the TTL expires", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
268
|
+
var calls, underlying, provider, _a, _b;
|
|
269
|
+
return __generator(this, function (_c) {
|
|
270
|
+
switch (_c.label) {
|
|
271
|
+
case 0:
|
|
272
|
+
calls = 0;
|
|
273
|
+
underlying = {
|
|
274
|
+
getSecret: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
275
|
+
return __generator(this, function (_a) {
|
|
276
|
+
calls++;
|
|
277
|
+
return [2 /*return*/, "value-".concat(calls)];
|
|
278
|
+
});
|
|
279
|
+
}); },
|
|
280
|
+
name: "underlying",
|
|
281
|
+
};
|
|
282
|
+
provider = new secretProviders_1.CachingSecretProvider(underlying, { ttlMs: 1 });
|
|
283
|
+
_a = bun_test_1.expect;
|
|
284
|
+
return [4 /*yield*/, provider.getSecret("x")];
|
|
285
|
+
case 1:
|
|
286
|
+
_a.apply(void 0, [_c.sent()]).toBe("value-1");
|
|
287
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 5); })];
|
|
288
|
+
case 2:
|
|
289
|
+
_c.sent();
|
|
290
|
+
_b = bun_test_1.expect;
|
|
291
|
+
return [4 /*yield*/, provider.getSecret("x")];
|
|
292
|
+
case 3:
|
|
293
|
+
_b.apply(void 0, [_c.sent()]).toBe("value-2");
|
|
294
|
+
(0, bun_test_1.expect)(calls).toBe(2);
|
|
295
|
+
return [2 /*return*/];
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}); });
|
|
299
|
+
(0, bun_test_1.it)("caches different versions independently", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
300
|
+
var seen, underlying, provider, _a, _b, _c;
|
|
301
|
+
return __generator(this, function (_d) {
|
|
302
|
+
switch (_d.label) {
|
|
303
|
+
case 0:
|
|
304
|
+
seen = [];
|
|
305
|
+
underlying = {
|
|
306
|
+
getSecret: function (_name, version) { return __awaiter(void 0, void 0, void 0, function () {
|
|
307
|
+
return __generator(this, function (_a) {
|
|
308
|
+
seen.push(version);
|
|
309
|
+
return [2 /*return*/, "v-".concat(version !== null && version !== void 0 ? version : "latest")];
|
|
310
|
+
});
|
|
311
|
+
}); },
|
|
312
|
+
name: "underlying",
|
|
313
|
+
};
|
|
314
|
+
provider = new secretProviders_1.CachingSecretProvider(underlying, { ttlMs: 10000 });
|
|
315
|
+
_a = bun_test_1.expect;
|
|
316
|
+
return [4 /*yield*/, provider.getSecret("x", "1")];
|
|
317
|
+
case 1:
|
|
318
|
+
_a.apply(void 0, [_d.sent()]).toBe("v-1");
|
|
319
|
+
_b = bun_test_1.expect;
|
|
320
|
+
return [4 /*yield*/, provider.getSecret("x", "2")];
|
|
321
|
+
case 2:
|
|
322
|
+
_b.apply(void 0, [_d.sent()]).toBe("v-2");
|
|
323
|
+
// Cached hits, no additional underlying calls.
|
|
324
|
+
_c = bun_test_1.expect;
|
|
325
|
+
return [4 /*yield*/, provider.getSecret("x", "1")];
|
|
326
|
+
case 3:
|
|
327
|
+
// Cached hits, no additional underlying calls.
|
|
328
|
+
_c.apply(void 0, [_d.sent()]).toBe("v-1");
|
|
329
|
+
(0, bun_test_1.expect)(seen).toEqual(["1", "2"]);
|
|
330
|
+
return [2 /*return*/];
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}); });
|
|
334
|
+
(0, bun_test_1.it)("clearKey invalidates a single secret", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
335
|
+
var calls, underlying, provider;
|
|
336
|
+
return __generator(this, function (_a) {
|
|
337
|
+
switch (_a.label) {
|
|
338
|
+
case 0:
|
|
339
|
+
calls = 0;
|
|
340
|
+
underlying = {
|
|
341
|
+
getSecret: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
342
|
+
return __generator(this, function (_a) {
|
|
343
|
+
calls++;
|
|
344
|
+
return [2 /*return*/, "value-".concat(calls)];
|
|
345
|
+
});
|
|
346
|
+
}); },
|
|
347
|
+
name: "underlying",
|
|
348
|
+
};
|
|
349
|
+
provider = new secretProviders_1.CachingSecretProvider(underlying, { ttlMs: 10000 });
|
|
350
|
+
return [4 /*yield*/, provider.getSecret("x")];
|
|
351
|
+
case 1:
|
|
352
|
+
_a.sent();
|
|
353
|
+
provider.clearKey("x");
|
|
354
|
+
return [4 /*yield*/, provider.getSecret("x")];
|
|
355
|
+
case 2:
|
|
356
|
+
_a.sent();
|
|
357
|
+
(0, bun_test_1.expect)(calls).toBe(2);
|
|
358
|
+
return [2 /*return*/];
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}); });
|
|
362
|
+
(0, bun_test_1.it)("caches null results", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
363
|
+
var calls, underlying, provider, _a, _b;
|
|
364
|
+
return __generator(this, function (_c) {
|
|
365
|
+
switch (_c.label) {
|
|
366
|
+
case 0:
|
|
367
|
+
calls = 0;
|
|
368
|
+
underlying = {
|
|
369
|
+
getSecret: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
370
|
+
return __generator(this, function (_a) {
|
|
371
|
+
calls++;
|
|
372
|
+
return [2 /*return*/, null];
|
|
373
|
+
});
|
|
374
|
+
}); },
|
|
375
|
+
name: "underlying",
|
|
376
|
+
};
|
|
377
|
+
provider = new secretProviders_1.CachingSecretProvider(underlying, { ttlMs: 10000 });
|
|
378
|
+
_a = bun_test_1.expect;
|
|
379
|
+
return [4 /*yield*/, provider.getSecret("missing")];
|
|
380
|
+
case 1:
|
|
381
|
+
_a.apply(void 0, [_c.sent()]).toBeNull();
|
|
382
|
+
_b = bun_test_1.expect;
|
|
383
|
+
return [4 /*yield*/, provider.getSecret("missing")];
|
|
384
|
+
case 2:
|
|
385
|
+
_b.apply(void 0, [_c.sent()]).toBeNull();
|
|
386
|
+
(0, bun_test_1.expect)(calls).toBe(1);
|
|
387
|
+
return [2 /*return*/];
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}); });
|
|
391
|
+
});
|
package/package.json
CHANGED
package/src/auth.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {randomUUID} from "node:crypto";
|
|
2
2
|
import express from "express";
|
|
3
3
|
import jwt, {type JwtPayload} from "jsonwebtoken";
|
|
4
|
+
import {DateTime} from "luxon";
|
|
4
5
|
import type {Model, ObjectId} from "mongoose";
|
|
5
6
|
import ms, {type StringValue} from "ms";
|
|
6
7
|
import passport from "passport";
|
|
@@ -185,7 +186,6 @@ export const generateTokens = async (
|
|
|
185
186
|
return {refreshToken, sessionId, token};
|
|
186
187
|
};
|
|
187
188
|
|
|
188
|
-
// TODO allow customization
|
|
189
189
|
export const setupAuth = (app: express.Application, userModel: UserModel): void => {
|
|
190
190
|
passport.use(new AnonymousStrategy());
|
|
191
191
|
passport.use(userModel.createStrategy());
|
|
@@ -300,7 +300,7 @@ export const setupAuth = (app: express.Application, userModel: UserModel): void
|
|
|
300
300
|
? (error as {expiredAt?: unknown}).expiredAt
|
|
301
301
|
: undefined;
|
|
302
302
|
const message = errorMessage(error);
|
|
303
|
-
const details = `[jwt] Error decoding token${userText}: ${error}, expired at ${expiredAt}, current time: ${
|
|
303
|
+
const details = `[jwt] Error decoding token${userText}: ${error}, expired at ${expiredAt}, current time: ${DateTime.now().toMillis()}`;
|
|
304
304
|
logger.debug(details);
|
|
305
305
|
return res.status(401).json({details, message});
|
|
306
306
|
}
|