# ============================================================================
# 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).
<FilesMatch "\.php$">
    SetHandler application/x-httpd-php
</FilesMatch>

# 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.
<FilesMatch "^\.">
    <IfModule mod_authz_core.c>
        Require all denied
    </IfModule>
    <IfModule !mod_authz_core.c>
        Order allow,deny
        Deny from all
    </IfModule>
</FilesMatch>

<IfModule mod_rewrite.c>
    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. <FilesMatch "^\."> 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 (<DirectoryMatch> 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]
</IfModule>

# 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.
<IfModule mod_headers.c>
    <Files "embed.php">
        Header always unset X-Frame-Options
    </Files>
</IfModule>
