Coder Social home page Coder Social logo

Comments (19)

kevinmitch14 avatar kevinmitch14 commented on August 23, 2024 2

This was introduced in v14.1.1 stable release.

from next.js.

kevinmitch14 avatar kevinmitch14 commented on August 23, 2024 2

Yeah I thought this issue would be picked up quickly as I think it's a pretty fundamental issue/regression.

It made it off canary and into a stable release so surely other people are going to be affected by this.

from next.js.

dawi avatar dawi commented on August 23, 2024 2

I am facing a similar issue while running playwright tests against a standalone export in a gitlab build pipeline.

uitests:playwright: [WebServer] failed to get redirect response TypeError: fetch failed
uitests:playwright:     at Object.fetch (node:internal/deps/undici/undici:11576:11)
uitests:playwright:     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
uitests:playwright:     at async globalThis.fetch (/builds/xxx/xxx/apps/uitest/.next/server/chunks/369.js:1:36492)
uitests:playwright:     at async rp (/builds/xxx/xxx/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:15:4325)
uitests:playwright:     at async rm (/builds/xxx/xxx/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:16:741)
uitests:playwright:     at async rq (/builds/xxx/xxx/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:18:1249)
uitests:playwright:     at async doRender (/builds/xxx/xxx/node_modules/next/dist/server/base-server.js:1376:30)
uitests:playwright:     at async cacheEntry.responseCache.get.routeKind (/builds/xxx/xxx/node_modules/next/dist/server/base-server.js:1537:28)
uitests:playwright:     at async NextNodeServer.renderToResponseWithComponentsImpl (/builds/xxx/xxx/node_modules/next/dist/server/base-server.js:1445:28)
uitests:playwright:     at async NextNodeServer.renderPageComponent (/builds/xxx/xxx/node_modules/next/dist/server/base-server.js:1842:24) {
uitests:playwright:   cause: Error: connect EADDRNOTAVAIL :::3000 - Local (:::0)
uitests:playwright:       at internalConnect (node:net:1100:16)
uitests:playwright:       at defaultTriggerAsyncIdScope (node:internal/async_hooks:463:18)
uitests:playwright:       at node:net:1306:9
uitests:playwright:       at process.processTicksAndRejections (node:internal/process/task_queues:77:11) {
uitests:playwright:     errno: -99,
uitests:playwright:     code: 'EADDRNOTAVAIL',
uitests:playwright:     syscall: 'connect',
uitests:playwright:     address: '::',
uitests:playwright:     port: 3000
uitests:playwright:   }
uitests:playwright: }

from next.js.

4lejandrito avatar 4lejandrito commented on August 23, 2024 1

Maybe caused by #62561? I am experiencing a similar issue in 14.1.1-canary.75 but not in 14.1.1-canary.74.

Edit: Oh sorry I did not read the description 😓.

from next.js.

4lejandrito avatar 4lejandrito commented on August 23, 2024 1

The problem here is not only with the hostname but also with the protocol. When running behind an nginx on https:

  • Before #62561 the protocol is https and the hostname mysite.com and the redirects work fine.
  • After #62561 the protocol is still https but the hostname is 0.0.0.0:3000 which listens on http and not on https (this is handled by nginx). This causes the redirects to break with failed to get redirect response TypeError: fetch failed + ERR_SSL_WRONG_VERSION_NUMBER.

I am debating whether or not to create a separate issue for this, since the root cause is the same one.

from next.js.

4lejandrito avatar 4lejandrito commented on August 23, 2024 1

I reported a separate issue: #63114. Same root cause.

from next.js.

dawi avatar dawi commented on August 23, 2024 1

@shuding Maybe the error i am running into is this one: #62953

from next.js.

mlynch avatar mlynch commented on August 23, 2024 1

I'm not sure but I think I'm seeing the same issue (ERR_SSL_WRONG_VERSION_NUMBER) when using redirect in a server action when running locally, specifically when following this supabase + Next.js auth flow doc:

image image

from next.js.

kevinmitch14 avatar kevinmitch14 commented on August 23, 2024

Hey @shuding, I've just upgraded to canary and redirects are now not working as before.

When calling redirect from a subdomain and logging request headers:
Previous versions: The host was subdomain.localhost:3000
Canary version: Host is localhost:3000

For additional context I am using middleware to rewrite requests. I need to get the subdomain of the incoming request to route it to /app/[tenant]/....

I see that x-forwarded-host in the canary versions does include the subdomain, is it better to use this to determine the subdomain of an incoming request?

from next.js.

shuding avatar shuding commented on August 23, 2024

@kevinmitch14 Hey Kevin, thanks for the info above. Is this specific to multi tenant apps, or it breaks all cases? I think the #63389 fix still doesn't cover these multi tenant cases yet.

The reason that we moved away from request headers (host or x-forwarded-host) is that the requester side can specify any host via these headers, including malicious ones. And the redirection request to that host happens on the server side which can be risky.

One work around idea might be adding an extra header that specifies the original request host/forwarded-host, to the redirection request. So the middleware can know its original host even if the request itself is just localhost:3000. What do you think?

from next.js.

kevinmitch14 avatar kevinmitch14 commented on August 23, 2024

Hey @shuding, here is a barebones version of the middleware. The issue appears when I use redirect() because the host passed to getSubdomain() no longer has the subdomain. It's just localhost:3000 instead of tenant.localhost:3000

Because tenants live on subdomains, when the host is localhost:3000 I just return NextResponse.next(), which results in the "wrong" redirect. Previously the subdomain is extracted and appended to the pathname before rewriting.

export function middleware(request: NextRequest) {
  const url = request.nextUrl.clone();
  const subdomain = getSubdomain(request.headers.get("host"));
  const path = url.pathname;
  console.log({ subdomain, path });
  if (subdomain === "localhost:3000") {
    return NextResponse.next();
  }
  const destinationUrl = url.clone();
  destinationUrl.pathname = `/${subdomain}${path}`;
  return NextResponse.rewrite(destinationUrl);
}

In my use-case, the tenant will always live on a subdomain (tenant1.example.com) would equate to tenant1. How I know which tenant was making the request was through the host header in middleware, as this is the first entry point into the app.

A request to tenant1.example.com/blog would go through middleware and be rewritten to /[tenant1]/blog. In the layout I do a check to see if this is a valid tenant via a DB call, if not, notFound() is invoked.


The initial way I find the current tenant is through middleware via the request host header. Is this part dangerous or is it just the redirection aspect that is considered potentially dangerous?

Just to be clear, you recommend something like the following:
User enters app tenant1.example.com

  1. Middleware invoked - request x-forwarded-host : tenant1.example.com.
  2. Do check if tenant1 is a valid tenant. (Is it signed up to the product)
  3. Do auth check if user is valid user of tenant1
  4. If above are all true, respond with rewrite and append header x-active-tenant : tenant1
  5. For each request in middleware, check for presence of x-active-tenant
  6. If present, skip step 2. Otherwise, go through all steps.

User completes action w/server action redirect:

  1. Read x-active-tenant from headers, it should be present at this stage if the user is in the app.
  2. If not present, use the x-forwarded-header to determine the subdomain and check if valid tenant.
  3. Middleware will receive this request with host : example.com, x-forwarded-host : tenant1.example.com
  4. For security Middleware will read x-active-tenant to determine the subdomain to use for rewrite.

Having a read of some docs here I also found this:

As an additional protection Server Actions in Next.js 14 also compares the Origin header to the Host header (or X-Forwarded-Host). If they don't match, the Action will be rejected. In other words, Server Actions can only be invoked on the same host as the page that hosts it. Very old unsupported and outdated browsers that don't support the Origin header could be at risk.

Does this have any relevance? Also a bit confusing, "Host header (or X-Forwarded-Host)", when using a subdomain, the host and forwarded host will be different anyway?

from next.js.

kevinmitch14 avatar kevinmitch14 commented on August 23, 2024

I think multi-tenant is definitely an area for improvement in docs + education.

Here is Next.js "recommended architecture" for Multi-tenant apps: https://github.com/vercel/platforms.

And the middleware specifically, similar to ours, uses the host from the incoming request headers - https://github.com/vercel/platforms/blob/main/middleware.ts

from next.js.

dawi avatar dawi commented on August 23, 2024

Just tried 14.2.0-canary.29. Everything works in dev mode, but in a standalone export I am now getting a client-side exception. No issues with 14.1.0. But maybe that's a new regression, and has nothing to do wit this issue. :/

Application error: a client-side exception has occurred (see the browser console for more information).

from next.js.

blizzy78 avatar blizzy78 commented on August 23, 2024

Application error: a client-side exception has occurred (see the browser console for more information).

So is there more info in the browser console you can post here?

from next.js.

dawi avatar dawi commented on August 23, 2024

@blizzy78 All I have is the minified react error, which tells me to use the dev environment for full errors.

However in dev mode everything runs fine. The error only occurs in a production standalone build.

7023-678442bb9053eb7a.js:1 Error: Minified React error #130; visit https://react.dev/errors/130?args[]=object&args[]= for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
at iz (fd9d1056-8215d544d1fd3378.js:1:113022)
at d (fd9d1056-8215d544d1fd3378.js:1:34851)
at fd9d1056-8215d544d1fd3378.js:1:37511
at u (fd9d1056-8215d544d1fd3378.js:1:37802)
at fd9d1056-8215d544d1fd3378.js:1:38869
at lD (fd9d1056-8215d544d1fd3378.js:1:57332)
at iZ (fd9d1056-8215d544d1fd3378.js:1:121359)
at ia (fd9d1056-8215d544d1fd3378.js:1:95165)
at fd9d1056-8215d544d1fd3378.js:1:94987
at il (fd9d1056-8215d544d1fd3378.js:1:94994)

from next.js.

shuding avatar shuding commented on August 23, 2024

@dawi Would you mind explaining a bit about your set up? In which context did it throw and is your app relying on the host headers somewhere?

from next.js.

dawi avatar dawi commented on August 23, 2024

@shuding I had a closer look using 14.2.0-canary.33 and was able to identify the component that causes the error.

As I already suspected, the error is caused be another change. The routing issues seems to be fixed. :)

The new issue seems to be caused by a change in how the standalone export is build.

We have an array of configuration objects like this:

import PeopleOutlineIcon from '@mui/icons-material/PeopleOutline';
import PhotoLibraryOutlinedIcon from '@mui/icons-material/PhotoLibraryOutlined';

export const APP_PARTS: AppPart[] = [
    {
        identifier: 'files',
        icon: PhotoLibraryOutlinedIcon
    },
    {
        identifier: 'users',
        icon: PeopleOutlineIcon
    }
]

One part of the configuration is an icon which is of type React.ComponentType<SvgIconProps>.

We can use it to display the icon somewhere else in the application like this:

function AppPartEntry(props: { appPart: AppPart }) {
    
    const Icon = props.appPart.icon;

    return <MenuItem ...>
        <ListItemIcon>
            <Icon color={'action'}/>
        </ListItemIcon>
        <ListItemText>
            {name}
        </ListItemText>
    </MenuItem>;
}

Now this runs fine in dev mode with all Next.js Versions (14.1.0 - 14.2.0-canary.33).
But for production, we are using a standalone export, which does not work in 14.2.0-canary.33.

I noticed the following difference:

console.log(Icon) with v14.1.0

with v14 1 0

console.log(Icon) with 14.2.0-canary.33

with 14 2 0-canary 33

from next.js.

dawi avatar dawi commented on August 23, 2024

@shuding I think this issue can be closed again.

But the client side exception (#62953) still exists in the latest canary.

from next.js.

kevinmitch14 avatar kevinmitch14 commented on August 23, 2024

Hey @shuding, here is a barebones version of the middleware. The issue appears when I use redirect() because the host passed to getSubdomain() no longer has the subdomain. It's just localhost:3000 instead of tenant.localhost:3000

Because tenants live on subdomains, when the host is localhost:3000 I just return NextResponse.next(), which results in the "wrong" redirect. Previously the subdomain is extracted and appended to the pathname before rewriting.

export function middleware(request: NextRequest) {
  const url = request.nextUrl.clone();
  const subdomain = getSubdomain(request.headers.get("host"));
  const path = url.pathname;
  console.log({ subdomain, path });
  if (subdomain === "localhost:3000") {
    return NextResponse.next();
  }
  const destinationUrl = url.clone();
  destinationUrl.pathname = `/${subdomain}${path}`;
  return NextResponse.rewrite(destinationUrl);
}

In my use-case, the tenant will always live on a subdomain (tenant1.example.com) would equate to tenant1. How I know which tenant was making the request was through the host header in middleware, as this is the first entry point into the app.

A request to tenant1.example.com/blog would go through middleware and be rewritten to /[tenant1]/blog. In the layout I do a check to see if this is a valid tenant via a DB call, if not, notFound() is invoked.

The initial way I find the current tenant is through middleware via the request host header. Is this part dangerous or is it just the redirection aspect that is considered potentially dangerous?

Just to be clear, you recommend something like the following: User enters app tenant1.example.com

  1. Middleware invoked - request x-forwarded-host : tenant1.example.com.
  2. Do check if tenant1 is a valid tenant. (Is it signed up to the product)
  3. Do auth check if user is valid user of tenant1
  4. If above are all true, respond with rewrite and append header x-active-tenant : tenant1
  5. For each request in middleware, check for presence of x-active-tenant
  6. If present, skip step 2. Otherwise, go through all steps.

User completes action w/server action redirect:

  1. Read x-active-tenant from headers, it should be present at this stage if the user is in the app.
  2. If not present, use the x-forwarded-header to determine the subdomain and check if valid tenant.
  3. Middleware will receive this request with host : example.com, x-forwarded-host : tenant1.example.com
  4. For security Middleware will read x-active-tenant to determine the subdomain to use for rewrite.

Having a read of some docs here I also found this:

As an additional protection Server Actions in Next.js 14 also compares the Origin header to the Host header (or X-Forwarded-Host). If they don't match, the Action will be rejected. In other words, Server Actions can only be invoked on the same host as the page that hosts it. Very old unsupported and outdated browsers that don't support the Origin header could be at risk.

Does this have any relevance? Also a bit confusing, "Host header (or X-Forwarded-Host)", when using a subdomain, the host and forwarded host will be different anyway?

@shuding would be great to know if I understood your original response here!

from next.js.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.