@o3co/create-auth-provider 0.15.0 → 0.17.0-rc.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.
Files changed (92) hide show
  1. package/README.ja.md +139 -48
  2. package/README.md +138 -47
  3. package/dist/index.d.mts +33 -10
  4. package/dist/index.d.mts.map +1 -1
  5. package/dist/index.mjs +118 -59
  6. package/dist/internal/template-filter.d.mts +7 -10
  7. package/dist/internal/template-filter.d.mts.map +1 -1
  8. package/dist/internal/template-filter.mjs +7 -10
  9. package/package.json +6 -4
  10. package/templates/standalone/.dockerignore +15 -2
  11. package/templates/standalone/.env.example +69 -24
  12. package/templates/standalone/Dockerfile +7 -3
  13. package/templates/standalone/README.ja.md +677 -184
  14. package/templates/standalone/README.md +699 -238
  15. package/templates/standalone/config/application.conf +203 -325
  16. package/templates/standalone/config/clients.yaml +2 -0
  17. package/templates/standalone/config/clients.yaml.example +4 -0
  18. package/templates/standalone/config/development.conf +13 -4
  19. package/templates/standalone/config/mailpit.conf +8 -0
  20. package/templates/standalone/config/production.conf +5 -6
  21. package/templates/standalone/config/reference.conf +399 -0
  22. package/templates/standalone/docker-compose.mailpit.yml +36 -0
  23. package/templates/standalone/docker-compose.production.yml +58 -30
  24. package/templates/standalone/docker-compose.yml +13 -3
  25. package/templates/standalone/gitignore +4 -2
  26. package/templates/standalone/package.json +9 -6
  27. package/templates/standalone/src/__tests__/access-token-lifetime-alias.test.mts +65 -32
  28. package/templates/standalone/src/__tests__/adapters.test.mts +358 -0
  29. package/templates/standalone/src/__tests__/all-modules-composition.fixture.mts +1178 -0
  30. package/templates/standalone/src/__tests__/all-modules-composition.multi.test.mts +256 -0
  31. package/templates/standalone/src/__tests__/all-modules-composition.test.mts +1119 -0
  32. package/templates/standalone/src/__tests__/audit-sink.test.mts +78 -76
  33. package/templates/standalone/src/__tests__/configPath.test.mts +13 -11
  34. package/templates/standalone/src/__tests__/core-federations.test.mts +226 -0
  35. package/templates/standalone/src/__tests__/core-sections.test.mts +190 -0
  36. package/templates/standalone/src/__tests__/device-code-store-client-module.test.mts +9 -14
  37. package/templates/standalone/src/__tests__/documented-env-overrides.test.mts +672 -271
  38. package/templates/standalone/src/__tests__/federation-grants-composition.test.mts +480 -78
  39. package/templates/standalone/src/__tests__/federation-grants-survive-logout.test.mts +102 -66
  40. package/templates/standalone/src/__tests__/federation-token-refresh-outage.test.mts +135 -0
  41. package/templates/standalone/src/__tests__/google-federation-config-module.test.mts +49 -18
  42. package/templates/standalone/src/__tests__/google-session.fixture.mts +315 -0
  43. package/templates/standalone/src/__tests__/host-routes.test.mts +134 -0
  44. package/templates/standalone/src/__tests__/library-references.fixture.mts +183 -0
  45. package/templates/standalone/src/__tests__/listen.test.mts +121 -0
  46. package/templates/standalone/src/__tests__/logger-projection.test.mts +77 -0
  47. package/templates/standalone/src/__tests__/mail-sender.test.mts +134 -0
  48. package/templates/standalone/src/__tests__/mailpit-compose.test.mts +328 -0
  49. package/templates/standalone/src/__tests__/metrics.test.mts +17 -7
  50. package/templates/standalone/src/__tests__/mfa-switch.redis.test.mts +123 -0
  51. package/templates/standalone/src/__tests__/mfa-switch.test.mts +780 -0
  52. package/templates/standalone/src/__tests__/oidc-federation-config-module.test.mts +52 -46
  53. package/templates/standalone/src/__tests__/own-modules.test.mts +1457 -0
  54. package/templates/standalone/src/__tests__/post-logout-redirect.test.mts +183 -0
  55. package/templates/standalone/src/__tests__/redis-connection-error-log.test.mts +135 -0
  56. package/templates/standalone/src/__tests__/redis-rate-limiter-config-survives.test.mts +112 -81
  57. package/templates/standalone/src/__tests__/refresh-token-family-client-module.test.mts +25 -31
  58. package/templates/standalone/src/__tests__/replica-safety.test.mts +166 -145
  59. package/templates/standalone/src/__tests__/router-error-answers.test.mts +146 -0
  60. package/templates/standalone/src/__tests__/scaffold-assets.test.mts +275 -95
  61. package/templates/standalone/src/__tests__/sections.test.mts +480 -0
  62. package/templates/standalone/src/__tests__/session-logout-invalidates-token.test.mts +67 -55
  63. package/templates/standalone/src/__tests__/session-requirements.test.mts +299 -0
  64. package/templates/standalone/src/__tests__/shutdown.test.mts +319 -115
  65. package/templates/standalone/src/__tests__/smoke.test.mts +179 -233
  66. package/templates/standalone/src/__tests__/store-credential.test.mts +317 -0
  67. package/templates/standalone/src/__tests__/supertest-loopback.test.mts +182 -11
  68. package/templates/standalone/src/__tests__/three-tier-resolution.test.mts +108 -95
  69. package/templates/standalone/src/__tests__/two-phase-config.test.mts +438 -0
  70. package/templates/standalone/src/__tests__/upstream-amr.acceptance.test.mts +390 -0
  71. package/templates/standalone/src/adapters.mts +149 -0
  72. package/templates/standalone/src/app.mts +71 -115
  73. package/templates/standalone/src/buildModules.mts +229 -251
  74. package/templates/standalone/src/configPath.mts +422 -29
  75. package/templates/standalone/src/listen.mts +60 -0
  76. package/templates/standalone/src/logger.mts +31 -57
  77. package/templates/standalone/src/metrics.mts +24 -47
  78. package/templates/standalone/src/mfaSwitch.mts +262 -0
  79. package/templates/standalone/src/modules.mts +480 -464
  80. package/templates/standalone/src/rootRenames.mts +79 -0
  81. package/templates/standalone/src/routes.mts +89 -0
  82. package/templates/standalone/src/sections.mts +366 -0
  83. package/templates/standalone/src/shutdown.mts +54 -126
  84. package/templates/standalone/tests/index.test.js +2 -2
  85. package/templates/standalone/tests/package-lock.json +1615 -2223
  86. package/templates/standalone/tests/package.json +2 -2
  87. package/templates/standalone/tests/vitest.config.mjs +26 -0
  88. package/templates/standalone/vitest.config.mts +8 -7
  89. package/templates/standalone/vitest.supertest-loopback.mts +97 -69
  90. package/templates/versions.json +18 -15
  91. package/templates/standalone/src/__tests__/terminal-error.test.mts +0 -96
  92. package/templates/standalone/src/terminalError.mts +0 -60
@@ -16,10 +16,10 @@
16
16
  import type { LifecycleRegistrar } from "@o3co/auth-provider-core";
17
17
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
18
18
 
19
- // D-2 v2: ioredis Redis constructor capture. The module under test imports
19
+ // ioredis Redis constructor capture. The module under test imports
20
20
  // `Redis` from "ioredis"; vi.mock replaces that import so the test never
21
21
  // opens a real socket. Captured arguments verify that the validated config
22
- // reaches the constructor (BLOCKER 1: schema strip closure).
22
+ // reaches the constructor.
23
23
  const redisCtorCalls: Array<{ url: string; options: Record<string, unknown> | undefined }> = [];
24
24
  const quitSpies: Array<ReturnType<typeof vi.fn>> = [];
25
25
  const onSpies: Array<ReturnType<typeof vi.fn>> = [];
@@ -60,17 +60,10 @@ const importModule = async () =>
60
60
  standaloneRedisClientsModule: import("@o3co/auth-provider-core").Module;
61
61
  };
62
62
 
63
- // D-1 / D-5 are already merged on develop, so importing `standaloneRedisClientsModule`
64
- // from `../modules.mjs` is the natural integration point. Pre-fix the export does not
65
- // exist — TS error → RED.
63
+ /** The module's own section, `redis-clients`, as boot hands it over. */
64
+ const baseSection = { url: "redis://example.com:6379", password: "test-pw" };
66
65
 
67
- const baseConfig = {
68
- refreshTokenFamilyStore: {
69
- redis: { url: "redis://example.com:6379", password: "test-pw" },
70
- },
71
- };
72
-
73
- describe("D-2 / standaloneRedisClientsModule", () => {
66
+ describe("standaloneRedisClientsModule", () => {
74
67
  beforeEach(() => {
75
68
  redisCtorCalls.length = 0;
76
69
  quitSpies.length = 0;
@@ -81,14 +74,14 @@ describe("D-2 / standaloneRedisClientsModule", () => {
81
74
  vi.restoreAllMocks();
82
75
  });
83
76
 
84
- it("passes operator-supplied Redis URL + password from config to the ioredis constructor (BLOCKER 1 closure)", async () => {
77
+ it("passes the operator's Redis URL and password from its section to the ioredis constructor", async () => {
85
78
  const { standaloneRedisClientsModule } = await importModule();
86
79
  const provides = (
87
80
  standaloneRedisClientsModule as unknown as {
88
81
  provides: Record<string, (deps: Record<string, unknown>) => Promise<unknown>>;
89
82
  }
90
83
  ).provides;
91
- await provides.refreshTokenFamilyClient({ config: { ...baseConfig } });
84
+ await provides.refreshTokenFamilyClient({ section: { ...baseSection } });
92
85
 
93
86
  expect(redisCtorCalls).toHaveLength(1);
94
87
  expect(redisCtorCalls[0]?.url).toBe("redis://example.com:6379");
@@ -109,7 +102,7 @@ describe("D-2 / standaloneRedisClientsModule", () => {
109
102
  provides: Record<string, (deps: Record<string, unknown>) => Promise<unknown>>;
110
103
  }
111
104
  ).provides;
112
- await provides.refreshTokenFamilyClient({ config: { ...baseConfig }, lifecycleRegistrar });
105
+ await provides.refreshTokenFamilyClient({ section: { ...baseSection }, lifecycleRegistrar });
113
106
 
114
107
  expect(registered).toHaveLength(1);
115
108
  // Pre-drain: quit not yet called.
@@ -133,12 +126,12 @@ describe("D-2 / standaloneRedisClientsModule", () => {
133
126
  // after the factory resolves; the registrar branch is the only place
134
127
  // `quit()` would be wired up in the no-real-shutdown unit-test path).
135
128
  await expect(
136
- provides.refreshTokenFamilyClient({ config: { ...baseConfig } }),
129
+ provides.refreshTokenFamilyClient({ section: { ...baseSection } }),
137
130
  ).resolves.toBeDefined();
138
131
  expect(quitSpies[0]).not.toHaveBeenCalled();
139
132
  });
140
133
 
141
- it("fails fast when refreshTokenFamilyStore.redis.url is missing (no silent localhost fallback)", async () => {
134
+ it("fails fast on an empty redis-clients.url (no silent localhost fallback)", async () => {
142
135
  const { standaloneRedisClientsModule } = await importModule();
143
136
  const provides = (
144
137
  standaloneRedisClientsModule as unknown as {
@@ -146,11 +139,12 @@ describe("D-2 / standaloneRedisClientsModule", () => {
146
139
  }
147
140
  ).provides;
148
141
 
149
- // Operator deliberately removed the section — must throw instead of
150
- // silently falling back to redis://localhost:6379 (which would re-
151
- // introduce the OR-1 multi-replica failure mode in production).
152
- await expect(provides.refreshTokenFamilyClient({ config: {} })).rejects.toThrow(
153
- /refreshTokenFamilyStore\.redis\.url/,
142
+ // An operator who blanked the URL — must throw instead of silently
143
+ // falling back to redis://localhost:6379, which in production would
144
+ // leave each replica with refresh-token families the others cannot
145
+ // see. A section with no URL at all is refused by boot's parse of it.
146
+ await expect(provides.refreshTokenFamilyClient({ section: { url: "" } })).rejects.toThrow(
147
+ /redis-clients\.url/,
154
148
  );
155
149
  // And no ioredis instance was constructed because we threw before
156
150
  // `new Redis(...)`.
@@ -164,12 +158,12 @@ describe("D-2 / standaloneRedisClientsModule", () => {
164
158
  provides: Record<string, (deps: Record<string, unknown>) => Promise<unknown>>;
165
159
  }
166
160
  ).provides;
167
- await provides.refreshTokenFamilyClient({ config: { ...baseConfig } });
161
+ await provides.refreshTokenFamilyClient({ section: { ...baseSection } });
168
162
 
169
163
  expect(onSpies[0]).toHaveBeenCalledWith("error", expect.any(Function));
170
164
  });
171
165
 
172
- it("bounds every command and every connect attempt (#286)", async () => {
166
+ it("bounds every command and every connect attempt", async () => {
173
167
  // Without these the driver's defaults apply: no command timeout at all,
174
168
  // a 10s connect timeout, and 20 reconnect attempts before a queued
175
169
  // command is failed. A partition therefore parks in-flight `/token`
@@ -181,7 +175,7 @@ describe("D-2 / standaloneRedisClientsModule", () => {
181
175
  provides: Record<string, (deps: Record<string, unknown>) => Promise<unknown>>;
182
176
  }
183
177
  ).provides;
184
- await provides.refreshTokenFamilyClient({ config: { ...baseConfig } });
178
+ await provides.refreshTokenFamilyClient({ section: { ...baseSection } });
185
179
 
186
180
  const options = redisCtorCalls[0]?.options;
187
181
  expect(options?.commandTimeout).toBe(1_000);
@@ -193,7 +187,7 @@ describe("D-2 / standaloneRedisClientsModule", () => {
193
187
  expect(options?.lazyConnect).toBe(false);
194
188
  });
195
189
 
196
- it("keeps the offline queue ON, explicitly, because one socket serves every purpose (#286)", async () => {
190
+ it("keeps the offline queue ON, explicitly, because one socket serves every purpose", async () => {
197
191
  // `enableOfflineQueue` is a per-CONNECTION option and every adapter in
198
192
  // this template draws from the single socket built below, so the
199
193
  // "off for the rate limiter, on elsewhere" split is not expressible
@@ -207,18 +201,18 @@ describe("D-2 / standaloneRedisClientsModule", () => {
207
201
  provides: Record<string, (deps: Record<string, unknown>) => Promise<unknown>>;
208
202
  }
209
203
  ).provides;
210
- await provides.refreshTokenFamilyClient({ config: { ...baseConfig } });
204
+ await provides.refreshTokenFamilyClient({ section: { ...baseSection } });
211
205
 
212
206
  expect(redisCtorCalls[0]?.options?.enableOfflineQueue).toBe(true);
213
207
  });
214
208
 
215
- it("declares 'config' as required and both registrars as optional", async () => {
209
+ it("requires no configuration, and takes both registrars as optional", async () => {
216
210
  const { standaloneRedisClientsModule } = await importModule();
217
211
  const m = standaloneRedisClientsModule as {
218
212
  requires?: readonly string[];
219
213
  optional?: readonly string[];
220
214
  };
221
- expect(m.requires).toContain("config");
215
+ expect(m.requires ?? []).not.toContain("config");
222
216
  expect(m.optional).toContain("lifecycleRegistrar");
223
217
  expect(m.optional).toContain("readinessRegistrar");
224
218
  });
@@ -238,12 +232,12 @@ describe("D-2 / standaloneRedisClientsModule", () => {
238
232
  const lifecycleRegistrar: LifecycleRegistrar = { register: () => {} };
239
233
 
240
234
  await provides.refreshTokenFamilyClient({
241
- config: { ...baseConfig },
235
+ section: { ...baseSection },
242
236
  lifecycleRegistrar,
243
237
  readinessRegistrar,
244
238
  });
245
239
  await provides.rateLimiterClient({
246
- config: { ...baseConfig },
240
+ section: { ...baseSection },
247
241
  lifecycleRegistrar,
248
242
  readinessRegistrar,
249
243
  });