@o3co/create-auth-provider 0.17.0-rc.4 → 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 -913
- package/templates/standalone/README.md +0 -1537
- package/templates/standalone/config/application.conf +0 -351
- package/templates/standalone/config/clients.yaml +0 -4
- package/templates/standalone/config/clients.yaml.example +0 -118
- 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 -424
- 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 -55
- 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 -1192
- 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 -346
- 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 -1290
- package/templates/standalone/src/__tests__/federation-grants-composition.test.mts +0 -784
- package/templates/standalone/src/__tests__/federation-grants-survive-logout.test.mts +0 -461
- 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__/google-session.fixture.mts +0 -308
- 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 -216
- 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 -1512
- package/templates/standalone/src/__tests__/post-logout-redirect.test.mts +0 -183
- package/templates/standalone/src/__tests__/redis-assume-noeviction.test.mts +0 -214
- 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__/refresh-token-family-client-module.test.mts +0 -249
- package/templates/standalone/src/__tests__/replica-safety.test.mts +0 -654
- 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 -504
- package/templates/standalone/src/__tests__/session-lifecycle.test.mts +0 -215
- package/templates/standalone/src/__tests__/session-logout-invalidates-token.test.mts +0 -296
- 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 -634
- package/templates/standalone/src/__tests__/startup-log-lines.test.mts +0 -148
- package/templates/standalone/src/__tests__/store-credential.test.mts +0 -312
- 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 -382
- package/templates/standalone/src/adapters.mts +0 -162
- package/templates/standalone/src/app.mts +0 -136
- 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 -377
- 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
package/dist/index.mjs
DELETED
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2026 1o1 Co. Ltd.
|
|
3
|
-
*
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
* you may not use this file except in compliance with the License.
|
|
6
|
-
* You may obtain a copy of the License at
|
|
7
|
-
*
|
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
*
|
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
* See the License for the specific language governing permissions and
|
|
14
|
-
* limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
import { spawnSync } from "node:child_process";
|
|
17
|
-
import { appendFileSync, cpSync, existsSync, readdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
|
|
18
|
-
import { dirname, resolve } from "node:path";
|
|
19
|
-
import { fileURLToPath } from "node:url";
|
|
20
|
-
import { shouldCopyTemplateEntry } from "./internal/template-filter.mjs";
|
|
21
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
-
const TEMPLATES_ROOT = resolve(__dirname, "../templates");
|
|
23
|
-
/** The template a scaffold is made from when none is named. */
|
|
24
|
-
export const DEFAULT_TEMPLATE = "standalone";
|
|
25
|
-
/**
|
|
26
|
-
* The templates under `templatesRoot`: every directory — not a symbolic link,
|
|
27
|
-
* not dot-named — holding a `package.json`, sorted; none when the directory is
|
|
28
|
-
* missing. This is the rule `scripts/templates.mjs` copies templates by, read
|
|
29
|
-
* here from the copy, where `versions.json` sits beside them. A name is only
|
|
30
|
-
* ever looked up in this list, so nothing outside the templates directory can
|
|
31
|
-
* be named as a template.
|
|
32
|
-
*/
|
|
33
|
-
export const availableTemplates = (templatesRoot = TEMPLATES_ROOT) => {
|
|
34
|
-
if (!existsSync(templatesRoot))
|
|
35
|
-
return [];
|
|
36
|
-
return readdirSync(templatesRoot, { withFileTypes: true })
|
|
37
|
-
.filter((entry) => entry.isDirectory() &&
|
|
38
|
-
!entry.name.startsWith(".") &&
|
|
39
|
-
existsSync(resolve(templatesRoot, entry.name, "package.json")))
|
|
40
|
-
.map((entry) => entry.name)
|
|
41
|
-
.sort();
|
|
42
|
-
};
|
|
43
|
-
/**
|
|
44
|
-
* Why `template` cannot be scaffolded from `templates`, or `undefined` when it
|
|
45
|
-
* can: none is bundled (a checkout that never ran the prebuild script), or it
|
|
46
|
-
* is not one of them.
|
|
47
|
-
*/
|
|
48
|
-
export const templateRefusal = (template, templates) => {
|
|
49
|
-
if (templates.length === 0) {
|
|
50
|
-
return `No templates found at ${TEMPLATES_ROOT}. If developing locally, run the prebuild script first.`;
|
|
51
|
-
}
|
|
52
|
-
if (!templates.includes(template)) {
|
|
53
|
-
return `Unknown template '${template}'. Available templates: ${templates.join(", ")}.`;
|
|
54
|
-
}
|
|
55
|
-
return undefined;
|
|
56
|
-
};
|
|
57
|
-
const getPackageVersions = () => {
|
|
58
|
-
const versionFile = resolve(__dirname, "../templates/versions.json");
|
|
59
|
-
if (existsSync(versionFile)) {
|
|
60
|
-
return JSON.parse(readFileSync(versionFile, "utf-8"));
|
|
61
|
-
}
|
|
62
|
-
return {};
|
|
63
|
-
};
|
|
64
|
-
const UNSCOPED_NAME_RE = /^[a-z0-9][a-z0-9-._~]*$/;
|
|
65
|
-
const SCOPED_NAME_RE = /^@[a-z0-9][a-z0-9-._~]*\/[a-z0-9][a-z0-9-._~]*$/;
|
|
66
|
-
const MAX_NAME_LEN = 214;
|
|
67
|
-
export const isValidProjectName = (name) => {
|
|
68
|
-
if (name.length === 0 || name.length > MAX_NAME_LEN)
|
|
69
|
-
return false;
|
|
70
|
-
if (name === "." || name === "..")
|
|
71
|
-
return false;
|
|
72
|
-
return UNSCOPED_NAME_RE.test(name) || SCOPED_NAME_RE.test(name);
|
|
73
|
-
};
|
|
74
|
-
export const isValidDirName = (name) => {
|
|
75
|
-
if (name.length === 0 || name.length > MAX_NAME_LEN)
|
|
76
|
-
return false;
|
|
77
|
-
if (name === "." || name === "..")
|
|
78
|
-
return false;
|
|
79
|
-
return UNSCOPED_NAME_RE.test(name);
|
|
80
|
-
};
|
|
81
|
-
/**
|
|
82
|
-
* Appended to the scaffold's `config/application.conf` by `noMfa`: the
|
|
83
|
-
* template's switch `mfaMode` written off, then bound to `MFA_MODE` so the
|
|
84
|
-
* variable wins over the written value. The MFA package stays a dependency.
|
|
85
|
-
*/
|
|
86
|
-
const MFA_OFF_LINES = [
|
|
87
|
-
"",
|
|
88
|
-
"# written by create-app --no-mfa: MFA is off unless MFA_MODE turns it on",
|
|
89
|
-
'mfaMode = "off"',
|
|
90
|
-
// biome-ignore lint/suspicious/noTemplateCurlyInString: a HOCON substitution, not a template
|
|
91
|
-
"mfaMode = ${?MFA_MODE}",
|
|
92
|
-
];
|
|
93
|
-
export const scaffold = (targetDir, projectName, template = DEFAULT_TEMPLATE, options = {}) => {
|
|
94
|
-
const refusal = templateRefusal(template, availableTemplates());
|
|
95
|
-
if (refusal !== undefined)
|
|
96
|
-
throw new Error(refusal);
|
|
97
|
-
const templateDir = resolve(TEMPLATES_ROOT, template);
|
|
98
|
-
// Copy template to target
|
|
99
|
-
cpSync(templateDir, targetDir, {
|
|
100
|
-
recursive: true,
|
|
101
|
-
filter: (source) => shouldCopyTemplateEntry(source, templateDir),
|
|
102
|
-
});
|
|
103
|
-
// npm drops a file literally named `.gitignore` from a published package,
|
|
104
|
-
// so `copy-templates.mjs` stages it dot-less and it is restored here.
|
|
105
|
-
// Without it the first `git add .` in a scaffolded project commits the
|
|
106
|
-
// `.env` and the signing key the README's setup steps create.
|
|
107
|
-
const stagedGitignore = resolve(targetDir, "gitignore");
|
|
108
|
-
if (existsSync(stagedGitignore)) {
|
|
109
|
-
renameSync(stagedGitignore, resolve(targetDir, ".gitignore"));
|
|
110
|
-
}
|
|
111
|
-
if (options.noMfa === true) {
|
|
112
|
-
appendFileSync(resolve(targetDir, "config", "application.conf"), `${MFA_OFF_LINES.join("\n")}\n`);
|
|
113
|
-
}
|
|
114
|
-
// Rewrite package.json
|
|
115
|
-
const pkgPath = resolve(targetDir, "package.json");
|
|
116
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
117
|
-
pkg.name = projectName;
|
|
118
|
-
// `"private": true` is deliberately kept: the scaffold is an identity
|
|
119
|
-
// provider (keys, config, policy), so an accidental `npm publish` must fail
|
|
120
|
-
// by default. An operator who means to publish removes the field.
|
|
121
|
-
// o3co/auth.policy-verifier's scaffolder does the same; change the two together.
|
|
122
|
-
// Replace all workspace:* references with per-package published versions
|
|
123
|
-
const versions = getPackageVersions();
|
|
124
|
-
for (const section of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
125
|
-
const deps = pkg[section];
|
|
126
|
-
if (!deps)
|
|
127
|
-
continue;
|
|
128
|
-
for (const [name, version] of Object.entries(deps)) {
|
|
129
|
-
if (version === "workspace:*") {
|
|
130
|
-
const resolved = versions[name];
|
|
131
|
-
if (!resolved) {
|
|
132
|
-
throw new Error(`Cannot resolve version for workspace dependency "${name}". Ensure versions.json includes this package.`);
|
|
133
|
-
}
|
|
134
|
-
deps[name] = `^${resolved}`;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
139
|
-
// pnpm ≥10.29 reads `onlyBuiltDependencies` only from pnpm-workspace.yaml,
|
|
140
|
-
// even in single-package projects. The template cannot ship the file:
|
|
141
|
-
// inside the monorepo it would shadow the workspace root. So the scaffold
|
|
142
|
-
// writes it, mirroring the workspace root's allowlist.
|
|
143
|
-
writeFileSync(resolve(targetDir, "pnpm-workspace.yaml"), [
|
|
144
|
-
"# pnpm (>= 10.29) reads onlyBuiltDependencies from this file, even in a",
|
|
145
|
-
"# single-package project. bcrypt's install hook compiles the native addon",
|
|
146
|
-
"# on platforms with no shipped prebuild; without this allowlist pnpm",
|
|
147
|
-
"# silently skips the hook and bcrypt fails at require time there.",
|
|
148
|
-
"onlyBuiltDependencies:",
|
|
149
|
-
" - bcrypt",
|
|
150
|
-
"",
|
|
151
|
-
].join("\n"));
|
|
152
|
-
};
|
|
153
|
-
/**
|
|
154
|
-
* `--lockfile-only` resolves the dependency graph without installing or
|
|
155
|
-
* running any script. `--ignore-workspace` keeps the new project's lockfile
|
|
156
|
-
* its own even when the target directory happens to sit inside somebody
|
|
157
|
-
* else's pnpm workspace.
|
|
158
|
-
*/
|
|
159
|
-
const LOCKFILE_ARGS = ["install", "--lockfile-only", "--ignore-workspace"];
|
|
160
|
-
/**
|
|
161
|
-
* Launchers tried in order: `pnpm` on PATH, then `corepack pnpm` for a machine
|
|
162
|
-
* with Node's bundled corepack but no global pnpm. Only ENOENT falls through to
|
|
163
|
-
* the next launcher; a non-zero exit would fail identically through any of
|
|
164
|
-
* them, and other launch errors surface as themselves.
|
|
165
|
-
*/
|
|
166
|
-
const LOCKFILE_LAUNCHERS = [["pnpm"], ["corepack", "pnpm"]];
|
|
167
|
-
/**
|
|
168
|
-
* Resolve the scaffolded project's dependency graph into `pnpm-lock.yaml`,
|
|
169
|
-
* which the template's `pnpm install --frozen-lockfile` build needs. The
|
|
170
|
-
* lockfile cannot ship with the template: the dependency set it pins exists
|
|
171
|
-
* only after `scaffold` has replaced every `workspace:*`. Best-effort (it needs
|
|
172
|
-
* a package manager and a reachable registry), so failure is a result, not a
|
|
173
|
-
* throw.
|
|
174
|
-
*/
|
|
175
|
-
export const generateLockfile = (targetDir) => {
|
|
176
|
-
const attempts = [];
|
|
177
|
-
for (const launcher of LOCKFILE_LAUNCHERS) {
|
|
178
|
-
const [bin, ...prefix] = launcher;
|
|
179
|
-
const args = [...prefix, ...LOCKFILE_ARGS];
|
|
180
|
-
const printable = [bin, ...args].join(" ");
|
|
181
|
-
const result = spawnSync(bin, args, {
|
|
182
|
-
cwd: targetDir,
|
|
183
|
-
stdio: "inherit",
|
|
184
|
-
shell: false,
|
|
185
|
-
env: { ...process.env, COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
|
|
186
|
-
});
|
|
187
|
-
if (result.error) {
|
|
188
|
-
attempts.push(`${printable}: ${result.error.message}`);
|
|
189
|
-
// Only "not on PATH" is worth asking a different launcher about.
|
|
190
|
-
if (result.error.code === "ENOENT")
|
|
191
|
-
continue;
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
if (result.status === 0)
|
|
195
|
-
return { ok: true, command: printable };
|
|
196
|
-
const how = result.status === null ? `killed by signal ${result.signal}` : `exit code ${result.status}`;
|
|
197
|
-
attempts.push(`${printable}: ${how}`);
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
return { ok: false, reason: attempts.join("; ") };
|
|
201
|
-
};
|
|
202
|
-
/** The flags that take a value, as `--flag <value>` or `--flag=<value>`. */
|
|
203
|
-
const VALUE_FLAGS = ["--dir", "--template"];
|
|
204
|
-
const parseArgs = (args) => {
|
|
205
|
-
const positionals = [];
|
|
206
|
-
const values = new Map();
|
|
207
|
-
let lockfile = true;
|
|
208
|
-
let noMfa = false;
|
|
209
|
-
const setValue = (flag, value) => {
|
|
210
|
-
if (values.has(flag))
|
|
211
|
-
throw new Error(`${flag} specified more than once`);
|
|
212
|
-
if (value === "")
|
|
213
|
-
throw new Error(`${flag} requires a value`);
|
|
214
|
-
values.set(flag, value);
|
|
215
|
-
};
|
|
216
|
-
for (let i = 0; i < args.length; i++) {
|
|
217
|
-
const a = args[i];
|
|
218
|
-
const spaced = VALUE_FLAGS.find((flag) => a === flag);
|
|
219
|
-
const joined = VALUE_FLAGS.find((flag) => a.startsWith(`${flag}=`));
|
|
220
|
-
if (spaced !== undefined) {
|
|
221
|
-
if (i + 1 >= args.length)
|
|
222
|
-
throw new Error(`${spaced} requires a value`);
|
|
223
|
-
setValue(spaced, args[i + 1]);
|
|
224
|
-
i++;
|
|
225
|
-
}
|
|
226
|
-
else if (joined !== undefined) {
|
|
227
|
-
setValue(joined, a.slice(`${joined}=`.length));
|
|
228
|
-
}
|
|
229
|
-
else if (a === "--no-lockfile") {
|
|
230
|
-
lockfile = false;
|
|
231
|
-
}
|
|
232
|
-
else if (a === "--no-mfa") {
|
|
233
|
-
noMfa = true;
|
|
234
|
-
}
|
|
235
|
-
else if (a.startsWith("-")) {
|
|
236
|
-
// Treats `--` and any --unknown as an unknown flag.
|
|
237
|
-
throw new Error(`unknown flag: ${a}`);
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
positionals.push(a);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (positionals.length === 0)
|
|
244
|
-
throw new Error("missing <project-name>");
|
|
245
|
-
if (positionals.length > 1)
|
|
246
|
-
throw new Error("too many positional arguments");
|
|
247
|
-
return {
|
|
248
|
-
projectName: positionals[0],
|
|
249
|
-
dir: values.get("--dir"),
|
|
250
|
-
template: values.get("--template"),
|
|
251
|
-
lockfile,
|
|
252
|
-
noMfa,
|
|
253
|
-
};
|
|
254
|
-
};
|
|
255
|
-
const deriveDirName = (projectName, dir) => {
|
|
256
|
-
if (dir !== undefined)
|
|
257
|
-
return dir;
|
|
258
|
-
if (projectName.startsWith("@")) {
|
|
259
|
-
const pkgPart = projectName.split("/")[1];
|
|
260
|
-
if (!pkgPart) {
|
|
261
|
-
// Unreachable when projectName has passed isValidProjectName (SCOPED_NAME_RE
|
|
262
|
-
// guarantees a non-empty package segment after the single "/"). Guarded here
|
|
263
|
-
// so refactors that reorder validation cannot silently produce undefined.
|
|
264
|
-
throw new Error(`invariant: unvalidated scoped name ${projectName}`);
|
|
265
|
-
}
|
|
266
|
-
return pkgPart;
|
|
267
|
-
}
|
|
268
|
-
return projectName;
|
|
269
|
-
};
|
|
270
|
-
// CLI entry point
|
|
271
|
-
export const main = () => {
|
|
272
|
-
const args = process.argv.slice(2);
|
|
273
|
-
let parsed;
|
|
274
|
-
try {
|
|
275
|
-
parsed = parseArgs(args);
|
|
276
|
-
}
|
|
277
|
-
catch (e) {
|
|
278
|
-
console.error(`Error: ${e.message}`);
|
|
279
|
-
console.error("Usage: @o3co/create-auth-provider <project-name> [--template <name>] [--dir <dir-name>] [--no-lockfile] [--no-mfa]");
|
|
280
|
-
console.error("<project-name> must be a valid npm package name (scoped like @scope/pkg, or unscoped).");
|
|
281
|
-
console.error(`--template names the template to copy (default: ${DEFAULT_TEMPLATE}); available: ${availableTemplates().join(", ")}.`);
|
|
282
|
-
process.exit(1);
|
|
283
|
-
}
|
|
284
|
-
const { projectName, dir, lockfile, noMfa } = parsed;
|
|
285
|
-
const template = parsed.template ?? DEFAULT_TEMPLATE;
|
|
286
|
-
if (!isValidProjectName(projectName)) {
|
|
287
|
-
console.error("Error: <project-name> must be a valid npm package name (scoped like @scope/pkg, or unscoped; max 214 chars; no backslashes; no extra '/' beyond the single scope separator).");
|
|
288
|
-
process.exit(1);
|
|
289
|
-
}
|
|
290
|
-
if (dir !== undefined && !isValidDirName(dir)) {
|
|
291
|
-
console.error("Error: --dir must be a valid unscoped package name (no '/', '\\', '@'; not '.' or '..'; max 214 chars).");
|
|
292
|
-
process.exit(1);
|
|
293
|
-
}
|
|
294
|
-
const refusal = templateRefusal(template, availableTemplates());
|
|
295
|
-
if (refusal !== undefined) {
|
|
296
|
-
console.error(`Error: ${refusal}`);
|
|
297
|
-
process.exit(1);
|
|
298
|
-
}
|
|
299
|
-
const dirName = deriveDirName(projectName, dir);
|
|
300
|
-
const targetDir = resolve(process.cwd(), dirName);
|
|
301
|
-
if (existsSync(targetDir)) {
|
|
302
|
-
console.error(`Error: Directory '${dirName}' already exists.`);
|
|
303
|
-
process.exit(1);
|
|
304
|
-
}
|
|
305
|
-
console.log(`Creating ${projectName} from the ${template} template...`);
|
|
306
|
-
scaffold(targetDir, projectName, template, { noMfa });
|
|
307
|
-
let lockfileGenerated = false;
|
|
308
|
-
if (lockfile) {
|
|
309
|
-
console.log("\nResolving dependencies into pnpm-lock.yaml...");
|
|
310
|
-
const result = generateLockfile(targetDir);
|
|
311
|
-
lockfileGenerated = result.ok;
|
|
312
|
-
if (!result.ok) {
|
|
313
|
-
console.error(`\nWarning: could not generate pnpm-lock.yaml (${result.reason}).`);
|
|
314
|
-
console.error(`Run 'pnpm install' in ${dirName} and commit pnpm-lock.yaml: the Dockerfile installs with --frozen-lockfile and will not build without it.`);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
console.log(`\nDone! Created ${projectName} at ${targetDir}`);
|
|
318
|
-
if (lockfileGenerated) {
|
|
319
|
-
console.log("\nCommit pnpm-lock.yaml along with the rest: it is what makes 'docker build' reproducible.");
|
|
320
|
-
}
|
|
321
|
-
console.log(`\nNext steps:`);
|
|
322
|
-
console.log(` cd ${dirName}`);
|
|
323
|
-
console.log(" pnpm install");
|
|
324
|
-
console.log(" pnpm run debug");
|
|
325
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Decide whether `cpSync` should copy a given source path.
|
|
3
|
-
*
|
|
4
|
-
* Only segments inside `templateRoot` are checked against EXCLUDED_DIRS; the
|
|
5
|
-
* install prefix above it (e.g. `~/.npm/_npx/<hash>/node_modules/...` under
|
|
6
|
-
* `npx`) is ignored, or every file would be rejected whenever the package
|
|
7
|
-
* itself lives under a `node_modules` directory.
|
|
8
|
-
*
|
|
9
|
-
* `pathSep` lets tests exercise POSIX and Windows separators on any host.
|
|
10
|
-
* Production callers keep the `path.sep` default: `cpSync` passes
|
|
11
|
-
* backslash-delimited paths on Windows.
|
|
12
|
-
*/
|
|
13
|
-
export declare const shouldCopyTemplateEntry: (source: string, templateRoot: string, pathSep?: string) => boolean;
|
|
14
|
-
//# sourceMappingURL=template-filter.d.mts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"template-filter.d.mts","sourceRoot":"","sources":["../../src/internal/template-filter.mts"],"names":[],"mappings":"AAuBA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,uBAAuB,GACnC,QAAQ,MAAM,EACd,cAAc,MAAM,EACpB,UAAS,MAAY,KACnB,OAMF,CAAC"}
|
|
@@ -1,42 +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
|
-
// INTERNAL — not exposed via the package's `exports` map. Reachable only from
|
|
18
|
-
// other files in this package (and its tests). See package.json `exports`.
|
|
19
|
-
//
|
|
20
|
-
import { sep } from "node:path";
|
|
21
|
-
const EXCLUDED_DIRS = new Set(["node_modules", "dist"]);
|
|
22
|
-
/**
|
|
23
|
-
* Decide whether `cpSync` should copy a given source path.
|
|
24
|
-
*
|
|
25
|
-
* Only segments inside `templateRoot` are checked against EXCLUDED_DIRS; the
|
|
26
|
-
* install prefix above it (e.g. `~/.npm/_npx/<hash>/node_modules/...` under
|
|
27
|
-
* `npx`) is ignored, or every file would be rejected whenever the package
|
|
28
|
-
* itself lives under a `node_modules` directory.
|
|
29
|
-
*
|
|
30
|
-
* `pathSep` lets tests exercise POSIX and Windows separators on any host.
|
|
31
|
-
* Production callers keep the `path.sep` default: `cpSync` passes
|
|
32
|
-
* backslash-delimited paths on Windows.
|
|
33
|
-
*/
|
|
34
|
-
export const shouldCopyTemplateEntry = (source, templateRoot, pathSep = sep) => {
|
|
35
|
-
if (source === templateRoot)
|
|
36
|
-
return true;
|
|
37
|
-
const prefix = templateRoot.endsWith(pathSep) ? templateRoot : `${templateRoot}${pathSep}`;
|
|
38
|
-
if (!source.startsWith(prefix))
|
|
39
|
-
return true;
|
|
40
|
-
const rel = source.slice(prefix.length);
|
|
41
|
-
return !rel.split(pathSep).some((s) => EXCLUDED_DIRS.has(s));
|
|
42
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
node_modules
|
|
2
|
-
dist
|
|
3
|
-
.git
|
|
4
|
-
Makefile
|
|
5
|
-
# #705: the secrets and the per-deployment configuration that `.gitignore`
|
|
6
|
-
# keeps out of git stay out of the build context as well. The builder stage
|
|
7
|
-
# COPYs `config/` and the runtime image takes it from there, so a
|
|
8
|
-
# `config/clients.yaml` in the working copy — client secrets included — was
|
|
9
|
-
# baked into a layer that travels with every push; the production compose
|
|
10
|
-
# mounts the registry instead. This is `.gitignore`'s secrets section, written
|
|
11
|
-
# with `**/` where it matches at any depth: a `.dockerignore` pattern is
|
|
12
|
-
# anchored at the context root. Keep the two in step; scaffold-assets.test.mts
|
|
13
|
-
# checks that they are.
|
|
14
|
-
**/.env*
|
|
15
|
-
!**/.env.example
|
|
16
|
-
**/*.pem
|
|
17
|
-
**/*.key
|
|
18
|
-
config/clients.yaml
|
|
19
|
-
config/*.local.conf
|
|
20
|
-
# .gitignore, the READMEs and the compose files stay in the context: the
|
|
21
|
-
# `test` image copies them because the shipped suite reads them (#512). The
|
|
22
|
-
# runtime image never copies them, so they cost the context a few kilobytes
|
|
23
|
-
# and the image nothing.
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
# Canonical issuer stamped as `iss` on every token this deployment mints.
|
|
2
|
-
# Absolute https URL (http only for a loopback host), no query or fragment.
|
|
3
|
-
# Boot fails when unset — it is never derived from the Host header.
|
|
4
|
-
OAUTH_JWT_ISSUER=
|
|
5
|
-
|
|
6
|
-
# --- JWT signing key (required) ---------------------------------------------
|
|
7
|
-
# The default algorithm is EdDSA (Ed25519). There is no key-material default:
|
|
8
|
-
# a deployment that sets none fails at boot naming the keys to set, rather than
|
|
9
|
-
# quietly signing with something guessable.
|
|
10
|
-
#
|
|
11
|
-
# openssl genpkey -algorithm ed25519 -out jwt-private.pem
|
|
12
|
-
# openssl pkey -in jwt-private.pem -pubout -out jwt-public.pem
|
|
13
|
-
#
|
|
14
|
-
# Keep jwt-private.pem out of version control and off the image.
|
|
15
|
-
KEY_STORE_LOCAL_PRIVATE_KEY_PATH=./jwt-private.pem
|
|
16
|
-
KEY_STORE_LOCAL_PUBLIC_KEY_PATH=./jwt-public.pem
|
|
17
|
-
# KEY_STORE_LOCAL_ALGORITHM=EdDSA # EdDSA | ES256 | RS256 | HS256
|
|
18
|
-
# KEY_STORE_LOCAL_KID=v0
|
|
19
|
-
#
|
|
20
|
-
# HS256 alternative — symmetric, so /.well-known/jwks.json publishes NOTHING
|
|
21
|
-
# and every relying party must hold a secret that can also MINT tokens. Only
|
|
22
|
-
# for deployments that verify in-process. Needs >= 32 bytes (256 bits):
|
|
23
|
-
# openssl rand -hex 32
|
|
24
|
-
# Hex/base64 values are measured DECODED, so a 32-character hex string counts
|
|
25
|
-
# as 16 bytes and is refused.
|
|
26
|
-
# KEY_STORE_LOCAL_ALGORITHM=HS256
|
|
27
|
-
# KEY_STORE_LOCAL_SECRET=
|
|
28
|
-
|
|
29
|
-
# --- Session (required) ------------------------------------------------------
|
|
30
|
-
# Signs the cookie that IS the authenticated session, so guessing it forges
|
|
31
|
-
# logins. Same >= 32-byte floor and same decoded measurement:
|
|
32
|
-
# openssl rand -hex 32
|
|
33
|
-
SESSION_STORE_SECRET=
|
|
34
|
-
|
|
35
|
-
# User repository
|
|
36
|
-
# Both URLs carry plaintext user credentials to the upstream store, so both must
|
|
37
|
-
# be absolute https URLs. http:// is accepted only for a loopback host
|
|
38
|
-
# (localhost, 127.0.0.0/8, [::1]) — a private-range address or a compose service
|
|
39
|
-
# name such as http://user-service:8080 still needs https. Boot fails otherwise.
|
|
40
|
-
# Each must be the endpoint that answers: no request follows a 3xx, so a URL that
|
|
41
|
-
# redirects fails every call.
|
|
42
|
-
REPOSITORIES_USER_HTTP_AUTHENTICATE_URL=
|
|
43
|
-
REPOSITORIES_USER_HTTP_AUTHENTICATE_BY_TOKEN_URL=
|
|
44
|
-
# With MFA on: the Store's endpoint the MFA enrollment witness is written to,
|
|
45
|
-
# so a Store that loses a subject's factors cannot open a first binding to
|
|
46
|
-
# whoever holds the password. Your Store must then answer `mfaEnrolled` on
|
|
47
|
-
# both URLs above. Unset, boot warns (mfa_enrollment_witness_unwritable).
|
|
48
|
-
# REPOSITORIES_USER_HTTP_MARK_MFA_ENROLLED_URL=https://users.example.com/mfa/enrolled
|
|
49
|
-
# The credential this server presents to the Store: every Store request carries
|
|
50
|
-
# `Authorization: Bearer <token>`. Without one, anyone who can reach the Store
|
|
51
|
-
# can resolve an identity to its user, so your Store should refuse any request
|
|
52
|
-
# without it, with 401 and `WWW-Authenticate: Bearer error="invalid_token"`
|
|
53
|
-
# (which this server then reports as an outage naming the refused credential).
|
|
54
|
-
# The one token goes to every Store URL above: they must be one trust domain.
|
|
55
|
-
# At least 32 bytes, measured like SESSION_STORE_SECRET; boot fails on a weak or
|
|
56
|
-
# malformed value. Left commented out because a blank value is refused too:
|
|
57
|
-
# openssl rand -hex 32
|
|
58
|
-
# REPOSITORIES_USER_HTTP_BEARER_TOKEN=
|
|
59
|
-
|
|
60
|
-
# --- Reverse proxy (required in production) -----------------------------------
|
|
61
|
-
# Express `trust proxy`. Set this to the address or CIDR range of whatever
|
|
62
|
-
# terminates TLS in front of this service — `10.0.0.0/8`, or `loopback` for a
|
|
63
|
-
# sidecar on the same host. Comma-separate several.
|
|
64
|
-
#
|
|
65
|
-
# Prefer naming the hop over `true`: `true` believes a forwarded client address
|
|
66
|
-
# from anyone who can reach this process, and with it any rate-limit identity.
|
|
67
|
-
# Left EMPTY here because it cannot be guessed; `docker-compose.production.yml`
|
|
68
|
-
# refuses to start until it is set, since without it the Secure cookie is never
|
|
69
|
-
# set, the CSRF origin check 403s every browser POST, and every IP-keyed rate
|
|
70
|
-
# limit shares one bucket. A plain-HTTP local run needs nothing here.
|
|
71
|
-
HTTP_TRUST_PROXY=
|
|
72
|
-
|
|
73
|
-
# Redis (session storage + the shared client socket)
|
|
74
|
-
# Use redis://redis:6379 when running via docker compose
|
|
75
|
-
SESSION_STORE_STORAGE_REDIS_URL=redis://redis:6379
|
|
76
|
-
# #407: this one backs the SHARED ioredis socket -- the refresh-token family
|
|
77
|
-
# store, the access-token denylist and the rate limiter all ride on it. It
|
|
78
|
-
# defaults to redis://localhost:6379, which inside the container is the
|
|
79
|
-
# container, so leaving it unset makes the compose boot dial nothing.
|
|
80
|
-
REDIS_CLIENTS_URL=redis://redis:6379
|
|
81
|
-
# The Redis stores refuse to boot unless the server reports maxmemory-policy
|
|
82
|
-
# noeviction (README: Multi-replica deployments). Only for a server that does
|
|
83
|
-
# run noeviction but will not report it (INFO and CONFIG refused to this user):
|
|
84
|
-
# REDIS_CLIENTS_ASSUME_NO_EVICTION=true
|
|
85
|
-
|
|
86
|
-
# --- Federation grants (#593, optional) --------------------------------------
|
|
87
|
-
# Offline delegation of an upstream IdP's tokens to a backend or an agent, on a
|
|
88
|
-
# consent that survives logout. Off by default; on, boot refuses what is
|
|
89
|
-
# missing by name. The consent page is yours (README: Federation Grants). The
|
|
90
|
-
# connections and the encryption key ring are lists, so they live in a HOCON
|
|
91
|
-
# layer, not here. Both stores ship as none: select each (redis, or memory on
|
|
92
|
-
# one replica) with the two ADAPTERS_ lines below. Redis GRANTS need
|
|
93
|
-
# ADAPTERS_USER_SESSION_STORES=redis beside them (the compose files set it) —
|
|
94
|
-
# the grants would outlive a memory boundary that ends them.
|
|
95
|
-
# FEDERATION_GRANTS_ENABLED=true
|
|
96
|
-
# FEDERATION_GRANTS_CONSENT_URL=/consent/grants
|
|
97
|
-
# `required` seals credentials under redis-federation-grant-store.encryptionKeys (HOCON);
|
|
98
|
-
# `allow-plaintext` is refused in production/staging and under multi.
|
|
99
|
-
# REDIS_FEDERATION_GRANT_STORE_ENCRYPTION_MODE=required
|
|
100
|
-
# `required` needs a user repository that covers every connection's
|
|
101
|
-
# registration: the shipped http one asks your Store at the URL below and
|
|
102
|
-
# covers what repositories.user.http.federatedIdentityLookupCoverage (HOCON)
|
|
103
|
-
# declares; `unsupported` records that the check is not made.
|
|
104
|
-
# FEDERATION_GRANTS_IDENTITY_LOOKUP=required
|
|
105
|
-
# REPOSITORIES_USER_HTTP_FIND_SUBJECT_BY_FEDERATED_IDENTITY_URL=https://users.example.com/find-subject-by-federated-identity
|
|
106
|
-
# ADAPTERS_FEDERATION_GRANT_STORE=redis
|
|
107
|
-
# ADAPTERS_FEDERATION_GRANT_INTENT_STORE=redis
|
|
108
|
-
|
|
109
|
-
# --- Multi-factor authentication (on by default) -----------------------------
|
|
110
|
-
# MFA_MODE: required (the default: every password login needs a second
|
|
111
|
-
# factor) | optional | off. The MFA page is yours (README: Multi-factor
|
|
112
|
-
# authentication). Development needs nothing here: config/development.conf
|
|
113
|
-
# holds a sample key, the codes are logged, and docker-compose.yml keeps the
|
|
114
|
-
# two MFA stores on its Redis. Outside development, set all of the following,
|
|
115
|
-
# or boot is refused naming them:
|
|
116
|
-
# - the two MFA stores, which ship in memory, refused outside development and
|
|
117
|
-
# test since a restart empties them of every factor: redis (or, for the
|
|
118
|
-
# factors, store);
|
|
119
|
-
# - a key of your own (openssl rand -base64 32);
|
|
120
|
-
# - the SMTP relay the account-email proof and the email factor's codes go
|
|
121
|
-
# through.
|
|
122
|
-
# The MFA page's URL defaults to /mfa, which you must serve, in development
|
|
123
|
-
# too, or every password login stops there.
|
|
124
|
-
# ADAPTERS_MFA_FACTOR_STORE=redis
|
|
125
|
-
# ADAPTERS_MFA_TRANSACTION_STORE=redis
|
|
126
|
-
# MFA_ENCRYPTION_KEY=
|
|
127
|
-
# STANDARD_SMTP_MAIL_SENDER_HOST=smtp.example.com
|
|
128
|
-
# STANDARD_SMTP_MAIL_SENDER_FROM=auth@example.com
|
|
129
|
-
# MFA_PAGE_URL=/mfa
|
|
130
|
-
# Or run without MFA:
|
|
131
|
-
# MFA_MODE=off
|
|
132
|
-
# To read the mail in development, leave every STANDARD_SMTP_MAIL_SENDER_*
|
|
133
|
-
# unset (USER and PASSWORD included), set MFA_ENCRYPTION_KEY to a key of your
|
|
134
|
-
# own (the run does not use the development sample key), and run the Mailpit
|
|
135
|
-
# overlay, which sets the sender's variables (README: Mailpit for the SMTP sender):
|
|
136
|
-
# docker compose -f docker-compose.yml -f docker-compose.mailpit.yml up --build
|
|
137
|
-
|
|
138
|
-
# Development (override for local HTTP).
|
|
139
|
-
# The default cookie name carries the `__Host-` prefix, which browsers only
|
|
140
|
-
# accept on a Secure cookie — so dropping Secure means renaming the cookie too,
|
|
141
|
-
# or boot refuses the combination.
|
|
142
|
-
SESSION_STORE_SECURE=false
|
|
143
|
-
SESSION_STORE_NAME=auth.sid
|
|
144
|
-
|
|
145
|
-
# Optional
|
|
146
|
-
# HTTP_PORT=3000
|
|
147
|
-
# OAUTH_ACCESS_TOKEN_DEFAULT_EXPIRES_IN=3600
|
|
148
|
-
# The most a token-exchange `expires_in` can obtain; unset = the default.
|
|
149
|
-
# OAUTH_ACCESS_TOKEN_MAX_EXPIRES_IN=3600
|
|
150
|
-
# OAUTH_REFRESH_TOKEN_EXPIRES_IN=86400
|
|
151
|
-
# SESSION_STORE_MAX_AGE=3600000
|
|
152
|
-
# SESSION_STORE_SAME_SITE=lax # `none` additionally requires SESSION_STORE_SECURE=true
|