h4ckath0n 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/bin/cli.js +189 -0
  2. package/lib/scaffold.js +144 -0
  3. package/package.json +38 -0
  4. package/templates/fullstack/README.md +56 -0
  5. package/templates/fullstack/backend/.python-version +1 -0
  6. package/templates/fullstack/backend/app/__init__.py +1 -0
  7. package/templates/fullstack/backend/app/cli.py +98 -0
  8. package/templates/fullstack/backend/app/main.py +7 -0
  9. package/templates/fullstack/backend/app/middleware.py +55 -0
  10. package/templates/fullstack/backend/pyproject.toml +19 -0
  11. package/templates/fullstack/web/eslint.config.js +22 -0
  12. package/templates/fullstack/web/index.html +13 -0
  13. package/templates/fullstack/web/package-lock.json +5133 -0
  14. package/templates/fullstack/web/package.json +42 -0
  15. package/templates/fullstack/web/public/vite.svg +4 -0
  16. package/templates/fullstack/web/src/App.tsx +45 -0
  17. package/templates/fullstack/web/src/auth/AuthContext.tsx +238 -0
  18. package/templates/fullstack/web/src/auth/__tests__/token.test.ts +22 -0
  19. package/templates/fullstack/web/src/auth/__tests__/webauthn.test.ts +44 -0
  20. package/templates/fullstack/web/src/auth/api.ts +63 -0
  21. package/templates/fullstack/web/src/auth/deviceKey.ts +71 -0
  22. package/templates/fullstack/web/src/auth/index.ts +26 -0
  23. package/templates/fullstack/web/src/auth/token.ts +59 -0
  24. package/templates/fullstack/web/src/auth/webauthn.ts +133 -0
  25. package/templates/fullstack/web/src/auth/ws.ts +40 -0
  26. package/templates/fullstack/web/src/components/Alert.tsx +35 -0
  27. package/templates/fullstack/web/src/components/Button.tsx +37 -0
  28. package/templates/fullstack/web/src/components/Card.tsx +22 -0
  29. package/templates/fullstack/web/src/components/Input.tsx +27 -0
  30. package/templates/fullstack/web/src/components/Layout.tsx +88 -0
  31. package/templates/fullstack/web/src/components/ProtectedRoute.tsx +34 -0
  32. package/templates/fullstack/web/src/components/index.ts +6 -0
  33. package/templates/fullstack/web/src/index.css +48 -0
  34. package/templates/fullstack/web/src/main.tsx +28 -0
  35. package/templates/fullstack/web/src/pages/Admin.tsx +43 -0
  36. package/templates/fullstack/web/src/pages/Dashboard.tsx +75 -0
  37. package/templates/fullstack/web/src/pages/Landing.tsx +73 -0
  38. package/templates/fullstack/web/src/pages/Login.tsx +66 -0
  39. package/templates/fullstack/web/src/pages/Register.tsx +80 -0
  40. package/templates/fullstack/web/src/pages/Settings.tsx +172 -0
  41. package/templates/fullstack/web/src/pages/index.ts +6 -0
  42. package/templates/fullstack/web/src/test/setup.ts +1 -0
  43. package/templates/fullstack/web/src/vite-env.d.ts +1 -0
  44. package/templates/fullstack/web/tsconfig.app.json +21 -0
  45. package/templates/fullstack/web/tsconfig.json +7 -0
  46. package/templates/fullstack/web/tsconfig.node.json +18 -0
  47. package/templates/fullstack/web/vite.config.ts +16 -0
  48. package/templates/fullstack/web/vitest.config.ts +9 -0
package/bin/cli.js ADDED
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync } from "node:child_process";
4
+ import { existsSync, readdirSync } from "node:fs";
5
+ import { resolve, join, dirname } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ import { copyDir, dirExists, generateSecret, validateProjectName, writeEnvFiles } from "../lib/scaffold.js";
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Helpers
12
+ // ---------------------------------------------------------------------------
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+
17
+ function printUsage() {
18
+ console.log(`
19
+ Usage: h4ckath0n <project-name> [options]
20
+
21
+ Options:
22
+ --no-install Skip dependency installation (uv sync / npm install)
23
+ --no-git Skip git init
24
+ --no-python Skip Python backend scaffolding
25
+ --no-node Skip Node/React frontend scaffolding
26
+ --db <type> Database type: postgres (default) or sqlite
27
+ -h, --help Show this help message
28
+ `);
29
+ }
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Arg parsing
33
+ // ---------------------------------------------------------------------------
34
+
35
+ function parseArgs(argv) {
36
+ const args = argv.slice(2);
37
+ const opts = {
38
+ name: null,
39
+ install: true,
40
+ git: true,
41
+ python: true,
42
+ node: true,
43
+ db: "postgres",
44
+ help: false,
45
+ };
46
+
47
+ for (let i = 0; i < args.length; i++) {
48
+ const arg = args[i];
49
+ if (arg === "--no-install") {
50
+ opts.install = false;
51
+ } else if (arg === "--no-git") {
52
+ opts.git = false;
53
+ } else if (arg === "--no-python") {
54
+ opts.python = false;
55
+ } else if (arg === "--no-node") {
56
+ opts.node = false;
57
+ } else if (arg === "--db") {
58
+ i++;
59
+ const val = args[i];
60
+ if (val !== "postgres" && val !== "sqlite") {
61
+ console.error(`Error: --db must be "postgres" or "sqlite", got "${val}".`);
62
+ process.exit(1);
63
+ }
64
+ opts.db = val;
65
+ } else if (arg === "-h" || arg === "--help") {
66
+ opts.help = true;
67
+ } else if (arg.startsWith("-")) {
68
+ console.error(`Error: Unknown flag "${arg}". Use --help for usage.`);
69
+ process.exit(1);
70
+ } else if (!opts.name) {
71
+ opts.name = arg;
72
+ } else {
73
+ console.error(`Error: Unexpected argument "${arg}". Only one project name allowed.`);
74
+ process.exit(1);
75
+ }
76
+ }
77
+
78
+ return opts;
79
+ }
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Main
83
+ // ---------------------------------------------------------------------------
84
+
85
+ function main() {
86
+ const opts = parseArgs(process.argv);
87
+
88
+ if (opts.help) {
89
+ printUsage();
90
+ process.exit(0);
91
+ }
92
+
93
+ if (!opts.name) {
94
+ console.error("Error: Please provide a project name.\n");
95
+ printUsage();
96
+ process.exit(1);
97
+ }
98
+
99
+ const validation = validateProjectName(opts.name);
100
+ if (!validation.valid) {
101
+ console.error(`Error: ${validation.reason}`);
102
+ process.exit(1);
103
+ }
104
+
105
+ const projectDir = resolve(process.cwd(), opts.name);
106
+
107
+ if (dirExists(projectDir) && readdirSync(projectDir).length > 0) {
108
+ console.error(`Error: Directory "${opts.name}" already exists and is not empty.`);
109
+ process.exit(1);
110
+ }
111
+
112
+ // Locate templates directory (bundled inside the npm package).
113
+ const templatesDir = join(__dirname, "..", "templates", "fullstack");
114
+ if (!existsSync(templatesDir)) {
115
+ console.error(
116
+ `Error: Templates directory not found at "${templatesDir}".\n` +
117
+ "This is a packaging issue — please report it.",
118
+ );
119
+ process.exit(1);
120
+ }
121
+
122
+ console.log(`\n🚀 Creating project "${opts.name}" in ${projectDir}\n`);
123
+
124
+ // Placeholder replacements applied to every text file in the template.
125
+ const replacements = {
126
+ "{{PROJECT_NAME}}": opts.name,
127
+ };
128
+
129
+ copyDir(templatesDir, projectDir, replacements);
130
+
131
+ // Write .env / .env.example
132
+ writeEnvFiles(projectDir, opts.db, opts.name);
133
+
134
+ console.log("✅ Project files created.");
135
+
136
+ // ---- Optional: git init ----
137
+ if (opts.git) {
138
+ try {
139
+ execSync("git init", { cwd: projectDir, stdio: "ignore" });
140
+ console.log("✅ Initialized git repository.");
141
+ } catch {
142
+ console.warn("⚠️ Could not run git init. Skipping.");
143
+ }
144
+ }
145
+
146
+ // ---- Optional: install deps ----
147
+ if (opts.install) {
148
+ if (opts.python && existsSync(join(projectDir, "backend"))) {
149
+ console.log("📦 Installing Python dependencies (uv sync)...");
150
+ try {
151
+ execSync("uv sync", { cwd: join(projectDir, "backend"), stdio: "inherit" });
152
+ console.log("✅ Python dependencies installed.");
153
+ } catch {
154
+ console.warn("⚠️ uv sync failed. You can run it manually later.");
155
+ }
156
+ }
157
+
158
+ if (opts.node && existsSync(join(projectDir, "web"))) {
159
+ console.log("📦 Installing Node dependencies (npm install)...");
160
+ try {
161
+ execSync("npm install", { cwd: join(projectDir, "web"), stdio: "inherit" });
162
+ console.log("✅ Node dependencies installed.");
163
+ } catch {
164
+ console.warn("⚠️ npm install failed. You can run it manually later.");
165
+ }
166
+ }
167
+ }
168
+
169
+ // ---- Success ----
170
+ console.log(`
171
+ 🎉 Done! Your project is ready.
172
+
173
+ Next steps:
174
+
175
+ cd ${opts.name}
176
+
177
+ # Start the backend
178
+ cd backend && uv run uvicorn app.main:app --reload
179
+
180
+ # Start the frontend (in another terminal)
181
+ cd web && npm run dev
182
+
183
+ # Open http://localhost:5173
184
+
185
+ Happy hacking! 🏴‍☠️
186
+ `);
187
+ }
188
+
189
+ main();
@@ -0,0 +1,144 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+
5
+ /**
6
+ * Validate that a project name contains only alphanumeric chars, hyphens,
7
+ * and underscores, starts with a letter or underscore, and is 1-214 chars.
8
+ * @param {string} name
9
+ * @returns {{ valid: boolean, reason?: string }}
10
+ */
11
+ export function validateProjectName(name) {
12
+ if (!name) {
13
+ return { valid: false, reason: "Project name is required." };
14
+ }
15
+ if (name.length > 214) {
16
+ return { valid: false, reason: "Project name must be 214 characters or fewer." };
17
+ }
18
+ if (!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(name)) {
19
+ return {
20
+ valid: false,
21
+ reason:
22
+ "Project name must start with a letter or underscore and contain only alphanumeric characters, hyphens, and underscores.",
23
+ };
24
+ }
25
+ return { valid: true };
26
+ }
27
+
28
+ /**
29
+ * Generate a cryptographically random hex secret.
30
+ * @param {number} [bytes=32]
31
+ * @returns {string}
32
+ */
33
+ export function generateSecret(bytes = 32) {
34
+ return randomBytes(bytes).toString("hex");
35
+ }
36
+
37
+ // Binary file extensions that should be copied without placeholder replacement.
38
+ const BINARY_EXTENSIONS = new Set([
39
+ ".png", ".jpg", ".jpeg", ".gif", ".ico", ".svg",
40
+ ".woff", ".woff2", ".ttf", ".eot",
41
+ ".zip", ".gz", ".tar", ".bz2",
42
+ ".pdf", ".mp3", ".mp4", ".webm",
43
+ ".wasm", ".bin",
44
+ ]);
45
+
46
+ /**
47
+ * Recursively copy a directory, replacing placeholder strings in text files.
48
+ * @param {string} src - source directory
49
+ * @param {string} dest - destination directory
50
+ * @param {Record<string, string>} replacements - map of placeholder -> value
51
+ */
52
+ export function copyDir(src, dest, replacements) {
53
+ mkdirSync(dest, { recursive: true });
54
+
55
+ for (const entry of readdirSync(src)) {
56
+ const srcPath = join(src, entry);
57
+ const destPath = join(dest, entry);
58
+ const stat = statSync(srcPath);
59
+
60
+ if (stat.isDirectory()) {
61
+ copyDir(srcPath, destPath, replacements);
62
+ } else {
63
+ const dotIdx = entry.lastIndexOf(".");
64
+ const ext = dotIdx > 0 ? entry.slice(dotIdx).toLowerCase() : "";
65
+ if (BINARY_EXTENSIONS.has(ext)) {
66
+ writeFileSync(destPath, readFileSync(srcPath));
67
+ } else {
68
+ let content = readFileSync(srcPath, "utf8");
69
+ for (const [placeholder, value] of Object.entries(replacements)) {
70
+ content = content.replaceAll(placeholder, value);
71
+ }
72
+ writeFileSync(destPath, content, "utf8");
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Build the contents of a .env file.
80
+ * @param {"postgres" | "sqlite"} dbType
81
+ * @param {string} projectName
82
+ * @returns {string}
83
+ */
84
+ function buildEnvContent(dbType, projectName) {
85
+ const signingKey = generateSecret();
86
+ const dbUrl =
87
+ dbType === "sqlite"
88
+ ? `sqlite+aiosqlite:///./data/${projectName}.db`
89
+ : `postgresql+psycopg://postgres:postgres@localhost:5432/${projectName}`;
90
+
91
+ return [
92
+ "# h4ckath0n environment",
93
+ "H4CKATH0N_ENV=development",
94
+ `H4CKATH0N_DATABASE_URL=${dbUrl}`,
95
+ `H4CKATH0N_AUTH_SIGNING_KEY=${signingKey}`,
96
+ "H4CKATH0N_RP_ID=localhost",
97
+ "H4CKATH0N_ORIGIN=http://localhost:5173",
98
+ "VITE_API_BASE_URL=/api",
99
+ "",
100
+ ].join("\n");
101
+ }
102
+
103
+ /**
104
+ * Build the contents of a .env.example file.
105
+ * @param {"postgres" | "sqlite"} dbType
106
+ * @returns {string}
107
+ */
108
+ function buildEnvExampleContent(dbType) {
109
+ const dbPlaceholder =
110
+ dbType === "sqlite"
111
+ ? "sqlite+aiosqlite:///./data/myproject.db"
112
+ : "postgresql+psycopg://postgres:postgres@localhost:5432/myproject";
113
+
114
+ return [
115
+ "# h4ckath0n environment",
116
+ "H4CKATH0N_ENV=development",
117
+ `H4CKATH0N_DATABASE_URL=${dbPlaceholder}`,
118
+ "H4CKATH0N_AUTH_SIGNING_KEY=<random-hex-secret>",
119
+ "H4CKATH0N_RP_ID=localhost",
120
+ "H4CKATH0N_ORIGIN=http://localhost:5173",
121
+ "VITE_API_BASE_URL=/api",
122
+ "",
123
+ ].join("\n");
124
+ }
125
+
126
+ /**
127
+ * Write .env and .env.example into the destination directory.
128
+ * @param {string} dest - project root directory
129
+ * @param {"postgres" | "sqlite"} dbType
130
+ * @param {string} projectName
131
+ */
132
+ export function writeEnvFiles(dest, dbType, projectName) {
133
+ writeFileSync(join(dest, ".env"), buildEnvContent(dbType, projectName), "utf8");
134
+ writeFileSync(join(dest, ".env.example"), buildEnvExampleContent(dbType), "utf8");
135
+ }
136
+
137
+ /**
138
+ * Check whether a directory already exists and is non-empty.
139
+ * @param {string} dir
140
+ * @returns {boolean}
141
+ */
142
+ export function dirExists(dir) {
143
+ return existsSync(dir) && statSync(dir).isDirectory();
144
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "h4ckath0n",
3
+ "version": "0.1.0",
4
+ "description": "Create a full-stack hackathon project with passkeys and strong defaults",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "h4ckath0n": "bin/cli.js"
9
+ },
10
+ "files": [
11
+ "bin/",
12
+ "lib/",
13
+ "templates/"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/BTreeMap/h4ckath0n.git",
18
+ "directory": "packages/create-h4ckath0n"
19
+ },
20
+ "homepage": "https://github.com/BTreeMap/h4ckath0n",
21
+ "bugs": {
22
+ "url": "https://github.com/BTreeMap/h4ckath0n/issues"
23
+ },
24
+ "keywords": [
25
+ "hackathon",
26
+ "fullstack",
27
+ "passkeys",
28
+ "webauthn",
29
+ "react",
30
+ "fastapi"
31
+ ],
32
+ "engines": {
33
+ "node": ">=18"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ }
38
+ }
@@ -0,0 +1,56 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ Full-stack hackathon project scaffolded with [h4ckath0n](https://github.com/BTreeMap/h4ckath0n).
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ # Start both backend and frontend
9
+ cd backend
10
+ uv run h4ckath0n dev
11
+ ```
12
+
13
+ Backend runs at http://localhost:8000, frontend at http://localhost:5173.
14
+
15
+ ## Structure
16
+
17
+ ```
18
+ {{PROJECT_NAME}}/
19
+ backend/ Python (FastAPI + h4ckath0n library)
20
+ web/ React + Vite + TypeScript + Tailwind v4
21
+ .env Environment variables (gitignored)
22
+ ```
23
+
24
+ ## Auth model
25
+
26
+ - Passkeys (WebAuthn) for registration and login
27
+ - Each device gets a P-256 keypair (private key non-extractable, stored in IndexedDB)
28
+ - API requests use short-lived JWTs (15 min) signed by the device key
29
+ - Server verifies JWT signature and enforces RBAC from the database
30
+ - No privilege claims in the JWT; all roles/scopes are server-derived
31
+
32
+ ## Development
33
+
34
+ ### Backend
35
+
36
+ ```bash
37
+ cd backend
38
+ uv sync
39
+ uv run uvicorn app.main:app --reload
40
+ ```
41
+
42
+ ### Frontend
43
+
44
+ ```bash
45
+ cd web
46
+ npm install
47
+ npm run dev
48
+ ```
49
+
50
+ ### Environment
51
+
52
+ Copy `.env.example` to `.env` and set values as needed. See the h4ckath0n docs for all configuration options.
53
+
54
+ ## License
55
+
56
+ MIT
@@ -0,0 +1 @@
1
+ """Backend application package."""
@@ -0,0 +1,98 @@
1
+ """CLI for the scaffolded h4ckath0n project."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import subprocess
7
+ import sys
8
+
9
+
10
+ def main() -> None:
11
+ """Entry point for the h4ckath0n CLI."""
12
+ if len(sys.argv) < 2:
13
+ _print_help()
14
+ return
15
+
16
+ command = sys.argv[1]
17
+ handlers = {
18
+ "dev": _cmd_dev,
19
+ "help": _print_help,
20
+ }
21
+
22
+ handler = handlers.get(command)
23
+ if handler:
24
+ handler()
25
+ else:
26
+ print(f"Unknown command: {command}")
27
+ _print_help()
28
+ sys.exit(1)
29
+
30
+
31
+ def _cmd_dev() -> None:
32
+ """Run backend and frontend dev servers concurrently."""
33
+ project_root = _find_project_root()
34
+ backend_dir = os.path.join(project_root, "backend")
35
+ web_dir = os.path.join(project_root, "web")
36
+
37
+ print("Starting h4ckath0n dev servers...")
38
+ print(f" Backend: http://localhost:8000 (from {backend_dir})")
39
+ print(f" Frontend: http://localhost:5173 (from {web_dir})")
40
+ print()
41
+
42
+ processes = []
43
+ try:
44
+ # Start backend
45
+ backend_proc = subprocess.Popen(
46
+ [sys.executable, "-m", "uvicorn", "app.main:app", "--reload", "--port", "8000"],
47
+ cwd=backend_dir,
48
+ )
49
+ processes.append(backend_proc)
50
+
51
+ # Start frontend
52
+ npm_cmd = "npm.cmd" if sys.platform == "win32" else "npm"
53
+ frontend_proc = subprocess.Popen(
54
+ [npm_cmd, "run", "dev"],
55
+ cwd=web_dir,
56
+ )
57
+ processes.append(frontend_proc)
58
+
59
+ # Wait for any process to exit
60
+ for proc in processes:
61
+ proc.wait()
62
+ except KeyboardInterrupt:
63
+ print("\nShutting down...")
64
+ finally:
65
+ for proc in processes:
66
+ try:
67
+ proc.terminate()
68
+ proc.wait(timeout=5)
69
+ except (subprocess.TimeoutExpired, OSError):
70
+ proc.kill()
71
+
72
+
73
+ def _find_project_root() -> str:
74
+ """Find the project root by looking for backend/ and web/ directories."""
75
+ # Start from the current working directory
76
+ cwd = os.getcwd()
77
+ if os.path.isdir(os.path.join(cwd, "backend")) and os.path.isdir(os.path.join(cwd, "web")):
78
+ return cwd
79
+ # Try parent of backend/
80
+ parent = os.path.dirname(cwd)
81
+ if os.path.isdir(os.path.join(parent, "backend")) and os.path.isdir(
82
+ os.path.join(parent, "web")
83
+ ):
84
+ return parent
85
+ # Default to cwd
86
+ return cwd
87
+
88
+
89
+ def _print_help() -> None:
90
+ """Print CLI help."""
91
+ print("h4ckath0n backend CLI")
92
+ print()
93
+ print("Usage: uv run h4ckath0n <command>")
94
+ print(" (run from the backend/ directory of your scaffolded project)")
95
+ print()
96
+ print("Commands:")
97
+ print(" dev Start backend and frontend dev servers")
98
+ print(" help Show this help message")
@@ -0,0 +1,7 @@
1
+ """Application entry point."""
2
+
3
+ from app.middleware import add_csp_middleware
4
+ from h4ckath0n import create_app
5
+
6
+ app = create_app()
7
+ add_csp_middleware(app)
@@ -0,0 +1,55 @@
1
+ """Security middleware: CSP headers and device JWT verification."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+ from fastapi import FastAPI
8
+ from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
9
+ from starlette.requests import Request
10
+ from starlette.responses import Response
11
+
12
+ ENV_VAR = "H4CKATH0N_ENV"
13
+
14
+
15
+ def add_csp_middleware(app: FastAPI) -> None:
16
+ """Add Content-Security-Policy middleware to the FastAPI app."""
17
+ app.add_middleware(CSPMiddleware)
18
+
19
+
20
+ class CSPMiddleware(BaseHTTPMiddleware):
21
+ """Set Content-Security-Policy headers based on environment."""
22
+
23
+ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
24
+ response = await call_next(request)
25
+ env = os.getenv(ENV_VAR, "development")
26
+ if env == "production":
27
+ csp = (
28
+ "default-src 'self'; "
29
+ "script-src 'self'; "
30
+ "style-src 'self'; "
31
+ "img-src 'self' data:; "
32
+ "font-src 'self'; "
33
+ "connect-src 'self'; "
34
+ "frame-ancestors 'none'; "
35
+ "base-uri 'self'; "
36
+ "form-action 'self'"
37
+ )
38
+ else:
39
+ # Development: allow Vite dev server
40
+ csp = (
41
+ "default-src 'self' http://localhost:*; "
42
+ "script-src 'self' http://localhost:*; "
43
+ "style-src 'self' 'unsafe-inline' http://localhost:*; "
44
+ "img-src 'self' data: http://localhost:*; "
45
+ "font-src 'self' http://localhost:*; "
46
+ "connect-src 'self' http://localhost:* ws://localhost:*; "
47
+ "frame-ancestors 'none'; "
48
+ "base-uri 'self'; "
49
+ "form-action 'self'"
50
+ )
51
+ response.headers["Content-Security-Policy"] = csp
52
+ response.headers["X-Content-Type-Options"] = "nosniff"
53
+ response.headers["X-Frame-Options"] = "DENY"
54
+ response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
55
+ return response
@@ -0,0 +1,19 @@
1
+ [project]
2
+ name = "{{PROJECT_NAME}}-backend"
3
+ version = "0.1.0"
4
+ description = "Backend for {{PROJECT_NAME}}"
5
+ requires-python = ">=3.11"
6
+ dependencies = [
7
+ "h4ckath0n>=0.1.0",
8
+ "cryptography>=44.0",
9
+ ]
10
+
11
+ [project.scripts]
12
+ h4ckath0n = "app.cli:main"
13
+
14
+ [build-system]
15
+ requires = ["hatchling"]
16
+ build-backend = "hatchling.build"
17
+
18
+ [tool.hatch.build.targets.wheel]
19
+ packages = ["app"]
@@ -0,0 +1,22 @@
1
+ import js from "@eslint/js";
2
+ import globals from "globals";
3
+ import reactHooks from "eslint-plugin-react-hooks";
4
+ import tseslint from "typescript-eslint";
5
+
6
+ export default tseslint.config(
7
+ { ignores: ["dist"] },
8
+ {
9
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
10
+ files: ["**/*.{ts,tsx}"],
11
+ languageOptions: {
12
+ ecmaVersion: 2020,
13
+ globals: globals.browser,
14
+ },
15
+ plugins: {
16
+ "react-hooks": reactHooks,
17
+ },
18
+ rules: {
19
+ ...reactHooks.configs.recommended.rules,
20
+ },
21
+ }
22
+ );
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>{{PROJECT_NAME}}</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>