add cookie manager
This commit is contained in:
parent
65bfec8078
commit
74ffbcb651
@ -2,6 +2,7 @@
|
||||
|
||||
require_once __DIR__ . '/Session.php';
|
||||
require_once __DIR__ . '/Validator.php';
|
||||
require_once __DIR__ . '/Cookies.php';
|
||||
|
||||
/**
|
||||
* Context holds the request, response, and shared state for a request
|
||||
@ -11,17 +12,20 @@ class Context
|
||||
public Request $request;
|
||||
public Response $response;
|
||||
public Session $session;
|
||||
public Cookies $cookie;
|
||||
public array $state = [];
|
||||
|
||||
/**
|
||||
* __construct creates a new Context with request and response
|
||||
*/
|
||||
public function __construct()
|
||||
public function __construct(?Cookie $cookie = null)
|
||||
{
|
||||
$this->request = new Request();
|
||||
$this->response = new Response();
|
||||
$this->session = new Session();
|
||||
$this->session->start();
|
||||
$this->cookie = $cookie ?: new Cookies();
|
||||
$this->response->setCookieManager($this->cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
240
Cookies.php
Normal file
240
Cookies.php
Normal file
@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Cookie manager for consistent cookie handling
|
||||
*/
|
||||
class Cookies
|
||||
{
|
||||
private array $defaults;
|
||||
|
||||
public function __construct(array $defaults = [])
|
||||
{
|
||||
$this->defaults = array_merge([
|
||||
'expires' => 0,
|
||||
'path' => '/',
|
||||
'domain' => '',
|
||||
'secure' => false,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax'
|
||||
], $defaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cookie
|
||||
*/
|
||||
public function set(string $name, string $value, array $options = []): bool
|
||||
{
|
||||
$options = array_merge($this->defaults, $options);
|
||||
|
||||
// Convert lifetime to expires timestamp if provided
|
||||
if (isset($options['lifetime'])) {
|
||||
$options['expires'] = time() + $options['lifetime'];
|
||||
unset($options['lifetime']);
|
||||
}
|
||||
|
||||
return setcookie($name, $value, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cookie value
|
||||
*/
|
||||
public function get(string $name, ?string $default = null): ?string
|
||||
{
|
||||
return $_COOKIE[$name] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a cookie exists
|
||||
*/
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return isset($_COOKIE[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a cookie
|
||||
*/
|
||||
public function delete(string $name, array $options = []): bool
|
||||
{
|
||||
$options = array_merge($this->defaults, $options);
|
||||
$options['expires'] = time() - 3600;
|
||||
|
||||
// Remove from $_COOKIE superglobal
|
||||
unset($_COOKIE[$name]);
|
||||
|
||||
return setcookie($name, '', $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cookie that expires when browser closes
|
||||
*/
|
||||
public function setSession(string $name, string $value, array $options = []): bool
|
||||
{
|
||||
$options['expires'] = 0;
|
||||
return $this->set($name, $value, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cookie with specific lifetime in seconds
|
||||
*/
|
||||
public function setWithLifetime(string $name, string $value, int $lifetime, array $options = []): bool
|
||||
{
|
||||
$options['lifetime'] = $lifetime;
|
||||
return $this->set($name, $value, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cookie that expires in specified days
|
||||
*/
|
||||
public function setForDays(string $name, string $value, int $days, array $options = []): bool
|
||||
{
|
||||
return $this->setWithLifetime($name, $value, $days * 86400, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cookie that expires in specified hours
|
||||
*/
|
||||
public function setForHours(string $name, string $value, int $hours, array $options = []): bool
|
||||
{
|
||||
return $this->setWithLifetime($name, $value, $hours * 3600, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cookie that expires in specified minutes
|
||||
*/
|
||||
public function setForMinutes(string $name, string $value, int $minutes, array $options = []): bool
|
||||
{
|
||||
return $this->setWithLifetime($name, $value, $minutes * 60, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a forever cookie (5 years)
|
||||
*/
|
||||
public function forever(string $name, string $value, array $options = []): bool
|
||||
{
|
||||
return $this->setWithLifetime($name, $value, 157680000, $options); // 5 years
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all cookies
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
return $_COOKIE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cookies
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
foreach ($_COOKIE as $name => $value) $this->delete($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default options
|
||||
*/
|
||||
public function setDefaults(array $defaults): void
|
||||
{
|
||||
$this->defaults = array_merge($this->defaults, $defaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default options
|
||||
*/
|
||||
public function getDefaults(): array
|
||||
{
|
||||
return $this->defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a signed cookie value
|
||||
*/
|
||||
public function sign(string $value, string $secret): string
|
||||
{
|
||||
$signature = hash_hmac('sha256', $value, $secret);
|
||||
return base64_encode($value . '|' . $signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify and extract signed cookie value
|
||||
*/
|
||||
public function verify(string $signedValue, string $secret): ?string
|
||||
{
|
||||
$decoded = base64_decode($signedValue);
|
||||
if (!$decoded || !str_contains($decoded, '|')) return null;
|
||||
|
||||
[$value, $signature] = explode('|', $decoded, 2);
|
||||
$expectedSignature = hash_hmac('sha256', $value, $secret);
|
||||
|
||||
if (!hash_equals($expectedSignature, $signature)) return null;
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a signed cookie
|
||||
*/
|
||||
public function setSigned(string $name, string $value, string $secret, array $options = []): bool
|
||||
{
|
||||
$signedValue = $this->sign($value, $secret);
|
||||
return $this->set($name, $signedValue, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and verify a signed cookie
|
||||
*/
|
||||
public function getSigned(string $name, string $secret, ?string $default = null): ?string
|
||||
{
|
||||
$signedValue = $this->get($name);
|
||||
if ($signedValue === null) return $default;
|
||||
|
||||
$value = $this->verify($signedValue, $secret);
|
||||
return $value !== null ? $value : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a cookie value
|
||||
*/
|
||||
public function encrypt(string $value, string $key): string
|
||||
{
|
||||
$iv = random_bytes(16);
|
||||
$encrypted = openssl_encrypt($value, 'AES-256-CBC', $key, 0, $iv);
|
||||
return base64_encode($iv . '|' . $encrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a cookie value
|
||||
*/
|
||||
public function decrypt(string $encryptedValue, string $key): ?string
|
||||
{
|
||||
$decoded = base64_decode($encryptedValue);
|
||||
if (!$decoded || !str_contains($decoded, '|')) return null;
|
||||
|
||||
[$iv, $encrypted] = explode('|', $decoded, 2);
|
||||
$decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, $iv);
|
||||
|
||||
return $decrypted !== false ? $decrypted : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an encrypted cookie
|
||||
*/
|
||||
public function setEncrypted(string $name, string $value, string $key, array $options = []): bool
|
||||
{
|
||||
$encryptedValue = $this->encrypt($value, $key);
|
||||
return $this->set($name, $encryptedValue, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and decrypt a cookie
|
||||
*/
|
||||
public function getEncrypted(string $name, string $key, ?string $default = null): ?string
|
||||
{
|
||||
$encryptedValue = $this->get($name);
|
||||
if ($encryptedValue === null) return $default;
|
||||
|
||||
$value = $this->decrypt($encryptedValue, $key);
|
||||
return $value !== null ? $value : $default;
|
||||
}
|
||||
}
|
||||
17
Response.php
17
Response.php
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/Cookies.php';
|
||||
|
||||
/**
|
||||
* Response class represents an HTTP response
|
||||
*/
|
||||
@ -9,6 +11,16 @@ class Response
|
||||
private array $headers = [];
|
||||
private string $body = '';
|
||||
private bool $sent = false;
|
||||
private ?Cookies $cookie = null;
|
||||
|
||||
/**
|
||||
* Set the cookie manager for this response
|
||||
*/
|
||||
public function setCookieManager(Cookies $cookie): Response
|
||||
{
|
||||
$this->cookie = $cookie;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the HTTP status code for the response
|
||||
@ -76,6 +88,10 @@ class Response
|
||||
*/
|
||||
public function cookie(string $name, string $value, array $options = []): Response
|
||||
{
|
||||
if ($this->cookie) {
|
||||
$this->cookie->set($name, $value, $options);
|
||||
} else {
|
||||
// Fallback to direct setcookie if no Cookie manager is set
|
||||
$options = array_merge([
|
||||
'expires' => 0,
|
||||
'path' => '/',
|
||||
@ -93,6 +109,7 @@ class Response
|
||||
'httponly' => $options['httponly'],
|
||||
'samesite' => $options['samesite']
|
||||
]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
7
Web.php
7
Web.php
@ -6,6 +6,7 @@ require_once __DIR__ . '/Response.php';
|
||||
require_once __DIR__ . '/Context.php';
|
||||
require_once __DIR__ . '/Router.php';
|
||||
require_once __DIR__ . '/ErrorHandler.php';
|
||||
require_once __DIR__ . '/Cookies.php';
|
||||
|
||||
/**
|
||||
* Web is the application controller itself
|
||||
@ -16,11 +17,13 @@ class Web
|
||||
private array $middleware = [];
|
||||
private Context $context;
|
||||
private ErrorHandler $errorHandler;
|
||||
private Cookies $cookie;
|
||||
|
||||
public function __construct(bool $debug = false)
|
||||
public function __construct(bool $debug = false, array $cookieDefaults = [])
|
||||
{
|
||||
$this->router = new Router();
|
||||
$this->errorHandler = new ErrorHandler($debug);
|
||||
$this->cookie = new Cookies($cookieDefaults);
|
||||
}
|
||||
|
||||
public function use(callable $middleware): self
|
||||
@ -118,7 +121,7 @@ class Web
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$this->context = new Context();
|
||||
$this->context = new Context($this->cookie);
|
||||
|
||||
try {
|
||||
$next = function() {
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
require_once __DIR__ . '/User.php';
|
||||
require_once __DIR__ . '/../Session.php';
|
||||
require_once __DIR__ . '/../Cookies.php';
|
||||
|
||||
/**
|
||||
* Simplified Auth handles user authentication with external verification
|
||||
@ -9,6 +10,7 @@ require_once __DIR__ . '/../Session.php';
|
||||
class Auth
|
||||
{
|
||||
private Session $session;
|
||||
private Cookies $cookie;
|
||||
private ?User $user = null;
|
||||
private array $config;
|
||||
|
||||
@ -16,17 +18,17 @@ class Auth
|
||||
const REMEMBER_COOKIE = 'remember_token';
|
||||
const REMEMBER_DURATION = 2592000; // 30 days in seconds
|
||||
|
||||
public function __construct(Session $session, array $config = [])
|
||||
public function __construct(Session $session, ?Cookie $cookie = null, array $config = [])
|
||||
{
|
||||
$this->session = $session;
|
||||
$this->cookie = $cookie ?: new Cookies([
|
||||
'path' => '/',
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax'
|
||||
]);
|
||||
$this->config = array_merge([
|
||||
'cookie_name' => self::REMEMBER_COOKIE,
|
||||
'cookie_lifetime' => self::REMEMBER_DURATION,
|
||||
'cookie_path' => '/',
|
||||
'cookie_domain' => '',
|
||||
'cookie_secure' => false,
|
||||
'cookie_httponly' => true,
|
||||
'cookie_samesite' => 'Lax'
|
||||
'cookie_lifetime' => self::REMEMBER_DURATION
|
||||
], $config);
|
||||
|
||||
$this->initializeFromSession();
|
||||
@ -176,17 +178,10 @@ class Auth
|
||||
*/
|
||||
private function setRememberCookie(string $value): void
|
||||
{
|
||||
setcookie(
|
||||
$this->cookie->setWithLifetime(
|
||||
$this->config['cookie_name'],
|
||||
$value,
|
||||
[
|
||||
'expires' => time() + $this->config['cookie_lifetime'],
|
||||
'path' => $this->config['cookie_path'],
|
||||
'domain' => $this->config['cookie_domain'],
|
||||
'secure' => $this->config['cookie_secure'],
|
||||
'httponly' => $this->config['cookie_httponly'],
|
||||
'samesite' => $this->config['cookie_samesite']
|
||||
]
|
||||
$this->config['cookie_lifetime']
|
||||
);
|
||||
}
|
||||
|
||||
@ -195,18 +190,7 @@ class Auth
|
||||
*/
|
||||
private function clearRememberCookie(): void
|
||||
{
|
||||
setcookie(
|
||||
$this->config['cookie_name'],
|
||||
'',
|
||||
[
|
||||
'expires' => time() - 3600,
|
||||
'path' => $this->config['cookie_path'],
|
||||
'domain' => $this->config['cookie_domain'],
|
||||
'secure' => $this->config['cookie_secure'],
|
||||
'httponly' => $this->config['cookie_httponly'],
|
||||
'samesite' => $this->config['cookie_samesite']
|
||||
]
|
||||
);
|
||||
$this->cookie->delete($this->config['cookie_name']);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,7 +198,7 @@ class Auth
|
||||
*/
|
||||
private function getUserDataFromRememberCookie(): ?array
|
||||
{
|
||||
$cookie = $_COOKIE[$this->config['cookie_name']] ?? null;
|
||||
$cookie = $this->cookie->get($this->config['cookie_name']);
|
||||
|
||||
if (!$cookie || !str_contains($cookie, '|')) return null;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user