@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,122 +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 MFA routes' budget the template ships: every `/session/mfa` POST is
|
|
19
|
-
* limited under the prefix `mfa` by the wired limiter alone, and the
|
|
20
|
-
* template's own `reference.conf` gives both limiters `limits.mfa`, 60
|
|
21
|
-
* requests per 300 s. Followed through both configuration phases and
|
|
22
|
-
* `createApp` to the limiter each module builds, in every environment the
|
|
23
|
-
* template ships a file for.
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { fileURLToPath } from "node:url";
|
|
27
|
-
import {
|
|
28
|
-
createApp,
|
|
29
|
-
defineModule,
|
|
30
|
-
memoryRateLimiterModule,
|
|
31
|
-
type RateLimiter,
|
|
32
|
-
} from "@o3co/auth-provider-core";
|
|
33
|
-
import { redisRateLimiterModule } from "@o3co/auth-provider-redis";
|
|
34
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
35
|
-
import { readOwnLayers, readSwitches, resolveConfigPaths, resolveForBoot } from "../configPath.mjs";
|
|
36
|
-
import { loggingModule } from "../modules.mjs";
|
|
37
|
-
|
|
38
|
-
const configDir = fileURLToPath(new URL("../../config", import.meta.url));
|
|
39
|
-
|
|
40
|
-
/** The secrets `application.conf` substitutes: test-only values past the entropy floor. */
|
|
41
|
-
const SECRETS = {
|
|
42
|
-
KEY_STORE_LOCAL_SECRET: "test-secret-mfa-routes-budget.at-least-32-bytes.ok",
|
|
43
|
-
OAUTH_JWT_ISSUER: "https://auth.test",
|
|
44
|
-
SESSION_STORE_SECRET: "test-session-secret-mfa-routes-budget.at-least-32-bytes.ok",
|
|
45
|
-
MFA_MODE: "off",
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/** The windows, in seconds, the Redis limiter asked its client to count over. */
|
|
49
|
-
const windows: number[] = [];
|
|
50
|
-
|
|
51
|
-
/** The limiter a consumer of the slot is handed, as the MFA routes are. */
|
|
52
|
-
let consumed: RateLimiter | undefined;
|
|
53
|
-
const consumerModule = defineModule({
|
|
54
|
-
name: "test:rate-limiter-consumer",
|
|
55
|
-
requires: ["rateLimiter"] as const,
|
|
56
|
-
contributes: {
|
|
57
|
-
grantMiddleware: [
|
|
58
|
-
({ rateLimiter }) => {
|
|
59
|
-
consumed = rateLimiter;
|
|
60
|
-
return null;
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const handles: { dispose(): Promise<void> }[] = [];
|
|
67
|
-
afterEach(async () => {
|
|
68
|
-
await Promise.all(handles.splice(0).map((handle) => handle.dispose()));
|
|
69
|
-
consumed = undefined;
|
|
70
|
-
windows.splice(0);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
/** The shipped configuration for `environment`, booted with the limiter `adapter` selects. */
|
|
74
|
-
async function shippedLimiter(
|
|
75
|
-
environment: string,
|
|
76
|
-
adapter: "memory" | "redis",
|
|
77
|
-
): Promise<RateLimiter> {
|
|
78
|
-
const env = { ...SECRETS, ADAPTERS_RATE_LIMITER: adapter };
|
|
79
|
-
const { applicationConfPath, envConfPath } = resolveConfigPaths(configDir, environment);
|
|
80
|
-
const own = readOwnLayers([envConfPath, applicationConfPath], { env });
|
|
81
|
-
const switches = readSwitches(own);
|
|
82
|
-
expect(switches.adapters.rateLimiter).toBe(adapter);
|
|
83
|
-
const limiterModule = adapter === "redis" ? redisRateLimiterModule : memoryRateLimiterModule;
|
|
84
|
-
const modules = [limiterModule, consumerModule];
|
|
85
|
-
const handle = await createApp({
|
|
86
|
-
modules,
|
|
87
|
-
bootstrapComponents: {
|
|
88
|
-
config: resolveForBoot(own, [...modules, loggingModule], switches),
|
|
89
|
-
pathResolver: (s: string) => s,
|
|
90
|
-
// Counts one request per key, the way an untouched Redis counter would.
|
|
91
|
-
rateLimiterClient: {
|
|
92
|
-
incrementWithTtl: async (_key: string, windowSeconds: number) => {
|
|
93
|
-
windows.push(windowSeconds);
|
|
94
|
-
return 1;
|
|
95
|
-
},
|
|
96
|
-
} as never,
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
handles.push(handle);
|
|
100
|
-
if (consumed === undefined) throw new Error("the boot handed no consumer the limiter");
|
|
101
|
-
return consumed;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
describe("the MFA routes' budget the template ships", () => {
|
|
105
|
-
it.each([
|
|
106
|
-
["development", "memory"],
|
|
107
|
-
["development", "redis"],
|
|
108
|
-
["production", "memory"],
|
|
109
|
-
["production", "redis"],
|
|
110
|
-
] as const)("is 60 per 300 s under %s on the %s limiter", async (environment, adapter) => {
|
|
111
|
-
const limiter = await shippedLimiter(environment, adapter);
|
|
112
|
-
const before = Date.now();
|
|
113
|
-
const decision = await limiter.check("mfa:ip:203.0.113.7", { ip: "203.0.113.7" });
|
|
114
|
-
expect(decision.allowed).toBe(true);
|
|
115
|
-
expect(decision.limit).toBe(60);
|
|
116
|
-
const windowSeconds =
|
|
117
|
-
adapter === "redis"
|
|
118
|
-
? windows[0]
|
|
119
|
-
: Math.round(((decision.resetAt?.getTime() ?? 0) - before) / 1000);
|
|
120
|
-
expect(windowSeconds).toBe(300);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
@@ -1,127 +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 MFA switch under the name production, where the MFA stores
|
|
19
|
-
* must be shared: both on Redis, as a production deployment selects them.
|
|
20
|
-
* ioredis is a stand-in, as in `all-modules-composition.multi.test.mts`: it
|
|
21
|
-
* answers `INFO memory` as a default server does, so the stores' eviction gate
|
|
22
|
-
* passes, and their persistence check names what it could not read and goes
|
|
23
|
-
* on. `packages/redis` runs the stores against a real server.
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { BootError } from "@o3co/auth-provider-core";
|
|
27
|
-
import { MFA_DEVELOPMENT_SAMPLE_KEY } from "@o3co/auth-provider-mfa";
|
|
28
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
29
|
-
import { type Composition, compose, SINGLE_ENV } from "./all-modules-composition.fixture.mjs";
|
|
30
|
-
|
|
31
|
-
vi.mock("ioredis", () => {
|
|
32
|
-
// Constructible, quits cleanly, answers the readiness probe's `ping`;
|
|
33
|
-
// every other command resolves to nothing.
|
|
34
|
-
const explicit: Record<string, unknown> = {
|
|
35
|
-
on: () => undefined,
|
|
36
|
-
quit: async () => "OK",
|
|
37
|
-
disconnect: () => undefined,
|
|
38
|
-
ping: async () => "PONG",
|
|
39
|
-
// The Redis stores' eviction gate reads the policy from INFO memory: a
|
|
40
|
-
// default server's.
|
|
41
|
-
info: async (section: string) =>
|
|
42
|
-
section === "memory" ? "# Memory\r\nmaxmemory_policy:noeviction\r\n" : null,
|
|
43
|
-
};
|
|
44
|
-
const makeMockRedis = (): object =>
|
|
45
|
-
new Proxy(
|
|
46
|
-
{},
|
|
47
|
-
{
|
|
48
|
-
get(_target, prop) {
|
|
49
|
-
// A function-valued `then` would make the instance a thenable.
|
|
50
|
-
if (typeof prop !== "string" || prop === "then") return undefined;
|
|
51
|
-
if (prop === "duplicate") return makeMockRedis;
|
|
52
|
-
if (prop in explicit) return explicit[prop];
|
|
53
|
-
return async () => null;
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
);
|
|
57
|
-
function MockRedis(): object {
|
|
58
|
-
return makeMockRedis();
|
|
59
|
-
}
|
|
60
|
-
return { Redis: MockRedis, default: MockRedis };
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
let current: Composition | undefined;
|
|
64
|
-
|
|
65
|
-
afterEach(async () => {
|
|
66
|
-
await current?.handle.dispose();
|
|
67
|
-
current = undefined;
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
/** MFA required in production, both MFA stores on Redis, a key of the deployment's own and an SMTP relay. */
|
|
71
|
-
const PRODUCTION_ENV: Readonly<Record<string, string>> = {
|
|
72
|
-
...SINGLE_ENV,
|
|
73
|
-
MFA_MODE: "required",
|
|
74
|
-
ADAPTERS_MFA_FACTOR_STORE: "redis",
|
|
75
|
-
ADAPTERS_MFA_TRANSACTION_STORE: "redis",
|
|
76
|
-
REDIS_CLIENTS_URL: "redis://redis.test:6379",
|
|
77
|
-
MFA_ENCRYPTION_KEY: Buffer.alloc(32, 9).toString("base64"),
|
|
78
|
-
STANDARD_SMTP_MAIL_SENDER_HOST: "smtp.auth.test",
|
|
79
|
-
STANDARD_SMTP_MAIL_SENDER_FROM: "auth@auth.test",
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
/** An error's message with every cause's under it. */
|
|
83
|
-
const textOf = (err: unknown): string => {
|
|
84
|
-
const parts: string[] = [];
|
|
85
|
-
for (let at: unknown = err; at instanceof Error; at = at.cause) parts.push(at.message);
|
|
86
|
-
return parts.join(" <- ");
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const refusal = (composing: Promise<Composition>): Promise<unknown> =>
|
|
90
|
-
composing.then(
|
|
91
|
-
(composition) => {
|
|
92
|
-
current = composition;
|
|
93
|
-
return undefined;
|
|
94
|
-
},
|
|
95
|
-
(caught: unknown) => caught,
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
describe("MFA_MODE=required under the name production, the MFA stores on Redis", () => {
|
|
99
|
-
it("boots, the requirement registered as the second-factor authority", async () => {
|
|
100
|
-
current = await compose({ env: PRODUCTION_ENV, environment: "production" });
|
|
101
|
-
const names = current.modules.map((m) => m.name);
|
|
102
|
-
expect(names).toContain("redis-mfa-factor-store");
|
|
103
|
-
expect(names).toContain("redis-mfa-transaction-store");
|
|
104
|
-
expect(
|
|
105
|
-
current.handle.components.sessionRequirementResolver?.get("mfa")?.secondFactorAuthority,
|
|
106
|
-
).toBe(true);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("refuses the development sample key, naming MFA_ENCRYPTION_KEY and quoting no key", async () => {
|
|
110
|
-
const err = await refusal(
|
|
111
|
-
compose({
|
|
112
|
-
env: { ...PRODUCTION_ENV, MFA_ENCRYPTION_KEY: MFA_DEVELOPMENT_SAMPLE_KEY },
|
|
113
|
-
environment: "production",
|
|
114
|
-
}),
|
|
115
|
-
);
|
|
116
|
-
expect(err).toBeInstanceOf(BootError);
|
|
117
|
-
expect(textOf(err)).toContain("MFA_ENCRYPTION_KEY");
|
|
118
|
-
expect(textOf(err)).not.toContain(MFA_DEVELOPMENT_SAMPLE_KEY);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("refuses without an SMTP relay, naming STANDARD_SMTP_MAIL_SENDER_HOST", async () => {
|
|
122
|
-
const { STANDARD_SMTP_MAIL_SENDER_HOST: _host, ...env } = PRODUCTION_ENV;
|
|
123
|
-
const err = await refusal(compose({ env, environment: "production" }));
|
|
124
|
-
expect(err).toBeInstanceOf(BootError);
|
|
125
|
-
expect(textOf(err)).toContain("STANDARD_SMTP_MAIL_SENDER_HOST");
|
|
126
|
-
});
|
|
127
|
-
});
|