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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user