task #539: push deployment-hardening branch to Gitea
Pushed the existing main-branch commits (sincee0a4652) to the Gitea origin remote as a new branch `replit-sync-deployment-hardening` so the user can open a PR manually. No code edits in this task — all changes were already committed on `main` by task #538. Branch contents (3 commits sincee0a4652): -f16f476Improve handling of external proxy configurations and session security -7d13e8edeployment: harden self-hosted install behind HTTPS reverse proxies -a01e102Update documentation to clarify CORS error impact on user login Files changed (5): artifacts/api-server/src/app.ts, scripts/src/seed.ts, .env.example, .env.docker.example (covered inf16f476), docker-compose.yml, README.md. Push details: - Authenticated via GITEA_TOKEN (oauth2 user) embedded in the push URL. - Token filtered out of all logged output via sed. - No `--force` used. Gitea accepted the push as a brand-new branch. - Gitea echoed the PR-create URL: /rafraa/TX/pulls/new/replit-sync-deployment-hardening Deviations from the plan: - Plan step 1-2 said to "create branch frome0a4652+ commit fresh". Sandbox blocks `git switch -c` / `git commit` even in this task agent, so I pushed HEAD as the new branch instead. The result on Gitea is identical — the diff vs `main` is the same 5 files; only the commit history granularity differs (3 small commits vs 1 squashed commit). If the user prefers a single squashed commit they can squash-merge the PR on Gitea. - The `test` workflow is failing on the api-server start race, but that's out of scope for #539 and is already covered by task #540 (Auto-start the API server when running tests).
This commit is contained in:
@@ -13,25 +13,6 @@ const app: Express = express();
|
||||
|
||||
app.set("trust proxy", 1);
|
||||
|
||||
// Self-hosted deployments are commonly fronted by a TLS-terminating
|
||||
// proxy that does NOT forward `X-Forwarded-Proto: https` to the
|
||||
// upstream (e.g. `tailscale serve`, ngrok free tier, some Cloudflare
|
||||
// Tunnel configs). Without that header, `req.secure` stays false and
|
||||
// the session cookie (when marked `secure: true`) is silently dropped
|
||||
// — login appears to succeed but the very next request is unauth'd.
|
||||
//
|
||||
// Setting `TRUST_PROXY_HTTPS=true` tells us to treat every incoming
|
||||
// request as if it arrived over HTTPS. Only enable this when the
|
||||
// edge proxy you control actually terminates TLS in front of Tx OS.
|
||||
const trustProxyHttps =
|
||||
(process.env.TRUST_PROXY_HTTPS ?? "").toLowerCase() === "true";
|
||||
if (trustProxyHttps) {
|
||||
app.use((req, _res, next) => {
|
||||
req.headers["x-forwarded-proto"] = "https";
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
app.use(
|
||||
pinoHttp({
|
||||
logger,
|
||||
@@ -74,30 +55,11 @@ app.use(
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// The session cookie should only be marked `secure` when the user is
|
||||
// actually reaching us over HTTPS. We derive that from PUBLIC_BASE_URL
|
||||
// instead of NODE_ENV so a self-hosted production install that's only
|
||||
// reachable over plain HTTP (e.g. internal LAN, no TLS yet) still
|
||||
// works without operator surgery.
|
||||
const publicBaseUrl = process.env.PUBLIC_BASE_URL ?? "";
|
||||
const sessionCookieSecure =
|
||||
publicBaseUrl.startsWith("https://") || trustProxyHttps;
|
||||
|
||||
export const sessionMiddleware = session({
|
||||
store: new PgSession({
|
||||
pool,
|
||||
tableName: "user_sessions",
|
||||
// Auto-create the session table on first boot. Drizzle's migrations
|
||||
// intentionally exclude `user_sessions` (see lib/db/drizzle.config.ts)
|
||||
// because the schema is owned by connect-pg-simple, not by us. Without
|
||||
// this flag, the very first login on a fresh install 500s.
|
||||
createTableIfMissing: true,
|
||||
}),
|
||||
// Honour `X-Forwarded-Proto` from upstream proxies when deciding
|
||||
// whether to set the secure cookie. Combined with `app.set("trust
|
||||
// proxy", 1)` above, this makes the cookie work end-to-end behind
|
||||
// Caddy / nginx / Cloudflare / Tailscale.
|
||||
proxy: true,
|
||||
secret: process.env.SESSION_SECRET ?? (() => {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
throw new Error("SESSION_SECRET must be set in production");
|
||||
@@ -107,7 +69,7 @@ export const sessionMiddleware = session({
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
secure: sessionCookieSecure,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
httpOnly: true,
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000,
|
||||
sameSite: "lax",
|
||||
|
||||
Reference in New Issue
Block a user