@o3co/create-auth-provider 0.17.0-rc.2 → 0.17.0-rc.3
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/README.ja.md +3 -1
- package/README.md +5 -2
- package/package.json +1 -1
- package/templates/standalone/.env.example +7 -2
- package/templates/standalone/README.ja.md +14 -10
- package/templates/standalone/README.md +26 -20
- package/templates/standalone/config/application.conf +1 -0
- package/templates/standalone/config/clients.yaml.example +8 -0
- package/templates/standalone/config/development.conf +2 -2
- package/templates/standalone/config/mailpit.conf +10 -5
- package/templates/standalone/config/reference.conf +9 -3
- package/templates/standalone/docker-compose.mailpit.yml +3 -0
- package/templates/standalone/package.json +1 -1
- package/templates/standalone/src/__tests__/adapters.test.mts +12 -6
- package/templates/standalone/src/__tests__/all-modules-composition.multi.test.mts +13 -5
- package/templates/standalone/src/__tests__/config-notices.test.mts +4 -1
- package/templates/standalone/src/__tests__/configured-mfa-mode.fixture.mts +48 -0
- package/templates/standalone/src/__tests__/core-federations.test.mts +9 -6
- package/templates/standalone/src/__tests__/core-sections.test.mts +7 -5
- package/templates/standalone/src/__tests__/documented-env-overrides.test.mts +89 -86
- package/templates/standalone/src/__tests__/federation-grants-composition.test.mts +4 -0
- package/templates/standalone/src/__tests__/mailpit-compose.test.mts +81 -14
- package/templates/standalone/src/__tests__/mfa-switch.redis.test.mts +8 -4
- package/templates/standalone/src/__tests__/mfa-switch.test.mts +79 -21
- package/templates/standalone/src/__tests__/own-modules.test.mts +38 -12
- package/templates/standalone/src/__tests__/redis-assume-noeviction.test.mts +214 -0
- package/templates/standalone/src/__tests__/replica-safety.test.mts +4 -0
- package/templates/standalone/src/__tests__/scaffold-assets.test.mts +10 -3
- package/templates/standalone/src/__tests__/sections.test.mts +24 -0
- package/templates/standalone/src/__tests__/shipped-config-notices.test.mts +4 -0
- package/templates/standalone/src/__tests__/startup-log-lines.test.mts +148 -0
- package/templates/standalone/src/__tests__/three-tier-resolution.test.mts +8 -4
- package/templates/standalone/src/__tests__/two-phase-config.test.mts +2 -1
- package/templates/standalone/src/adapters.mts +2 -2
- package/templates/standalone/src/mfaSwitch.mts +6 -7
- package/templates/standalone/src/modules.mts +12 -6
- package/templates/standalone/src/rootRenames.mts +12 -13
- package/templates/standalone/src/sections.mts +12 -2
- package/templates/versions.json +18 -18
|
@@ -22,9 +22,8 @@
|
|
|
22
22
|
* bundles are listed whatever the map says (`federation-types.test.mts`
|
|
23
23
|
* boots them). A variable the
|
|
24
24
|
* template bound before, `FEDERATIONS_<NAME>_<KEY>`, set alone or beside its
|
|
25
|
-
* new name at
|
|
26
|
-
*
|
|
27
|
-
* are accepted. The map written at the top level refuses boot, naming its
|
|
25
|
+
* new name, at any value, is refused before any module is chosen, naming the
|
|
26
|
+
* new variable and path and never a value. The map written at the top level refuses boot, naming its
|
|
28
27
|
* paths under `core.federations`.
|
|
29
28
|
*/
|
|
30
29
|
|
|
@@ -206,9 +205,13 @@ describe("a variable the template bound before, FEDERATIONS_<NAME>_<KEY>", () =>
|
|
|
206
205
|
);
|
|
207
206
|
|
|
208
207
|
it.each(RENAMED)(
|
|
209
|
-
"core.federations.%s.%s: %s beside its new name at the same value is
|
|
210
|
-
(
|
|
211
|
-
|
|
208
|
+
"core.federations.%s.%s: %s beside its new name at the same value is refused all the same",
|
|
209
|
+
(_name, _key, old, variable, value) => {
|
|
210
|
+
const err = refusal({ [old]: value, [variable]: value });
|
|
211
|
+
expect(err).toMatchObject({
|
|
212
|
+
reason: "environment-variable-renamed",
|
|
213
|
+
details: { renamed: [{ from: old, state: "different" }] },
|
|
214
|
+
});
|
|
212
215
|
},
|
|
213
216
|
);
|
|
214
217
|
});
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
* environment, phase one's switches, then the layers over every loaded
|
|
21
21
|
* package's `reference.conf` handed to boot. A path they moved from, written
|
|
22
22
|
* in the operator's own layer, refuses boot naming the new one;
|
|
23
|
-
* `DEPLOYMENT_MODE`, renamed `CORE_DEPLOYMENT_MODE`, refuses boot
|
|
24
|
-
*
|
|
23
|
+
* `DEPLOYMENT_MODE`, renamed `CORE_DEPLOYMENT_MODE`, refuses boot while it is
|
|
24
|
+
* set, whatever the new name holds.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import { BootError } from "@o3co/auth-provider-core";
|
|
@@ -176,10 +176,12 @@ describe("DEPLOYMENT_MODE, renamed CORE_DEPLOYMENT_MODE, through the template's
|
|
|
176
176
|
expect(err.message).not.toContain("new-mode-c81a");
|
|
177
177
|
});
|
|
178
178
|
|
|
179
|
-
it("set beside the new name at the same value:
|
|
180
|
-
const
|
|
179
|
+
it("set beside the new name at the same value: refused all the same", async () => {
|
|
180
|
+
const err = await refused({ DEPLOYMENT_MODE: "single", CORE_DEPLOYMENT_MODE: "single" });
|
|
181
181
|
|
|
182
|
-
expect(
|
|
182
|
+
expect(err.details).toMatchObject({
|
|
183
|
+
renamed: [{ from: "DEPLOYMENT_MODE", to: "CORE_DEPLOYMENT_MODE", state: "different" }],
|
|
184
|
+
});
|
|
183
185
|
});
|
|
184
186
|
|
|
185
187
|
it("unset, with the new name set: boots with its mode", async () => {
|
|
@@ -44,7 +44,12 @@ import {
|
|
|
44
44
|
resolveLayers,
|
|
45
45
|
type Switches,
|
|
46
46
|
} from "../configPath.mjs";
|
|
47
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
httpModule,
|
|
49
|
+
keyStoreModule,
|
|
50
|
+
standaloneRedisClientsModule,
|
|
51
|
+
templateReference,
|
|
52
|
+
} from "../modules.mjs";
|
|
48
53
|
|
|
49
54
|
/**
|
|
50
55
|
* Boots the shipped config with EVERY documented override supplied the way an
|
|
@@ -195,6 +200,7 @@ const DOCUMENTED_ENV: Readonly<Record<string, string>> = {
|
|
|
195
200
|
REDIS_REFRESH_TOKEN_FAMILY_STORE_CAS_RETRY_LIMIT: "3",
|
|
196
201
|
REDIS_CLIENTS_URL: "redis://redis:6379",
|
|
197
202
|
REDIS_CLIENTS_PASSWORD: "rt-family-password",
|
|
203
|
+
REDIS_CLIENTS_ASSUME_NO_EVICTION: "true",
|
|
198
204
|
// The federation token store's Redis branch.
|
|
199
205
|
REDIS_FEDERATION_TOKEN_STORE_KEY_PREFIX: "ft:",
|
|
200
206
|
REDIS_FEDERATION_TOKEN_STORE_ENCRYPTION_MODE: "required",
|
|
@@ -311,139 +317,139 @@ const DELIBERATELY_UNSET: Readonly<Record<string, string>> = {
|
|
|
311
317
|
OAUTH_AUTHORIZE_ALLOW_UNMARKED_CLIENTS:
|
|
312
318
|
"the variable of a removed key, and only captured — any value fails boot",
|
|
313
319
|
OAUTH_ACCESS_TOKEN_EXPIRES_IN:
|
|
314
|
-
"renamed OAUTH_ACCESS_TOKEN_DEFAULT_EXPIRES_IN, and only captured — set
|
|
320
|
+
"renamed OAUTH_ACCESS_TOKEN_DEFAULT_EXPIRES_IN, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
315
321
|
DEPLOYMENT_MODE:
|
|
316
|
-
"renamed CORE_DEPLOYMENT_MODE, and only captured — set
|
|
322
|
+
"renamed CORE_DEPLOYMENT_MODE, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
317
323
|
MEMORY_RATE_LIMITER_MAX_BUCKETS:
|
|
318
|
-
"renamed CORE_RATE_LIMITER_MEMORY_MAX_BUCKETS, and only captured — set
|
|
324
|
+
"renamed CORE_RATE_LIMITER_MEMORY_MAX_BUCKETS, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
319
325
|
RATE_LIMIT_FAIL_MODE:
|
|
320
|
-
"renamed REDIS_RATE_LIMITER_FAIL_MODE, and only captured — set
|
|
326
|
+
"renamed REDIS_RATE_LIMITER_FAIL_MODE, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
321
327
|
REFRESH_TOKEN_FAMILY_STORE_KEY_PREFIX:
|
|
322
|
-
"renamed REDIS_REFRESH_TOKEN_FAMILY_STORE_KEY_PREFIX, and only captured — set
|
|
328
|
+
"renamed REDIS_REFRESH_TOKEN_FAMILY_STORE_KEY_PREFIX, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
323
329
|
REFRESH_TOKEN_FAMILY_STORE_CAS_RETRY_LIMIT:
|
|
324
|
-
"renamed REDIS_REFRESH_TOKEN_FAMILY_STORE_CAS_RETRY_LIMIT, and only captured — set
|
|
330
|
+
"renamed REDIS_REFRESH_TOKEN_FAMILY_STORE_CAS_RETRY_LIMIT, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
325
331
|
FEDERATION_GRANTS_ENCRYPTION_MODE:
|
|
326
|
-
"renamed REDIS_FEDERATION_GRANT_STORE_ENCRYPTION_MODE, and only captured — set
|
|
332
|
+
"renamed REDIS_FEDERATION_GRANT_STORE_ENCRYPTION_MODE, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
327
333
|
OAUTH_TOKEN_BINDING_DISPATCH_POLICY:
|
|
328
|
-
"renamed CORE_TOKEN_BINDING_DISPATCH_POLICY, and only captured — set
|
|
334
|
+
"renamed CORE_TOKEN_BINDING_DISPATCH_POLICY, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
329
335
|
OAUTH_TOKEN_BINDING_BIND_CONFIDENTIAL_CLIENT_REFRESH_TOKENS:
|
|
330
|
-
"renamed CORE_TOKEN_BINDING_BIND_CONFIDENTIAL_CLIENT_REFRESH_TOKENS, and only captured — set
|
|
336
|
+
"renamed CORE_TOKEN_BINDING_BIND_CONFIDENTIAL_CLIENT_REFRESH_TOKENS, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
331
337
|
OAUTH_GRANTS_SESSION_ENABLED:
|
|
332
|
-
"renamed OAUTH_SESSION_ENABLED, and only captured — set
|
|
338
|
+
"renamed OAUTH_SESSION_ENABLED, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
333
339
|
OAUTH_GRANTS_AUTHORIZATION_CODE_ENABLED:
|
|
334
|
-
"renamed OAUTH_AUTHORIZATION_GRANTS_AUTHORIZATION_CODE_ENABLED, and only captured — set
|
|
340
|
+
"renamed OAUTH_AUTHORIZATION_GRANTS_AUTHORIZATION_CODE_ENABLED, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
335
341
|
OAUTH_GRANTS_REFRESH_TOKEN_ENABLED:
|
|
336
|
-
"renamed OAUTH_AUTHORIZATION_GRANTS_REFRESH_TOKEN_ENABLED, and only captured — set
|
|
342
|
+
"renamed OAUTH_AUTHORIZATION_GRANTS_REFRESH_TOKEN_ENABLED, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
337
343
|
OAUTH_REFRESH_TOKEN_UNKNOWN_FAMILY_POLICY:
|
|
338
|
-
"renamed OAUTH_AUTHORIZATION_GRANTS_REFRESH_TOKEN_UNKNOWN_FAMILY_POLICY, and only captured — set
|
|
344
|
+
"renamed OAUTH_AUTHORIZATION_GRANTS_REFRESH_TOKEN_UNKNOWN_FAMILY_POLICY, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
339
345
|
OAUTH_GRANTS_CLIENT_CREDENTIALS_ENABLED:
|
|
340
|
-
"renamed OAUTH_AUTHORIZATION_GRANTS_CLIENT_CREDENTIALS_ENABLED, and only captured — set
|
|
346
|
+
"renamed OAUTH_AUTHORIZATION_GRANTS_CLIENT_CREDENTIALS_ENABLED, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
341
347
|
OAUTH_GRANTS_JWT_BEARER_ENABLED:
|
|
342
|
-
"renamed OAUTH_AUTHORIZATION_GRANTS_JWT_BEARER_ENABLED, and only captured — set
|
|
348
|
+
"renamed OAUTH_AUTHORIZATION_GRANTS_JWT_BEARER_ENABLED, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
343
349
|
ENDPOINTS_CONSENT_URL:
|
|
344
|
-
"renamed OAUTH_CONSENT_PAGE_URL, and only captured — set
|
|
350
|
+
"renamed OAUTH_CONSENT_PAGE_URL, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
345
351
|
OAUTH_CIMD_ENABLED:
|
|
346
|
-
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_ENABLED, and only captured — set
|
|
352
|
+
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_ENABLED, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
347
353
|
OAUTH_CIMD_ALLOWED_SCOPES:
|
|
348
|
-
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_ALLOWED_SCOPES, and only captured — set
|
|
354
|
+
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_ALLOWED_SCOPES, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
349
355
|
OAUTH_CIMD_ALLOWED_AUDIENCES:
|
|
350
|
-
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_ALLOWED_AUDIENCES, and only captured — set
|
|
356
|
+
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_ALLOWED_AUDIENCES, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
351
357
|
OAUTH_CIMD_ALLOWED_HOSTS:
|
|
352
|
-
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_ALLOWED_HOSTS, and only captured — set
|
|
358
|
+
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_ALLOWED_HOSTS, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
353
359
|
OAUTH_CIMD_DENIED_HOSTS:
|
|
354
|
-
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_DENIED_HOSTS, and only captured — set
|
|
360
|
+
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_DENIED_HOSTS, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
355
361
|
OAUTH_CIMD_MAX_BYTES:
|
|
356
|
-
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_MAX_BYTES, and only captured — set
|
|
362
|
+
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_MAX_BYTES, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
357
363
|
OAUTH_CIMD_TIMEOUT_MS:
|
|
358
|
-
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_TIMEOUT_MS, and only captured — set
|
|
364
|
+
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_TIMEOUT_MS, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
359
365
|
OAUTH_CIMD_CACHE_MAX_AGE_MS:
|
|
360
|
-
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_CACHE_MAX_AGE_MS, and only captured — set
|
|
366
|
+
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_CACHE_MAX_AGE_MS, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
361
367
|
OAUTH_CIMD_MAX_CACHE_ENTRIES:
|
|
362
|
-
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_MAX_CACHE_ENTRIES, and only captured — set
|
|
368
|
+
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_MAX_CACHE_ENTRIES, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
363
369
|
OAUTH_CIMD_STALE_IF_ERROR_MS:
|
|
364
|
-
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_STALE_IF_ERROR_MS, and only captured — set
|
|
370
|
+
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_STALE_IF_ERROR_MS, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
365
371
|
OAUTH_CIMD_NEGATIVE_CACHE_MS:
|
|
366
|
-
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_NEGATIVE_CACHE_MS, and only captured — set
|
|
372
|
+
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_NEGATIVE_CACHE_MS, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
367
373
|
OAUTH_CIMD_MAX_CONCURRENT_FETCHES:
|
|
368
|
-
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_MAX_CONCURRENT_FETCHES, and only captured — set
|
|
374
|
+
"renamed OAUTH_CLIENT_ID_METADATA_DOCUMENTS_MAX_CONCURRENT_FETCHES, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
369
375
|
OAUTH_GRANTS_AUTHORIZATION_CODE_PKCE_REQUIRE_S256:
|
|
370
376
|
"the authorization-code grant's pkce block was removed, and this is only captured — set at all, it fails boot",
|
|
371
377
|
ENDPOINTS_LOGIN_URL:
|
|
372
|
-
"renamed SESSION_LOGIN_PAGE_URL, and only captured — set
|
|
378
|
+
"renamed SESSION_LOGIN_PAGE_URL, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
373
379
|
SESSION_SECRET:
|
|
374
|
-
"renamed SESSION_STORE_SECRET, and only captured — set
|
|
380
|
+
"renamed SESSION_STORE_SECRET, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
375
381
|
SESSION_NAME:
|
|
376
|
-
"renamed SESSION_STORE_NAME, and only captured — set
|
|
382
|
+
"renamed SESSION_STORE_NAME, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
377
383
|
SESSION_MAX_AGE:
|
|
378
|
-
"renamed SESSION_STORE_MAX_AGE, and only captured — set
|
|
384
|
+
"renamed SESSION_STORE_MAX_AGE, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
379
385
|
SESSION_SECURE:
|
|
380
|
-
"renamed SESSION_STORE_SECURE, and only captured — set
|
|
386
|
+
"renamed SESSION_STORE_SECURE, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
381
387
|
SESSION_SAME_SITE:
|
|
382
|
-
"renamed SESSION_STORE_SAME_SITE, and only captured — set
|
|
388
|
+
"renamed SESSION_STORE_SAME_SITE, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
383
389
|
SESSION_DOMAIN:
|
|
384
|
-
"renamed SESSION_STORE_DOMAIN, and only captured — set
|
|
390
|
+
"renamed SESSION_STORE_DOMAIN, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
385
391
|
SESSION_STORAGE_TYPE:
|
|
386
|
-
"renamed SESSION_STORE_STORAGE_TYPE, and only captured — set
|
|
392
|
+
"renamed SESSION_STORE_STORAGE_TYPE, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
387
393
|
SESSION_STORAGE_REDIS_URL:
|
|
388
|
-
"renamed SESSION_STORE_STORAGE_REDIS_URL, and only captured — set
|
|
394
|
+
"renamed SESSION_STORE_STORAGE_REDIS_URL, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
389
395
|
SESSION_STORAGE_REDIS_PASSWORD:
|
|
390
|
-
"renamed SESSION_STORE_STORAGE_REDIS_PASSWORD, and only captured — set
|
|
396
|
+
"renamed SESSION_STORE_STORAGE_REDIS_PASSWORD, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
391
397
|
LOG_LEVEL:
|
|
392
|
-
"renamed LOGGING_LEVEL, and only captured — set
|
|
398
|
+
"renamed LOGGING_LEVEL, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
393
399
|
CORS_ALLOWED_ORIGINS:
|
|
394
|
-
"renamed HTTP_CORS_ALLOWED_ORIGINS, and only captured — set
|
|
400
|
+
"renamed HTTP_CORS_ALLOWED_ORIGINS, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
395
401
|
OAUTH_JWT_SIGNING_KEY_PROVIDER:
|
|
396
|
-
"renamed KEY_STORE_PROVIDER, and only captured — set
|
|
402
|
+
"renamed KEY_STORE_PROVIDER, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
397
403
|
OAUTH_JWT_ALGORITHM:
|
|
398
|
-
"renamed KEY_STORE_LOCAL_ALGORITHM, and only captured — set
|
|
404
|
+
"renamed KEY_STORE_LOCAL_ALGORITHM, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
399
405
|
OAUTH_JWT_KID:
|
|
400
|
-
"renamed KEY_STORE_LOCAL_KID, and only captured — set
|
|
406
|
+
"renamed KEY_STORE_LOCAL_KID, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
401
407
|
OAUTH_JWT_SECRET:
|
|
402
|
-
"renamed KEY_STORE_LOCAL_SECRET, and only captured — set
|
|
408
|
+
"renamed KEY_STORE_LOCAL_SECRET, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
403
409
|
OAUTH_JWT_PRIVATE_KEY_PATH:
|
|
404
|
-
"renamed KEY_STORE_LOCAL_PRIVATE_KEY_PATH, and only captured — set
|
|
410
|
+
"renamed KEY_STORE_LOCAL_PRIVATE_KEY_PATH, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
405
411
|
OAUTH_JWT_PUBLIC_KEY_PATH:
|
|
406
|
-
"renamed KEY_STORE_LOCAL_PUBLIC_KEY_PATH, and only captured — set
|
|
412
|
+
"renamed KEY_STORE_LOCAL_PUBLIC_KEY_PATH, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
407
413
|
OAUTH_JWT_PRIVATE_KEY:
|
|
408
|
-
"renamed KEY_STORE_LOCAL_PRIVATE_KEY, and only captured — set
|
|
414
|
+
"renamed KEY_STORE_LOCAL_PRIVATE_KEY, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
409
415
|
OAUTH_JWT_PUBLIC_KEY:
|
|
410
|
-
"renamed KEY_STORE_LOCAL_PUBLIC_KEY, and only captured — set
|
|
416
|
+
"renamed KEY_STORE_LOCAL_PUBLIC_KEY, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
411
417
|
REFRESH_TOKEN_FAMILY_STORE_REDIS_URL:
|
|
412
|
-
"renamed REDIS_CLIENTS_URL, and only captured — set
|
|
418
|
+
"renamed REDIS_CLIENTS_URL, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
413
419
|
REFRESH_TOKEN_FAMILY_STORE_REDIS_PASSWORD:
|
|
414
|
-
"renamed REDIS_CLIENTS_PASSWORD, and only captured — set
|
|
420
|
+
"renamed REDIS_CLIENTS_PASSWORD, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
415
421
|
CLIENT_PATH:
|
|
416
|
-
"renamed REPOSITORIES_CLIENT_YAML_PATH, and only captured — set
|
|
422
|
+
"renamed REPOSITORIES_CLIENT_YAML_PATH, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
417
423
|
CLIENT_USER_PATH:
|
|
418
|
-
"renamed REPOSITORIES_USER_YAML_PATH, and only captured — set
|
|
424
|
+
"renamed REPOSITORIES_USER_YAML_PATH, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
419
425
|
CLIENT_USER_AUTHENTICATE_URL:
|
|
420
|
-
"renamed REPOSITORIES_USER_HTTP_AUTHENTICATE_URL, and only captured — set
|
|
426
|
+
"renamed REPOSITORIES_USER_HTTP_AUTHENTICATE_URL, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
421
427
|
CLIENT_USER_AUTHENTICATE_BY_TOKEN_URL:
|
|
422
|
-
"renamed REPOSITORIES_USER_HTTP_AUTHENTICATE_BY_TOKEN_URL, and only captured — set
|
|
428
|
+
"renamed REPOSITORIES_USER_HTTP_AUTHENTICATE_BY_TOKEN_URL, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
423
429
|
CLIENT_USER_LINK_FEDERATED_IDENTITY_URL:
|
|
424
|
-
"renamed REPOSITORIES_USER_HTTP_LINK_FEDERATED_IDENTITY_URL, and only captured — set
|
|
430
|
+
"renamed REPOSITORIES_USER_HTTP_LINK_FEDERATED_IDENTITY_URL, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
425
431
|
CLIENT_USER_FIND_SUBJECT_BY_FEDERATED_IDENTITY_URL:
|
|
426
|
-
"renamed REPOSITORIES_USER_HTTP_FIND_SUBJECT_BY_FEDERATED_IDENTITY_URL, and only captured — set
|
|
432
|
+
"renamed REPOSITORIES_USER_HTTP_FIND_SUBJECT_BY_FEDERATED_IDENTITY_URL, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
427
433
|
CLIENT_USER_BEARER_TOKEN:
|
|
428
|
-
"renamed REPOSITORIES_USER_HTTP_BEARER_TOKEN, and only captured — set
|
|
434
|
+
"renamed REPOSITORIES_USER_HTTP_BEARER_TOKEN, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
429
435
|
CLIENT_USER_TIMEOUT:
|
|
430
|
-
"renamed REPOSITORIES_USER_HTTP_TIMEOUT, and only captured — set
|
|
436
|
+
"renamed REPOSITORIES_USER_HTTP_TIMEOUT, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
431
437
|
CLIENT_USER_MAX_RESPONSE_BYTES:
|
|
432
|
-
"renamed REPOSITORIES_USER_HTTP_MAX_RESPONSE_BYTES, and only captured — set
|
|
438
|
+
"renamed REPOSITORIES_USER_HTTP_MAX_RESPONSE_BYTES, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
433
439
|
CLIENT_CODE_DEFAULT_EXPIRES_IN:
|
|
434
|
-
"renamed REDIS_CODE_REPOSITORY_DEFAULT_EXPIRES_IN (the Redis code repository) and STANDALONE_IN_MEMORY_CODE_REPOSITORY_DEFAULT_EXPIRES_IN (the in-process one), and only captured — set
|
|
440
|
+
"renamed REDIS_CODE_REPOSITORY_DEFAULT_EXPIRES_IN (the Redis code repository) and STANDALONE_IN_MEMORY_CODE_REPOSITORY_DEFAULT_EXPIRES_IN (the in-process one), and only captured — set at any value, beside its new name or not, it fails boot",
|
|
435
441
|
CLIENT_CODE_KEY_PREFIX:
|
|
436
|
-
"renamed REDIS_CODE_REPOSITORY_KEY_PREFIX, and only captured — set
|
|
442
|
+
"renamed REDIS_CODE_REPOSITORY_KEY_PREFIX, and only captured — set at any value, beside its new name or not, it fails boot",
|
|
437
443
|
CLIENT_CODE_ENDPOINT_URI:
|
|
438
444
|
"removed: the Redis code repository uses the shared redis-clients connection; only captured — set at all, it fails boot",
|
|
439
445
|
CLIENT_CODE_PASSWORD:
|
|
440
446
|
"removed: the Redis code repository uses the shared redis-clients connection; only captured — set at all, it fails boot",
|
|
441
447
|
ENDPOINTS_MFA_URL:
|
|
442
|
-
"renamed MFA_PAGE_URL, and only captured — set
|
|
448
|
+
"renamed MFA_PAGE_URL, and only captured — set at any value, beside its new name or not, it fails boot where MFA is installed",
|
|
443
449
|
MFA_TOTP_ENABLED:
|
|
444
|
-
"renamed MFA_TOTP_FACTOR_ENABLED, and only captured — set
|
|
450
|
+
"renamed MFA_TOTP_FACTOR_ENABLED, and only captured — set at any value, beside its new name or not, it fails boot where MFA is installed",
|
|
445
451
|
MFA_TOTP_ISSUER:
|
|
446
|
-
"renamed MFA_TOTP_FACTOR_ISSUER, and only captured — set
|
|
452
|
+
"renamed MFA_TOTP_FACTOR_ISSUER, and only captured — set at any value, beside its new name or not, it fails boot where MFA is installed",
|
|
447
453
|
};
|
|
448
454
|
|
|
449
455
|
/**
|
|
@@ -480,11 +486,11 @@ function commentedFederationBindings(): string {
|
|
|
480
486
|
}
|
|
481
487
|
|
|
482
488
|
/**
|
|
483
|
-
* The provider environment `o3co/auth`'s `tests/docker-compose.yml`
|
|
484
|
-
* transcribed: each renamed variable under its
|
|
485
|
-
*
|
|
486
|
-
* so a parse failure here is a red umbrella
|
|
487
|
-
* first. `SESSION_STORE_SECURE=false` is the one it cannot run without: the
|
|
489
|
+
* The provider environment `o3co/auth`'s `tests/docker-compose.yml` is to
|
|
490
|
+
* set, transcribed: each renamed variable under its new name alone, since an
|
|
491
|
+
* old name set refuses boot beside its new one too. The umbrella E2E boots
|
|
492
|
+
* the shipped template with this, so a parse failure here is a red umbrella
|
|
493
|
+
* build that this repository can see first. `SESSION_STORE_SECURE=false` is the one it cannot run without: the
|
|
488
494
|
* suite speaks plain HTTP.
|
|
489
495
|
*
|
|
490
496
|
* The two `FEDERATION_TOKEN_STORE` lines are required: the federation token
|
|
@@ -493,37 +499,23 @@ function commentedFederationBindings(): string {
|
|
|
493
499
|
* Redis store is selected with its encryption key.
|
|
494
500
|
*/
|
|
495
501
|
const UMBRELLA_E2E_ENV: Readonly<Record<string, string>> = {
|
|
496
|
-
OAUTH_JWT_ALGORITHM: "HS256",
|
|
497
502
|
KEY_STORE_LOCAL_ALGORITHM: "HS256",
|
|
498
|
-
OAUTH_JWT_SECRET: "e2e-shared-hs256-secret.at-least-32-bytes.ok",
|
|
499
503
|
KEY_STORE_LOCAL_SECRET: "e2e-shared-hs256-secret.at-least-32-bytes.ok",
|
|
500
504
|
OAUTH_JWT_ISSUER: "https://auth.e2e.test",
|
|
501
|
-
SESSION_SECRET: "lO0QH09fuKSGuViZ9myJbH3jsgai99A2GpC3RYRuy6Y=",
|
|
502
505
|
SESSION_STORE_SECRET: "lO0QH09fuKSGuViZ9myJbH3jsgai99A2GpC3RYRuy6Y=",
|
|
503
|
-
SESSION_SECURE: "false",
|
|
504
506
|
SESSION_STORE_SECURE: "false",
|
|
505
|
-
SESSION_NAME: "auth.session",
|
|
506
507
|
SESSION_STORE_NAME: "auth.session",
|
|
507
|
-
DEPLOYMENT_MODE: "multi",
|
|
508
508
|
CORE_DEPLOYMENT_MODE: "multi",
|
|
509
|
-
REFRESH_TOKEN_FAMILY_STORE_REDIS_URL: "redis://redis:6379",
|
|
510
509
|
REDIS_CLIENTS_URL: "redis://redis:6379",
|
|
511
|
-
SESSION_STORAGE_REDIS_URL: "redis://redis:6379",
|
|
512
510
|
SESSION_STORE_STORAGE_REDIS_URL: "redis://redis:6379",
|
|
513
|
-
USER_SESSION_STORES_ADAPTER: "redis",
|
|
514
511
|
ADAPTERS_USER_SESSION_STORES: "redis",
|
|
515
|
-
RATE_LIMITER_ADAPTER: "redis",
|
|
516
512
|
ADAPTERS_RATE_LIMITER: "redis",
|
|
517
|
-
OAUTH_CODE_ADAPTER: "redis",
|
|
518
513
|
ADAPTERS_CODE_REPOSITORY: "redis",
|
|
519
|
-
FEDERATION_TOKEN_STORE_TYPE: "redis",
|
|
520
514
|
ADAPTERS_FEDERATION_TOKEN_STORE: "redis",
|
|
521
515
|
REDIS_FEDERATION_TOKEN_STORE_ENCRYPTION_KEY: "BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc=",
|
|
522
|
-
CLIENT_USER_TYPE: "yaml",
|
|
523
516
|
ADAPTERS_USER_REPOSITORY: "yaml",
|
|
524
517
|
OAUTH_RESOURCE_INDICATOR_ENABLED: "true",
|
|
525
518
|
OAUTH_REQUIRE_EMAIL_VERIFIED: "true",
|
|
526
|
-
OAUTH_GRANTS_SESSION_ENABLED: "true",
|
|
527
519
|
OAUTH_SESSION_ENABLED: "true",
|
|
528
520
|
MFA_MODE: "off",
|
|
529
521
|
};
|
|
@@ -782,6 +774,11 @@ describe("the shipped config boots with every documented override supplied as a
|
|
|
782
774
|
// A leftover string here would be read as "on" by a truthiness check
|
|
783
775
|
// and as "off" by `=== true`, for a feature whose whole default is off.
|
|
784
776
|
expect(federationGrantsSection(config).enabled).toBe(true);
|
|
777
|
+
expect(
|
|
778
|
+
standaloneRedisClientsModule.section?.schema.parse(
|
|
779
|
+
(config as Record<string, unknown>)["redis-clients"],
|
|
780
|
+
),
|
|
781
|
+
).toMatchObject({ assumeNoEviction: true });
|
|
785
782
|
});
|
|
786
783
|
|
|
787
784
|
it("turns every non-boolean override into its declared type", async () => {
|
|
@@ -1245,12 +1242,18 @@ describe("the shipped config boots with every documented override supplied as a
|
|
|
1245
1242
|
).rejects.toMatchObject({ reason: "environment-variable-renamed" });
|
|
1246
1243
|
});
|
|
1247
1244
|
|
|
1248
|
-
it("
|
|
1249
|
-
const
|
|
1245
|
+
it("refuses OAUTH_ACCESS_TOKEN_EXPIRES_IN beside its new name set to the same value", async () => {
|
|
1246
|
+
const booting = bootParsed({
|
|
1250
1247
|
...DOCUMENTED_ENV,
|
|
1251
1248
|
OAUTH_ACCESS_TOKEN_EXPIRES_IN:
|
|
1252
1249
|
DOCUMENTED_ENV.OAUTH_ACCESS_TOKEN_DEFAULT_EXPIRES_IN as string,
|
|
1253
1250
|
});
|
|
1251
|
+
await expect(booting).rejects.toMatchObject({ reason: "environment-variable-renamed" });
|
|
1252
|
+
await expect(booting).rejects.toThrow(/unset OAUTH_ACCESS_TOKEN_EXPIRES_IN/);
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
it("boots with OAUTH_ACCESS_TOKEN_DEFAULT_EXPIRES_IN alone, reading it", async () => {
|
|
1256
|
+
const config = await bootParsed(DOCUMENTED_ENV);
|
|
1254
1257
|
expect(oauthSection(config).accessToken).toEqual({
|
|
1255
1258
|
defaultExpiresIn: 900,
|
|
1256
1259
|
maxExpiresIn: 7200,
|
|
@@ -85,6 +85,10 @@ vi.mock("ioredis", () => {
|
|
|
85
85
|
quit: async () => "OK",
|
|
86
86
|
disconnect: () => undefined,
|
|
87
87
|
ping: async () => "PONG",
|
|
88
|
+
// The Redis stores' eviction gate reads the policy from INFO memory: a
|
|
89
|
+
// default server's.
|
|
90
|
+
info: async (section: string) =>
|
|
91
|
+
section === "memory" ? "# Memory\r\nmaxmemory_policy:noeviction\r\n" : null,
|
|
88
92
|
};
|
|
89
93
|
const makeMockRedis = (): object =>
|
|
90
94
|
new Proxy(
|
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* The development compose run, `docker-compose.yml`, with MFA
|
|
19
|
-
* template ships it, and the Mailpit
|
|
20
|
-
* development run whose mail goes
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* The development compose run, `docker-compose.yml`, with MFA as the
|
|
19
|
+
* configuration's switch says (on as the template ships it), and the Mailpit
|
|
20
|
+
* overlay, `docker-compose.mailpit.yml`: a development run whose mail goes
|
|
21
|
+
* through the SMTP sender to Mailpit. It is an overlay on
|
|
22
|
+
* `docker-compose.yml`, so a plain `docker compose up` starts no Mailpit, and
|
|
23
|
+
* the production file names none. The compose files are read as
|
|
23
24
|
* the fixed-shape files they are, and the process each run describes is booted
|
|
24
25
|
* from the shipped files, as `app.mts` boots them, under the environment the
|
|
25
26
|
* run sets: `.env.example`'s lines, then each compose file's `environment`, the
|
|
@@ -44,6 +45,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
44
45
|
import { buildModules } from "#/buildModules.mjs";
|
|
45
46
|
import { readOwnLayers, readSwitches, resolveConfigPaths, resolveForBoot } from "#/configPath.mjs";
|
|
46
47
|
import { createRecordingLogger } from "./all-modules-composition.fixture.mjs";
|
|
48
|
+
import { configuredMfaMode } from "./configured-mfa-mode.fixture.mjs";
|
|
47
49
|
|
|
48
50
|
vi.mock("redis", () => ({
|
|
49
51
|
createClient: vi.fn(() => ({
|
|
@@ -74,6 +76,10 @@ vi.mock("ioredis", () => {
|
|
|
74
76
|
quit: async () => "OK",
|
|
75
77
|
disconnect: () => undefined,
|
|
76
78
|
ping: async () => "PONG",
|
|
79
|
+
// The Redis stores' eviction gate reads the policy from INFO memory: a
|
|
80
|
+
// default server's.
|
|
81
|
+
info: async (section: string) =>
|
|
82
|
+
section === "memory" ? "# Memory\r\nmaxmemory_policy:noeviction\r\n" : null,
|
|
77
83
|
};
|
|
78
84
|
const makeMockRedis = (): object =>
|
|
79
85
|
new Proxy(
|
|
@@ -245,14 +251,15 @@ describe("the process each development run describes", () => {
|
|
|
245
251
|
};
|
|
246
252
|
}
|
|
247
253
|
|
|
248
|
-
it("boots docker-compose.yml's run with MFA
|
|
254
|
+
it("boots docker-compose.yml's run with MFA as the configuration's switch says: development, the development mail sender, the MFA stores on its Redis", async () => {
|
|
249
255
|
const env = containerEnv(false);
|
|
250
256
|
// `app.mts`: CONFIG_ENV, then NODE_ENV, then development; the run sets neither.
|
|
251
257
|
expect(env.CONFIG_ENV ?? env.NODE_ENV).toBeUndefined();
|
|
252
258
|
const { applicationConfPath, envConfPath } = resolveConfigPaths(configDir, "development");
|
|
253
259
|
const own = readOwnLayers([envConfPath, applicationConfPath], { env });
|
|
254
260
|
const switches = readSwitches(own);
|
|
255
|
-
|
|
261
|
+
const mode = configuredMfaMode("development");
|
|
262
|
+
expect(switches.mfaMode).toBe(mode);
|
|
256
263
|
// A hot reload restarts the process on every save, and Redis keeps the
|
|
257
264
|
// factors, and the sessions beside them, across it.
|
|
258
265
|
expect(switches.adapters).toMatchObject({
|
|
@@ -277,7 +284,7 @@ describe("the process each development run describes", () => {
|
|
|
277
284
|
"standard-development",
|
|
278
285
|
);
|
|
279
286
|
expect((handle.components.config as { mfa?: { mode?: unknown } } | undefined)?.mfa?.mode).toBe(
|
|
280
|
-
"
|
|
287
|
+
mode === "off" ? undefined : mode,
|
|
281
288
|
);
|
|
282
289
|
});
|
|
283
290
|
|
|
@@ -289,18 +296,27 @@ describe("the process each development run describes", () => {
|
|
|
289
296
|
expect(existsSync(resolveConfigPaths(configDir, configEnv as string).envConfPath)).toBe(true);
|
|
290
297
|
});
|
|
291
298
|
|
|
292
|
-
|
|
293
|
-
|
|
299
|
+
/** The key the operator writes into `.env` for the Mailpit run (`openssl rand -base64 32`). */
|
|
300
|
+
const OWN_MFA_KEY = Buffer.alloc(32, 7).toString("base64");
|
|
301
|
+
|
|
302
|
+
/** The Mailpit run's modules and the configuration boot is handed, under `env`. */
|
|
303
|
+
function mailpitRun(env: Record<string, string>) {
|
|
294
304
|
const configEnv = env.CONFIG_ENV as string;
|
|
295
305
|
const { applicationConfPath, envConfPath } = resolveConfigPaths(configDir, configEnv);
|
|
296
306
|
const own = readOwnLayers([envConfPath, applicationConfPath], { env });
|
|
297
307
|
const switches = readSwitches(own);
|
|
298
|
-
expect(switches.mfaMode).toBe("required");
|
|
299
|
-
|
|
300
308
|
const modules = buildModules(switches, {
|
|
301
309
|
environment: configEnv,
|
|
302
310
|
refreshTokenFamilyModules: [memoryRefreshTokenFamilyStoreModule],
|
|
303
311
|
});
|
|
312
|
+
return { switches, modules, config: () => resolveForBoot(own, modules, switches) };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
it("boots with the SMTP sender relaying to Mailpit: in the mailSender slot, with the operator's MFA key, while MFA is on (the configuration's switch); unread, with no mfa section, while it is off", async () => {
|
|
316
|
+
const env: Record<string, string> = { ...containerEnv(), MFA_ENCRYPTION_KEY: OWN_MFA_KEY };
|
|
317
|
+
const { switches, modules, config } = mailpitRun(env);
|
|
318
|
+
const mode = configuredMfaMode(env.CONFIG_ENV as string);
|
|
319
|
+
expect(switches.mfaMode).toBe(mode);
|
|
304
320
|
const names = modules.map((module) => module.name);
|
|
305
321
|
expect(names).toContain(standardSmtpMailSenderModule.name);
|
|
306
322
|
expect(names).not.toContain(
|
|
@@ -310,17 +326,68 @@ describe("the process each development run describes", () => {
|
|
|
310
326
|
const handle = await createApp({
|
|
311
327
|
modules,
|
|
312
328
|
bootstrapComponents: {
|
|
313
|
-
config:
|
|
329
|
+
config: config(),
|
|
314
330
|
pathResolver: (s: string) => s,
|
|
315
331
|
logger: createRecordingLogger(),
|
|
316
332
|
},
|
|
317
333
|
});
|
|
318
334
|
handles.push(handle);
|
|
319
|
-
expect((handle.components.mailSender as MailSender | undefined)?.kind).toBe(
|
|
335
|
+
expect((handle.components.mailSender as MailSender | undefined)?.kind).toBe(
|
|
336
|
+
mode === "off" ? undefined : "standard-smtp",
|
|
337
|
+
);
|
|
338
|
+
const mfa = (
|
|
339
|
+
handle.components.config as {
|
|
340
|
+
mfa?: { mode?: unknown; encryptionKeys?: { key?: unknown }[] };
|
|
341
|
+
}
|
|
342
|
+
).mfa;
|
|
343
|
+
if (mode === "off") {
|
|
344
|
+
expect(mfa).toBeUndefined();
|
|
345
|
+
} else {
|
|
346
|
+
expect(mfa?.mode).toBe(mode);
|
|
347
|
+
expect(mfa?.encryptionKeys?.map((entry) => entry.key)).toEqual([OWN_MFA_KEY]);
|
|
348
|
+
}
|
|
320
349
|
expect(
|
|
321
350
|
(handle.components.config as Record<string, unknown> | undefined)?.[
|
|
322
351
|
"standard-smtp-mail-sender"
|
|
323
352
|
],
|
|
324
353
|
).toMatchObject({ host: "localhost", port: 1025, secure: "none" });
|
|
325
354
|
});
|
|
355
|
+
|
|
356
|
+
it("does not carry the development sample key: with MFA on, without MFA_ENCRYPTION_KEY the run is refused; with it off, nothing reads a key", async () => {
|
|
357
|
+
const env = containerEnv();
|
|
358
|
+
expect(env.MFA_ENCRYPTION_KEY).toBeUndefined();
|
|
359
|
+
const { modules, config } = mailpitRun(env);
|
|
360
|
+
if (configuredMfaMode(env.CONFIG_ENV as string) === "off") {
|
|
361
|
+
const handle = await createApp({
|
|
362
|
+
modules,
|
|
363
|
+
bootstrapComponents: {
|
|
364
|
+
config: config(),
|
|
365
|
+
pathResolver: (s: string) => s,
|
|
366
|
+
logger: createRecordingLogger(),
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
handles.push(handle);
|
|
370
|
+
expect(handle.components.config).not.toHaveProperty("mfa");
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const refused = await (async () => {
|
|
374
|
+
try {
|
|
375
|
+
handles.push(
|
|
376
|
+
await createApp({
|
|
377
|
+
modules,
|
|
378
|
+
bootstrapComponents: {
|
|
379
|
+
config: config(),
|
|
380
|
+
pathResolver: (s: string) => s,
|
|
381
|
+
logger: createRecordingLogger(),
|
|
382
|
+
},
|
|
383
|
+
}),
|
|
384
|
+
);
|
|
385
|
+
} catch (error) {
|
|
386
|
+
return error;
|
|
387
|
+
}
|
|
388
|
+
return undefined;
|
|
389
|
+
})();
|
|
390
|
+
expect(refused).toBeInstanceOf(Error);
|
|
391
|
+
expect(String((refused as Error).message)).toMatch(/mfa\.encryptionKeys/);
|
|
392
|
+
});
|
|
326
393
|
});
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
/**
|
|
18
18
|
* The template's MFA switch under the name production, where the MFA stores
|
|
19
19
|
* must be shared: both on Redis, as a production deployment selects them.
|
|
20
|
-
* ioredis is a stand-in, as in `all-modules-composition.multi.test.mts`:
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* real server.
|
|
20
|
+
* ioredis is a stand-in, as in `all-modules-composition.multi.test.mts`: it
|
|
21
|
+
* answers `INFO memory` as a default server does, so the stores' eviction gate
|
|
22
|
+
* passes, and their persistence check names what it could not read and goes
|
|
23
|
+
* on. `packages/redis` runs the stores against a real server.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import { BootError } from "@o3co/auth-provider-core";
|
|
@@ -36,6 +36,10 @@ vi.mock("ioredis", () => {
|
|
|
36
36
|
quit: async () => "OK",
|
|
37
37
|
disconnect: () => undefined,
|
|
38
38
|
ping: async () => "PONG",
|
|
39
|
+
// The Redis stores' eviction gate reads the policy from INFO memory: a
|
|
40
|
+
// default server's.
|
|
41
|
+
info: async (section: string) =>
|
|
42
|
+
section === "memory" ? "# Memory\r\nmaxmemory_policy:noeviction\r\n" : null,
|
|
39
43
|
};
|
|
40
44
|
const makeMockRedis = (): object =>
|
|
41
45
|
new Proxy(
|