@anchrd/intel-api 0.16.0 → 0.16.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.
@@ -162,9 +162,20 @@ export function createOpenId(deps) {
162
162
  // one with a 404, which surfaced as a bare 500 on /auth/connect (#93). The fallback repeats the
163
163
  // operation once with the OAuth document; when both fail, the second error is thrown because it
164
164
  // belongs to the attempt that got further for the issuer that needed the fallback at all.
165
- async function withAlgorithmFallback(run) {
165
+ async function withAlgorithmFallback(run, usable = () => true) {
166
166
  try {
167
- return await run();
167
+ const first = await run();
168
+ // ⚠️ A THROW is not the only way the OIDC document can be the wrong one (#425). Cloudflare
169
+ // Access answers `/.well-known/openid-configuration` with **200** and a two-field stub —
170
+ // `issuer` and `jwks_uri`, nothing else — while the endpoints live only under RFC 8414's
171
+ // `oauth-authorization-server`. Discovery therefore SUCCEEDS, the fallback never runs, and
172
+ // the missing endpoint surfaces minutes later inside `buildAuthorizationUrl` as
173
+ // `authorization server metadata does not contain a valid "as.authorization_endpoint"` — a
174
+ // 500 on `/auth/connect` with nothing in it pointing at discovery.
175
+ //
176
+ // So the question is not "did it fail" but "is what came back usable". An answer that parses
177
+ // and carries nothing is the same situation as a 404, and it gets the same second attempt.
178
+ return usable(first) ? first : await run("oauth2");
168
179
  }
169
180
  catch {
170
181
  return await run("oauth2");
@@ -174,7 +185,14 @@ export function createOpenId(deps) {
174
185
  const key = `${issuer}#${clientId}`;
175
186
  let configuration = configurations.get(key);
176
187
  if (!configuration) {
177
- configuration = withAlgorithmFallback((algorithm) => client.discovery(new URL(issuer), clientId, { token_endpoint_auth_method: "none" }, client.None(), { ...options(issuer), ...(algorithm ? { algorithm } : {}) }));
188
+ configuration = withAlgorithmFallback((algorithm) => client.discovery(new URL(issuer), clientId, { token_endpoint_auth_method: "none" }, client.None(), { ...options(issuer), ...(algorithm ? { algorithm } : {}) }),
189
+ // The two endpoints every flow here needs. Asking for exactly them rather than for a long
190
+ // list keeps the check honest: a server that publishes these two is usable, and one that
191
+ // publishes neither is the stub case above, whatever else it carries.
192
+ (resolved) => {
193
+ const metadata = resolved.serverMetadata();
194
+ return Boolean(metadata.authorization_endpoint && metadata.token_endpoint);
195
+ });
178
196
  configurations.set(key, configuration);
179
197
  void configuration.catch(() => configurations.delete(key));
180
198
  }
@@ -60,7 +60,16 @@ export function createIntel(deps) {
60
60
  if (error instanceof IntelError) {
61
61
  return context.redirect(`/tools?connectError=${encodeURIComponent(error.code)}`);
62
62
  }
63
- throw error;
63
+ // ⚠️ Everything else used to be rethrown, and the outer handler turned it into a bare 500
64
+ // with a generic body (#425). For an API that is the right answer; for a route a BROWSER is
65
+ // sent to it is a dead end — the reader gets JSON instead of a screen, and the Tools page
66
+ // never learns that the attempt happened at all, so it says "no access" about a sign-in
67
+ // that crashed.
68
+ //
69
+ // The trace still goes to the log, which is the operator's channel; what changes is that
70
+ // the reader is returned to a screen that can say "this broke" instead of "you may not".
71
+ reportUnexpectedError(error);
72
+ return context.redirect("/tools?connectError=portal_sign_in_failed");
64
73
  }
65
74
  });
66
75
  app.get("/auth/callback", async (context) => await browserAuth.callback(new URL(context.req.url), context.req.raw.headers));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anchrd/intel-api",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {