add all files

This commit is contained in:
Rucus
2026-02-17 09:29:34 -06:00
parent b8c8d67c67
commit 782d203799
21925 changed files with 2433086 additions and 0 deletions

263
lasuca/api/helpers/jwt.php Normal file
View File

@@ -0,0 +1,263 @@
<?php
// phpcs:ignoreFile
/**
* JWT Token Helpers
*/
declare(strict_types=1);
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\ExpiredException;
/**
* Get the JWT secret key from environment or fallback.
*/
function jwt_get_secret(): string
{
$secret = getenv('JWT_SECRET');
if ($secret === false || $secret === '') {
// Fallback for development - MUST be set in production
$secret = 'lasuca-dev-secret-change-in-production-' . md5(__DIR__);
}
return $secret;
}
/**
* Generate an access token for a user.
*/
function jwt_create_access_token(array $user): string
{
$issuedAt = time();
$expiresAt = $issuedAt + (15 * 60); // 15 minutes
$payload = [
'iss' => 'lasuca-api',
'iat' => $issuedAt,
'exp' => $expiresAt,
'sub' => $user['username'],
'type' => 'access',
'user' => [
'username' => $user['username'],
'growerid' => $user['growerid'] ?? null,
'growername' => $user['growername'] ?? null,
'email' => $user['email'] ?? null,
],
];
return JWT::encode($payload, jwt_get_secret(), 'HS256');
}
/**
* Generate a refresh token for a user.
*/
function jwt_create_refresh_token(array $user): array
{
$issuedAt = time();
$expiresAt = $issuedAt + (30 * 24 * 60 * 60); // 30 days
$tokenId = bin2hex(random_bytes(32));
$payload = [
'iss' => 'lasuca-api',
'iat' => $issuedAt,
'exp' => $expiresAt,
'sub' => $user['username'],
'type' => 'refresh',
'jti' => $tokenId,
];
$token = JWT::encode($payload, jwt_get_secret(), 'HS256');
return [
'token' => $token,
'token_id' => $tokenId,
'expires_at' => date('Y-m-d H:i:s', $expiresAt),
];
}
/**
* Validate and decode a JWT token.
*/
function jwt_decode_token(string $token): ?array
{
try {
$decoded = JWT::decode($token, new Key(jwt_get_secret(), 'HS256'));
return (array) $decoded;
} catch (ExpiredException $e) {
return null;
} catch (\Exception $e) {
return null;
}
}
/**
* Extract Bearer token from Authorization header.
*/
function jwt_get_bearer_token(): ?string
{
$headers = null;
if (isset($_SERVER['Authorization'])) {
$headers = trim($_SERVER['Authorization']);
} elseif (isset($_SERVER['HTTP_AUTHORIZATION'])) {
$headers = trim($_SERVER['HTTP_AUTHORIZATION']);
} elseif (function_exists('apache_request_headers')) {
$requestHeaders = apache_request_headers();
$requestHeaders = array_combine(
array_map('ucwords', array_keys($requestHeaders)),
array_values($requestHeaders)
);
if (isset($requestHeaders['Authorization'])) {
$headers = trim($requestHeaders['Authorization']);
}
}
if ($headers === null) {
return null;
}
if (preg_match('/Bearer\s+(\S+)/i', $headers, $matches)) {
return $matches[1];
}
return null;
}
/**
* Require valid access token and return user data.
*/
function jwt_require_auth(): array
{
$token = jwt_get_bearer_token();
if ($token === null) {
api_error('Authorization required', 401);
}
$payload = jwt_decode_token($token);
if ($payload === null) {
api_error('Invalid or expired token', 401);
}
if (!isset($payload['type']) || $payload['type'] !== 'access') {
api_error('Invalid token type', 401);
}
if (!isset($payload['user']) || !is_array((array) $payload['user'])) {
api_error('Invalid token payload', 401);
}
return (array) $payload['user'];
}
/**
* Store refresh token in database.
*/
function jwt_store_refresh_token(string $username, string $tokenId, string $expiresAt): bool
{
global $conn;
$sql = "INSERT INTO refresh_tokens (username, token_id, expires_at, created_at)
VALUES (?, ?, ?, NOW())";
$stmt = $conn->prepare($sql);
if ($stmt === false) {
return false;
}
$stmt->bind_param('sss', $username, $tokenId, $expiresAt);
$success = $stmt->execute();
$stmt->close();
return $success;
}
/**
* Validate refresh token exists and is not revoked.
*/
function jwt_validate_refresh_token(string $username, string $tokenId): bool
{
global $conn;
$sql = "SELECT id FROM refresh_tokens
WHERE username = ?
AND token_id = ?
AND expires_at > NOW()
AND revoked_at IS NULL
LIMIT 1";
$stmt = $conn->prepare($sql);
if ($stmt === false) {
return false;
}
$stmt->bind_param('ss', $username, $tokenId);
$stmt->execute();
$result = $stmt->get_result();
$exists = $result->num_rows > 0;
$stmt->close();
return $exists;
}
/**
* Revoke a specific refresh token.
*/
function jwt_revoke_refresh_token(string $tokenId): bool
{
global $conn;
$sql = "UPDATE refresh_tokens SET revoked_at = NOW() WHERE token_id = ?";
$stmt = $conn->prepare($sql);
if ($stmt === false) {
return false;
}
$stmt->bind_param('s', $tokenId);
$success = $stmt->execute();
$stmt->close();
return $success;
}
/**
* Revoke all refresh tokens for a user.
*/
function jwt_revoke_all_user_tokens(string $username): bool
{
global $conn;
$sql = "UPDATE refresh_tokens SET revoked_at = NOW() WHERE username = ? AND revoked_at IS NULL";
$stmt = $conn->prepare($sql);
if ($stmt === false) {
return false;
}
$stmt->bind_param('s', $username);
$success = $stmt->execute();
$stmt->close();
return $success;
}
/**
* Clean up expired tokens (call periodically).
*/
function jwt_cleanup_expired_tokens(): int
{
global $conn;
$sql = "DELETE FROM refresh_tokens WHERE expires_at < NOW()";
$result = $conn->query($sql);
return $result ? $conn->affected_rows : 0;
}

View File

@@ -0,0 +1,84 @@
<?php
// phpcs:ignoreFile
/**
* API Response Helpers
*/
declare(strict_types=1);
/**
* Send a JSON success response.
*/
function api_success(array $data = [], int $statusCode = 200): void
{
http_response_code($statusCode);
echo json_encode([
'success' => true,
'data' => $data,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit();
}
/**
* Send a JSON error response.
*/
function api_error(string $message, int $statusCode = 400, array $details = []): void
{
http_response_code($statusCode);
$response = [
'success' => false,
'error' => [
'message' => $message,
'code' => $statusCode,
],
];
if (!empty($details)) {
$response['error']['details'] = $details;
}
echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit();
}
/**
* Get JSON body from request.
*/
function api_get_json_body(): array
{
$rawBody = file_get_contents('php://input');
if ($rawBody === '' || $rawBody === false) {
return [];
}
$decoded = json_decode($rawBody, true);
if (!is_array($decoded)) {
return [];
}
return $decoded;
}
/**
* Require specific fields in request body.
*/
function api_require_fields(array $body, array $fields): void
{
$missing = [];
foreach ($fields as $field) {
if (!isset($body[$field]) || (is_string($body[$field]) && trim($body[$field]) === '')) {
$missing[] = $field;
}
}
if (!empty($missing)) {
api_error('Missing required fields: ' . implode(', ', $missing), 422);
}
}