create-caspian-app 0.2.0-beta.95 → 0.2.0-beta.97

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-caspian-app",
3
- "version": "0.2.0-beta.95",
3
+ "version": "0.2.0-beta.97",
4
4
  "description": "Scaffold a new Caspian project (FastAPI-powered reactive Python framework).",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -31,8 +31,8 @@
31
31
  "author": "Jefferson Abraham Omier <thesteelninjacode@gmail.com>",
32
32
  "license": "MIT",
33
33
  "devDependencies": {
34
- "@types/node": "^25.6.2",
35
- "eslint": "^10.3.0",
34
+ "@types/node": "^25.8.0",
35
+ "eslint": "^10.4.0",
36
36
  "typescript": "^6.0.3"
37
37
  },
38
38
  "dependencies": {
@@ -1,165 +0,0 @@
1
- import mimetypes
2
- import os
3
- from pathlib import Path
4
- from typing import Any, Optional
5
-
6
- from fastapi import Response
7
- from fastapi.responses import FileResponse
8
-
9
- INSECURE_SESSION_SECRET_VALUES = {"", "change-me", "changeme"}
10
- GOOGLE_FONTS_STYLES_ORIGIN = "https://fonts.googleapis.com"
11
- GOOGLE_FONTS_ASSETS_ORIGIN = "https://fonts.gstatic.com"
12
- GOOGLE_ACCOUNTS_ORIGIN = "https://accounts.google.com"
13
- GOOGLE_OAUTH_API_ORIGIN = "https://oauth2.googleapis.com"
14
-
15
- DEFAULT_CSP_DIRECTIVES: tuple[tuple[str, list[str]], ...] = (
16
- ("default-src", ["'self'"]),
17
- ("base-uri", ["'self'"]),
18
- ("object-src", ["'none'"]),
19
- ("frame-ancestors", ["'self'"]),
20
- ("form-action", ["'self'"]),
21
- ("img-src", ["'self'", "data:", "blob:", GOOGLE_ACCOUNTS_ORIGIN]),
22
- ("font-src", ["'self'", "data:", GOOGLE_FONTS_ASSETS_ORIGIN]),
23
- ("connect-src", ["'self'", GOOGLE_ACCOUNTS_ORIGIN,
24
- GOOGLE_OAUTH_API_ORIGIN]),
25
- ("frame-src", ["'self'", GOOGLE_ACCOUNTS_ORIGIN]),
26
- ("script-src", ["'self'", "'unsafe-inline'", "'unsafe-eval'"]),
27
- ("script-src-elem", ["'self'", "'unsafe-inline'", GOOGLE_ACCOUNTS_ORIGIN]),
28
- ("style-src", ["'self'", "'unsafe-inline'", GOOGLE_FONTS_STYLES_ORIGIN]),
29
- ("style-src-elem", ["'self'", "'unsafe-inline'",
30
- GOOGLE_FONTS_STYLES_ORIGIN]),
31
- )
32
-
33
- # Add project-specific browser-side origins here only when the browser must
34
- # load a third-party resource that is not already covered by the defaults.
35
- # Example:
36
- # PROJECT_CSP_EXTRA_SOURCES = {
37
- # "img-src": ["https://images.unsplash.com"],
38
- # "script-src-elem": ["https://cdn.jsdelivr.net"],
39
- # "connect-src": ["https://api.example.com"],
40
- # }
41
- PROJECT_CSP_EXTRA_SOURCES: dict[str, Any] = {}
42
-
43
-
44
- def resolve_safe_public_path(base_dir: str | Path, relative_path: str) -> Optional[Path]:
45
- base_path = Path(base_dir).resolve()
46
- try:
47
- candidate = (base_path / relative_path).resolve()
48
- candidate.relative_to(base_path)
49
- except (OSError, RuntimeError, ValueError):
50
- return None
51
-
52
- if not candidate.is_file():
53
- return None
54
-
55
- return candidate
56
-
57
-
58
- def public_file_response(
59
- base_dir: str | Path,
60
- relative_path: str,
61
- *,
62
- media_type: Optional[str] = None,
63
- ) -> Response:
64
- file_path = resolve_safe_public_path(base_dir, relative_path)
65
- if file_path is None:
66
- return Response(status_code=404)
67
-
68
- resolved_media_type = media_type
69
- if resolved_media_type is None:
70
- resolved_media_type, _ = mimetypes.guess_type(str(file_path))
71
-
72
- return FileResponse(
73
- file_path,
74
- media_type=resolved_media_type or "application/octet-stream",
75
- )
76
-
77
-
78
- def client_error_message(exc: Exception, *, is_production: bool) -> str:
79
- return str(exc) if not is_production else "An unexpected error occurred."
80
-
81
-
82
- def get_session_secret(*, is_production: bool) -> str:
83
- session_secret = (os.getenv("AUTH_SECRET") or "").strip()
84
- if session_secret and session_secret.lower() not in INSECURE_SESSION_SECRET_VALUES:
85
- return session_secret
86
-
87
- if not is_production:
88
- return "change-me"
89
-
90
- raise RuntimeError(
91
- "AUTH_SECRET must be set to a non-default value when APP_ENV=production."
92
- )
93
-
94
-
95
- def _normalize_csp_sources(value: Any) -> list[str]:
96
- if isinstance(value, str):
97
- return [token.strip() for token in value.replace(",", " ").split() if token.strip()]
98
-
99
- if isinstance(value, (list, tuple, set)):
100
- normalized: list[str] = []
101
- for item in value:
102
- normalized.extend(_normalize_csp_sources(item))
103
- return normalized
104
-
105
- return []
106
-
107
-
108
- def normalize_csp_extra_sources(parsed: Optional[dict[str, Any]]) -> dict[str, list[str]]:
109
- if not isinstance(parsed, dict):
110
- return {}
111
-
112
- normalized: dict[str, list[str]] = {}
113
- for directive, sources in parsed.items():
114
- if not isinstance(directive, str):
115
- continue
116
-
117
- directive_sources = _normalize_csp_sources(sources)
118
- if directive_sources:
119
- normalized[directive.strip()] = directive_sources
120
-
121
- return normalized
122
-
123
-
124
- def _merge_sources(*groups: list[str]) -> list[str]:
125
- merged: list[str] = []
126
- seen: set[str] = set()
127
-
128
- for group in groups:
129
- for source in group:
130
- normalized = source.strip()
131
- if not normalized or normalized in seen:
132
- continue
133
- merged.append(normalized)
134
- seen.add(normalized)
135
-
136
- return merged
137
-
138
-
139
- def build_content_security_policy() -> str:
140
- # PulsePoint currently relies on inline scripts/styles and runtime codegen,
141
- # so this policy narrows source scope without breaking the app runtime.
142
- extra_sources = normalize_csp_extra_sources(PROJECT_CSP_EXTRA_SOURCES)
143
- directives: list[str] = []
144
-
145
- for name, defaults in DEFAULT_CSP_DIRECTIVES:
146
- sources = _merge_sources(
147
- defaults,
148
- extra_sources.get(name, []),
149
- )
150
- directives.append(f"{name} {' '.join(sources)}")
151
-
152
- return "; ".join(directives)
153
-
154
-
155
- def build_security_headers(*, is_production: bool) -> dict[str, str]:
156
- headers = {
157
- "content-security-policy": build_content_security_policy(),
158
- "permissions-policy": "camera=(), geolocation=(), microphone=(), payment=(), usb=()",
159
- "referrer-policy": "strict-origin-when-cross-origin",
160
- "x-content-type-options": "nosniff",
161
- "x-frame-options": "SAMEORIGIN",
162
- }
163
- if is_production:
164
- headers["strict-transport-security"] = "max-age=31536000; includeSubDomains"
165
- return headers