@o3co/create-auth-provider 0.17.0-rc.4 → 0.17.0
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/package.json +1 -1
- package/templates/standalone/README.ja.md +2 -3
- package/templates/standalone/README.md +2 -3
- package/templates/standalone/config/clients.yaml.example +11 -5
- package/templates/standalone/config/reference.conf +5 -2
- package/templates/standalone/package.json +2 -1
- package/templates/standalone/src/__tests__/all-modules-composition.fixture.mts +6 -10
- package/templates/standalone/src/__tests__/all-modules-composition.test.mts +2 -2
- package/templates/standalone/src/__tests__/audit-sink.test.mts +13 -4
- package/templates/standalone/src/__tests__/backchannel-peer.fixture.mts +64 -0
- package/templates/standalone/src/__tests__/documented-env-overrides.test.mts +11 -10
- package/templates/standalone/src/__tests__/federation-grants-composition.test.mts +7 -4
- package/templates/standalone/src/__tests__/federation-grants-survive-logout.test.mts +7 -4
- package/templates/standalone/src/__tests__/frontchannel-logout-csp.test.mts +168 -0
- package/templates/standalone/src/__tests__/google-session.fixture.mts +17 -7
- package/templates/standalone/src/__tests__/lifecycle-pending-close.test.mts +13 -12
- package/templates/standalone/src/__tests__/own-modules.test.mts +3 -35
- package/templates/standalone/src/__tests__/redis-assume-noeviction.test.mts +73 -4
- package/templates/standalone/src/__tests__/redis-stand-in.fixture.mts +98 -0
- package/templates/standalone/src/__tests__/replica-safety.test.mts +7 -4
- package/templates/standalone/src/__tests__/sections.test.mts +15 -3
- package/templates/standalone/src/__tests__/session-lifecycle.test.mts +8 -10
- package/templates/standalone/src/__tests__/session-logout-invalidates-token.test.mts +13 -4
- package/templates/standalone/src/__tests__/session-logout-lifecycle.test.mts +7 -7
- package/templates/standalone/src/__tests__/smoke.test.mts +13 -5
- package/templates/standalone/src/__tests__/startup-log-lines.test.mts +24 -8
- package/templates/standalone/src/__tests__/store-credential.test.mts +7 -5
- package/templates/standalone/src/__tests__/upstream-amr.acceptance.test.mts +7 -4
- package/templates/standalone/src/app.mts +2 -11
- package/templates/standalone/src/sections.mts +3 -2
- package/templates/standalone/src/securityHeaders.mts +36 -0
- package/templates/versions.json +18 -18
|
@@ -36,7 +36,7 @@ import request from "supertest";
|
|
|
36
36
|
import { afterEach, describe, expect, it } from "vitest";
|
|
37
37
|
import { buildModules } from "../buildModules.mjs";
|
|
38
38
|
import type { Switches } from "../configPath.mjs";
|
|
39
|
-
import type
|
|
39
|
+
import { type Adapters, keyStoreSectionSchema } from "../sections.mjs";
|
|
40
40
|
import {
|
|
41
41
|
capturedRenames,
|
|
42
42
|
inProcessAdapters,
|
|
@@ -110,6 +110,12 @@ const config: AppConfig & Record<string, unknown> = {
|
|
|
110
110
|
},
|
|
111
111
|
rateLimit: { failMode: "open" },
|
|
112
112
|
"standalone-in-memory-code-repository": { defaultExpiresIn: 600 },
|
|
113
|
+
// The in-process limiter's section as core's `reference.conf` ships it.
|
|
114
|
+
"core-rate-limiter-memory": {
|
|
115
|
+
maxBuckets: 10000,
|
|
116
|
+
defaultLimit: { limit: 60, windowSeconds: 60 },
|
|
117
|
+
limits: {},
|
|
118
|
+
},
|
|
113
119
|
};
|
|
114
120
|
|
|
115
121
|
/** What phase one hands `buildModules`: the configuration, with `changes` over every store in process. */
|
|
@@ -165,16 +171,18 @@ const testRepositoriesModule = defineModule({
|
|
|
165
171
|
},
|
|
166
172
|
});
|
|
167
173
|
|
|
174
|
+
// The template's `key-store` section, read by its own schema, without the
|
|
175
|
+
// section's relocation bridge.
|
|
168
176
|
const testKeyStoreModule = defineModule({
|
|
169
|
-
name: "
|
|
170
|
-
|
|
177
|
+
name: "key-store",
|
|
178
|
+
section: { schema: keyStoreSectionSchema },
|
|
171
179
|
provides: {
|
|
172
|
-
keyStore: async ({
|
|
180
|
+
keyStore: async ({ section }) => {
|
|
173
181
|
const factory = createKeyStoreFactory();
|
|
174
182
|
registerBuiltinKeyStores(factory);
|
|
175
183
|
return factory.create({
|
|
176
184
|
type: "local",
|
|
177
|
-
...(
|
|
185
|
+
...(section.local ?? {}),
|
|
178
186
|
});
|
|
179
187
|
},
|
|
180
188
|
},
|
|
@@ -28,7 +28,8 @@ import { createRequire } from "node:module";
|
|
|
28
28
|
import { tmpdir } from "node:os";
|
|
29
29
|
import { join } from "node:path";
|
|
30
30
|
import { fileURLToPath } from "node:url";
|
|
31
|
-
import { describe, expect, it } from "vitest";
|
|
31
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
32
|
+
import { listeningRedis } from "./redis-stand-in.fixture.mjs";
|
|
32
33
|
|
|
33
34
|
const templateRoot = fileURLToPath(new URL("../..", import.meta.url));
|
|
34
35
|
const tsxCli = createRequire(import.meta.url).resolve("tsx/cli");
|
|
@@ -45,14 +46,29 @@ const clientsFile = (() => {
|
|
|
45
46
|
return file;
|
|
46
47
|
})();
|
|
47
48
|
|
|
49
|
+
/**
|
|
50
|
+
* The Redis the refresh-token family store's shared socket dials: a stand-in
|
|
51
|
+
* that reports `noeviction`, so the store passes its eviction gate and the run
|
|
52
|
+
* reaches no real server.
|
|
53
|
+
*/
|
|
54
|
+
let redis: Awaited<ReturnType<typeof listeningRedis>>;
|
|
55
|
+
|
|
56
|
+
beforeAll(async () => {
|
|
57
|
+
redis = await listeningRedis();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterAll(async () => {
|
|
61
|
+
await redis?.close();
|
|
62
|
+
});
|
|
63
|
+
|
|
48
64
|
/**
|
|
49
65
|
* Development, with every store the `adapters` section selects in memory.
|
|
50
66
|
* The refresh-token family store has no memory choice in the template; its
|
|
51
|
-
* shared Redis socket points at
|
|
52
|
-
*
|
|
53
|
-
*
|
|
67
|
+
* shared Redis socket points at the stand-in above. Nothing is inherited from
|
|
68
|
+
* the test runner's environment but PATH, so its NODE_ENV and LOGGING_LEVEL do
|
|
69
|
+
* not reach the child.
|
|
54
70
|
*/
|
|
55
|
-
const
|
|
71
|
+
const env = (): Readonly<Record<string, string>> => ({
|
|
56
72
|
PATH: process.env.PATH ?? "",
|
|
57
73
|
CONFIG_ENV: "development",
|
|
58
74
|
NODE_ENV: "development",
|
|
@@ -74,11 +90,11 @@ const ENV: Readonly<Record<string, string>> = {
|
|
|
74
90
|
ADAPTERS_FEDERATION_TOKEN_STORE: "memory",
|
|
75
91
|
ADAPTERS_MFA_FACTOR_STORE: "memory",
|
|
76
92
|
ADAPTERS_MFA_TRANSACTION_STORE: "memory",
|
|
77
|
-
REDIS_CLIENTS_URL:
|
|
93
|
+
REDIS_CLIENTS_URL: `redis://127.0.0.1:${redis.port}`,
|
|
78
94
|
REPOSITORIES_CLIENT_YAML_PATH: clientsFile,
|
|
79
95
|
REPOSITORIES_USER_HTTP_AUTHENTICATE_URL: "http://127.0.0.1:9/authenticate",
|
|
80
96
|
REPOSITORIES_USER_HTTP_AUTHENTICATE_BY_TOKEN_URL: "http://127.0.0.1:9/authenticate-by-token",
|
|
81
|
-
};
|
|
97
|
+
});
|
|
82
98
|
|
|
83
99
|
interface Run {
|
|
84
100
|
readonly stdout: string;
|
|
@@ -91,7 +107,7 @@ function startAndStop(timeoutMs: number): Promise<Run> {
|
|
|
91
107
|
return new Promise((resolve, reject) => {
|
|
92
108
|
const child: ChildProcess = spawn(process.execPath, [tsxCli, "src/app.mts"], {
|
|
93
109
|
cwd: templateRoot,
|
|
94
|
-
env:
|
|
110
|
+
env: env(),
|
|
95
111
|
stdio: ["ignore", "pipe", "pipe"],
|
|
96
112
|
});
|
|
97
113
|
let stdout = "";
|
|
@@ -48,7 +48,7 @@ import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
|
|
|
48
48
|
import { buildModules } from "../buildModules.mjs";
|
|
49
49
|
import { resolveConfigPaths } from "../configPath.mjs";
|
|
50
50
|
import { repositoriesModuleFor, templateReference } from "../modules.mjs";
|
|
51
|
-
import { repositoriesSectionSchema } from "../sections.mjs";
|
|
51
|
+
import { keyStoreSectionSchema, repositoriesSectionSchema } from "../sections.mjs";
|
|
52
52
|
import {
|
|
53
53
|
type BothPhases,
|
|
54
54
|
bothPhasesOf,
|
|
@@ -186,16 +186,18 @@ describe("a token the Store refuses, seen from outside the booted app", () => {
|
|
|
186
186
|
ADAPTERS_FEDERATION_GRANT_INTENT_STORE: "memory",
|
|
187
187
|
};
|
|
188
188
|
|
|
189
|
+
// The template's `key-store` section, read by its own schema, without the
|
|
190
|
+
// section's relocation bridge.
|
|
189
191
|
const testKeyStoreModule = defineModule({
|
|
190
|
-
name: "
|
|
191
|
-
|
|
192
|
+
name: "key-store",
|
|
193
|
+
section: { schema: keyStoreSectionSchema },
|
|
192
194
|
provides: {
|
|
193
|
-
keyStore: async ({
|
|
195
|
+
keyStore: async ({ section }) => {
|
|
194
196
|
const factory = createKeyStoreFactory();
|
|
195
197
|
registerBuiltinKeyStores(factory);
|
|
196
198
|
return factory.create({
|
|
197
199
|
type: "local",
|
|
198
|
-
...(
|
|
200
|
+
...(section.local ?? {}),
|
|
199
201
|
});
|
|
200
202
|
},
|
|
201
203
|
},
|
|
@@ -50,6 +50,7 @@ import { afterEach, describe, expect, it } from "vitest";
|
|
|
50
50
|
import { buildModules } from "#/buildModules.mjs";
|
|
51
51
|
import { resolveConfigPaths } from "#/configPath.mjs";
|
|
52
52
|
import { templateReference } from "../modules.mjs";
|
|
53
|
+
import { keyStoreSectionSchema } from "../sections.mjs";
|
|
53
54
|
import {
|
|
54
55
|
type BothPhases,
|
|
55
56
|
bothPhasesOf,
|
|
@@ -181,16 +182,18 @@ const testRepositoriesModule = defineModule({
|
|
|
181
182
|
} as never,
|
|
182
183
|
});
|
|
183
184
|
|
|
185
|
+
// The template's `key-store` section, read by its own schema, without the
|
|
186
|
+
// section's relocation bridge.
|
|
184
187
|
const testKeyStoreModule = defineModule({
|
|
185
|
-
name: "
|
|
186
|
-
|
|
188
|
+
name: "key-store",
|
|
189
|
+
section: { schema: keyStoreSectionSchema },
|
|
187
190
|
provides: {
|
|
188
|
-
keyStore: async ({
|
|
191
|
+
keyStore: async ({ section }) => {
|
|
189
192
|
const factory = createKeyStoreFactory();
|
|
190
193
|
registerBuiltinKeyStores(factory);
|
|
191
194
|
return factory.create({
|
|
192
195
|
type: "local",
|
|
193
|
-
...(
|
|
196
|
+
...(section.local ?? {}),
|
|
194
197
|
});
|
|
195
198
|
},
|
|
196
199
|
},
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
17
|
import { createApp } from "@o3co/auth-provider-core";
|
|
18
18
|
import express from "express";
|
|
19
|
-
import helmet from "helmet";
|
|
20
19
|
import { buildModules } from "./buildModules.mjs";
|
|
21
20
|
import {
|
|
22
21
|
configDefaultsFor,
|
|
@@ -31,6 +30,7 @@ import { listen } from "./listen.mjs";
|
|
|
31
30
|
import { createAppLogger } from "./logger.mjs";
|
|
32
31
|
import { createMetrics } from "./metrics.mjs";
|
|
33
32
|
import { mountRoutes } from "./routes.mjs";
|
|
33
|
+
import { securityHeaders } from "./securityHeaders.mjs";
|
|
34
34
|
import { installGracefulShutdown } from "./shutdown.mjs";
|
|
35
35
|
|
|
36
36
|
// Step 1: the configuration, in two phases (`configPath.mts`; template
|
|
@@ -57,16 +57,7 @@ await (async (): Promise<void> => {
|
|
|
57
57
|
// `trust proxy` is set from the `http` module's `httpSettings` once boot
|
|
58
58
|
// has it (step 3), before a request can arrive.
|
|
59
59
|
const app = express();
|
|
60
|
-
app.use(
|
|
61
|
-
helmet({
|
|
62
|
-
contentSecurityPolicy: {
|
|
63
|
-
directives: {
|
|
64
|
-
defaultSrc: ["'none'"],
|
|
65
|
-
frameAncestors: ["'none'"],
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
}),
|
|
69
|
-
);
|
|
60
|
+
app.use(securityHeaders());
|
|
70
61
|
|
|
71
62
|
// Timing middleware goes ahead of everything so the histogram covers the
|
|
72
63
|
// whole stack — including responses produced by middleware that short-circuits
|
|
@@ -244,13 +244,14 @@ export const keyStoreSectionSchema = z
|
|
|
244
244
|
* `redis-clients`: the one Redis connection every Redis-backed store shares.
|
|
245
245
|
* `assumeNoEviction` is the operator's assertion that the server runs
|
|
246
246
|
* `maxmemory-policy noeviction`, read by the Redis stores' eviction gate only
|
|
247
|
-
* where the server will not report its policy
|
|
247
|
+
* where the server will not report its policy; the template's
|
|
248
|
+
* `config/reference.conf` ships it false.
|
|
248
249
|
*/
|
|
249
250
|
export const redisClientsSectionSchema = z
|
|
250
251
|
.object({
|
|
251
252
|
url: z.string(),
|
|
252
253
|
password: z.string().optional(),
|
|
253
|
-
assumeNoEviction: coerceBooleanFromEnv
|
|
254
|
+
assumeNoEviction: coerceBooleanFromEnv,
|
|
254
255
|
})
|
|
255
256
|
.strict();
|
|
256
257
|
|
|
@@ -0,0 +1,36 @@
|
|
|
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 security headers, on every response: helmet's defaults with
|
|
19
|
+
* a Content-Security-Policy that allows nothing by default and no framing. A
|
|
20
|
+
* route whose page needs more sets that response's own policy in place of
|
|
21
|
+
* this one.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { RequestHandler } from "express";
|
|
25
|
+
import helmet from "helmet";
|
|
26
|
+
|
|
27
|
+
export function securityHeaders(): RequestHandler {
|
|
28
|
+
return helmet({
|
|
29
|
+
contentSecurityPolicy: {
|
|
30
|
+
directives: {
|
|
31
|
+
defaultSrc: ["'none'"],
|
|
32
|
+
frameAncestors: ["'none'"],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
package/templates/versions.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
|
-
"@o3co/auth-provider-core": "0.17.0
|
|
3
|
-
"@o3co/auth-provider-device-grant": "0.17.0
|
|
4
|
-
"@o3co/auth-provider-dpop": "0.17.0
|
|
5
|
-
"@o3co/auth-provider-mtls": "0.17.0
|
|
6
|
-
"@o3co/auth-provider-federation-apple": "0.17.0
|
|
7
|
-
"@o3co/auth-provider-federation-google": "0.17.0
|
|
8
|
-
"@o3co/auth-provider-federation-github": "0.17.0
|
|
9
|
-
"@o3co/auth-provider-federation-grants": "0.17.0
|
|
10
|
-
"@o3co/auth-provider-federation-oidc": "0.17.0
|
|
11
|
-
"@o3co/auth-provider-oauth": "0.17.0
|
|
12
|
-
"@o3co/auth-provider-oauth-token-exchange": "0.17.0
|
|
13
|
-
"@o3co/auth-provider-session": "0.17.0
|
|
14
|
-
"@o3co/auth-provider-foundation": "0.17.0
|
|
15
|
-
"@o3co/auth-provider-mfa": "0.17.0
|
|
16
|
-
"@o3co/auth-provider-redis": "0.17.0
|
|
17
|
-
"@o3co/auth-provider-standard": "0.17.0
|
|
18
|
-
"@o3co/auth-provider-test-kit": "0.17.0
|
|
19
|
-
"@o3co/auth-provider-webauthn": "0.17.0
|
|
2
|
+
"@o3co/auth-provider-core": "0.17.0",
|
|
3
|
+
"@o3co/auth-provider-device-grant": "0.17.0",
|
|
4
|
+
"@o3co/auth-provider-dpop": "0.17.0",
|
|
5
|
+
"@o3co/auth-provider-mtls": "0.17.0",
|
|
6
|
+
"@o3co/auth-provider-federation-apple": "0.17.0",
|
|
7
|
+
"@o3co/auth-provider-federation-google": "0.17.0",
|
|
8
|
+
"@o3co/auth-provider-federation-github": "0.17.0",
|
|
9
|
+
"@o3co/auth-provider-federation-grants": "0.17.0",
|
|
10
|
+
"@o3co/auth-provider-federation-oidc": "0.17.0",
|
|
11
|
+
"@o3co/auth-provider-oauth": "0.17.0",
|
|
12
|
+
"@o3co/auth-provider-oauth-token-exchange": "0.17.0",
|
|
13
|
+
"@o3co/auth-provider-session": "0.17.0",
|
|
14
|
+
"@o3co/auth-provider-foundation": "0.17.0",
|
|
15
|
+
"@o3co/auth-provider-mfa": "0.17.0",
|
|
16
|
+
"@o3co/auth-provider-redis": "0.17.0",
|
|
17
|
+
"@o3co/auth-provider-standard": "0.17.0",
|
|
18
|
+
"@o3co/auth-provider-test-kit": "0.17.0",
|
|
19
|
+
"@o3co/auth-provider-webauthn": "0.17.0"
|
|
20
20
|
}
|