vkauth

package
v1.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 28, 2026 License: Apache-2.0 Imports: 31 Imported by: 0

Documentation

Overview

Captcha solvers for the VK anonymous-login flow.

Three flavours, picked at runtime:

  • AutoProxyCaptchaSolver — spins up a local 127.0.0.1:<port> reverse-proxy + auto-launches the operator's default browser. Default for CLI/GUI clients running on a workstation.

  • PreSolvedCaptchaSolver — replays a token captured out-of-band. One-shot.

  • AdminWebviewCaptchaSolver — delegates to an AdminCaptchaBroker hosted by the goloom admin panel. The operator opens the broker's proxy URL in their browser, solves the captcha, and the token flows back through the same channel. Fits headless production servers where there's no desktop session for AutoProxy.

All three converge on the same internal handler tree (reverse-proxy + JS shim + token capture); the only differences are where the handlers are mounted (own ephemeral HTTP server vs. an existing one) and how the URL is delivered to the operator (auto-open browser vs. admin-panel badge).

Index

Constants

This section is empty.

Variables

View Source
var ErrCaptchaV2Bot = errors.New("vkcalls: captcha v2: bot challenge")

ErrCaptchaV2Bot — VK увидел bot-сигнал на checkbox-попытке. С данным профилем не пройдёт; caller должен MarkFail и пробовать другой профиль или фоллбэкнуться.

View Source
var ErrCaptchaV2RateLimit = errors.New("vkcalls: captcha v2: rate limit reached")

ErrCaptchaV2RateLimit — VK сообщил error_limit; смысла ретраить нет.

View Source
var ErrSliderUnsupported = errors.New("vkcalls: captcha v2: slider show_type not supported (S1c-checkbox)")

ErrSliderUnsupported возвращается из SolveCaptchaV2 когда VK отдал show_type=slider. S1c-checkbox-only; caller должен fallback'нуть на интерактивный solver.

View Source
var ProfileList = []BrowserProfile{
	{
		UserAgent:       "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
		SecChUa:         `"Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"`,
		SecChUaMobile:   "?0",
		SecChUaPlatform: `"Windows"`,
	},
	{
		UserAgent:       "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36",
		SecChUa:         `"Chromium";v="145", "Not-A.Brand";v="99", "Google Chrome";v="145"`,
		SecChUaMobile:   "?0",
		SecChUaPlatform: `"Windows"`,
	},
	{
		UserAgent:       "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0",
		SecChUa:         `"Chromium";v="146", "Not-A.Brand";v="24", "Microsoft Edge";v="146"`,
		SecChUaMobile:   "?0",
		SecChUaPlatform: `"Windows"`,
	},
	{
		UserAgent:       "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
		SecChUa:         `"Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"`,
		SecChUaMobile:   "?0",
		SecChUaPlatform: `"macOS"`,
	},
	{
		UserAgent:       "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
		SecChUa:         `"Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"`,
		SecChUaMobile:   "?0",
		SecChUaPlatform: `"Linux"`,
	},
}

Functions

func AdminCaptchaProxyURL

func AdminCaptchaProxyURL(urlPrefix string, target *neturl.URL) string

AdminCaptchaProxyURL returns the relative URL where a freshly- registered challenge will live, given the broker's URL prefix scheme ("/captcha-proxy/<id>"). Convenience for callers that build challenge IDs externally.

func AdminWebviewCaptchaSolver

func AdminWebviewCaptchaSolver(broker AdminCaptchaBroker, tag string, lg *log.Logger) sfu.VKCaptchaSolver

AdminWebviewCaptchaSolver returns a sfu.VKCaptchaSolver that delegates to the goloom admin panel via the supplied broker. Use this for inbounds running on headless servers where AutoProxy can't open a browser.

`tag` is a human-readable label (e.g. the inbound's tag) shown in the admin UI's pending-captcha badge.

The solver blocks until the operator solves the challenge or ctx is cancelled — under normal conditions the admin's pending-captcha badge surfaces the request within a second of the inbound asking.

func AutoProxyCaptchaSolver

func AutoProxyCaptchaSolver(timeout time.Duration, lg *log.Logger, sink CaptureSink) sfu.VKCaptchaSolver

AutoProxyCaptchaSolver returns a sfu.VKCaptchaSolver that opens a local reverse-proxy + the user's default browser, then waits for the success_token to flow back through the proxy. Default for CLI tooling on a workstation.

sink — опциональный CaptureSink (обычно *ProfileStore); если задан, перехваченные device/browser_fp/UA отправятся туда для будущего auto-replay. Передавай nil если не используешь стор.

func AutoProxyCaptchaSolverWithOpener

func AutoProxyCaptchaSolverWithOpener(timeout time.Duration, lg *log.Logger, sink CaptureSink, openURL func(string)) sfu.VKCaptchaSolver

AutoProxyCaptchaSolverWithOpener — то же что AutoProxyCaptchaSolver, но использует custom URL opener. Мобильные клиенты передают сюда функцию которая шлёт URL в native WebView (см. mobile/api.go); desktop-вариант идёт через [openBrowser] (system browser).

func MountAdminCaptchaProxy

func MountAdminCaptchaProxy(mux *http.ServeMux, urlPrefix string, target *neturl.URL, onToken func(string), opts MountAdminCaptchaProxyOptions) error

MountAdminCaptchaProxy installs the captcha proxy + JS shim handlers under urlPrefix on mux. urlPrefix must start with "/" and not end with "/" (e.g. "/captcha-proxy/abc123"). target is the upstream URL (id.vk.com/not_robot_captcha?session_token=…). onToken fires when a success_token is captured (server-side or via the JS shim's POST fallback).

Если opts.FingerprintSink задан — поверх defaultTransport натягивается [loggingTransport], который параллельно с обычным proxying извлекает device/browser_fp/UA и кормит их в sink (для будущего auto-replay, см. ProfileStore).

Used by internal/admin.CaptchaBroker; sites without an admin server should use AutoProxyCaptchaSolver instead.

func NewLoggingTransport

func NewLoggingTransport(base http.RoundTripper, sink CaptureSink, lg *log.Logger) http.RoundTripper

NewLoggingTransport возвращает обёртку. base = http транспорт, который реально шлёт запросы (обычно [defaultTransport]).

func PreSolvedCaptchaSolver

func PreSolvedCaptchaSolver(token string) sfu.VKCaptchaSolver

PreSolvedCaptchaSolver returns a one-shot solver that hands back a pre-captured success_token. Useful when an external orchestrator (admin webview, mobile app) has already solved the captcha and the caller just needs to feed the token into the auth chain.

Returns an error on the second invocation — tokens are single-use.

func SolveCaptchaV2

func SolveCaptchaV2(ctx context.Context, challenge sfu.VKCaptchaChallenge, profile BrowserProfile, saved *CapturedProfile, lg *log.Logger) (string, error)

SolveCaptchaV2 пытается пройти captcha автоматически по captured-profile из пула. Возвращает success_token при успехе.

challenge — то что прилетело из getAnonymousToken (sid, redirect_uri, session_token). profile — текущий браузерный профиль для UA + sec-ch-ua-* (тот же что и у auth-ладдера; брать из AuthResult.Profile). saved — captured-profile из ProfileStore.Pick(); определяет device + browser_fp + UA для replay'я (если не nil — replay; если nil — генерируется свежий browser_fp но device берётся из default'а, шансов меньше).

func WithReplaySolver

func WithReplaySolver(store *ProfileStore, base sfu.VKCaptchaSolver, lg *log.Logger) sfu.VKCaptchaSolver

Types

type AdminCaptchaBroker

type AdminCaptchaBroker interface {
	// Register adds a pending challenge to the broker's queue and
	// returns the URL the operator should open in their browser to
	// solve it, plus a channel that fires once with the captured
	// success_token (empty string on timeout/cancel).
	//
	// `tag` is a human-readable label (typically the inbound tag) so
	// the admin UI can show "captcha needed for <tag>" instead of
	// an opaque hex id.
	//
	// Implementations must respect ctx — when ctx is cancelled, the
	// channel must be closed so the solver doesn't hang.
	Register(ctx context.Context, ch sfu.VKCaptchaChallenge, tag string) (proxyURL string, done <-chan string)
}

AdminCaptchaBroker is the admin-side surface that AdminWebviewCaptchaSolver delegates to. The admin package implements this with a registry of pending challenges + HTTP routes mounted on the existing admin server.

type BrowserProfile

type BrowserProfile struct {
	UserAgent       string
	SecChUa         string
	SecChUaMobile   string
	SecChUaPlatform string
}

BrowserProfile — спаренные UA и Client Hints. VK-WAF матчит UA против sec-ch-ua-*: рассогласование («Chrome/146 в UA, но sec-ch-ua-platform: macOS на Windows-UA») — сильный bot-сигнал.

Список лифтнут из vk-turn-proxy PR #162 client/profiles.go::ProfileList (5 swap'ов из 10 — оставляем самые свежие, чтобы заявленный Chrome version совпадал с реальным major-релизом).

func PickProfile

func PickProfile() BrowserProfile

PickProfile возвращает случайный профиль. Один DoAuth должен использовать один профиль на все 4 запроса — иначе UA «сменился между шагами», что само по себе bot-сигнал.

type CaptureSink

type CaptureSink interface {
	Capture(p CapturedProfile)
}

CaptureSink — куда сбрасываются перехваченные fingerprint'ы. Реализуется ProfileStore.Capture; абстракция нужна чтобы loggingTransport не зависел от ProfileStore напрямую (легче тестировать + AutoProxy путь может писать в другое место).

type CapturedProfile

type CapturedProfile struct {
	ID         string    `json:"id"`          // 16-hex-чисел; для индексации
	UserAgent  string    `json:"user_agent"`  // как в `User-Agent` HTTP-заголовке
	DeviceJSON string    `json:"device_json"` // raw JSON из form-поля `device`
	BrowserFP  string    `json:"browser_fp"`  // hex-строка из form-поля `browser_fp`
	CapturedAt time.Time `json:"captured_at"`
	LastUsedAt time.Time `json:"last_used_at,omitempty"`
	Successes  int       `json:"successes"`
	Failures   int       `json:"failures"`
	// ConsecutiveFails — фейлы подряд с момента последнего успеха
	// (сбрасывается в 0 на MarkSuccess). Это и есть «подряд» для
	// DropAfterFailures: профиль с большой историей успехов, который VK
	// внезапно забанил, должен вылетать через N фейлов подряд, а не
	// ждать пока Failures догонит Successes.
	ConsecutiveFails int `json:"consecutive_fails,omitempty"`
}

CapturedProfile — один захваченный fingerprint из браузера.

type MountAdminCaptchaProxyOptions

type MountAdminCaptchaProxyOptions struct {
	FingerprintSink CaptureSink
	Logger          *log.Logger
}

MountAdminCaptchaProxyOptions конфигурирует MountAdminCaptchaProxy.

FingerprintSink — опциональный сборщик device/browser_fp/UA, перехватывает тела `captchaNotRobot.componentDone` и `.check` пока они проходят через reverse-proxy. Передаётся обычно как `*ProfileStore`; nil → перехвата нет (legacy-поведение).

Logger — лог для loggingTransport diagnostics; nil → silent.

type ProfileStore

type ProfileStore struct {
	// contains filtered or unexported fields
}

ProfileStore — пул CapturedProfile с persistence.

Параметры пула (из решения по открытым вопросам в poc/docs/vk-calls-redesign.md):

  • Capacity: cap пула (10–20). При переполнении вытесняется худший профиль (max Failures, затем oldest LastUsedAt).
  • Cooldown: минимальное время между двумя реюзами одного профиля. Если все профили в cooldown'е — Pick возвращает freshest anyway, лучше попытаться чем фолбэкнуть на ручной solve.
  • DropAfterFailures: 2 fail подряд → выбрасываем.

func NewProfileStore

func NewProfileStore(opts ProfileStoreOptions) (*ProfileStore, error)

NewProfileStore создаёт стор и загружает существующие записи с диска. Если файла нет — стартует с пустым пулом. Если файл битый — возвращает ошибку (бить файл программно мы не должны, выгоднее дать оператору починить вручную).

func (*ProfileStore) Capture

func (s *ProfileStore) Capture(p CapturedProfile)

Capture сохраняет fingerprint в пул.

Если профиль с такими же device_json + browser_fp уже есть — апдейтится CapturedAt (актуализация freshness), счётчики не сбрасываем. Иначе вставляется новая запись; если пул переполнен — вытесняется худший.

Пустые поля (UA / DeviceJSON / BrowserFP) → silently no-op, не загрязняем пул мусором.

func (*ProfileStore) MarkFail

func (s *ProfileStore) MarkFail(id string)

MarkFail отмечает captcha-fail. После dropAfterFailures фейлов ПОДРЯД (без успеха между ними) — удаляем профиль из пула: VK его уже flag'нул, дальнейшие попытки только жгут session_token'ы и упираются в интерактивный fallback. Считаем именно подряд (ConsecutiveFails), чтобы профиль с богатой историей успехов, который VK внезапно забанил, вылетал быстро, а не ждал пока Failures догонит Successes.

func (*ProfileStore) MarkSuccess

func (s *ProfileStore) MarkSuccess(id string)

MarkSuccess отмечает профиль как успешно прошедший captcha. LastUsedAt обновляется, Successes++.

func (*ProfileStore) Pick

func (s *ProfileStore) Pick() *CapturedProfile

Pick возвращает наиболее подходящий профиль для следующей попытки auto-solve или nil если пул пуст. Стратегия:

  1. Профили в cooldown'е (LastUsedAt < now-cooldown) — приоритет.
  2. Среди cooldown-passed — берём с минимумом fail/success ratio, ties break'аем oldest LastUsedAt (LRU — даём отдохнуть свежим).
  3. Если все в cooldown'е — берём наиболее «отдохнувший» (oldest LastUsedAt) — лучше попытаться чем фолбэкнуть.

Не помечает использование — это делает MarkSuccess/MarkFail в зависимости от исхода.

func (*ProfileStore) Snapshot

func (s *ProfileStore) Snapshot() []CapturedProfile

Snapshot возвращает копию текущего пула — для admin UI / тестов.

type ProfileStoreOptions

type ProfileStoreOptions struct {
	Path              string        // путь к persistence-файлу. Обязателен.
	Capacity          int           // 0 → дефолт 16
	Cooldown          time.Duration // 0 → дефолт 90s
	DropAfterFailures int           // 0 → дефолт 2
}

ProfileStoreOptions — конфиг при создании.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL