@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.
- package/README.md +2 -198
- package/main.js +4 -0
- package/package.json +8 -30
- package/README.ja.md +0 -200
- package/dist/cli.d.mts +0 -3
- package/dist/cli.d.mts.map +0 -1
- package/dist/cli.mjs +0 -18
- package/dist/index.d.mts +0 -48
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs +0 -325
- package/dist/internal/template-filter.d.mts +0 -14
- package/dist/internal/template-filter.d.mts.map +0 -1
- package/dist/internal/template-filter.mjs +0 -42
- package/templates/standalone/.dockerignore +0 -23
- package/templates/standalone/.env.example +0 -152
- package/templates/standalone/Dockerfile +0 -125
- package/templates/standalone/Makefile +0 -12
- package/templates/standalone/README.ja.md +0 -912
- package/templates/standalone/README.md +0 -1536
- package/templates/standalone/config/application.conf +0 -351
- package/templates/standalone/config/clients.yaml +0 -4
- package/templates/standalone/config/clients.yaml.example +0 -124
- package/templates/standalone/config/development.conf +0 -17
- package/templates/standalone/config/mailpit.conf +0 -13
- package/templates/standalone/config/production.conf +0 -15
- package/templates/standalone/config/reference.conf +0 -427
- package/templates/standalone/docker-compose.mailpit.yml +0 -39
- package/templates/standalone/docker-compose.production.yml +0 -229
- package/templates/standalone/docker-compose.test.yml +0 -16
- package/templates/standalone/docker-compose.yml +0 -67
- package/templates/standalone/gitignore +0 -23
- package/templates/standalone/package.json +0 -56
- package/templates/standalone/src/__tests__/adapters.test.mts +0 -425
- package/templates/standalone/src/__tests__/admission-lifecycle.test.mts +0 -87
- package/templates/standalone/src/__tests__/all-modules-composition.fixture.mts +0 -1188
- package/templates/standalone/src/__tests__/all-modules-composition.multi.test.mts +0 -285
- package/templates/standalone/src/__tests__/all-modules-composition.test.mts +0 -1154
- package/templates/standalone/src/__tests__/audit-sink.test.mts +0 -355
- package/templates/standalone/src/__tests__/backchannel-peer.fixture.mts +0 -64
- package/templates/standalone/src/__tests__/config-notices.test.mts +0 -157
- package/templates/standalone/src/__tests__/configPath.test.mts +0 -69
- package/templates/standalone/src/__tests__/configured-mfa-mode.fixture.mts +0 -48
- package/templates/standalone/src/__tests__/core-federations.test.mts +0 -252
- package/templates/standalone/src/__tests__/core-sections.test.mts +0 -192
- package/templates/standalone/src/__tests__/device-code-store-client-module.test.mts +0 -95
- package/templates/standalone/src/__tests__/documented-env-overrides.test.mts +0 -1291
- package/templates/standalone/src/__tests__/federation-grants-composition.test.mts +0 -787
- package/templates/standalone/src/__tests__/federation-grants-survive-logout.test.mts +0 -464
- package/templates/standalone/src/__tests__/federation-token-refresh-outage.test.mts +0 -141
- package/templates/standalone/src/__tests__/federation-types.test.mts +0 -239
- package/templates/standalone/src/__tests__/frontchannel-logout-csp.test.mts +0 -168
- package/templates/standalone/src/__tests__/google-session.fixture.mts +0 -318
- package/templates/standalone/src/__tests__/host-routes.test.mts +0 -134
- package/templates/standalone/src/__tests__/library-reference-resolution.smoke.test.mts +0 -29
- package/templates/standalone/src/__tests__/library-references.fixture.mts +0 -188
- package/templates/standalone/src/__tests__/lifecycle-pending-close.test.mts +0 -217
- package/templates/standalone/src/__tests__/listen.test.mts +0 -121
- package/templates/standalone/src/__tests__/logger-projection.test.mts +0 -77
- package/templates/standalone/src/__tests__/logout-session-cookie.test.mts +0 -113
- package/templates/standalone/src/__tests__/mail-sender.test.mts +0 -134
- package/templates/standalone/src/__tests__/mailpit-compose.test.mts +0 -393
- package/templates/standalone/src/__tests__/metrics.test.mts +0 -253
- package/templates/standalone/src/__tests__/mfa-routes-budget.test.mts +0 -122
- package/templates/standalone/src/__tests__/mfa-switch.redis.test.mts +0 -127
- package/templates/standalone/src/__tests__/mfa-switch.test.mts +0 -883
- package/templates/standalone/src/__tests__/own-modules.test.mts +0 -1480
- package/templates/standalone/src/__tests__/post-logout-redirect.test.mts +0 -183
- package/templates/standalone/src/__tests__/redis-assume-noeviction.test.mts +0 -283
- package/templates/standalone/src/__tests__/redis-connection-error-log.test.mts +0 -135
- package/templates/standalone/src/__tests__/redis-rate-limiter-config-survives.test.mts +0 -214
- package/templates/standalone/src/__tests__/redis-stand-in.fixture.mts +0 -98
- package/templates/standalone/src/__tests__/refresh-token-family-client-module.test.mts +0 -249
- package/templates/standalone/src/__tests__/replica-safety.test.mts +0 -657
- package/templates/standalone/src/__tests__/repositories-logger.test.mts +0 -119
- package/templates/standalone/src/__tests__/router-error-answers.test.mts +0 -146
- package/templates/standalone/src/__tests__/scaffold-assets.test.mts +0 -610
- package/templates/standalone/src/__tests__/sections.test.mts +0 -516
- package/templates/standalone/src/__tests__/session-lifecycle.test.mts +0 -213
- package/templates/standalone/src/__tests__/session-logout-invalidates-token.test.mts +0 -305
- package/templates/standalone/src/__tests__/session-logout-lifecycle.test.mts +0 -130
- package/templates/standalone/src/__tests__/session-requirements.test.mts +0 -382
- package/templates/standalone/src/__tests__/shipped-config-notices.test.mts +0 -274
- package/templates/standalone/src/__tests__/shutdown.test.mts +0 -622
- package/templates/standalone/src/__tests__/smoke.test.mts +0 -642
- package/templates/standalone/src/__tests__/startup-log-lines.test.mts +0 -164
- package/templates/standalone/src/__tests__/store-credential.test.mts +0 -314
- package/templates/standalone/src/__tests__/supertest-loopback.test.mts +0 -296
- package/templates/standalone/src/__tests__/three-tier-resolution.test.mts +0 -265
- package/templates/standalone/src/__tests__/two-phase-config.test.mts +0 -605
- package/templates/standalone/src/__tests__/upstream-amr.acceptance.test.mts +0 -385
- package/templates/standalone/src/adapters.mts +0 -162
- package/templates/standalone/src/app.mts +0 -127
- package/templates/standalone/src/bootRefusal.mts +0 -117
- package/templates/standalone/src/buildModules.mts +0 -358
- package/templates/standalone/src/configPath.mts +0 -487
- package/templates/standalone/src/listen.mts +0 -60
- package/templates/standalone/src/logger.mts +0 -80
- package/templates/standalone/src/metrics.mts +0 -167
- package/templates/standalone/src/mfaSwitch.mts +0 -284
- package/templates/standalone/src/modules.mts +0 -705
- package/templates/standalone/src/rootRenames.mts +0 -95
- package/templates/standalone/src/routes.mts +0 -89
- package/templates/standalone/src/sections.mts +0 -378
- package/templates/standalone/src/securityHeaders.mts +0 -36
- package/templates/standalone/src/shutdown.mts +0 -213
- package/templates/standalone/tests/Makefile +0 -5
- package/templates/standalone/tests/index.test.js +0 -283
- package/templates/standalone/tests/package-lock.json +0 -1617
- package/templates/standalone/tests/package.json +0 -16
- package/templates/standalone/tests/vitest.config.mjs +0 -26
- package/templates/standalone/tsconfig.json +0 -31
- package/templates/standalone/vitest.config.mts +0 -31
- package/templates/standalone/vitest.supertest-loopback.mts +0 -278
- package/templates/versions.json +0 -20
|
@@ -1,117 +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 template's refusals before boot, as core's `BootError`: each under the
|
|
19
|
-
* reason boot raises for the same case, at the stage boot raises it in
|
|
20
|
-
* (`validateManifests`), so that an alert keyed on a boot error's reason sees
|
|
21
|
-
* every refusal to start. The template's checks stay its own; only how a
|
|
22
|
-
* refusal is reported is boot's.
|
|
23
|
-
*
|
|
24
|
-
* The details name a module as boot does. A key of the composition root's
|
|
25
|
-
* own — the `adapters` section, the `mfaMode` switch — is named after itself,
|
|
26
|
-
* as boot names core's own section "core": a module with a section under
|
|
27
|
-
* either name is refused (`resolveForBoot`), so the name points at the key.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
import {
|
|
31
|
-
BootError,
|
|
32
|
-
type ConfigPathRelocatedDetails,
|
|
33
|
-
type ConfigValidationFailedDetails,
|
|
34
|
-
type EnvironmentVariableRenamedDetails,
|
|
35
|
-
} from "@o3co/auth-provider-core";
|
|
36
|
-
|
|
37
|
-
/** A Zod issue as `config-validation-failed` carries one: its path is the path in the configuration. */
|
|
38
|
-
export type ConfigIssue = ConfigValidationFailedDetails["issues"][number];
|
|
39
|
-
|
|
40
|
-
/** The stage boot raises the configuration's refusals in. */
|
|
41
|
-
const STAGE = "validateManifests";
|
|
42
|
-
|
|
43
|
-
/** An issue at `path` of the configuration that no schema raised: what is wrong there, in `message`. */
|
|
44
|
-
export function customIssue(path: readonly string[], message: string): ConfigIssue {
|
|
45
|
-
return { code: "custom", path: [...path], message } as ConfigIssue;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** `issues`, each with `prefix`, the path its schema read at, in front of its own path. */
|
|
49
|
-
export function issuesAt(prefix: readonly string[], issues: readonly ConfigIssue[]): ConfigIssue[] {
|
|
50
|
-
return issues.map((issue) => ({ ...issue, path: [...prefix, ...issue.path] }) as ConfigIssue);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* `config-validation-failed`: `issues` at their paths, refused in the
|
|
55
|
-
* sections `modules` read, each at its `schemaPath`.
|
|
56
|
-
*/
|
|
57
|
-
export function configRefused(
|
|
58
|
-
message: string,
|
|
59
|
-
issues: readonly ConfigIssue[],
|
|
60
|
-
modules: ConfigValidationFailedDetails["modules"],
|
|
61
|
-
): BootError {
|
|
62
|
-
return new BootError({
|
|
63
|
-
message,
|
|
64
|
-
reason: "config-validation-failed",
|
|
65
|
-
stage: STAGE,
|
|
66
|
-
details: { reason: "config-validation-failed", issues: [...issues], modules: [...modules] },
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* `config-validation-failed` for one key, at `path`, of the section `module`
|
|
72
|
-
* reads at its own name: `message` is the refusal and the issue's message.
|
|
73
|
-
*/
|
|
74
|
-
export function keyRefused(message: string, module: string, path: readonly string[]): BootError {
|
|
75
|
-
return configRefused(message, [customIssue(path, message)], [{ module, schemaPath: module }]);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** `config-path-relocated`: each key still set at a path it moved from. */
|
|
79
|
-
export function pathsRelocated(
|
|
80
|
-
message: string,
|
|
81
|
-
relocated: ConfigPathRelocatedDetails["relocated"],
|
|
82
|
-
): BootError {
|
|
83
|
-
return new BootError({
|
|
84
|
-
message,
|
|
85
|
-
reason: "config-path-relocated",
|
|
86
|
-
stage: STAGE,
|
|
87
|
-
details: { reason: "config-path-relocated", relocated: [...relocated] },
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** `environment-variable-renamed`: each old variable name set, with no value. */
|
|
92
|
-
export function variablesRenamed(
|
|
93
|
-
message: string,
|
|
94
|
-
renamed: EnvironmentVariableRenamedDetails["renamed"],
|
|
95
|
-
): BootError {
|
|
96
|
-
return new BootError({
|
|
97
|
-
message,
|
|
98
|
-
reason: "environment-variable-renamed",
|
|
99
|
-
stage: STAGE,
|
|
100
|
-
details: { reason: "environment-variable-renamed", renamed: [...renamed] },
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** `module-section-path-invalid`: `module`'s section is read at `at`, which `problem` says it may not be. */
|
|
105
|
-
export function sectionPathRefused(
|
|
106
|
-
message: string,
|
|
107
|
-
module: string,
|
|
108
|
-
at: string,
|
|
109
|
-
problem: string,
|
|
110
|
-
): BootError {
|
|
111
|
-
return new BootError({
|
|
112
|
-
message,
|
|
113
|
-
reason: "module-section-path-invalid",
|
|
114
|
-
stage: STAGE,
|
|
115
|
-
details: { reason: "module-section-path-invalid", module, at, problem },
|
|
116
|
-
});
|
|
117
|
-
}
|
|
@@ -1,358 +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
|
-
import {
|
|
17
|
-
defaultRefreshTokenFamilyRevocationModule,
|
|
18
|
-
defaultRefreshTokenFamilyRotationModule,
|
|
19
|
-
jwksModule,
|
|
20
|
-
type Module,
|
|
21
|
-
memoryAccessTokenDenylistModule,
|
|
22
|
-
memoryConsentStoreModule,
|
|
23
|
-
memoryFederationGrantIntentStoreModule,
|
|
24
|
-
memoryFederationGrantStoreModule,
|
|
25
|
-
memoryRateLimiterModule,
|
|
26
|
-
memoryReplaySeenSetModule,
|
|
27
|
-
sessionLifecycleModule,
|
|
28
|
-
} from "@o3co/auth-provider-core";
|
|
29
|
-
import { googleFederationTypeModule } from "@o3co/auth-provider-federation-google";
|
|
30
|
-
import { federationGrantsModules } from "@o3co/auth-provider-federation-grants";
|
|
31
|
-
import { oidcFederationTypeModule } from "@o3co/auth-provider-federation-oidc";
|
|
32
|
-
import {
|
|
33
|
-
oauthAuthorizationGrantsModule,
|
|
34
|
-
oauthEndpointsModule,
|
|
35
|
-
oauthSessionGrantModule,
|
|
36
|
-
subjectRevocationServiceModule,
|
|
37
|
-
} from "@o3co/auth-provider-oauth";
|
|
38
|
-
import {
|
|
39
|
-
redisAccessTokenDenylistModule,
|
|
40
|
-
redisAttemptCounterModule,
|
|
41
|
-
redisCodeRepositoryModule,
|
|
42
|
-
redisConsentStoreModule,
|
|
43
|
-
redisFederationGrantIntentStoreModule,
|
|
44
|
-
redisFederationGrantStoreModuleFor,
|
|
45
|
-
redisFederationTokenStoreModuleFor,
|
|
46
|
-
redisRateLimiterModule,
|
|
47
|
-
redisRefreshTokenFamilyStoreModule,
|
|
48
|
-
redisReplaySeenSetModule,
|
|
49
|
-
redisSessionStoresModule,
|
|
50
|
-
} from "@o3co/auth-provider-redis";
|
|
51
|
-
import { sessionModule, sessionStoreModule } from "@o3co/auth-provider-session";
|
|
52
|
-
import {
|
|
53
|
-
standardDevelopmentMailSenderModule,
|
|
54
|
-
standardSmtpMailSenderModule,
|
|
55
|
-
} from "@o3co/auth-provider-standard";
|
|
56
|
-
import type { Switches } from "./configPath.mjs";
|
|
57
|
-
import { mfaModulesFor } from "./mfaSwitch.mjs";
|
|
58
|
-
import {
|
|
59
|
-
auditSinkModuleFor,
|
|
60
|
-
httpModule,
|
|
61
|
-
inMemoryCodeRepositoryModule,
|
|
62
|
-
inMemoryFederationTokenStoreModule,
|
|
63
|
-
inMemorySessionStoresModule,
|
|
64
|
-
keyStoreModule,
|
|
65
|
-
loggingModule,
|
|
66
|
-
repositoriesModuleFor,
|
|
67
|
-
standaloneRedisClientsModule,
|
|
68
|
-
} from "./modules.mjs";
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Overrides for the composition. All but `environment` and
|
|
72
|
-
* `mailSenderModules` are test-only: they let the smoke test substitute
|
|
73
|
-
* in-memory implementations of the file-system-backed modules, and production
|
|
74
|
-
* callers should not pass them — the defaults match the standalone scaffold.
|
|
75
|
-
*/
|
|
76
|
-
export interface BuildModulesOverrides {
|
|
77
|
-
/**
|
|
78
|
-
* The name this deployment selected its configuration by (`CONFIG_ENV ||
|
|
79
|
-
* NODE_ENV`, computed once in `app.mts`), so the Redis federation-token
|
|
80
|
-
* store's `allow-plaintext` guard reads the environment the config came
|
|
81
|
-
* from, not `NODE_ENV` alone. Omitted, the guard falls back to `NODE_ENV`
|
|
82
|
-
* (and the `deploymentMode` slot core fills from `core.deployment.mode`, which it
|
|
83
|
-
* reads either way). Unless `mailSenderModules` is given, it also chooses
|
|
84
|
-
* the mail sender: `development` installs the one that logs each code, any
|
|
85
|
-
* other name the SMTP one, and none is installed when it is omitted. The
|
|
86
|
-
* MFA module reads it too, for the development sample key's refusal.
|
|
87
|
-
*/
|
|
88
|
-
readonly environment?: string;
|
|
89
|
-
readonly keyStoreModule?: Module;
|
|
90
|
-
readonly repositoriesModule?: Module;
|
|
91
|
-
/**
|
|
92
|
-
* Replaces the refresh-token family store modules (default
|
|
93
|
-
* `[redisRefreshTokenFamilyStoreModule]`, on the shared ioredis socket).
|
|
94
|
-
* Tests that must not open an ioredis connection pass
|
|
95
|
-
* `[memoryRefreshTokenFamilyStoreModule]`.
|
|
96
|
-
*/
|
|
97
|
-
readonly refreshTokenFamilyModules?: readonly Module[];
|
|
98
|
-
/**
|
|
99
|
-
* Replaces the module filling the `auditSink` slot (the default writes the
|
|
100
|
-
* trail to stdout); tests substitute a sink they can assert on. It replaces
|
|
101
|
-
* rather than adds, since a second provider would be a boot-time slot
|
|
102
|
-
* collision. There is no way to pass "no audit sink": pass one that discards.
|
|
103
|
-
*/
|
|
104
|
-
readonly auditSinkModule?: Module;
|
|
105
|
-
/**
|
|
106
|
-
* The deployment's own modules behind core's `mailSender` slot. Given, they
|
|
107
|
-
* are installed under every environment name in place of the bundled
|
|
108
|
-
* sender, which is never installed beside them (a second provider would be
|
|
109
|
-
* a boot-time slot collision); an empty list installs no sender, and a
|
|
110
|
-
* module that needs one (the MFA email factor, or the MFA module with
|
|
111
|
-
* `requireEmailProof = "always"`) then refuses the boot.
|
|
112
|
-
*/
|
|
113
|
-
readonly mailSenderModules?: readonly Module[];
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Compose the standalone module list from phase one's `config`: the
|
|
118
|
-
* composition root's `adapters`, which choose the adapter behind each slot,
|
|
119
|
-
* its MFA switch, and whether federation grants are installed. Kept out of
|
|
120
|
-
* `app.mts` so a smoke test can check the manifest (that disabling federation
|
|
121
|
-
* grants removes their modules, say) without an HTTP server. The rules for
|
|
122
|
-
* editing the list (order, one module per store slot, the federation types it
|
|
123
|
-
* bundles) are in the template README, "Module Composition Order".
|
|
124
|
-
*/
|
|
125
|
-
export function buildModules(config: Switches, overrides: BuildModulesOverrides = {}): Module[] {
|
|
126
|
-
const adapters = config.adapters;
|
|
127
|
-
|
|
128
|
-
// Federation grants: a user's standing consent that a client may obtain
|
|
129
|
-
// upstream tokens with no session behind the call. Off by default, and off
|
|
130
|
-
// installs nothing: no store, no socket, no boot requirement. On, the routes
|
|
131
|
-
// and the background registry the shutdown drains come as a pair; each store
|
|
132
|
-
// follows its own selection (grants in Redis with acquisition in memory is
|
|
133
|
-
// a supported single-replica shape); and the subject-revocation service is
|
|
134
|
-
// installed, whose `federationGrantStore` edge makes "revoke everything this
|
|
135
|
-
// subject holds" include the grants. Enabling it also states the deployment
|
|
136
|
-
// has a consent page, a callback per connection and a user repository
|
|
137
|
-
// covering each connection's registration; the routes module refuses at
|
|
138
|
-
// boot what is missing, naming it. Phase one decides it (`readSwitches`): a
|
|
139
|
-
// value boot would refuse installs the feature, so that boot refuses it
|
|
140
|
-
// rather than reading the feature as off.
|
|
141
|
-
const federationGrantsEnabled = config["federation-grants"]?.enabled === true;
|
|
142
|
-
|
|
143
|
-
// MFA: the template's own switch, `mfaMode` (MFA_MODE). `off` installs
|
|
144
|
-
// nothing of MFA; `optional` and `required` install what `mfaModulesFor`
|
|
145
|
-
// lists, the configuration then expects `mfa`
|
|
146
|
-
// (`expectedSessionRequirements`) and `mfa.mode` is written from the
|
|
147
|
-
// switch (`resolveForBoot`).
|
|
148
|
-
const mfaInstalled = config.mfaMode !== "off";
|
|
149
|
-
|
|
150
|
-
const refreshTokenFamilyModules: readonly Module[] = overrides.refreshTokenFamilyModules ?? [
|
|
151
|
-
redisRefreshTokenFamilyStoreModule,
|
|
152
|
-
];
|
|
153
|
-
// Whether the family modules keep the Redis-backed store (the default
|
|
154
|
-
// does). Without the shared clients module added for it, that store fails
|
|
155
|
-
// boot on the missing `refreshTokenFamilyClient` component.
|
|
156
|
-
const refreshTokenFamilyUsesRedis = refreshTokenFamilyModules.some(
|
|
157
|
-
(m) => m.name === "redis-refresh-token-family-store",
|
|
158
|
-
);
|
|
159
|
-
// When ANY selection is `"redis"`, or the refresh-token family modules keep
|
|
160
|
-
// the Redis store (the default), the shared `standaloneRedisClientsModule`
|
|
161
|
-
// is added once, one ioredis socket per replica; memory-only deployments
|
|
162
|
-
// open none.
|
|
163
|
-
const usingRedisAnywhere =
|
|
164
|
-
refreshTokenFamilyUsesRedis ||
|
|
165
|
-
adapters.rateLimiter === "redis" ||
|
|
166
|
-
adapters.attemptCounter === "redis" ||
|
|
167
|
-
adapters.userSessionStores === "redis" ||
|
|
168
|
-
adapters.codeRepository === "redis" ||
|
|
169
|
-
adapters.accessTokenDenylist === "redis" ||
|
|
170
|
-
adapters.replaySeenSet === "redis" ||
|
|
171
|
-
adapters.consentStore === "redis" ||
|
|
172
|
-
adapters.federationTokenStore === "redis" ||
|
|
173
|
-
// Only while the feature is on: a selection left at "redis" for a
|
|
174
|
-
// feature that is off must not open a socket.
|
|
175
|
-
(federationGrantsEnabled &&
|
|
176
|
-
(adapters.federationGrantStore === "redis" ||
|
|
177
|
-
adapters.federationGrantIntentStore === "redis")) ||
|
|
178
|
-
(mfaInstalled &&
|
|
179
|
-
(adapters.mfaFactorStore === "redis" || adapters.mfaTransactionStore === "redis"));
|
|
180
|
-
|
|
181
|
-
// The four user-session stores and the subject-level revocation pair
|
|
182
|
-
// follow `adapters.userSessionStores`; the federation-token store is always
|
|
183
|
-
// wired and follows its own selection below.
|
|
184
|
-
const sessionStoresModules: Module[] =
|
|
185
|
-
adapters.userSessionStores === "redis"
|
|
186
|
-
? [redisSessionStoresModule]
|
|
187
|
-
: [inMemorySessionStoresModule];
|
|
188
|
-
|
|
189
|
-
const rateLimiterModules: Module[] =
|
|
190
|
-
adapters.rateLimiter === "redis" ? [redisRateLimiterModule] : [memoryRateLimiterModule];
|
|
191
|
-
|
|
192
|
-
// `memory` installs no counter: the login's attempt guard counts per
|
|
193
|
-
// process, which `core.deployment.mode = "multi"` refuses.
|
|
194
|
-
const attemptCounterModules: Module[] =
|
|
195
|
-
adapters.attemptCounter === "redis" ? [redisAttemptCounterModule] : [];
|
|
196
|
-
|
|
197
|
-
// Mutually exclusive: both modules provide the `codeRepository` slot, and
|
|
198
|
-
// including both would be a boot-time slot collision.
|
|
199
|
-
const codeRepositoryModules: Module[] =
|
|
200
|
-
adapters.codeRepository === "redis"
|
|
201
|
-
? [redisCodeRepositoryModule]
|
|
202
|
-
: [inMemoryCodeRepositoryModule];
|
|
203
|
-
|
|
204
|
-
// The RFC 7009 access-token denylist has no "none": `oauthEndpointsModule` mounts
|
|
205
|
-
// `/oauth/revoke` and reads the `accessTokenDenylist` slot, and core's boot
|
|
206
|
-
// validator refuses a composition that reads the slot with nothing filling
|
|
207
|
-
// it, since the endpoint would answer 200 while the token kept working. The
|
|
208
|
-
// memory denylist is a dev convenience: it forks per replica, so
|
|
209
|
-
// `core.deployment.mode = "multi"` refuses it by name. The template ships
|
|
210
|
-
// `"redis"`.
|
|
211
|
-
const accessTokenDenylistModules: Module[] =
|
|
212
|
-
adapters.accessTokenDenylist === "redis"
|
|
213
|
-
? [redisAccessTokenDenylistModule]
|
|
214
|
-
: [memoryAccessTokenDenylistModule];
|
|
215
|
-
|
|
216
|
-
// Opt-in: `"none"` (the default) wires nothing, and a client that is not
|
|
217
|
-
// first-party is refused. Each module provides both slots the consent step
|
|
218
|
-
// needs (the consent records and the requests parked while the page asks),
|
|
219
|
-
// so the two cannot be wired apart. The memory one declares itself
|
|
220
|
-
// replica-unsafe and `core.deployment.mode = "multi"` refuses it by name;
|
|
221
|
-
// the Redis one shares both over the ioredis socket.
|
|
222
|
-
const consentStoreModules: Module[] =
|
|
223
|
-
adapters.consentStore === "redis"
|
|
224
|
-
? [redisConsentStoreModule]
|
|
225
|
-
: adapters.consentStore === "memory"
|
|
226
|
-
? [memoryConsentStoreModule]
|
|
227
|
-
: [];
|
|
228
|
-
|
|
229
|
-
// The jti single-use record behind private_key_jwt client authentication.
|
|
230
|
-
const replaySeenSetModules: Module[] =
|
|
231
|
-
adapters.replaySeenSet === "redis" ? [redisReplaySeenSetModule] : [memoryReplaySeenSetModule];
|
|
232
|
-
|
|
233
|
-
// One federation-token-store module per adapter, so the memory one can
|
|
234
|
-
// declare `replicaSafety` on its manifest and the Redis one can `require`
|
|
235
|
-
// its client slot. The Redis module is built for this composition root so
|
|
236
|
-
// its plaintext guard knows which environment selected the config; it
|
|
237
|
-
// reads the replica count from core's `deploymentMode` slot.
|
|
238
|
-
const federationTokenStoreModules: Module[] =
|
|
239
|
-
adapters.federationTokenStore === "redis"
|
|
240
|
-
? [redisFederationTokenStoreModuleFor({ environment: overrides.environment })]
|
|
241
|
-
: [inMemoryFederationTokenStoreModule];
|
|
242
|
-
|
|
243
|
-
// One module per store, nothing while the feature is off or the store is
|
|
244
|
-
// `"none"` (the default), which the routes module refuses at boot naming
|
|
245
|
-
// the store. The Redis grant store is built for this composition root so
|
|
246
|
-
// its plaintext guard knows which environment selected the config, as the
|
|
247
|
-
// federation-token store's is. Both memory modules declare
|
|
248
|
-
// `replicaSafety`, so `core.deployment.mode = "multi"` refuses them by name;
|
|
249
|
-
// the routes module itself refuses a Redis grant store beside memory
|
|
250
|
-
// user-session stores, because the grants would outlive the boundary that
|
|
251
|
-
// ends them.
|
|
252
|
-
const federationGrantStoreModules: Module[] = !federationGrantsEnabled
|
|
253
|
-
? []
|
|
254
|
-
: adapters.federationGrantStore === "redis"
|
|
255
|
-
? [
|
|
256
|
-
overrides.environment === undefined
|
|
257
|
-
? redisFederationGrantStoreModuleFor()
|
|
258
|
-
: redisFederationGrantStoreModuleFor({ environment: overrides.environment }),
|
|
259
|
-
]
|
|
260
|
-
: adapters.federationGrantStore === "memory"
|
|
261
|
-
? [memoryFederationGrantStoreModule]
|
|
262
|
-
: [];
|
|
263
|
-
const federationGrantIntentStoreModules: Module[] = !federationGrantsEnabled
|
|
264
|
-
? []
|
|
265
|
-
: adapters.federationGrantIntentStore === "redis"
|
|
266
|
-
? [redisFederationGrantIntentStoreModule]
|
|
267
|
-
: adapters.federationGrantIntentStore === "memory"
|
|
268
|
-
? [memoryFederationGrantIntentStoreModule]
|
|
269
|
-
: [];
|
|
270
|
-
|
|
271
|
-
// What the MFA switch installs (`mfaSwitch.mts`): nothing while it is off.
|
|
272
|
-
const mfaInstalledModules = mfaModulesFor({
|
|
273
|
-
mode: config.mfaMode,
|
|
274
|
-
adapters,
|
|
275
|
-
storeTransport: config.storeTransport,
|
|
276
|
-
environment: overrides.environment,
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
// The mail sender behind core's `mailSender` slot: the deployment's own when
|
|
280
|
-
// given, otherwise the bundled one for the environment. The development one
|
|
281
|
-
// logs each code and refuses the boot where the configuration or NODE_ENV
|
|
282
|
-
// is production or staging, or the deployment multi-replica.
|
|
283
|
-
const mailSenderModules: readonly Module[] =
|
|
284
|
-
overrides.mailSenderModules ??
|
|
285
|
-
(overrides.environment === undefined
|
|
286
|
-
? []
|
|
287
|
-
: overrides.environment === "development"
|
|
288
|
-
? [standardDevelopmentMailSenderModule({ environment: overrides.environment })]
|
|
289
|
-
: [standardSmtpMailSenderModule]);
|
|
290
|
-
|
|
291
|
-
return [
|
|
292
|
-
// MUST stay first: it declares no `before`/`after`, so this position is
|
|
293
|
-
// what mounts express-session ahead of every session-consuming module.
|
|
294
|
-
// It declares `session-store.storage.type = "memory"` replica-unsafe from
|
|
295
|
-
// its own section, so `core.deployment.mode = "multi"` refuses it.
|
|
296
|
-
sessionStoreModule,
|
|
297
|
-
// Under `/oauth` beside `oauthEndpointsModule`, each parsing its own requests, so
|
|
298
|
-
// their relative order does not matter; the browser half orders itself
|
|
299
|
-
// after the session middleware by its own `after`.
|
|
300
|
-
...(federationGrantsEnabled ? federationGrantsModules : []),
|
|
301
|
-
oauthEndpointsModule,
|
|
302
|
-
oauthSessionGrantModule,
|
|
303
|
-
oauthAuthorizationGrantsModule,
|
|
304
|
-
// Always wired: a provider that signs tokens must publish its
|
|
305
|
-
// verification keys regardless of OIDC issuer config, and `oauthEndpointsModule`'s
|
|
306
|
-
// discovery `jwks_uri` (advertised when an issuer is set) must resolve.
|
|
307
|
-
jwksModule,
|
|
308
|
-
sessionModule,
|
|
309
|
-
// The federation types the template bundles, always: each contributes
|
|
310
|
-
// its type, and boot hands it every enabled `core.federations` entry
|
|
311
|
-
// that names it, under the entry's name. Nothing here reads an entry.
|
|
312
|
-
googleFederationTypeModule(),
|
|
313
|
-
oidcFederationTypeModule(),
|
|
314
|
-
// The template's own settings: `logging {}` and `http {}`.
|
|
315
|
-
loggingModule,
|
|
316
|
-
httpModule,
|
|
317
|
-
overrides.keyStoreModule ?? keyStoreModule,
|
|
318
|
-
overrides.repositoriesModule ??
|
|
319
|
-
repositoriesModuleFor({ client: adapters.clientRepository, user: adapters.userRepository }),
|
|
320
|
-
// The audit sink, always wired: `emitAuditEvent` no-ops on an empty slot,
|
|
321
|
-
// so the routes' security events (`token.issued.failure`,
|
|
322
|
-
// `authorize.rejected`, `rate_limit.unavailable`, …) would go nowhere.
|
|
323
|
-
// Which sink is a selection (`adapters.auditSink`); whether there is one
|
|
324
|
-
// is not.
|
|
325
|
-
overrides.auditSinkModule ?? auditSinkModuleFor(adapters.auditSink),
|
|
326
|
-
...mailSenderModules,
|
|
327
|
-
...(usingRedisAnywhere ? [standaloneRedisClientsModule] : []),
|
|
328
|
-
...federationTokenStoreModules,
|
|
329
|
-
...federationGrantStoreModules,
|
|
330
|
-
...federationGrantIntentStoreModules,
|
|
331
|
-
...sessionStoresModules,
|
|
332
|
-
// Core's session lifecycle, over the session stores' lifecycle store:
|
|
333
|
-
// a login opens its session's record, a code exchange joins its relying
|
|
334
|
-
// party and family to it, and both logout endpoints close it, which
|
|
335
|
-
// revokes the families and tells the relying parties. Its sweep resumes
|
|
336
|
-
// a close left pending.
|
|
337
|
-
sessionLifecycleModule,
|
|
338
|
-
...rateLimiterModules,
|
|
339
|
-
...attemptCounterModules,
|
|
340
|
-
...codeRepositoryModules,
|
|
341
|
-
...accessTokenDenylistModules,
|
|
342
|
-
...replaySeenSetModules,
|
|
343
|
-
...consentStoreModules,
|
|
344
|
-
...refreshTokenFamilyModules,
|
|
345
|
-
defaultRefreshTokenFamilyRotationModule,
|
|
346
|
-
defaultRefreshTokenFamilyRevocationModule,
|
|
347
|
-
// MFA's modules and stores; the MFA routes order themselves after the
|
|
348
|
-
// session middleware.
|
|
349
|
-
...mfaInstalledModules,
|
|
350
|
-
// The composed "end everything this subject holds" a credential change
|
|
351
|
-
// calls, reached as `handle.components.subjectRevocationService`.
|
|
352
|
-
// Installed with federation grants, which is what makes it reach the
|
|
353
|
-
// grants, and with MFA, whose operator reset ends every session and
|
|
354
|
-
// token of the subject's through it; otherwise a Store calls core's
|
|
355
|
-
// revokeAllForSubject.
|
|
356
|
-
...(federationGrantsEnabled || mfaInstalled ? [subjectRevocationServiceModule] : []),
|
|
357
|
-
];
|
|
358
|
-
}
|