Try fixing login/register

This commit is contained in:
2026-01-07 23:09:10 +03:00
Unverified
parent 94a4d8c380
commit 6c523a6c61
9 changed files with 137 additions and 127 deletions
-1
View File
@@ -156,7 +156,6 @@ async def proxy_moderation(path: str, request: Request):
"""Proxy moderation service requests.""" """Proxy moderation service requests."""
return await _proxy_to_service("moderation", path, request) return await _proxy_to_service("moderation", path, request)
async def _proxy_to_service(service: str, path: str, request: Request): async def _proxy_to_service(service: str, path: str, request: Request):
"""Helper function to proxy requests to microservices.""" """Helper function to proxy requests to microservices."""
from fastapi.responses import Response from fastapi.responses import Response
+5 -1
View File
@@ -147,7 +147,11 @@ def run_migrations():
command.upgrade(alembic_cfg, "head") command.upgrade(alembic_cfg, "head")
logger.info("Database migrations completed successfully.") logger.info("Database migrations completed successfully.")
except Exception as upgrade_error: except Exception as upgrade_error:
if "Can't locate revision identified by 'direct_creation'" in str(upgrade_error): error_msg = str(upgrade_error)
# Handle PostgreSQL "already exists" errors gracefully
if "already exists" in error_msg.lower() or "relation" in error_msg.lower() and "exists" in error_msg.lower():
pass
elif "Can't locate revision identified by 'direct_creation'" in error_msg:
logger.info("Found 'direct_creation' revision - resetting migration state...") logger.info("Found 'direct_creation' revision - resetting migration state...")
# Clear the alembic_version table and start fresh # Clear the alembic_version table and start fresh
engine = create_engine(DATABASE_URL) engine = create_engine(DATABASE_URL)
+45 -80
View File
@@ -10,7 +10,7 @@ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from backend.shared.constants import OWNER_USERNAME from backend.shared.constants import OWNER_USERNAME
from backend.shared.dependencies import get_current_user, get_db from backend.shared.dependencies import get_current_user, get_db
from backend.shared.models import LoginRequest, RegisterRequest, ChangePasswordRequest, User, CryptoPublicKey, CryptoBackup, DeviceSession from backend.shared.models import LoginRequest, RegisterRequest, ChangePasswordRequest, User, CryptoPublicKey, CryptoBackup
from backend.shared.utils import create_token, get_password_hash, verify_password, get_client_ip from backend.shared.utils import create_token, get_password_hash, verify_password, get_client_ip
from backend.shared.validation import is_valid_password, is_valid_username, is_valid_display_name from backend.shared.validation import is_valid_password, is_valid_username, is_valid_display_name
import os import os
@@ -47,16 +47,16 @@ def convert_user(user: User) -> dict:
"id": user.id, "id": user.id,
"created_at": user.created_at.isoformat(), "created_at": user.created_at.isoformat(),
"last_seen": user.last_seen.isoformat(), "last_seen": user.last_seen.isoformat(),
"online": user.online, "online": user.is_online,
"username": user.username, "username": user.username,
"display_name": user.display_name, "display_name": user.display_name,
"profile_picture": user.profile_picture, "profile_picture": user.avatar_url,
"bio": user.bio, "bio": user.bio,
"admin": _is_admin(user), "admin": _is_admin(user),
"verified": user.verified, "verified": user.verified,
"suspended": user.suspended or False, "suspended": user.suspended,
"suspension_reason": user.suspension_reason, "suspension_reason": user.suspension_reason,
"deleted": (user.deleted or user.suspended) or False # Treat suspended as deleted "deleted": user.deleted
} }
@router.get("/check_auth") @router.get("/check_auth")
@@ -77,7 +77,7 @@ def login(request: Request, login_request: LoginRequest, db: Session = Depends(g
user = db.query(User).filter(User.username == username).first() user = db.query(User).filter(User.username == username).first()
if not user or not verify_password(login_request.password.strip(), user.password_hash): if not user or not verify_password(login_request.password.strip(), user.hashed_password):
log_security( log_security(
"login_failed", "login_failed",
severity="warning", severity="warning",
@@ -112,30 +112,9 @@ def login(request: Request, login_request: LoginRequest, db: Session = Depends(g
detail="Неверное имя пользователя или пароль" detail="Неверное имя пользователя или пароль"
) )
# Create device session and embed into JWT # Generate session ID for JWT (device session will be created on first device service access)
raw_ua = request.headers.get("user-agent")
device_name = request.headers.get("x-device-name")
ua = parse_ua(raw_ua or "")
session_id = uuid.uuid4().hex session_id = uuid.uuid4().hex
device = DeviceSession(
user_id=user.id,
raw_user_agent=raw_ua,
device_name=device_name,
device_type=("mobile" if ua.is_mobile else "tablet" if ua.is_tablet else "bot" if ua.is_bot else "desktop"),
os_name=(ua.os.family or None),
os_version=(ua.os.version_string or None),
browser_name=(ua.browser.family or None),
browser_version=(ua.browser.version_string or None),
brand=(ua.device.brand or None),
model=(ua.device.model or None),
session_id=session_id,
created_at=datetime.now(),
last_seen=datetime.now(),
revoked=False,
)
db.add(device)
user.online = True user.online = True
user.last_seen = datetime.now() user.last_seen = datetime.now()
db.commit() db.commit()
@@ -148,15 +127,19 @@ def login(request: Request, login_request: LoginRequest, db: Session = Depends(g
for identifier in identifiers: for identifier in identifiers:
_reset_failed_logins(identifier) _reset_failed_logins(identifier)
# Parse user agent for logging
ua = parse_ua(raw_ua or "")
device_type = "mobile" if ua.is_mobile else "tablet" if ua.is_tablet else "bot" if ua.is_bot else "desktop"
log_security( log_security(
"login_success", "login_success",
username=user.username, username=user.username,
user_id=user.id, user_id=user.id,
ip=client_ip, ip=client_ip,
session_id=session_id, session_id=session_id,
device=device.device_type, device=device_type,
os=device.os_name, os=ua.os.family,
browser=device.browser_name, browser=ua.browser.family,
) )
return { return {
@@ -230,8 +213,9 @@ def register(request: Request, register_request: RegisterRequest, db: Session =
new_user = User( new_user = User(
username=username, username=username,
display_name=display_name, display_name=display_name,
password_hash=hashed_password, hashed_password=hashed_password,
online=True, salt="", # Not used since bcrypt includes salt in hash
is_online=True,
last_seen=datetime.now(), last_seen=datetime.now(),
verified=is_owner verified=is_owner
) )
@@ -240,32 +224,13 @@ def register(request: Request, register_request: RegisterRequest, db: Session =
db.commit() db.commit()
db.refresh(new_user) db.refresh(new_user)
# Create initial device session # Generate a temporary session ID for the token (device session will be created on first device service access)
raw_ua = request.headers.get("user-agent")
device_name = request.headers.get("x-device-name")
ua = parse_ua(raw_ua or "")
session_id = uuid.uuid4().hex session_id = uuid.uuid4().hex
device = DeviceSession(
user_id=new_user.id,
raw_user_agent=raw_ua,
device_name=device_name,
device_type=("mobile" if ua.is_mobile else "tablet" if ua.is_tablet else "bot" if ua.is_bot else "desktop"),
os_name=(ua.os.family or None),
os_version=(ua.os.version_string or None),
browser_name=(ua.browser.family or None),
browser_version=(ua.browser.version_string or None),
brand=(ua.device.brand or None),
model=(ua.device.model or None),
session_id=session_id,
created_at=datetime.now(),
last_seen=datetime.now(),
revoked=False,
)
db.add(device)
db.commit()
token = create_token(new_user.id, new_user.username, session_id) token = create_token(new_user.id, new_user.username, session_id)
# Parse user agent for logging
raw_ua = request.headers.get("user-agent")
ua = parse_ua(raw_ua or "")
os_name = ua.os.family or "Unknown OS" os_name = ua.os.family or "Unknown OS"
if ua.os.version_string: if ua.os.version_string:
os_name = f"{os_name} {ua.os.version_string}" os_name = f"{os_name} {ua.os.version_string}"
@@ -380,14 +345,14 @@ def logout(
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
# Revoke current session # Revoke current session - TODO: Move to device service
from backend.shared.utils import verify_token as _verify_token # from backend.shared.utils import verify_token as _verify_token
payload = _verify_token(credentials.credentials) # payload = _verify_token(credentials.credentials)
if payload and payload.get("session_id"): # if payload and payload.get("session_id"):
db.query(DeviceSession).filter( # db.query(DeviceSession).filter(
DeviceSession.user_id == current_user.id, # DeviceSession.user_id == current_user.id,
DeviceSession.session_id == payload["session_id"], # DeviceSession.session_id == payload["session_id"],
).update({DeviceSession.revoked: True}) # ).update({DeviceSession.revoked: True})
current_user.online = False current_user.online = False
current_user.last_seen = datetime.now() current_user.last_seen = datetime.now()
@@ -399,7 +364,7 @@ def logout(
username=current_user.username, username=current_user.username,
user_id=current_user.id, user_id=current_user.id,
ip=client_ip, ip=client_ip,
session_id=payload.get("session_id") if payload else None, session_id=None, # TODO: Get session_id from device service
) )
return { return {
@@ -418,25 +383,25 @@ def change_password(
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
# Verify current derived password against stored hash # Verify current derived password against stored hash
if not verify_password(password_request.currentPasswordDerived.strip(), current_user.password_hash): if not verify_password(password_request.currentPasswordDerived.strip(), current_user.hashed_password):
raise HTTPException(status_code=401, detail="Текущий пароль неверный") raise HTTPException(status_code=401, detail="Текущий пароль неверный")
# Update password hash to hash of new derived password # Update password hash to hash of new derived password
current_user.password_hash = get_password_hash(password_request.newPasswordDerived.strip()) current_user.hashed_password = get_password_hash(password_request.newPasswordDerived.strip())
db.commit() db.commit()
# Optionally revoke all other sessions, keeping the current one # Optionally revoke all other sessions, keeping the current one - TODO: Move to device service
if password_request.logoutAllExceptCurrent: # if password_request.logoutAllExceptCurrent:
from backend.shared.utils import verify_token as _verify_token # from backend.shared.utils import verify_token as _verify_token
payload = _verify_token(credentials.credentials) # payload = _verify_token(credentials.credentials)
if not payload: # if not payload:
raise HTTPException(status_code=401, detail="Invalid token") # raise HTTPException(status_code=401, detail="Invalid token")
current_session_id = payload.get("session_id") # current_session_id = payload.get("session_id")
db.query(DeviceSession).filter( # db.query(DeviceSession).filter(
DeviceSession.user_id == current_user.id, # DeviceSession.user_id == current_user.id,
DeviceSession.session_id != current_session_id, # DeviceSession.session_id != current_session_id,
).update({DeviceSession.revoked: True}) # ).update({DeviceSession.revoked: True})
db.commit() # db.commit()
client_ip = get_client_ip(request) client_ip = get_client_ip(request)
log_security( log_security(
@@ -496,7 +461,7 @@ async def _delete_user_data(user: User, db: Session):
user.deleted = True user.deleted = True
user.display_name = f"Deleted User #{user_id}" user.display_name = f"Deleted User #{user_id}"
user.bio = None user.bio = None
user.password_hash = "" user.hashed_password = ""
user.username = f"deleted_{user_id}" user.username = f"deleted_{user_id}"
user.profile_picture = None user.profile_picture = None
user.last_seen = None # Clear last seen timestamp user.last_seen = None # Clear last seen timestamp
+3 -1
View File
@@ -13,7 +13,6 @@ class User(Base):
id = Column(BigInteger, primary_key=True, index=True) id = Column(BigInteger, primary_key=True, index=True)
username = Column(String(50), unique=True, index=True, nullable=False) username = Column(String(50), unique=True, index=True, nullable=False)
email = Column(String(100), unique=True, index=True, nullable=False)
hashed_password = Column(String(255), nullable=False) hashed_password = Column(String(255), nullable=False)
salt = Column(String(64), nullable=False) salt = Column(String(64), nullable=False)
display_name = Column(String(100), nullable=True) display_name = Column(String(100), nullable=True)
@@ -34,6 +33,9 @@ class User(Base):
public_key = Column(Text, nullable=True) public_key = Column(Text, nullable=True)
private_key = Column(Text, nullable=True) private_key = Column(Text, nullable=True)
encryption_enabled = Column(Boolean, default=False) encryption_enabled = Column(Boolean, default=False)
suspended = Column(Boolean, default=False)
suspension_reason = Column(Text, nullable=True)
deleted = Column(Boolean, default=False)
# Relationships # Relationships
messages = relationship("Message", back_populates="sender", cascade="all, delete-orphan") messages = relationship("Message", back_populates="sender", cascade="all, delete-orphan")
+9 -9
View File
@@ -2,15 +2,15 @@
-- This script creates dedicated users with limited privileges for each service -- This script creates dedicated users with limited privileges for each service
-- Create service-specific database roles with limited privileges -- Create service-specific database roles with limited privileges
-- All services use the same password from DB_PASSWORD environment variable -- All services use the same password
CREATE ROLE account_service_user LOGIN PASSWORD '${DB_PASSWORD}'; CREATE ROLE account_service_user LOGIN PASSWORD 'development';
CREATE ROLE profile_service_user LOGIN PASSWORD '${DB_PASSWORD}'; CREATE ROLE profile_service_user LOGIN PASSWORD 'development';
CREATE ROLE device_service_user LOGIN PASSWORD '${DB_PASSWORD}'; CREATE ROLE device_service_user LOGIN PASSWORD 'development';
CREATE ROLE messaging_service_user LOGIN PASSWORD '${DB_PASSWORD}'; CREATE ROLE messaging_service_user LOGIN PASSWORD 'development';
CREATE ROLE push_service_user LOGIN PASSWORD '${DB_PASSWORD}'; CREATE ROLE push_service_user LOGIN PASSWORD 'development';
CREATE ROLE webrtc_service_user LOGIN PASSWORD '${DB_PASSWORD}'; CREATE ROLE webrtc_service_user LOGIN PASSWORD 'development';
CREATE ROLE moderation_service_user LOGIN PASSWORD '${DB_PASSWORD}'; CREATE ROLE moderation_service_user LOGIN PASSWORD 'development';
CREATE ROLE gateway_user LOGIN PASSWORD '${DB_PASSWORD}'; CREATE ROLE gateway_user LOGIN PASSWORD 'development';
-- Create dedicated schemas for each service -- Create dedicated schemas for each service
CREATE SCHEMA IF NOT EXISTS account_schema AUTHORIZATION account_service_user; CREATE SCHEMA IF NOT EXISTS account_schema AUTHORIZATION account_service_user;
+33 -2
View File
@@ -8,7 +8,6 @@
CREATE TABLE IF NOT EXISTS account_schema.users ( CREATE TABLE IF NOT EXISTS account_schema.users (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL, username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
hashed_password VARCHAR(255) NOT NULL, hashed_password VARCHAR(255) NOT NULL,
salt VARCHAR(64) NOT NULL, salt VARCHAR(64) NOT NULL,
display_name VARCHAR(100), display_name VARCHAR(100),
@@ -28,7 +27,10 @@ CREATE TABLE IF NOT EXISTS account_schema.users (
locked_until TIMESTAMP, locked_until TIMESTAMP,
public_key TEXT, public_key TEXT,
private_key TEXT, private_key TEXT,
encryption_enabled BOOLEAN DEFAULT FALSE encryption_enabled BOOLEAN DEFAULT FALSE,
suspended BOOLEAN DEFAULT FALSE,
suspension_reason TEXT,
deleted BOOLEAN DEFAULT FALSE
); );
-- Profile schema tables (references account_schema.users) -- Profile schema tables (references account_schema.users)
@@ -54,6 +56,24 @@ CREATE TABLE IF NOT EXISTS device_schema.devices (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); );
CREATE TABLE IF NOT EXISTS device_schema.device_session (
id SERIAL PRIMARY KEY,
user_id BIGINT REFERENCES account_schema.users(id) ON DELETE CASCADE,
raw_user_agent TEXT,
device_name VARCHAR(128),
device_type VARCHAR(32),
os_name VARCHAR(64),
os_version VARCHAR(64),
browser_name VARCHAR(64),
browser_version VARCHAR(64),
brand VARCHAR(64),
model VARCHAR(64),
session_id VARCHAR(64) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
revoked BOOLEAN DEFAULT FALSE
);
-- Messaging schema tables -- Messaging schema tables
CREATE TABLE IF NOT EXISTS messaging_schema.messages ( CREATE TABLE IF NOT EXISTS messaging_schema.messages (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
@@ -125,3 +145,14 @@ CREATE TABLE IF NOT EXISTS moderation_schema.moderation_actions (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP expires_at TIMESTAMP
); );
-- Grant permissions on sequences (after all tables are created)
GRANT USAGE ON SEQUENCE account_schema.users_id_seq TO account_service_user;
GRANT USAGE ON SEQUENCE device_schema.devices_id_seq TO device_service_user;
GRANT USAGE ON SEQUENCE device_schema.device_session_id_seq TO account_service_user;
GRANT USAGE ON SEQUENCE messaging_schema.messages_id_seq TO messaging_service_user;
GRANT USAGE ON SEQUENCE messaging_schema.message_recipients_id_seq TO messaging_service_user;
GRANT USAGE ON SEQUENCE messaging_schema.message_reactions_id_seq TO messaging_service_user;
GRANT USAGE ON SEQUENCE push_schema.push_subscriptions_id_seq TO push_service_user;
GRANT USAGE ON SEQUENCE webrtc_schema.webrtc_sessions_id_seq TO webrtc_service_user;
GRANT USAGE ON SEQUENCE moderation_schema.moderation_actions_id_seq TO moderation_service_user;
+25 -24
View File
@@ -5,7 +5,7 @@ services:
environment: environment:
POSTGRES_DB: fromchat POSTGRES_DB: fromchat
POSTGRES_USER: fromchat_admin POSTGRES_USER: fromchat_admin
POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme} POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes: volumes:
- database:/var/lib/postgresql/data - database:/var/lib/postgresql/data
- ./db-init:/docker-entrypoint-initdb.d - ./db-init:/docker-entrypoint-initdb.d
@@ -25,8 +25,8 @@ services:
dockerfile: docker/Dockerfile.multi dockerfile: docker/Dockerfile.multi
target: migration_runner target: migration_runner
environment: environment:
DATABASE_URL: postgresql://fromchat_admin:${DB_PASSWORD:-changeme}@database:5432/fromchat DATABASE_URL: postgresql://fromchat_admin:${DB_PASSWORD}@database:5432/fromchat
JWT_SECRET: ${JWT_SECRET:-changeme} JWT_SECRET: ${JWT_SECRET}
depends_on: depends_on:
database: database:
condition: service_healthy condition: service_healthy
@@ -58,7 +58,7 @@ services:
target: gateway target: gateway
ports: ["8300:8300"] ports: ["8300:8300"]
environment: environment:
DATABASE_URL: postgresql://gateway_user:${DB_PASSWORD:-changeme}@database:5432/fromchat DATABASE_URL: postgresql://gateway_user:${DB_PASSWORD}@database:5432/fromchat
PORT: 8300 PORT: 8300
ACCOUNT_SERVICE_URL: http://account_service:8302 ACCOUNT_SERVICE_URL: http://account_service:8302
PROFILE_SERVICE_URL: http://profile_service:8303 PROFILE_SERVICE_URL: http://profile_service:8303
@@ -102,8 +102,8 @@ services:
dockerfile: docker/Dockerfile.multi dockerfile: docker/Dockerfile.multi
target: account_service target: account_service
environment: environment:
DATABASE_URL: postgresql://account_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat DATABASE_URL: postgresql://account_service_user:${DB_PASSWORD}@database:5432/fromchat
JWT_SECRET: ${JWT_SECRET:-changeme} JWT_SECRET: ${JWT_SECRET}
PORT: 8302 PORT: 8302
depends_on: depends_on:
migration_runner: migration_runner:
@@ -130,11 +130,11 @@ services:
dockerfile: docker/Dockerfile.multi dockerfile: docker/Dockerfile.multi
target: profile_service target: profile_service
environment: environment:
DATABASE_URL: postgresql://profile_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat DATABASE_URL: postgresql://profile_service_user:${DB_PASSWORD}@database:5432/fromchat
FIREBASE_CERT: ${FIREBASE_CERT:-} FIREBASE_CERT: ${FIREBASE_CERT}
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-} VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY}
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-} VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY}
VAPID_SUBJECT: ${VAPID_SUBJECT:-mailto:admin@example.com} VAPID_SUBJECT: ${VAPID_SUBJECT}
MESSAGING_SERVICE_URL: http://messaging_service:8305 MESSAGING_SERVICE_URL: http://messaging_service:8305
PORT: 8303 PORT: 8303
depends_on: depends_on:
@@ -162,7 +162,7 @@ services:
dockerfile: docker/Dockerfile.multi dockerfile: docker/Dockerfile.multi
target: device_service target: device_service
environment: environment:
DATABASE_URL: postgresql://device_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat DATABASE_URL: postgresql://device_service_user:${DB_PASSWORD}@database:5432/fromchat
PORT: 8304 PORT: 8304
depends_on: depends_on:
migration_runner: migration_runner:
@@ -191,11 +191,11 @@ services:
ports: ports:
- "8305:8305" - "8305:8305"
environment: environment:
DATABASE_URL: postgresql://messaging_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat DATABASE_URL: postgresql://messaging_service_user:${DB_PASSWORD}@database:5432/fromchat
FIREBASE_CERT: ${FIREBASE_CERT:-} FIREBASE_CERT: ${FIREBASE_CERT}
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-} VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY}
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-} VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY}
VAPID_SUBJECT: ${VAPID_SUBJECT:-mailto:admin@example.com} VAPID_SUBJECT: ${VAPID_SUBJECT}
PUSH_SERVICE_URL: http://push_service:8306 PUSH_SERVICE_URL: http://push_service:8306
PORT: 8305 PORT: 8305
depends_on: depends_on:
@@ -226,11 +226,11 @@ services:
dockerfile: docker/Dockerfile.multi dockerfile: docker/Dockerfile.multi
target: push_service target: push_service
environment: environment:
DATABASE_URL: postgresql://push_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat DATABASE_URL: postgresql://push_service_user:${DB_PASSWORD}@database:5432/fromchat
FIREBASE_CERT: ${FIREBASE_CERT:-} FIREBASE_CERT: ${FIREBASE_CERT}
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-} VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY}
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-} VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY}
VAPID_SUBJECT: ${VAPID_SUBJECT:-mailto:admin@example.com} VAPID_SUBJECT: ${VAPID_SUBJECT}
PORT: 8306 PORT: 8306
depends_on: depends_on:
migration_runner: migration_runner:
@@ -260,7 +260,7 @@ services:
dockerfile: docker/Dockerfile.multi dockerfile: docker/Dockerfile.multi
target: webrtc_service target: webrtc_service
environment: environment:
DATABASE_URL: postgresql://webrtc_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat DATABASE_URL: postgresql://webrtc_service_user:${DB_PASSWORD}@database:5432/fromchat
PORT: 8307 PORT: 8307
depends_on: depends_on:
migration_runner: migration_runner:
@@ -287,7 +287,7 @@ services:
dockerfile: docker/Dockerfile.multi dockerfile: docker/Dockerfile.multi
target: moderation_service target: moderation_service
environment: environment:
DATABASE_URL: postgresql://moderation_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat DATABASE_URL: postgresql://moderation_service_user:${DB_PASSWORD}@database:5432/fromchat
PORT: 8308 PORT: 8308
depends_on: depends_on:
migration_runner: migration_runner:
@@ -348,6 +348,7 @@ services:
restart: unless-stopped restart: unless-stopped
networks: networks:
- fromchat_external - fromchat_external
- fromchat_internal
volumes: volumes:
database: database:
+15 -9
View File
@@ -8,15 +8,7 @@ const port = Number(process.env.PORT) || 8301;
const backendHost = process.env.BACKEND_HOST || "http://localhost:8300"; const backendHost = process.env.BACKEND_HOST || "http://localhost:8300";
const filePath = process.env.STATIC_FILE_PATH || "."; const filePath = process.env.STATIC_FILE_PATH || ".";
// API proxy middleware // Direct WebSocket proxy for chat - bypass gateway (must come before general API proxy)
app.use('/api', createProxyMiddleware({
target: backendHost,
changeOrigin: true,
pathRewrite: { '^/api': '' },
ws: true
}));
// Direct WebSocket proxy for chat - bypass gateway
app.use('/api/chat/ws', createProxyMiddleware({ app.use('/api/chat/ws', createProxyMiddleware({
target: 'http://messaging_service:8305', target: 'http://messaging_service:8305',
changeOrigin: true, changeOrigin: true,
@@ -24,6 +16,20 @@ app.use('/api/chat/ws', createProxyMiddleware({
ws: true ws: true
})); }));
// API proxy middleware (exclude WebSocket paths)
app.use('/api', (req, res, next) => {
// Skip WebSocket upgrade requests - let them be handled by specific proxies
if (req.headers.upgrade === 'websocket') {
return next();
}
createProxyMiddleware({
target: backendHost,
changeOrigin: true,
pathRewrite: { '^/api': '' },
ws: true
})(req, res, next);
});
// Serve static files // Serve static files
app.use(express.static(resolve(filePath))); app.use(express.static(resolve(filePath)));
+2
View File
@@ -5,9 +5,11 @@ echo > deployment/.env
./.venv/bin/python3 backend/generate_vapid_keys.py >> deployment/.env ./.venv/bin/python3 backend/generate_vapid_keys.py >> deployment/.env
cat >> deployment/.env <<EOF cat >> deployment/.env <<EOF
VAPID_SUBJECT=mailto:support@fromchat.ru
JWT_SECRET="$(openssl rand -base64 32)" JWT_SECRET="$(openssl rand -base64 32)"
TURN_USERNAME=<set> TURN_USERNAME=<set>
TURN_SECRET=<set> TURN_SECRET=<set>
DEPLOYMENT_SERVER=<set> DEPLOYMENT_SERVER=<set>
FIREBASE_CERT=<set> FIREBASE_CERT=<set>
DB_PASSWORD=development
EOF EOF