Skip to content

feat: Refactor to Fastify with auto-swagger and MCP support#254

Open
horner wants to merge 3 commits intomainfrom
feature/fastify-refactor
Open

feat: Refactor to Fastify with auto-swagger and MCP support#254
horner wants to merge 3 commits intomainfrom
feature/fastify-refactor

Conversation

@horner
Copy link
Member

@horner horner commented Mar 23, 2026

Refactor create-a-container from Express to Fastify 5.x with auto-swagger and MCP support.

  • Replace Express with Fastify 5.x
  • Add @fastify/swagger for automatic OpenAPI generation
  • Add @fastify/swagger-ui at /api
  • Implement MCP server plugin for AI agent tooling
  • Port all 12 routers with JSON schema validation
  • Create Fastify plugins: auth, flash, load-sites, mcp
  • Preserve EJS views and dual HTML/JSON content negotiation

Run: node server-fastify.js

horner added 3 commits March 20, 2026 15:28
- Add split-button dropdown with 'Login as Admin' and 'Login as Standard User' options
- Show quick login only when no .env file exists (dev mode)
- Auto-create test user and log in with one click
- First registered user automatically becomes admin
- Show 'No users exist' message on login page when database is empty
- Add Playwright tests for quick login and API key creation
- Simplify README with developer-focused quick start guide
- Update server startup message with clear ready banner
- Replace Express with Fastify 5.x
- Add @fastify/swagger for automatic OpenAPI generation from JSON schemas
- Add @fastify/swagger-ui at /api/docs
- Implement MCP server plugin with container management tools
- Port all routes with JSON schema validation:
  - login/logout, register, reset-password (auth flows)
  - apikeys, settings (simple CRUD)
  - users, groups (user management)
  - sites, nodes, containers (infrastructure)
  - jobs (async task monitoring with SSE)
  - external-domains (DNS management)
- Create Fastify plugins:
  - auth.js: requireAuth, requireAdmin, requireLocalhostOrAdmin
  - flash.js: flash message support for EJS views
  - load-sites.js: site context for authenticated users
  - mcp.js: Model Context Protocol server
- Preserve dual HTML/JSON content negotiation
- Keep existing Sequelize models unchanged
- Maintain EJS views with @fastify/view
@horner horner force-pushed the feature/fastify-refactor branch from 1086bb2 to 2fa96b7 Compare March 23, 2026 18:09
};
}

return reply.redirect(redirectUrl);

Check warning

Code scanning / CodeQL

Server-side URL redirect Medium

Untrusted URL redirection depends on a
user-provided value
.

Copilot Autofix

AI 1 day ago

In general, the fix is to ensure that any redirect URL influenced by user input is strictly validated or normalized before use. The safest approach is to (a) only allow relative paths within the same application, and/or (b) restrict redirects to a known allow-list of paths, so that arbitrary external URLs can never be used.

For this specific code, the best fix with minimal functional change is to enforce that redirectUrl is both a safe relative URL and not an absolute or protocol-relative URL. We can do this where redirectUrl is computed, just before the redirect, by:

  • Ensuring the value is a string.
  • Rejecting values starting with http:, https:, or // (case-insensitive).
  • Continuing to rely on isSafeRelativeUrl as an additional safeguard.
    If any of these checks fail, we fall back to '/'. This keeps the existing behavior (redirect to the requested page when safe, otherwise to /) while making the validation logic explicit in this file.

Concretely:

  • In create-a-container/routes/login.js, replace the block that sets and validates redirectUrl (lines 283–287) with a more explicit validation that:
    • Normalizes request.body.redirect to a string.
    • Rejects absolute/protocol-relative URLs.
    • Uses isSafeRelativeUrl as a final gate.
      No new imports or helper functions are necessary; we only use built-in string methods and the existing isSafeRelativeUrl import.
Suggested changeset 1
create-a-container/routes/login.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/create-a-container/routes/login.js b/create-a-container/routes/login.js
--- a/create-a-container/routes/login.js
+++ b/create-a-container/routes/login.js
@@ -281,9 +281,25 @@
     request.session.isAdmin = user.groups?.some(group => group.isAdmin) || false;
 
     // Return redirect to original page or default to home
-    let redirectUrl = request.body.redirect || '/';
-    if (!isSafeRelativeUrl(redirectUrl)) {
+    let redirectUrl = request.body.redirect;
+
+    // Ensure redirectUrl is a safe relative URL; otherwise, fall back to home
+    if (typeof redirectUrl !== 'string') {
       redirectUrl = '/';
+    } else {
+      const trimmed = redirectUrl.trim();
+      const lower = trimmed.toLowerCase();
+      // Disallow absolute/protocol-relative URLs
+      if (
+        lower.startsWith('http://') ||
+        lower.startsWith('https://') ||
+        lower.startsWith('//') ||
+        !isSafeRelativeUrl(trimmed)
+      ) {
+        redirectUrl = '/';
+      } else {
+        redirectUrl = trimmed;
+      }
     }
 
     if (request.isApiRequest()) {
EOF
@@ -281,9 +281,25 @@
request.session.isAdmin = user.groups?.some(group => group.isAdmin) || false;

// Return redirect to original page or default to home
let redirectUrl = request.body.redirect || '/';
if (!isSafeRelativeUrl(redirectUrl)) {
let redirectUrl = request.body.redirect;

// Ensure redirectUrl is a safe relative URL; otherwise, fall back to home
if (typeof redirectUrl !== 'string') {
redirectUrl = '/';
} else {
const trimmed = redirectUrl.trim();
const lower = trimmed.toLowerCase();
// Disallow absolute/protocol-relative URLs
if (
lower.startsWith('http://') ||
lower.startsWith('https://') ||
lower.startsWith('//') ||
!isSafeRelativeUrl(trimmed)
) {
redirectUrl = '/';
} else {
redirectUrl = trimmed;
}
}

if (request.isApiRequest()) {
Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant