-
Notifications
You must be signed in to change notification settings - Fork 15
Root Folder Tidy
The application root used to hold fifteen loose PHP files β login, logout, password pages, OAuth callbacks, background workers, a CSAT survey and a stray test script. In July 2026 they were moved into purposeful folders, leaving the root with just
index.php,config.phpanddb_config.sample.php, and the login page gained a single canonical pretty URL:/login. This page documents every move, the URL contract that keeps old links working, and the rules to follow when adding new root-level pages.
The same login page was reachable at three URLs, one of them broken:
| URL | What happened |
|---|---|
/login.php |
worked |
/login |
worked by accident β Apache's MultiViews content-negotiation silently mapped it onto login.php
|
/login/index.php |
half-broken β MultiViews served login.php with /index.php as PATH_INFO, so the browser resolved relative assets against a phantom /login/ folder and the logo 404'd |
MultiViews came from the WAMP vhost (Options +MultiViews). The Docker image never had it β so /login 404'd in Docker until this change. Explicit rewrites now replace the accident in both environments, and MultiViews is switched off (Options -MultiViews in the root .htaccess).
/login is the one canonical login URL. Everything else funnels into it:
| Request | Result |
|---|---|
/login |
β 200 β the login page (canonical) |
/login.php (GET) |
301 β /login (query string preserved) |
/login.php (POST) |
served internally β a redirect would drop the form body |
/login/, /login/index.php, /login/anything
|
301 β /login
|
/auth/login.php (the file's real location) |
404 β direct access is blocked so a third URL can never exist |
The same pattern applies to /forgot-password, /reset-password and /csat. The ~100 in-app references to login.php were deliberately not rewritten β they keep working through the 301 and can be migrated to /login opportunistically.
Key: π auth page Β· π external contract (URL must never change) Β· βοΈ CLI worker Β· π§ͺ test Β· π module page
| π¨ | Old (root) | New location | URL behaviour |
|---|---|---|---|
| π | login.php |
auth/login.php |
canonical /login; .php GET 301s, POST internally rewritten |
| π | forgot-password.php |
auth/forgot-password.php |
canonical /forgot-password; .php 301s |
| π | reset-password.php |
auth/reset-password.php |
canonical /reset-password; .php 301s β emailed reset links keep working (1-hour tokens, query preserved) |
| π | force_password_change.php |
auth/force_password_change.php |
internal rewrite only, URL unchanged (POST-heavy interstitial; no pretty URL needed) |
| π | analyst_logout.php |
auth/analyst_logout.php |
internal rewrite, URL unchanged; also aliased as /logout
|
| π | oauth_callback.php |
auth/oauth_callback.php |
internal rewrite only β never redirected. This exact URL is registered in Azure app registrations and stored (encrypted) per mailbox |
| π | google_oauth_callback.php |
auth/google_oauth_callback.php |
same β registered in Google Cloud Console |
| π | csat.php |
tickets/csat/survey.php |
canonical /csat; csat.php?token=β¦ 301s with the token intact β survey links in already-sent emails keep working forever
|
| βοΈ | intune_worker.php |
scripts/intune_worker.php |
CLI only, spawned by intuneSpawnGenericWorker() in includes/intune.php (path updated) β never a URL |
| βοΈ | intune_app_worker.php |
scripts/intune_app_worker.php |
same |
| π§ͺ | test_email_thread.php |
tests/test_email_thread.php |
dev script, zero references β never a URL |
| π | logout.php |
auth/logout.php |
TOKEN_STORAGE_FILE constant that no longer exists, so it has fatalled on every call for a long time, and nothing references it (the real logout is analyst_logout.php). Moved rather than deleted only pending a deliberate decision β candidate for deletion
|
What deliberately stayed in root: index.php (the landing page β the web server's default document), config.php (its path is hardcoded in every require across the app) and db_config.sample.php (documented in the install guide as a root file to copy). Plus the non-PHP infrastructure: .htaccess, web.config, Dockerfile, docker-compose.yml, LICENSE, README.md.
Everything is driven by the root .htaccess (heavily commented in place):
-
Options -MultiViews -Indexesβ kills the accidental URL mapping and directory listings. - Pretty URLs are internal rewrites (the address bar keeps the pretty form).
-
Legacy
.phpURLs: GET β 301 to the pretty URL; POST β internal rewrite (a 301 would discard the body). Redirect targets are built from aREQUEST_URIcapture (%1), never a bare relative substitution β a relative substitution in anR=rule leaks the filesystem path into the Location header. This also makes the rules deployment-agnostic: they work at/freeitsm-app/(WAMP) and/(Docker) unchanged. -
Guard files in
auth/andtickets/csat/return 404 on direct access: internal rewrites arrive withREDIRECT_STATUSset, direct requests don't. One page, one URL. - All rewrite rules sit inside
<IfModule mod_rewrite.c>(house style, matchingapi/v1/.htaccess) so a server without mod_rewrite serves the rest of the app instead of 500ing β but note that on such a server the auth pages are unreachable: mod_rewrite is effectively required since this change. The Docker image enables it (a2enmod rewrite); WAMP's vhost hasAllowOverride All.
-
SSO/OIDC β its callback was already
api/auth/oidc_callback.php; untouched. -
The self-service portal β has its own
self-service/login.php; untouched. - Mailbox OAuth registrations β nothing to re-register in Azure or Google; the callback URLs are byte-identical externally.
- The mailbox save validation (
api/tickets/save_mailbox.php) matches the callback filename with an any-prefix pattern ((^|/)google_oauth_callback\.php$), so it was already tolerant of the file's location.
-
Don't put new PHP pages in the root. Auth-flow pages go in
auth/; module pages go in their module. -
Give a public page a pretty URL via a rewrite pair in the root
.htaccess(internal rewrite for the canonical URL + guarded 301 for any legacy name), copying the existingREQUEST_URI%1pattern for redirects. - Never rely on MultiViews β it is off, deliberately.
- Never move or redirect an OAuth callback URL β it is an external contract with the IdP; if a callback file must move, add an internal rewrite so the URL survives.
- URLs embedded in sent emails (CSAT, password reset, verification links) must keep working indefinitely β 301 with the query string preserved is the minimum.
- Architecture β the directory layout (updated for these moves)
- Installation β prerequisites, including mod_rewrite
- Security β the login flows themselves
-
Single Sign-On / Mailbox Authentication β the OAuth flows whose callbacks live in
auth/
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- Theming & Dark Mode
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
- MobileβFriendly
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- Problem Management
- Tasks
- Assets
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
- Forms
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)