@o3co/create-auth-provider 0.17.0-rc.2 → 0.17.0-rc.3
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.ja.md +3 -1
- package/README.md +5 -2
- package/package.json +1 -1
- package/templates/standalone/.env.example +7 -2
- package/templates/standalone/README.ja.md +14 -10
- package/templates/standalone/README.md +26 -20
- package/templates/standalone/config/application.conf +1 -0
- package/templates/standalone/config/clients.yaml.example +8 -0
- package/templates/standalone/config/development.conf +2 -2
- package/templates/standalone/config/mailpit.conf +10 -5
- package/templates/standalone/config/reference.conf +9 -3
- package/templates/standalone/docker-compose.mailpit.yml +3 -0
- package/templates/standalone/package.json +1 -1
- package/templates/standalone/src/__tests__/adapters.test.mts +12 -6
- package/templates/standalone/src/__tests__/all-modules-composition.multi.test.mts +13 -5
- package/templates/standalone/src/__tests__/config-notices.test.mts +4 -1
- package/templates/standalone/src/__tests__/configured-mfa-mode.fixture.mts +48 -0
- package/templates/standalone/src/__tests__/core-federations.test.mts +9 -6
- package/templates/standalone/src/__tests__/core-sections.test.mts +7 -5
- package/templates/standalone/src/__tests__/documented-env-overrides.test.mts +89 -86
- package/templates/standalone/src/__tests__/federation-grants-composition.test.mts +4 -0
- package/templates/standalone/src/__tests__/mailpit-compose.test.mts +81 -14
- package/templates/standalone/src/__tests__/mfa-switch.redis.test.mts +8 -4
- package/templates/standalone/src/__tests__/mfa-switch.test.mts +79 -21
- package/templates/standalone/src/__tests__/own-modules.test.mts +38 -12
- package/templates/standalone/src/__tests__/redis-assume-noeviction.test.mts +214 -0
- package/templates/standalone/src/__tests__/replica-safety.test.mts +4 -0
- package/templates/standalone/src/__tests__/scaffold-assets.test.mts +10 -3
- package/templates/standalone/src/__tests__/sections.test.mts +24 -0
- package/templates/standalone/src/__tests__/shipped-config-notices.test.mts +4 -0
- package/templates/standalone/src/__tests__/startup-log-lines.test.mts +148 -0
- package/templates/standalone/src/__tests__/three-tier-resolution.test.mts +8 -4
- package/templates/standalone/src/__tests__/two-phase-config.test.mts +2 -1
- package/templates/standalone/src/adapters.mts +2 -2
- package/templates/standalone/src/mfaSwitch.mts +6 -7
- package/templates/standalone/src/modules.mts +12 -6
- package/templates/standalone/src/rootRenames.mts +12 -13
- package/templates/standalone/src/sections.mts +12 -2
- package/templates/versions.json +18 -18
|
@@ -0,0 +1,148 @@
|
|
|
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
|
+
* What the template writes while it starts: `src/app.mts` in a process of its
|
|
19
|
+
* own, in development on the memory stores, until it listens and then until
|
|
20
|
+
* it has shut down. Every line on stdout and on stderr is one JSON object from
|
|
21
|
+
* the structured logger — a warning a store gives at boot included.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { type ChildProcess, spawn } from "node:child_process";
|
|
25
|
+
import { generateKeyPairSync } from "node:crypto";
|
|
26
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
27
|
+
import { createRequire } from "node:module";
|
|
28
|
+
import { tmpdir } from "node:os";
|
|
29
|
+
import { join } from "node:path";
|
|
30
|
+
import { fileURLToPath } from "node:url";
|
|
31
|
+
import { describe, expect, it } from "vitest";
|
|
32
|
+
|
|
33
|
+
const templateRoot = fileURLToPath(new URL("../..", import.meta.url));
|
|
34
|
+
const tsxCli = createRequire(import.meta.url).resolve("tsx/cli");
|
|
35
|
+
|
|
36
|
+
const keyPair = generateKeyPairSync("ed25519", {
|
|
37
|
+
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
38
|
+
privateKeyEncoding: { type: "pkcs8", format: "pem" },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/** An empty client registry: a scaffolded project's clone has no `config/clients.yaml`. */
|
|
42
|
+
const clientsFile = (() => {
|
|
43
|
+
const file = join(mkdtempSync(join(tmpdir(), "startup-log-lines-")), "clients.yaml");
|
|
44
|
+
writeFileSync(file, "");
|
|
45
|
+
return file;
|
|
46
|
+
})();
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Development, with every store the `adapters` section selects in memory.
|
|
50
|
+
* The refresh-token family store has no memory choice in the template; its
|
|
51
|
+
* shared Redis socket points at a port nothing answers, so the run reaches no
|
|
52
|
+
* real server. Nothing is inherited from the test runner's environment but
|
|
53
|
+
* PATH, so its NODE_ENV and LOGGING_LEVEL do not reach the child.
|
|
54
|
+
*/
|
|
55
|
+
const ENV: Readonly<Record<string, string>> = {
|
|
56
|
+
PATH: process.env.PATH ?? "",
|
|
57
|
+
CONFIG_ENV: "development",
|
|
58
|
+
NODE_ENV: "development",
|
|
59
|
+
LOGGING_LEVEL: "info",
|
|
60
|
+
HTTP_PORT: "0",
|
|
61
|
+
OAUTH_JWT_ISSUER: "http://localhost:3000",
|
|
62
|
+
KEY_STORE_LOCAL_PRIVATE_KEY: keyPair.privateKey,
|
|
63
|
+
KEY_STORE_LOCAL_PUBLIC_KEY: keyPair.publicKey,
|
|
64
|
+
SESSION_STORE_SECRET: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
65
|
+
SESSION_STORE_SECURE: "false",
|
|
66
|
+
SESSION_STORE_NAME: "auth.sid",
|
|
67
|
+
SESSION_STORE_STORAGE_TYPE: "memory",
|
|
68
|
+
ADAPTERS_RATE_LIMITER: "memory",
|
|
69
|
+
ADAPTERS_ATTEMPT_COUNTER: "memory",
|
|
70
|
+
ADAPTERS_USER_SESSION_STORES: "memory",
|
|
71
|
+
ADAPTERS_ACCESS_TOKEN_DENYLIST: "memory",
|
|
72
|
+
ADAPTERS_REPLAY_SEEN_SET: "memory",
|
|
73
|
+
ADAPTERS_CODE_REPOSITORY: "memory",
|
|
74
|
+
ADAPTERS_FEDERATION_TOKEN_STORE: "memory",
|
|
75
|
+
ADAPTERS_MFA_FACTOR_STORE: "memory",
|
|
76
|
+
ADAPTERS_MFA_TRANSACTION_STORE: "memory",
|
|
77
|
+
REDIS_CLIENTS_URL: "redis://127.0.0.1:9",
|
|
78
|
+
REPOSITORIES_CLIENT_YAML_PATH: clientsFile,
|
|
79
|
+
REPOSITORIES_USER_HTTP_AUTHENTICATE_URL: "http://127.0.0.1:9/authenticate",
|
|
80
|
+
REPOSITORIES_USER_HTTP_AUTHENTICATE_BY_TOKEN_URL: "http://127.0.0.1:9/authenticate-by-token",
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
interface Run {
|
|
84
|
+
readonly stdout: string;
|
|
85
|
+
readonly stderr: string;
|
|
86
|
+
readonly listened: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Starts the template, stops it once it listens (or gives up), and returns what it wrote. */
|
|
90
|
+
function startAndStop(timeoutMs: number): Promise<Run> {
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
const child: ChildProcess = spawn(process.execPath, [tsxCli, "src/app.mts"], {
|
|
93
|
+
cwd: templateRoot,
|
|
94
|
+
env: ENV,
|
|
95
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
96
|
+
});
|
|
97
|
+
let stdout = "";
|
|
98
|
+
let stderr = "";
|
|
99
|
+
let listened = false;
|
|
100
|
+
const stop = (): void => {
|
|
101
|
+
if (child.exitCode === null && child.signalCode === null) child.kill("SIGTERM");
|
|
102
|
+
};
|
|
103
|
+
const deadline = setTimeout(stop, timeoutMs);
|
|
104
|
+
child.stdout?.setEncoding("utf8").on("data", (chunk: string) => {
|
|
105
|
+
stdout += chunk;
|
|
106
|
+
if (!listened && stdout.includes('"msg":"server_listening"')) {
|
|
107
|
+
listened = true;
|
|
108
|
+
stop();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
child.stderr?.setEncoding("utf8").on("data", (chunk: string) => {
|
|
112
|
+
stderr += chunk;
|
|
113
|
+
});
|
|
114
|
+
child.on("error", (err) => {
|
|
115
|
+
clearTimeout(deadline);
|
|
116
|
+
reject(err);
|
|
117
|
+
});
|
|
118
|
+
child.on("close", () => {
|
|
119
|
+
clearTimeout(deadline);
|
|
120
|
+
resolve({ stdout, stderr, listened });
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Each line of `text` that is not one JSON object. */
|
|
126
|
+
function linesThatAreNotJsonObjects(text: string): string[] {
|
|
127
|
+
return text
|
|
128
|
+
.split("\n")
|
|
129
|
+
.filter((line) => line.trim() !== "")
|
|
130
|
+
.filter((line) => {
|
|
131
|
+
try {
|
|
132
|
+
const parsed: unknown = JSON.parse(line);
|
|
133
|
+
return typeof parsed !== "object" || parsed === null || Array.isArray(parsed);
|
|
134
|
+
} catch {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
describe("the template's startup output", () => {
|
|
141
|
+
it("is one JSON object per line on stdout and stderr, in development on the memory stores", async () => {
|
|
142
|
+
const run = await startAndStop(15_000);
|
|
143
|
+
|
|
144
|
+
expect(run.listened, `the template did not listen; stderr:\n${run.stderr}`).toBe(true);
|
|
145
|
+
expect(linesThatAreNotJsonObjects(run.stdout)).toEqual([]);
|
|
146
|
+
expect(linesThatAreNotJsonObjects(run.stderr)).toEqual([]);
|
|
147
|
+
}, 30_000);
|
|
148
|
+
});
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type Switches,
|
|
30
30
|
} from "../configPath.mjs";
|
|
31
31
|
import { templateReference } from "../modules.mjs";
|
|
32
|
+
import { configuredMfaMode } from "./configured-mfa-mode.fixture.mjs";
|
|
32
33
|
import { type BothPhases, withSwitches } from "./library-references.fixture.mjs";
|
|
33
34
|
|
|
34
35
|
// config/ is two levels above this test file:
|
|
@@ -109,15 +110,18 @@ describe("three-tier HOCON resolution (env → application.conf → reference.co
|
|
|
109
110
|
expect(grants(config).authorizationCode?.enabled).toBe("false");
|
|
110
111
|
});
|
|
111
112
|
|
|
112
|
-
it("reads the MFA switch, mfaMode, as
|
|
113
|
-
// The template's reference.conf ships the switch required and binds
|
|
113
|
+
it("reads the MFA switch, mfaMode, as the files write it where MFA_MODE is unset, and installs no MFA module under MFA_MODE=off", () => {
|
|
114
|
+
// The template's reference.conf ships the switch required and binds
|
|
115
|
+
// MFA_MODE; a project's application.conf may write it over that.
|
|
114
116
|
const mfaNames = (config: Switches) =>
|
|
115
117
|
buildModules(config)
|
|
116
118
|
.map((m) => m.name)
|
|
117
119
|
.filter((name) => /mfa/i.test(name));
|
|
118
120
|
const shipped = buildResolvedConfig("development");
|
|
119
|
-
|
|
120
|
-
expect(
|
|
121
|
+
const configured = configuredMfaMode("development");
|
|
122
|
+
expect(shipped.mfaMode).toBe(configured);
|
|
123
|
+
if (configured === "off") expect(mfaNames(shipped)).toEqual([]);
|
|
124
|
+
else expect(mfaNames(shipped)).toContain("mfa");
|
|
121
125
|
const off = buildResolvedConfig("development", { MFA_MODE: "off" });
|
|
122
126
|
expect(off.mfaMode).toBe("off");
|
|
123
127
|
expect(mfaNames(off)).toEqual([]);
|
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
type Switches,
|
|
70
70
|
} from "../configPath.mjs";
|
|
71
71
|
import { MFA_SWITCH } from "../mfaSwitch.mjs";
|
|
72
|
+
import { configuredMfaMode } from "./configured-mfa-mode.fixture.mjs";
|
|
72
73
|
|
|
73
74
|
const configDir = fileURLToPath(new URL("../../config", import.meta.url));
|
|
74
75
|
|
|
@@ -270,7 +271,7 @@ describe("phase one reads federation-grants.enabled as the federation-grants mod
|
|
|
270
271
|
it("reads the MFA switch as its own mfaMode, as the environment sets it, and no key of the MFA module's", () => {
|
|
271
272
|
for (const [name, env] of Object.entries(ENVIRONMENTS)) {
|
|
272
273
|
const switches = readSwitches(readOwnLayers(ownFiles("production"), { env }));
|
|
273
|
-
expect(switches.mfaMode, name).toBe(env.MFA_MODE ?? "
|
|
274
|
+
expect(switches.mfaMode, name).toBe(env.MFA_MODE ?? configuredMfaMode("production"));
|
|
274
275
|
expect(switches, name).not.toHaveProperty("mfa");
|
|
275
276
|
}
|
|
276
277
|
});
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* moved from, and the variables renamed with them, are refused here, before
|
|
22
22
|
* any module is chosen, in the words and under the reasons boot refuses a
|
|
23
23
|
* module's with (`config-path-relocated`, `environment-variable-renamed`): a
|
|
24
|
-
* renamed variable set alone
|
|
24
|
+
* renamed variable set, alone or beside its new name at any value, is
|
|
25
25
|
* refused, naming the variables and never a value.
|
|
26
26
|
*/
|
|
27
27
|
|
|
@@ -128,7 +128,7 @@ export function readAdapters(
|
|
|
128
128
|
`Configuration sets ${moved.length} path(s) that moved: ${moved
|
|
129
129
|
.map(
|
|
130
130
|
({ from, to, environmentVariable }) =>
|
|
131
|
-
`${from} has moved to ${to}; see
|
|
131
|
+
`${from} has moved to ${to}; see the upgrade guide (docs/upgrading-from-v0.16.0.md). Write it there (environment variable ${environmentVariable}) and remove this field from your config (or unset the environment variable that sets it).`,
|
|
132
132
|
)
|
|
133
133
|
.join(" ")}`,
|
|
134
134
|
moved,
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
40
|
import {
|
|
41
|
+
isDevelopmentEnvironment,
|
|
41
42
|
type Module,
|
|
42
43
|
memoryMfaFactorStoreModule,
|
|
43
44
|
memoryMfaTransactionStoreModule,
|
|
@@ -96,13 +97,11 @@ export function readMfaSwitch(
|
|
|
96
97
|
return result.data;
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
/** The names the memory MFA stores are let in under. */
|
|
100
|
-
const DEVELOPMENT_NAMES: ReadonlySet<string> = new Set(["development", "test"]);
|
|
101
|
-
|
|
102
100
|
/**
|
|
103
101
|
* Why the MFA stores may not be kept in memory under `environment` and
|
|
104
|
-
* `CONFIG_ENV` and `NODE_ENV` wherever they are set
|
|
105
|
-
*
|
|
102
|
+
* `CONFIG_ENV` and `NODE_ENV` wherever they are set, as core's
|
|
103
|
+
* `isDevelopmentEnvironment` reads them: each name that is not development
|
|
104
|
+
* or test, or that nothing is named. None lifts another's.
|
|
106
105
|
*/
|
|
107
106
|
function memoryRefusals(environment: string | undefined): string[] {
|
|
108
107
|
const named: [string, string][] =
|
|
@@ -113,10 +112,10 @@ function memoryRefusals(environment: string | undefined): string[] {
|
|
|
113
112
|
}
|
|
114
113
|
if (named.length === 0) return ["no environment is named"];
|
|
115
114
|
return named.flatMap(([label, value]) => {
|
|
115
|
+
if (isDevelopmentEnvironment([value])) return [];
|
|
116
116
|
const production = productionEnvironmentIn([value]);
|
|
117
117
|
if (production !== undefined) return [`${label} is "${production}"`];
|
|
118
|
-
|
|
119
|
-
return DEVELOPMENT_NAMES.has(name) ? [] : [`${label} "${name}" is not development or test`];
|
|
118
|
+
return [`${label} "${readEnvironmentName(value)}" is not development or test`];
|
|
120
119
|
});
|
|
121
120
|
}
|
|
122
121
|
|
|
@@ -338,7 +338,7 @@ export const inMemorySessionStoresModule: Module = defineModule({
|
|
|
338
338
|
* the `adapters.userSessionStores` selection.
|
|
339
339
|
*
|
|
340
340
|
* The memory adapter comes through core's factory: that is where its
|
|
341
|
-
* "dev/test only" boot warning lives
|
|
341
|
+
* "dev/test only" boot warning lives, said on the composition's `logger`.
|
|
342
342
|
*/
|
|
343
343
|
export const inMemoryFederationTokenStoreModule: Module = defineModule({
|
|
344
344
|
name: "standalone-in-memory-federation-token-store",
|
|
@@ -347,10 +347,11 @@ export const inMemoryFederationTokenStoreModule: Module = defineModule({
|
|
|
347
347
|
reason:
|
|
348
348
|
"upstream federation tokens fork per replica — a token stored by the replica that completed the federation callback is missing on the others, so a session cannot refresh its upstream token from another replica, and logout removes only the tokens the replica it lands on can see",
|
|
349
349
|
},
|
|
350
|
+
optional: ["logger"] as const,
|
|
350
351
|
provides: {
|
|
351
|
-
federationTokenStore: async () => {
|
|
352
|
+
federationTokenStore: async ({ logger }) => {
|
|
352
353
|
const factory = createFederationTokenStoreFactory();
|
|
353
|
-
registerBuiltinFederationTokenStores(factory);
|
|
354
|
+
registerBuiltinFederationTokenStores(factory, logger);
|
|
354
355
|
return factory.create({ type: "memory" });
|
|
355
356
|
},
|
|
356
357
|
},
|
|
@@ -408,7 +409,8 @@ export function auditSinkModuleFor(sink: string): Module {
|
|
|
408
409
|
* `redis-clients`, which moved from `refreshTokenFamilyStore.redis` with its
|
|
409
410
|
* variables (`REDIS_CLIENTS_*`). Per-store Redis instances belong in a
|
|
410
411
|
* custom composition root. An empty URL throws rather than falling back to
|
|
411
|
-
* localhost. `
|
|
412
|
+
* localhost. `assumeNoEviction` is handed to every client, for the Redis
|
|
413
|
+
* stores' eviction gate. `io.quit()` is registered once with `lifecycleRegistrar`, so
|
|
412
414
|
* `handle.dispose()` closes the connection.
|
|
413
415
|
*
|
|
414
416
|
* `buildModules` adds this module only when some composed module needs a
|
|
@@ -639,7 +641,11 @@ type StandaloneRedisClients = ReturnType<typeof makeIoredisClients> & {
|
|
|
639
641
|
// registrar (tests that seed none), each call creates a fresh client.
|
|
640
642
|
const clientsCache = new WeakMap<LifecycleRegistrar, StandaloneRedisClients>();
|
|
641
643
|
function getOrCreateClients(
|
|
642
|
-
section: {
|
|
644
|
+
section: {
|
|
645
|
+
readonly url: string;
|
|
646
|
+
readonly password?: string | undefined;
|
|
647
|
+
readonly assumeNoEviction: boolean;
|
|
648
|
+
},
|
|
643
649
|
lifecycleRegistrar: LifecycleRegistrar | undefined,
|
|
644
650
|
readinessRegistrar?: ReadinessRegistrar,
|
|
645
651
|
injectedLogger?: Logger,
|
|
@@ -684,7 +690,7 @@ function getOrCreateClients(
|
|
|
684
690
|
// (`refreshTokenFamilyClient.duplicate()`), which inherit no listeners from
|
|
685
691
|
// `io`; passing the logger lets those report through the same channel.
|
|
686
692
|
const clients: StandaloneRedisClients = {
|
|
687
|
-
...makeIoredisClients(io, { logger }),
|
|
693
|
+
...makeIoredisClients(io, { logger, assumeNoEviction: section.assumeNoEviction }),
|
|
688
694
|
federationGrantStoreClient: makeIoredisFederationGrantStoreClient(io),
|
|
689
695
|
federationGrantIntentStoreClient: makeIoredisFederationGrantIntentStoreClient(io),
|
|
690
696
|
};
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
/**
|
|
18
18
|
* The variables renamed with a path of the composition root's own layers,
|
|
19
19
|
* which no module declares: the adapter selections and the federations the
|
|
20
|
-
* template ships. Phase one refuses an old name set alone
|
|
21
|
-
* name at
|
|
22
|
-
* variable a module declares renamed (`environment-variable-renamed`)
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
20
|
+
* template ships. Phase one refuses an old name set, alone or beside its new
|
|
21
|
+
* name at any value, before any module is chosen, as boot refuses a
|
|
22
|
+
* variable a module declares renamed (`environment-variable-renamed`).
|
|
23
|
+
* Boot's own check cannot hold these: an adapter selection decides which
|
|
24
|
+
* modules are chosen before boot, and a federation's key sits in core's
|
|
25
|
+
* section, whose renames core alone declares.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
import { variablesRenamed } from "./bootRefusal.mjs";
|
|
@@ -41,9 +41,9 @@ export interface RootRename {
|
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Refuses, with an `environment-variable-renamed` `BootError`, an old name in
|
|
44
|
-
* `env` set alone (`unset`), or beside its new name
|
|
45
|
-
*
|
|
46
|
-
* named at once; no value is quoted or carried.
|
|
44
|
+
* `env` set alone (`unset`), or beside its new name (`different`, whatever
|
|
45
|
+
* either holds), in the words core refuses a module's rename in. Every such
|
|
46
|
+
* rename is named at once; no value is compared, quoted or carried.
|
|
47
47
|
*/
|
|
48
48
|
export function refuseRenamedVariables(
|
|
49
49
|
env: Readonly<Record<string, string>>,
|
|
@@ -51,18 +51,17 @@ export function refuseRenamedVariables(
|
|
|
51
51
|
): void {
|
|
52
52
|
const refused = renames.flatMap((rename) => {
|
|
53
53
|
const { from, to } = rename;
|
|
54
|
-
|
|
55
|
-
if (old === undefined || env[to] === old) return [];
|
|
54
|
+
if (env[from] === undefined) return [];
|
|
56
55
|
return [
|
|
57
56
|
{ ...rename, state: env[to] === undefined ? ("unset" as const) : ("different" as const) },
|
|
58
57
|
];
|
|
59
58
|
});
|
|
60
59
|
if (refused.length === 0) return;
|
|
61
60
|
const named = refused.map(({ from, to, path, state }) => {
|
|
62
|
-
const renamed = `${from} was renamed ${to}, the variable ${path} is bound to; see
|
|
61
|
+
const renamed = `${from} was renamed ${to}, the variable ${path} is bound to; see the upgrade guide (docs/upgrading-from-v0.16.0.md).`;
|
|
63
62
|
return state === "unset"
|
|
64
63
|
? `${renamed} Set ${to} instead and unset ${from}.`
|
|
65
|
-
: `${renamed} ${to} is set
|
|
64
|
+
: `${renamed} ${to} is set as well: keep the value you mean in ${to} and unset ${from}.`;
|
|
66
65
|
});
|
|
67
66
|
throw variablesRenamed(
|
|
68
67
|
`The environment sets ${refused.length} variable(s) that were renamed: ${named.join(" ")}`,
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
import {
|
|
30
30
|
checkSerializedOrigin,
|
|
31
31
|
checkTrustedProxyEntry,
|
|
32
|
+
coerceBooleanFromEnv,
|
|
32
33
|
describeSerializedOriginRejection,
|
|
33
34
|
describeTrustedProxyEntryRejection,
|
|
34
35
|
isWellFormedKid,
|
|
@@ -239,9 +240,18 @@ export const keyStoreSectionSchema = z
|
|
|
239
240
|
})
|
|
240
241
|
.strict();
|
|
241
242
|
|
|
242
|
-
/**
|
|
243
|
+
/**
|
|
244
|
+
* `redis-clients`: the one Redis connection every Redis-backed store shares.
|
|
245
|
+
* `assumeNoEviction` is the operator's assertion that the server runs
|
|
246
|
+
* `maxmemory-policy noeviction`, read by the Redis stores' eviction gate only
|
|
247
|
+
* where the server will not report its policy.
|
|
248
|
+
*/
|
|
243
249
|
export const redisClientsSectionSchema = z
|
|
244
|
-
.object({
|
|
250
|
+
.object({
|
|
251
|
+
url: z.string(),
|
|
252
|
+
password: z.string().optional(),
|
|
253
|
+
assumeNoEviction: coerceBooleanFromEnv.default(false),
|
|
254
|
+
})
|
|
245
255
|
.strict();
|
|
246
256
|
|
|
247
257
|
/**
|
package/templates/versions.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
|
-
"@o3co/auth-provider-core": "0.17.0-rc.
|
|
3
|
-
"@o3co/auth-provider-device-grant": "0.17.0-rc.
|
|
4
|
-
"@o3co/auth-provider-dpop": "0.17.0-rc.
|
|
5
|
-
"@o3co/auth-provider-mtls": "0.17.0-rc.
|
|
6
|
-
"@o3co/auth-provider-federation-apple": "0.17.0-rc.
|
|
7
|
-
"@o3co/auth-provider-federation-google": "0.17.0-rc.
|
|
8
|
-
"@o3co/auth-provider-federation-github": "0.17.0-rc.
|
|
9
|
-
"@o3co/auth-provider-federation-grants": "0.17.0-rc.
|
|
10
|
-
"@o3co/auth-provider-federation-oidc": "0.17.0-rc.
|
|
11
|
-
"@o3co/auth-provider-oauth": "0.17.0-rc.
|
|
12
|
-
"@o3co/auth-provider-oauth-token-exchange": "0.17.0-rc.
|
|
13
|
-
"@o3co/auth-provider-session": "0.17.0-rc.
|
|
14
|
-
"@o3co/auth-provider-foundation": "0.17.0-rc.
|
|
15
|
-
"@o3co/auth-provider-mfa": "0.17.0-rc.
|
|
16
|
-
"@o3co/auth-provider-redis": "0.17.0-rc.
|
|
17
|
-
"@o3co/auth-provider-standard": "0.17.0-rc.
|
|
18
|
-
"@o3co/auth-provider-test-kit": "0.17.0-rc.
|
|
19
|
-
"@o3co/auth-provider-webauthn": "0.17.0-rc.
|
|
2
|
+
"@o3co/auth-provider-core": "0.17.0-rc.3",
|
|
3
|
+
"@o3co/auth-provider-device-grant": "0.17.0-rc.3",
|
|
4
|
+
"@o3co/auth-provider-dpop": "0.17.0-rc.3",
|
|
5
|
+
"@o3co/auth-provider-mtls": "0.17.0-rc.3",
|
|
6
|
+
"@o3co/auth-provider-federation-apple": "0.17.0-rc.3",
|
|
7
|
+
"@o3co/auth-provider-federation-google": "0.17.0-rc.3",
|
|
8
|
+
"@o3co/auth-provider-federation-github": "0.17.0-rc.3",
|
|
9
|
+
"@o3co/auth-provider-federation-grants": "0.17.0-rc.3",
|
|
10
|
+
"@o3co/auth-provider-federation-oidc": "0.17.0-rc.3",
|
|
11
|
+
"@o3co/auth-provider-oauth": "0.17.0-rc.3",
|
|
12
|
+
"@o3co/auth-provider-oauth-token-exchange": "0.17.0-rc.3",
|
|
13
|
+
"@o3co/auth-provider-session": "0.17.0-rc.3",
|
|
14
|
+
"@o3co/auth-provider-foundation": "0.17.0-rc.3",
|
|
15
|
+
"@o3co/auth-provider-mfa": "0.17.0-rc.3",
|
|
16
|
+
"@o3co/auth-provider-redis": "0.17.0-rc.3",
|
|
17
|
+
"@o3co/auth-provider-standard": "0.17.0-rc.3",
|
|
18
|
+
"@o3co/auth-provider-test-kit": "0.17.0-rc.3",
|
|
19
|
+
"@o3co/auth-provider-webauthn": "0.17.0-rc.3"
|
|
20
20
|
}
|