@segmentstream-cloud/app 0.16.1 → 0.16.3
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/dist/decision-pages.d.ts +2 -1
- package/dist/decision-pages.js +42 -5
- package/dist/handlers.js +2 -2
- package/dist/neon.js +7 -8
- package/dist/proxy.d.ts +2 -0
- package/dist/proxy.js +24 -0
- package/package.json +1 -1
package/dist/decision-pages.d.ts
CHANGED
|
@@ -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;
|
package/dist/decision-pages.js
CHANGED
|
@@ -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
|
|
33
|
-
if (
|
|
34
|
-
return
|
|
34
|
+
const site = request.headers.get("sec-fetch-site");
|
|
35
|
+
if (site !== null) {
|
|
36
|
+
return site === "same-origin";
|
|
35
37
|
}
|
|
36
|
-
|
|
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
|
-
|
|
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/dist/neon.js
CHANGED
|
@@ -8,19 +8,14 @@ import { NOT_INVITED, SIGN_IN_LINK_INVALID, SegmentStreamApiError, apiErrorFrom,
|
|
|
8
8
|
import { parseMe } from "./me.js";
|
|
9
9
|
import { createOntologyTransport } from "./ontology-transport.js";
|
|
10
10
|
import { isAllowed } from "./permissions.js";
|
|
11
|
-
import { matchesPathPrefix, normalizePublicPath } from "./proxy.js";
|
|
11
|
+
import { matchesPathPrefix, normalizePublicPath, wellKnownResponse } from "./proxy.js";
|
|
12
12
|
import { isApiRequest, unauthenticatedResponse } from "./sign-in.js";
|
|
13
13
|
/** The app's sign-in page, which renders `<SignIn />` from `@segmentstream-cloud/app/ui`. */
|
|
14
14
|
export const SIGN_IN_PAGE = "/sign-in";
|
|
15
15
|
/** Where `app/src/app/api/auth/[...path]/route.ts` serves the Neon Auth API. */
|
|
16
16
|
export const AUTH_API_PATH = "/api/auth";
|
|
17
|
-
/** The
|
|
18
|
-
const ONTOLOGY_PATHS = [
|
|
19
|
-
"/api/ontology",
|
|
20
|
-
"/mcp",
|
|
21
|
-
"/.well-known/oauth-protected-resource",
|
|
22
|
-
"/.well-known/oauth-authorization-server",
|
|
23
|
-
];
|
|
17
|
+
/** The ontology's HTTP API and MCP transport; metadata probes are handled separately. */
|
|
18
|
+
const ONTOLOGY_PATHS = ["/api/ontology", "/mcp"];
|
|
24
19
|
/**
|
|
25
20
|
* Paths every visitor may open: the Neon Auth API, the sign-in page, the health check, and the
|
|
26
21
|
* Next.js assets and metadata files the sign-in page needs.
|
|
@@ -310,6 +305,10 @@ export function createNeonSession(dependencies) {
|
|
|
310
305
|
const publicPaths = [...OPEN_PATHS, ...(options.publicPaths ?? [])].map(normalizePublicPath);
|
|
311
306
|
return async function neonProxy(request) {
|
|
312
307
|
const { pathname, search } = request.nextUrl;
|
|
308
|
+
const discovery = wellKnownResponse(request, settings.ontologyUrl);
|
|
309
|
+
if (discovery !== undefined) {
|
|
310
|
+
return discovery;
|
|
311
|
+
}
|
|
313
312
|
if (settings.ontologyUrl !== undefined &&
|
|
314
313
|
ONTOLOGY_PATHS.some((path) => matchesPathPrefix(pathname, path))) {
|
|
315
314
|
// Local development: the service runs elsewhere; deployed, its paths never reach the app.
|
package/dist/proxy.d.ts
CHANGED
|
@@ -11,5 +11,7 @@ export interface ProxyOptions {
|
|
|
11
11
|
}
|
|
12
12
|
export type SegmentStreamProxy = (request: NextRequest) => Promise<NextResponse>;
|
|
13
13
|
export declare function createProxy(context: AppContext, options?: ProxyOptions): SegmentStreamProxy;
|
|
14
|
+
/** Discovery probes never start browser authentication, including probes for unsupported metadata. */
|
|
15
|
+
export declare function wellKnownResponse(request: NextRequest, ontologyUrl?: string): NextResponse | undefined;
|
|
14
16
|
export declare function normalizePublicPath(path: string): string;
|
|
15
17
|
export declare function matchesPathPrefix(pathname: string, prefix: string): boolean;
|
package/dist/proxy.js
CHANGED
|
@@ -22,6 +22,10 @@ export function createProxy(context, options = {}) {
|
|
|
22
22
|
}
|
|
23
23
|
return async function proxy(request) {
|
|
24
24
|
const pathname = request.nextUrl.pathname;
|
|
25
|
+
const discovery = wellKnownResponse(request);
|
|
26
|
+
if (discovery !== undefined) {
|
|
27
|
+
return discovery;
|
|
28
|
+
}
|
|
25
29
|
if (matchesPathPrefix(pathname, SDK_ROUTE_PREFIX) ||
|
|
26
30
|
publicPaths.some((path) => matchesPathPrefix(pathname, path))) {
|
|
27
31
|
return NextResponse.next();
|
|
@@ -58,6 +62,26 @@ export function createProxy(context, options = {}) {
|
|
|
58
62
|
return startSignIn(context, request, policy, true);
|
|
59
63
|
};
|
|
60
64
|
}
|
|
65
|
+
const ONTOLOGY_METADATA_PATHS = new Set([
|
|
66
|
+
"/.well-known/oauth-protected-resource",
|
|
67
|
+
"/.well-known/oauth-protected-resource/mcp",
|
|
68
|
+
"/.well-known/oauth-authorization-server",
|
|
69
|
+
]);
|
|
70
|
+
/** Discovery probes never start browser authentication, including probes for unsupported metadata. */
|
|
71
|
+
export function wellKnownResponse(request, ontologyUrl) {
|
|
72
|
+
const { pathname, search } = request.nextUrl;
|
|
73
|
+
if (!matchesPathPrefix(pathname, "/.well-known")) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
if (ONTOLOGY_METADATA_PATHS.has(pathname)) {
|
|
77
|
+
return ontologyUrl === undefined
|
|
78
|
+
? NextResponse.next()
|
|
79
|
+
: NextResponse.rewrite(new URL(`${pathname}${search}`, ontologyUrl));
|
|
80
|
+
}
|
|
81
|
+
// The issuer is the root origin, so RFC 8414 does not define an AS document suffixed by /mcp.
|
|
82
|
+
// This service also does not publish OpenID Connect discovery metadata.
|
|
83
|
+
return NextResponse.json({ error: "not_found" }, { status: 404 });
|
|
84
|
+
}
|
|
61
85
|
/**
|
|
62
86
|
* SegmentStream is unreachable. API requests get a retryable 503; pages restart sign-in,
|
|
63
87
|
* which ends on the callback's error page rather than looping. Session cookies are kept.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@segmentstream-cloud/app",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.3",
|
|
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",
|