@authup/server-auth-console 1.0.0-beta.64 → 1.0.0-beta.65

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 ADDED
@@ -0,0 +1,76 @@
1
+ # @authup/server-auth-console
2
+
3
+ [![npm version](https://badge.fury.io/js/@authup%2Fserver-auth-console.svg)](https://badge.fury.io/js/@authup%2Fserver-auth-console)
4
+ [![main](https://github.com/authup/authup/actions/workflows/main.yml/badge.svg)](https://github.com/authup/authup/actions/workflows/main.yml)
5
+ [![Known Vulnerabilities](https://snyk.io/test/github/authup/authup/badge.svg)](https://snyk.io/test/github/authup/authup)
6
+
7
+ The service behind authup's hosted auth pages: `/authorize` (login and
8
+ consent), `/register`, `/activate`, `/password-forgot`, `/password-reset` and
9
+ `/logout`. A server-core page GET redirects here carrying the request's own
10
+ query, so the authorization request is what identifies the flow.
11
+
12
+ It renders `@authup/client-auth-console` through that package's own render
13
+ contract, resolved out of `node_modules`: the SSR bundle per request with the
14
+ hydration payload spliced into the template, the bundle's client assets under
15
+ its own `/assets`, plus the operator theme and the security headers a login
16
+ page needs.
17
+
18
+ It hydrates ANONYMOUSLY over HTTP and holds no credential, no database and no
19
+ loopback: `/authorize` from `GET /authorize/info`, the four workflow pages from
20
+ `GET /` plus their own query, and `/logout` from nothing at all, since that page
21
+ drives the end-session call itself. Substituting a package that fulfills the
22
+ render contract is the supported way to replace the hosted auth UI.
23
+
24
+ ## Usage
25
+
26
+ `authup start console auth` is the supported route: the `authup` CLI reads
27
+ `authup.yml` and hands this service its own section. `authup start` composes it
28
+ onto server-core's own listener instead, which is the single-origin deployment.
29
+
30
+ The `authup-auth-console` bin is the standalone escape hatch, for a deployment
31
+ that runs a console without the CLI. It reads the environment alone.
32
+
33
+ ```bash
34
+ PUBLIC_URL=https://example.com AUTH_CONSOLE_PORT=3020 authup-auth-console
35
+ ```
36
+
37
+ ```typescript
38
+ import { createApplication } from '@authup/server-auth-console';
39
+
40
+ const application = createApplication();
41
+
42
+ await application.setup();
43
+ ```
44
+
45
+ `createApplication()` builds this console's own module graph, resolves its
46
+ configuration and listens. Pass `{ listen: false }` to be mounted on someone
47
+ else's listener, or `{ config }` to hand over a configuration.
48
+
49
+ ## Configuration
50
+
51
+ Keys are declared once in `@authup/server-config`; this service selects the ones
52
+ it reads. Its own section is `authConsole`, and it carries no `enabled` key:
53
+ the hosted login, consent and workflow pages are the issuance surface.
54
+
55
+ | Key | Environment variable | Default |
56
+ |---|---|---|
57
+ | `url` | `AUTH_CONSOLE_URL` | derived from `publicUrl` |
58
+ | `path` | `AUTH_CONSOLE_PATH` | resolve the bundle from `node_modules` |
59
+ | `port` | `AUTH_CONSOLE_PORT` | `3020` |
60
+ | `host` | `AUTH_CONSOLE_HOST` | inherits the deployment-wide `host` |
61
+
62
+ It is the one console that fetches server-side, so the API address is two keys:
63
+ `publicUrl` (`PUBLIC_URL`) is the browser's and reaches the hydration payload,
64
+ `internalUrl` (`INTERNAL_URL`) is the one this service dispatches against and
65
+ falls back to `publicUrl`. It also reads `theme.directoryPath`
66
+ (`THEME_DIRECTORY_PATH`) and `theme.fragmentsEnabled`
67
+ (`THEME_FRAGMENTS_ENABLED`). `resolveConfig()` and `readConfigFromEnv()` both
68
+ return a `Promise`.
69
+
70
+ ## License
71
+
72
+ Made with 💚
73
+
74
+ Published under the [AGPL-3.0-only License](./LICENSE).
75
+ A commercial license is available for organizations that cannot meet the AGPL's conditions.
76
+ See [LICENSING.md](../../LICENSING.md) or contact **contact@tada5hi.net**.
@@ -1,6 +1,7 @@
1
- import { THEME_ASSET_MOUNT_PATH, applyTheme, applyUIPageHeaders, createThemeAssetsHandler, createThemeProvider, readUIClientPreferences, rebaseAssetURLs, replaceTemplateMarker, stampHtmlAttributes } from "@authup/server-console-kit";
1
+ import { THEME_ASSET_MOUNT_PATH, applyTheme, applyUIPageHeaders, createApplication, createThemeAssetsHandler, createThemeProvider, readUIClientPreferences, rebaseAssetURLs, replaceTemplateMarker, stampHtmlAttributes } from "@authup/server-console-kit";
2
2
  import { AUTH_CONSOLE_SCHEMA, CORE_SCHEMA, ROOT_SCHEMA, SECTION_KEY, THEME_SCHEMA } from "@authup/server-config";
3
- import { buildSchemaDefaults, defineSchema, mergeSchemaData, readSchemaFromEnv, resolveSchemaData } from "@authup/server-config-kit";
3
+ import { buildSchemaDefaults, defineSchema, mergeSchemaData, mountSchema, readSchemaFromEnv, resolveSchemaData } from "@authup/server-config-kit";
4
+ import { Container } from "validup";
4
5
  import { createHandler } from "@routup/assets";
5
6
  import { basic } from "@routup/basic";
6
7
  import path from "node:path";
@@ -36,17 +37,20 @@ const CONFIG_SCHEMA = defineSchema({
36
37
  })
37
38
  });
38
39
  /**
39
- * Turn the configuration namespace into the service's own shape: fill the
40
- * defaults, derive the one key that is derived rather than configured, and
41
- * rename. An empty `url` means the console sits on server-core's own origin
40
+ * Turn the configuration namespace into the service's own shape: validate the
41
+ * input against the declared zod types, fill the defaults, derive the one key
42
+ * that is derived rather than configured, and rename. An empty `url` means the console sits on server-core's own origin
42
43
  * under the default segment, which is the single-origin deployment.
43
44
  *
44
45
  * The defaults are layered SECTION-AWARE ({@link mergeSchemaData}): a spread
45
46
  * would let an input carrying one key of a section replace the whole section
46
47
  * and take every other key's default with it.
47
48
  */
48
- function resolveConfig(input) {
49
- const values = resolveSchemaData(CONFIG_SCHEMA, mergeSchemaData(CONFIG_SCHEMA, buildSchemaDefaults(CONFIG_SCHEMA), input));
49
+ async function resolveConfig(input) {
50
+ const validator = new Container();
51
+ mountSchema(validator, CONFIG_SCHEMA);
52
+ const parsed = await validator.run(input);
53
+ const values = resolveSchemaData(CONFIG_SCHEMA, mergeSchemaData(CONFIG_SCHEMA, buildSchemaDefaults(CONFIG_SCHEMA), parsed));
50
54
  return {
51
55
  url: values.url,
52
56
  apiUrl: values.publicUrl,
@@ -62,12 +66,17 @@ function resolveConfig(input) {
62
66
  * `authup.yml` reaches this service through the CLI roles, which compose this
63
67
  * very registry into the one document loader.
64
68
  */
65
- function readConfigFromEnv() {
69
+ async function readConfigFromEnv() {
66
70
  return resolveConfig(readSchemaFromEnv(CONFIG_SCHEMA));
67
71
  }
68
72
  //#endregion
69
73
  //#region src/constants.ts
70
74
  /**
75
+ * The render contract this service compiles against. The literal type fails
76
+ * the build the moment the bundle's constant moves without this one.
77
+ */
78
+ const CONTRACT_VERSION = 3;
79
+ /**
71
80
  * The vite base the auth console bundle is built with. Asset hrefs in the
72
81
  * shell are emitted against it, so the service mounts its assets there and
73
82
  * rebases them only when the deployment carries a sub-path.
@@ -243,90 +252,81 @@ const CODE_PATH = path.dirname(fileURLToPath(import.meta.url));
243
252
  const PACKAGE_PATH = path.join(CODE_PATH, "..");
244
253
  //#endregion
245
254
  //#region src/resolve.ts
246
- let cachedPackagePath;
247
- let cachedDistPath;
248
- let overridePackagePath;
249
- /**
250
- * Point the resolution at a substituted package (config `distPath`)
251
- * instead of the node_modules walk. Called once at boot.
252
- */
253
- function setPackagePath(value) {
254
- overridePackagePath = value || void 0;
255
- cachedPackagePath = void 0;
256
- cachedDistPath = void 0;
257
- }
258
255
  /**
259
- * Locate the auth console package. The SSR auth workflow UI ships as
260
- * `@authup/client-auth-console` (a dependency of THIS service), so the
256
+ * Locate the auth console package: a substituted one (config `distPath`)
257
+ * first, else the SSR auth workflow UI shipped as
258
+ * `@authup/client-auth-console` (a dependency of THIS service), which the
261
259
  * node_modules ancestor walk from this package's own root (locter's
262
- * `locateUp`) finds it for the workspace (symlink onto
263
- * apps/client-auth-console) and for a published install alike. Only a
264
- * positive result is cached.
260
+ * `locateUp`) finds for the workspace (symlink onto
261
+ * apps/client-auth-console) and for a published install alike.
265
262
  *
266
263
  * The `cwd` anchor is behavioural, not incidental: it decides which
267
264
  * node_modules tree is walked, so it stays pinned to this package rather
268
265
  * than to the process cwd.
269
266
  */
270
- function resolvePackagePath() {
271
- if (cachedPackagePath) return cachedPackagePath;
272
- if (overridePackagePath) {
273
- cachedPackagePath = overridePackagePath;
274
- return cachedPackagePath;
275
- }
276
- const manifest = locateUpSync("node_modules/@authup/client-auth-console/package.json", { cwd: PACKAGE_PATH });
277
- if (manifest) cachedPackagePath = manifest.directory;
278
- return cachedPackagePath;
267
+ function resolvePackagePath(distPath) {
268
+ return distPath || locateUpSync("node_modules/@authup/client-auth-console/package.json", { cwd: PACKAGE_PATH })?.directory;
279
269
  }
280
270
  /**
281
271
  * Locate the BUILT auth console bundle (`dist/client` template + assets,
282
- * `dist/server/server.js` render entry). Only a positive result is cached,
283
- * so a dev building the package after boot is picked up on the next
284
- * request.
272
+ * `dist/server/server.js` render entry). A half-built package is no bundle:
273
+ * without the render entry there is nothing to render with.
285
274
  */
286
- function resolveDistPath() {
287
- if (cachedDistPath) return cachedDistPath;
288
- const packagePath = resolvePackagePath();
289
- if (packagePath) {
290
- const distPath = path.join(packagePath, "dist");
291
- if (fs.existsSync(path.join(distPath, "client", "index.html"))) cachedDistPath = distPath;
292
- }
293
- return cachedDistPath;
275
+ function resolveDistPath(distPath) {
276
+ const packagePath = resolvePackagePath(distPath);
277
+ if (!packagePath) return;
278
+ const candidate = path.join(packagePath, "dist");
279
+ if (fs.existsSync(path.join(candidate, "client", "index.html")) && fs.existsSync(path.join(candidate, "server", "server.js"))) return candidate;
294
280
  }
295
281
  //#endregion
296
282
  //#region src/render.ts
297
- let cachedHtml;
298
- let cachedManifest;
299
- let cachedRender;
300
- const renderPage = async (event, config, ctx) => {
301
- const distPath = resolveDistPath();
302
- if (!distPath) throw new InternalError("The auth console bundle (@authup/client-auth-console) is not built or installed.");
303
- const html = cachedHtml ??= await fs.promises.readFile(path.join(distPath, "client", "index.html"), "utf-8");
304
- const manifest = cachedManifest ??= JSON.parse(await fs.promises.readFile(path.join(distPath, "client", ".vite", "ssr-manifest.json"), "utf-8"));
305
- const render = cachedRender ??= (await read(path.join(distPath, "server", "server.js"))).render;
306
- const preferences = readUIClientPreferences(event);
307
- const basePath = getURLBasePath(config.url);
308
- const payload = {
309
- config: {
310
- baseURL: config.apiUrl,
311
- basePath,
312
- colorMode: preferences.colorMode,
313
- locale: preferences.locale
314
- },
315
- data: ctx.data
283
+ /**
284
+ * Refuse a bundle built against another render contract at boot, naming the
285
+ * entry and both versions, instead of failing per request on `/authorize`.
286
+ * A bundle predating the export is version 1. Reads the module itself, which
287
+ * Node's ESM cache then hands the render for free.
288
+ */
289
+ async function assertRenderContract(distPath) {
290
+ const entry = path.join(distPath, "server", "server.js");
291
+ const version = (await read(entry)).CONTRACT_VERSION ?? 1;
292
+ if (version !== 3) throw new InternalError(`The auth console bundle "${entry}" implements render-contract version ${version}, but this service requires 3. Rebuild it against the current @authup/client-auth-console contract.`);
293
+ }
294
+ function createRenderPage(distPath) {
295
+ let cachedDistPath;
296
+ let cachedHtml;
297
+ let cachedManifest;
298
+ let cachedRender;
299
+ return async (event, config, ctx) => {
300
+ const resolved = cachedDistPath ??= resolveDistPath(distPath);
301
+ if (!resolved) throw new InternalError("The auth console bundle (@authup/client-auth-console) is not built or installed.");
302
+ const html = cachedHtml ??= await fs.promises.readFile(path.join(resolved, "client", "index.html"), "utf-8");
303
+ const manifest = cachedManifest ??= JSON.parse(await fs.promises.readFile(path.join(resolved, "client", ".vite", "ssr-manifest.json"), "utf-8"));
304
+ const render = cachedRender ??= (await read(path.join(resolved, "server", "server.js"))).render;
305
+ const preferences = readUIClientPreferences(event);
306
+ const basePath = getURLBasePath(config.url);
307
+ const payload = {
308
+ config: {
309
+ baseURL: config.apiUrl,
310
+ basePath,
311
+ colorMode: preferences.colorMode,
312
+ locale: preferences.locale
313
+ },
314
+ data: ctx.data
315
+ };
316
+ const [appHtml, preloadLinks] = await render({
317
+ url: ctx.url,
318
+ manifest,
319
+ payload
320
+ });
321
+ let body = replaceTemplateMarker(html, "<!--preload-links-->", preloadLinks);
322
+ body = replaceTemplateMarker(body, "<!--app-html-->", appHtml);
323
+ body = stampHtmlAttributes(body, preferences);
324
+ body = rebaseAssetURLs(body, VITE_BASE, `${basePath}/`);
325
+ body = await applyTheme(body, ctx.theme, basePath);
326
+ applyUIPageHeaders(event);
327
+ return body;
316
328
  };
317
- const [appHtml, preloadLinks] = await render({
318
- url: ctx.url,
319
- manifest,
320
- payload
321
- });
322
- let body = replaceTemplateMarker(html, "<!--preload-links-->", preloadLinks);
323
- body = replaceTemplateMarker(body, "<!--app-html-->", appHtml);
324
- body = stampHtmlAttributes(body, preferences);
325
- body = rebaseAssetURLs(body, VITE_BASE, `${basePath}/`);
326
- body = await applyTheme(body, ctx.theme, basePath);
327
- applyUIPageHeaders(event);
328
- return body;
329
- };
329
+ }
330
330
  //#endregion
331
331
  //#region src/handler.ts
332
332
  const WORKFLOW_PAGES = [
@@ -357,8 +357,8 @@ const WORKFLOW_PAGES = [
357
357
  * proxy, so a service published at `<origin>/console/auth` receives
358
358
  * `/authorize`, exactly as server-core received it before the split.
359
359
  */
360
- async function createHandler$1(config, themeProvider, render = renderPage) {
361
- setPackagePath(config.distPath);
360
+ async function createHandler$1(config, themeProvider, render) {
361
+ const renderPage = render ?? createRenderPage(config.distPath);
362
362
  const app = new App();
363
363
  const client = createAPIClient(config);
364
364
  const readFeatures = createFeaturesReader(client);
@@ -376,7 +376,7 @@ async function createHandler$1(config, themeProvider, render = renderPage) {
376
376
  app.use(defineCoreHandler({
377
377
  method: "get",
378
378
  path: "/authorize",
379
- fn: async (event) => render(event, config, {
379
+ fn: async (event) => renderPage(event, config, {
380
380
  url: "/authorize",
381
381
  data: await readAuthorizeInfo(client, event),
382
382
  theme
@@ -387,7 +387,7 @@ async function createHandler$1(config, themeProvider, render = renderPage) {
387
387
  path: page.url,
388
388
  fn: async (event) => {
389
389
  const features = await readFeatures();
390
- return render(event, config, {
390
+ return renderPage(event, config, {
391
391
  url: page.url,
392
392
  data: buildWorkflowPageData(event, features, page),
393
393
  theme
@@ -397,31 +397,46 @@ async function createHandler$1(config, themeProvider, render = renderPage) {
397
397
  app.use(defineCoreHandler({
398
398
  method: "get",
399
399
  path: "/logout",
400
- fn: async (event) => render(event, config, {
400
+ fn: async (event) => renderPage(event, config, {
401
401
  url: "/logout",
402
402
  data: {},
403
403
  theme
404
404
  })
405
405
  }));
406
- const distPath = resolveDistPath();
407
- if (distPath) app.use(ASSETS_PATH, createHandler(path.posix.join(distPath, "client", "assets"), {
408
- fallthrough: false,
409
- scan: false
410
- }));
406
+ const distPath = resolveDistPath(config.distPath);
407
+ if (distPath) {
408
+ if (!render) await assertRenderContract(distPath);
409
+ app.use(ASSETS_PATH, createHandler(path.posix.join(distPath, "client", "assets"), {
410
+ fallthrough: false,
411
+ scan: false,
412
+ cacheMaxAge: 31536e3,
413
+ cacheImmutable: true
414
+ }));
415
+ }
411
416
  return app;
412
417
  }
413
418
  //#endregion
414
- //#region src/server.ts
419
+ //#region src/application.ts
415
420
  /**
416
- * The standalone service: the same handler the CLI mounts in process,
417
- * behind a listener of its own.
421
+ * This console as a runnable service: its own module graph, its own
422
+ * container, its own lifecycle, and nothing of server-core's. It is what
423
+ * `authup start console` starts, what the `authup-auth-console` bin starts, and what
424
+ * `authup start` composes onto its own listener, so no caller reaches past
425
+ * the graph for a bare handler.
426
+ *
427
+ * `config` defaults to a FACTORY, not a value: resolving it reads the
428
+ * document, and a console started alongside others must not do that at
429
+ * construction time, before the caller has said where to look. Pass
430
+ * `listen: false` to be mounted on someone else's listener.
418
431
  */
419
- async function createServer(config) {
420
- const app = new App();
421
- app.use(await createHandler$1(config));
422
- return app;
432
+ function createApplication$1(context = {}) {
433
+ return createApplication({
434
+ listen: context.listen,
435
+ config: context.config ?? readConfigFromEnv,
436
+ createHandler: (resolved, theme) => createHandler$1(resolved, theme)
437
+ });
423
438
  }
424
439
  //#endregion
425
- export { CONFIG_SCHEMA as _, resolvePackagePath as a, createAPIClient as c, sanitizeRelativeRedirect as d, ASSETS_PATH as f, VITE_BASE as g, PAGES as h, resolveDistPath as i, createFeaturesReader as l, HEALTH_PATH as m, createHandler$1 as n, setPackagePath as o, BASE_PATH as p, renderPage as r, buildWorkflowPageData as s, createServer as t, readAuthorizeInfo as u, readConfigFromEnv as v, resolveConfig as y };
440
+ export { VITE_BASE as _, resolveDistPath as a, resolveConfig as b, createAPIClient as c, sanitizeRelativeRedirect as d, ASSETS_PATH as f, PAGES as g, HEALTH_PATH as h, createRenderPage as i, createFeaturesReader as l, CONTRACT_VERSION as m, createHandler$1 as n, resolvePackagePath as o, BASE_PATH as p, assertRenderContract as r, buildWorkflowPageData as s, createApplication$1 as t, readAuthorizeInfo as u, CONFIG_SCHEMA as v, readConfigFromEnv as y };
426
441
 
427
- //# sourceMappingURL=server-Brhsp_x-.mjs.map
442
+ //# sourceMappingURL=application-YmicGt26.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"application-YmicGt26.mjs","names":["createHandler","createAssetsHandler","createApplication","createConsoleApplication","createHandler"],"sources":["../src/config.ts","../src/constants.ts","../src/redirect.ts","../src/payload.ts","../src/path.ts","../src/resolve.ts","../src/render.ts","../src/handler.ts","../src/application.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport {\n AUTH_CONSOLE_SCHEMA,\n CORE_SCHEMA,\n ROOT_SCHEMA,\n SECTION_KEY,\n THEME_SCHEMA,\n} from '@authup/server-config';\nimport {\n buildSchemaDefaults,\n defineSchema,\n mergeSchemaData,\n mountSchema,\n readSchemaFromEnv,\n resolveSchemaData,\n} from '@authup/server-config-kit';\nimport { Container } from 'validup';\nimport type { CoreConfig, EnvironmentVariable } from '@authup/server-config';\nimport type { Config, ConfigInput } from './types';\n\n/**\n * The keys this service reads, SELECTED out of the document schema by name.\n *\n * Nothing is declared here: every key of `authup.yml` is declared once in\n * `@authup/server-config`, so this service cannot spell a path, an\n * environment variable, a default or a reader differently from server-core,\n * which reads several of the same keys. Neither package depends on the other;\n * both depend on the declaration.\n *\n * Its own section is spread, so this service reads it in its own vocabulary\n * (`url`, `port`, `host`); every other section stays under the key the\n * document nests it at.\n */\nexport const CONFIG_SCHEMA = defineSchema<ConfigInput, 'publicUrl' | 'internalUrl' | 'db'>({\n ...AUTH_CONSOLE_SCHEMA,\n ...ROOT_SCHEMA,\n [SECTION_KEY.THEME]: THEME_SCHEMA,\n // Two keys of core, never the section. `publicUrl` derives from\n // the core listener address, which is what a console needs; selecting the\n // whole section also selects its INVARIANTS, and `resolveSchemaData` runs\n // every resolver in a registry -- so a console would refuse to start on a\n // short SECRETS_ENCRYPTION_KEY, or on mfaRequired without mfaEnabled, for\n // keys it has no database, key store or MFA to use.\n [SECTION_KEY.CORE]: defineSchema<Pick<CoreConfig, 'host' | 'port'>, never, EnvironmentVariable>({\n host: CORE_SCHEMA.host,\n port: CORE_SCHEMA.port,\n }),\n});\n\n/**\n * Turn the configuration namespace into the service's own shape: validate the\n * input against the declared zod types, fill the defaults, derive the one key\n * that is derived rather than configured, and rename. An empty `url` means the console sits on server-core's own origin\n * under the default segment, which is the single-origin deployment.\n *\n * The defaults are layered SECTION-AWARE ({@link mergeSchemaData}): a spread\n * would let an input carrying one key of a section replace the whole section\n * and take every other key's default with it.\n */\nexport async function resolveConfig(\n input: Partial<ConfigInput>,\n) : Promise<Config> {\n const validator = new Container<ConfigInput>();\n mountSchema(validator, CONFIG_SCHEMA);\n\n const parsed = await validator.run(input);\n\n const values = resolveSchemaData<ConfigInput>(\n CONFIG_SCHEMA,\n mergeSchemaData<ConfigInput>(\n CONFIG_SCHEMA,\n buildSchemaDefaults<ConfigInput>(CONFIG_SCHEMA),\n parsed,\n ),\n ) as ConfigInput;\n\n return {\n url: values.url,\n apiUrl: values.publicUrl,\n // `internalUrl` falls back to `publicUrl`, so an operator who has one\n // address for both sides configures nothing. The composed `authup\n // start` overrides this with its own listen address unless the\n // document named one.\n apiInternalUrl: values.internalUrl,\n port: values.port,\n host: values.host,\n distPath: values.path,\n theme: values.theme,\n };\n}\n\n/**\n * The standalone entry's configuration, read from the environment alone.\n * `authup.yml` reaches this service through the CLI roles, which compose this\n * very registry into the one document loader.\n */\nexport async function readConfigFromEnv() : Promise<Config> {\n // The explicit type argument is load-bearing: inferred from the schema\n // object, a key declared without a default (the derived publicUrl) comes\n // back as unknown.\n const input : Partial<ConfigInput> = readSchemaFromEnv<ConfigInput>(CONFIG_SCHEMA);\n\n return resolveConfig(input);\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { CONTRACT_VERSION as BundleContractVersion } from '@authup/client-auth-console';\n\n/**\n * The render contract this service compiles against. The literal type fails\n * the build the moment the bundle's constant moves without this one.\n */\nexport const CONTRACT_VERSION : typeof BundleContractVersion = 3;\n\n/**\n * The vite base the auth console bundle is built with. Asset hrefs in the\n * shell are emitted against it, so the service mounts its assets there and\n * rebases them only when the deployment carries a sub-path.\n */\nexport const VITE_BASE = '/console/auth/';\n\n/**\n * The path the service is served under by default, i.e. the path component\n * of `authConsole.url`. It is the same segment server-core mounted\n * the assets at before the split, so a built bundle needs no rebuild.\n */\nexport const BASE_PATH = '/console/auth';\n\n/**\n * The pages the service renders. Each is a route of the console bundle's\n * own router, and each is what a server-core page GET now redirects to.\n */\nexport const PAGES = [\n '/authorize',\n '/register',\n '/activate',\n '/password-forgot',\n '/password-reset',\n '/logout',\n] as const;\n\n/**\n * Where this service mounts the bundle's `assets/` directory. The public\n * asset URL is the service's own public path plus this route, which is what\n * the bundle's fixed vite base is rebased onto.\n */\nexport const ASSETS_PATH = '/assets';\n\nexport const HEALTH_PATH = '/healthy';\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\n/**\n * Open-redirect guard for the `redirect` query parameter carried by the\n * auth workflow pages: only same-origin relative paths pass through, so the\n * value can be safely reflected into an anchor href / forwarded as a\n * post-login destination.\n *\n * Rejects: non-strings, control/whitespace chars (browsers strip some,\n * shifting where the URL authority begins), protocol-relative (`//host`),\n * backslash variants (browsers normalise `\\` → `/`), and any scheme\n * (`javascript:`, `http:`) — both on the raw and once-decoded form so a\n * percent-encoded separator (`%2f`, `%5c`) can't slip past.\n */\nexport function sanitizeRelativeRedirect(input: unknown): string | undefined {\n if (typeof input !== 'string' || input.length === 0) {\n return undefined;\n }\n\n // Raw input: reject control chars AND whitespace (space/tab/newline) — a\n // raw space can shift where the URL authority begins.\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u0020\\u007f]/.test(input)) {\n return undefined;\n }\n\n let decoded: string;\n try {\n decoded = decodeURIComponent(input);\n } catch {\n return undefined;\n }\n\n // Decoded form: reject C0 controls + DEL (CR/LF/NUL — the header-injection\n // vectors a `%0a`/`%0d` would smuggle past the raw check) but NOT space\n // (0x20), so a legitimate `%20` in the path still passes.\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001f\\u007f]/.test(decoded)) {\n return undefined;\n }\n\n for (const candidate of [input, decoded]) {\n if (!candidate.startsWith('/')) {\n return undefined;\n }\n if (candidate.length > 1 && (candidate[1] === '/' || candidate[1] === '\\\\')) {\n return undefined;\n }\n if (candidate.includes('\\\\')) {\n return undefined;\n }\n }\n\n return input;\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { AuthorizeInfo, StatusResponse, StatusResponseFeatures } from '@authup/core-http-kit';\nimport { Client } from '@authup/core-http-kit';\nimport { useRequestQuery } from '@routup/basic/query';\nimport type { IAppEvent } from 'routup';\nimport { sanitizeRelativeRedirect } from './redirect';\nimport type { Config } from './types';\n\n/**\n * The service hydrates ANONYMOUSLY. It holds no credential of its own and\n * asks server-core only for what an unauthenticated visitor may see, which\n * is the whole of what these pages render.\n *\n * It dispatches against `apiInternalUrl`, never the browser-facing `apiUrl`:\n * this is a server-side call, so it must not depend on the public address\n * resolving from inside the deployment. Unlike server-core's own internal\n * client the swap is on `baseURL` rather than at the transport layer,\n * because nothing derived from it reaches a caller -- there is no\n * `redirect_uri` here to be compared byte for byte later.\n */\nexport function createAPIClient(config: Config) : Client {\n return new Client({ baseURL: config.apiInternalUrl });\n}\n\n/**\n * Report an outbound failure to the operator and raise one the visitor may\n * see, which is deliberately not the same error.\n *\n * hapic embeds the resolved request URL in its message, and this service has\n * no error middleware: routup answers with `event.error.message` verbatim, on\n * the public login page. That URL is `apiInternalUrl`, which on a split\n * deployment names a service on the operator's own network, so an API that is\n * merely down would publish its internal address to every anonymous visitor.\n * It was harmless while the value was `publicUrl`, an address the browser\n * already had.\n *\n * The rule and the remedy are server-core's own (`sanitizeError` /\n * `describeError`): the caller gets a message it can act on, the detail goes\n * to the log. `console.error` rather than a logger because a console handler\n * is built from a config alone and is given none -- and because this line is\n * the only trace such a failure leaves, which is how #3550 was diagnosed in\n * the first place.\n */\nfunction failWithoutAddress(error: unknown) : never {\n // eslint-disable-next-line no-console\n console.error('[authup] the auth console could not reach the API:', error);\n\n throw new Error('The identity provider could not be reached.');\n}\n\n/**\n * The feature flags every workflow page gates its form on. A page that\n * cannot reach them must not silently claim a workflow is disabled, so the\n * failure propagates rather than defaulting to false.\n *\n * Read ONCE and held for the reader's lifetime, the same shape render.ts\n * keeps for the dist template, manifest and render entry: `GET /` reports\n * the version and the boot-config feature flags, none of which changes\n * without a restart, so a TTL would be a number with nothing behind it.\n * Without it every workflow page render is an API call, and under the\n * composed `authup start` those all arrive over loopback on one shared\n * rate-limit key. Only a SUCCESS is held, so an API that was down at the\n * first render is retried rather than remembered as broken.\n *\n * The cache is instance-scoped rather than module-scoped (098 C4): the\n * answer depends on which API the client was built against, so two handlers\n * in one process must not share one, and a module slot would additionally\n * leak between test cases.\n */\nexport function createFeaturesReader(client: Client) : () => Promise<StatusResponseFeatures> {\n let cached : StatusResponseFeatures | undefined;\n let pending : Promise<StatusResponseFeatures> | undefined;\n\n return () => {\n if (cached) {\n return Promise.resolve(cached);\n }\n\n if (pending) {\n return pending;\n }\n\n pending = client.status.get()\n .then((status: StatusResponse) => {\n cached = status.features;\n\n return cached;\n })\n .catch((e) => failWithoutAddress(e));\n\n const clear = () => {\n pending = undefined;\n };\n pending.then(clear, clear);\n\n return pending;\n };\n}\n\n/**\n * The `/authorize` render input, resolved by server-core (plan 101 D2-1).\n * The browser's query is forwarded verbatim: the answer is derived from all\n * of it, the `provider` hint and the closed `error` marker set included.\n */\nexport async function readAuthorizeInfo(client: Client, event: IAppEvent) : Promise<AuthorizeInfo> {\n const { search } = new URL(event.request.url);\n\n try {\n return await client.authorize.getInfo(search);\n } catch (e) {\n return failWithoutAddress(e);\n }\n}\n\nexport type WorkflowPageOptions = {\n // Whether the page consumes the `realmId` (legacy `realm_id`) / `token`\n // query params (e.g. prefill from an email deep link). Off by default so\n // a page never reflects a param it doesn't use.\n realmAware?: boolean,\n tokenAware?: boolean,\n};\n\n/**\n * The payload the four workflow pages render from: the feature flags plus\n * the handful of query params each one declares. Unchanged from the\n * assembly server-core did before the split, including the open-redirect\n * guard on `redirect`.\n */\nexport function buildWorkflowPageData(\n event: IAppEvent,\n features: StatusResponseFeatures,\n options: WorkflowPageOptions = {},\n) : Record<string, any> {\n const query = useRequestQuery(event);\n\n const data: Record<string, any> = {\n features,\n redirect: sanitizeRelativeRedirect(query.redirect),\n };\n\n if (options.realmAware) {\n const realmId = typeof query.realmId === 'string' ? query.realmId : query.realm_id;\n data.realmId = typeof realmId === 'string' ? realmId : undefined;\n }\n\n if (options.tokenAware) {\n data.token = typeof query.token === 'string' ? query.token : undefined;\n }\n\n return data;\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport const CODE_PATH = path.dirname(fileURLToPath(import.meta.url));\nexport const PACKAGE_PATH = path.join(CODE_PATH, '..');\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { locateUpSync } from 'locter';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { PACKAGE_PATH } from './path';\n\n/**\n * Locate the auth console package: a substituted one (config `distPath`)\n * first, else the SSR auth workflow UI shipped as\n * `@authup/client-auth-console` (a dependency of THIS service), which the\n * node_modules ancestor walk from this package's own root (locter's\n * `locateUp`) finds for the workspace (symlink onto\n * apps/client-auth-console) and for a published install alike.\n *\n * The `cwd` anchor is behavioural, not incidental: it decides which\n * node_modules tree is walked, so it stays pinned to this package rather\n * than to the process cwd.\n */\nexport function resolvePackagePath(distPath?: string) : string | undefined {\n return distPath || locateUpSync(\n 'node_modules/@authup/client-auth-console/package.json',\n { cwd: PACKAGE_PATH },\n )?.directory;\n}\n\n/**\n * Locate the BUILT auth console bundle (`dist/client` template + assets,\n * `dist/server/server.js` render entry). A half-built package is no bundle:\n * without the render entry there is nothing to render with.\n */\nexport function resolveDistPath(distPath?: string) : string | undefined {\n const packagePath = resolvePackagePath(distPath);\n if (!packagePath) {\n return undefined;\n }\n\n const candidate = path.join(packagePath, 'dist');\n if (\n fs.existsSync(path.join(candidate, 'client', 'index.html')) &&\n fs.existsSync(path.join(candidate, 'server', 'server.js'))\n ) {\n return candidate;\n }\n\n return undefined;\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { HydrationPayload, RenderFunction } from '@authup/client-auth-console';\nimport { InternalError } from '@authup/errors';\nimport { getURLBasePath } from '@authup/kit';\nimport {\n applyTheme,\n applyUIPageHeaders,\n readUIClientPreferences,\n rebaseAssetURLs,\n replaceTemplateMarker,\n stampHtmlAttributes,\n} from '@authup/server-console-kit';\nimport { read } from 'locter';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { CONTRACT_VERSION, VITE_BASE } from './constants';\nimport { resolveDistPath } from './resolve';\nimport type { RenderPage } from './types';\n\n/**\n * Refuse a bundle built against another render contract at boot, naming the\n * entry and both versions, instead of failing per request on `/authorize`.\n * A bundle predating the export is version 1. Reads the module itself, which\n * Node's ESM cache then hands the render for free.\n */\nexport async function assertRenderContract(distPath: string) : Promise<void> {\n const entry = path.join(distPath, 'server', 'server.js');\n const bundle = await read(entry) as Record<string, any>;\n const version = bundle.CONTRACT_VERSION ?? 1;\n\n if (version !== CONTRACT_VERSION) {\n throw new InternalError(\n `The auth console bundle \"${entry}\" implements render-contract version ${version}, ` +\n `but this service requires ${CONTRACT_VERSION}. ` +\n 'Rebuild it against the current @authup/client-auth-console contract.',\n );\n }\n}\n\nexport function createRenderPage(distPath?: string) : RenderPage {\n // Per-handler caches for the immutable production SSR assets: the dist\n // template, manifest and server bundle do not change after boot, so read\n // them once instead of per request (the auth/login routes are hot paths).\n // Held in this closure rather than at module scope so a second handler in\n // the process, with a substituted package, never renders another's\n // template. Only a resolved dist is kept, so a bundle built after boot is\n // picked up on the next request.\n let cachedDistPath: string | undefined;\n let cachedHtml: string | undefined;\n let cachedManifest: Record<string, any> | undefined;\n let cachedRender: RenderFunction | undefined;\n\n return async (event, config, ctx) => {\n const resolved = (cachedDistPath ??= resolveDistPath(distPath));\n if (!resolved) {\n throw new InternalError(\n 'The auth console bundle (@authup/client-auth-console) is not built or installed.',\n );\n }\n\n const html = (cachedHtml ??= await fs.promises.readFile(\n path.join(resolved, 'client', 'index.html'),\n 'utf-8',\n ));\n const manifest = (cachedManifest ??= JSON.parse(await fs.promises.readFile(\n path.join(resolved, 'client', '.vite', 'ssr-manifest.json'),\n 'utf-8',\n )));\n const render = (cachedRender ??= (await read(\n path.join(resolved, 'server', 'server.js'),\n )).render as RenderFunction);\n\n // The client app hydrates locale + color-mode from the payload, so it\n // adopts the same values the HTML shell is stamped with below.\n const preferences = readUIClientPreferences(event);\n\n // `baseURL` is server-core's public URL: the console derives its HTTP\n // client and its cookie path from it. `basePath` is where THIS service\n // is served, which is what the router and every inter-page href need.\n // Before the split the two were the same value; they are not any more,\n // so the console reads them separately.\n const basePath = getURLBasePath(config.url);\n\n const payload : HydrationPayload = {\n config: {\n baseURL: config.apiUrl,\n basePath,\n colorMode: preferences.colorMode,\n locale: preferences.locale,\n },\n data: ctx.data,\n };\n\n const [appHtml, preloadLinks] = await render({\n url: ctx.url,\n manifest,\n payload,\n });\n\n let body = replaceTemplateMarker(html, '<!--preload-links-->', preloadLinks);\n body = replaceTemplateMarker(body, '<!--app-html-->', appHtml);\n\n body = stampHtmlAttributes(body, preferences);\n\n // This service mounts the bundle's assets at its own /assets, so the\n // fixed vite base in every href is replaced by this service's public\n // path. The vite base was decided when the bundle was built and says\n // nothing about where the service is published.\n body = rebaseAssetURLs(body, VITE_BASE, `${basePath}/`);\n\n // The theme's own asset urls are built from the same base, and the\n // service mounts them under it, so a themed deployment needs no rule of\n // its own at the reverse proxy beyond the one that reaches this service.\n body = await applyTheme(body, ctx.theme, basePath);\n\n applyUIPageHeaders(event);\n\n return body;\n };\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { IThemeProvider } from '@authup/server-console-kit';\nimport {\n THEME_ASSET_MOUNT_PATH,\n createThemeAssetsHandler,\n createThemeProvider,\n} from '@authup/server-console-kit';\nimport { createHandler as createAssetsHandler } from '@routup/assets';\nimport { basic } from '@routup/basic';\nimport path from 'node:path';\nimport type { IApp } from 'routup';\nimport { App, defineCoreHandler } from 'routup';\nimport { ASSETS_PATH, HEALTH_PATH } from './constants';\nimport {\n buildWorkflowPageData,\n createAPIClient,\n createFeaturesReader,\n readAuthorizeInfo,\n} from './payload';\nimport { assertRenderContract, createRenderPage } from './render';\nimport { resolveDistPath } from './resolve';\nimport type { Config, RenderPage } from './types';\n\nconst WORKFLOW_PAGES : {\n url: string,\n realmAware?: boolean,\n tokenAware?: boolean\n}[] = [\n { url: '/register', realmAware: true },\n { url: '/activate', tokenAware: true },\n { url: '/password-forgot', realmAware: true },\n {\n url: '/password-reset',\n realmAware: true,\n tokenAware: true,\n },\n];\n\n/**\n * The service as a mountable routup handler, so the CLI can compose it onto\n * server-core's own listener for the single-container deployment while a\n * split deployment runs it behind its own server.\n *\n * The paths are prefix-free. authup assumes a prefix-stripping reverse\n * proxy, so a service published at `<origin>/console/auth` receives\n * `/authorize`, exactly as server-core received it before the split.\n */\nexport async function createHandler(\n config: Config,\n themeProvider?: IThemeProvider,\n render?: RenderPage,\n) : Promise<IApp> {\n const renderPage = render ?? createRenderPage(config.distPath);\n\n const app = new App();\n const client = createAPIClient(config);\n const readFeatures = createFeaturesReader(client);\n\n // The shell is stamped from the vc-locale / vc-color-mode cookies, and\n // without the plugin every cookie read answers undefined, which is a\n // silent flash of the wrong color mode on every full load. server-core\n // registers it globally, so a mounted handler inherits one; a standalone\n // service has nothing above it.\n app.use(basic({ cookie: true, query: true }));\n\n // A missing directory disables theming entirely: no provider is created\n // and the rendered pages are byte-identical to the un-themed ones, so the\n // default configuration pays nothing. An invalid manifest throws here and\n // fails the boot rather than surfacing per request.\n // Injected by the theme module when this runs inside the console\n // application; built here for a caller holding only a config.\n const theme = themeProvider ?? await createThemeProvider(config);\n\n app.use(defineCoreHandler({\n method: 'get',\n path: HEALTH_PATH,\n fn: () => ({ status: 'ok' }),\n }));\n\n if (theme) {\n app.use(THEME_ASSET_MOUNT_PATH, createThemeAssetsHandler(theme));\n }\n\n app.use(defineCoreHandler({\n method: 'get',\n path: '/authorize',\n fn: async (event) => renderPage(event, config, {\n url: '/authorize',\n data: await readAuthorizeInfo(client, event),\n theme,\n }),\n }));\n\n for (const page of WORKFLOW_PAGES) {\n app.use(defineCoreHandler({\n method: 'get',\n path: page.url,\n fn: async (event) => {\n const features = await readFeatures();\n\n return renderPage(event, config, {\n url: page.url,\n data: buildWorkflowPageData(event, features, page),\n theme,\n });\n },\n }));\n }\n\n // The logout page needs nothing resolved up front: it drives the\n // end-session call itself and renders from the answer, so the render\n // stays a pure shell and no request reaches server-core here.\n app.use(defineCoreHandler({\n method: 'get',\n path: '/logout',\n fn: async (event) => renderPage(event, config, {\n url: '/logout',\n data: {},\n theme,\n }),\n }));\n\n // Only the assets directory is mounted, never dist/client itself: the\n // template and the ssr manifest are inputs of the render, not files to\n // serve. A missing bundle only disables the mount; the page routes\n // report the actionable error. A resolved one is checked against the\n // render contract here, unless a substituted render never reads it.\n const distPath = resolveDistPath(config.distPath);\n if (distPath) {\n if (!render) {\n await assertRenderContract(distPath);\n }\n\n // Every name carries a content hash, so a new build means new names.\n app.use(ASSETS_PATH, createAssetsHandler(\n path.posix.join(distPath, 'client', 'assets'),\n {\n fallthrough: false,\n scan: false,\n cacheMaxAge: 60 * 60 * 24 * 365,\n cacheImmutable: true,\n },\n ));\n }\n\n return app;\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { createApplication as createConsoleApplication } from '@authup/server-console-kit';\nimport type { CreateApplicationContext } from '@authup/server-console-kit';\nimport type { Application } from 'orkos';\nimport { readConfigFromEnv } from './config';\nimport { createHandler } from './handler';\nimport type { Config } from './types';\n\n/**\n * This console as a runnable service: its own module graph, its own\n * container, its own lifecycle, and nothing of server-core's. It is what\n * `authup start console` starts, what the `authup-auth-console` bin starts, and what\n * `authup start` composes onto its own listener, so no caller reaches past\n * the graph for a bare handler.\n *\n * `config` defaults to a FACTORY, not a value: resolving it reads the\n * document, and a console started alongside others must not do that at\n * construction time, before the caller has said where to look. Pass\n * `listen: false` to be mounted on someone else's listener.\n */\nexport function createApplication(\n context: Partial<Omit<CreateApplicationContext<Config>, 'createHandler'>> = {},\n) : Application {\n return createConsoleApplication<Config>({\n listen: context.listen,\n config: context.config ?? readConfigFromEnv,\n createHandler: (resolved, theme) => createHandler(resolved, theme),\n });\n}\n"],"mappings":"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"}
package/dist/bin.mjs CHANGED
@@ -1,20 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { t as createServer, v as readConfigFromEnv } from "./server-Brhsp_x-.mjs";
3
- import { serve } from "routup/node";
2
+ import { t as createApplication } from "./application-YmicGt26.mjs";
3
+ import process from "node:process";
4
4
  //#region src/bin.ts
5
5
  /**
6
- * The standalone entry. It reads the keys this service declares from the
7
- * environment; `authup.yml` reaches the service through the CLI roles
8
- * (`authup start console auth`), which hand each factory its own section, and that
9
- * is also how it is started in practice.
6
+ * The standalone entry: the same application `authup start console auth`
7
+ * starts, configured from the environment alone. `authup.yml` reaches this
8
+ * service through the CLI roles, which hand each factory its own section, and
9
+ * that is also how it is started in practice.
10
10
  */
11
- const config = readConfigFromEnv();
12
- const app = await createServer(config);
13
- await serve(app, {
14
- port: config.port,
15
- hostname: config.host,
16
- silent: true
17
- }).ready();
11
+ const application = createApplication();
12
+ await application.setup();
13
+ const shutdown = () => application.teardown().then(() => process.exit(0), () => process.exit(1));
14
+ process.once("SIGINT", shutdown);
15
+ process.once("SIGTERM", shutdown);
18
16
  //#endregion
19
17
  export {};
20
18
 
package/dist/bin.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.mjs","names":[],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { serve } from 'routup/node';\nimport { readConfigFromEnv } from './config';\nimport { createServer } from './server';\n\n/**\n * The standalone entry. It reads the keys this service declares from the\n * environment; `authup.yml` reaches the service through the CLI roles\n * (`authup start console auth`), which hand each factory its own section, and that\n * is also how it is started in practice.\n */\nconst config = readConfigFromEnv();\nconst app = await createServer(config);\n\nconst server = serve(app, {\n port: config.port,\n hostname: config.host,\n silent: true,\n});\n\nawait server.ready();\n"],"mappings":";;;;;;;;;;AAkBA,MAAM,SAAS,kBAAkB;AACjC,MAAM,MAAM,MAAM,aAAa,MAAM;AAQrC,MANe,MAAM,KAAK;CACtB,MAAM,OAAO;CACb,UAAU,OAAO;CACjB,QAAQ;AACZ,CAEW,CAAC,CAAC,MAAM"}
1
+ {"version":3,"file":"bin.mjs","names":[],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport process from 'node:process';\nimport { createApplication } from './application';\n\n/**\n * The standalone entry: the same application `authup start console auth`\n * starts, configured from the environment alone. `authup.yml` reaches this\n * service through the CLI roles, which hand each factory its own section, and\n * that is also how it is started in practice.\n */\nconst application = createApplication();\nawait application.setup();\n\nconst shutdown = () => application.teardown()\n .then(() => process.exit(0), () => process.exit(1));\nprocess.once('SIGINT', shutdown);\nprocess.once('SIGTERM', shutdown);\n"],"mappings":";;;;;;;;;;AAiBA,MAAM,cAAc,kBAAkB;AACtC,MAAM,YAAY,MAAM;AAExB,MAAM,iBAAiB,YAAY,SAAS,CAAC,CACxC,WAAW,QAAQ,KAAK,CAAC,SAAS,QAAQ,KAAK,CAAC,CAAC;AACtD,QAAQ,KAAK,UAAU,QAAQ;AAC/B,QAAQ,KAAK,WAAW,QAAQ"}
package/dist/config.d.ts CHANGED
@@ -18,20 +18,20 @@ export declare const CONFIG_SCHEMA: import("@authup/server-config-kit").Schema<i
18
18
  core: Pick<CoreConfig, "host" | "port">;
19
19
  } & import("@authup/server-config").AuthConsoleConfig>, "publicUrl" | "internalUrl" | "db", string>;
20
20
  /**
21
- * Turn the configuration namespace into the service's own shape: fill the
22
- * defaults, derive the one key that is derived rather than configured, and
23
- * rename. An empty `url` means the console sits on server-core's own origin
21
+ * Turn the configuration namespace into the service's own shape: validate the
22
+ * input against the declared zod types, fill the defaults, derive the one key
23
+ * that is derived rather than configured, and rename. An empty `url` means the console sits on server-core's own origin
24
24
  * under the default segment, which is the single-origin deployment.
25
25
  *
26
26
  * The defaults are layered SECTION-AWARE ({@link mergeSchemaData}): a spread
27
27
  * would let an input carrying one key of a section replace the whole section
28
28
  * and take every other key's default with it.
29
29
  */
30
- export declare function resolveConfig(input: Partial<ConfigInput>): Config;
30
+ export declare function resolveConfig(input: Partial<ConfigInput>): Promise<Config>;
31
31
  /**
32
32
  * The standalone entry's configuration, read from the environment alone.
33
33
  * `authup.yml` reaches this service through the CLI roles, which compose this
34
34
  * very registry into the one document loader.
35
35
  */
36
- export declare function readConfigFromEnv(): Config;
36
+ export declare function readConfigFromEnv(): Promise<Config>;
37
37
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,UAAU,EAAuB,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEnD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa;;;mGAcxB,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CACzB,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,GAC3B,MAAM,CAuBT;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAK,MAAM,CAO3C"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,UAAU,EAAuB,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEnD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa;;;mGAcxB,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CAC/B,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,GAC3B,OAAO,CAAC,MAAM,CAAC,CA4BlB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAK,OAAO,CAAC,MAAM,CAAC,CAO1D"}
@@ -1,3 +1,9 @@
1
+ import type { CONTRACT_VERSION as BundleContractVersion } from '@authup/client-auth-console';
2
+ /**
3
+ * The render contract this service compiles against. The literal type fails
4
+ * the build the moment the bundle's constant moves without this one.
5
+ */
6
+ export declare const CONTRACT_VERSION: typeof BundleContractVersion;
1
7
  /**
2
8
  * The vite base the auth console bundle is built with. Asset hrefs in the
3
9
  * shell are emitted against it, so the service mounts its assets there and
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,eAAO,MAAM,SAAS,mBAAmB,CAAC;AAE1C;;;;GAIG;AACH,eAAO,MAAM,SAAS,kBAAkB,CAAC;AAEzC;;;GAGG;AACH,eAAO,MAAM,KAAK,qGAOR,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,WAAW,YAAY,CAAC;AAErC,eAAO,MAAM,WAAW,aAAa,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,IAAI,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAE7F;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAG,OAAO,qBAAyB,CAAC;AAEjE;;;;GAIG;AACH,eAAO,MAAM,SAAS,mBAAmB,CAAC;AAE1C;;;;GAIG;AACH,eAAO,MAAM,SAAS,kBAAkB,CAAC;AAEzC;;;GAGG;AACH,eAAO,MAAM,KAAK,qGAOR,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,WAAW,YAAY,CAAC;AAErC,eAAO,MAAM,WAAW,aAAa,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AASjE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAWnC,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAiBlD;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAC/B,MAAM,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,cAAc,EAC9B,MAAM,GAAE,UAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAuFhB"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AASjE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAWnC,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAiBlD;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAC/B,MAAM,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,cAAc,EAC9B,MAAM,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,IAAI,CAAC,CA+FhB"}
package/dist/index.d.mts CHANGED
@@ -3,6 +3,7 @@ import { AuthConsoleConfig, CoreConfig, RootConfig, SECTION_KEY, ThemeConfig, To
3
3
  import { App, IApp, IAppEvent } from "routup";
4
4
  import { AuthorizeInfo, Client, StatusResponseFeatures } from "@authup/core-http-kit";
5
5
  import { Application } from "orkos";
6
+ import { CONTRACT_VERSION as CONTRACT_VERSION$1 } from "@authup/client-auth-console";
6
7
  //#region src/types.d.ts
7
8
  /**
8
9
  * The `authup.yml` NAMESPACE: the sections this service selects keys from.
@@ -13,7 +14,7 @@ import { Application } from "orkos";
13
14
  * `distPath` below are this service's names for values the document calls
14
15
  * something else, so they belong to {@link Config} alone.
15
16
  */
16
- type ConfigInput = ToObjectLiteral<RootConfig & {
17
+ export type ConfigInput = ToObjectLiteral<RootConfig & {
17
18
  [SECTION_KEY.THEME]: ThemeConfig;
18
19
  [SECTION_KEY.CORE]: Pick<CoreConfig, 'host' | 'port'>;
19
20
  } & AuthConsoleConfig>;
@@ -22,7 +23,7 @@ type ConfigInput = ToObjectLiteral<RootConfig & {
22
23
  * and server-core is the external thing it calls, hence `url` for its own
23
24
  * address and `apiUrl` for the API's.
24
25
  */
25
- type Config = {
26
+ export type Config = {
26
27
  /**
27
28
  * The public URL this service is reachable at, e.g.
28
29
  * `https://example.com/console/auth`. Its path component becomes the
@@ -67,10 +68,10 @@ type Config = {
67
68
  };
68
69
  /**
69
70
  * How a page becomes a document. The default is this service's own
70
- * `renderPage`, reading the built bundle; a caller serving the console from
71
- * source substitutes one that reads through a vite dev server instead.
71
+ * `createRenderPage`, reading the built bundle; a caller serving the console
72
+ * from source substitutes one that reads through a vite dev server instead.
72
73
  */
73
- type RenderPage = (event: IAppEvent, config: Config, ctx: {
74
+ export type RenderPage = (event: IAppEvent, config: Config, ctx: {
74
75
  url: string;
75
76
  data: Record<string, any>;
76
77
  theme?: IThemeProvider;
@@ -89,7 +90,7 @@ type RenderPage = (event: IAppEvent, config: Config, ctx: {
89
90
  * construction time, before the caller has said where to look. Pass
90
91
  * `listen: false` to be mounted on someone else's listener.
91
92
  */
92
- declare function createApplication(context?: Partial<Omit<CreateApplicationContext<Config>, 'createHandler'>>): Application;
93
+ export declare function createApplication(context?: Partial<Omit<CreateApplicationContext<Config>, 'createHandler'>>): Application;
93
94
  //#endregion
94
95
  //#region src/config.d.ts
95
96
  /**
@@ -105,53 +106,58 @@ declare function createApplication(context?: Partial<Omit<CreateApplicationConte
105
106
  * (`url`, `port`, `host`); every other section stays under the key the
106
107
  * document nests it at.
107
108
  */
108
- declare const CONFIG_SCHEMA: import("@authup/server-config-kit").Schema<import("@authup/server-config").ToObjectLiteral<import("@authup/server-config").RootConfig & {
109
+ export declare const CONFIG_SCHEMA: import("@authup/server-config-kit").Schema<import("@authup/server-config").ToObjectLiteral<import("@authup/server-config").RootConfig & {
109
110
  theme: import("@authup/server-config").ThemeConfig;
110
111
  core: Pick<CoreConfig, "host" | "port">;
111
112
  } & import("@authup/server-config").AuthConsoleConfig>, "publicUrl" | "internalUrl" | "db", string>;
112
113
  /**
113
- * Turn the configuration namespace into the service's own shape: fill the
114
- * defaults, derive the one key that is derived rather than configured, and
115
- * rename. An empty `url` means the console sits on server-core's own origin
114
+ * Turn the configuration namespace into the service's own shape: validate the
115
+ * input against the declared zod types, fill the defaults, derive the one key
116
+ * that is derived rather than configured, and rename. An empty `url` means the console sits on server-core's own origin
116
117
  * under the default segment, which is the single-origin deployment.
117
118
  *
118
119
  * The defaults are layered SECTION-AWARE ({@link mergeSchemaData}): a spread
119
120
  * would let an input carrying one key of a section replace the whole section
120
121
  * and take every other key's default with it.
121
122
  */
122
- declare function resolveConfig(input: Partial<ConfigInput>): Config;
123
+ export declare function resolveConfig(input: Partial<ConfigInput>): Promise<Config>;
123
124
  /**
124
125
  * The standalone entry's configuration, read from the environment alone.
125
126
  * `authup.yml` reaches this service through the CLI roles, which compose this
126
127
  * very registry into the one document loader.
127
128
  */
128
- declare function readConfigFromEnv(): Config;
129
+ export declare function readConfigFromEnv(): Promise<Config>;
129
130
  //#endregion
130
131
  //#region src/constants.d.ts
132
+ /**
133
+ * The render contract this service compiles against. The literal type fails
134
+ * the build the moment the bundle's constant moves without this one.
135
+ */
136
+ export declare const CONTRACT_VERSION: typeof CONTRACT_VERSION$1;
131
137
  /**
132
138
  * The vite base the auth console bundle is built with. Asset hrefs in the
133
139
  * shell are emitted against it, so the service mounts its assets there and
134
140
  * rebases them only when the deployment carries a sub-path.
135
141
  */
136
- declare const VITE_BASE = "/console/auth/";
142
+ export declare const VITE_BASE = "/console/auth/";
137
143
  /**
138
144
  * The path the service is served under by default, i.e. the path component
139
145
  * of `authConsole.url`. It is the same segment server-core mounted
140
146
  * the assets at before the split, so a built bundle needs no rebuild.
141
147
  */
142
- declare const BASE_PATH = "/console/auth";
148
+ export declare const BASE_PATH = "/console/auth";
143
149
  /**
144
150
  * The pages the service renders. Each is a route of the console bundle's
145
151
  * own router, and each is what a server-core page GET now redirects to.
146
152
  */
147
- declare const PAGES: readonly ["/authorize", "/register", "/activate", "/password-forgot", "/password-reset", "/logout"];
153
+ export declare const PAGES: readonly ["/authorize", "/register", "/activate", "/password-forgot", "/password-reset", "/logout"];
148
154
  /**
149
155
  * Where this service mounts the bundle's `assets/` directory. The public
150
156
  * asset URL is the service's own public path plus this route, which is what
151
157
  * the bundle's fixed vite base is rebased onto.
152
158
  */
153
- declare const ASSETS_PATH = "/assets";
154
- declare const HEALTH_PATH = "/healthy";
159
+ export declare const ASSETS_PATH = "/assets";
160
+ export declare const HEALTH_PATH = "/healthy";
155
161
  //#endregion
156
162
  //#region src/handler.d.ts
157
163
  /**
@@ -163,7 +169,7 @@ declare const HEALTH_PATH = "/healthy";
163
169
  * proxy, so a service published at `<origin>/console/auth` receives
164
170
  * `/authorize`, exactly as server-core received it before the split.
165
171
  */
166
- declare function createHandler(config: Config, themeProvider?: IThemeProvider, render?: RenderPage): Promise<IApp>;
172
+ export declare function createHandler(config: Config, themeProvider?: IThemeProvider, render?: RenderPage): Promise<IApp>;
167
173
  //#endregion
168
174
  //#region src/payload.d.ts
169
175
  /**
@@ -178,7 +184,7 @@ declare function createHandler(config: Config, themeProvider?: IThemeProvider, r
178
184
  * because nothing derived from it reaches a caller -- there is no
179
185
  * `redirect_uri` here to be compared byte for byte later.
180
186
  */
181
- declare function createAPIClient(config: Config): Client;
187
+ export declare function createAPIClient(config: Config): Client;
182
188
  /**
183
189
  * The feature flags every workflow page gates its form on. A page that
184
190
  * cannot reach them must not silently claim a workflow is disabled, so the
@@ -198,14 +204,14 @@ declare function createAPIClient(config: Config): Client;
198
204
  * in one process must not share one, and a module slot would additionally
199
205
  * leak between test cases.
200
206
  */
201
- declare function createFeaturesReader(client: Client): () => Promise<StatusResponseFeatures>;
207
+ export declare function createFeaturesReader(client: Client): () => Promise<StatusResponseFeatures>;
202
208
  /**
203
209
  * The `/authorize` render input, resolved by server-core (plan 101 D2-1).
204
210
  * The browser's query is forwarded verbatim: the answer is derived from all
205
211
  * of it, the `provider` hint and the closed `error` marker set included.
206
212
  */
207
- declare function readAuthorizeInfo(client: Client, event: IAppEvent): Promise<AuthorizeInfo>;
208
- type WorkflowPageOptions = {
213
+ export declare function readAuthorizeInfo(client: Client, event: IAppEvent): Promise<AuthorizeInfo>;
214
+ export type WorkflowPageOptions = {
209
215
  realmAware?: boolean;
210
216
  tokenAware?: boolean;
211
217
  };
@@ -215,7 +221,7 @@ type WorkflowPageOptions = {
215
221
  * assembly server-core did before the split, including the open-redirect
216
222
  * guard on `redirect`.
217
223
  */
218
- declare function buildWorkflowPageData(event: IAppEvent, features: StatusResponseFeatures, options?: WorkflowPageOptions): Record<string, any>;
224
+ export declare function buildWorkflowPageData(event: IAppEvent, features: StatusResponseFeatures, options?: WorkflowPageOptions): Record<string, any>;
219
225
  //#endregion
220
226
  //#region src/redirect.d.ts
221
227
  /**
@@ -230,44 +236,44 @@ declare function buildWorkflowPageData(event: IAppEvent, features: StatusRespons
230
236
  * (`javascript:`, `http:`) — both on the raw and once-decoded form so a
231
237
  * percent-encoded separator (`%2f`, `%5c`) can't slip past.
232
238
  */
233
- declare function sanitizeRelativeRedirect(input: unknown): string | undefined;
239
+ export declare function sanitizeRelativeRedirect(input: unknown): string | undefined;
234
240
  //#endregion
235
241
  //#region src/render.d.ts
236
- declare const renderPage: RenderPage;
237
- //#endregion
238
- //#region src/resolve.d.ts
239
242
  /**
240
- * Point the resolution at a substituted package (config `distPath`)
241
- * instead of the node_modules walk. Called once at boot.
243
+ * Refuse a bundle built against another render contract at boot, naming the
244
+ * entry and both versions, instead of failing per request on `/authorize`.
245
+ * A bundle predating the export is version 1. Reads the module itself, which
246
+ * Node's ESM cache then hands the render for free.
242
247
  */
243
- declare function setPackagePath(value: string | undefined): void;
248
+ export declare function assertRenderContract(distPath: string): Promise<void>;
249
+ export declare function createRenderPage(distPath?: string): RenderPage;
250
+ //#endregion
251
+ //#region src/resolve.d.ts
244
252
  /**
245
- * Locate the auth console package. The SSR auth workflow UI ships as
246
- * `@authup/client-auth-console` (a dependency of THIS service), so the
253
+ * Locate the auth console package: a substituted one (config `distPath`)
254
+ * first, else the SSR auth workflow UI shipped as
255
+ * `@authup/client-auth-console` (a dependency of THIS service), which the
247
256
  * node_modules ancestor walk from this package's own root (locter's
248
- * `locateUp`) finds it for the workspace (symlink onto
249
- * apps/client-auth-console) and for a published install alike. Only a
250
- * positive result is cached.
257
+ * `locateUp`) finds for the workspace (symlink onto
258
+ * apps/client-auth-console) and for a published install alike.
251
259
  *
252
260
  * The `cwd` anchor is behavioural, not incidental: it decides which
253
261
  * node_modules tree is walked, so it stays pinned to this package rather
254
262
  * than to the process cwd.
255
263
  */
256
- declare function resolvePackagePath(): string | undefined;
264
+ export declare function resolvePackagePath(distPath?: string): string | undefined;
257
265
  /**
258
266
  * Locate the BUILT auth console bundle (`dist/client` template + assets,
259
- * `dist/server/server.js` render entry). Only a positive result is cached,
260
- * so a dev building the package after boot is picked up on the next
261
- * request.
267
+ * `dist/server/server.js` render entry). A half-built package is no bundle:
268
+ * without the render entry there is nothing to render with.
262
269
  */
263
- declare function resolveDistPath(): string | undefined;
270
+ export declare function resolveDistPath(distPath?: string): string | undefined;
264
271
  //#endregion
265
272
  //#region src/server.d.ts
266
273
  /**
267
274
  * The standalone service: the same handler the CLI mounts in process,
268
275
  * behind a listener of its own.
269
276
  */
270
- declare function createServer(config: Config): Promise<App>;
277
+ export declare function createServer(config: Config): Promise<App>;
271
278
  //#endregion
272
- export { ASSETS_PATH, BASE_PATH, CONFIG_SCHEMA, Config, ConfigInput, HEALTH_PATH, PAGES, RenderPage, VITE_BASE, WorkflowPageOptions, buildWorkflowPageData, createAPIClient, createApplication, createFeaturesReader, createHandler, createServer, readAuthorizeInfo, readConfigFromEnv, renderPage, resolveConfig, resolveDistPath, resolvePackagePath, sanitizeRelativeRedirect, setPackagePath };
273
279
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/application.ts","../src/config.ts","../src/constants.ts","../src/handler.ts","../src/payload.ts","../src/redirect.ts","../src/render.ts","../src/resolve.ts","../src/server.ts"],"mappings":";;;;;;;;;;;;;;;KA2BY,cAAc,gBACtB;GAEK,YAAY,QAAQ;GAEpB,YAAY,OAAO,KAAK;IAE7B;;;;;;KAQQ;;;;;;EAMR;;;;;;;;EAQA;;;;;;;;;;;;;;;EAeA;;;;;EAKA;EACA;;;;;EAKA;EAEA,OAAO;;;;;;;KAQC,cACR,OAAO,WACP,QAAQ,QACR;EACI;EACA,MAAM;EACN,QAAQ;MAEX;;;;;;;;;;;;;;;iBC1EW,kBACZ,UAAS,QAAQ,KAAK,yBAAyB,6BAC/C;;;;;;;;;;;;;;;;cCSS,mDAAa,uCAAA,gDAAA;;QAAb,KAAA;oCAcX;;;;;;;;;;;iBAYc,cACZ,OAAO,QAAQ,eACf;;;;;;iBA8BY,qBAAsB;;;;;;;;cCnFzB;;;;;;cAOA;;;;;cAMA;;;;;;cAcA;cAEA;;;;;;;;;;;;iBCYS,cAClB,QAAQ,QACR,gBAAgB,gBAChB,SAAQ,aACR,QAAQ;;;;;;;;;;;;;;;iBC/BI,gBAAgB,QAAQ,SAAU;;;;;;;;;;;;;;;;;;;;iBAiDlC,qBAAqB,QAAQ,eAAgB,QAAQ;;;;;;iBAmC/C,kBAAkB,QAAQ,QAAQ,OAAO,YAAa,QAAQ;KAUxE;EAIR;EACA;;;;;;;;iBASY,sBACZ,OAAO,WACP,UAAU,wBACV,UAAS,sBACT;;;;;;;;;;;;;;;iBCvHY,yBAAyB;;;cCa5B,YAAa;;;;;;;iBCZV,eAAe;;;;;;;;;;;;;iBAkBf;;;;;;;iBA4BA;;;;;;;iBCnDM,aAAa,QAAQ,SAAU,QAAQ"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/application.ts","../src/config.ts","../src/constants.ts","../src/handler.ts","../src/payload.ts","../src/redirect.ts","../src/render.ts","../src/resolve.ts","../src/server.ts"],"mappings":";;;;;;;;;;;;;;;;YA2BY,cAAc,gBACtB;GAEK,YAAY,QAAQ;GAEpB,YAAY,OAAO,KAAK;IAE7B;;;;;;YAQQ;;;;;;EAMR;;;;;;;;EAQA;;;;;;;;;;;;;;;EAeA;;;;;EAKA;EACA;;;;;EAKA;EAEA,OAAO;;;;;;;YAQC,cACR,OAAO,WACP,QAAQ,QACR;EACI;EACA,MAAM;EACN,QAAQ;MAEX;;;;;;;;;;;;;;;wBC1EW,kBACZ,UAAS,QAAQ,KAAK,yBAAyB,6BAC/C;;;;;;;;;;;;;;;;qBCWS,mDAAa,uCAAA,gDAAA;;QAAb,KAAA;oCAcX;;;;;;;;;;;wBAYoB,cAClB,OAAO,QAAQ,eACf,QAAQ;;;;;;wBAmCU,qBAAsB,QAAQ;;;;;;;qBCzFvC,yBAA0B;;;;;;qBAO1B;;;;;;qBAOA;;;;;qBAMA;;;;;;qBAcA;qBAEA;;;;;;;;;;;;wBCIS,cAClB,QAAQ,QACR,gBAAgB,gBAChB,SAAS,aACT,QAAQ;;;;;;;;;;;;;;;wBC/BI,gBAAgB,QAAQ,SAAU;;;;;;;;;;;;;;;;;;;;wBAiDlC,qBAAqB,QAAQ,eAAgB,QAAQ;;;;;;wBAmC/C,kBAAkB,QAAQ,QAAQ,OAAO,YAAa,QAAQ;YAUxE;EAIR;EACA;;;;;;;;wBASY,sBACZ,OAAO,WACP,UAAU,wBACV,UAAS,sBACT;;;;;;;;;;;;;;;wBCvHY,yBAAyB;;;;;;;;;wBCYnB,qBAAqB,mBAAoB;wBAc/C,iBAAiB,oBAAqB;;;;;;;;;;;;;;;wBCrBtC,mBAAmB;;;;;;wBAYnB,gBAAgB;;;;;;;wBCrBV,aAAa,QAAQ,SAAU,QAAQ"}
package/dist/index.mjs CHANGED
@@ -1,26 +1,16 @@
1
- import { _ as CONFIG_SCHEMA, a as resolvePackagePath, c as createAPIClient, d as sanitizeRelativeRedirect, f as ASSETS_PATH, g as VITE_BASE, h as PAGES, i as resolveDistPath, l as createFeaturesReader, m as HEALTH_PATH, n as createHandler, o as setPackagePath, p as BASE_PATH, r as renderPage, s as buildWorkflowPageData, t as createServer, u as readAuthorizeInfo, v as readConfigFromEnv, y as resolveConfig } from "./server-Brhsp_x-.mjs";
2
- import { createApplication as createApplication$1 } from "@authup/server-console-kit";
3
- //#region src/application.ts
1
+ import { _ as VITE_BASE, a as resolveDistPath, b as resolveConfig, c as createAPIClient, d as sanitizeRelativeRedirect, f as ASSETS_PATH, g as PAGES, h as HEALTH_PATH, i as createRenderPage, l as createFeaturesReader, m as CONTRACT_VERSION, n as createHandler, o as resolvePackagePath, p as BASE_PATH, r as assertRenderContract, s as buildWorkflowPageData, t as createApplication, u as readAuthorizeInfo, v as CONFIG_SCHEMA, y as readConfigFromEnv } from "./application-YmicGt26.mjs";
2
+ import { App } from "routup";
3
+ //#region src/server.ts
4
4
  /**
5
- * This console as a runnable service: its own module graph, its own
6
- * container, its own lifecycle, and nothing of server-core's. It is what
7
- * `authup start console` starts, what the `authup-auth-console` bin starts, and what
8
- * `authup start` composes onto its own listener, so no caller reaches past
9
- * the graph for a bare handler.
10
- *
11
- * `config` defaults to a FACTORY, not a value: resolving it reads the
12
- * document, and a console started alongside others must not do that at
13
- * construction time, before the caller has said where to look. Pass
14
- * `listen: false` to be mounted on someone else's listener.
5
+ * The standalone service: the same handler the CLI mounts in process,
6
+ * behind a listener of its own.
15
7
  */
16
- function createApplication(context = {}) {
17
- return createApplication$1({
18
- listen: context.listen,
19
- config: context.config ?? readConfigFromEnv,
20
- createHandler: (resolved, theme) => createHandler(resolved, theme)
21
- });
8
+ async function createServer(config) {
9
+ const app = new App();
10
+ app.use(await createHandler(config));
11
+ return app;
22
12
  }
23
13
  //#endregion
24
- export { ASSETS_PATH, BASE_PATH, CONFIG_SCHEMA, HEALTH_PATH, PAGES, VITE_BASE, buildWorkflowPageData, createAPIClient, createApplication, createFeaturesReader, createHandler, createServer, readAuthorizeInfo, readConfigFromEnv, renderPage, resolveConfig, resolveDistPath, resolvePackagePath, sanitizeRelativeRedirect, setPackagePath };
14
+ export { ASSETS_PATH, BASE_PATH, CONFIG_SCHEMA, CONTRACT_VERSION, HEALTH_PATH, PAGES, VITE_BASE, assertRenderContract, buildWorkflowPageData, createAPIClient, createApplication, createFeaturesReader, createHandler, createRenderPage, createServer, readAuthorizeInfo, readConfigFromEnv, resolveConfig, resolveDistPath, resolvePackagePath, sanitizeRelativeRedirect };
25
15
 
26
16
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["createConsoleApplication"],"sources":["../src/application.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { createApplication as createConsoleApplication } from '@authup/server-console-kit';\nimport type { CreateApplicationContext } from '@authup/server-console-kit';\nimport type { Application } from 'orkos';\nimport { readConfigFromEnv } from './config';\nimport { createHandler } from './handler';\nimport type { Config } from './types';\n\n/**\n * This console as a runnable service: its own module graph, its own\n * container, its own lifecycle, and nothing of server-core's. It is what\n * `authup start console` starts, what the `authup-auth-console` bin starts, and what\n * `authup start` composes onto its own listener, so no caller reaches past\n * the graph for a bare handler.\n *\n * `config` defaults to a FACTORY, not a value: resolving it reads the\n * document, and a console started alongside others must not do that at\n * construction time, before the caller has said where to look. Pass\n * `listen: false` to be mounted on someone else's listener.\n */\nexport function createApplication(\n context: Partial<Omit<CreateApplicationContext<Config>, 'createHandler'>> = {},\n) : Application {\n return createConsoleApplication<Config>({\n listen: context.listen,\n config: context.config ?? readConfigFromEnv,\n createHandler: (resolved, theme) => createHandler(resolved, theme),\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;AA0BA,SAAgB,kBACZ,UAA4E,CAAC,GACjE;CACZ,OAAOA,oBAAiC;EACpC,QAAQ,QAAQ;EAChB,QAAQ,QAAQ,UAAU;EAC1B,gBAAgB,UAAU,UAAU,cAAc,UAAU,KAAK;CACrE,CAAC;AACL"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/server.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { App } from 'routup';\nimport { createHandler } from './handler';\nimport type { Config } from './types';\n\n/**\n * The standalone service: the same handler the CLI mounts in process,\n * behind a listener of its own.\n */\nexport async function createServer(config: Config) : Promise<App> {\n const app = new App();\n\n app.use(await createHandler(config));\n\n return app;\n}\n"],"mappings":";;;;;;;AAeA,eAAsB,aAAa,QAA+B;CAC9D,MAAM,MAAM,IAAI,IAAI;CAEpB,IAAI,IAAI,MAAM,cAAc,MAAM,CAAC;CAEnC,OAAO;AACX"}
package/dist/render.d.ts CHANGED
@@ -1,3 +1,10 @@
1
1
  import type { RenderPage } from './types';
2
- export declare const renderPage: RenderPage;
2
+ /**
3
+ * Refuse a bundle built against another render contract at boot, naming the
4
+ * entry and both versions, instead of failing per request on `/authorize`.
5
+ * A bundle predating the export is version 1. Reads the module itself, which
6
+ * Node's ESM cache then hands the render for free.
7
+ */
8
+ export declare function assertRenderContract(distPath: string): Promise<void>;
9
+ export declare function createRenderPage(distPath?: string): RenderPage;
3
10
  //# sourceMappingURL=render.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAS1C,eAAO,MAAM,UAAU,EAAG,UAkEzB,CAAC"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAI,OAAO,CAAC,IAAI,CAAC,CAY3E;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAI,UAAU,CAgF/D"}
package/dist/resolve.d.ts CHANGED
@@ -1,26 +1,20 @@
1
1
  /**
2
- * Point the resolution at a substituted package (config `distPath`)
3
- * instead of the node_modules walk. Called once at boot.
4
- */
5
- export declare function setPackagePath(value: string | undefined): void;
6
- /**
7
- * Locate the auth console package. The SSR auth workflow UI ships as
8
- * `@authup/client-auth-console` (a dependency of THIS service), so the
2
+ * Locate the auth console package: a substituted one (config `distPath`)
3
+ * first, else the SSR auth workflow UI shipped as
4
+ * `@authup/client-auth-console` (a dependency of THIS service), which the
9
5
  * node_modules ancestor walk from this package's own root (locter's
10
- * `locateUp`) finds it for the workspace (symlink onto
11
- * apps/client-auth-console) and for a published install alike. Only a
12
- * positive result is cached.
6
+ * `locateUp`) finds for the workspace (symlink onto
7
+ * apps/client-auth-console) and for a published install alike.
13
8
  *
14
9
  * The `cwd` anchor is behavioural, not incidental: it decides which
15
10
  * node_modules tree is walked, so it stays pinned to this package rather
16
11
  * than to the process cwd.
17
12
  */
18
- export declare function resolvePackagePath(): string | undefined;
13
+ export declare function resolvePackagePath(distPath?: string): string | undefined;
19
14
  /**
20
15
  * Locate the BUILT auth console bundle (`dist/client` template + assets,
21
- * `dist/server/server.js` render entry). Only a positive result is cached,
22
- * so a dev building the package after boot is picked up on the next
23
- * request.
16
+ * `dist/server/server.js` render entry). A half-built package is no bundle:
17
+ * without the render entry there is nothing to render with.
24
18
  */
25
- export declare function resolveDistPath(): string | undefined;
19
+ export declare function resolveDistPath(distPath?: string): string | undefined;
26
20
  //# sourceMappingURL=resolve.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAgBA;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAI,IAAI,CAI/D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,IAAK,MAAM,GAAG,SAAS,CAoBxD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAK,MAAM,GAAG,SAAS,CAerD"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAI,MAAM,GAAG,SAAS,CAKzE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAI,MAAM,GAAG,SAAS,CAetE"}
package/dist/types.d.ts CHANGED
@@ -64,8 +64,8 @@ export type Config = {
64
64
  };
65
65
  /**
66
66
  * How a page becomes a document. The default is this service's own
67
- * `renderPage`, reading the built bundle; a caller serving the console from
68
- * source substitutes one that reads through a vite dev server instead.
67
+ * `createRenderPage`, reading the built bundle; a caller serving the console
68
+ * from source substitutes one that reads through a vite dev server instead.
69
69
  */
70
70
  export type RenderPage = (event: IAppEvent, config: Config, ctx: {
71
71
  url: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@authup/server-auth-console",
3
3
  "type": "module",
4
- "version": "1.0.0-beta.64",
4
+ "version": "1.0.0-beta.65",
5
5
  "description": "The SSR service behind authup's hosted auth pages: it renders the auth console bundle and hydrates it anonymously over HTTP.",
6
6
  "license": "AGPL-3.0-only",
7
7
  "bin": {
@@ -51,21 +51,23 @@
51
51
  },
52
52
  "homepage": "https://github.com/authup/authup#readme",
53
53
  "dependencies": {
54
- "@authup/client-auth-console": "^1.0.0-beta.64",
55
- "@authup/core-http-kit": "^1.0.0-beta.64",
56
- "@authup/errors": "^1.0.0-beta.64",
57
- "@authup/kit": "^1.0.0-beta.64",
58
- "@authup/server-config-kit": "^1.0.0-beta.64",
59
- "@authup/server-config": "^1.0.0-beta.64",
60
- "@authup/server-console-kit": "^1.0.0-beta.64",
54
+ "@authup/client-auth-console": "^1.0.0-beta.65",
55
+ "@authup/core-http-kit": "^1.0.0-beta.65",
56
+ "@authup/errors": "^1.0.0-beta.65",
57
+ "@authup/kit": "^1.0.0-beta.65",
58
+ "@authup/server-config": "^1.0.0-beta.65",
59
+ "@authup/server-config-kit": "^1.0.0-beta.65",
60
+ "@authup/server-console-kit": "^1.0.0-beta.65",
61
61
  "@routup/assets": "^5.0.0",
62
62
  "@routup/basic": "^3.0.0",
63
63
  "locter": "^4.1.1",
64
64
  "orkos": "^1.1.1",
65
65
  "routup": "^6.1.0",
66
+ "validup": "^2.0.1",
66
67
  "zod": "^4.5.4"
67
68
  },
68
69
  "devDependencies": {
70
+ "@types/node": "^26.4.1",
69
71
  "cross-env": "^10.1.0",
70
72
  "vitest": "^4.0.18"
71
73
  },
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-Brhsp_x-.mjs","names":["createHandler","createAssetsHandler","createHandler"],"sources":["../src/config.ts","../src/constants.ts","../src/redirect.ts","../src/payload.ts","../src/path.ts","../src/resolve.ts","../src/render.ts","../src/handler.ts","../src/server.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport {\n AUTH_CONSOLE_SCHEMA,\n CORE_SCHEMA,\n ROOT_SCHEMA,\n SECTION_KEY,\n THEME_SCHEMA,\n} from '@authup/server-config';\nimport {\n buildSchemaDefaults,\n defineSchema,\n mergeSchemaData,\n readSchemaFromEnv,\n resolveSchemaData,\n} from '@authup/server-config-kit';\nimport type { CoreConfig, EnvironmentVariable } from '@authup/server-config';\nimport type { Config, ConfigInput } from './types';\n\n/**\n * The keys this service reads, SELECTED out of the document schema by name.\n *\n * Nothing is declared here: every key of `authup.yml` is declared once in\n * `@authup/server-config`, so this service cannot spell a path, an\n * environment variable, a default or a reader differently from server-core,\n * which reads several of the same keys. Neither package depends on the other;\n * both depend on the declaration.\n *\n * Its own section is spread, so this service reads it in its own vocabulary\n * (`url`, `port`, `host`); every other section stays under the key the\n * document nests it at.\n */\nexport const CONFIG_SCHEMA = defineSchema<ConfigInput, 'publicUrl' | 'internalUrl' | 'db'>({\n ...AUTH_CONSOLE_SCHEMA,\n ...ROOT_SCHEMA,\n [SECTION_KEY.THEME]: THEME_SCHEMA,\n // Two keys of core, never the section. `publicUrl` derives from\n // the core listener address, which is what a console needs; selecting the\n // whole section also selects its INVARIANTS, and `resolveSchemaData` runs\n // every resolver in a registry -- so a console would refuse to start on a\n // short SECRETS_ENCRYPTION_KEY, or on mfaRequired without mfaEnabled, for\n // keys it has no database, key store or MFA to use.\n [SECTION_KEY.CORE]: defineSchema<Pick<CoreConfig, 'host' | 'port'>, never, EnvironmentVariable>({\n host: CORE_SCHEMA.host,\n port: CORE_SCHEMA.port,\n }),\n});\n\n/**\n * Turn the configuration namespace into the service's own shape: fill the\n * defaults, derive the one key that is derived rather than configured, and\n * rename. An empty `url` means the console sits on server-core's own origin\n * under the default segment, which is the single-origin deployment.\n *\n * The defaults are layered SECTION-AWARE ({@link mergeSchemaData}): a spread\n * would let an input carrying one key of a section replace the whole section\n * and take every other key's default with it.\n */\nexport function resolveConfig(\n input: Partial<ConfigInput>,\n) : Config {\n const values = resolveSchemaData<ConfigInput>(\n CONFIG_SCHEMA,\n mergeSchemaData<ConfigInput>(\n CONFIG_SCHEMA,\n buildSchemaDefaults<ConfigInput>(CONFIG_SCHEMA),\n input,\n ),\n ) as ConfigInput;\n\n return {\n url: values.url,\n apiUrl: values.publicUrl,\n // `internalUrl` falls back to `publicUrl`, so an operator who has one\n // address for both sides configures nothing. The composed `authup\n // start` overrides this with its own listen address unless the\n // document named one.\n apiInternalUrl: values.internalUrl,\n port: values.port,\n host: values.host,\n distPath: values.path,\n theme: values.theme,\n };\n}\n\n/**\n * The standalone entry's configuration, read from the environment alone.\n * `authup.yml` reaches this service through the CLI roles, which compose this\n * very registry into the one document loader.\n */\nexport function readConfigFromEnv() : Config {\n // The explicit type argument is load-bearing: inferred from the schema\n // object, a key declared without a default (the derived publicUrl) comes\n // back as unknown.\n const input : Partial<ConfigInput> = readSchemaFromEnv<ConfigInput>(CONFIG_SCHEMA);\n\n return resolveConfig(input);\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\n/**\n * The vite base the auth console bundle is built with. Asset hrefs in the\n * shell are emitted against it, so the service mounts its assets there and\n * rebases them only when the deployment carries a sub-path.\n */\nexport const VITE_BASE = '/console/auth/';\n\n/**\n * The path the service is served under by default, i.e. the path component\n * of `authConsole.url`. It is the same segment server-core mounted\n * the assets at before the split, so a built bundle needs no rebuild.\n */\nexport const BASE_PATH = '/console/auth';\n\n/**\n * The pages the service renders. Each is a route of the console bundle's\n * own router, and each is what a server-core page GET now redirects to.\n */\nexport const PAGES = [\n '/authorize',\n '/register',\n '/activate',\n '/password-forgot',\n '/password-reset',\n '/logout',\n] as const;\n\n/**\n * Where this service mounts the bundle's `assets/` directory. The public\n * asset URL is the service's own public path plus this route, which is what\n * the bundle's fixed vite base is rebased onto.\n */\nexport const ASSETS_PATH = '/assets';\n\nexport const HEALTH_PATH = '/healthy';\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\n/**\n * Open-redirect guard for the `redirect` query parameter carried by the\n * auth workflow pages: only same-origin relative paths pass through, so the\n * value can be safely reflected into an anchor href / forwarded as a\n * post-login destination.\n *\n * Rejects: non-strings, control/whitespace chars (browsers strip some,\n * shifting where the URL authority begins), protocol-relative (`//host`),\n * backslash variants (browsers normalise `\\` → `/`), and any scheme\n * (`javascript:`, `http:`) — both on the raw and once-decoded form so a\n * percent-encoded separator (`%2f`, `%5c`) can't slip past.\n */\nexport function sanitizeRelativeRedirect(input: unknown): string | undefined {\n if (typeof input !== 'string' || input.length === 0) {\n return undefined;\n }\n\n // Raw input: reject control chars AND whitespace (space/tab/newline) — a\n // raw space can shift where the URL authority begins.\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u0020\\u007f]/.test(input)) {\n return undefined;\n }\n\n let decoded: string;\n try {\n decoded = decodeURIComponent(input);\n } catch {\n return undefined;\n }\n\n // Decoded form: reject C0 controls + DEL (CR/LF/NUL — the header-injection\n // vectors a `%0a`/`%0d` would smuggle past the raw check) but NOT space\n // (0x20), so a legitimate `%20` in the path still passes.\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001f\\u007f]/.test(decoded)) {\n return undefined;\n }\n\n for (const candidate of [input, decoded]) {\n if (!candidate.startsWith('/')) {\n return undefined;\n }\n if (candidate.length > 1 && (candidate[1] === '/' || candidate[1] === '\\\\')) {\n return undefined;\n }\n if (candidate.includes('\\\\')) {\n return undefined;\n }\n }\n\n return input;\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { AuthorizeInfo, StatusResponse, StatusResponseFeatures } from '@authup/core-http-kit';\nimport { Client } from '@authup/core-http-kit';\nimport { useRequestQuery } from '@routup/basic/query';\nimport type { IAppEvent } from 'routup';\nimport { sanitizeRelativeRedirect } from './redirect';\nimport type { Config } from './types';\n\n/**\n * The service hydrates ANONYMOUSLY. It holds no credential of its own and\n * asks server-core only for what an unauthenticated visitor may see, which\n * is the whole of what these pages render.\n *\n * It dispatches against `apiInternalUrl`, never the browser-facing `apiUrl`:\n * this is a server-side call, so it must not depend on the public address\n * resolving from inside the deployment. Unlike server-core's own internal\n * client the swap is on `baseURL` rather than at the transport layer,\n * because nothing derived from it reaches a caller -- there is no\n * `redirect_uri` here to be compared byte for byte later.\n */\nexport function createAPIClient(config: Config) : Client {\n return new Client({ baseURL: config.apiInternalUrl });\n}\n\n/**\n * Report an outbound failure to the operator and raise one the visitor may\n * see, which is deliberately not the same error.\n *\n * hapic embeds the resolved request URL in its message, and this service has\n * no error middleware: routup answers with `event.error.message` verbatim, on\n * the public login page. That URL is `apiInternalUrl`, which on a split\n * deployment names a service on the operator's own network, so an API that is\n * merely down would publish its internal address to every anonymous visitor.\n * It was harmless while the value was `publicUrl`, an address the browser\n * already had.\n *\n * The rule and the remedy are server-core's own (`sanitizeError` /\n * `describeError`): the caller gets a message it can act on, the detail goes\n * to the log. `console.error` rather than a logger because a console handler\n * is built from a config alone and is given none -- and because this line is\n * the only trace such a failure leaves, which is how #3550 was diagnosed in\n * the first place.\n */\nfunction failWithoutAddress(error: unknown) : never {\n // eslint-disable-next-line no-console\n console.error('[authup] the auth console could not reach the API:', error);\n\n throw new Error('The identity provider could not be reached.');\n}\n\n/**\n * The feature flags every workflow page gates its form on. A page that\n * cannot reach them must not silently claim a workflow is disabled, so the\n * failure propagates rather than defaulting to false.\n *\n * Read ONCE and held for the reader's lifetime, the same shape render.ts\n * keeps for the dist template, manifest and render entry: `GET /` reports\n * the version and the boot-config feature flags, none of which changes\n * without a restart, so a TTL would be a number with nothing behind it.\n * Without it every workflow page render is an API call, and under the\n * composed `authup start` those all arrive over loopback on one shared\n * rate-limit key. Only a SUCCESS is held, so an API that was down at the\n * first render is retried rather than remembered as broken.\n *\n * The cache is instance-scoped rather than module-scoped (098 C4): the\n * answer depends on which API the client was built against, so two handlers\n * in one process must not share one, and a module slot would additionally\n * leak between test cases.\n */\nexport function createFeaturesReader(client: Client) : () => Promise<StatusResponseFeatures> {\n let cached : StatusResponseFeatures | undefined;\n let pending : Promise<StatusResponseFeatures> | undefined;\n\n return () => {\n if (cached) {\n return Promise.resolve(cached);\n }\n\n if (pending) {\n return pending;\n }\n\n pending = client.status.get()\n .then((status: StatusResponse) => {\n cached = status.features;\n\n return cached;\n })\n .catch((e) => failWithoutAddress(e));\n\n const clear = () => {\n pending = undefined;\n };\n pending.then(clear, clear);\n\n return pending;\n };\n}\n\n/**\n * The `/authorize` render input, resolved by server-core (plan 101 D2-1).\n * The browser's query is forwarded verbatim: the answer is derived from all\n * of it, the `provider` hint and the closed `error` marker set included.\n */\nexport async function readAuthorizeInfo(client: Client, event: IAppEvent) : Promise<AuthorizeInfo> {\n const { search } = new URL(event.request.url);\n\n try {\n return await client.authorize.getInfo(search);\n } catch (e) {\n return failWithoutAddress(e);\n }\n}\n\nexport type WorkflowPageOptions = {\n // Whether the page consumes the `realmId` (legacy `realm_id`) / `token`\n // query params (e.g. prefill from an email deep link). Off by default so\n // a page never reflects a param it doesn't use.\n realmAware?: boolean,\n tokenAware?: boolean,\n};\n\n/**\n * The payload the four workflow pages render from: the feature flags plus\n * the handful of query params each one declares. Unchanged from the\n * assembly server-core did before the split, including the open-redirect\n * guard on `redirect`.\n */\nexport function buildWorkflowPageData(\n event: IAppEvent,\n features: StatusResponseFeatures,\n options: WorkflowPageOptions = {},\n) : Record<string, any> {\n const query = useRequestQuery(event);\n\n const data: Record<string, any> = {\n features,\n redirect: sanitizeRelativeRedirect(query.redirect),\n };\n\n if (options.realmAware) {\n const realmId = typeof query.realmId === 'string' ? query.realmId : query.realm_id;\n data.realmId = typeof realmId === 'string' ? realmId : undefined;\n }\n\n if (options.tokenAware) {\n data.token = typeof query.token === 'string' ? query.token : undefined;\n }\n\n return data;\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport const CODE_PATH = path.dirname(fileURLToPath(import.meta.url));\nexport const PACKAGE_PATH = path.join(CODE_PATH, '..');\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { locateUpSync } from 'locter';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { PACKAGE_PATH } from './path';\n\nlet cachedPackagePath: string | undefined;\nlet cachedDistPath: string | undefined;\nlet overridePackagePath: string | undefined;\n\n/**\n * Point the resolution at a substituted package (config `distPath`)\n * instead of the node_modules walk. Called once at boot.\n */\nexport function setPackagePath(value: string | undefined) : void {\n overridePackagePath = value || undefined;\n cachedPackagePath = undefined;\n cachedDistPath = undefined;\n}\n\n/**\n * Locate the auth console package. The SSR auth workflow UI ships as\n * `@authup/client-auth-console` (a dependency of THIS service), so the\n * node_modules ancestor walk from this package's own root (locter's\n * `locateUp`) finds it for the workspace (symlink onto\n * apps/client-auth-console) and for a published install alike. Only a\n * positive result is cached.\n *\n * The `cwd` anchor is behavioural, not incidental: it decides which\n * node_modules tree is walked, so it stays pinned to this package rather\n * than to the process cwd.\n */\nexport function resolvePackagePath() : string | undefined {\n if (cachedPackagePath) {\n return cachedPackagePath;\n }\n\n if (overridePackagePath) {\n cachedPackagePath = overridePackagePath;\n\n return cachedPackagePath;\n }\n\n const manifest = locateUpSync(\n 'node_modules/@authup/client-auth-console/package.json',\n { cwd: PACKAGE_PATH },\n );\n if (manifest) {\n cachedPackagePath = manifest.directory;\n }\n\n return cachedPackagePath;\n}\n\n/**\n * Locate the BUILT auth console bundle (`dist/client` template + assets,\n * `dist/server/server.js` render entry). Only a positive result is cached,\n * so a dev building the package after boot is picked up on the next\n * request.\n */\nexport function resolveDistPath() : string | undefined {\n if (cachedDistPath) {\n return cachedDistPath;\n }\n\n const packagePath = resolvePackagePath();\n if (packagePath) {\n const distPath = path.join(packagePath, 'dist');\n\n if (fs.existsSync(path.join(distPath, 'client', 'index.html'))) {\n cachedDistPath = distPath;\n }\n }\n\n return cachedDistPath;\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { HydrationPayload, RenderFunction } from '@authup/client-auth-console';\nimport { InternalError } from '@authup/errors';\nimport { getURLBasePath } from '@authup/kit';\nimport {\n applyTheme,\n applyUIPageHeaders,\n readUIClientPreferences,\n rebaseAssetURLs,\n replaceTemplateMarker,\n stampHtmlAttributes,\n} from '@authup/server-console-kit';\nimport { read } from 'locter';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { VITE_BASE } from './constants';\nimport { resolveDistPath } from './resolve';\nimport type { RenderPage } from './types';\n\n// Process-lifetime caches for the immutable production SSR assets. The dist\n// template, manifest and server bundle don't change after boot, so read them\n// once instead of per request (the auth/login routes are hot paths).\nlet cachedHtml: string | undefined;\nlet cachedManifest: Record<string, any> | undefined;\nlet cachedRender: RenderFunction | undefined;\n\nexport const renderPage : RenderPage = async (event, config, ctx) => {\n const distPath = resolveDistPath();\n if (!distPath) {\n throw new InternalError(\n 'The auth console bundle (@authup/client-auth-console) is not built or installed.',\n );\n }\n\n const html = (cachedHtml ??= await fs.promises.readFile(\n path.join(distPath, 'client', 'index.html'),\n 'utf-8',\n ));\n const manifest = (cachedManifest ??= JSON.parse(await fs.promises.readFile(\n path.join(distPath, 'client', '.vite', 'ssr-manifest.json'),\n 'utf-8',\n )));\n const render = (cachedRender ??= (await read(\n path.join(distPath, 'server', 'server.js'),\n )).render as RenderFunction);\n\n // The client app hydrates locale + color-mode from the payload, so it\n // adopts the same values the HTML shell is stamped with below.\n const preferences = readUIClientPreferences(event);\n\n // `baseURL` is server-core's public URL: the console derives its HTTP\n // client and its cookie path from it. `basePath` is where THIS service\n // is served, which is what the router and every inter-page href need.\n // Before the split the two were the same value; they are not any more,\n // so the console reads them separately.\n const basePath = getURLBasePath(config.url);\n\n const payload : HydrationPayload = {\n config: {\n baseURL: config.apiUrl,\n basePath,\n colorMode: preferences.colorMode,\n locale: preferences.locale,\n },\n data: ctx.data,\n };\n\n const [appHtml, preloadLinks] = await render({\n url: ctx.url,\n manifest,\n payload,\n });\n\n let body = replaceTemplateMarker(html, '<!--preload-links-->', preloadLinks);\n body = replaceTemplateMarker(body, '<!--app-html-->', appHtml);\n\n body = stampHtmlAttributes(body, preferences);\n\n // This service mounts the bundle's assets at its own /assets, so the\n // fixed vite base in every href is replaced by this service's public\n // path. The vite base was decided when the bundle was built and says\n // nothing about where the service is published.\n body = rebaseAssetURLs(body, VITE_BASE, `${basePath}/`);\n\n // The theme's own asset urls are built from the same base, and the\n // service mounts them under it, so a themed deployment needs no rule of\n // its own at the reverse proxy beyond the one that reaches this service.\n body = await applyTheme(body, ctx.theme, basePath);\n\n applyUIPageHeaders(event);\n\n return body;\n};\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { IThemeProvider } from '@authup/server-console-kit';\nimport {\n THEME_ASSET_MOUNT_PATH,\n createThemeAssetsHandler,\n createThemeProvider,\n} from '@authup/server-console-kit';\nimport { createHandler as createAssetsHandler } from '@routup/assets';\nimport { basic } from '@routup/basic';\nimport path from 'node:path';\nimport type { IApp } from 'routup';\nimport { App, defineCoreHandler } from 'routup';\nimport { ASSETS_PATH, HEALTH_PATH } from './constants';\nimport {\n buildWorkflowPageData,\n createAPIClient,\n createFeaturesReader,\n readAuthorizeInfo,\n} from './payload';\nimport { renderPage } from './render';\nimport { resolveDistPath, setPackagePath } from './resolve';\nimport type { Config, RenderPage } from './types';\n\nconst WORKFLOW_PAGES : {\n url: string,\n realmAware?: boolean,\n tokenAware?: boolean\n}[] = [\n { url: '/register', realmAware: true },\n { url: '/activate', tokenAware: true },\n { url: '/password-forgot', realmAware: true },\n {\n url: '/password-reset',\n realmAware: true,\n tokenAware: true,\n },\n];\n\n/**\n * The service as a mountable routup handler, so the CLI can compose it onto\n * server-core's own listener for the single-container deployment while a\n * split deployment runs it behind its own server.\n *\n * The paths are prefix-free. authup assumes a prefix-stripping reverse\n * proxy, so a service published at `<origin>/console/auth` receives\n * `/authorize`, exactly as server-core received it before the split.\n */\nexport async function createHandler(\n config: Config,\n themeProvider?: IThemeProvider,\n render: RenderPage = renderPage,\n) : Promise<IApp> {\n setPackagePath(config.distPath);\n\n const app = new App();\n const client = createAPIClient(config);\n const readFeatures = createFeaturesReader(client);\n\n // The shell is stamped from the vc-locale / vc-color-mode cookies, and\n // without the plugin every cookie read answers undefined, which is a\n // silent flash of the wrong color mode on every full load. server-core\n // registers it globally, so a mounted handler inherits one; a standalone\n // service has nothing above it.\n app.use(basic({ cookie: true, query: true }));\n\n // A missing directory disables theming entirely: no provider is created\n // and the rendered pages are byte-identical to the un-themed ones, so the\n // default configuration pays nothing. An invalid manifest throws here and\n // fails the boot rather than surfacing per request.\n // Injected by the theme module when this runs inside the console\n // application; built here for a caller holding only a config.\n const theme = themeProvider ?? await createThemeProvider(config);\n\n app.use(defineCoreHandler({\n method: 'get',\n path: HEALTH_PATH,\n fn: () => ({ status: 'ok' }),\n }));\n\n if (theme) {\n app.use(THEME_ASSET_MOUNT_PATH, createThemeAssetsHandler(theme));\n }\n\n app.use(defineCoreHandler({\n method: 'get',\n path: '/authorize',\n fn: async (event) => render(event, config, {\n url: '/authorize',\n data: await readAuthorizeInfo(client, event),\n theme,\n }),\n }));\n\n for (const page of WORKFLOW_PAGES) {\n app.use(defineCoreHandler({\n method: 'get',\n path: page.url,\n fn: async (event) => {\n const features = await readFeatures();\n\n return render(event, config, {\n url: page.url,\n data: buildWorkflowPageData(event, features, page),\n theme,\n });\n },\n }));\n }\n\n // The logout page needs nothing resolved up front: it drives the\n // end-session call itself and renders from the answer, so the render\n // stays a pure shell and no request reaches server-core here.\n app.use(defineCoreHandler({\n method: 'get',\n path: '/logout',\n fn: async (event) => render(event, config, {\n url: '/logout',\n data: {},\n theme,\n }),\n }));\n\n // Only the assets directory is mounted, never dist/client itself: the\n // template and the ssr manifest are inputs of the render, not files to\n // serve. A missing bundle only disables the mount; the page routes\n // report the actionable error.\n const distPath = resolveDistPath();\n if (distPath) {\n app.use(ASSETS_PATH, createAssetsHandler(\n path.posix.join(distPath, 'client', 'assets'),\n {\n fallthrough: false,\n scan: false,\n },\n ));\n }\n\n return app;\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { App } from 'routup';\nimport { createHandler } from './handler';\nimport type { Config } from './types';\n\n/**\n * The standalone service: the same handler the CLI mounts in process,\n * behind a listener of its own.\n */\nexport async function createServer(config: Config) : Promise<App> {\n const app = new App();\n\n app.use(await createHandler(config));\n\n return app;\n}\n"],"mappings":"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"}