@stigmer/server 3.14.1 → 3.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/dist/boot/compose.d.ts +8 -1
  2. package/dist/boot/compose.d.ts.map +1 -1
  3. package/dist/boot/compose.js +121 -14
  4. package/dist/boot/compose.js.map +1 -1
  5. package/dist/boot/config.d.ts +14 -0
  6. package/dist/boot/config.d.ts.map +1 -1
  7. package/dist/boot/config.js +1 -0
  8. package/dist/boot/config.js.map +1 -1
  9. package/dist/domain/apikey/verifier.d.ts +8 -1
  10. package/dist/domain/apikey/verifier.d.ts.map +1 -1
  11. package/dist/domain/apikey/verifier.js +14 -2
  12. package/dist/domain/apikey/verifier.js.map +1 -1
  13. package/dist/domain/identityaccount/constants.d.ts +66 -0
  14. package/dist/domain/identityaccount/constants.d.ts.map +1 -0
  15. package/dist/domain/identityaccount/constants.js +153 -0
  16. package/dist/domain/identityaccount/constants.js.map +1 -0
  17. package/dist/domain/identityaccount/controller.d.ts +34 -0
  18. package/dist/domain/identityaccount/controller.d.ts.map +1 -0
  19. package/dist/domain/identityaccount/controller.js +342 -0
  20. package/dist/domain/identityaccount/controller.js.map +1 -0
  21. package/dist/domain/identityaccount/operator.d.ts +14 -0
  22. package/dist/domain/identityaccount/operator.d.ts.map +1 -0
  23. package/dist/domain/identityaccount/operator.js +67 -0
  24. package/dist/domain/identityaccount/operator.js.map +1 -0
  25. package/dist/domain/identityaccount/provisioning.d.ts +52 -0
  26. package/dist/domain/identityaccount/provisioning.d.ts.map +1 -0
  27. package/dist/domain/identityaccount/provisioning.js +114 -0
  28. package/dist/domain/identityaccount/provisioning.js.map +1 -0
  29. package/dist/domain/identityaccount/resolve.d.ts +36 -0
  30. package/dist/domain/identityaccount/resolve.d.ts.map +1 -0
  31. package/dist/domain/identityaccount/resolve.js +12 -0
  32. package/dist/domain/identityaccount/resolve.js.map +1 -0
  33. package/dist/domain/identityaccount/resource-store.d.ts +4 -0
  34. package/dist/domain/identityaccount/resource-store.d.ts.map +1 -0
  35. package/dist/domain/identityaccount/resource-store.js +131 -0
  36. package/dist/domain/identityaccount/resource-store.js.map +1 -0
  37. package/dist/domain/identityaccount/steps.d.ts +79 -0
  38. package/dist/domain/identityaccount/steps.d.ts.map +1 -0
  39. package/dist/domain/identityaccount/steps.js +291 -0
  40. package/dist/domain/identityaccount/steps.js.map +1 -0
  41. package/dist/domain/identityaccount/store-contract.d.ts +22 -0
  42. package/dist/domain/identityaccount/store-contract.d.ts.map +1 -0
  43. package/dist/domain/identityaccount/store-contract.js +307 -0
  44. package/dist/domain/identityaccount/store-contract.js.map +1 -0
  45. package/dist/domain/identityaccount/store.d.ts +66 -0
  46. package/dist/domain/identityaccount/store.d.ts.map +1 -0
  47. package/dist/domain/identityaccount/store.js +13 -0
  48. package/dist/domain/identityaccount/store.js.map +1 -0
  49. package/dist/extensions/drivers.d.ts +26 -0
  50. package/dist/extensions/drivers.d.ts.map +1 -1
  51. package/dist/extensions/gate-slots.d.ts +16 -1
  52. package/dist/extensions/gate-slots.d.ts.map +1 -1
  53. package/dist/extensions/gate-slots.js +1 -0
  54. package/dist/extensions/gate-slots.js.map +1 -1
  55. package/dist/extensions/identity-federation.d.ts +36 -0
  56. package/dist/extensions/identity-federation.d.ts.map +1 -0
  57. package/dist/extensions/identity-federation.js +2 -0
  58. package/dist/extensions/identity-federation.js.map +1 -0
  59. package/dist/extensions/registry.d.ts +18 -1
  60. package/dist/extensions/registry.d.ts.map +1 -1
  61. package/dist/extensions/registry.js +20 -0
  62. package/dist/extensions/registry.js.map +1 -1
  63. package/dist/identity/oidc-discovery.d.ts +36 -0
  64. package/dist/identity/oidc-discovery.d.ts.map +1 -0
  65. package/dist/identity/oidc-discovery.js +60 -0
  66. package/dist/identity/oidc-discovery.js.map +1 -0
  67. package/dist/identity/oidc-userinfo.d.ts +30 -0
  68. package/dist/identity/oidc-userinfo.d.ts.map +1 -0
  69. package/dist/identity/oidc-userinfo.js +28 -0
  70. package/dist/identity/oidc-userinfo.js.map +1 -0
  71. package/dist/identity/oidc-verifier.d.ts +6 -0
  72. package/dist/identity/oidc-verifier.d.ts.map +1 -1
  73. package/dist/identity/oidc-verifier.js +35 -34
  74. package/dist/identity/oidc-verifier.js.map +1 -1
  75. package/dist/index.d.ts +7 -0
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +3 -0
  78. package/dist/index.js.map +1 -1
  79. package/dist/pipeline/interceptors/auth.d.ts +10 -0
  80. package/dist/pipeline/interceptors/auth.d.ts.map +1 -1
  81. package/dist/pipeline/interceptors/auth.js +9 -1
  82. package/dist/pipeline/interceptors/auth.js.map +1 -1
  83. package/dist/transport/console/handler.d.ts +22 -5
  84. package/dist/transport/console/handler.d.ts.map +1 -1
  85. package/dist/transport/console/handler.js +53 -24
  86. package/dist/transport/console/handler.js.map +1 -1
  87. package/package.json +4 -4
  88. package/src/boot/__tests__/auth-enabled-composition.test.ts +274 -4
  89. package/src/boot/__tests__/config.test.ts +41 -3
  90. package/src/boot/compose.ts +148 -15
  91. package/src/boot/config.ts +15 -0
  92. package/src/domain/apikey/__tests__/verifier.test.ts +146 -22
  93. package/src/domain/apikey/verifier.ts +24 -2
  94. package/src/domain/identityaccount/__tests__/constants.test.ts +155 -0
  95. package/src/domain/identityaccount/__tests__/identityaccount.test.ts +484 -0
  96. package/src/domain/identityaccount/__tests__/operator.test.ts +180 -0
  97. package/src/domain/identityaccount/__tests__/provisioning.test.ts +281 -0
  98. package/src/domain/identityaccount/__tests__/resolve.test.ts +114 -0
  99. package/src/domain/identityaccount/__tests__/resource-store.test.ts +216 -0
  100. package/src/domain/identityaccount/__tests__/support.ts +58 -0
  101. package/src/domain/identityaccount/constants.ts +178 -0
  102. package/src/domain/identityaccount/controller.ts +667 -0
  103. package/src/domain/identityaccount/operator.ts +94 -0
  104. package/src/domain/identityaccount/provisioning.ts +182 -0
  105. package/src/domain/identityaccount/resolve.ts +52 -0
  106. package/src/domain/identityaccount/resource-store.ts +157 -0
  107. package/src/domain/identityaccount/steps.ts +377 -0
  108. package/src/domain/identityaccount/store-contract.ts +471 -0
  109. package/src/domain/identityaccount/store.ts +70 -0
  110. package/src/extensions/__tests__/identity-account-composed.test.ts +803 -0
  111. package/src/extensions/__tests__/identity-account-points.test.ts +129 -0
  112. package/src/extensions/__tests__/registry.test.ts +4 -0
  113. package/src/extensions/__tests__/tier-truthfulness.test.ts +299 -0
  114. package/src/extensions/drivers.ts +26 -0
  115. package/src/extensions/gate-slots.ts +16 -0
  116. package/src/extensions/identity-federation.ts +58 -0
  117. package/src/extensions/registry.ts +44 -1
  118. package/src/identity/__tests__/oidc-discovery.test.ts +122 -0
  119. package/src/identity/__tests__/oidc-userinfo.test.ts +103 -0
  120. package/src/identity/__tests__/oidc-verifier.test.ts +173 -3
  121. package/src/identity/oidc-discovery.ts +84 -0
  122. package/src/identity/oidc-userinfo.ts +64 -0
  123. package/src/identity/oidc-verifier.ts +42 -45
  124. package/src/index.ts +19 -0
  125. package/src/pipeline/interceptors/auth.ts +13 -1
  126. package/src/pipeline/steps/__tests__/authorize-annotation-completeness.test.ts +184 -0
  127. package/src/transport/console/__tests__/handler.test.ts +93 -23
  128. package/src/transport/console/__tests__/resolver.test.ts +6 -0
  129. package/src/transport/console/handler.ts +84 -30
@@ -6,6 +6,13 @@
6
6
  * nginx-equivalence-gated); this module owns the HTTP half: the lane
7
7
  * guard, header policy, config synthesis, and file streaming.
8
8
  *
9
+ * /config.json is also how the served console learns to sign in
10
+ * (20260913.02 sp.console-login; stigmer#924): the composition root hands
11
+ * the lane the server's authentication posture (ConsoleSignInPosture) and
12
+ * the lane publishes it in the console's own vocabulary — the OIDC
13
+ * issuer, audience and the console's PKCE client id under the posture,
14
+ * `disabled` under trusted local.
15
+ *
9
16
  * Lane guard (consoleLaneEligible): the lane claims GET/HEAD only — RPC
10
17
  * traffic is POST on service-qualified paths, and OPTIONS preflights keep
11
18
  * flowing to the RPC lane's CORS handling — and it never claims:
@@ -74,18 +81,39 @@ function isServiceShapedPath(pathname: string): boolean {
74
81
  return segments.length === 2 && (segments[0] ?? "").includes(".");
75
82
  }
76
83
 
84
+ /**
85
+ * How the console this lane serves signs in — the server's authentication
86
+ * posture as the console needs to hear it (20260913.02 sp.console-login).
87
+ * A modeled state, never a nullable: "no issuer configured" and "forgot to
88
+ * wire the posture" must stay distinguishable at the call site.
89
+ *
90
+ * Under `oidc`, `consoleClientId` is the PUBLIC client the operator
91
+ * registered for the browser's PKCE flow, or "" when they have not
92
+ * (Q-CL-1): the lane publishes that truth as-is and the console refuses
93
+ * with copy that names the knob (Q-CL-2), while the composition root has
94
+ * already WARNed at wiring time. Emitting `disabled` instead would send
95
+ * the console into every RPC tokenless, to fail with a worse message.
96
+ */
97
+ export type ConsoleSignInPosture =
98
+ | { readonly posture: "trusted-local" }
99
+ | {
100
+ readonly posture: "oidc";
101
+ readonly issuer: string;
102
+ readonly audience: string;
103
+ readonly consoleClientId: string;
104
+ };
105
+
77
106
  export interface ConsoleLaneOptions {
78
107
  readonly assets: ConsoleAssets;
79
- /**
80
- * Fallback port for the synthesized apiUrl when a request carries no
81
- * Host/:authority (technically possible on HTTP/1.0-style clients).
82
- */
83
- readonly grpcPort: number;
108
+ readonly signIn: ConsoleSignInPosture;
84
109
  readonly logger: Logger;
85
110
  }
86
111
 
87
112
  export function createConsoleLane(options: ConsoleLaneOptions): LaneHandler {
88
- const { assets, grpcPort, logger } = options;
113
+ const { assets, signIn, logger } = options;
114
+ // The console's runtime config is a pure function of the posture: one
115
+ // body, built once, served on every request.
116
+ const configJsonBody = JSON.stringify(consoleRuntimeConfig(signIn));
89
117
 
90
118
  return (request: LaneRequest, response: LaneResponse): void => {
91
119
  const url = request.url ?? "";
@@ -94,7 +122,7 @@ export function createConsoleLane(options: ConsoleLaneOptions): LaneHandler {
94
122
  const query = queryStart === -1 ? "" : url.slice(queryStart);
95
123
 
96
124
  if (rawPath === CONFIG_JSON_PATH) {
97
- serveConfigJson(request, response, grpcPort);
125
+ serveConfigJson(request, response, configJsonBody);
98
126
  return;
99
127
  }
100
128
 
@@ -134,34 +162,60 @@ export function createConsoleLane(options: ConsoleLaneOptions): LaneHandler {
134
162
 
135
163
  /**
136
164
  * The runtime config the cloud container's entrypoint.sh generates from
137
- * env — synthesized here from the server's own knowledge instead (DD-012:
138
- * the nginx entrypoint script is not ported). apiUrl derives from the
139
- * request's own Host so the answer is correct wherever the browser
140
- * reached us from (localhost, a LAN address, a self-host name) — the
141
- * console cannot express "same origin" itself (its config loader maps
142
- * empty fields to a localhost default). TLS-terminating proxies
143
- * (x-forwarded-proto) are out of scope until a tier serves TLS.
165
+ * env, synthesized here from the server's own knowledge instead (DD-012:
166
+ * the nginx entrypoint script is not ported). The field names are the
167
+ * console's loader's (client-apps/web/src/config/runtime-config.ts).
168
+ *
169
+ * `apiUrl` is the empty string, which the console reads as "my own
170
+ * origin" — the rule its `appUrl` already follows (20260913.02 Q-CL-3).
171
+ * This lane and the RPC lane share one port, so the console can never
172
+ * need a different origin, and it makes the answer right behind a
173
+ * TLS-terminating proxy without trusting x-forwarded-proto: the earlier
174
+ * Host-derived `http://<host>` sent an https-loaded console to an http
175
+ * API and died on mixed content.
176
+ *
177
+ * Every value here is public metadata: an OIDC issuer URL, an audience
178
+ * string and a PKCE client id are what a browser is handed by design.
144
179
  */
180
+ function consoleRuntimeConfig(signIn: ConsoleSignInPosture): {
181
+ readonly apiUrl: "";
182
+ readonly appUrl: "";
183
+ readonly authMode: "disabled" | "oidc";
184
+ readonly oidcIssuer: string;
185
+ readonly oidcClientId: string;
186
+ readonly oidcAudience: string;
187
+ } {
188
+ switch (signIn.posture) {
189
+ case "trusted-local":
190
+ return {
191
+ apiUrl: "",
192
+ appUrl: "",
193
+ authMode: "disabled",
194
+ oidcIssuer: "",
195
+ oidcClientId: "",
196
+ oidcAudience: "",
197
+ };
198
+ case "oidc":
199
+ return {
200
+ apiUrl: "",
201
+ appUrl: "",
202
+ authMode: "oidc",
203
+ oidcIssuer: signIn.issuer,
204
+ oidcClientId: signIn.consoleClientId,
205
+ oidcAudience: signIn.audience,
206
+ };
207
+ default: {
208
+ const exhaustive: never = signIn;
209
+ throw new Error(`unhandled sign-in posture ${String(exhaustive)}`);
210
+ }
211
+ }
212
+ }
213
+
145
214
  function serveConfigJson(
146
215
  request: LaneRequest,
147
216
  response: LaneResponse,
148
- grpcPort: number,
217
+ body: string,
149
218
  ): void {
150
- // HTTP/1.1 carries Host; Node maps HTTP/2's :authority into the same
151
- // headers view (and Http2ServerRequest.authority falls back to it).
152
- const host = request.headers.host ?? request.headers[":authority"];
153
- const apiUrl =
154
- typeof host === "string" && host !== ""
155
- ? `http://${host}`
156
- : `http://localhost:${grpcPort}`;
157
- const body = JSON.stringify({
158
- apiUrl,
159
- appUrl: "",
160
- authMode: "disabled",
161
- oidcIssuer: "",
162
- oidcClientId: "",
163
- oidcAudience: "",
164
- });
165
219
  response.statusCode = 200;
166
220
  response.setHeader("Content-Type", "application/json");
167
221
  response.setHeader("Cache-Control", CONFIG_JSON_CACHE_CONTROL);