@o3co/create-auth-provider 0.16.0 → 0.17.0-rc.2
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 +66 -21
- package/README.md +76 -31
- package/dist/index.d.mts +33 -10
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +118 -59
- package/dist/internal/template-filter.d.mts +7 -10
- package/dist/internal/template-filter.d.mts.map +1 -1
- package/dist/internal/template-filter.mjs +7 -10
- package/package.json +6 -4
- package/templates/standalone/.dockerignore +15 -2
- package/templates/standalone/.env.example +59 -29
- package/templates/standalone/Dockerfile +6 -2
- package/templates/standalone/README.ja.md +324 -169
- package/templates/standalone/README.md +618 -208
- package/templates/standalone/config/application.conf +211 -352
- package/templates/standalone/config/clients.yaml +2 -0
- package/templates/standalone/config/clients.yaml.example +4 -0
- package/templates/standalone/config/development.conf +13 -4
- package/templates/standalone/config/mailpit.conf +8 -0
- package/templates/standalone/config/production.conf +5 -6
- package/templates/standalone/config/reference.conf +418 -0
- package/templates/standalone/docker-compose.mailpit.yml +36 -0
- package/templates/standalone/docker-compose.production.yml +63 -32
- package/templates/standalone/docker-compose.yml +13 -3
- package/templates/standalone/gitignore +1 -0
- package/templates/standalone/package.json +9 -6
- package/templates/standalone/src/__tests__/adapters.test.mts +419 -0
- package/templates/standalone/src/__tests__/admission-lifecycle.test.mts +87 -0
- package/templates/standalone/src/__tests__/all-modules-composition.fixture.mts +287 -198
- package/templates/standalone/src/__tests__/all-modules-composition.multi.test.mts +71 -35
- package/templates/standalone/src/__tests__/all-modules-composition.test.mts +148 -94
- package/templates/standalone/src/__tests__/audit-sink.test.mts +84 -81
- package/templates/standalone/src/__tests__/config-notices.test.mts +154 -0
- package/templates/standalone/src/__tests__/configPath.test.mts +13 -11
- package/templates/standalone/src/__tests__/core-federations.test.mts +249 -0
- package/templates/standalone/src/__tests__/core-sections.test.mts +190 -0
- package/templates/standalone/src/__tests__/device-code-store-client-module.test.mts +10 -15
- package/templates/standalone/src/__tests__/documented-env-overrides.test.mts +970 -288
- package/templates/standalone/src/__tests__/federation-grants-composition.test.mts +421 -86
- package/templates/standalone/src/__tests__/federation-grants-survive-logout.test.mts +87 -72
- package/templates/standalone/src/__tests__/federation-token-refresh-outage.test.mts +16 -8
- package/templates/standalone/src/__tests__/federation-types.test.mts +239 -0
- package/templates/standalone/src/__tests__/google-session.fixture.mts +97 -93
- package/templates/standalone/src/__tests__/host-routes.test.mts +4 -11
- package/templates/standalone/src/__tests__/library-reference-resolution.smoke.test.mts +1 -1
- package/templates/standalone/src/__tests__/library-references.fixture.mts +188 -0
- package/templates/standalone/src/__tests__/lifecycle-pending-close.test.mts +216 -0
- package/templates/standalone/src/__tests__/listen.test.mts +6 -13
- package/templates/standalone/src/__tests__/logger-projection.test.mts +1 -1
- package/templates/standalone/src/__tests__/mail-sender.test.mts +134 -0
- package/templates/standalone/src/__tests__/mailpit-compose.test.mts +326 -0
- package/templates/standalone/src/__tests__/metrics.test.mts +4 -9
- package/templates/standalone/src/__tests__/mfa-routes-budget.test.mts +122 -0
- package/templates/standalone/src/__tests__/mfa-switch.redis.test.mts +123 -0
- package/templates/standalone/src/__tests__/mfa-switch.test.mts +825 -0
- package/templates/standalone/src/__tests__/own-modules.test.mts +1486 -0
- package/templates/standalone/src/__tests__/post-logout-redirect.test.mts +10 -14
- package/templates/standalone/src/__tests__/redis-connection-error-log.test.mts +1 -5
- package/templates/standalone/src/__tests__/redis-rate-limiter-config-survives.test.mts +116 -81
- package/templates/standalone/src/__tests__/refresh-token-family-client-module.test.mts +25 -31
- package/templates/standalone/src/__tests__/replica-safety.test.mts +265 -146
- package/templates/standalone/src/__tests__/router-error-answers.test.mts +4 -7
- package/templates/standalone/src/__tests__/scaffold-assets.test.mts +278 -103
- package/templates/standalone/src/__tests__/sections.test.mts +480 -0
- package/templates/standalone/src/__tests__/session-lifecycle.test.mts +215 -0
- package/templates/standalone/src/__tests__/session-logout-invalidates-token.test.mts +69 -61
- package/templates/standalone/src/__tests__/session-logout-lifecycle.test.mts +130 -0
- package/templates/standalone/src/__tests__/session-requirements.test.mts +382 -0
- package/templates/standalone/src/__tests__/shipped-config-notices.test.mts +270 -0
- package/templates/standalone/src/__tests__/shutdown.test.mts +163 -96
- package/templates/standalone/src/__tests__/smoke.test.mts +176 -258
- package/templates/standalone/src/__tests__/store-credential.test.mts +88 -52
- package/templates/standalone/src/__tests__/supertest-loopback.test.mts +182 -11
- package/templates/standalone/src/__tests__/three-tier-resolution.test.mts +131 -102
- package/templates/standalone/src/__tests__/two-phase-config.test.mts +604 -0
- package/templates/standalone/src/__tests__/upstream-amr.acceptance.test.mts +382 -0
- package/templates/standalone/src/adapters.mts +162 -0
- package/templates/standalone/src/app.mts +65 -79
- package/templates/standalone/src/bootRefusal.mts +117 -0
- package/templates/standalone/src/buildModules.mts +220 -309
- package/templates/standalone/src/configPath.mts +449 -29
- package/templates/standalone/src/listen.mts +12 -18
- package/templates/standalone/src/logger.mts +31 -60
- package/templates/standalone/src/metrics.mts +24 -47
- package/templates/standalone/src/mfaSwitch.mts +285 -0
- package/templates/standalone/src/modules.mts +423 -629
- package/templates/standalone/src/rootRenames.mts +96 -0
- package/templates/standalone/src/routes.mts +8 -14
- package/templates/standalone/src/sections.mts +367 -0
- package/templates/standalone/src/shutdown.mts +45 -123
- package/templates/standalone/tests/index.test.js +6 -5
- package/templates/standalone/tests/package-lock.json +1615 -2223
- package/templates/standalone/tests/package.json +2 -2
- package/templates/standalone/tests/vitest.config.mjs +26 -0
- package/templates/standalone/vitest.config.mts +8 -7
- package/templates/standalone/vitest.supertest-loopback.mts +97 -69
- package/templates/versions.json +18 -15
- package/templates/standalone/src/__tests__/access-token-lifetime-alias.test.mts +0 -135
- package/templates/standalone/src/__tests__/google-federation-config-module.test.mts +0 -199
- package/templates/standalone/src/__tests__/oidc-federation-config-module.test.mts +0 -138
|
@@ -15,94 +15,127 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
|
-
import {
|
|
18
|
+
import type { AppConfig } from "@o3co/auth-provider-core";
|
|
19
|
+
import { type OAuthSection, oauthEndpointsModule } from "@o3co/auth-provider-oauth";
|
|
19
20
|
import { parseFile } from "@o3co/ts.hocon";
|
|
20
|
-
import { validate } from "@o3co/ts.hocon/zod";
|
|
21
21
|
import { describe, expect, it } from "vitest";
|
|
22
|
+
import { readAdapters } from "../adapters.mjs";
|
|
22
23
|
import { buildModules } from "../buildModules.mjs";
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
readOwnLayers,
|
|
26
|
+
readSwitches,
|
|
27
|
+
resolveConfigPaths,
|
|
28
|
+
resolveForBoot,
|
|
29
|
+
type Switches,
|
|
30
|
+
} from "../configPath.mjs";
|
|
31
|
+
import { templateReference } from "../modules.mjs";
|
|
32
|
+
import { type BothPhases, withSwitches } from "./library-references.fixture.mjs";
|
|
24
33
|
|
|
25
34
|
// config/ is two levels above this test file:
|
|
26
35
|
// src/__tests__/ → src/ → standalone/ → config/
|
|
27
36
|
const configDir = fileURLToPath(new URL("../../config", import.meta.url));
|
|
28
37
|
|
|
29
|
-
// Provide
|
|
30
|
-
// test-only values — no real keys are embedded here.
|
|
38
|
+
// Provide the secrets the template's files require. These are
|
|
39
|
+
// test-only values — no real keys are embedded here. SESSION_STORE_SECRET
|
|
31
40
|
// carries a 256-bit entropy floor, so these clear it (the '.' characters keep
|
|
32
41
|
// them outside the base64 alphabet, so the UTF-8 length is what counts).
|
|
33
42
|
const testEnv = {
|
|
34
|
-
|
|
43
|
+
KEY_STORE_LOCAL_SECRET: "test-secret-three-tier.at-least-32-bytes.ok",
|
|
35
44
|
OAUTH_JWT_ISSUER: "https://auth.test",
|
|
36
|
-
|
|
45
|
+
SESSION_STORE_SECRET: "test-session-secret-three-tier.at-least-32-bytes.ok",
|
|
37
46
|
};
|
|
38
47
|
|
|
39
|
-
|
|
48
|
+
/**
|
|
49
|
+
* The three tiers under `env`, as `app.mts` reads them: what phase one reads
|
|
50
|
+
* of the template's own layers, laid over what phase two hands boot — the
|
|
51
|
+
* own layers over the reference of every package the chosen modules come
|
|
52
|
+
* from, resolved and unparsed.
|
|
53
|
+
*/
|
|
54
|
+
function buildResolvedConfig(env: string, extraEnv: Record<string, string> = {}): BothPhases {
|
|
40
55
|
const { applicationConfPath, envConfPath } = resolveConfigPaths(configDir, env);
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
const own = readOwnLayers([envConfPath, applicationConfPath], {
|
|
57
|
+
env: { ...testEnv, ...extraEnv },
|
|
58
|
+
});
|
|
59
|
+
const switches = readSwitches(own);
|
|
60
|
+
return withSwitches(
|
|
61
|
+
resolveForBoot(own, buildModules(switches), switches) as unknown as Record<string, unknown>,
|
|
62
|
+
switches,
|
|
48
63
|
);
|
|
49
64
|
}
|
|
50
65
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
/** `oauth.resourceIndicator` as the section's owner, the oauth module, parses it at boot. */
|
|
67
|
+
function parsedResourceIndicator(config: AppConfig): { readonly enabled?: unknown } | undefined {
|
|
68
|
+
const schema = oauthEndpointsModule.section?.schema;
|
|
69
|
+
if (schema === undefined) throw new Error("the oauth module declares no section");
|
|
70
|
+
return (schema.parse(config.oauth) as { resourceIndicator?: { enabled?: unknown } })
|
|
71
|
+
.resourceIndicator;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// The oauth-authorization module's section as resolved, unparsed: the
|
|
75
|
+
// runtime shape is a string from env substitution, a boolean from a literal.
|
|
56
76
|
type GrantEntry = { enabled?: unknown };
|
|
57
77
|
const grants = (config: AppConfig) =>
|
|
58
|
-
(config
|
|
78
|
+
((config["oauth-authorization"] as { grants?: unknown } | undefined)?.grants as
|
|
79
|
+
| Record<string, GrantEntry | undefined>
|
|
80
|
+
| undefined) ?? {};
|
|
81
|
+
|
|
82
|
+
/** `oauth {}` as the resolution leaves it, read as the shape the oauth module parses. */
|
|
83
|
+
const oauthOf = (config: AppConfig): OAuthSection => config.oauth as OAuthSection;
|
|
59
84
|
|
|
60
85
|
describe("three-tier HOCON resolution (env → application.conf → reference.conf)", () => {
|
|
61
86
|
it("template application.conf wins over reference.conf for grant.enabled", () => {
|
|
62
87
|
const config = buildResolvedConfig("development");
|
|
63
|
-
// Template's application.conf sets
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
expect(grants(config).authorization_code?.enabled).toBe(true);
|
|
88
|
+
// Template's application.conf sets authorizationCode.enabled = true,
|
|
89
|
+
// so the resolved value is true whatever reference.conf says.
|
|
90
|
+
expect(grants(config).authorizationCode?.enabled).toBe(true);
|
|
67
91
|
});
|
|
68
92
|
|
|
69
93
|
it("reference.conf default reaches resolved config when template omits the key", () => {
|
|
70
94
|
const config = buildResolvedConfig("development");
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
expect(config.
|
|
95
|
+
// nonce.maxLength is library-owned in both layers (template doesn't
|
|
96
|
+
// override it). Verifies precedence falls through.
|
|
97
|
+
expect(oauthOf(config).nonce?.maxLength).toBe(256);
|
|
74
98
|
});
|
|
75
99
|
|
|
76
100
|
it("env var at template layer can disable a template-enabled grant (precedence: env-override line must be repeated)", () => {
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
// Note: ts.hocon substitutes env vars as strings, and `oauth.grants` is
|
|
83
|
-
// `z.object({}).passthrough()` — an open tree each grant module's own
|
|
84
|
-
// `configSchema` validates — so no shape-walking coercion reaches this
|
|
85
|
-
// leaf and the string survives to the resolved config. #288 unified the
|
|
86
|
-
// coercion for every DECLARED boolean in the schema and deliberately
|
|
87
|
-
// left this one alone: every consumer of a grant's `enabled`
|
|
88
|
-
// (`GrantRegistry.isEnabled`, `oauthAuthorization`, `oauthSession`)
|
|
89
|
-
// already reads `true` and `"true"` as enabled and everything else,
|
|
90
|
-
// `"false"` included, as not-enabled.
|
|
101
|
+
// The env-override line is repeated at the template layer alongside
|
|
102
|
+
// `enabled = true`; without it, the package reference's substitution is
|
|
103
|
+
// shadowed by the template's literal `true`. The value stays a string
|
|
104
|
+
// until boot: the module reads the switch with its own schema, `"false"`
|
|
105
|
+
// as off.
|
|
91
106
|
const config = buildResolvedConfig("development", {
|
|
92
|
-
|
|
107
|
+
OAUTH_AUTHORIZATION_GRANTS_AUTHORIZATION_CODE_ENABLED: "false",
|
|
93
108
|
});
|
|
94
|
-
expect(grants(config).
|
|
109
|
+
expect(grants(config).authorizationCode?.enabled).toBe("false");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("reads the MFA switch, mfaMode, as required where MFA_MODE is unset, and installs no MFA module under MFA_MODE=off", () => {
|
|
113
|
+
// The template's reference.conf ships the switch required and binds MFA_MODE.
|
|
114
|
+
const mfaNames = (config: Switches) =>
|
|
115
|
+
buildModules(config)
|
|
116
|
+
.map((m) => m.name)
|
|
117
|
+
.filter((name) => /mfa/i.test(name));
|
|
118
|
+
const shipped = buildResolvedConfig("development");
|
|
119
|
+
expect(shipped.mfaMode).toBe("required");
|
|
120
|
+
expect(mfaNames(shipped)).toContain("mfa");
|
|
121
|
+
const off = buildResolvedConfig("development", { MFA_MODE: "off" });
|
|
122
|
+
expect(off.mfaMode).toBe("off");
|
|
123
|
+
expect(mfaNames(off)).toEqual([]);
|
|
124
|
+
const optional = buildResolvedConfig("development", { MFA_MODE: "optional" });
|
|
125
|
+
expect(optional.mfaMode).toBe("optional");
|
|
126
|
+
expect(mfaNames(optional)).toContain("mfa");
|
|
95
127
|
});
|
|
96
128
|
|
|
97
|
-
it("reference.conf
|
|
129
|
+
it("core's reference.conf ships no rateLimit.failMode: it is the Redis limiter's own key", () => {
|
|
98
130
|
const config = buildResolvedConfig("development");
|
|
99
|
-
expect(config
|
|
131
|
+
expect(config).not.toHaveProperty("rateLimit.failMode");
|
|
100
132
|
});
|
|
101
133
|
|
|
102
|
-
it("
|
|
103
|
-
//
|
|
134
|
+
it("template application.conf ships clientCredentials.enabled off", () => {
|
|
135
|
+
// The template writes every grant switch it reads before boot, so the
|
|
136
|
+
// value phase one reads is the one boot parses.
|
|
104
137
|
const config = buildResolvedConfig("development");
|
|
105
|
-
expect(grants(config).
|
|
138
|
+
expect(grants(config).clientCredentials?.enabled).toBe(false);
|
|
106
139
|
});
|
|
107
140
|
|
|
108
141
|
it("reference.conf default for oauth.resourceIndicator.enabled is false", () => {
|
|
@@ -110,7 +143,8 @@ describe("three-tier HOCON resolution (env → application.conf → reference.co
|
|
|
110
143
|
// schema coercion (coerceBooleanFromEnv) can produce a boolean value.
|
|
111
144
|
// Without the HOCON block the field resolves to undefined.
|
|
112
145
|
const config = buildResolvedConfig("development");
|
|
113
|
-
expect(config.
|
|
146
|
+
expect(oauthOf(config).resourceIndicator?.enabled).toBe(false);
|
|
147
|
+
expect(parsedResourceIndicator(config)?.enabled).toBe(false);
|
|
114
148
|
});
|
|
115
149
|
|
|
116
150
|
it("env var OAUTH_RESOURCE_INDICATOR_ENABLED=true reaches resolved config and coerces to boolean true", () => {
|
|
@@ -121,20 +155,17 @@ describe("three-tier HOCON resolution (env → application.conf → reference.co
|
|
|
121
155
|
const config = buildResolvedConfig("development", {
|
|
122
156
|
OAUTH_RESOURCE_INDICATOR_ENABLED: "true",
|
|
123
157
|
});
|
|
124
|
-
expect(config.
|
|
158
|
+
expect(oauthOf(config).resourceIndicator?.enabled).toBe("true");
|
|
159
|
+
expect(parsedResourceIndicator(config)?.enabled).toBe(true);
|
|
125
160
|
});
|
|
126
161
|
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// looking effective. These assertions run the real three-tier merge, so they
|
|
133
|
-
// fail if either layer stops carrying the key.
|
|
134
|
-
describe("#287: the audit sink as the shipped artifact resolves it", () => {
|
|
135
|
-
it("resolves audit.sink.type to the template's logger sink with nothing set", () => {
|
|
162
|
+
// The selection is the composition root's own, `adapters.auditSink`,
|
|
163
|
+
// which phase one reads over the template's own layers. These assertions
|
|
164
|
+
// run the real merge, so they fail if a layer stops carrying the key.
|
|
165
|
+
describe("the audit sink as the shipped artifact resolves it", () => {
|
|
166
|
+
it("resolves adapters.auditSink to the template's logger sink with nothing set", () => {
|
|
136
167
|
const config = buildResolvedConfig("production");
|
|
137
|
-
expect(config.
|
|
168
|
+
expect(config.adapters.auditSink).toBe("logger");
|
|
138
169
|
});
|
|
139
170
|
|
|
140
171
|
it("wires exactly one auditSink provider for that resolved config", () => {
|
|
@@ -144,41 +175,35 @@ describe("three-tier HOCON resolution (env → application.conf → reference.co
|
|
|
144
175
|
});
|
|
145
176
|
|
|
146
177
|
it("lets an operator select core's built-in console sink by env var", () => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// same precedence rule the grant tests above pin.
|
|
150
|
-
const config = buildResolvedConfig("production", { AUDIT_SINK_TYPE: "console" });
|
|
151
|
-
expect(config.audit?.sink.type).toBe("console");
|
|
178
|
+
const config = buildResolvedConfig("production", { ADAPTERS_AUDIT_SINK: "console" });
|
|
179
|
+
expect(config.adapters.auditSink).toBe("console");
|
|
152
180
|
});
|
|
153
181
|
|
|
154
|
-
it("reference.conf
|
|
155
|
-
// A
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
AppConfigSchema,
|
|
182
|
+
it("the template's reference.conf alone selects a sink, not a drop", () => {
|
|
183
|
+
// A composition resolved against the template's reference alone
|
|
184
|
+
// still lands on a sink: an application.conf that forgets the
|
|
185
|
+
// selection must not thereby lose its audit trail.
|
|
186
|
+
const referenceOnly = parseFile(fileURLToPath(templateReference()), { env: {} });
|
|
187
|
+
expect(readAdapters(referenceOnly.toObject() as Record<string, unknown>, {}).auditSink).toBe(
|
|
188
|
+
"logger",
|
|
162
189
|
);
|
|
163
|
-
expect(referenceOnly.audit?.sink.type).toBe("console");
|
|
164
190
|
});
|
|
165
191
|
});
|
|
166
192
|
|
|
167
|
-
//
|
|
168
|
-
// application.conf, `
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
it("resolves accessTokenDenylist.adapter to redis with nothing set", () => {
|
|
193
|
+
// The shape the umbrella E2E (o3co/auth) boots: the shipped
|
|
194
|
+
// application.conf, `CORE_DEPLOYMENT_MODE=multi`, and one shared ioredis socket.
|
|
195
|
+
// A memory denylist under `multi` is refused by the replica-safety guard,
|
|
196
|
+
// and NO denylist by core's denylist boot guard, so the template has to
|
|
197
|
+
// land on "redis" without the deployment naming it.
|
|
198
|
+
describe("access-token revocation as the shipped artifact resolves it", () => {
|
|
199
|
+
it("resolves adapters.accessTokenDenylist to redis with nothing set", () => {
|
|
175
200
|
const config = buildResolvedConfig("production");
|
|
176
|
-
expect(config.accessTokenDenylist
|
|
201
|
+
expect(config.adapters.accessTokenDenylist).toBe("redis");
|
|
177
202
|
});
|
|
178
203
|
|
|
179
204
|
it("resolves oauth.revocation.accessToken to denylist with nothing set", () => {
|
|
180
205
|
const config = buildResolvedConfig("production");
|
|
181
|
-
expect(config.
|
|
206
|
+
expect(oauthOf(config).revocation?.accessToken).toBe("denylist");
|
|
182
207
|
});
|
|
183
208
|
|
|
184
209
|
it("picks the replica-safe denylist module for that resolved config", () => {
|
|
@@ -189,35 +214,39 @@ describe("three-tier HOCON resolution (env → application.conf → reference.co
|
|
|
189
214
|
|
|
190
215
|
it("leaves nothing replica-unsafe under the umbrella E2E's environment", async () => {
|
|
191
216
|
// The environment `o3co/auth`'s tests/docker-compose.yml sets. Under
|
|
192
|
-
// `
|
|
193
|
-
// every in-memory shared store, so the denylist
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
// — which no name list in core covers — count too.
|
|
217
|
+
// `CORE_DEPLOYMENT_MODE=multi` the replica-safety guard fails boot naming
|
|
218
|
+
// every in-memory shared store, so the denylist has to come out as
|
|
219
|
+
// the Redis one from the template's own config: the compose file
|
|
220
|
+
// names no denylist variable. Asked of each manifest, as the guard
|
|
221
|
+
// does, so a rename cannot quietly invalidate it and the template's
|
|
222
|
+
// own memory modules count too.
|
|
199
223
|
const { replicaUnsafeReason } = await import("@o3co/auth-provider-core");
|
|
200
224
|
const config = buildResolvedConfig("production", {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
|
|
225
|
+
ADAPTERS_USER_SESSION_STORES: "redis",
|
|
226
|
+
ADAPTERS_RATE_LIMITER: "redis",
|
|
227
|
+
ADAPTERS_CODE_REPOSITORY: "redis",
|
|
228
|
+
// The federation token store defaults to memory, and the memory
|
|
229
|
+
// module declares itself replica-unsafe.
|
|
230
|
+
ADAPTERS_FEDERATION_TOKEN_STORE: "redis",
|
|
207
231
|
REDIS_FEDERATION_TOKEN_STORE_ENCRYPTION_KEY: "BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc=",
|
|
232
|
+
// The umbrella sets MFA off.
|
|
233
|
+
MFA_MODE: "off",
|
|
208
234
|
});
|
|
209
235
|
const modules = buildModules(config);
|
|
210
236
|
expect(modules.map((m) => m.name)).toContain("redis-access-token-denylist");
|
|
237
|
+
// Each module's section at its name: a declaration made from the
|
|
238
|
+
// section is answered for it.
|
|
239
|
+
const sections = config as unknown as Record<string, unknown>;
|
|
211
240
|
for (const module of modules) {
|
|
212
|
-
expect(replicaUnsafeReason(module), module.name).toBeUndefined();
|
|
241
|
+
expect(replicaUnsafeReason(module, sections[module.name]), module.name).toBeUndefined();
|
|
213
242
|
}
|
|
214
243
|
});
|
|
215
244
|
|
|
216
245
|
it("lets a single-instance deployment opt down to memory by env var", () => {
|
|
217
246
|
const config = buildResolvedConfig("production", {
|
|
218
|
-
|
|
247
|
+
ADAPTERS_ACCESS_TOKEN_DENYLIST: "memory",
|
|
219
248
|
});
|
|
220
|
-
expect(config.accessTokenDenylist
|
|
249
|
+
expect(config.adapters.accessTokenDenylist).toBe("memory");
|
|
221
250
|
const names = buildModules(config).map((m) => m.name);
|
|
222
251
|
expect(names).toContain("core-access-token-denylist-memory");
|
|
223
252
|
});
|
|
@@ -226,7 +255,7 @@ describe("three-tier HOCON resolution (env → application.conf → reference.co
|
|
|
226
255
|
const config = buildResolvedConfig("production", {
|
|
227
256
|
OAUTH_REVOCATION_ACCESS_TOKEN: "unsupported",
|
|
228
257
|
});
|
|
229
|
-
expect(config.
|
|
258
|
+
expect(oauthOf(config).revocation?.accessToken).toBe("unsupported");
|
|
230
259
|
});
|
|
231
260
|
});
|
|
232
261
|
});
|