""" Copyright 2024-2026 ChatterMate Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import Optional, Tuple from uuid import UUID import http.cookies from urllib.parse import parse_qs, urlparse from app.core.security import verify_conversation_token from app.core.logger import get_logger from app.database import get_db, SessionLocal from app.models.user import User from sqlalchemy.orm import Session from app.core.cors import is_first_party_origin, origin_of from app.models.widget import Widget from app.repositories.customer import CustomerRepository from app.services.auth_sessions import ACCESS, REFRESH, issue_tokens, live_payload logger = get_logger(__name__) async def refresh_access_token(refresh_token: str, db: Session) -> str | None: """Refresh access token using refresh token""" try: payload = live_payload(db, refresh_token, REFRESH) if not payload: return None user_id = payload.get("sub") org_id = payload.get("org") if not user_id or not org_id: return None # Verify user exists and is active user = db.query(User).filter(User.id == UUID(user_id)).first() if not user or not user.is_active: return None # Generate new access token for the same session (a legacy token # without one gets a new session) access_token, _ = issue_tokens(db, user_id, org_id, sid=payload.get("sid")) db.commit() return access_token except Exception as e: logger.error(f"Token refresh failed: {str(e)}") return None def _is_own_origin(origin: str, host: str) -> bool: """Whether Origin is the API's own origin (the host the request was sent to).""" own = origin_of(origin) return bool(own and host) and own == origin_of(f"{urlparse(origin).scheme}://{host}") def cookie_socket_allowed(environ: dict) -> bool: """Whether a socket handshake may sign in with the dashboard's cookies. Sockets get no CORS check and the cookies are SameSite=None, so a page on another site (an org's signup domain is unverified) could otherwise open the socket as whoever is signed in. socket.io authenticates a namespace with the headers of the first handshake request, so all of it must have come from our own pages: - never a JSONP handshake: a