Files
2026-07-27 12:58:44 +03:00

89 lines
2.9 KiB
PHP

<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file system/App/Frontend/Auth/OAuth/OAuthFlow.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Frontend\Auth\OAuth;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Common\Session;
class OAuthFlow
{
const SESSION_KEY = 'public_oauth_flows';
const TTL = 600;
public static function create($provider, $returnUrl, array $context = array())
{
$state = bin2hex(random_bytes(24));
$verifier = self::base64Url(random_bytes(48));
$flow = array(
'provider' => (string) $provider, 'state' => $state,
'return_url' => (string) $returnUrl, 'expires_at' => time() + self::TTL,
'redirect_uri' => self::callbackUrl($provider),
'code_verifier' => $verifier,
'code_challenge' => self::base64Url(hash('sha256', $verifier, true)),
);
if (!empty($context['link_user_id'])) { $flow['link_user_id'] = (int) $context['link_user_id']; }
$flows = self::active();
$flows[$state] = $flow;
while (count($flows) > 5) { array_shift($flows); }
Session::set(self::SESSION_KEY, $flows);
return $flow;
}
public static function consume($provider, $state)
{
$flows = self::active();
$state = trim((string) $state);
$flow = isset($flows[$state]) ? $flows[$state] : null;
unset($flows[$state]);
Session::set(self::SESSION_KEY, $flows);
if (!$flow || !hash_equals((string) $flow['provider'], (string) $provider)) {
throw new \InvalidArgumentException('Сессия социального входа устарела. Повторите вход.');
}
return $flow;
}
public static function callbackUrl($provider)
{
$base = defined('HOST') ? rtrim((string) HOST, '/') : self::requestOrigin();
$path = defined('ABS_PATH') ? rtrim((string) ABS_PATH, '/') : '';
return $base . $path . '/auth/oauth/' . rawurlencode((string) $provider) . '/callback';
}
protected static function active()
{
$flows = Session::get(self::SESSION_KEY);
$flows = is_array($flows) ? $flows : array();
foreach ($flows as $state => $flow) {
if (!is_array($flow) || empty($flow['expires_at']) || (int) $flow['expires_at'] < time()) { unset($flows[$state]); }
}
return $flows;
}
protected static function requestOrigin()
{
$https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== '' && $_SERVER['HTTPS'] !== 'off';
$host = isset($_SERVER['HTTP_HOST']) ? preg_replace('/[^A-Za-z0-9.:[\]-]/', '', (string) $_SERVER['HTTP_HOST']) : 'localhost';
return ($https ? 'https://' : 'http://') . $host;
}
protected static function base64Url($value)
{
return rtrim(strtr(base64_encode((string) $value), '+/', '-_'), '=');
}
}