Make Requests to IP on Client's private network from the browser?

635 Views Asked by At

I'm trying to make a web UI for the Philips Hue API. This involves sending http requests over the users' private network to communicate with their hue bridge device. Is this possible? I'm currently trying to do it on a page load using axios, but it doesn't seem to execute on the client side. This would likely be a giant security vulnerability if possible, so I'm thinking it's not. Just wondering if anyone has experience trying to do something like this.

An example of my sveltekit code:

export const load: PageLoad = (async () => {
  try {
    const headers = new AxiosHeaders();
    headers.set('hue-application-key', BRIDGE_USERNAME);


    const res = await axios.request({
      url: `https://${BRIDGE_IP}/clip/v2/resource/light`,
      headers: headers,
      method: 'GET',
      httpsAgent: new https.Agent({
        checkServerIdentity: (hostname: string, cert: PeerCertificate) => {
          // Make sure the peer certificate's common name is equal to
          // the Hue bridgeId that this request is for.
          if (cert.subject.CN === BRIDGE_SERVER_NAME) {
            return undefined;
          } else {
            return new Error('Server identity check failed. CN does not match bridgeId.');
          }
        },
        ca: BRIDGE_SSL_CERT,
      }),
    });

    if (!res.data) {
      throw error(500, 'No data returned from bridge');
    }
    return res.data
  } catch (err) {
    return { error: 'Failed to load lights' };
  }
}) satisfies PageLoad;

I expect this to run on the client and retrieve data from the hue bridge, but it only seems to work server-side.

logging on the backend and not the front

2

There are 2 best solutions below

7
carlosV2 On

By default, SvelteKit pages are rendered on the server (SSR) for the initial request and in the browser (CSR) for subsequent navigation. Take a look at the documentation here.

It is very likely that you are seeing the output of the server side only because you didn't navigate back and forth to this page after the initial render (refreshing the page is also considered an initial render).

If you want the +page.ts to only run on the client, you can set the ssr variable to false:

export const ssr = false;

This will force the page to render on the client only even for the initial request. At this point, you should see whether the request was successful or not in the browser itself.

Alternatively, if you still want some code to be run on the server for the initial render, you can move your request code to inside the onMount method in the +page.svelte file:

<script lang="ts">
  import { onMount } from 'svelte';

  onMount(async () => {
    // Place your request here
  });
</script>
0
Andres Delgado On

When making a client-side https request to the Hue Bridge on the user's network, it failed because it didn't recognize the certificate. After downloading the certificate from the Hue API documentation and installing it on my browser, I got errors about not being able to verify the server name.

When making the request on the sever side, I was able to get around this by providing a callback function for verifying the server name in a custom http agent. Since this option isn't available on the client-side fetch or axios request methods, I abandoned the idea of using the v2 API and switched to using v1, which sets headers to allow for CORS.

Here is the code I ended up with:

export const ssr = false;

export const load: PageLoad = (async () => {
  try {
    const opts = {
      method: 'GET',
    };

    const res = await fetch(`http://${PUBLIC_BRIDGE_IP}/api/${PUBLIC_BRIDGE_USERNAME}/lights`, opts);

    const data: HueLights = await res.json();
    if (!data) {
      throw error(500, 'No data returned from bridge');
    }
    const lights = Object.keys(data).map((key): Light => {
      const light: HueLight = data[key];
      return {
        id: key,
        uniqueId: light?.uniqueid,
        name: light?.name,
        type: light?.config?.archetype,
        color: {
          xy: {
            x: light?.state?.xy[0],
            y: light?.state?.xy[1],
          },
        },
        on: light?.state?.on,
        dimming: {
          brightness: light?.state?.bri,
          minDimLevel: light?.capabilities?.control?.mindimlevel,
        },
      };
    });
    return { lights };
  } catch (err) {
    return { error: 'Failed to load lights' };
  }
}) satisfies PageLoad;