# ----------------------------------------------------------------------
# SPB Portal .htaccess
# ----------------------------------------------------------------------
# Adapted from the draft in portal_UNFINISHED_HALF_WAY_OF_CONFIGURING...zip.
# One change from that draft, and why:
#
# Added "RewriteCond %{REQUEST_FILENAME} !-f" before the new-page
# pass-through rule below. Without it, this file would have silently
# hijacked the EXISTING, working /portal/index.php (Cognito start) and
# /portal/logout.php (Cognito logout) the moment it was deployed,
# because both names are real files at the portal root today. With the
# guard, a request only gets rewritten into public/ when no real file
# already answers it — so today's index.php and logout.php keep working
# untouched, and the day you remove them in favour of the public/
# versions, the rewrite starts serving those automatically with no
# further .htaccess changes.
#
# Everything else (including the bare "/portal/" rule below) is exactly
# as drafted. See README-REDESIGN.md, section "About that bare /portal/
# rule", before deploying — it explains a real, easy-to-miss interaction
# with the point above.
# ----------------------------------------------------------------------

# Redirect accidental direct /public URL back to clean portal URL
RewriteCond %{THE_REQUEST} \s/+portal/portal_redesign/public/? [NC]
RewriteRule ^public/?$ /portal/portal_redesign/ [R=302,L]

RewriteCond %{THE_REQUEST} \s/+portal/portal_redesign/public/(.*) [NC]
RewriteRule ^public/(.*)$ /portal/portal_redesign/$1 [R=302,L]

Options -Indexes

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /portal/portal_redesign/

    # ------------------------------------------------------------------
    # Block hidden files and sensitive files
    # ------------------------------------------------------------------

    RewriteRule (^|/)\. - [F,L]

    RewriteRule ^(composer\.json|composer\.lock|package\.json|package-lock\.json|webpack\.config\.js|vite\.config\.js)$ - [F,L,NC]
    RewriteRule ^(.*)\.(bak|backup|old|orig|save|swp|sql|log|ini|env)$ - [F,L,NC]

    # ------------------------------------------------------------------
    # Block direct browser access to backend folders
    # ------------------------------------------------------------------

    RewriteRule ^app(/|$) - [F,L]
    RewriteRule ^storage(/|$) - [F,L]

    # If vendor is not inside portal, this rule will not matter.
    # Keep it for safety if vendor is ever created here.
    RewriteRule ^vendor(/|$) - [F,L]

    # ------------------------------------------------------------------
    # Block direct physical /legacy URL access
    # Example blocked:
    # /portal/legacy/brrpts_erd/welcome.php
    # Reach the same content through the clean aliases below instead:
    # /portal/brrpts_erd/welcome.php
    # ------------------------------------------------------------------

    RewriteCond %{THE_REQUEST} \s/+portal/portal_redesign/legacy/ [NC]
    RewriteRule ^legacy(/|$) - [F,L]

    # ------------------------------------------------------------------
    # Main portal entry
    # /portal/  →  public/index.php  (the NEW shell's landing page)
    #
    # NOTE: this does not affect explicit /portal/index.php requests —
    # those keep hitting the real, existing Cognito-starting file below,
    # because of the -f guard on the rule right after this one. See
    # README-REDESIGN.md for what that means in practice.
    # ------------------------------------------------------------------

    RewriteRule ^$ public/index.php [L,QSA]

    # ------------------------------------------------------------------
    # New public PHP entry pages
    # /portal/dashboard.php → /portal/public/dashboard.php
    # ------------------------------------------------------------------

    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(index|login|callback|logout|dashboard|unit-select|report|transaction)\.php$ public/$1.php [L,QSA]

    # ------------------------------------------------------------------
    # Public API
    # /portal/api/... → /portal/public/api/index.php
    # ------------------------------------------------------------------

    RewriteRule ^api(/.*)?$ public/api/index.php [L,QSA]

    # ------------------------------------------------------------------
    # Public assets
    # /portal/assets/... → /portal/public/assets/...
    # ------------------------------------------------------------------

    RewriteRule ^assets/(.*)$ public/assets/$1 [L,QSA]

    # ------------------------------------------------------------------
    # Legacy compatibility routes
    # These give the copied legacy/ folders clean URLs:
    #
    # /portal/admin/welcome.php
    # /portal/brrpts_erd/welcome.php
    # /portal/brrpts_tvl/welcome.php
    #
    # The original, untouched files under /portal/otp/... keep working
    # exactly as they do today, completely unaffected by this section.
    # ------------------------------------------------------------------

    RewriteRule ^admin/(.*)$ legacy/admin/$1 [L,QSA]

    RewriteRule ^brrpts/(.*)$ legacy/brrpts/$1 [L,QSA]
    RewriteRule ^brrpts_erd/(.*)$ legacy/brrpts_erd/$1 [L,QSA]
    RewriteRule ^brrpts_tvl/(.*)$ legacy/brrpts_tvl/$1 [L,QSA]

    RewriteRule ^acc/(.*)$ legacy/acc/$1 [L,QSA]
    RewriteRule ^acc_erd/(.*)$ legacy/acc_erd/$1 [L,QSA]
    RewriteRule ^acc_tvl/(.*)$ legacy/acc_tvl/$1 [L,QSA]

    RewriteRule ^indr/(.*)$ legacy/indr/$1 [L,QSA]
    RewriteRule ^indr_erd/(.*)$ legacy/indr_erd/$1 [L,QSA]
    RewriteRule ^indr_tvl/(.*)$ legacy/indr_tvl/$1 [L,QSA]

</IfModule>

# ----------------------------------------------------------------------
# Security Headers
# PHP also sends headers, but Apache-level headers help static files too.
# ----------------------------------------------------------------------

<IfModule mod_headers.c>
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set X-XSS-Protection "0"
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()"

    # Keep CSP migration-friendly for now because old pages may use inline JS/CSS.
    # ------------------------------------------------------------------
    # Content Security Policy
    # ------------------------------------------------------------------
    # The CDN hosts below are NOT optional extras - the legacy screens
    # rendered inside the new frame load jQuery, DataTables, DevExtreme,
    # bootstrap-select and Bootstrap from them. The earlier, stricter
    # policy blocked all of it, so screens opened but stayed empty with
    # console errors like "$(...).dxDataGrid is not a function" and
    # "process_data is not defined".
    #
    # This list was taken from the actual <script> and <link> tags across
    # the legacy screens, not guessed. The live portal has no .htaccess
    # and therefore no CSP at all, so this is still strictly tighter than
    # what those same screens run under today.
    #
    # As screens are rebuilt natively, entries here can be removed.
    # ------------------------------------------------------------------
    Header always set Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; object-src 'none'; img-src 'self' data:; font-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; connect-src 'self'"
</IfModule>

# ----------------------------------------------------------------------
# PHP direct access safety
# ----------------------------------------------------------------------

# Two names were added here after checking the pattern against the actual
# filenames rather than trusting it:
#
#   conn_live_ro.php          5 copies, spb_user on spb_schema
#   indr_cust_data_config.php 2 copies, sys_readonly on spb_schema
#
# indr_cust_data_config.php carries a comment saying the *db_config.php
# rule matches it. It does not - that rule is ".*db_config\.php$" and this
# file ends "data_config.php". The credential was reachable over HTTP the
# whole time. Both files are include-only ($conn and a config array), so
# denying them breaks nothing.
#
# NOT denied, deliberately: indr_cust_data.php. It looks like a config
# file and it does hold a plaintext login, but it is a live AJAX endpoint
# - it answers $_POST["type"] for the customer picker. Denying it would
# empty that dropdown. The fix there is rotation, not a deny rule.
#
# prconnaws.php and mconnaws.php ARE already covered: the "connaws\.php$"
# alternative is a suffix match, so any name ending in connaws.php hits.
<FilesMatch "(^\.|secrets\.php|database\.php|security\.php|app\.php|modules\.php|units\.php|legacy_routes\.php|.*db_config\.php|connaws\.php|conn_live_ro\.php|indr_cust_data_config\.php)$">
    Require all denied
</FilesMatch>
