UNPKG

@ory/nextjs

Version:

This library was generated with [Nx](https://nx.dev).

220 lines (213 loc) 8.1 kB
import { NextResponse } from 'next/server'; import { parse as parse$1, splitCookiesString } from 'set-cookie-parser'; import { serialize } from 'cookie'; import { parse } from 'psl'; // src/middleware/middleware.ts function isErrorResult(result) { return !!result && typeof result === "object" && "error" in result && "input" in result; } function guessCookieDomain(url, config) { if (!url || config.forceCookieDomain) { return config.forceCookieDomain; } let parsedUrl; try { parsedUrl = new URL(url).hostname; } catch (_) { parsedUrl = url; } if (isIPAddress(parsedUrl)) { return parsedUrl; } const parsed = parse(parsedUrl); if (isErrorResult(parsed)) { return void 0; } return parsed.domain ?? parsed.input; } function isIPAddress(hostname) { const ipv4Pattern = /^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})){3}$/; const ipv6Pattern = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))$/; return ipv4Pattern.test(hostname) || ipv6Pattern.test(hostname); } // src/utils/headers.ts var defaultForwardedHeaders = [ "accept", "accept-charset", "accept-encoding", "accept-language", "authorization", "cache-control", "content-type", "cookie", "host", "user-agent", "referer" ]; var defaultOmitHeaders = [ "transfer-encoding", "content-encoding", "content-length" ]; // src/utils/utils.ts function processSetCookieHeaders(protocol, fetchResponse, options, requestHeaders) { const isTls = protocol === "https:" || requestHeaders.get("x-forwarded-proto") === "https"; const forwarded = requestHeaders.get("x-forwarded-host"); const host = forwarded ? forwarded : requestHeaders.get("host"); const domain = guessCookieDomain(host ?? "", options); return parse$1( splitCookiesString(fetchResponse.headers.get("set-cookie") || "") ).map((cookie) => ({ ...cookie, domain, secure: isTls, encode: (v) => v })).map( ({ value, name, ...options2 }) => serialize(name, value, options2) ); } function filterRequestHeaders(headers, forwardAdditionalHeaders) { const filteredHeaders = new Headers(); headers.forEach((value, key) => { const isValid = defaultForwardedHeaders.includes(key) || (forwardAdditionalHeaders ?? []).includes(key); if (isValid) filteredHeaders.set(key, value); }); return filteredHeaders; } function joinUrlPaths(baseUrl, relativeUrl) { const base = new URL(baseUrl); const relative = new URL(relativeUrl, baseUrl); relative.pathname = base.pathname.replace(/\/$/, "") + "/" + relative.pathname.replace(/^\//, ""); return new URL(relative.toString(), baseUrl).href; } // src/utils/sdk.ts function orySdkUrl() { let baseUrl; if (process.env["NEXT_PUBLIC_ORY_SDK_URL"]) { baseUrl = process.env["NEXT_PUBLIC_ORY_SDK_URL"]; } if (!baseUrl) { throw new Error( "You need to set environment variable `NEXT_PUBLIC_ORY_SDK_URL` to your Ory Network SDK URL." ); } return baseUrl.replace(/\/$/, ""); } // src/utils/rewrite.ts function rewriteUrls(source, matchBaseUrl, selfUrl, config) { for (const [_, [matchPath, replaceWith]] of [ // TODO load these dynamically from the project config ["/ui/recovery", config.override?.recoveryUiPath], ["/ui/registration", config.override?.registrationUiPath], ["/ui/login", config.override?.loginUiPath], ["/ui/verification", config.override?.verificationUiPath], ["/ui/settings", config.override?.settingsUiPath], ["/ui/welcome", config.override?.defaultRedirectUri] ].entries()) { const match = joinUrlPaths(matchBaseUrl, matchPath || ""); if (replaceWith && source.startsWith(match)) { source = source.replaceAll( match, new URL(replaceWith, selfUrl).toString() ); } } return source.replaceAll( matchBaseUrl.replace(/\/$/, ""), new URL(selfUrl).toString().replace(/\/$/, "") ); } // src/middleware/middleware.ts function getProjectApiKey() { let baseUrl = ""; if (process.env["ORY_PROJECT_API_TOKEN"]) { baseUrl = process.env["ORY_PROJECT_API_TOKEN"]; } return baseUrl.replace(/\/$/, ""); } async function proxyRequest(request, options) { const match = [ "/self-service", "/sessions/whoami", "/ui", "/.well-known/ory", "/.ory" ]; if (!match.some((m) => request.nextUrl.pathname.startsWith(m))) { return NextResponse.next(); } const matchBaseUrl = new URL(orySdkUrl()); const selfUrl = request.nextUrl.protocol + "//" + request.nextUrl.host; const upstreamUrl = request.nextUrl.clone(); upstreamUrl.hostname = matchBaseUrl.hostname; upstreamUrl.host = matchBaseUrl.host; upstreamUrl.protocol = matchBaseUrl.protocol; upstreamUrl.port = matchBaseUrl.port; const upstreamRequestHeaders = filterRequestHeaders( request.headers, options.forwardAdditionalHeaders ); upstreamRequestHeaders.set("Host", upstreamUrl.host); upstreamRequestHeaders.set("Ory-Base-URL-Rewrite", selfUrl.toString()); upstreamRequestHeaders.set("Ory-Base-URL-Rewrite-Token", getProjectApiKey()); upstreamRequestHeaders.set("Ory-No-Custom-Domain-Redirect", "true"); const upstreamResponse = await fetch(upstreamUrl.toString(), { method: request.method, headers: upstreamRequestHeaders, body: request.method !== "GET" && request.method !== "HEAD" ? await request.arrayBuffer() : null, redirect: "manual" }); defaultOmitHeaders.forEach((header) => { upstreamResponse.headers.delete(header); }); if (upstreamResponse.headers.get("set-cookie")) { const cookies = processSetCookieHeaders( request.nextUrl.protocol, upstreamResponse, options, request.headers ); upstreamResponse.headers.delete("set-cookie"); cookies.forEach((cookie) => { upstreamResponse.headers.append("Set-Cookie", cookie); }); } const originalLocation = upstreamResponse.headers.get("location"); if (originalLocation) { let location = originalLocation; if (location.startsWith("../self-service")) { location = location.replace("../self-service", "/self-service"); } else if (!location.startsWith("http")) { location = new URL(location, matchBaseUrl).toString(); } location = rewriteUrls(location, matchBaseUrl.toString(), selfUrl, options); if (!location.startsWith("http")) { location = new URL(location, selfUrl).toString(); } if (!location.startsWith("http")) { throw new Error( "The HTTP location header must be an absolute URL in NextJS middlewares. However, it is not. The resulting HTTP location is `" + location + "`. This is either a configuration or code bug. Please open an issue on https://github.com/ory/elements." ); } upstreamResponse.headers.set("location", location); } let modifiedBody = Buffer.from(await upstreamResponse.arrayBuffer()); if (upstreamResponse.headers.get("content-type")?.includes("text/") || upstreamResponse.headers.get("content-type")?.includes("application/json")) { const bufferString = modifiedBody.toString("utf-8"); modifiedBody = Buffer.from( rewriteUrls(bufferString, matchBaseUrl.toString(), selfUrl, options) ); } return new NextResponse(modifiedBody, { headers: upstreamResponse.headers, status: upstreamResponse.status }); } function createOryMiddleware(options) { return (r) => { return proxyRequest(r, options); }; } export { createOryMiddleware }; //# sourceMappingURL=index.mjs.map //# sourceMappingURL=index.mjs.map