From 991ef5dd0c30a027c5f52a5c17a5e99a928a30fb Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Sun, 21 Sep 2025 17:48:36 +0300 Subject: [PATCH] Improve security --- backend/generate_vapid_keys.py | 42 ++++--------------- backend/push_service.py | 11 +++-- deployment/.env.example | 1 - .../ui/components/settings/SettingsDialog.tsx | 2 +- frontend/src/ui/screen/LoginScreen.tsx | 2 +- frontend/src/ui/state.ts | 2 +- ...notifications.ts => push-notifications.ts} | 0 package.json | 5 ++- 8 files changed, 20 insertions(+), 45 deletions(-) delete mode 100644 deployment/.env.example rename frontend/src/utils/{notifications.ts => push-notifications.ts} (100%) diff --git a/backend/generate_vapid_keys.py b/backend/generate_vapid_keys.py index 8e3a074..3fc101f 100644 --- a/backend/generate_vapid_keys.py +++ b/backend/generate_vapid_keys.py @@ -4,35 +4,18 @@ Generate VAPID keys for push notifications Run this script to generate new VAPID keys for your application """ -from pywebpush import WebPushException +import sys import base64 -import json +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.backends import default_backend def generate_vapid_keys(): """Generate VAPID keys for push notifications""" try: - from cryptography.hazmat.primitives import serialization - from cryptography.hazmat.primitives.asymmetric import ec - from cryptography.hazmat.backends import default_backend - - # Generate private key private_key = ec.generate_private_key(ec.SECP256R1(), default_backend()) - - # Get public key public_key = private_key.public_key() - # Serialize keys - private_pem = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption() - ) - - public_pem = public_key.public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo - ) - # Convert to base64 for web push private_key_b64 = base64.urlsafe_b64encode( private_key.private_bytes( @@ -50,23 +33,12 @@ def generate_vapid_keys(): public_key_b64 = base64.urlsafe_b64encode(public_key_raw).decode('utf-8').rstrip('=') - print("VAPID Keys Generated:") - print("=" * 50) - print(f"Private Key: {private_key_b64}") - print(f"Public Key: {public_key_b64}") - print("=" * 50) - print("\nAdd these to your environment variables:") - print(f"VAPID_PRIVATE_KEY={private_key_b64}") - print(f"VAPID_PUBLIC_KEY={public_key_b64}") + print(f"VAPID_PRIVATE_KEY=\"{private_key_b64}\"") + print(f"VAPID_PUBLIC_KEY=\"{public_key_b64}\"") return private_key_b64, public_key_b64 - - except ImportError: - print("Error: cryptography library not found.") - print("Install it with: pip install cryptography") - return None, None except Exception as e: - print(f"Error generating VAPID keys: {e}") + print(f"Error generating VAPID keys: {e}", file=sys.stderr) return None, None if __name__ == "__main__": diff --git a/backend/push_service.py b/backend/push_service.py index d8b9b1c..ee48ded 100644 --- a/backend/push_service.py +++ b/backend/push_service.py @@ -10,11 +10,14 @@ logger = logging.getLogger("uvicorn.error") class PushNotificationService: def __init__(self): - # VAPID keys - load from environment variables - self.vapid_private_key = os.getenv("VAPID_PRIVATE_KEY", "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQghg2CSKiq0KsXXXImE75Z8UAphGBjkpYjUE87zPBmGqKhRANCAATxbNBGMhNl6gLmPL0PAf2YIJCVYX_TZrSqkj7SCqsu5VNMhnDOan6Qc9hEkcTZgvwj286C24SnxfH5CghVMCI6") - self.vapid_public_key = os.getenv("VAPID_PUBLIC_KEY", "BPFs0EYyE2XqAuY8vQ8B_ZggkJVhf9NmtKqSPtIKqy7lU0yGcM5qfpBz2ESRxNmC_CPbzoLbhKfF8fkKCFUwIjo") + self.vapid_private_key = os.getenv("VAPID_PRIVATE_KEY") + self.vapid_public_key = os.getenv("VAPID_PUBLIC_KEY") + + if (not self.vapid_public_key) or (not self.vapid_private_key): + raise ValueError("VAPID public or private key is None") + self.vapid_claims = { - "sub": "mailto:admin@fromchat.com", + "sub": "mailto:support@fromchat.ru", "aud": "https://fcm.googleapis.com" } diff --git a/deployment/.env.example b/deployment/.env.example deleted file mode 100644 index 63e97f8..0000000 --- a/deployment/.env.example +++ /dev/null @@ -1 +0,0 @@ -JWT_SECRET="jwt-secret-change-in-production" \ No newline at end of file diff --git a/frontend/src/ui/components/settings/SettingsDialog.tsx b/frontend/src/ui/components/settings/SettingsDialog.tsx index 64471bc..14b1ad5 100644 --- a/frontend/src/ui/components/settings/SettingsDialog.tsx +++ b/frontend/src/ui/components/settings/SettingsDialog.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; import { PRODUCT_NAME, API_BASE_URL } from "../../../core/config"; import type { DialogProps } from "../../../core/types"; import { MaterialDialog } from "../core/Dialog"; -import { initialize, isSupported, startElectronReceiver, stopElectronReceiver, subscribe, unsubscribe } from "../../../utils/notifications"; +import { initialize, isSupported, startElectronReceiver, stopElectronReceiver, subscribe, unsubscribe } from "../../../utils/push-notifications"; import { isElectron } from "../../../electron/electron"; import { useAppState } from "../../state"; import type { Switch } from "mdui/components/switch"; diff --git a/frontend/src/ui/screen/LoginScreen.tsx b/frontend/src/ui/screen/LoginScreen.tsx index 40ba22b..06df52f 100644 --- a/frontend/src/ui/screen/LoginScreen.tsx +++ b/frontend/src/ui/screen/LoginScreen.tsx @@ -8,7 +8,7 @@ import { useRef } from "react"; import type { TextField } from "mdui/components/text-field"; import { useAppState } from "../state"; import { MaterialTextField } from "../components/core/TextField"; -import { initialize, isSupported, startElectronReceiver, subscribe } from "../../utils/notifications"; +import { initialize, isSupported, startElectronReceiver, subscribe } from "../../utils/push-notifications"; import { isElectron } from "../../electron/electron"; export default function LoginScreen() { diff --git a/frontend/src/ui/state.ts b/frontend/src/ui/state.ts index 21e6594..27a8219 100644 --- a/frontend/src/ui/state.ts +++ b/frontend/src/ui/state.ts @@ -7,7 +7,7 @@ import { DMPanel, type DMPanelData } from "./panels/DMPanel"; import { getAuthHeaders } from "../auth/api"; import { restoreKeys } from "../auth/crypto"; import { API_BASE_URL } from "../core/config"; -import { initialize, subscribe, startElectronReceiver, isSupported } from "../utils/notifications"; +import { initialize, subscribe, startElectronReceiver, isSupported } from "../utils/push-notifications"; import { isElectron } from "../electron/electron"; type Page = "login" | "register" | "chat" diff --git a/frontend/src/utils/notifications.ts b/frontend/src/utils/push-notifications.ts similarity index 100% rename from frontend/src/utils/notifications.ts rename to frontend/src/utils/push-notifications.ts diff --git a/package.json b/package.json index f145564..17d7c44 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,9 @@ "preview": "cd deployment && docker compose up --build --watch", "preview:clean": "cd deployment && docker compose down -v", "clean": "npm run backend:clean && npm run frontend:clean && npm run preview:clean", - "install": "npm run backend:dependencies && cp deployment/.env.example deployment/.env", - "prepare": "husky" + "install": "npm run backend:dependencies && npm run generate:env", + "prepare": "husky", + "generate:env": "echo \"JWT_SECRET=\\\"$(openssl rand -base64 32)\\\"\" > deployment/.env && ./.venv/bin/python3 backend/generate_vapid_keys.py 1>>deployment/.env" }, "files": [ "frontend/build/electron"