пʼятниця, 9 травня 2025 р.

Як правильно виконати редірект після Google OAuth 2.0 у Flask (PythonAnywhere)

✅ Як правильно виконати редірект після Google OAuth 2.0 у Flask (PythonAnywhere)

Цей допис — нагадування самому собі, як реалізувати повернення користувача на потрібну сторінку після авторизації через Google OAuth 2.0, та як уникати помилок, які забирають півдня.

📦 Залежності

  • flask
  • authlib
  • python-dotenv
  • werkzeug.middleware.proxy_fix.ProxyFix

1. Правильна реєстрація Google OAuth


google = oauth.register(
    name='google',
    client_id=GOOGLE_CLIENT_ID,
    client_secret=GOOGLE_CLIENT_SECRET,
    server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
    api_base_url='https://openidconnect.googleapis.com/v1/',
    client_kwargs={'scope': 'openid email profile'}
)

⚠️ Без server_metadata_url буде помилка: Missing "jwks_uri" in metadata
⚠️ Без api_base_url буде помилка: Invalid URL 'userinfo'

2. Установка змінних середовища (.env)


GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
SECRET_KEY=...

У WSGI-файлі обов’язково:


from dotenv import load_dotenv
load_dotenv('/home/your_user/myauth/.env')

3. Налаштування Flask


from flask import Flask
from werkzeug.middleware.proxy_fix import ProxyFix

app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
app.config.update(
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_SAMESITE="None",
    PREFERRED_URL_SCHEME="https",
    SECRET_KEY=os.getenv("SECRET_KEY")
)

4. Обробка авторизації та редіректу


@app.route('/login')
def login():
    redirect_uri = url_for('authorize_route', _external=True)
    return google.authorize_redirect(redirect_uri)

@app.route('/authorize')
def authorize_route():
    try:
        token = google.authorize_access_token()
        userinfo_response = google.get('userinfo')
        userinfo_response.raise_for_status()
        user = userinfo_response.json()
        session['user'] = user
        return redirect(url_for('index'))
    except Exception as e:
        print("❌ Помилка під час авторизації:", e)
        return "Внутрішня помилка сервера", 500

5. Типові помилки й способи вирішення

❌ Помилка ✅ Рішення
Missing "jwks_uri" in metadata Додай server_metadata_url при реєстрації OAuth
Invalid URL 'userinfo': No scheme supplied Додай api_base_url з базовим URL
BuildError: Could not build url for endpoint 'authorize' Використовуй правильну назву функції (наприклад, authorize_route) у url_for()
TemplateNotFound: index.html Створи папку templates/ у тій самій директорії, де й main.py
Internal Server Error після login Додай логування в /authorize, перевір SECRET_KEY та сесію

🔚 Після авторизації

Функція повинна зробити редірект на:

  • url_for('index') — головна сторінка
  • Або будь-яку іншу: redirect(url_for('dashboard'))

📌 Поради

  • Ніколи не використовуй ~ у шляху до `.env` у WSGI — краще os.path.join
  • Завжди додавай ProxyFix у Flask + HTTPS
  • Перевір ідеальну відповідність redirect URI в Google Console
  • Використовуй print() або logging.debug() для налагодження

Налаштування Google OAuth 2.0 у Flask (PythonAnywhere)

Налаштування Google OAuth 2.0 у Flask (PythonAnywhere)

Цей допис — покрокова інструкція для себе, щоб наступного разу налаштування авторизації через Google у Flask не забрало півдня і не викликало MismatchingStateError.

📦 Залежності

  • flask
  • authlib
  • python-dotenv
  • flask-login (опційно)
  • werkzeug.middleware.proxy_fix.ProxyFix

1. Створити файл .env


GOOGLE_CLIENT_ID=тут_твій_id
GOOGLE_CLIENT_SECRET=тут_секрет
SECRET_KEY=фіксований_ключ

2. Налаштування flask_app.py


from flask import Flask
from werkzeug.middleware.proxy_fix import ProxyFix
from dotenv import load_dotenv
import os
from pathlib import Path

project_root = Path(__file__).resolve().parent
load_dotenv(dotenv_path=project_root / '.env')

app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)

app.config.update(
    SECRET_KEY=os.getenv("SECRET_KEY"),
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_SAMESITE="None",
    PREFERRED_URL_SCHEME="https",
    GOOGLE_CLIENT_ID=os.getenv("GOOGLE_CLIENT_ID"),
    GOOGLE_CLIENT_SECRET=os.getenv("GOOGLE_CLIENT_SECRET"),
)

from auth import auth_bp, oauth, login_manager, init_oauth
init_oauth(app)
app.register_blueprint(auth_bp)
oauth.init_app(app)
login_manager.init_app(app)

3. Налаштування auth.py


from flask import Blueprint
from authlib.integrations.flask_client import OAuth
from flask_login import LoginManager

auth_bp = Blueprint('auth', __name__)
oauth = OAuth()
login_manager = LoginManager()

google = None

def init_oauth(app):
    global google
    google = oauth.register(
        name='google',
        client_id=app.config.get("GOOGLE_CLIENT_ID"),
        client_secret=app.config.get("GOOGLE_CLIENT_SECRET"),
        access_token_url='https://oauth2.googleapis.com/token',
        authorize_url='https://accounts.google.com/o/oauth2/auth',
        client_kwargs={
            'scope': 'openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile'
        }
    )

4. Роути /login та /auth/callback


@auth_bp.route('/login')
def login():
    redirect_uri = url_for('auth_callback', _external=True, _scheme="https")
    print(f"[DEBUG] session before redirect: {dict(session)}")
    return google.authorize_redirect(redirect_uri)

@auth_bp.route('/auth/callback')
def auth_callback():
    print(f"[DEBUG] session on callback: {dict(session)}")
    try:
        token = google.authorize_access_token()
        userinfo = google.parse_id_token(token)
        print("[DEBUG] userinfo:", userinfo)
        return redirect(url_for("review"))
    except Exception as e:
        print("[ERROR]", e)
        return "OAuth error"

❌ Типові помилки і як їх уникнути

ПомилкаПричинаРішення
MismatchingStateErrorКука не зберігається або не повертаєтьсяДодати ProxyFix, SESSION_COOKIE_SAMESITE="None", SECURE=True
client_id=Noneclient_id зчитується до load_dotenv()Зберігати client_id у app.config, а не через os.getenv під час імпорту
RuntimeError: working outside of application contextcurrent_app використовується під час імпортуВикликати oauth.register у функції init_oauth(app)
Cookie зникає після редиректуSESSION_COOKIE_SAMESITE неправильнийПоставити SESSION_COOKIE_SAMESITE="None", SECURE=True

🧪 Як перевірити

  • У браузері відкрий DevTools (F12) → Application → Cookies → leox.pythonanywhere.com
  • Перевір, чи є session-cookie
  • У вкладці Network → auth/callback → заголовки запиту → перевір, чи передалась кука

✅ Тестування


print("[DEBUG] session:", dict(session))
print("[DEBUG] redirect_uri:", redirect_uri)

Якщо після логіну все працює — побачите токен, і Flask сесія залишиться живою після редиректу.

---

🧠 Урок: у Flask найважливіше — порядок ініціалізації, контекст і cookie. Якщо ці три речі під контролем — навіть Google OAuth здається логічним.

неділя, 27 жовтня 2024 р.

Очищення фону

Як замінити фон зображення на білий за допомогою OpenCV

Як замінити фон зображення на білий за допомогою OpenCV

У цій статті ми розглянемо, як можна за допомогою Python та бібліотеки OpenCV замінити фон зображення на білий. Це може бути корисним для обробки підписів, логотипів або інших об'єктів, де потрібен чистий білий фон. Нижче наведено код, який можна використовувати для цієї задачі.

Пояснення коду

Функція replace_background_with_white приймає зображення як вхідні дані, виконує обробку та зберігає результат з білим фоном у поточному каталозі. Вона використовує HSV-простір для точнішого виділення кольорів і маскування об'єкта. Цей підхід дозволяє залишити об'єкт видимим, а фон замінити на білий.

Код


import cv2
import numpy as np
import os

def replace_background_with_white(image, output_filename="processed_image.png"):
    """
    Замінює фон зображення на білий і зберігає його в поточному каталозі.

    Параметри:
    image (numpy.ndarray): Вхідне зображення у форматі OpenCV.
    output_filename (str): Назва файлу для збереження обробленого зображення.

    Повертає:
    str: Шлях до збереженого обробленого зображення.
    """
    # Перетворити зображення в HSV для кращого виділення кольорів
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

    # Встановити діапазон для світлих кольорів (білий фон)
    lower_white = np.array([0, 0, 200])
    upper_white = np.array([180, 20, 255])

    # Створити маску для виділення білого фону
    mask = cv2.inRange(hsv, lower_white, upper_white)

    # Інвертувати маску, щоб виділити об'єкт (підпис)
    mask_inv = cv2.bitwise_not(mask)

    # Застосувати маску до оригінального зображення
    result = cv2.bitwise_and(image, image, mask=mask_inv)

    # Створити білий фон
    white_bg = np.full_like(image, 255)  # Білий фон

    # Поєднати об'єкт з білим фоном
    final_image = np.where(result == 0, white_bg, result)

    # Зберегти зображення в поточний каталог
    output_path = os.path.join(os.getcwd(), output_filename)
    cv2.imwrite(output_path, final_image)

    return output_path

# Приклад використання
input_path = "sign.jpg"  # Заміни на шлях до твого файлу

# Завантажити зображення
image = cv2.imread(input_path)

# Виклик функції для обробки зображення та збереження результату
output_path = replace_background_with_white(image, "sign_white_bg.png")

print(f"Зображення збережено за адресою: {output_path}")
Як видно з прикладу, цей код автоматично визначає білі області на зображенні та замінює фон на білий. Ви можете використовувати цю функцію для різних типів зображень, що потребують видалення або заміни фону. Збереження відбувається в поточному каталозі, що робить процес простим і зручним для роботи.

субота, 13 квітня 2024 р.

Доступ до камери

Доступ до відеокамер у Python можна ефективно реалізувати за допомогою бібліотеки OpenCV, яка надає інструменти для захоплення відеопотоків з локальних та зовнішніх джерел. Користуючись класом `VideoCapture`, можна легко інтегрувати функціональність відеозахоплення в програми, що працюють як з веб-камерами, так і з файлами відео. Це дозволить взаємодіяти з відеопотоками в реальному часі, що відкриває можливості для створення програм відеонагляду, відеочатів, аналітики зображень та інших програм, які вимагають відеоаналітики.
 import cv2
import sys

# Ініціалізація змінної для визначення джерела відео
s = 0
# Перевірка чи передані аргументи командного рядка
if len(sys.argv) > 1:
    s = sys.argv[1]  # Використання першого аргументу як шляху до файлу або ідентифікатора камери

# Створення об'єкта VideoCapture для захоплення відео з камери або відтворення файлу
source = cv2.VideoCapture(s)

# Назва вікна, яке буде відображати відео
win_name = 'Camera Preview'
# Створення вікна для відображення відео з можливістю зміни розміру
cv2.namedWindow(win_name, cv2.WINDOW_NORMAL)

# Головний цикл для читання кадрів і відображення їх у вікні
while cv2.waitKey(1) != 27: # Чекати 1 мс і перервати цикл, якщо натиснуто Escape (27)
    has_frame, frame = source.read()  # Зчитування кадру
    if not has_frame:  # Якщо кадри закінчились, перервати цикл
        break
    cv2.imshow(win_name, frame)  # Відображення кадру у вікні

# Звільнення ресурсів та закриття вікон
source.release()
cv2.destroyWindow(win_name)
  

вівторок, 19 вересня 2023 р.

Вилучення метаданих із pdf файлу

Вилучення метаданих із PDF файлу

Метадані в PDF файлах можуть містити інформацію про автора, дату створення, програмне забезпечення, яке було використано для створення файлу, та інші деталі. Інколи може виникнути необхідність видалити або змінити ці метадані.

У цьому дописі ми розглянемо, як використовувати бібліотеку PyPDF2 для вилучення метаданих із PDF файлу.

Встановлення бібліотеки

Для початку потрібно встановити бібліотеку PyPDF2:

pip install PyPDF2
    

Вилучення метаданих

Ось приклад коду для вилучення метаданих із PDF файлу:

import PyPDF2

def remove_metadata(pdf_path, output_path):
    with open(pdf_path, 'rb') as file:
        reader = PyPDF2.PdfReader(file)
        
        # Створення нового PDF-файлу
        writer = PyPDF2.PdfWriter()
        
        # Копіювання всіх сторінок 
        for page_num in range(len(reader.pages)):
            page = reader.pages[page_num]
            writer.add_page(page)

        # Вилучення метаданих
        writer.add_metadata({
            '/Producer': '',
            '/Creator': '',
            '/CreationDate': '',
            '/ModDate': '',
            '/Author': ''
        })

        with open(output_path, 'wb') as output_file:
            writer.write(output_file)
             
    

Після запуску цього коду метадані у вихідному PDF-файлі будуть видалені.

Приклад використання

Для вилучення метаданих із вашого PDF файлу, ви можете використати нашу функцію таким чином:

# Приклад використання
remove_metadata('your_input_file.pdf', 'output_without_metadata.pdf')

Де 'your_input_file.pdf' - це шлях до вашого вхідного файлу, а 'output_without_metadata.pdf' - шлях, за яким буде збережено новий PDF файл без метаданих.

Зміна метаданих PDF файлу

Якщо ви бажаєте змінити деякі метадані, а не видаляти їх повністю, ви можете використати наступну функцію:

def modify_metadata(input_file, output_file, new_metadata):
    # Відкриваємо вхідний файл
    with open(input_file, 'rb') as file:
        reader = PyPDF2.PdfReader(file)
        
        # Створюємо новий PDF файл
        writer = PyPDF2.PdfWriter()
        
        # Додаємо сторінки з вхідного файлу
        for page in reader.pages:
            writer.add_page(page)
            
        # Змінюємо метадані
        writer.add_metadata(new_metadata)
        
        # Зберігаємо новий PDF файл
        with open(output_file, 'wb') as output:
            writer.write(output)

Приклад використання

Для зміни метаданих вашого PDF файлу:


new_data = {
    '/Title': 'Новий заголовок',
    '/Author': 'Новий автор',
    '/Subject': 'Нова тема',
    # ... (інші метадані для зміни) ...
}

# Приклад використання
modify_metadata('your_input_file.pdf', 'output_with_modified_metadata.pdf', new_data)

Де 'your_input_file.pdf' - це шлях до вашого вхідного файлу, 'output_with_modified_metadata.pdf' - шлях, за яким буде збережено новий PDF файл із зміненими метаданими, а new_data - словник з новими метаданими.