@segmentstream-cloud/app 0.16.1 → 0.16.2

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.
@@ -7,6 +7,7 @@ import { type SegmentStreamProxy } from "./proxy.js";
7
7
  export declare function withUnframeablePages(proxy: SegmentStreamProxy): SegmentStreamProxy;
8
8
  /**
9
9
  * Whether a form post came from the app's own pages. A decision must never be made from another
10
- * site, which could otherwise approve an agent in the member's name.
10
+ * site, which could otherwise approve an agent in the member's name. Fetch Metadata describes
11
+ * the browser's public request even when a trusted proxy rewrites the server's request URL.
11
12
  */
12
13
  export declare function isSameOrigin(request: Request): boolean;
@@ -1,3 +1,4 @@
1
+ import { isLoopbackHostname } from "./cookies.js";
1
2
  import { AGENT_CONNECT_PAGE, CONSENT_PAGE } from "./pages.js";
2
3
  import { matchesPathPrefix } from "./proxy.js";
3
4
  /** Pages where a signed-in member lets an agent act for them. */
@@ -26,12 +27,48 @@ export function withUnframeablePages(proxy) {
26
27
  }
27
28
  /**
28
29
  * Whether a form post came from the app's own pages. A decision must never be made from another
29
- * site, which could otherwise approve an agent in the member's name.
30
+ * site, which could otherwise approve an agent in the member's name. Fetch Metadata describes
31
+ * the browser's public request even when a trusted proxy rewrites the server's request URL.
30
32
  */
31
33
  export function isSameOrigin(request) {
32
- const origin = request.headers.get("origin");
33
- if (origin !== null) {
34
- return origin === new URL(request.url).origin;
34
+ const site = request.headers.get("sec-fetch-site");
35
+ if (site !== null) {
36
+ return site === "same-origin";
35
37
  }
36
- return request.headers.get("sec-fetch-site") === "same-origin";
38
+ const supplied = request.headers.get("origin");
39
+ const origin = supplied === null ? undefined : httpOrigin(supplied);
40
+ if (origin === undefined) {
41
+ return false;
42
+ }
43
+ if (origin === new URL(request.url).origin) {
44
+ return true;
45
+ }
46
+ const host = (request.headers.get("x-forwarded-host") ?? request.headers.get("host"))
47
+ ?.split(",")[0]
48
+ ?.trim();
49
+ if (!host) {
50
+ return false;
51
+ }
52
+ const forwardedProto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
53
+ const protocol = forwardedProto === "http" || forwardedProto === "https"
54
+ ? forwardedProto
55
+ : isLoopbackHostname(host.replace(/:\d+$/, ""))
56
+ ? "http"
57
+ : "https";
58
+ return origin === httpOrigin(`${protocol}://${host}`);
59
+ }
60
+ /** An Origin names only an HTTP(S) authority, never credentials, a path or query. */
61
+ function httpOrigin(value) {
62
+ if (!URL.canParse(value)) {
63
+ return undefined;
64
+ }
65
+ const url = new URL(value);
66
+ return (url.protocol === "http:" || url.protocol === "https:") &&
67
+ url.username === "" &&
68
+ url.password === "" &&
69
+ url.pathname === "/" &&
70
+ url.search === "" &&
71
+ url.hash === ""
72
+ ? url.origin
73
+ : undefined;
37
74
  }
package/dist/handlers.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { authFlowCookieName, clearCookie, clearSessionCookies, cookiePolicyFor, decodeAuthFlowState, pendingAuthFlowCookies, safeReturnTo, setSessionCookies, } from "./cookies.js";
3
+ import { isSameOrigin } from "./decision-pages.js";
3
4
  import { constantTimeEqual } from "./pkce.js";
4
5
  import { CALLBACK_PATH, SIGNED_OUT_PATH, SIGN_IN_PATH, SDK_ROUTE_PREFIX, signInRedirect, } from "./sign-in.js";
5
6
  export function createHandlers(context) {
@@ -92,8 +93,7 @@ function returnToApp(request, policy, flowCookie, returnTo) {
92
93
  return response;
93
94
  }
94
95
  async function handleSignOut(context, request) {
95
- const origin = request.headers.get("origin");
96
- if (origin !== null && origin !== request.nextUrl.origin) {
96
+ if (!isSameOrigin(request)) {
97
97
  return htmlResponse(403, "Sign-out refused", "Sign out from this app's own pages.", "Return to the app");
98
98
  }
99
99
  const policy = cookiePolicyFor(request.nextUrl, context.config.workspaceId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@segmentstream-cloud/app",
3
- "version": "0.16.1",
3
+ "version": "0.16.2",
4
4
  "description": "Sign-in, users, permissions, the ontology client and warehouse data access for SegmentStream workspace apps built with Next.js.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "SegmentStream",