From 763b754ecd5f36564271ce54fe6c64486c46c398 Mon Sep 17 00:00:00 2001 From: zheng861 Date: Thu, 30 Apr 2026 08:48:54 -0400 Subject: [PATCH 1/4] XSS protections Customize content-security-policy header Verify the origin or referer on state changes, failing if not present --- backend/package-lock.json | 72 +++++++++++++++++++++++--------------- backend/src/index.ts | 12 ++++++- backend/src/server/auth.ts | 38 ++++++++++++++++++-- 3 files changed, 91 insertions(+), 31 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 4a214d54..1956f39b 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -99,7 +99,8 @@ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", "devOptional": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@electric-sql/pglite-socket": { "version": "0.1.1", @@ -124,29 +125,6 @@ "@electric-sql/pglite": "0.4.1" } }, - "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -639,6 +617,7 @@ "node_modules/@octokit/core": { "version": "7.0.6", "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -838,6 +817,7 @@ "node_modules/@opentelemetry/api": { "version": "1.9.0", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -961,6 +941,7 @@ "node_modules/@opentelemetry/core": { "version": "2.4.0", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -3018,6 +2999,7 @@ "node_modules/@prisma/client": { "version": "7.3.0", "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/client-runtime-utils": "7.3.0" }, @@ -3726,6 +3708,29 @@ "node": ">=14.0.0" } }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.0.0-rc.15", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", @@ -3866,6 +3871,7 @@ "version": "5.0.5", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -4080,6 +4086,7 @@ "version": "8.53.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/types": "8.53.1", @@ -4414,6 +4421,7 @@ "node_modules/acorn": { "version": "8.15.0", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5022,8 +5030,7 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", @@ -5284,6 +5291,7 @@ "version": "9.39.2", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5559,6 +5567,7 @@ "node_modules/express": { "version": "5.1.0", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -6236,6 +6245,7 @@ "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -6507,6 +6517,7 @@ "node_modules/jsep": { "version": "1.4.0", "license": "MIT", + "peer": true, "engines": { "node": ">= 10.16.0" } @@ -7736,6 +7747,7 @@ "node_modules/pg": { "version": "8.16.3", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -8007,6 +8019,7 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/config": "7.7.0", "@prisma/dev": "0.24.3", @@ -8450,8 +8463,7 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/semver": { "version": "7.7.3", @@ -8914,6 +8926,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9017,6 +9030,7 @@ "version": "5.9.3", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9155,6 +9169,7 @@ "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -9440,6 +9455,7 @@ "node_modules/ws": { "version": "8.18.2", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/backend/src/index.ts b/backend/src/index.ts index 041c0242..caebab9b 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -37,7 +37,17 @@ app.use( ); // Sets several security-related HTTP headers -app.use(helmet()); +app.use( + helmet({ + contentSecurityPolicy: { + directives: { + "script-src": ["'self'"], // blocks inline + "connect-src": ["'self'"], + "frame-ancestors": ["'none'"], + }, + }, + }), +); app.use(cookieParser()); diff --git a/backend/src/server/auth.ts b/backend/src/server/auth.ts index 30b482ac..609362f7 100644 --- a/backend/src/server/auth.ts +++ b/backend/src/server/auth.ts @@ -1,7 +1,8 @@ import { SpanStatusCode, trace } from "@opentelemetry/api"; -import express from "express"; +import express, { type Request } from "express"; import type { operations } from "../generated/openapi.ts"; import type { AuthenticatedRequest } from "../handlers/index.ts"; +import { env } from "../lib/env.ts"; import { logger } from "../logger.ts"; import { InvalidIDPError, @@ -82,8 +83,14 @@ export const ALLOWED_ANONYMOUS_OPERATIONS: (keyof operations)[] = [ "getTemplates", ]; +const isAllowedAnonymousRoute = (req: Request) => { + return ALLOWED_ANONYMOUS_ROUTES.some( + (path) => req.path === path || req.path.startsWith(`${path}/`), + ); +}; + router.use((req, res, next) => { - if (ALLOWED_ANONYMOUS_ROUTES.some((path) => req.url.startsWith(path))) { + if (isAllowedAnonymousRoute(req)) { next(); return; } @@ -100,4 +107,31 @@ router.use((req, res, next) => { next(); }); +const UNSAFE_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]); +router.use((req, res, next) => { + if (isAllowedAnonymousRoute(req) || !UNSAFE_METHODS.has(req.method)) { + next(); + return; + } + for (const header of [req.get("origin"), req.get("referer")]) { + let source: string | null; + try { + source = new URL(header).origin; + } catch { + source = null; + } + + if (source) { + if (source === env.APP_DOMAIN) { + next(); + return; + } + res.status(401).json({ code: 401, message: "Unauthorized" }); + return; + } + } + res.status(401).json({ code: 401, message: "No origin or referer" }); + return; +}); + export default router; From 77ed9dcf030f6f6daa68c34a220cc8f6e9ad1f03 Mon Sep 17 00:00:00 2001 From: zheng861 Date: Thu, 30 Apr 2026 11:47:22 -0400 Subject: [PATCH 2/4] Create CSRF token per session Transmit in /user/me Send back in X-CSRF-Token --- backend/package-lock.json | 51 +++++++++++-------- backend/src/handlers/getUser.ts | 2 +- backend/src/handlers/index.ts | 1 + backend/src/server/auth.ts | 48 ++++++++++------- backend/src/types.ts | 1 + frontend/src/lib/api.ts | 24 +++++++-- .../src/pages/create-app/CreateAppView.tsx | 36 ++++--------- openapi/openapi.yaml | 3 ++ 8 files changed, 99 insertions(+), 67 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 1956f39b..7dd4ba79 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -99,8 +99,7 @@ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", "devOptional": true, - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/@electric-sql/pglite-socket": { "version": "0.1.1", @@ -125,6 +124,31 @@ "@electric-sql/pglite": "0.4.1" } }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -617,7 +641,6 @@ "node_modules/@octokit/core": { "version": "7.0.6", "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -817,7 +840,6 @@ "node_modules/@opentelemetry/api": { "version": "1.9.0", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -941,7 +963,6 @@ "node_modules/@opentelemetry/core": { "version": "2.4.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -2999,7 +3020,6 @@ "node_modules/@prisma/client": { "version": "7.3.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@prisma/client-runtime-utils": "7.3.0" }, @@ -3871,7 +3891,6 @@ "version": "5.0.5", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -4086,7 +4105,6 @@ "version": "8.53.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/types": "8.53.1", @@ -4421,7 +4439,6 @@ "node_modules/acorn": { "version": "8.15.0", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5030,7 +5047,8 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", @@ -5291,7 +5309,6 @@ "version": "9.39.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5567,7 +5584,6 @@ "node_modules/express": { "version": "5.1.0", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -6245,7 +6261,6 @@ "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -6517,7 +6532,6 @@ "node_modules/jsep": { "version": "1.4.0", "license": "MIT", - "peer": true, "engines": { "node": ">= 10.16.0" } @@ -7747,7 +7761,6 @@ "node_modules/pg": { "version": "8.16.3", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -8019,7 +8032,6 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@prisma/config": "7.7.0", "@prisma/dev": "0.24.3", @@ -8463,7 +8475,8 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "devOptional": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/semver": { "version": "7.7.3", @@ -8926,7 +8939,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -9030,7 +9042,6 @@ "version": "5.9.3", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9169,7 +9180,6 @@ "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -9455,7 +9465,6 @@ "node_modules/ws": { "version": "8.18.2", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/backend/src/handlers/getUser.ts b/backend/src/handlers/getUser.ts index 869d3448..bb29357d 100644 --- a/backend/src/handlers/getUser.ts +++ b/backend/src/handlers/getUser.ts @@ -8,5 +8,5 @@ export const getUserHandler: HandlerMap["getUser"] = async ( res, ) => { const user = await getUserService.getUser(req.user.id); - return json(200, res, user); + return json(200, res, { ...user, csrfToken: req.user.csrfToken }); }; diff --git a/backend/src/handlers/index.ts b/backend/src/handlers/index.ts index e7306984..c2dc1e15 100644 --- a/backend/src/handlers/index.ts +++ b/backend/src/handlers/index.ts @@ -53,6 +53,7 @@ export type AuthenticatedRequest = ExpressRequest & { id: number; email?: string; name?: string; + csrfToken?: string; }; }; diff --git a/backend/src/server/auth.ts b/backend/src/server/auth.ts index 609362f7..925f8856 100644 --- a/backend/src/server/auth.ts +++ b/backend/src/server/auth.ts @@ -1,5 +1,6 @@ import { SpanStatusCode, trace } from "@opentelemetry/api"; import express, { type Request } from "express"; +import crypto from "node:crypto"; import type { operations } from "../generated/openapi.ts"; import type { AuthenticatedRequest } from "../handlers/index.ts"; import { env } from "../lib/env.ts"; @@ -34,10 +35,13 @@ router.get("/oauth_callback", async (req, res) => { req.session.nonce, ); + const csrfToken = crypto.randomBytes(32).toString("hex"); + req.session.user = { id: user.id, name: user.name, email: user.email, + csrfToken, }; return res.redirect("/dashboard"); } catch (err) { @@ -108,30 +112,40 @@ router.use((req, res, next) => { }); const UNSAFE_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]); + +const validateOrigin = (req: Request) => { + for (const value of [req.get("origin"), req.get("referer")]) { + let source: string; + try { + source = new URL(value).origin; + } catch { + continue; + } + + if (source === env.BASE_URL) { + return true; + } + } + return false; +}; + router.use((req, res, next) => { if (isAllowedAnonymousRoute(req) || !UNSAFE_METHODS.has(req.method)) { next(); return; } - for (const header of [req.get("origin"), req.get("referer")]) { - let source: string | null; - try { - source = new URL(header).origin; - } catch { - source = null; - } - if (source) { - if (source === env.APP_DOMAIN) { - next(); - return; - } - res.status(401).json({ code: 401, message: "Unauthorized" }); - return; - } + if (!validateOrigin(req)) { + res.status(401).json({ code: 401, message: "Unauthorized" }); + return; } - res.status(401).json({ code: 401, message: "No origin or referer" }); - return; + + if (req.session["user"].csrfToken !== req.headers["x-csrf-token"]) { + res.status(401).json({ code: 401, message: "Unauthorized" }); + return; + } + + next(); }); export default router; diff --git a/backend/src/types.ts b/backend/src/types.ts index 71081ed0..fe86a956 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -123,6 +123,7 @@ declare module "express-session" { id: number; name: string; email?: string; + csrfToken?: string; }; code_verifier?: string; diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index f9319f06..1cca1f7f 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -10,7 +10,7 @@ import { import createFetchClient from "openapi-fetch"; import createClient from "openapi-react-query"; import { toast } from "sonner"; -import { type paths } from "../generated/openapi"; +import { type components, type paths } from "../generated/openapi"; const acceptJson = new Headers(); acceptJson.set("Accept", "application/json"); @@ -20,8 +20,6 @@ const fetchClient = createFetchClient({ headers: acceptJson, }); -export const api = createClient(fetchClient); - /** * When the user visits one of these pages, they won't be redirected to the sign-in page if they're logged out. */ @@ -60,3 +58,23 @@ export const queryClient = new QueryClient({ queryCache: new QueryCache({ onError }), mutationCache: new MutationCache({ onError }), }); + +const MUTATING_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]); +const USER_ME_QUERY_KEY = ["get", "/user/me", {}] as const; + +fetchClient.use({ + async onRequest({ request }) { + if (MUTATING_METHODS.has(request.method)) { + const user = + queryClient.getQueryData( + USER_ME_QUERY_KEY, + ); + if (user?.csrfToken) { + request.headers.set("X-CSRF-Token", user.csrfToken); + } + } + return request; + }, +}); + +export const api = createClient(fetchClient); diff --git a/frontend/src/pages/create-app/CreateAppView.tsx b/frontend/src/pages/create-app/CreateAppView.tsx index 2c5faa68..f1de4911 100644 --- a/frontend/src/pages/create-app/CreateAppView.tsx +++ b/frontend/src/pages/create-app/CreateAppView.tsx @@ -47,7 +47,7 @@ export default function CreateAppView() { }), }); if (search.has("repoName")) { - initialState.namespace = generateNamespace(initialState); + initialState.workload.namespace = generateNamespace(initialState); } const [appState, setAppState] = useState(initialState); @@ -60,20 +60,12 @@ export default function CreateAppView() { (org) => org.id === groupState.orgId && org.gitProvider !== null, ); - // Form refactor increasingly needed - const [selectedTemplateChartUrls, setSelectedTemplateChartUrls] = useState( - new Set(), - ); - return (
{ e.preventDefault(); - if (selectedTemplateChartUrls.size > 0) { - return; - } const finalGroupState = groupState as Required; const finalAppState = appState as Required; @@ -104,9 +96,9 @@ export default function CreateAppView() {
- setGroupState((prev) => ({ ...prev, orgId: parseInt(orgId) })) - } + onValueChange={(orgId) => { + setGroupState((prev) => ({ ...prev, orgId: parseInt(orgId) })); + }} value={groupState.orgId?.toString()} name="org" > @@ -122,10 +130,22 @@ export default function CreateAppView() { groupState={groupState} state={appState} setState={setAppState} + templateChartSelection={selectedTemplateChartUrls} + setTemplateChartSelection={setSelectedTemplateChartUrls} /> + {selectedTemplateChartUrls.size > 0 && ( +

+ Finish template configuration or clear template selections before + deploying. +

+ )} {shouldShowDeploy ? ( -