@o3co/create-auth-provider 0.15.0 → 0.17.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.ja.md +139 -48
  2. package/README.md +138 -47
  3. package/dist/index.d.mts +33 -10
  4. package/dist/index.d.mts.map +1 -1
  5. package/dist/index.mjs +118 -59
  6. package/dist/internal/template-filter.d.mts +7 -10
  7. package/dist/internal/template-filter.d.mts.map +1 -1
  8. package/dist/internal/template-filter.mjs +7 -10
  9. package/package.json +6 -4
  10. package/templates/standalone/.dockerignore +15 -2
  11. package/templates/standalone/.env.example +69 -24
  12. package/templates/standalone/Dockerfile +7 -3
  13. package/templates/standalone/README.ja.md +677 -184
  14. package/templates/standalone/README.md +699 -238
  15. package/templates/standalone/config/application.conf +203 -325
  16. package/templates/standalone/config/clients.yaml +2 -0
  17. package/templates/standalone/config/clients.yaml.example +4 -0
  18. package/templates/standalone/config/development.conf +13 -4
  19. package/templates/standalone/config/mailpit.conf +8 -0
  20. package/templates/standalone/config/production.conf +5 -6
  21. package/templates/standalone/config/reference.conf +399 -0
  22. package/templates/standalone/docker-compose.mailpit.yml +36 -0
  23. package/templates/standalone/docker-compose.production.yml +58 -30
  24. package/templates/standalone/docker-compose.yml +13 -3
  25. package/templates/standalone/gitignore +4 -2
  26. package/templates/standalone/package.json +9 -6
  27. package/templates/standalone/src/__tests__/access-token-lifetime-alias.test.mts +65 -32
  28. package/templates/standalone/src/__tests__/adapters.test.mts +358 -0
  29. package/templates/standalone/src/__tests__/all-modules-composition.fixture.mts +1178 -0
  30. package/templates/standalone/src/__tests__/all-modules-composition.multi.test.mts +256 -0
  31. package/templates/standalone/src/__tests__/all-modules-composition.test.mts +1119 -0
  32. package/templates/standalone/src/__tests__/audit-sink.test.mts +78 -76
  33. package/templates/standalone/src/__tests__/configPath.test.mts +13 -11
  34. package/templates/standalone/src/__tests__/core-federations.test.mts +226 -0
  35. package/templates/standalone/src/__tests__/core-sections.test.mts +190 -0
  36. package/templates/standalone/src/__tests__/device-code-store-client-module.test.mts +9 -14
  37. package/templates/standalone/src/__tests__/documented-env-overrides.test.mts +672 -271
  38. package/templates/standalone/src/__tests__/federation-grants-composition.test.mts +480 -78
  39. package/templates/standalone/src/__tests__/federation-grants-survive-logout.test.mts +102 -66
  40. package/templates/standalone/src/__tests__/federation-token-refresh-outage.test.mts +135 -0
  41. package/templates/standalone/src/__tests__/google-federation-config-module.test.mts +49 -18
  42. package/templates/standalone/src/__tests__/google-session.fixture.mts +315 -0
  43. package/templates/standalone/src/__tests__/host-routes.test.mts +134 -0
  44. package/templates/standalone/src/__tests__/library-references.fixture.mts +183 -0
  45. package/templates/standalone/src/__tests__/listen.test.mts +121 -0
  46. package/templates/standalone/src/__tests__/logger-projection.test.mts +77 -0
  47. package/templates/standalone/src/__tests__/mail-sender.test.mts +134 -0
  48. package/templates/standalone/src/__tests__/mailpit-compose.test.mts +328 -0
  49. package/templates/standalone/src/__tests__/metrics.test.mts +17 -7
  50. package/templates/standalone/src/__tests__/mfa-switch.redis.test.mts +123 -0
  51. package/templates/standalone/src/__tests__/mfa-switch.test.mts +780 -0
  52. package/templates/standalone/src/__tests__/oidc-federation-config-module.test.mts +52 -46
  53. package/templates/standalone/src/__tests__/own-modules.test.mts +1457 -0
  54. package/templates/standalone/src/__tests__/post-logout-redirect.test.mts +183 -0
  55. package/templates/standalone/src/__tests__/redis-connection-error-log.test.mts +135 -0
  56. package/templates/standalone/src/__tests__/redis-rate-limiter-config-survives.test.mts +112 -81
  57. package/templates/standalone/src/__tests__/refresh-token-family-client-module.test.mts +25 -31
  58. package/templates/standalone/src/__tests__/replica-safety.test.mts +166 -145
  59. package/templates/standalone/src/__tests__/router-error-answers.test.mts +146 -0
  60. package/templates/standalone/src/__tests__/scaffold-assets.test.mts +275 -95
  61. package/templates/standalone/src/__tests__/sections.test.mts +480 -0
  62. package/templates/standalone/src/__tests__/session-logout-invalidates-token.test.mts +67 -55
  63. package/templates/standalone/src/__tests__/session-requirements.test.mts +299 -0
  64. package/templates/standalone/src/__tests__/shutdown.test.mts +319 -115
  65. package/templates/standalone/src/__tests__/smoke.test.mts +179 -233
  66. package/templates/standalone/src/__tests__/store-credential.test.mts +317 -0
  67. package/templates/standalone/src/__tests__/supertest-loopback.test.mts +182 -11
  68. package/templates/standalone/src/__tests__/three-tier-resolution.test.mts +108 -95
  69. package/templates/standalone/src/__tests__/two-phase-config.test.mts +438 -0
  70. package/templates/standalone/src/__tests__/upstream-amr.acceptance.test.mts +390 -0
  71. package/templates/standalone/src/adapters.mts +149 -0
  72. package/templates/standalone/src/app.mts +71 -115
  73. package/templates/standalone/src/buildModules.mts +229 -251
  74. package/templates/standalone/src/configPath.mts +422 -29
  75. package/templates/standalone/src/listen.mts +60 -0
  76. package/templates/standalone/src/logger.mts +31 -57
  77. package/templates/standalone/src/metrics.mts +24 -47
  78. package/templates/standalone/src/mfaSwitch.mts +262 -0
  79. package/templates/standalone/src/modules.mts +480 -464
  80. package/templates/standalone/src/rootRenames.mts +79 -0
  81. package/templates/standalone/src/routes.mts +89 -0
  82. package/templates/standalone/src/sections.mts +366 -0
  83. package/templates/standalone/src/shutdown.mts +54 -126
  84. package/templates/standalone/tests/index.test.js +2 -2
  85. package/templates/standalone/tests/package-lock.json +1615 -2223
  86. package/templates/standalone/tests/package.json +2 -2
  87. package/templates/standalone/tests/vitest.config.mjs +26 -0
  88. package/templates/standalone/vitest.config.mts +8 -7
  89. package/templates/standalone/vitest.supertest-loopback.mts +97 -69
  90. package/templates/versions.json +18 -15
  91. package/templates/standalone/src/__tests__/terminal-error.test.mts +0 -96
  92. package/templates/standalone/src/terminalError.mts +0 -60
@@ -1,2 +1,4 @@
1
1
  # Client definitions loaded via loadYamlMap into InMemoryClientRepository.
2
2
  # Add client entries here. See clients.yaml.example for format.
3
+ # Per-deployment: `.gitignore` keeps this file out of git and `.dockerignore`
4
+ # out of the image (#705).
@@ -1,6 +1,10 @@
1
1
  # Example client definitions loaded via loadYamlMap into InMemoryClientRepository.
2
2
  # Copy this file to clients.yaml and customize for your deployment.
3
3
  #
4
+ # clients.yaml is per-deployment (#705): `.gitignore` keeps it out of git and
5
+ # `.dockerignore` out of the image, and docker-compose.production.yml mounts it
6
+ # as the secret `client_registry`. This example is the committed one.
7
+ #
4
8
  # `tokenEndpointAuthMethod` is REQUIRED (RFC 6749 §2.3 / RFC 7591 §2):
5
9
  # - "client_secret_basic": HTTP Basic Authorization header — recommended for confidential clients
6
10
  # - "client_secret_post": form-encoded body parameters
@@ -2,7 +2,16 @@
2
2
  # Loaded when CONFIG_ENV (or NODE_ENV) is "development" — the default.
3
3
  # Values here override application.conf.
4
4
  #
5
- # Most values are controlled via environment variables (see ${?VAR}
6
- # substitutions in application.conf). Override entries here only when
7
- # you need a fixed development-specific value that cannot come from
8
- # an environment variable.
5
+ # Most values are controlled via environment variables (see the ${?VAR}
6
+ # substitutions in application.conf and reference.conf). Override entries
7
+ # here only when you need a fixed development-specific value that cannot come
8
+ # from an environment variable.
9
+
10
+ # The MFA key ring's development key: the MFA package's published sample key
11
+ # (MFA_DEVELOPMENT_SAMPLE_KEY), which everyone holds. It stands in for
12
+ # MFA_ENCRYPTION_KEY here: to use a key of your own in development, write it
13
+ # in place of this one. It is refused where the configuration is selected as
14
+ # production or staging, where NODE_ENV is either, and under
15
+ # `core.deployment.mode = "multi"`; boot warns while it is in use
16
+ # (mfa_development_sample_key_in_use). Read only while MFA_MODE installs MFA.
17
+ mfa.encryptionKeys = [{ key = "bzNjbzptZmE6ZGV2ZWxvcG1lbnQtc2FtcGxlLWtleSE=" }]
@@ -0,0 +1,8 @@
1
+ # Environment overlay for a development run whose mail goes to Mailpit
2
+ # (docker-compose.mailpit.yml). Loaded when CONFIG_ENV is "mailpit".
3
+ #
4
+ # The development overlay under another name: the template installs the
5
+ # sender that logs each code only where the configuration is selected as
6
+ # "development", and the SMTP sender under any other name. For development
7
+ # only; a deployment names its own relay.
8
+ include "development.conf"
@@ -2,14 +2,13 @@
2
2
  # Loaded when CONFIG_ENV (or NODE_ENV) is "production".
3
3
  # Values here override application.conf.
4
4
  #
5
- # Most production-sensitive values (SESSION_SECRET, the JWT signing key, REDIS
5
+ # Most production-sensitive values (SESSION_STORE_SECRET, the JWT signing key, REDIS
6
6
  # URLs, federation client secrets, etc.) should be supplied via environment
7
- # variables — see the ${?VAR} substitutions in application.conf.
7
+ # variables — see the ${?VAR} substitutions in application.conf and
8
+ # reference.conf.
8
9
  #
9
- # #407: this used to name OAUTH_JWT_SECRET as the typical example. That is the
10
- # HMAC secret, and it stopped being the default when EdDSA became one (#282) —
11
- # the shipped path is now a key pair delivered as files
12
- # (OAUTH_JWT_PRIVATE_KEY_PATH / OAUTH_JWT_PUBLIC_KEY_PATH), which
10
+ # The signing key is a key pair delivered as files
11
+ # (KEY_STORE_LOCAL_PRIVATE_KEY_PATH / KEY_STORE_LOCAL_PUBLIC_KEY_PATH), which
13
12
  # docker-compose.production.yml mounts as compose secrets.
14
13
  #
15
14
  # Override entries here only when a value must be fixed per-environment rather
@@ -0,0 +1,399 @@
1
+ # Defaults of the standalone template's own modules (src/modules.mts), each in
2
+ # the section named after its module, and of its own `adapters` and
3
+ # `mfaMode`, each key with its variable, named after its path. Layered
4
+ # beneath application.conf and {env}.conf: set a deployment's own values
5
+ # there, not here. A value either file sets wins over
6
+ # the variable bound here, except for HTTP_PORT, HTTP_TRUST_PROXY,
7
+ # HTTP_CORS_ALLOWED_ORIGINS and REDIS_CLIENTS_URL / _PASSWORD, which
8
+ # application.conf binds again in its last lines, so they win over a value it
9
+ # sets above them.
10
+
11
+ # `logging`: trace | debug | info | warn | error | fatal | silent. `trace` and
12
+ # `debug` carry request-shaped detail; `silent` spares only the audit trail.
13
+ logging {
14
+ level = "info"
15
+ level = ${?LOGGING_LEVEL}
16
+ }
17
+
18
+ # `http`: the listener, the trusted forwarding hops, the readiness deadline and
19
+ # the CORS list.
20
+ http {
21
+ port = 3000
22
+ port = ${?HTTP_PORT}
23
+ # Express `trust proxy`: whether X-Forwarded-For may rewrite `req.ip`
24
+ # (every IP-keyed rate limit) and X-Forwarded-Proto may rewrite
25
+ # `req.protocol`.
26
+ #
27
+ # Four shapes, all of them Express's own:
28
+ #
29
+ # false trust nothing. `req.ip` is the socket peer. The default.
30
+ # <address list> trust these hops, and only these. An IP literal, a CIDR
31
+ # range (10.0.0.0/8, fc00::/7), or a named range
32
+ # (loopback, linklocal, uniquelocal). This is the only
33
+ # shape that says WHICH hop is trusted, and it is the one
34
+ # to reach for. Via env var, comma-separate:
35
+ # HTTP_TRUST_PROXY=10.0.0.0/8,192.168.0.0/16
36
+ # <hop count> trust that many hops back from the socket peer, e.g. 1
37
+ # for exactly one reverse proxy in front of this process.
38
+ # true trust every hop, i.e. believe the leftmost
39
+ # X-Forwarded-For entry from whoever opened the connection.
40
+ # Correct ONLY when nothing but the proxy can route to this
41
+ # process; otherwise a client that reaches it directly, or
42
+ # through one more hop than you accounted for, forges its
43
+ # own `req.ip` and with it any IP-keyed rate-limit identity.
44
+ #
45
+ # Entries are validated at boot: a hostname, a mistyped keyword or a
46
+ # malformed prefix length fails loudly rather than becoming a rule that
47
+ # never matches.
48
+ #
49
+ # An allowlist is a network control, not a cryptographic one. The edge must
50
+ # also STRIP inbound X-Forwarded-* headers rather than appending to them,
51
+ # and the hop between it and this process must not be reachable by anyone
52
+ # able to spoof a source address.
53
+ #
54
+ # Unrelated to `mtls.trustedProxies`, which shares this vocabulary but
55
+ # answers a different question (may this peer forward a client certificate?)
56
+ # and is deliberately not derived from this key.
57
+ trustProxy = false
58
+ trustProxy = ${?HTTP_TRUST_PROXY}
59
+ # Per-probe deadline for /readyz and the metrics scrape's probes. Keep it
60
+ # below the orchestrator's own probe timeout so an unreachable dependency
61
+ # answers 503 rather than letting the probe hang.
62
+ readinessTimeoutMs = 1000
63
+ readinessTimeoutMs = ${?HTTP_READINESS_TIMEOUT_MS}
64
+ # `cors`: the browser origins allowed to read the token, userinfo,
65
+ # revocation and discovery/JWKS responses. Empty (the default) means CORS
66
+ # is off.
67
+ #
68
+ # Matching against the `Origin` request header is exact string equality,
69
+ # so each entry is a bare origin: scheme, host, and a port only when it is
70
+ # not the scheme's default; no trailing slash, no path, no wildcard. https
71
+ # is required except for a loopback host (localhost, 127.0.0.0/8, [::1]).
72
+ # Each rule is checked at boot.
73
+ #
74
+ # cors.allowedOrigins = ["https://app.example.com", "http://localhost:5173"]
75
+ #
76
+ # No credentials are ever allowed: an allowlisted origin can read these
77
+ # responses and cannot use the browser's session cookie. This list confers
78
+ # no CSRF trust; that is `session.csrf.trustedOrigins`. The variable takes a
79
+ # comma-separated list.
80
+ cors {
81
+ allowedOrigins = []
82
+ allowedOrigins = ${?HTTP_CORS_ALLOWED_ORIGINS}
83
+ }
84
+ }
85
+
86
+ # `key-store`: the JWT signing key.
87
+ key-store {
88
+ provider = "local"
89
+ provider = ${?KEY_STORE_PROVIDER}
90
+ local {
91
+ # The default is asymmetric. HS256 leaves a relying party with two bad
92
+ # options: verify nothing (there is no public key to publish, so
93
+ # /.well-known/jwks.json has nothing to serve), or hold the shared
94
+ # secret, which also lets it mint tokens. EdDSA (Ed25519) publishes a
95
+ # real JWKS and keeps signing capability here.
96
+ #
97
+ # There is no key material default and no fallback: a deployment that
98
+ # configures nothing fails at boot naming the keys to set, rather than
99
+ # quietly signing with a guessable secret.
100
+ #
101
+ # openssl genpkey -algorithm ed25519 -out jwt-private.pem
102
+ # openssl pkey -in jwt-private.pem -pubout -out jwt-public.pem
103
+ #
104
+ # HS256 remains selectable (KEY_STORE_LOCAL_ALGORITHM=HS256) for
105
+ # deployments that verify in-process and publish no JWKS. It then requires
106
+ # KEY_STORE_LOCAL_SECRET with at least 32 bytes (256 bits) of random material
107
+ # (`openssl rand -hex 32`). Hex and base64 values are measured decoded, so
108
+ # a 32-character hex string is only 16 bytes and is refused.
109
+ algorithm = "EdDSA"
110
+ algorithm = ${?KEY_STORE_LOCAL_ALGORITHM}
111
+ kid = "v0"
112
+ kid = ${?KEY_STORE_LOCAL_KID}
113
+ secret = ${?KEY_STORE_LOCAL_SECRET}
114
+ privateKeyPath = ${?KEY_STORE_LOCAL_PRIVATE_KEY_PATH}
115
+ publicKeyPath = ${?KEY_STORE_LOCAL_PUBLIC_KEY_PATH}
116
+ privateKey = ${?KEY_STORE_LOCAL_PRIVATE_KEY}
117
+ publicKey = ${?KEY_STORE_LOCAL_PUBLIC_KEY}
118
+ # Key rotation is shaped per algorithm family. Asymmetric algorithms
119
+ # (RS256, ES256, EdDSA) use `previousKeys` (kid, publicKey or
120
+ # publicKeyPath, expiresAt); HS256 uses `previousSecrets` (kid, secret,
121
+ # expiresAt). The schema is a discriminated union and the key store
122
+ # refuses the other family's field, so the wrong shape fails at boot
123
+ # rather than being dropped at verification time. Neither is declared
124
+ # here: the default algorithm is EdDSA, and an empty
125
+ # `previousSecrets = []` under it would be exactly that wrong shape. Add
126
+ # the one matching your algorithm in your own layer.
127
+ }
128
+ }
129
+
130
+ # `redis-clients`: the one ioredis socket per replica every Redis-backed store
131
+ # shares. More than one replica needs a shared Redis 7.2+ here; localhost
132
+ # inside a container is the container itself.
133
+ redis-clients {
134
+ url = "redis://localhost:6379"
135
+ url = ${?REDIS_CLIENTS_URL}
136
+ password = ${?REDIS_CLIENTS_PASSWORD}
137
+ }
138
+
139
+ # `repositories`: the client registry, and the user repository's settings for
140
+ # each adapter it may be (`adapters.userRepository`). The two YAML paths are
141
+ # cwd-relative.
142
+ repositories {
143
+ client {
144
+ yaml {
145
+ path = "./config/clients.yaml"
146
+ path = ${?REPOSITORIES_CLIENT_YAML_PATH}
147
+ }
148
+ }
149
+ user {
150
+ yaml {
151
+ path = "./config/users.yaml"
152
+ path = ${?REPOSITORIES_USER_YAML_PATH}
153
+ }
154
+ # The Store's HTTP endpoints. Every URL here carries what a Store must
155
+ # never receive in the clear -- user credentials to the first two, a
156
+ # verified federation token and its claims to the link and the lookup, a
157
+ # subject's MFA enrollment witness to the last -- so each must be an
158
+ # absolute https URL; http:// is accepted only for a loopback host
159
+ # (localhost, 127.0.0.0/8, [::1]). Each must be the endpoint that answers:
160
+ # no request follows a 3xx. Boot fails otherwise.
161
+ http {
162
+ authenticateUrl = ${?REPOSITORIES_USER_HTTP_AUTHENTICATE_URL}
163
+ authenticateByTokenUrl = ${?REPOSITORIES_USER_HTTP_AUTHENTICATE_BY_TOKEN_URL}
164
+ # Optional. With it, a signed-in user can link a second federated
165
+ # identity (GET /session/oauth/federation/<name>?link=1) and the Store
166
+ # decides; without it, link=1 is refused with `link_unsupported`.
167
+ linkFederatedIdentityUrl = ${?REPOSITORIES_USER_HTTP_LINK_FEDERATED_IDENTITY_URL}
168
+ # Optional. The Store's identity lookup for federation grants. With it,
169
+ # declare what the lookup covers, per registration, in a HOCON layer (a
170
+ # list has no environment form):
171
+ # federatedIdentityLookupCoverage = [
172
+ # { provider = "entra-files", issuer = "...", clientId = "...", requiredClaims = ["tid", "oid"] }
173
+ # ]
174
+ findSubjectByFederatedIdentityUrl = ${?REPOSITORIES_USER_HTTP_FIND_SUBJECT_BY_FEDERATED_IDENTITY_URL}
175
+ federatedIdentityLookupCoverage = []
176
+ # Optional. The Store's MFA enrollment witness endpoint: with it, the
177
+ # provider writes whether a subject has a second factor, which keeps a
178
+ # lost factor store from opening a first binding; without it, a boot
179
+ # with MFA on warns (`mfa_enrollment_witness_unwritable`). The Store
180
+ # must then answer `mfaEnrolled` on both authenticate URLs.
181
+ markMfaEnrolledUrl = ${?REPOSITORIES_USER_HTTP_MARK_MFA_ENROLLED_URL}
182
+ # Optional. Sent on every Store request as `Authorization: Bearer
183
+ # <token>`, so the Store can refuse any caller but this deployment;
184
+ # unset, none is sent. A bare token (no "Bearer " prefix) of at least 32
185
+ # bytes of key material -- `openssl rand -hex 32`. Boot fails on a weak,
186
+ # malformed or blank value. One token goes to every URL above.
187
+ bearerToken = ${?REPOSITORIES_USER_HTTP_BEARER_TOKEN}
188
+ # Positive integer, milliseconds, <= 2147483647.
189
+ timeout = 5000
190
+ timeout = ${?REPOSITORIES_USER_HTTP_TIMEOUT}
191
+ # Largest upstream response body accepted, in bytes. Positive integer.
192
+ maxResponseBytes = 1048576
193
+ maxResponseBytes = ${?REPOSITORIES_USER_HTTP_MAX_RESPONSE_BYTES}
194
+ }
195
+ }
196
+ }
197
+
198
+ # `standalone-in-memory-code-repository`: the in-process authorization-code
199
+ # repository (`adapters.codeRepository = "memory"`), single-replica only. Its
200
+ # codes' default lifetime, in positive whole seconds.
201
+ standalone-in-memory-code-repository {
202
+ defaultExpiresIn = 600
203
+ defaultExpiresIn = ${?STANDALONE_IN_MEMORY_CODE_REPOSITORY_DEFAULT_EXPIRES_IN}
204
+ }
205
+
206
+ # `audit-sink`: each sink's options, under the sink's name, for the sink
207
+ # `adapters.auditSink` selects -- `audit-sink { splunk-hec { ... } }` for a sink
208
+ # a deployment registers in the audit-sink module (src/modules.mts). The
209
+ # template's `logger` and core's `console` take none.
210
+ audit-sink {}
211
+
212
+ # `adapters`: the composition root's own section -- which adapter fills each
213
+ # slot. The template reads it before it chooses its modules, and hands it to
214
+ # no module. Each value is what the template ships.
215
+ #
216
+ # More than one replica needs every shared store on Redis: `memory` forks per
217
+ # replica, and `core.deployment.mode = "multi"` refuses each memory store by
218
+ # name. Each Redis store shares the one socket `redis-clients` opens.
219
+ adapters {
220
+ # The OAuth endpoints' and /session/login's rate-limit counters.
221
+ rateLimiter = "memory"
222
+ rateLimiter = ${?ADAPTERS_RATE_LIMITER}
223
+ # The four user-session stores and the subject-level revocation pair. They
224
+ # must be on Redis whenever express-session's store is, or a restart leaves
225
+ # every browser a session whose user session is gone.
226
+ userSessionStores = "memory"
227
+ userSessionStores = ${?ADAPTERS_USER_SESSION_STORES}
228
+ # The RFC 7009 access-token denylist, which POST /oauth/revoke writes and
229
+ # verification reads. On Redis, so a token revoked on one replica is revoked
230
+ # on every one.
231
+ accessTokenDenylist = "redis"
232
+ accessTokenDenylist = ${?ADAPTERS_ACCESS_TOKEN_DENYLIST}
233
+ # The `jti` single-use record behind private_key_jwt client authentication.
234
+ replaySeenSet = "redis"
235
+ replaySeenSet = ${?ADAPTERS_REPLAY_SEEN_SET}
236
+ # Consent to a client that is not first-party: "none" wires no store and
237
+ # such clients are refused at /authorize; "memory" is single-replica only;
238
+ # "redis" keeps the consent records and parked requests on the shared
239
+ # socket (key namespace REDIS_CONSENT_STORE_KEY_PREFIX).
240
+ consentStore = "none"
241
+ consentStore = ${?ADAPTERS_CONSENT_STORE}
242
+ # The upstream IdP tokens held for a session. "redis" needs its encryption
243
+ # key, REDIS_FEDERATION_TOKEN_STORE_ENCRYPTION_KEY.
244
+ federationTokenStore = "memory"
245
+ federationTokenStore = ${?ADAPTERS_FEDERATION_TOKEN_STORE}
246
+ # Federation grants and acquisition's records, read only while
247
+ # `federation-grants.enabled`: "none" (the default) wires no store, and an
248
+ # enabled feature refuses boot naming the one it lacks; "memory" is
249
+ # single-replica only; "redis" is the shared socket. Redis grants need the
250
+ # subject boundary in Redis too (`userSessionStores = "redis"`): boot
251
+ # refuses the pairing with a memory one.
252
+ federationGrantStore = "none"
253
+ federationGrantStore = ${?ADAPTERS_FEDERATION_GRANT_STORE}
254
+ federationGrantIntentStore = "none"
255
+ federationGrantIntentStore = ${?ADAPTERS_FEDERATION_GRANT_INTENT_STORE}
256
+ # Where MFA factors ("memory", "redis" or "store") and MFA transactions
257
+ # ("memory" or "redis") are kept, for a composition that installs MFA.
258
+ mfaFactorStore = "memory"
259
+ mfaFactorStore = ${?ADAPTERS_MFA_FACTOR_STORE}
260
+ mfaTransactionStore = "memory"
261
+ mfaTransactionStore = ${?ADAPTERS_MFA_TRANSACTION_STORE}
262
+ # The authorization-code repository ("memory" loses codes on restart and
263
+ # across replicas).
264
+ codeRepository = "redis"
265
+ codeRepository = ${?ADAPTERS_CODE_REPOSITORY}
266
+ # The client registry (YAML), and the user repository: the Store's HTTP
267
+ # endpoints, or a YAML directory.
268
+ clientRepository = "yaml"
269
+ clientRepository = ${?ADAPTERS_CLIENT_REPOSITORY}
270
+ userRepository = "http"
271
+ userRepository = ${?ADAPTERS_USER_REPOSITORY}
272
+ # Where the audit trail goes: "logger" writes each event through the pino
273
+ # stream every other line takes; core's "console" writes the bare event.
274
+ # There is no "none": a name no sink is registered under refuses boot.
275
+ auditSink = "logger"
276
+ auditSink = ${?ADAPTERS_AUDIT_SINK}
277
+ }
278
+
279
+ # `mfaMode`: the composition root's own key -- whether the template installs
280
+ # MFA. Read before it chooses its modules, and handed to no module, as
281
+ # `adapters` is. "required" (the default: every password login has a second
282
+ # factor) and "optional" (users with a factor are challenged) install the MFA
283
+ # package's modules over the two MFA stores above, declare `mfa` in
284
+ # core.sessionRequirements.expected and write `mfa.mode` from this; "off"
285
+ # installs nothing of MFA. Anything else is refused before boot. A
286
+ # configuration that writes `mfa.mode` itself must write what this says, or
287
+ # boot is refused.
288
+ #
289
+ # With MFA on outside development, it needs its two stores on "redis" (or the
290
+ # factors in the "store"), MFA_ENCRYPTION_KEY, an SMTP relay
291
+ # (STANDARD_SMTP_MAIL_SENDER_HOST and _FROM) or the deployment's own mail
292
+ # sender, and a page served at MFA_PAGE_URL (/mfa unless set); a deployment
293
+ # that wants none of it sets MFA_MODE=off.
294
+ mfaMode = "required"
295
+ mfaMode = ${?MFA_MODE}
296
+
297
+ # The old paths of these sections refuse boot naming the new ones: `cors`,
298
+ # `oauth.jwt.signingKey`, `refreshTokenFamilyStore.redis`,
299
+ # `repositories.code` and `audit.sink`; the old adapter switches
300
+ # (`rateLimiter.adapter`, `oauth.code.adapter`, ...) are refused before the
301
+ # modules are chosen, and so are the variables renamed with them. What the
302
+ # resolution saw of each variable they renamed: each name null, then its value
303
+ # when set. Boot refuses an old name set unless its new name is set to the
304
+ # same value, and removes this section before it parses the configuration.
305
+ # Never write it in another layer.
306
+ renamed-variables {
307
+ LOG_LEVEL = null
308
+ LOG_LEVEL = ${?LOG_LEVEL}
309
+ LOGGING_LEVEL = null
310
+ LOGGING_LEVEL = ${?LOGGING_LEVEL}
311
+ CORS_ALLOWED_ORIGINS = null
312
+ CORS_ALLOWED_ORIGINS = ${?CORS_ALLOWED_ORIGINS}
313
+ HTTP_CORS_ALLOWED_ORIGINS = null
314
+ HTTP_CORS_ALLOWED_ORIGINS = ${?HTTP_CORS_ALLOWED_ORIGINS}
315
+ OAUTH_JWT_SIGNING_KEY_PROVIDER = null
316
+ OAUTH_JWT_SIGNING_KEY_PROVIDER = ${?OAUTH_JWT_SIGNING_KEY_PROVIDER}
317
+ KEY_STORE_PROVIDER = null
318
+ KEY_STORE_PROVIDER = ${?KEY_STORE_PROVIDER}
319
+ OAUTH_JWT_ALGORITHM = null
320
+ OAUTH_JWT_ALGORITHM = ${?OAUTH_JWT_ALGORITHM}
321
+ KEY_STORE_LOCAL_ALGORITHM = null
322
+ KEY_STORE_LOCAL_ALGORITHM = ${?KEY_STORE_LOCAL_ALGORITHM}
323
+ OAUTH_JWT_KID = null
324
+ OAUTH_JWT_KID = ${?OAUTH_JWT_KID}
325
+ KEY_STORE_LOCAL_KID = null
326
+ KEY_STORE_LOCAL_KID = ${?KEY_STORE_LOCAL_KID}
327
+ OAUTH_JWT_SECRET = null
328
+ OAUTH_JWT_SECRET = ${?OAUTH_JWT_SECRET}
329
+ KEY_STORE_LOCAL_SECRET = null
330
+ KEY_STORE_LOCAL_SECRET = ${?KEY_STORE_LOCAL_SECRET}
331
+ OAUTH_JWT_PRIVATE_KEY_PATH = null
332
+ OAUTH_JWT_PRIVATE_KEY_PATH = ${?OAUTH_JWT_PRIVATE_KEY_PATH}
333
+ KEY_STORE_LOCAL_PRIVATE_KEY_PATH = null
334
+ KEY_STORE_LOCAL_PRIVATE_KEY_PATH = ${?KEY_STORE_LOCAL_PRIVATE_KEY_PATH}
335
+ OAUTH_JWT_PUBLIC_KEY_PATH = null
336
+ OAUTH_JWT_PUBLIC_KEY_PATH = ${?OAUTH_JWT_PUBLIC_KEY_PATH}
337
+ KEY_STORE_LOCAL_PUBLIC_KEY_PATH = null
338
+ KEY_STORE_LOCAL_PUBLIC_KEY_PATH = ${?KEY_STORE_LOCAL_PUBLIC_KEY_PATH}
339
+ OAUTH_JWT_PRIVATE_KEY = null
340
+ OAUTH_JWT_PRIVATE_KEY = ${?OAUTH_JWT_PRIVATE_KEY}
341
+ KEY_STORE_LOCAL_PRIVATE_KEY = null
342
+ KEY_STORE_LOCAL_PRIVATE_KEY = ${?KEY_STORE_LOCAL_PRIVATE_KEY}
343
+ OAUTH_JWT_PUBLIC_KEY = null
344
+ OAUTH_JWT_PUBLIC_KEY = ${?OAUTH_JWT_PUBLIC_KEY}
345
+ KEY_STORE_LOCAL_PUBLIC_KEY = null
346
+ KEY_STORE_LOCAL_PUBLIC_KEY = ${?KEY_STORE_LOCAL_PUBLIC_KEY}
347
+ REFRESH_TOKEN_FAMILY_STORE_REDIS_URL = null
348
+ REFRESH_TOKEN_FAMILY_STORE_REDIS_URL = ${?REFRESH_TOKEN_FAMILY_STORE_REDIS_URL}
349
+ REDIS_CLIENTS_URL = null
350
+ REDIS_CLIENTS_URL = ${?REDIS_CLIENTS_URL}
351
+ REFRESH_TOKEN_FAMILY_STORE_REDIS_PASSWORD = null
352
+ REFRESH_TOKEN_FAMILY_STORE_REDIS_PASSWORD = ${?REFRESH_TOKEN_FAMILY_STORE_REDIS_PASSWORD}
353
+ REDIS_CLIENTS_PASSWORD = null
354
+ REDIS_CLIENTS_PASSWORD = ${?REDIS_CLIENTS_PASSWORD}
355
+ CLIENT_PATH = null
356
+ CLIENT_PATH = ${?CLIENT_PATH}
357
+ REPOSITORIES_CLIENT_YAML_PATH = null
358
+ REPOSITORIES_CLIENT_YAML_PATH = ${?REPOSITORIES_CLIENT_YAML_PATH}
359
+ CLIENT_USER_PATH = null
360
+ CLIENT_USER_PATH = ${?CLIENT_USER_PATH}
361
+ REPOSITORIES_USER_YAML_PATH = null
362
+ REPOSITORIES_USER_YAML_PATH = ${?REPOSITORIES_USER_YAML_PATH}
363
+ CLIENT_USER_AUTHENTICATE_URL = null
364
+ CLIENT_USER_AUTHENTICATE_URL = ${?CLIENT_USER_AUTHENTICATE_URL}
365
+ REPOSITORIES_USER_HTTP_AUTHENTICATE_URL = null
366
+ REPOSITORIES_USER_HTTP_AUTHENTICATE_URL = ${?REPOSITORIES_USER_HTTP_AUTHENTICATE_URL}
367
+ CLIENT_USER_AUTHENTICATE_BY_TOKEN_URL = null
368
+ CLIENT_USER_AUTHENTICATE_BY_TOKEN_URL = ${?CLIENT_USER_AUTHENTICATE_BY_TOKEN_URL}
369
+ REPOSITORIES_USER_HTTP_AUTHENTICATE_BY_TOKEN_URL = null
370
+ REPOSITORIES_USER_HTTP_AUTHENTICATE_BY_TOKEN_URL = ${?REPOSITORIES_USER_HTTP_AUTHENTICATE_BY_TOKEN_URL}
371
+ CLIENT_USER_LINK_FEDERATED_IDENTITY_URL = null
372
+ CLIENT_USER_LINK_FEDERATED_IDENTITY_URL = ${?CLIENT_USER_LINK_FEDERATED_IDENTITY_URL}
373
+ REPOSITORIES_USER_HTTP_LINK_FEDERATED_IDENTITY_URL = null
374
+ REPOSITORIES_USER_HTTP_LINK_FEDERATED_IDENTITY_URL = ${?REPOSITORIES_USER_HTTP_LINK_FEDERATED_IDENTITY_URL}
375
+ CLIENT_USER_FIND_SUBJECT_BY_FEDERATED_IDENTITY_URL = null
376
+ CLIENT_USER_FIND_SUBJECT_BY_FEDERATED_IDENTITY_URL = ${?CLIENT_USER_FIND_SUBJECT_BY_FEDERATED_IDENTITY_URL}
377
+ REPOSITORIES_USER_HTTP_FIND_SUBJECT_BY_FEDERATED_IDENTITY_URL = null
378
+ REPOSITORIES_USER_HTTP_FIND_SUBJECT_BY_FEDERATED_IDENTITY_URL = ${?REPOSITORIES_USER_HTTP_FIND_SUBJECT_BY_FEDERATED_IDENTITY_URL}
379
+ CLIENT_USER_BEARER_TOKEN = null
380
+ CLIENT_USER_BEARER_TOKEN = ${?CLIENT_USER_BEARER_TOKEN}
381
+ REPOSITORIES_USER_HTTP_BEARER_TOKEN = null
382
+ REPOSITORIES_USER_HTTP_BEARER_TOKEN = ${?REPOSITORIES_USER_HTTP_BEARER_TOKEN}
383
+ CLIENT_USER_TIMEOUT = null
384
+ CLIENT_USER_TIMEOUT = ${?CLIENT_USER_TIMEOUT}
385
+ REPOSITORIES_USER_HTTP_TIMEOUT = null
386
+ REPOSITORIES_USER_HTTP_TIMEOUT = ${?REPOSITORIES_USER_HTTP_TIMEOUT}
387
+ CLIENT_USER_MAX_RESPONSE_BYTES = null
388
+ CLIENT_USER_MAX_RESPONSE_BYTES = ${?CLIENT_USER_MAX_RESPONSE_BYTES}
389
+ REPOSITORIES_USER_HTTP_MAX_RESPONSE_BYTES = null
390
+ REPOSITORIES_USER_HTTP_MAX_RESPONSE_BYTES = ${?REPOSITORIES_USER_HTTP_MAX_RESPONSE_BYTES}
391
+ CLIENT_CODE_DEFAULT_EXPIRES_IN = null
392
+ CLIENT_CODE_DEFAULT_EXPIRES_IN = ${?CLIENT_CODE_DEFAULT_EXPIRES_IN}
393
+ STANDALONE_IN_MEMORY_CODE_REPOSITORY_DEFAULT_EXPIRES_IN = null
394
+ STANDALONE_IN_MEMORY_CODE_REPOSITORY_DEFAULT_EXPIRES_IN = ${?STANDALONE_IN_MEMORY_CODE_REPOSITORY_DEFAULT_EXPIRES_IN}
395
+ CLIENT_CODE_ENDPOINT_URI = null
396
+ CLIENT_CODE_ENDPOINT_URI = ${?CLIENT_CODE_ENDPOINT_URI}
397
+ CLIENT_CODE_PASSWORD = null
398
+ CLIENT_CODE_PASSWORD = ${?CLIENT_CODE_PASSWORD}
399
+ }
@@ -0,0 +1,36 @@
1
+ # Mailpit for the SMTP mail sender: a DEVELOPMENT overlay on
2
+ # docker-compose.yml, for a run with MFA on (the template's default, or
3
+ # MFA_MODE in .env) whose mail you can read.
4
+ #
5
+ # docker compose -f docker-compose.yml -f docker-compose.mailpit.yml up --build
6
+ #
7
+ # Mailpit keeps every mail the provider sends and delivers none; read them at
8
+ # http://localhost:8025. Never use it in production, where
9
+ # STANDARD_SMTP_MAIL_SENDER_* name a real relay. Leave every
10
+ # STANDARD_SMTP_MAIL_SENDER_* unset in .env for this run, USER and PASSWORD
11
+ # included: the overlay sets HOST, PORT, SECURE and FROM, not the credentials.
12
+ #
13
+ # The SMTP sender sends in plaintext (`none`) only to a loopback host, so
14
+ # Mailpit shares the app container's network namespace and listens on its
15
+ # localhost, and the app service publishes Mailpit's web UI. The configuration
16
+ # is selected as `mailpit` (config/mailpit.conf, the development overlay
17
+ # under another name), since under `development` the template installs the
18
+ # sender that logs each code instead. Under that name the MFA stores may not
19
+ # be kept in memory, so both are on the Redis docker-compose.yml runs.
20
+
21
+ services:
22
+ app:
23
+ ports:
24
+ - "127.0.0.1:8025:8025"
25
+ environment:
26
+ CONFIG_ENV: mailpit
27
+ ADAPTERS_MFA_FACTOR_STORE: redis
28
+ ADAPTERS_MFA_TRANSACTION_STORE: redis
29
+ STANDARD_SMTP_MAIL_SENDER_HOST: localhost
30
+ STANDARD_SMTP_MAIL_SENDER_PORT: "1025"
31
+ STANDARD_SMTP_MAIL_SENDER_SECURE: none
32
+ STANDARD_SMTP_MAIL_SENDER_FROM: auth@example.com
33
+
34
+ mailpit:
35
+ image: axllent/mailpit:v1.31.3
36
+ network_mode: service:app