# ============================================================================ # LUPMIS2 PWA — Apache config # ============================================================================ # Apache's default DirectoryIndex order serves index.html before index.php. # We need the opposite so the SSO-aware index.php gets a chance to run first, # inject session data into the page, and then return the index.html content. DirectoryIndex index.php index.html # Make sure .php files are executed (defensive — usually enabled site-wide, # but explicit here in case the deployment dropped this association). SetHandler application/x-httpd-php # Second line of defence for hidden FILES (.env, .htpasswd, …), in case # mod_rewrite is not loaded. This does not cover hidden directories such as # /.git/config — see the mod_rewrite block below, which does. # Both Apache 2.4 and 2.2 syntaxes are given so this works either way. # # Access control runs before mod_rewrite, so a hidden file answers 403 here # while a hidden directory answers 404 from the rewrite below. Both refuse to # serve the content, which is the point; the differing codes are cosmetic. Require all denied Order allow,deny Deny from all RewriteEngine On # ------------------------------------------------------------------ # Block hidden paths (.git, .env, .svn, …) — MUST stay first # ------------------------------------------------------------------ # Scanners routinely fetch /.env and /.git/config, and if /.git/index # plus /.git/objects/… can be read the whole repository — including its # history and any credential ever committed — can be reconstructed. # # Two things make this easy to get wrong: # # 1. does NOT stop /.git/config. FilesMatch tests # the basename only, and the basename there is "config". Blocking a # hidden *directory* needs a rule that sees the whole path, which in # .htaccess means mod_rewrite ( is server-config only). # # 2. The SPA fallback further down only rewrites paths that do NOT # exist (!-f / !-d). A real .env on disk therefore skips the # fallback and gets served as a plain file. So this rule has to come # before it, not after. # # .well-known is deliberately exempt: blocking it breaks Let's Encrypt # (ACME) certificate issuance and renewal. # # 404 rather than 403, so a probe learns nothing about what exists. RewriteCond %{REQUEST_URI} !^/\.well-known/ RewriteRule (^|/)\. - [R=404,L] # Files that are never meant to be fetched over HTTP, if one is ever # deployed into the document root by mistake. RewriteRule \.(sql|sqlite|sqlite3|db|bak|backup|old|orig|save|swp|log|ini|conf|key|pem|crt|p12|pfx)$ - [R=404,L,NC] RewriteRule (^|/)(composer\.(json|lock)|package(-lock)?\.json|yarn\.lock|Dockerfile|docker-compose\.ya?ml|\.dockerignore)$ - [R=404,L,NC] # Clean URL for the iframe embed endpoint: /embed → embed.php # Must come BEFORE the SPA fallback so /embed doesn't get routed to # index.php. Query strings (?mode=permit&...) pass through automatically. RewriteRule ^embed/?$ embed.php [L] # Common single-page-app behaviour: if a route doesn't map to a real file # or directory, send the request to index.php so the PWA can handle it # client-side. Comment out this block if hash-based routing is preferred. RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^ index.php [L] # Iframe-policy override for the embed endpoint. Some Apache deployments set # `X-Frame-Options: SAMEORIGIN` as a default security header for every # response — that prevents `permits.lupmis4luspa.org` from framing # `pwa.lupmis4luspa.org/embed`, even though our Content-Security-Policy # `frame-ancestors` directive explicitly allows it. Safari prefers # `X-Frame-Options` when both are present, so we have to remove it. # # We unset it ONLY for embed.php (so index.php still inherits the # site-wide SAMEORIGIN protection against clickjacking). embed.php's own # `Content-Security-Policy: frame-ancestors` header (set in PHP) is then # the sole iframe-policy header and permits the configured embedder. Header always unset X-Frame-Options