@o3co/create-auth-provider 0.17.0 → 0.18.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 (114) hide show
  1. package/README.md +2 -198
  2. package/main.js +4 -0
  3. package/package.json +8 -30
  4. package/README.ja.md +0 -200
  5. package/dist/cli.d.mts +0 -3
  6. package/dist/cli.d.mts.map +0 -1
  7. package/dist/cli.mjs +0 -18
  8. package/dist/index.d.mts +0 -48
  9. package/dist/index.d.mts.map +0 -1
  10. package/dist/index.mjs +0 -325
  11. package/dist/internal/template-filter.d.mts +0 -14
  12. package/dist/internal/template-filter.d.mts.map +0 -1
  13. package/dist/internal/template-filter.mjs +0 -42
  14. package/templates/standalone/.dockerignore +0 -23
  15. package/templates/standalone/.env.example +0 -152
  16. package/templates/standalone/Dockerfile +0 -125
  17. package/templates/standalone/Makefile +0 -12
  18. package/templates/standalone/README.ja.md +0 -912
  19. package/templates/standalone/README.md +0 -1536
  20. package/templates/standalone/config/application.conf +0 -351
  21. package/templates/standalone/config/clients.yaml +0 -4
  22. package/templates/standalone/config/clients.yaml.example +0 -124
  23. package/templates/standalone/config/development.conf +0 -17
  24. package/templates/standalone/config/mailpit.conf +0 -13
  25. package/templates/standalone/config/production.conf +0 -15
  26. package/templates/standalone/config/reference.conf +0 -427
  27. package/templates/standalone/docker-compose.mailpit.yml +0 -39
  28. package/templates/standalone/docker-compose.production.yml +0 -229
  29. package/templates/standalone/docker-compose.test.yml +0 -16
  30. package/templates/standalone/docker-compose.yml +0 -67
  31. package/templates/standalone/gitignore +0 -23
  32. package/templates/standalone/package.json +0 -56
  33. package/templates/standalone/src/__tests__/adapters.test.mts +0 -425
  34. package/templates/standalone/src/__tests__/admission-lifecycle.test.mts +0 -87
  35. package/templates/standalone/src/__tests__/all-modules-composition.fixture.mts +0 -1188
  36. package/templates/standalone/src/__tests__/all-modules-composition.multi.test.mts +0 -285
  37. package/templates/standalone/src/__tests__/all-modules-composition.test.mts +0 -1154
  38. package/templates/standalone/src/__tests__/audit-sink.test.mts +0 -355
  39. package/templates/standalone/src/__tests__/backchannel-peer.fixture.mts +0 -64
  40. package/templates/standalone/src/__tests__/config-notices.test.mts +0 -157
  41. package/templates/standalone/src/__tests__/configPath.test.mts +0 -69
  42. package/templates/standalone/src/__tests__/configured-mfa-mode.fixture.mts +0 -48
  43. package/templates/standalone/src/__tests__/core-federations.test.mts +0 -252
  44. package/templates/standalone/src/__tests__/core-sections.test.mts +0 -192
  45. package/templates/standalone/src/__tests__/device-code-store-client-module.test.mts +0 -95
  46. package/templates/standalone/src/__tests__/documented-env-overrides.test.mts +0 -1291
  47. package/templates/standalone/src/__tests__/federation-grants-composition.test.mts +0 -787
  48. package/templates/standalone/src/__tests__/federation-grants-survive-logout.test.mts +0 -464
  49. package/templates/standalone/src/__tests__/federation-token-refresh-outage.test.mts +0 -141
  50. package/templates/standalone/src/__tests__/federation-types.test.mts +0 -239
  51. package/templates/standalone/src/__tests__/frontchannel-logout-csp.test.mts +0 -168
  52. package/templates/standalone/src/__tests__/google-session.fixture.mts +0 -318
  53. package/templates/standalone/src/__tests__/host-routes.test.mts +0 -134
  54. package/templates/standalone/src/__tests__/library-reference-resolution.smoke.test.mts +0 -29
  55. package/templates/standalone/src/__tests__/library-references.fixture.mts +0 -188
  56. package/templates/standalone/src/__tests__/lifecycle-pending-close.test.mts +0 -217
  57. package/templates/standalone/src/__tests__/listen.test.mts +0 -121
  58. package/templates/standalone/src/__tests__/logger-projection.test.mts +0 -77
  59. package/templates/standalone/src/__tests__/logout-session-cookie.test.mts +0 -113
  60. package/templates/standalone/src/__tests__/mail-sender.test.mts +0 -134
  61. package/templates/standalone/src/__tests__/mailpit-compose.test.mts +0 -393
  62. package/templates/standalone/src/__tests__/metrics.test.mts +0 -253
  63. package/templates/standalone/src/__tests__/mfa-routes-budget.test.mts +0 -122
  64. package/templates/standalone/src/__tests__/mfa-switch.redis.test.mts +0 -127
  65. package/templates/standalone/src/__tests__/mfa-switch.test.mts +0 -883
  66. package/templates/standalone/src/__tests__/own-modules.test.mts +0 -1480
  67. package/templates/standalone/src/__tests__/post-logout-redirect.test.mts +0 -183
  68. package/templates/standalone/src/__tests__/redis-assume-noeviction.test.mts +0 -283
  69. package/templates/standalone/src/__tests__/redis-connection-error-log.test.mts +0 -135
  70. package/templates/standalone/src/__tests__/redis-rate-limiter-config-survives.test.mts +0 -214
  71. package/templates/standalone/src/__tests__/redis-stand-in.fixture.mts +0 -98
  72. package/templates/standalone/src/__tests__/refresh-token-family-client-module.test.mts +0 -249
  73. package/templates/standalone/src/__tests__/replica-safety.test.mts +0 -657
  74. package/templates/standalone/src/__tests__/repositories-logger.test.mts +0 -119
  75. package/templates/standalone/src/__tests__/router-error-answers.test.mts +0 -146
  76. package/templates/standalone/src/__tests__/scaffold-assets.test.mts +0 -610
  77. package/templates/standalone/src/__tests__/sections.test.mts +0 -516
  78. package/templates/standalone/src/__tests__/session-lifecycle.test.mts +0 -213
  79. package/templates/standalone/src/__tests__/session-logout-invalidates-token.test.mts +0 -305
  80. package/templates/standalone/src/__tests__/session-logout-lifecycle.test.mts +0 -130
  81. package/templates/standalone/src/__tests__/session-requirements.test.mts +0 -382
  82. package/templates/standalone/src/__tests__/shipped-config-notices.test.mts +0 -274
  83. package/templates/standalone/src/__tests__/shutdown.test.mts +0 -622
  84. package/templates/standalone/src/__tests__/smoke.test.mts +0 -642
  85. package/templates/standalone/src/__tests__/startup-log-lines.test.mts +0 -164
  86. package/templates/standalone/src/__tests__/store-credential.test.mts +0 -314
  87. package/templates/standalone/src/__tests__/supertest-loopback.test.mts +0 -296
  88. package/templates/standalone/src/__tests__/three-tier-resolution.test.mts +0 -265
  89. package/templates/standalone/src/__tests__/two-phase-config.test.mts +0 -605
  90. package/templates/standalone/src/__tests__/upstream-amr.acceptance.test.mts +0 -385
  91. package/templates/standalone/src/adapters.mts +0 -162
  92. package/templates/standalone/src/app.mts +0 -127
  93. package/templates/standalone/src/bootRefusal.mts +0 -117
  94. package/templates/standalone/src/buildModules.mts +0 -358
  95. package/templates/standalone/src/configPath.mts +0 -487
  96. package/templates/standalone/src/listen.mts +0 -60
  97. package/templates/standalone/src/logger.mts +0 -80
  98. package/templates/standalone/src/metrics.mts +0 -167
  99. package/templates/standalone/src/mfaSwitch.mts +0 -284
  100. package/templates/standalone/src/modules.mts +0 -705
  101. package/templates/standalone/src/rootRenames.mts +0 -95
  102. package/templates/standalone/src/routes.mts +0 -89
  103. package/templates/standalone/src/sections.mts +0 -378
  104. package/templates/standalone/src/securityHeaders.mts +0 -36
  105. package/templates/standalone/src/shutdown.mts +0 -213
  106. package/templates/standalone/tests/Makefile +0 -5
  107. package/templates/standalone/tests/index.test.js +0 -283
  108. package/templates/standalone/tests/package-lock.json +0 -1617
  109. package/templates/standalone/tests/package.json +0 -16
  110. package/templates/standalone/tests/vitest.config.mjs +0 -26
  111. package/templates/standalone/tsconfig.json +0 -31
  112. package/templates/standalone/vitest.config.mts +0 -31
  113. package/templates/standalone/vitest.supertest-loopback.mts +0 -278
  114. package/templates/versions.json +0 -20
@@ -1,285 +0,0 @@
1
- /*
2
- * Copyright 2026 1o1 Co. Ltd.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
-
17
- /**
18
- * The all-modules composition of `all-modules-composition.test.mts` on more
19
- * than one replica: every shared store on Redis, `core.deployment.mode = "multi"`,
20
- * every module the template can turn on switched on together.
21
- *
22
- * With every module on, the all-Redis composition boots with nothing declaring
23
- * replica-unsafe state, and each store switched back to memory is refused at
24
- * boot, naming the module. The Redis adapters change nothing a client can
25
- * see: the same routes, the same discovery document as on one replica.
26
- *
27
- * ioredis, node-redis and connect-redis are stand-ins, as in
28
- * `replica-safety.test.mts`: nothing here issues a command. `packages/redis`
29
- * runs its adapters against a real server.
30
- */
31
-
32
- import { type Module, replicaUnsafeReason } from "@o3co/auth-provider-core";
33
- import * as redisPackage from "@o3co/auth-provider-redis";
34
- import request from "supertest";
35
- import { afterEach, describe, expect, it, vi } from "vitest";
36
- import { readOwnLayers, resolveForBoot } from "#/configPath.mjs";
37
- import { standaloneRedisClientsModule } from "#/modules.mjs";
38
- import {
39
- type Composition,
40
- compose,
41
- composedModules,
42
- MULTI_ENV,
43
- ownFiles,
44
- resolveConfig,
45
- } from "./all-modules-composition.fixture.mjs";
46
-
47
- vi.mock("redis", () => ({
48
- createClient: vi.fn(() => ({
49
- connect: vi.fn().mockResolvedValue(undefined),
50
- quit: vi.fn().mockResolvedValue(undefined),
51
- ping: vi.fn().mockResolvedValue("PONG"),
52
- on: vi.fn(),
53
- })),
54
- }));
55
-
56
- vi.mock("connect-redis", async () => {
57
- const { EventEmitter } = await import("node:events");
58
- // express-session subscribes to store events, so the stand-in is an emitter.
59
- return {
60
- RedisStore: class MockRedisStore extends EventEmitter {
61
- constructor(_opts: { client: unknown }) {
62
- super();
63
- }
64
- get(): unknown {
65
- return undefined;
66
- }
67
- set(): void {}
68
- destroy(): void {}
69
- },
70
- };
71
- });
72
-
73
- vi.mock("ioredis", () => {
74
- // Constructible, quits cleanly, answers the readiness probe's `ping`;
75
- // every other command resolves to nothing.
76
- const explicit: Record<string, unknown> = {
77
- on: () => undefined,
78
- quit: async () => "OK",
79
- disconnect: () => undefined,
80
- ping: async () => "PONG",
81
- // The Redis stores' eviction gate reads the policy from INFO memory: a
82
- // default server's.
83
- info: async (section: string) =>
84
- section === "memory" ? "# Memory\r\nmaxmemory_policy:noeviction\r\n" : null,
85
- };
86
- const makeMockRedis = (): object =>
87
- new Proxy(
88
- {},
89
- {
90
- get(_target, prop) {
91
- // A function-valued `then` would make the instance a thenable.
92
- if (typeof prop !== "string" || prop === "then") return undefined;
93
- if (prop === "duplicate") return makeMockRedis;
94
- if (prop in explicit) return explicit[prop];
95
- return async () => null;
96
- },
97
- },
98
- );
99
- function MockRedis(): object {
100
- return makeMockRedis();
101
- }
102
- return { Redis: MockRedis, default: MockRedis };
103
- });
104
-
105
- let current: Composition | undefined;
106
-
107
- afterEach(async () => {
108
- await current?.handle.dispose();
109
- current = undefined;
110
- });
111
-
112
- const MULTI = { env: MULTI_ENV, shippedRefreshTokenFamilyStore: true } as const;
113
-
114
- /** What `buildModules` lists with every switch on and every shared store on Redis. */
115
- const ALL_ON_REDIS_MODULES = [
116
- "session-store",
117
- "federation-grant-background",
118
- "federation-grants",
119
- "oauth",
120
- "oauth-session",
121
- "oauth-authorization",
122
- "jwks",
123
- "session",
124
- "federation-google-type",
125
- "federation-oidc",
126
- "logging",
127
- "http",
128
- "key-store",
129
- "test:repositories",
130
- "audit-sink",
131
- "standard-smtp-mail-sender",
132
- "redis-clients",
133
- "redis-federation-token-store",
134
- "redis-federation-grant-store",
135
- "redis-federation-grant-intent-store",
136
- "redis-session-stores",
137
- "core-session-lifecycle",
138
- "redis-rate-limiter",
139
- "redis-attempt-counter",
140
- "redis-code-repository",
141
- "redis-access-token-denylist",
142
- "redis-replay-seen-set",
143
- "redis-consent-store",
144
- "redis-refresh-token-family-store",
145
- "core-default-refresh-token-family-rotation",
146
- "core-default-refresh-token-family-revocation",
147
- "subject-revocation-service",
148
- ];
149
-
150
- describe('every module on, every shared store on Redis, core.deployment.mode = "multi"', () => {
151
- it("lists every module, none declaring replica-unsafe state, and boots", async () => {
152
- const own = readOwnLayers(ownFiles(MULTI_ENV), { env: MULTI_ENV });
153
- const config = resolveConfig(MULTI_ENV, own);
154
- const modules = composedModules(config, MULTI);
155
- expect(modules.map((m) => m.name)).toEqual(ALL_ON_REDIS_MODULES);
156
- // Each module's section at its name, as boot is handed it: a
157
- // declaration made from the section is answered for it.
158
- const sections = resolveForBoot(own, modules, config) as unknown as Record<string, unknown>;
159
- for (const module of modules) {
160
- expect(replicaUnsafeReason(module, sections[module.name]), module.name).toBeUndefined();
161
- }
162
- current = await compose(MULTI);
163
- expect(current.handle.readinessProbes.map((p) => p.name)).toContain("redis");
164
- });
165
-
166
- it("mounts the same routes and serves the same discovery document as one replica on memory", async () => {
167
- current = await compose();
168
- const single = {
169
- routes: current.handle.routes.map((r) => r.contribution.id),
170
- discovery: (await request(current.app).get("/.well-known/openid-configuration")).body,
171
- };
172
- await current.handle.dispose();
173
- current = undefined;
174
-
175
- current = await compose(MULTI);
176
- expect(current.handle.routes.map((r) => r.contribution.id)).toEqual(single.routes);
177
- const discovery = await request(current.app).get("/.well-known/openid-configuration");
178
- expect(discovery.status).toBe(200);
179
- expect(discovery.body).toEqual(single.discovery);
180
- });
181
-
182
- it("boots as multi on CORE_DEPLOYMENT_MODE=multi alone", async () => {
183
- expect(MULTI_ENV).toMatchObject({ CORE_DEPLOYMENT_MODE: "multi" });
184
- expect(MULTI_ENV).not.toHaveProperty("DEPLOYMENT_MODE");
185
- current = await compose(MULTI);
186
- expect(current.handle.components.deploymentMode).toBe("multi");
187
- });
188
-
189
- it("refuses DEPLOYMENT_MODE=multi beside CORE_DEPLOYMENT_MODE=multi: the old name must be unset", async () => {
190
- await expect(
191
- compose({
192
- ...MULTI,
193
- env: { ...MULTI_ENV, DEPLOYMENT_MODE: "multi", CORE_DEPLOYMENT_MODE: "multi" },
194
- }),
195
- ).rejects.toMatchObject({
196
- reason: "environment-variable-renamed",
197
- details: { renamed: [{ from: "DEPLOYMENT_MODE", state: "different" }] },
198
- });
199
- });
200
-
201
- /**
202
- * Each store's switch back to memory, alone, with every module on. The
203
- * boot names the module that declared the state it would fork.
204
- */
205
- const MEMORY_SWITCHES: ReadonlyArray<readonly [variable: string, module: string]> = [
206
- ["SESSION_STORE_STORAGE_TYPE", "session-store"],
207
- ["ADAPTERS_USER_SESSION_STORES", "standalone-in-memory-session-stores"],
208
- ["ADAPTERS_CODE_REPOSITORY", "standalone-in-memory-code-repository"],
209
- ["ADAPTERS_FEDERATION_TOKEN_STORE", "standalone-in-memory-federation-token-store"],
210
- ["ADAPTERS_RATE_LIMITER", "core-rate-limiter-memory"],
211
- ["ADAPTERS_ACCESS_TOKEN_DENYLIST", "core-access-token-denylist-memory"],
212
- ["ADAPTERS_REPLAY_SEEN_SET", "core-replay-seen-set-memory"],
213
- ["ADAPTERS_CONSENT_STORE", "core-consent-store-memory"],
214
- ["ADAPTERS_FEDERATION_GRANT_STORE", "core-federation-grant-store-memory"],
215
- ["ADAPTERS_FEDERATION_GRANT_INTENT_STORE", "core-federation-grant-intent-store-memory"],
216
- ];
217
-
218
- it.each(MEMORY_SWITCHES)("%s=memory is refused at boot, naming %s", async (variable, module) => {
219
- await expect(
220
- compose({ ...MULTI, env: { ...MULTI_ENV, [variable]: "memory" } }),
221
- ).rejects.toMatchObject({
222
- name: "BootError",
223
- reason: "replica-unsafe-adapter",
224
- details: { modules: [module] },
225
- });
226
- });
227
-
228
- it("ADAPTERS_ATTEMPT_COUNTER=memory is refused at boot: the login would count its attempts per replica", async () => {
229
- await expect(
230
- compose({ ...MULTI, env: { ...MULTI_ENV, ADAPTERS_ATTEMPT_COUNTER: "memory" } }),
231
- ).rejects.toMatchObject({
232
- name: "BootError",
233
- reason: "contribute-factory-failed",
234
- cause: {
235
- message: expect.stringContaining('no shared attemptCounter is wired for "login"'),
236
- },
237
- });
238
- });
239
-
240
- it("names every memory store together when every switch is memory", async () => {
241
- const env = { ...MULTI_ENV };
242
- for (const [variable] of MEMORY_SWITCHES) env[variable] = "memory";
243
- await expect(compose({ ...MULTI, env })).rejects.toMatchObject({
244
- reason: "replica-unsafe-adapter",
245
- details: {
246
- modules: expect.arrayContaining(MEMORY_SWITCHES.map(([, module]) => module)),
247
- },
248
- });
249
- });
250
- });
251
-
252
- describe("the shared Redis socket can back every store the Redis package ships", () => {
253
- /**
254
- * Every store module `@o3co/auth-provider-redis` exports, the two built per
255
- * composition root included. This template composes some; a deployment adds
256
- * the others to the manifest with the modules that read them (the device
257
- * grant's code store, WebAuthn's challenge store), and each requires its
258
- * client from this module. One it does not provide is a boot refused with
259
- * `missing-required-component`, which `modules.mts` guards against for
260
- * the device-code store by name.
261
- */
262
- const storeModules: Module[] = Object.entries(redisPackage).flatMap(([name, value]) => {
263
- if (name.endsWith("ModuleFor") && typeof value === "function") {
264
- return [(value as (options: object) => Module)({})];
265
- }
266
- if (name.endsWith("Module") && typeof value === "object" && value !== null) {
267
- return [value as Module];
268
- }
269
- return [];
270
- });
271
-
272
- it.each(storeModules.map((m) => [m.name, m] as const))(
273
- "%s: its client slot is provided by redis-clients",
274
- (_name, module) => {
275
- const provided = Object.keys(standaloneRedisClientsModule.provides ?? {});
276
- const clients = (module.requires ?? []).filter((slot) => String(slot).endsWith("Client"));
277
- expect(clients.length).toBeGreaterThan(0);
278
- for (const slot of clients) expect(provided, slot).toContain(slot);
279
- },
280
- );
281
-
282
- it("finds the store modules to check", () => {
283
- expect(storeModules.length).toBeGreaterThanOrEqual(12);
284
- });
285
- });