feat(auth): add signing in through external services
GitHub, Google, VK and Yandex in the box, and any other service as a module: a provider is a class tagged johncms.auth.external_provider, and the contract deliberately describes 'where do we send the visitor' and 'what do we make of them coming back' rather than the OAuth2 flow, so a service like Telegram or Steam can implement it too. AbstractOAuth2Provider covers the usual case in three addresses and a field mapping.
The flow stays in the core: single-use state with PKCE in the session, the routes, matching against accounts and opening the session. A provider never sees the request and cannot decide who it is linked to, so the worst a third-party one can do is break its own button.
Matching an identity to an existing account by email is allowed only when the provider verified the address and the account confirmed it too — anything else is an account takeover. A newcomer finishes the profile before an account exists, since VK may withhold the address and a display name is rarely free as a login. An account signed up this way has no password, and unlinking the only way in is refused.
VK goes through VK ID: the classic oauth.vk.com endpoints answer a bare 'Security Error' for an application registered today, while VK ID requires PKCE, the device_id from the callback and the profile from /oauth2/user_info.