@o3co/create-auth-provider 0.4.1 → 0.5.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.
@@ -35,12 +35,12 @@ oauth {
35
35
  }
36
36
  grants {
37
37
  session { enabled = true, enabled = ${?OAUTH_GRANTS_SESSION_ENABLED} }
38
- authorization {
38
+ authorization_code {
39
39
  enabled = true
40
- enabled = ${?OAUTH_GRANTS_AUTHORIZATION_ENABLED}
40
+ enabled = ${?OAUTH_GRANTS_AUTHORIZATION_CODE_ENABLED}
41
41
  pkce {
42
42
  requireS256 = false
43
- requireS256 = ${?OAUTH_GRANTS_AUTHORIZATION_PKCE_REQUIRE_S256}
43
+ requireS256 = ${?OAUTH_GRANTS_AUTHORIZATION_CODE_PKCE_REQUIRE_S256}
44
44
  }
45
45
  }
46
46
  refresh_token { enabled = true, enabled = ${?OAUTH_GRANTS_REFRESH_TOKEN_ENABLED} }
@@ -122,7 +122,10 @@ repositories {
122
122
  }
123
123
 
124
124
  endpoints {
125
- login { url = ${?ENDPOINTS_LOGIN_URL} }
125
+ login {
126
+ url = "/login"
127
+ url = ${?ENDPOINTS_LOGIN_URL}
128
+ }
126
129
  client { url = ${?ENDPOINTS_CLIENT_URL} }
127
130
  authCallback { url = ${?ENDPOINTS_AUTH_CALLBACK_URL} }
128
131
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o3co/auth-provider-standalone",
3
- "version": "0.0.0",
3
+ "version": "0.5.1",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "packageManager": "pnpm@10.29.3",
@@ -23,6 +23,7 @@
23
23
  "@o3co/auth-provider-oauth": "workspace:*",
24
24
  "@o3co/auth-provider-session": "workspace:*",
25
25
  "@o3co/auth-provider-foundation": "workspace:*",
26
+ "@o3co/auth-provider-redis": "workspace:*",
26
27
  "@o3co/auth.utils": "^0.0.3",
27
28
  "@o3co/ts.hocon": "^1.1.0",
28
29
  "connect-redis": "^9.0.0",
@@ -17,26 +17,18 @@
17
17
  import {
18
18
  type AppConfig,
19
19
  createApp,
20
- createFederationTokenStoreFactory,
21
20
  createKeyStoreFactory,
22
- createUserSessionStoreFactory,
21
+ defineModule,
23
22
  generateToken,
24
23
  InMemoryClientRepository,
25
24
  InMemoryCodeRepository,
26
25
  InMemoryUserRepository,
27
- registerBuiltinFederationTokenStores,
28
26
  registerBuiltinKeyStores,
29
- registerBuiltinUserSessionStores,
30
27
  } from "@o3co/auth-provider-core";
31
- import {
32
- oauthAuthorizationModule,
33
- oauthModule,
34
- oauthSessionModule,
35
- } from "@o3co/auth-provider-oauth";
36
- import { sessionModule } from "@o3co/auth-provider-session";
37
28
  import express from "express";
38
29
  import request from "supertest";
39
- import { afterAll, describe, expect, it } from "vitest";
30
+ import { afterEach, describe, expect, it } from "vitest";
31
+ import { buildModules } from "../buildModules.mjs";
40
32
 
41
33
  const config: AppConfig = {
42
34
  http: { port: 0, trustProxy: false },
@@ -76,109 +68,127 @@ const config: AppConfig = {
76
68
  code: { type: "memory", defaultExpiresIn: 600 },
77
69
  },
78
70
  endpoints: {
79
- login: { url: undefined },
71
+ login: { url: "/login" },
80
72
  client: { url: undefined },
81
73
  authCallback: { url: undefined },
82
74
  },
83
75
  cors: { allowedOrigins: [] },
84
76
  };
85
77
 
86
- describe("standalone smoke test", () => {
87
- let grantRegistryRef: Awaited<ReturnType<typeof buildApp>>["grantRegistry"];
88
- let _appRef: ReturnType<typeof express>;
78
+ /**
79
+ * Test-only repository module — bypasses the file-system-backed repositories
80
+ * that `repositoriesModule` provides in production. Smoke tests use in-memory
81
+ * empty repositories to verify the boot pipeline shape, not the data layer.
82
+ */
83
+ const testRepositoriesModule = defineModule({
84
+ name: "test:repositories",
85
+ provides: {
86
+ clientRepository: () => new InMemoryClientRepository(new Map()),
87
+ userRepository: () => new InMemoryUserRepository(new Map()),
88
+ codeRepository: () => new InMemoryCodeRepository(),
89
+ },
90
+ });
89
91
 
90
- async function buildApp() {
91
- const clientRepository = new InMemoryClientRepository(new Map());
92
- const userRepository = new InMemoryUserRepository(new Map());
93
- const codeRepository = new InMemoryCodeRepository();
92
+ const testKeyStoreModule = defineModule({
93
+ name: "test:key-store",
94
+ requires: ["config"] as const,
95
+ provides: {
96
+ keyStore: async ({ config: c }) => {
97
+ const factory = createKeyStoreFactory();
98
+ registerBuiltinKeyStores(factory);
99
+ return factory.create({
100
+ type: "local",
101
+ ...((c as AppConfig).oauth.jwt.signingKey.local ?? {}),
102
+ });
103
+ },
104
+ },
105
+ });
94
106
 
95
- const keyStoreFactory = createKeyStoreFactory();
96
- registerBuiltinKeyStores(keyStoreFactory);
97
- const keyStore = await keyStoreFactory.create({
98
- type: "local",
99
- ...(config.oauth.jwt.signingKey.local ?? {}),
100
- });
107
+ describe("standalone smoke test", () => {
108
+ let handleRef: Awaited<ReturnType<typeof buildApp>>["handle"] | undefined;
101
109
 
102
- const userSessionStoreFactory = createUserSessionStoreFactory();
103
- registerBuiltinUserSessionStores(userSessionStoreFactory);
104
- const userSessionStore = await userSessionStoreFactory.create({ type: "memory" });
105
-
106
- const federationTokenStoreFactory = createFederationTokenStoreFactory();
107
- registerBuiltinFederationTokenStores(federationTokenStoreFactory);
108
- const federationTokenStore = await federationTokenStoreFactory.create({ type: "memory" });
109
-
110
- const { init, router, grantRegistry } = createApp({
111
- express,
112
- config,
113
- keyStore,
114
- userSessionStore,
115
- federationTokenStore,
116
- modules: [
117
- oauthModule({ clientRepository, codeRepository, express }),
118
- sessionModule({ userRepository, express }),
119
- oauthSessionModule({ clientRepository }),
120
- oauthAuthorizationModule({ codeRepository, clientRepository }),
121
- ],
110
+ async function buildApp() {
111
+ const handle = await createApp({
112
+ modules: buildModules(config, {
113
+ keyStoreModule: testKeyStoreModule,
114
+ repositoriesModule: testRepositoriesModule,
115
+ }),
116
+ bootstrapComponents: { config, pathResolver: (s) => s },
122
117
  });
123
118
 
124
- await init();
125
-
126
119
  const app = express();
127
- app.use(router);
128
-
129
- return { app, grantRegistry };
120
+ app.get("/_healthcheck", (_req, res) => {
121
+ res.status(200).json({ status: "ok" });
122
+ });
123
+ app.use(handle.router);
124
+ return { app, handle };
130
125
  }
131
126
 
132
- afterAll(async () => {
133
- await grantRegistryRef?.cleanup();
127
+ afterEach(async () => {
128
+ await handleRef?.dispose();
129
+ handleRef = undefined;
134
130
  });
135
131
 
136
132
  it("GET /_healthcheck returns 200", async () => {
137
- const { app, grantRegistry } = await buildApp();
138
- grantRegistryRef = grantRegistry;
139
- _appRef = app;
140
-
133
+ const { app, handle } = await buildApp();
134
+ handleRef = handle;
141
135
  const res = await request(app).get("/_healthcheck");
142
136
  expect(res.status).toBe(200);
143
137
  });
144
138
 
145
139
  it("POST /oauth/token with unsupported grant_type returns 400", async () => {
146
- const { app, grantRegistry } = await buildApp();
147
- grantRegistryRef = grantRegistry;
148
- _appRef = app;
149
-
140
+ const { app, handle } = await buildApp();
141
+ handleRef = handle;
150
142
  const res = await request(app)
151
143
  .post("/oauth/token")
152
144
  .type("form")
153
145
  .send({ grant_type: "unsupported" });
154
-
155
146
  expect(res.status).toBe(400);
156
147
  });
157
148
 
158
- it("POST /oauth/token successful response has Cache-Control: no-store", async () => {
159
- // We cannot easily trigger a full token issuance without a session,
160
- // so we verify the header via a session grant with an authenticated session.
161
- // Instead, test that 400 responses do NOT have Cache-Control (only successes do).
162
- // The Cache-Control header is set only when "tokens" in result — covered by
163
- // the introspect test below which exercises the full response path.
164
- const { app, grantRegistry } = await buildApp();
165
- grantRegistryRef = grantRegistry;
166
- _appRef = app;
167
-
168
- // 400 responses must NOT have Cache-Control: no-store
149
+ it("POST /oauth/token 400 responses do NOT have Cache-Control: no-store", async () => {
150
+ const { app, handle } = await buildApp();
151
+ handleRef = handle;
169
152
  const res = await request(app)
170
153
  .post("/oauth/token")
171
154
  .type("form")
172
155
  .send({ grant_type: "unsupported" });
173
-
174
156
  expect(res.status).toBe(400);
175
157
  expect(res.headers["cache-control"]).not.toBe("no-store");
176
158
  });
177
159
 
160
+ // Regression guard: a freshly scaffolded standalone app must boot under
161
+ // the default `federations.google.enabled = false`. The earlier shape of
162
+ // app.mts unconditionally listed `googleFederationModule` and the config
163
+ // bridge `googleFederationConfigModule`, so the bridge's
164
+ // `extractFederationSection` returned undefined and threw at boot. The
165
+ // fix moved the manifest assembly into `buildModules`, which conditionally
166
+ // includes the federation pair. The first sub-assertion asserts directly
167
+ // on the manifest list — a regression that bypasses the gate fails the
168
+ // `not.toContain` even if `googleFederationConfigModule` is later made
169
+ // tolerant of `undefined` (which would otherwise mask the gating bug).
170
+ // The second sub-assertion verifies the resulting handle still boots
171
+ // (Codex Round 1 P1 reproducibility).
172
+ it("boots when google federation is disabled (default scaffold config)", async () => {
173
+ const modules = buildModules(config, {
174
+ keyStoreModule: testKeyStoreModule,
175
+ repositoriesModule: testRepositoriesModule,
176
+ });
177
+ const moduleNames = modules.map((m) => m.name);
178
+ expect(moduleNames).not.toContain("federation:google");
179
+ expect(moduleNames).not.toContain("standalone:google-federation-config");
180
+
181
+ const handle = await createApp({
182
+ modules,
183
+ bootstrapComponents: { config, pathResolver: (s) => s },
184
+ });
185
+ handleRef = handle;
186
+ expect(handle).toBeDefined();
187
+ });
188
+
178
189
  it("POST /oauth/introspect returns iat in active token response", async () => {
179
- const { app, grantRegistry } = await buildApp();
180
- grantRegistryRef = grantRegistry;
181
- _appRef = app;
190
+ const { app, handle } = await buildApp();
191
+ handleRef = handle;
182
192
 
183
193
  const ksf = createKeyStoreFactory();
184
194
  registerBuiltinKeyStores(ksf);
@@ -13,34 +13,12 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import path from "node:path";
17
16
  import { fileURLToPath } from "node:url";
18
17
  import { gracefulShutdown } from "@o3co/auth.utils";
18
+ import { type AppConfig, AppConfigSchema, createApp } from "@o3co/auth-provider-core";
19
19
  import {
20
- type AppConfig,
21
- AppConfigSchema,
22
- createApp,
23
- createDefaultFactories,
24
- createFederationTokenStoreFactory,
25
- createKeyStoreFactory,
26
- createUserSessionStoreFactory,
27
- registerBuiltinFederationTokenStores,
28
- registerBuiltinKeyStores,
29
- registerBuiltinUserSessionStores,
30
- } from "@o3co/auth-provider-core";
31
- import { registerGoogleFederation } from "@o3co/auth-provider-federation-google";
32
- // import { registerGithubFederation } from "@o3co/auth-provider-federation-github";
33
- import { registerBuiltinAdapters } from "@o3co/auth-provider-foundation";
34
- import {
35
- oauthAuthorizationModule,
36
- oauthModule,
37
- oauthSessionModule,
38
- } from "@o3co/auth-provider-oauth";
39
- import {
40
- createFederationProviderFactory,
41
20
  createSessionStoreFactory,
42
21
  registerBuiltinSessionStores,
43
- sessionModule,
44
22
  } from "@o3co/auth-provider-session";
45
23
  import { parseFile } from "@o3co/ts.hocon";
46
24
  import { validate } from "@o3co/ts.hocon/zod";
@@ -49,12 +27,11 @@ import session from "express-session";
49
27
  import helmet from "helmet";
50
28
 
51
29
  import logger from "#/logger.mjs";
30
+ import { buildModules } from "./buildModules.mjs";
52
31
  import { resolveConfigPaths } from "./configPath.mjs";
53
32
 
54
33
  // Step 1: Load and validate application config (HOCON → Zod schema).
55
- // Layering: {ENV}.conf overrides application.conf.
56
- // ENV = CONFIG_ENV || NODE_ENV || "development". A missing {ENV}.conf is a
57
- // boot-time error (fail-fast on typos or unconfigured environments).
34
+ // ENV = CONFIG_ENV || NODE_ENV || "development"; missing {ENV}.conf is fatal.
58
35
  const env = process.env.CONFIG_ENV || process.env.NODE_ENV || "development";
59
36
  const configDir = new URL("../config/", import.meta.url);
60
37
  const configDirPath = fileURLToPath(configDir);
@@ -64,47 +41,9 @@ const config: AppConfig = validate(
64
41
  AppConfigSchema,
65
42
  );
66
43
 
67
- const flattenAdapterConfig = (
68
- section: ({ type: string } | { provider: string }) & Record<string, unknown>,
69
- ): { type: string } & Record<string, unknown> => {
70
- const selector =
71
- (section as { type?: string; provider?: string }).type ??
72
- (section as { provider?: string }).provider;
73
- if (typeof selector !== "string") {
74
- throw new TypeError("flattenAdapterConfig: section requires 'type' or 'provider' string");
75
- }
76
- const sub = section[selector];
77
- const flattenedSub =
78
- typeof sub === "object" && sub !== null && !Array.isArray(sub)
79
- ? (sub as Record<string, unknown>)
80
- : {};
81
- return { type: selector, ...flattenedSub };
82
- };
83
-
84
44
  await (async (): Promise<void> => {
85
- // Step 2: Build repository factories and register built-in adapters (memory / file / etc.).
86
- const appDir = path.dirname(fileURLToPath(import.meta.url));
87
- const { clientFactory, userFactory, codeFactory } = createDefaultFactories();
88
- registerBuiltinAdapters({ userFactory, codeFactory, pathResolver: import.meta.resolve });
89
-
90
- // Step 3: Instantiate client / user / code repositories from config.
91
- const clientConfig = flattenAdapterConfig(
92
- config.repositories.client as { type: string } & Record<string, unknown>,
93
- );
94
- if (typeof clientConfig.path === "string") {
95
- clientConfig.path = path.resolve(appDir, "..", clientConfig.path);
96
- }
97
- const clientRepository = await clientFactory.create(clientConfig);
98
- const userRepository = await userFactory.create(
99
- flattenAdapterConfig(config.repositories.user as { type: string } & Record<string, unknown>),
100
- );
101
- const codeRepository = await codeFactory.create(
102
- flattenAdapterConfig(config.repositories.code as { type: string } & Record<string, unknown>),
103
- );
104
-
105
- // Step 4: Create the Express app and apply base security middleware (trust proxy + helmet).
45
+ // Step 2: Create the Express app and apply base security middleware.
106
46
  const app = express();
107
-
108
47
  app.set("trust proxy", config.http.trustProxy);
109
48
  app.use(
110
49
  helmet({
@@ -117,19 +56,24 @@ await (async (): Promise<void> => {
117
56
  }),
118
57
  );
119
58
 
120
- // Step 5: Build the session store from config and mount express-session middleware.
59
+ // Step 3: Build the Express session store and mount express-session.
60
+ // (express-session is host-environment middleware, not part of the auth
61
+ // boot pipeline — wired before createApp so it sits ahead of the router.)
121
62
  const sessionStoreFactory = createSessionStoreFactory();
122
63
  registerBuiltinSessionStores(sessionStoreFactory);
123
- const store = await sessionStoreFactory.create(
124
- flattenAdapterConfig(config.session.storage as { type: string } & Record<string, unknown>),
125
- );
126
-
64
+ const sessionStorageSlice = config.session.storage as {
65
+ type: string;
66
+ } & Record<string, unknown>;
67
+ const sessionStore = await sessionStoreFactory.create({
68
+ type: sessionStorageSlice.type,
69
+ ...((sessionStorageSlice[sessionStorageSlice.type] ?? {}) as Record<string, unknown>),
70
+ });
127
71
  app.use(
128
72
  session({
129
73
  secret: config.session.secret,
130
74
  resave: false,
131
75
  saveUninitialized: false,
132
- store,
76
+ store: sessionStore,
133
77
  cookie: {
134
78
  path: "/",
135
79
  httpOnly: true,
@@ -141,61 +85,34 @@ await (async (): Promise<void> => {
141
85
  }),
142
86
  );
143
87
 
144
- // Step 6: Build the JWT signing KeyStore used to issue access / ID tokens.
145
- const keyStoreFactory = createKeyStoreFactory();
146
- registerBuiltinKeyStores(keyStoreFactory);
147
- const keyStore = await keyStoreFactory.create(flattenAdapterConfig(config.oauth.jwt.signingKey));
148
-
149
- // Step 7: Build UserSessionStore and FederationTokenStore (required by sessionModule).
150
- const userSessionStoreFactory = createUserSessionStoreFactory();
151
- registerBuiltinUserSessionStores(userSessionStoreFactory);
152
- const userSessionStore = await userSessionStoreFactory.create(
153
- flattenAdapterConfig(
154
- (config as { userSessionStore?: { type: string } & Record<string, unknown> })
155
- .userSessionStore ?? { type: "memory" },
156
- ),
157
- );
158
-
159
- const federationTokenStoreFactory = createFederationTokenStoreFactory();
160
- registerBuiltinFederationTokenStores(federationTokenStoreFactory);
161
- const federationTokenStore = await federationTokenStoreFactory.create(
162
- flattenAdapterConfig(
163
- (config as { federationTokenStore?: { type: string } & Record<string, unknown> })
164
- .federationTokenStore ?? { type: "memory" },
165
- ),
166
- );
167
-
168
- const federationProviderFactory = createFederationProviderFactory();
169
- registerGoogleFederation(federationProviderFactory);
170
- // registerGithubFederation(federationProviderFactory);
171
-
172
- // Step 8: Compose the OAuth / session modules and build the app router.
173
- const { init, router, grantRegistry } = createApp({
174
- express,
175
- pathResolver: import.meta.resolve,
176
- config,
177
- keyStore,
178
- userSessionStore,
179
- federationTokenStore,
180
- modules: [
181
- oauthModule({ clientRepository, codeRepository, express }),
182
- sessionModule({ userRepository, express, federationProviderFactory }),
183
- oauthSessionModule({ clientRepository }),
184
- oauthAuthorizationModule({ codeRepository, clientRepository }),
185
- ],
88
+ // Step 4: Boot the auth pipeline. bootstrapComponents carries only host-
89
+ // environment values (config + pathResolver per A2-γ §4 worked example);
90
+ // every other component flows through composition-root-local modules.
91
+ // `buildModules` is the single source of truth for the module list — it
92
+ // gates federation modules on `config.federations.<name>.enabled`, which
93
+ // the standalone scaffold defaults to false.
94
+ const handle = await createApp({
95
+ modules: buildModules(config),
96
+ bootstrapComponents: {
97
+ config,
98
+ pathResolver: import.meta.resolve,
99
+ },
186
100
  });
187
101
 
188
- // Step 9: Run async module init (resolves external deps via pathResolver).
189
- await init();
190
-
191
- // Step 10: Mount the composed router onto the Express app.
192
- app.use(router);
102
+ // Step 5: Wire host-level routes (healthcheck) before the auth router so
103
+ // they remain reachable even when the auth pipeline is degraded.
104
+ app.get("/_healthcheck", (_req, res) => {
105
+ res.status(200).json({ status: "ok" });
106
+ });
193
107
 
194
- // Step 11: Start the HTTP server.
108
+ // Step 6: Mount the composed auth router and start the HTTP server.
109
+ app.use(handle.router);
195
110
  const server = app.listen(config.http.port, (): void => {
196
111
  logger.info(`Server is running on http://localhost:${config.http.port}`);
197
112
  });
198
113
 
199
- // Step 12: Register graceful shutdown so in-flight grants are cleaned up on SIGTERM / SIGINT.
200
- gracefulShutdown(server, () => grantRegistry.cleanup());
114
+ // Step 7: Graceful shutdown — handle.dispose() runs reverse-topological
115
+ // per-component cleanup (per A2-β §8.1). Replaces the v0.4.x
116
+ // grantRegistry.cleanup() bridge.
117
+ gracefulShutdown(server, () => handle.dispose());
201
118
  })();
@@ -0,0 +1,78 @@
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
+ import {
17
+ type AppConfig,
18
+ defaultRefreshTokenFamilyRevocationModule,
19
+ defaultRefreshTokenFamilyRotationModule,
20
+ type Module,
21
+ memoryRefreshTokenFamilyStoreModule,
22
+ } from "@o3co/auth-provider-core";
23
+ import { googleFederationModule } from "@o3co/auth-provider-federation-google";
24
+ import {
25
+ oauthAuthorizationModule,
26
+ oauthModule,
27
+ oauthSessionModule,
28
+ } from "@o3co/auth-provider-oauth";
29
+ import { sessionModule } from "@o3co/auth-provider-session";
30
+ import {
31
+ googleFederationConfigModule,
32
+ keyStoreModule,
33
+ repositoriesModule,
34
+ storesModule,
35
+ } from "./modules.mjs";
36
+
37
+ /**
38
+ * Test-only overrides allowing the smoke test to substitute in-memory
39
+ * implementations of the file-system-backed modules. Production callers
40
+ * should not pass overrides; the defaults match the standalone scaffold.
41
+ */
42
+ export interface BuildModulesOverrides {
43
+ readonly keyStoreModule?: Module;
44
+ readonly repositoriesModule?: Module;
45
+ readonly storesModule?: Module;
46
+ }
47
+
48
+ /**
49
+ * Compose the standalone v0.5.0 module list from `config`. Splitting this
50
+ * out of `app.mts` keeps the composition root testable: a smoke test can
51
+ * verify that disabling a federation removes its module pair from the
52
+ * manifest without spinning up a full HTTP server.
53
+ *
54
+ * Federation gating: `googleFederationModule` requires `googleFederationConfig`,
55
+ * which `googleFederationConfigModule` produces by reading
56
+ * `config.federations.google`. When google is disabled (or the section is
57
+ * absent), the config-bridge module's provider throws — so the entire pair
58
+ * MUST be conditionally included at composition time, not gated inside the
59
+ * provider.
60
+ */
61
+ export function buildModules(config: AppConfig, overrides: BuildModulesOverrides = {}): Module[] {
62
+ const googleEnabled =
63
+ (config.federations?.google as { enabled?: boolean } | undefined)?.enabled === true;
64
+
65
+ return [
66
+ oauthModule({ config }),
67
+ oauthSessionModule({ config }),
68
+ oauthAuthorizationModule({ config }),
69
+ sessionModule,
70
+ ...(googleEnabled ? [googleFederationModule, googleFederationConfigModule] : []),
71
+ overrides.keyStoreModule ?? keyStoreModule,
72
+ overrides.repositoriesModule ?? repositoriesModule,
73
+ overrides.storesModule ?? storesModule,
74
+ memoryRefreshTokenFamilyStoreModule,
75
+ defaultRefreshTokenFamilyRotationModule,
76
+ defaultRefreshTokenFamilyRevocationModule,
77
+ ];
78
+ }