Switch feedback reverse proxy from IIS to NGINX

- Add nginx-wp-suite.conf: static site + /api/feedback proxy to the Power
  Automate trigger (SNI on, Host header, POST-only, body cap)
- Remove IIS web.config
- Update feedback-config.js comment and DEPLOYMENT.md to the NGINX setup

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 10:46:35 -07:00
parent 7186d46196
commit 3d4402c88c
8 changed files with 83 additions and 79 deletions

View File

@@ -78,20 +78,27 @@ app.listen(8080);
Each line of `feedback.jsonl` is one submission; download it anytime. (PHP/
Python/ASP.NET equivalents are a few lines too.)
#### Option B — Internal IIS reverse proxy → Power Automate (the chosen setup)
#### Option B — Internal NGINX reverse proxy → Power Automate (the chosen setup)
The browser posts to a **same-origin** path `/api/feedback`; IIS forwards that to
the Power Automate trigger. This avoids CORS entirely and keeps the secret
The browser posts to a **same-origin** path `/api/feedback`; NGINX forwards that
to the Power Automate trigger. This avoids CORS entirely and keeps the secret
trigger URL off the client. `FEEDBACK_ENDPOINT` is already set to
`/api/feedback`, and [`web.config`](web.config) contains the proxy rule.
`/api/feedback`, and [`nginx-wp-suite.conf`](nginx-wp-suite.conf) contains the
full server block.
**On the IIS box (one-time, server admin):**
1. Install the **URL Rewrite** and **Application Request Routing (ARR)** modules.
2. Enable the proxy: IIS Manager → server node → *Application Request Routing
Cache* → *Server Proxy Settings* → check **Enable proxy**.
3. Bind the site to **HTTPS** with an internal certificate.
4. In `web.config`, replace `POWER_AUTOMATE_TRIGGER_URL` with the real trigger
URL (write every `&` as `&amp;`).
**On the NGINX box (one-time, server admin):**
1. Copy the project files to the web root (e.g. `/var/www/wp-suite`).
2. Install [`nginx-wp-suite.conf`](nginx-wp-suite.conf) (e.g. into
`/etc/nginx/conf.d/`), and set `server_name`, the `ssl_certificate` paths
(internal cert), and the `root`.
3. In the `location = /api/feedback` block, replace the `proxy_pass` URL and the
`Host` header with your real trigger URL / region host (keep the full query
string incl. `sig=`).
4. `sudo nginx -t && sudo systemctl reload nginx`.
> Two NGINX details that matter: `proxy_ssl_server_name on;` (SNI is required for
> `*.logic.azure.com` or the TLS handshake fails) and the `Host` header set to
> the Azure region host. Both are already in the provided config.
**In Power Automate:**
1. Create a flow with the **"When an HTTP request is received"** trigger.
@@ -126,7 +133,7 @@ trigger URL off the client. `FEEDBACK_ENDPOINT` is already set to
(`POWER_AUTOMATE_TRIGGER_URL`).
5. A Power App (or just the list/Excel) reads that store to show live comments.
Chain: `browser → /api/feedback (IIS proxy) → Power Automate → SharePoint/Dataverse → Power App`.
Chain: `browser → /api/feedback (NGINX proxy) → Power Automate → SharePoint/Dataverse → Power App`.
The "downloadable file" is then just the Excel/SharePoint list, viewable live or
exported — all inside your corporate cloud.

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -16,11 +16,11 @@
Leave it as an empty string to stay fully local (export/import only).
See DEPLOYMENT.md for setup details and sample receivers.
This is set to the same-origin path '/api/feedback', which the IIS reverse
proxy (see web.config) forwards to the Power Automate HTTP trigger. Keeping it
relative means no CORS and the secret trigger URL never appears in client
code. Locally (no proxy) the POST simply fails silently and feedback is still
saved/exported from the browser.
This is set to the same-origin path '/api/feedback', which the NGINX reverse
proxy (see nginx-wp-suite.conf) forwards to the Power Automate HTTP trigger.
Keeping it relative means no CORS and the secret trigger URL never appears in
client code. Locally (no proxy) the POST simply fails silently and feedback is
still saved/exported from the browser.
────────────────────────────────────────────────────────────────────────── */
window.FEEDBACK_ENDPOINT = '/api/feedback';

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Work Package Suite — Prime Controls</title>
<link rel="icon" href="favicon.ico" sizes="any">
<link rel="stylesheet" href="theme-light.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }

56
nginx-wp-suite.conf Normal file
View File

@@ -0,0 +1,56 @@
# ─────────────────────────────────────────────────────────────────────────────
# Work Package Suite — NGINX site config
#
# Serves the static site and reverse-proxies feedback to a Power Automate
# "When an HTTP request is received" trigger. The browser only ever sees the
# same-origin path /api/feedback, so there is NO CORS and the secret trigger URL
# (with its sig= token) never reaches client code.
#
# Install:
# 1. Copy the project files to the web root (e.g. /var/www/wp-suite).
# 2. Put this file at /etc/nginx/conf.d/wp-suite.conf
# (or /etc/nginx/sites-available/ + symlink into sites-enabled/).
# 3. Replace server_name, the ssl_certificate paths, and the proxy_pass URL.
# 4. sudo nginx -t && sudo systemctl reload nginx
# ─────────────────────────────────────────────────────────────────────────────
# Redirect plain HTTP to HTTPS
server {
listen 80;
server_name wp-suite.company.local; # <-- your internal hostname
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name wp-suite.company.local; # <-- your internal hostname
# Internal certificate from your company CA
ssl_certificate /etc/nginx/ssl/wp-suite.crt; # <-- cert path
ssl_certificate_key /etc/nginx/ssl/wp-suite.key; # <-- key path
ssl_protocols TLSv1.2 TLSv1.3;
# Static site
root /var/www/wp-suite; # <-- web root
index index.html;
location / {
try_files $uri $uri/ =404;
}
# ── Feedback proxy → Power Automate ──────────────────────────────────────
# Paste your real trigger URL into proxy_pass below, keeping the FULL query
# string (api-version, sp, sv, sig). A small body cap keeps this endpoint safe.
location = /api/feedback {
limit_except POST { deny all; } # only accept POST
client_max_body_size 256k;
proxy_pass https://prod-XX.westus.logic.azure.com/workflows/REPLACE_WORKFLOW_ID/triggers/manual/paths/invoke?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=REPLACE_SIGNATURE;
proxy_http_version 1.1;
proxy_ssl_server_name on; # SNI — required for *.logic.azure.com
proxy_set_header Host prod-XX.westus.logic.azure.com; # <-- match your region host
proxy_set_header Content-Type application/json;
proxy_set_header Cookie ""; # don't leak site cookies upstream
}
}

View File

@@ -1,62 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
IIS configuration for the Work Package Suite (static site) + feedback proxy.
PREREQUISITES (one-time, done by the server admin in IIS Manager):
1. Install the "URL Rewrite" module.
2. Install "Application Request Routing" (ARR).
3. Enable the proxy: IIS Manager > (server node) >
Application Request Routing Cache > Server Proxy Settings >
check "Enable proxy". (This is server-level and cannot be set here.)
4. Bind the site to HTTPS with an internal certificate.
THEN: replace POWER_AUTOMATE_TRIGGER_URL below with the real "When an HTTP
request is received" URL from your flow. Remember every literal "&" in the URL
must be written as "&amp;" inside this file.
-->
<configuration>
<system.webServer>
<!-- index.html is the entry point -->
<defaultDocument>
<files>
<clear />
<add value="index.html" />
</files>
</defaultDocument>
<!-- Don't list folder contents -->
<directoryBrowse enabled="false" />
<rewrite>
<rules>
<!-- Forward same-origin /api/feedback POSTs to the Power Automate flow.
The browser sees only /api/feedback; the secret trigger URL stays
on the server. Requires ARR proxy enabled (see prerequisites). -->
<rule name="Feedback proxy to Power Automate" stopProcessing="true">
<match url="^api/feedback/?$" />
<action type="Rewrite"
url="POWER_AUTOMATE_TRIGGER_URL" />
</rule>
</rules>
</rewrite>
<!-- Make sure POST is allowed and the JSON body isn't truncated -->
<security>
<requestFiltering>
<verbs>
<add verb="POST" allowed="true" />
</verbs>
<!-- 1 MB cap on a feedback body is plenty -->
<requestLimits maxAllowedContentLength="1048576" />
</requestFiltering>
</security>
<!-- Sensible static MIME types (most are built in; this is belt-and-braces) -->
<staticContent>
<remove fileExtension=".json" />
<mimeMap fileExtension=".json" mimeType="application/json" />
</staticContent>
</system.webServer>
</configuration>

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Work Package Suite</title>
<link rel="icon" href="favicon.ico" sizes="any">
<link rel="stylesheet" href="theme-light.css">
<link rel="stylesheet" href="work-package-suite-styles.css">
</head>

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Work Package (IWP) — Prime Controls</title>
<link rel="icon" href="favicon.ico" sizes="any">
<link rel="stylesheet" href="theme-light.css">
<link rel="stylesheet" href="wp-creation-styles.css">
</head>