feat: add nginx reverse proxy and Let's Encrypt TLS setup

- Add nginx config for SSL termination and HTTP->HTTPS redirect
- Add init-letsencrypt.sh script for automated certificate provisioning
- Update docker-compose.yml: add nginx service, expose web on internal port only
- Fix Evotor OAuth token exchange: move client credentials to form body
- Add request logging for token exchange errors
- Update BASE_URL to https://evosync.ru and set default in docker-compose
- Add refresh_token field to EvotorConnection model

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
mguschin
2026-03-06 16:57:46 +03:00
parent 9aeef73b10
commit bacfd8fe54
7 changed files with 208 additions and 12 deletions

View File

@@ -1,7 +1,10 @@
import secrets
import logging
import httpx
from fastapi import APIRouter, Request, Depends
logger = logging.getLogger(__name__)
from fastapi.responses import RedirectResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session
@@ -85,16 +88,23 @@ async def evotor_callback(
"grant_type": "authorization_code",
"code": code,
"redirect_uri": _redirect_uri(),
"client_id": settings.EVOTOR_CLIENT_ID,
"client_secret": settings.EVOTOR_CLIENT_SECRET,
},
auth=(settings.EVOTOR_CLIENT_ID, settings.EVOTOR_CLIENT_SECRET),
timeout=15,
)
token_response.raise_for_status()
token_data = token_response.json()
except Exception:
except httpx.HTTPStatusError as e:
logger.error("Evotor token exchange HTTP error %s: %s", e.response.status_code, e.response.text)
return RedirectResponse("/evotor?error=token_exchange", 303)
except Exception as e:
logger.error("Evotor token exchange failed: %s", e, exc_info=True)
return RedirectResponse("/evotor?error=token_exchange", 303)
access_token = token_data.get("access_token")
refresh_token = token_data.get("refresh_token")
expires_in = token_data.get("expires_in")
if not access_token:
return RedirectResponse("/evotor?error=no_token", 303)
@@ -118,22 +128,29 @@ async def evotor_callback(
pass # Store info is optional; token is still saved
# Save or update connection
from datetime import datetime
from datetime import datetime, timedelta
now = datetime.utcnow()
token_expires_at = now + timedelta(seconds=expires_in) if expires_in else None
connection = db.query(EvotorConnection).filter(EvotorConnection.user_id == user.id).first()
if connection:
connection.access_token = access_token
connection.refresh_token = refresh_token
connection.token_expires_at = token_expires_at
connection.store_id = store_id
connection.store_name = store_name
connection.is_online = True
connection.last_checked_at = datetime.utcnow()
connection.last_checked_at = now
else:
connection = EvotorConnection(
user_id=user.id,
access_token=access_token,
refresh_token=refresh_token,
token_expires_at=token_expires_at,
store_id=store_id,
store_name=store_name,
is_online=True,
last_checked_at=datetime.utcnow(),
last_checked_at=now,
)
db.add(connection)
db.commit()