@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.
Files changed (100) hide show
  1. package/README.ja.md +66 -21
  2. package/README.md +76 -31
  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 +59 -29
  12. package/templates/standalone/Dockerfile +6 -2
  13. package/templates/standalone/README.ja.md +324 -169
  14. package/templates/standalone/README.md +618 -208
  15. package/templates/standalone/config/application.conf +211 -352
  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 +418 -0
  22. package/templates/standalone/docker-compose.mailpit.yml +36 -0
  23. package/templates/standalone/docker-compose.production.yml +63 -32
  24. package/templates/standalone/docker-compose.yml +13 -3
  25. package/templates/standalone/gitignore +1 -0
  26. package/templates/standalone/package.json +9 -6
  27. package/templates/standalone/src/__tests__/adapters.test.mts +419 -0
  28. package/templates/standalone/src/__tests__/admission-lifecycle.test.mts +87 -0
  29. package/templates/standalone/src/__tests__/all-modules-composition.fixture.mts +287 -198
  30. package/templates/standalone/src/__tests__/all-modules-composition.multi.test.mts +71 -35
  31. package/templates/standalone/src/__tests__/all-modules-composition.test.mts +148 -94
  32. package/templates/standalone/src/__tests__/audit-sink.test.mts +84 -81
  33. package/templates/standalone/src/__tests__/config-notices.test.mts +154 -0
  34. package/templates/standalone/src/__tests__/configPath.test.mts +13 -11
  35. package/templates/standalone/src/__tests__/core-federations.test.mts +249 -0
  36. package/templates/standalone/src/__tests__/core-sections.test.mts +190 -0
  37. package/templates/standalone/src/__tests__/device-code-store-client-module.test.mts +10 -15
  38. package/templates/standalone/src/__tests__/documented-env-overrides.test.mts +970 -288
  39. package/templates/standalone/src/__tests__/federation-grants-composition.test.mts +421 -86
  40. package/templates/standalone/src/__tests__/federation-grants-survive-logout.test.mts +87 -72
  41. package/templates/standalone/src/__tests__/federation-token-refresh-outage.test.mts +16 -8
  42. package/templates/standalone/src/__tests__/federation-types.test.mts +239 -0
  43. package/templates/standalone/src/__tests__/google-session.fixture.mts +97 -93
  44. package/templates/standalone/src/__tests__/host-routes.test.mts +4 -11
  45. package/templates/standalone/src/__tests__/library-reference-resolution.smoke.test.mts +1 -1
  46. package/templates/standalone/src/__tests__/library-references.fixture.mts +188 -0
  47. package/templates/standalone/src/__tests__/lifecycle-pending-close.test.mts +216 -0
  48. package/templates/standalone/src/__tests__/listen.test.mts +6 -13
  49. package/templates/standalone/src/__tests__/logger-projection.test.mts +1 -1
  50. package/templates/standalone/src/__tests__/mail-sender.test.mts +134 -0
  51. package/templates/standalone/src/__tests__/mailpit-compose.test.mts +326 -0
  52. package/templates/standalone/src/__tests__/metrics.test.mts +4 -9
  53. package/templates/standalone/src/__tests__/mfa-routes-budget.test.mts +122 -0
  54. package/templates/standalone/src/__tests__/mfa-switch.redis.test.mts +123 -0
  55. package/templates/standalone/src/__tests__/mfa-switch.test.mts +825 -0
  56. package/templates/standalone/src/__tests__/own-modules.test.mts +1486 -0
  57. package/templates/standalone/src/__tests__/post-logout-redirect.test.mts +10 -14
  58. package/templates/standalone/src/__tests__/redis-connection-error-log.test.mts +1 -5
  59. package/templates/standalone/src/__tests__/redis-rate-limiter-config-survives.test.mts +116 -81
  60. package/templates/standalone/src/__tests__/refresh-token-family-client-module.test.mts +25 -31
  61. package/templates/standalone/src/__tests__/replica-safety.test.mts +265 -146
  62. package/templates/standalone/src/__tests__/router-error-answers.test.mts +4 -7
  63. package/templates/standalone/src/__tests__/scaffold-assets.test.mts +278 -103
  64. package/templates/standalone/src/__tests__/sections.test.mts +480 -0
  65. package/templates/standalone/src/__tests__/session-lifecycle.test.mts +215 -0
  66. package/templates/standalone/src/__tests__/session-logout-invalidates-token.test.mts +69 -61
  67. package/templates/standalone/src/__tests__/session-logout-lifecycle.test.mts +130 -0
  68. package/templates/standalone/src/__tests__/session-requirements.test.mts +382 -0
  69. package/templates/standalone/src/__tests__/shipped-config-notices.test.mts +270 -0
  70. package/templates/standalone/src/__tests__/shutdown.test.mts +163 -96
  71. package/templates/standalone/src/__tests__/smoke.test.mts +176 -258
  72. package/templates/standalone/src/__tests__/store-credential.test.mts +88 -52
  73. package/templates/standalone/src/__tests__/supertest-loopback.test.mts +182 -11
  74. package/templates/standalone/src/__tests__/three-tier-resolution.test.mts +131 -102
  75. package/templates/standalone/src/__tests__/two-phase-config.test.mts +604 -0
  76. package/templates/standalone/src/__tests__/upstream-amr.acceptance.test.mts +382 -0
  77. package/templates/standalone/src/adapters.mts +162 -0
  78. package/templates/standalone/src/app.mts +65 -79
  79. package/templates/standalone/src/bootRefusal.mts +117 -0
  80. package/templates/standalone/src/buildModules.mts +220 -309
  81. package/templates/standalone/src/configPath.mts +449 -29
  82. package/templates/standalone/src/listen.mts +12 -18
  83. package/templates/standalone/src/logger.mts +31 -60
  84. package/templates/standalone/src/metrics.mts +24 -47
  85. package/templates/standalone/src/mfaSwitch.mts +285 -0
  86. package/templates/standalone/src/modules.mts +423 -629
  87. package/templates/standalone/src/rootRenames.mts +96 -0
  88. package/templates/standalone/src/routes.mts +8 -14
  89. package/templates/standalone/src/sections.mts +367 -0
  90. package/templates/standalone/src/shutdown.mts +45 -123
  91. package/templates/standalone/tests/index.test.js +6 -5
  92. package/templates/standalone/tests/package-lock.json +1615 -2223
  93. package/templates/standalone/tests/package.json +2 -2
  94. package/templates/standalone/tests/vitest.config.mjs +26 -0
  95. package/templates/standalone/vitest.config.mts +8 -7
  96. package/templates/standalone/vitest.supertest-loopback.mts +97 -69
  97. package/templates/versions.json +18 -15
  98. package/templates/standalone/src/__tests__/access-token-lifetime-alias.test.mts +0 -135
  99. package/templates/standalone/src/__tests__/google-federation-config-module.test.mts +0 -199
  100. package/templates/standalone/src/__tests__/oidc-federation-config-module.test.mts +0 -138
@@ -1,199 +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
- * #278: the bridge used to return only `{ clientId, clientSecret, callbackURL }`,
19
- * dropping the four fields the redirect policy reads. Nothing failed — the
20
- * policy was simply constructed with an empty config, and the generated app
21
- * silently ran without a redirect allowlist. These tests fail if the bridge
22
- * stops forwarding any of them.
23
- */
24
-
25
- import type { GoogleProviderConfig } from "@o3co/auth-provider-federation-google";
26
- import { describe, expect, it } from "vitest";
27
- import { googleFederationConfigModule } from "../modules.mjs";
28
-
29
- type ConfigFactory = (deps: { config: unknown }) => GoogleProviderConfig;
30
-
31
- const bridge = (
32
- googleFederationConfigModule as unknown as {
33
- provides: { googleFederationConfig: ConfigFactory };
34
- }
35
- ).provides.googleFederationConfig;
36
-
37
- /** Runs the bridge over a `federations.google` section in the flat shape. */
38
- function buildConfig(googleSection: Record<string, unknown>): GoogleProviderConfig {
39
- return bridge({ config: { federations: { google: { enabled: true, ...googleSection } } } });
40
- }
41
-
42
- const credentials = {
43
- clientId: "id",
44
- clientSecret: "secret",
45
- callbackURL: "https://auth.example.com/session/oauth/federation/google/callback",
46
- };
47
-
48
- describe("googleFederationConfigModule — redirect-policy plumbing (#278)", () => {
49
- it("forwards sessionDomain, authCallbackUrl, clientUrl and redirectAllowlist", () => {
50
- const out = buildConfig({
51
- ...credentials,
52
- sessionDomain: ".example.com",
53
- authCallbackUrl: "https://app.example.com/auth/callback",
54
- clientUrl: "https://app.example.com/",
55
- redirectAllowlist: ["https://app.example.com/dashboard"],
56
- });
57
-
58
- expect(out).toMatchObject({
59
- ...credentials,
60
- sessionDomain: ".example.com",
61
- authCallbackUrl: "https://app.example.com/auth/callback",
62
- clientUrl: "https://app.example.com/",
63
- redirectAllowlist: ["https://app.example.com/dashboard"],
64
- });
65
- });
66
-
67
- it("omits the optional fields when the operator did not configure them", () => {
68
- const out = buildConfig(credentials);
69
-
70
- expect(out).toEqual(credentials);
71
- expect("sessionDomain" in out).toBe(false);
72
- expect("authCallbackUrl" in out).toBe(false);
73
- expect("clientUrl" in out).toBe(false);
74
- expect("redirectAllowlist" in out).toBe(false);
75
- });
76
-
77
- it("rejects a redirectAllowlist that is not an array", () => {
78
- expect(() =>
79
- buildConfig({ ...credentials, redirectAllowlist: "https://app.example.com/dashboard" }),
80
- ).toThrow(/redirectAllowlist/);
81
- });
82
-
83
- it("rejects a redirectAllowlist holding a non-string", () => {
84
- expect(() => buildConfig({ ...credentials, redirectAllowlist: [42] })).toThrow(
85
- /redirectAllowlist/,
86
- );
87
- });
88
-
89
- it("rejects a non-string sessionDomain rather than forwarding it", () => {
90
- expect(() => buildConfig({ ...credentials, sessionDomain: 42 })).toThrow(/sessionDomain/);
91
- });
92
-
93
- it("still rejects a section missing the credentials", () => {
94
- expect(() => buildConfig({ clientId: "id" })).toThrow(/clientId, clientSecret, callbackURL/);
95
- });
96
-
97
- it("reads through the nested federation shape as well as the flat one", () => {
98
- const out = bridge({
99
- config: {
100
- federations: {
101
- google: {
102
- enabled: true,
103
- type: "google",
104
- sessionDomain: ".example.com",
105
- redirectAllowlist: ["https://app.example.com/dashboard"],
106
- google: credentials,
107
- },
108
- },
109
- },
110
- });
111
- expect(out.sessionDomain).toBe(".example.com");
112
- expect(out.redirectAllowlist).toEqual(["https://app.example.com/dashboard"]);
113
- });
114
- });
115
-
116
- describe("googleFederationConfigModule — requireAuthorizationResponseIss (#597)", () => {
117
- it("is absent by default, so the provider's own default (required) applies", () => {
118
- expect("requireAuthorizationResponseIss" in buildConfig(credentials)).toBe(false);
119
- });
120
-
121
- it("forwards the operator's escape hatch", () => {
122
- expect(
123
- buildConfig({ ...credentials, requireAuthorizationResponseIss: false })
124
- .requireAuthorizationResponseIss,
125
- ).toBe(false);
126
- expect(
127
- buildConfig({ ...credentials, requireAuthorizationResponseIss: true })
128
- .requireAuthorizationResponseIss,
129
- ).toBe(true);
130
- });
131
-
132
- it("reads the strings an environment override produces", () => {
133
- expect(
134
- buildConfig({ ...credentials, requireAuthorizationResponseIss: "false" })
135
- .requireAuthorizationResponseIss,
136
- ).toBe(false);
137
- expect(
138
- buildConfig({ ...credentials, requireAuthorizationResponseIss: "true" })
139
- .requireAuthorizationResponseIss,
140
- ).toBe(true);
141
- });
142
-
143
- it("reads the spellings every other env-overridable boolean accepts", () => {
144
- for (const [raw, expected] of [
145
- ["False", false],
146
- [" false ", false],
147
- ["0", false],
148
- ["TRUE", true],
149
- ["1", true],
150
- ] as const) {
151
- expect(
152
- buildConfig({ ...credentials, requireAuthorizationResponseIss: raw })
153
- .requireAuthorizationResponseIss,
154
- ).toBe(expected);
155
- }
156
- });
157
-
158
- it("refuses an empty value, which elsewhere reads as false: exported-but-empty must not switch this check off", () => {
159
- expect(() => buildConfig({ ...credentials, requireAuthorizationResponseIss: "" })).toThrow(
160
- /federations\.google\.requireAuthorizationResponseIss/,
161
- );
162
- });
163
-
164
- it("refuses anything else: a typo must not silently switch the check off, or leave it on", () => {
165
- for (const bad of ["no", "off", 0, 1, []]) {
166
- expect(() => buildConfig({ ...credentials, requireAuthorizationResponseIss: bad })).toThrow(
167
- /federations\.google\.requireAuthorizationResponseIss/,
168
- );
169
- }
170
- });
171
- });
172
-
173
- describe("googleFederationConfigModule — accessType", () => {
174
- // federation-google asks every sign-in for consent with offline access, so
175
- // every session gets a refresh token; "online" is the operator's way to
176
- // keep Google's consent screen to the first sign-in, at the price of no
177
- // refresh token at all. A bridge that drops the field leaves the operator
178
- // without that choice.
179
- it("is absent by default, so the provider's own default (offline) applies", () => {
180
- expect("accessType" in buildConfig(credentials)).toBe(false);
181
- });
182
-
183
- it("leaves a null accessType absent, never forwarded as null, which the provider refuses", () => {
184
- expect("accessType" in buildConfig({ ...credentials, accessType: null })).toBe(false);
185
- });
186
-
187
- it("forwards offline and online", () => {
188
- expect(buildConfig({ ...credentials, accessType: "online" }).accessType).toBe("online");
189
- expect(buildConfig({ ...credentials, accessType: "offline" }).accessType).toBe("offline");
190
- });
191
-
192
- it("refuses anything else at boot, naming the key", () => {
193
- for (const bad of ["", "offine", "Online", "true", false, 1]) {
194
- expect(() => buildConfig({ ...credentials, accessType: bad })).toThrow(
195
- /federations\.google\.accessType/,
196
- );
197
- }
198
- });
199
- });
@@ -1,138 +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
- import type { AppConfig } from "@o3co/auth-provider-core";
18
- import { makeValidAppConfig } from "@o3co/auth-provider-core/testing";
19
- import { describe, expect, it } from "vitest";
20
- import { buildModules } from "../buildModules.mjs";
21
- import { oidcFederationConfigModule } from "../modules.mjs";
22
-
23
- /**
24
- * #524: the scaffold turns every enabled `federations.<name>` of type
25
- * `oidc` into one instance of the generic OIDC federation module, fed by a
26
- * single config bridge. A deployment adds an IdP with configuration only.
27
- */
28
- type Factory = (deps: { config: unknown }) => Record<string, unknown>;
29
-
30
- const bridge = (
31
- oidcFederationConfigModule as unknown as {
32
- provides: { oidcFederationConfigs: Factory };
33
- }
34
- ).provides.oidcFederationConfigs;
35
-
36
- const okta = {
37
- issuer: "https://dev-1.okta.test",
38
- clientId: "okta-client",
39
- clientSecret: "okta-secret",
40
- callbackURL: "https://auth.test/session/oauth/federation/okta/callback",
41
- };
42
-
43
- /** Core's valid fixture, with the federations under test and every adapter on memory. */
44
- const configWith = (federations: Record<string, unknown>): AppConfig => {
45
- const base = makeValidAppConfig();
46
- return {
47
- ...base,
48
- federations,
49
- oauth: { ...base.oauth, code: { adapter: "memory" } },
50
- } as unknown as AppConfig;
51
- };
52
-
53
- describe("oidcFederationConfigModule (#524)", () => {
54
- it("has the scaffold's module name and reads config", () => {
55
- expect(oidcFederationConfigModule.name).toBe("standalone:oidc-federation-config");
56
- expect(oidcFederationConfigModule.requires).toEqual(["config"]);
57
- });
58
-
59
- it("reads every enabled oidc section into the slot, keyed by federation name", () => {
60
- const out = bridge({
61
- config: {
62
- federations: {
63
- okta: { enabled: true, type: "oidc", ...okta },
64
- keycloak: {
65
- enabled: true,
66
- type: "oidc",
67
- clientUrl: "https://app.test/",
68
- oidc: {
69
- issuer: "https://kc.test/realms/dev",
70
- clientId: "kc-client",
71
- clientSecret: "kc-secret",
72
- callbackURL: "https://auth.test/session/oauth/federation/keycloak/callback",
73
- },
74
- },
75
- google: {
76
- enabled: true,
77
- clientId: "g",
78
- clientSecret: "s",
79
- callbackURL: "https://auth.test/g",
80
- },
81
- paused: { enabled: false, type: "oidc", ...okta },
82
- },
83
- },
84
- });
85
- expect(Object.keys(out).sort()).toEqual(["keycloak", "okta"]);
86
- expect(out.okta).toEqual(okta);
87
- expect(out.keycloak).toMatchObject({
88
- issuer: "https://kc.test/realms/dev",
89
- clientUrl: "https://app.test/",
90
- });
91
- });
92
-
93
- it("refuses a malformed section at boot, naming the field", () => {
94
- const { issuer: _dropped, ...noIssuer } = okta;
95
- expect(() =>
96
- bridge({ config: { federations: { okta: { enabled: true, type: "oidc", ...noIssuer } } } }),
97
- ).toThrow(/federations\.okta\.issuer/);
98
- });
99
-
100
- it("is an empty slot when no section is of type oidc", () => {
101
- expect(bridge({ config: { federations: { google: { enabled: false } } } })).toEqual({});
102
- });
103
- });
104
-
105
- describe("buildModules gating for OIDC federations (#524)", () => {
106
- it("lists one federation:oidc:<name> per enabled section, plus the bridge once", () => {
107
- const names = buildModules(
108
- configWith({
109
- okta: { enabled: true, type: "oidc", ...okta },
110
- keycloak: { enabled: true, type: "oidc", ...okta },
111
- google: { enabled: false },
112
- }),
113
- ).map((m) => m.name);
114
- expect(names).toContain("federation:oidc:okta");
115
- expect(names).toContain("federation:oidc:keycloak");
116
- expect(names.filter((n) => n === "standalone:oidc-federation-config")).toHaveLength(1);
117
- expect(names).not.toContain("federation:google");
118
- });
119
-
120
- it("a google section of type oidc is the generic provider, not the built-in Google pair", () => {
121
- // The section's `type` names the implementation. Composing both would
122
- // contribute the same federation and redirect-policy keys twice.
123
- const names = buildModules(
124
- configWith({ google: { enabled: true, type: "oidc", ...okta } }),
125
- ).map((m) => m.name);
126
- expect(names).toContain("federation:oidc:google");
127
- expect(names).not.toContain("federation:google");
128
- expect(names).not.toContain("standalone:google-federation-config");
129
- });
130
-
131
- it("lists nothing OIDC when no section is enabled (the shipped default)", () => {
132
- const names = buildModules(
133
- configWith({ google: { enabled: false }, oidc: { enabled: false, type: "oidc" } }),
134
- ).map((m) => m.name);
135
- expect(names.some((n) => n.startsWith("federation:oidc:"))).toBe(false);
136
- expect(names).not.toContain("standalone:oidc-federation-config");
137
- });
138
- });