Skip to content

ТЗ: OAuth redirect server и upload-инфраструктура для YouTube/TikTok #32

Description

@LimiNode

ТЗ: OAuth redirect server и upload-инфраструктура для YouTube/TikTok

Цель

Добавить в kurlyk инфраструктуру для авторизации пользователя через OAuth2 Authorization Code + PKCE и последующей загрузки видео через API платформ вроде YouTube и TikTok.

Текущий OAuthPkceClient уже покрывает построение authorization URL, обмен code на token, refresh token и Bearer auth. Следующий слой должен добавить:

  • локальный redirect server на базе Simple-Web-Server;
  • хранение и обновление OAuth token;
  • refreshable Bearer provider;
  • базовый resumable/chunked upload helper;
  • платформенные примеры для YouTube/TikTok.

YouTube upload требует OAuth 2.0 и scope https://www.googleapis.com/auth/youtube.upload, а официальный пример YouTube использует resumable upload и exponential backoff для retry. ([Google for Developers]1) TikTok Content Posting API использует user access token и Direct Post flow с Bearer authorization; для Direct Post нужен scope video.publish. ([TikTok для разработчиков]2)


Scope

Входит в MVP

  1. OAuthRedirectServer на базе Simple-Web-Server.

  2. OAuthRedirectResult для результата callback.

  3. OAuthTokenStorage / file-based token storage.

  4. RefreshableBearerAuthProvider.

  5. Общий retry helper с exponential backoff.

  6. Базовый resumable/chunked upload helper.

  7. Примеры:

    • YouTube OAuth login + token save.
    • YouTube resumable upload skeleton.
    • TikTok OAuth login + token save.
    • TikTok upload skeleton.
  8. Tests:

    • redirect server получает code и state;
    • redirect server обрабатывает error;
    • token storage сохраняет/читает token;
    • refreshable provider обновляет expired token;
    • retry policy считает delay и retryable statuses.

Не входит в MVP

  • Полноценный YouTube/TikTok SDK.
  • UI для авторизации.
  • Шифрованное хранилище токенов.
  • OpenID Connect validation.
  • JWT/JWKS verification.
  • Device Code Flow.
  • Client Credentials Flow.
  • Service Account JWT flow.
  • Автопостинг без участия пользователя.
  • Обход ограничений платформ.

Архитектура

Новые файлы

include/kurlyk/http/auth/OAuthRedirectServer.hpp
include/kurlyk/http/auth/data/OAuthRedirectConfig.hpp
include/kurlyk/http/auth/data/OAuthRedirectResult.hpp

include/kurlyk/http/auth/storage/FileTokenStorage.hpp

include/kurlyk/http/auth/RefreshableBearerAuthProvider.hpp

include/kurlyk/http/upload/UploadRetryPolicy.hpp
include/kurlyk/http/upload/ResumableUploadClient.hpp
include/kurlyk/http/upload/ChunkedUploadClient.hpp

guides/oauth-redirect-server.md
guides/video-upload.md

examples/youtube_oauth_redirect_example.cpp
examples/youtube_resumable_upload_example.cpp
examples/tiktok_oauth_redirect_example.cpp
examples/tiktok_chunked_upload_example.cpp

Если http/upload/ кажется слишком крупным доменом, можно временно положить upload helpers в:

include/kurlyk/http/upload/

а платформенные wrappers не добавлять в MVP.


1. OAuthRedirectServer

Назначение

Локальный HTTP-сервер, который слушает callback от OAuth provider на 127.0.0.1:<port>/<callback_path> и извлекает:

  • code;
  • state;
  • error;
  • error_description.

Сервер должен использовать Simple-Web-Server.

Public API

namespace kurlyk::http::auth {

struct OAuthRedirectConfig {
    std::string host = "127.0.0.1";
    uint16_t port = 0;
    std::string callback_path = "/callback";
    std::string success_html;
    std::string error_html;
    std::chrono::milliseconds timeout{120000};
};

struct OAuthRedirectResult {
    bool success = false;
    std::string code;
    std::string state;
    std::string error;
    std::string error_description;
    std::string raw_query;
};

class OAuthRedirectServer {
public:
    explicit OAuthRedirectServer(OAuthRedirectConfig config);

    bool start();
    void stop();

    uint16_t port() const;
    std::string redirect_uri() const;

    OAuthRedirectResult wait();
    OAuthRedirectResult wait_for(std::chrono::milliseconds timeout);

private:
    OAuthRedirectConfig m_config;
};

} // namespace kurlyk::http::auth

Требования

  • Если port = 0, сервер должен выбрать свободный порт.
  • redirect_uri() должен возвращать URL вида:
http://127.0.0.1:<port>/callback
  • wait() блокирует до callback или timeout.
  • После первого callback сервер может быть остановлен.
  • Сервер должен корректно возвращать HTML-страницу успеха/ошибки.
  • state не должен валидироваться внутри redirect server. Валидация остаётся задачей OAuthPkceClient::validate_state().
  • Сервер не должен логировать code, access_token, refresh_token, code_verifier.

Acceptance criteria

  • Тест: callback ?code=abc&state=xyz возвращает success=true, code=abc, state=xyz.
  • Тест: callback ?error=access_denied возвращает success=false, error=access_denied.
  • Тест: redirect_uri() содержит фактический порт.
  • Тест: timeout возвращает success=false.

2. FileTokenStorage

Назначение

Простое файловое хранилище для OAuthToken, чтобы не авторизоваться заново при каждом запуске.

Public API

namespace kurlyk::http::auth {

class FileTokenStorage : public ITokenStorage {
public:
    explicit FileTokenStorage(std::string file_path);

    bool load(OAuthToken& token) override;
    bool save(const OAuthToken& token) override;
    bool clear();

private:
    std::string m_file_path;
};

} // namespace kurlyk::http::auth

Формат файла

Если KURLYK_JSON_SUPPORT=1, использовать JSON:

{
  "access_token": "...",
  "refresh_token": "...",
  "token_type": "Bearer",
  "expires_at_ms": 1234567890,
  "scope": "..."
}

Если JSON выключен — либо отключить FileTokenStorage, либо сделать простой key-value формат. Для MVP можно сделать:

#if KURLYK_JSON_SUPPORT
// FileTokenStorage enabled
#endif

Требования

  • Не логировать содержимое token.
  • Не падать при отсутствующем файле.
  • Не падать при повреждённом JSON, а вернуть false.
  • clear() удаляет файл или очищает его содержимое.

3. RefreshableBearerAuthProvider

Назначение

Auth provider, который перед запросом проверяет срок действия access token и при необходимости вызывает refresh через OAuthPkceClient.

Public API

namespace kurlyk::http::auth {

class RefreshableBearerAuthProvider : public IAuthProvider {
public:
    RefreshableBearerAuthProvider(
        std::shared_ptr<OAuthPkceClient> client,
        std::shared_ptr<ITokenStorage> storage,
        std::chrono::seconds refresh_skew = std::chrono::seconds(60));

    bool authorize(HttpRequest& request) override;
    bool authorize(Headers& headers) override;

    bool refresh_if_needed();
    bool set_token(const OAuthToken& token);
    bool token(OAuthToken& out_token) const;

private:
    std::shared_ptr<OAuthPkceClient> m_client;
    std::shared_ptr<ITokenStorage> m_storage;
    OAuthToken m_token;
    std::chrono::seconds m_refresh_skew;
    mutable std::mutex m_mutex;
};

} // namespace kurlyk::http::auth

Поведение

  • Если token ещё валиден, provider добавляет:
Authorization: Bearer <access_token>
  • Если token истёк или скоро истечёт, provider вызывает refresh_access_token().
  • После успешного refresh сохраняет token через ITokenStorage.
  • Если refresh не удался, authorize() возвращает false.
  • Provider должен быть thread-safe внутри authorize().

Acceptance criteria

  • Тест: валидный token добавляет Bearer header.
  • Тест: expired token вызывает refresh.
  • Тест: refresh failure возвращает false.
  • Тест: после refresh token сохраняется в storage.
  • Тест: пустой access token не добавляет header.

4. UploadRetryPolicy

Назначение

Общая политика retry для upload-запросов.

Public API

namespace kurlyk::http::upload {

struct UploadRetryPolicy {
    int max_retries = 10;
    std::chrono::milliseconds base_delay{1000};
    std::chrono::milliseconds max_delay{60000};
    bool use_jitter = true;

    std::set<long> retry_status_codes = {500, 502, 503, 504};
};

std::chrono::milliseconds calculate_retry_delay(
    const UploadRetryPolicy& policy,
    int retry_index);

bool is_retryable_status(
    const UploadRetryPolicy& policy,
    long status_code);

} // namespace kurlyk::http::upload

Требования

  • Использовать exponential backoff.
  • Поддержать jitter.
  • По умолчанию retry для 500, 502, 503, 504, как в официальном YouTube upload sample. ([Google for Developers]1)
  • Не retry для 401/403 без отдельного refresh token сценария.
  • Не retry бесконечно.

5. ResumableUploadClient

Назначение

Базовый helper для resumable upload, пригодный для YouTube-style upload.

YouTube официальный пример использует videos.insert с resumable=True и retry/resume при сбоях. ([Google for Developers]1)

Public API draft

namespace kurlyk::http::upload {

struct ResumableUploadConfig {
    std::string init_url;
    kurlyk::Headers init_headers;
    std::string metadata_json;
    std::string file_path;
    std::string mime_type = "video/mp4";
    std::size_t chunk_size = 0; // 0 = whole file / platform default
    UploadRetryPolicy retry_policy;
};

struct UploadProgress {
    uint64_t uploaded_bytes = 0;
    uint64_t total_bytes = 0;
    int retry_count = 0;
};

struct UploadResult {
    bool success = false;
    long status_code = 0;
    std::string response_body;
    std::string upload_url;
    std::string error_message;
};

using UploadProgressCallback = std::function<void(const UploadProgress&)>;

class ResumableUploadClient {
public:
    explicit ResumableUploadClient(HttpClient& client);

    UploadResult upload(
        const ResumableUploadConfig& config,
        UploadProgressCallback progress = nullptr);

private:
    HttpClient& m_client;
};

} // namespace kurlyk::http::upload

MVP-поведение

  • Выполнить init request.
  • Получить upload URL из Location header.
  • Отправить файл целиком или чанками.
  • При retryable error повторить с exponential backoff.
  • Для MVP можно поддержать только blocking API.
  • Progress callback вызывается после каждого chunk.

Что можно отложить

  • Resume с query текущего offset.
  • Пауза/продолжение после перезапуска программы.
  • Hash verification.
  • Parallel upload.

6. ChunkedUploadClient

Назначение

Базовый helper для upload flow, где платформа возвращает upload_url, а файл отправляется чанками через PUT/POST.

TikTok Direct Post flow использует user access token и Direct Post init endpoint, после чего видео загружается по upload URL. Для Direct Post нужен Bearer token и scope video.publish. ([TikTok для разработчиков]2)

Public API draft

namespace kurlyk::http::upload {

struct ChunkedUploadConfig {
    std::string upload_url;
    kurlyk::Headers headers;
    std::string file_path;
    std::size_t chunk_size = 8 * 1024 * 1024;
    UploadRetryPolicy retry_policy;
};

class ChunkedUploadClient {
public:
    explicit ChunkedUploadClient(HttpClient& client);

    UploadResult upload(
        const ChunkedUploadConfig& config,
        UploadProgressCallback progress = nullptr);

private:
    HttpClient& m_client;
};

} // namespace kurlyk::http::upload

Требования

  • Поддержать Content-Range, если требуется платформой.
  • Поддержать отправку чанков.
  • Поддержать retry на retryable status.
  • Не хранить файл целиком в памяти.
  • Работать с большими видеофайлами.

7. YouTube example

Файл

examples/youtube_oauth_redirect_example.cpp

Сценарий

  1. Создать OAuthRedirectServer.

  2. Создать OAuthConfig:

    • authorization endpoint;
    • token endpoint;
    • client id;
    • redirect uri из redirect server;
    • scope https://www.googleapis.com/auth/youtube.upload.
  3. Построить authorization URL.

  4. Вывести URL пользователю.

  5. Дождаться callback.

  6. Проверить state.

  7. Выполнить exchange_code().

  8. Сохранить token в FileTokenStorage.

Второй пример

examples/youtube_resumable_upload_example.cpp

Сценарий:

  1. Загрузить token из FileTokenStorage.
  2. Создать RefreshableBearerAuthProvider.
  3. Создать HttpClient.
  4. Установить auth provider.
  5. Выполнить resumable upload skeleton.

8. TikTok example

Файл

examples/tiktok_oauth_redirect_example.cpp

Сценарий

  1. Создать OAuthRedirectServer.
  2. Создать OAuthConfig для TikTok.
  3. Указать scope, включая video.publish.
  4. Построить authorization URL.
  5. Дождаться callback.
  6. Проверить state.
  7. Выполнить exchange_code().
  8. Сохранить token.

Второй пример

examples/tiktok_chunked_upload_example.cpp

Сценарий:

  1. Загрузить token.
  2. Создать RefreshableBearerAuthProvider.
  3. Вызвать Direct Post init endpoint.
  4. Получить upload_url.
  5. Передать файл в ChunkedUploadClient.

9. CMake options

Добавить опции:

option(KURLYK_OAUTH_REDIRECT_SERVER_SUPPORT "Enable local OAuth redirect server based on Simple-Web-Server" ON)
option(KURLYK_UPLOAD_SUPPORT "Enable HTTP upload helpers" ON)

Зависимости:

KURLYK_OAUTH_REDIRECT_SERVER_SUPPORT requires:
- KURLYK_AUTH_SUPPORT=ON
- KURLYK_OAUTH_SUPPORT=ON
- Simple-Web-Server available

KURLYK_UPLOAD_SUPPORT requires:
- KURLYK_HTTP_SUPPORT=ON

Если redirect server выключен, OAuthPkceClient остаётся доступным, но callback нужно получать вручную.


10. Documentation

Добавить:

guides/oauth-redirect-server.md
guides/video-upload.md

guides/oauth-redirect-server.md

Содержит:

  • зачем нужен local redirect server;

  • пример Authorization Code + PKCE;

  • security notes:

    • не логировать code/token;
    • проверять state;
    • слушать только 127.0.0.1;
    • использовать random port по умолчанию;
    • закрывать сервер после callback.

guides/video-upload.md

Содержит:

  • общий pipeline:

    • OAuth login;
    • token storage;
    • refreshable bearer;
    • upload init;
    • chunk/resumable upload;
    • retry/backoff.
  • YouTube notes.

  • TikTok notes.

  • Ограничения MVP.


11. Suggested PR plan

PR 1

feat(auth): add OAuth local redirect server

Состав:

  • OAuthRedirectServer;
  • OAuthRedirectConfig;
  • OAuthRedirectResult;
  • tests;
  • guide;
  • example.

PR 2

feat(auth): add file token storage and refreshable bearer provider

Состав:

  • FileTokenStorage;
  • RefreshableBearerAuthProvider;
  • tests;
  • docs;
  • example.

PR 3

feat(upload): add retry policy and resumable upload helpers

Состав:

  • UploadRetryPolicy;
  • ResumableUploadClient;
  • ChunkedUploadClient;
  • tests for retry policy;
  • skeleton examples.

PR 4

docs(examples): add YouTube and TikTok upload guides

Состав:

  • YouTube guide;
  • TikTok guide;
  • examples cleanup;
  • README/README-RU update.

Acceptance criteria for whole epic

  • OAuth redirect server works with local callback.
  • Auth code can be exchanged using existing OAuthPkceClient.
  • Token can be persisted and loaded.
  • Expired token can be refreshed before request.
  • Bearer header is attached automatically through HttpClient::set_auth_provider().
  • Upload helpers can stream file from disk without loading entire file into memory.
  • Retry policy supports exponential backoff and retryable HTTP statuses.
  • YouTube/TikTok examples compile when relevant macros are enabled.
  • Disabling KURLYK_OAUTH_REDIRECT_SERVER_SUPPORT removes redirect server dependencies.
  • Disabling KURLYK_UPLOAD_SUPPORT removes upload helpers from build.
  • C++11 public headers remain compatible unless guarded.
  • README and README-RU are updated together if public-facing docs are changed.

Notes

Текущий OAuth layer не надо превращать в “универсальный OAuth-комбайн”. Для YouTube/TikTok достаточно развивать текущую линию:

Authorization Code + PKCE
local redirect callback
refresh token
Bearer provider
resumable/chunked upload
retry/backoff

Client Credentials, Device Code, JWT service accounts и OIDC validation лучше оставить отдельными будущими задачами.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions