ТЗ: 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
-
OAuthRedirectServer на базе Simple-Web-Server.
-
OAuthRedirectResult для результата callback.
-
OAuthTokenStorage / file-based token storage.
-
RefreshableBearerAuthProvider.
-
Общий retry helper с exponential backoff.
-
Базовый resumable/chunked upload helper.
-
Примеры:
- YouTube OAuth login + token save.
- YouTube resumable upload skeleton.
- TikTok OAuth login + token save.
- TikTok upload skeleton.
-
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
Сценарий
-
Создать OAuthRedirectServer.
-
Создать OAuthConfig:
- authorization endpoint;
- token endpoint;
- client id;
- redirect uri из redirect server;
- scope
https://www.googleapis.com/auth/youtube.upload.
-
Построить authorization URL.
-
Вывести URL пользователю.
-
Дождаться callback.
-
Проверить state.
-
Выполнить exchange_code().
-
Сохранить token в FileTokenStorage.
Второй пример
examples/youtube_resumable_upload_example.cpp
Сценарий:
- Загрузить token из
FileTokenStorage.
- Создать
RefreshableBearerAuthProvider.
- Создать
HttpClient.
- Установить auth provider.
- Выполнить resumable upload skeleton.
8. TikTok example
Файл
examples/tiktok_oauth_redirect_example.cpp
Сценарий
- Создать
OAuthRedirectServer.
- Создать
OAuthConfig для TikTok.
- Указать scope, включая
video.publish.
- Построить authorization URL.
- Дождаться callback.
- Проверить
state.
- Выполнить
exchange_code().
- Сохранить token.
Второй пример
examples/tiktok_chunked_upload_example.cpp
Сценарий:
- Загрузить token.
- Создать
RefreshableBearerAuthProvider.
- Вызвать Direct Post init endpoint.
- Получить
upload_url.
- Передать файл в
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
Содержит:
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 лучше оставить отдельными будущими задачами.
ТЗ: OAuth redirect server и upload-инфраструктура для YouTube/TikTok
Цель
Добавить в
kurlykинфраструктуру для авторизации пользователя через OAuth2 Authorization Code + PKCE и последующей загрузки видео через API платформ вроде YouTube и TikTok.Текущий
OAuthPkceClientуже покрывает построение authorization URL, обменcodeна token, refresh token и Bearer auth. Следующий слой должен добавить: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 нужен scopevideo.publish. ([TikTok для разработчиков]2)Scope
Входит в MVP
OAuthRedirectServerна базе Simple-Web-Server.OAuthRedirectResultдля результата callback.OAuthTokenStorage/ file-based token storage.RefreshableBearerAuthProvider.Общий retry helper с exponential backoff.
Базовый resumable/chunked upload helper.
Примеры:
Tests:
codeиstate;error;Не входит в MVP
Архитектура
Новые файлы
Если
http/upload/кажется слишком крупным доменом, можно временно положить upload helpers в:а платформенные 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
Требования
port = 0, сервер должен выбрать свободный порт.redirect_uri()должен возвращать URL вида:wait()блокирует до callback или timeout.stateне должен валидироваться внутри redirect server. Валидация остаётся задачейOAuthPkceClient::validate_state().code,access_token,refresh_token,code_verifier.Acceptance criteria
?code=abc&state=xyzвозвращаетsuccess=true,code=abc,state=xyz.?error=access_deniedвозвращаетsuccess=false,error=access_denied.redirect_uri()содержит фактический порт.success=false.2. FileTokenStorage
Назначение
Простое файловое хранилище для
OAuthToken, чтобы не авторизоваться заново при каждом запуске.Public API
Формат файла
Если
KURLYK_JSON_SUPPORT=1, использовать JSON:{ "access_token": "...", "refresh_token": "...", "token_type": "Bearer", "expires_at_ms": 1234567890, "scope": "..." }Если JSON выключен — либо отключить
FileTokenStorage, либо сделать простой key-value формат. Для MVP можно сделать:Требования
false.clear()удаляет файл или очищает его содержимое.3. RefreshableBearerAuthProvider
Назначение
Auth provider, который перед запросом проверяет срок действия access token и при необходимости вызывает refresh через
OAuthPkceClient.Public API
Поведение
refresh_access_token().ITokenStorage.authorize()возвращаетfalse.authorize().Acceptance criteria
false.4. UploadRetryPolicy
Назначение
Общая политика retry для upload-запросов.
Public API
Требования
500,502,503,504, как в официальном YouTube upload sample. ([Google for Developers]1)401/403без отдельного refresh token сценария.5. ResumableUploadClient
Назначение
Базовый helper для resumable upload, пригодный для YouTube-style upload.
YouTube официальный пример использует
videos.insertсresumable=Trueи retry/resume при сбоях. ([Google for Developers]1)Public API draft
MVP-поведение
Locationheader.Что можно отложить
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
Требования
Content-Range, если требуется платформой.7. YouTube example
Файл
Сценарий
Создать
OAuthRedirectServer.Создать
OAuthConfig:https://www.googleapis.com/auth/youtube.upload.Построить authorization URL.
Вывести URL пользователю.
Дождаться callback.
Проверить
state.Выполнить
exchange_code().Сохранить token в
FileTokenStorage.Второй пример
Сценарий:
FileTokenStorage.RefreshableBearerAuthProvider.HttpClient.8. TikTok example
Файл
Сценарий
OAuthRedirectServer.OAuthConfigдля TikTok.video.publish.state.exchange_code().Второй пример
Сценарий:
RefreshableBearerAuthProvider.upload_url.ChunkedUploadClient.9. CMake options
Добавить опции:
Зависимости:
Если redirect server выключен,
OAuthPkceClientостаётся доступным, но callback нужно получать вручную.10. Documentation
Добавить:
guides/oauth-redirect-server.mdСодержит:
зачем нужен local redirect server;
пример Authorization Code + PKCE;
security notes:
127.0.0.1;guides/video-upload.mdСодержит:
общий pipeline:
YouTube notes.
TikTok notes.
Ограничения MVP.
11. Suggested PR plan
PR 1
Состав:
OAuthRedirectServer;OAuthRedirectConfig;OAuthRedirectResult;PR 2
Состав:
FileTokenStorage;RefreshableBearerAuthProvider;PR 3
Состав:
UploadRetryPolicy;ResumableUploadClient;ChunkedUploadClient;PR 4
Состав:
Acceptance criteria for whole epic
OAuthPkceClient.HttpClient::set_auth_provider().KURLYK_OAUTH_REDIRECT_SERVER_SUPPORTremoves redirect server dependencies.KURLYK_UPLOAD_SUPPORTremoves upload helpers from build.Notes
Текущий OAuth layer не надо превращать в “универсальный OAuth-комбайн”. Для YouTube/TikTok достаточно развивать текущую линию:
Client Credentials, Device Code, JWT service accounts и OIDC validation лучше оставить отдельными будущими задачами.