NAME
Developer::Dashboard::Web::App - local web application for Developer Dashboard
SYNOPSIS
my $app = Developer::Dashboard::Web::App->new(
auth => $auth,
pages => $pages,
sessions => $sessions,
);
DESCRIPTION
This module handles the browser-facing dashboard routes, helper login flow, page rendering modes, and page/action execution endpoints. It also provides static file serving for JavaScript, CSS, and other assets from the public directory structure (~/.developer-dashboard/dashboard/public/{js,css,others}). The browser tab icon at /favicon.ico is served from the same layered others roots with a bundled fallback, and resolves before the authorization gate because browsers request it implicitly on every page load.
Cross-site request forgery defense: every state-changing request (POST, PUT, DELETE, PATCH) passes one origin check before any trust-tier dispatch. When the request carries an Origin header (or, absent that, a Referer), its authority must equal the request's own Host header or name a trusted local alias — a numeric loopback literal, the localhost hostname family, or a configured web.ssl_subject_alt_names entry, the same alias semantics the loopback-admin trust check applies. Anything else, including the opaque Origin: null, is refused with an empty 403 on every tier: the loopback-admin shortcut, helper sessions, and the machine API tier alike, because browsers attach ambient credentials and loopback reachability to cross-site requests automatically. Requests with neither header keep working, since non-browser machine clients send neither while browsers always attach Origin to cross-site state-changing requests. The check also holds behind the SSL front-proxy, which forwards TLS bytes unmodified, so the backend compares against the browser's own Host header.
That origin check cannot defend a GET, because browsers omit Origin on same-origin GETs and a hostile page can drop its Referer with a referrer policy — yet /ajax/<file> runs an operator-written saved handler as a child process from a plain GET, on a tier that needs no cookie. So the same choke point additionally refuses, on every method, any request the browser labelled Sec-Fetch-Site: cross-site or same-site, unless the accompanying Origin/Referer names this dashboard or a trusted local alias (the localhost/127.0.0.1 pair is one host to the trust model and two sites to the browser). Sec-Fetch-Site is set by the browser itself and is a forbidden header name, so page script can neither forge nor suppress it. Requests carrying no fetch metadata are unaffected, which keeps machine clients and browsers too old to send it working unchanged.
METHODS
new, handle
Construct and dispatch the local web application.
_serve_static_file($type, $filename)
Serves static files from the public directory.
Input: $type (js, css, or others subdirectory), $filename (requested filename). Output: array reference of [status_code, content_type, body].
Security: Prevents directory traversal attacks and verifies files are within the public directory before serving.
_static_path_contained($file_path, $allowed_roots)
Package function asserting that one resolved static asset path still lives beneath an allowed public root after symlinks and parent-directory components are resolved with Cwd::abs_path, denying by default when no allowed roots are supplied. Every static-serving entry point passes its lookup roots through this check before opening a resolved path, including skill-namespaced assets, whose allowed roots come from the skill dispatcher's layered dashboards/public trees.
Input: resolved candidate file path string and array reference of allowed root directory path strings. Output: boolean true when the resolved path is inside one existing allowed root, otherwise false.
_get_content_type($type, $filename)
Determines the MIME type for a file based on its type and extension.
Input: $type (js, css, or others), $filename (requested filename). Output: MIME type string suitable for Content-Type header.
Supports: JS, CSS, JSON, XML, HTML, SVG, PNG, JPEG, GIF, WebP, ICO, and others.
PURPOSE
This module is the main route backend for the browser application. It handles login and logout, saved and transient page render/source/edit routes, status endpoints, saved Ajax endpoints, and the auth checks that decide whether a request is local admin, helper user, API-authorized machine caller, or unauthorized outsider.
WHY IT EXISTS
It exists because the dashboard browser surface is large and security-sensitive. Centralizing route behavior, auth gating, saved-page handling, Ajax endpoints, layered config/api.json machine auth, and response shaping keeps the product behavior coherent and testable.
WHEN TO USE
Use this file when changing browser routes, helper login behavior, page render/source/edit flows, saved Ajax endpoints, or the runtime JSON and HTML responses for dashboard workspaces.
HOW TO USE
Construct it with the action runner, auth service, config service, page store, prompt, page resolver, page runtime, and session store, then hand it to the Dancer adapter or PSGI bootstrap. Route-specific behavior belongs here rather than in the transport wrapper.
WHAT USES IT
It is used by Developer::Dashboard::Web::DancerApp, by app.psgi, by the CLI web server wrapper, and by the broad web/browser regression suite that covers routes, auth, Ajax, and workspace behavior.
EXAMPLES
Example 1:
perl -Ilib -MDeveloper::Dashboard::Web::App -e 1
Do a direct compile-and-load check against the module from a source checkout.
Example 2:
prove -lv t/03-web-app.t t/08-web-update-coverage.t t/web_app_static_files.t
Run the focused regression tests that most directly exercise this module's behavior.
Example 3:
HARNESS_PERL_SWITCHES=-MDevel::Cover prove -lr t
Recheck the module under the repository coverage gate rather than relying on a load-only probe.
Example 4:
prove -lr t
Put any module-level change back through the entire repository suite before release.