create-prisma-php-app 3.1.11 → 4.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap.php +10 -10
- package/dist/index.js +12 -8
- package/dist/settings/restart-websocket.bat +1 -1
- package/dist/settings/restart-websocket.ts +2 -9
- package/dist/src/{Lib/Websocket → Websocket}/ConnectionManager.php +1 -1
- package/dist/src/app/error.php +1 -1
- package/dist/src/app/index.php +1 -1
- package/dist/src/app/js/index.js +1 -1
- package/dist/src/app/layout.php +2 -2
- package/dist/{src/Lib/Websocket/websocket-server.php → websocket-server.php} +2 -7
- package/package.json +1 -1
- package/dist/src/Lib/AI/ChatGPTClient.php +0 -147
- package/dist/src/Lib/Auth/Auth.php +0 -544
- package/dist/src/Lib/Auth/AuthConfig.php +0 -89
- package/dist/src/Lib/CacheHandler.php +0 -121
- package/dist/src/Lib/ErrorHandler.php +0 -322
- package/dist/src/Lib/FileManager/UploadFile.php +0 -383
- package/dist/src/Lib/Headers/Boom.php +0 -208
- package/dist/src/Lib/IncludeTracker.php +0 -59
- package/dist/src/Lib/MainLayout.php +0 -215
- package/dist/src/Lib/Middleware/AuthMiddleware.php +0 -154
- package/dist/src/Lib/PHPMailer/Mailer.php +0 -169
- package/dist/src/Lib/PHPX/Exceptions/ComponentValidationException.php +0 -49
- package/dist/src/Lib/PHPX/IPHPX.php +0 -22
- package/dist/src/Lib/PHPX/PHPX.php +0 -173
- package/dist/src/Lib/PHPX/TemplateCompiler.php +0 -571
- package/dist/src/Lib/PHPX/TwMerge.php +0 -195
- package/dist/src/Lib/PHPX/TypeCoercer.php +0 -490
- package/dist/src/Lib/PartialRenderer.php +0 -40
- package/dist/src/Lib/PrismaPHPSettings.php +0 -181
- package/dist/src/Lib/Request.php +0 -476
- package/dist/src/Lib/Set.php +0 -102
- package/dist/src/Lib/StateManager.php +0 -127
- package/dist/src/Lib/Validator.php +0 -738
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Lib\AI;
|
|
6
|
-
|
|
7
|
-
use GuzzleHttp\Client;
|
|
8
|
-
use GuzzleHttp\Exception\RequestException;
|
|
9
|
-
use Lib\Validator;
|
|
10
|
-
use RuntimeException;
|
|
11
|
-
|
|
12
|
-
class ChatGPTClient
|
|
13
|
-
{
|
|
14
|
-
private Client $client;
|
|
15
|
-
private string $apiUrl = '';
|
|
16
|
-
private string $apiKey = '';
|
|
17
|
-
private array $cache = [];
|
|
18
|
-
|
|
19
|
-
public function __construct(?Client $client = null)
|
|
20
|
-
{
|
|
21
|
-
// Initialize the Guzzle HTTP client, allowing for dependency injection
|
|
22
|
-
$this->client = $client ?: new Client();
|
|
23
|
-
|
|
24
|
-
// API URL for chat completions
|
|
25
|
-
$this->apiUrl = 'https://api.openai.com/v1/chat/completions';
|
|
26
|
-
|
|
27
|
-
// Get the API key from environment variables (keep this private and secure)
|
|
28
|
-
$this->apiKey = $_ENV['CHATGPT_API_KEY'];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Determines the appropriate model based on internal logic.
|
|
33
|
-
*
|
|
34
|
-
* @param array $conversationHistory The conversation history array.
|
|
35
|
-
* @return string The model name to be used.
|
|
36
|
-
*/
|
|
37
|
-
protected function determineModel(array $conversationHistory): string
|
|
38
|
-
{
|
|
39
|
-
$messageCount = count($conversationHistory);
|
|
40
|
-
$totalTokens = array_reduce(
|
|
41
|
-
$conversationHistory,
|
|
42
|
-
fn($carry, $item) => $carry + str_word_count($item['content'] ?? ''),
|
|
43
|
-
0
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
// If the conversation is long or complex, use a model with more tokens
|
|
47
|
-
if ($totalTokens > 4000 || $messageCount > 10) {
|
|
48
|
-
return 'gpt-3.5-turbo-16k'; // Use the model with a larger token limit
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Default to the standard model for shorter conversations
|
|
52
|
-
return 'gpt-3.5-turbo';
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Formats the conversation history to ensure it is valid.
|
|
57
|
-
*
|
|
58
|
-
* @param array $conversationHistory The conversation history array.
|
|
59
|
-
* @return array The formatted conversation history.
|
|
60
|
-
*/
|
|
61
|
-
protected function formatConversationHistory(array $conversationHistory): array
|
|
62
|
-
{
|
|
63
|
-
$formattedHistory = [];
|
|
64
|
-
foreach ($conversationHistory as $message) {
|
|
65
|
-
if (is_array($message) && isset($message['role'], $message['content']) && Validator::string($message['content'])) {
|
|
66
|
-
$formattedHistory[] = $message;
|
|
67
|
-
} else {
|
|
68
|
-
$formattedHistory[] = ['role' => 'user', 'content' => (string) $message];
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return $formattedHistory;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Sends a message to the OpenAI API and returns the AI's response as HTML.
|
|
76
|
-
*
|
|
77
|
-
* @param array $conversationHistory The conversation history array containing previous messages.
|
|
78
|
-
* @param string $userMessage The new user message to add to the conversation.
|
|
79
|
-
* @return string The AI-generated HTML response.
|
|
80
|
-
*
|
|
81
|
-
* @throws \InvalidArgumentException If a message in the conversation history is not valid.
|
|
82
|
-
* @throws RuntimeException If the API request fails or returns an unexpected format.
|
|
83
|
-
*/
|
|
84
|
-
public function sendMessage(array $conversationHistory, string $userMessage): string
|
|
85
|
-
{
|
|
86
|
-
if (!Validator::string($userMessage)) {
|
|
87
|
-
throw new \InvalidArgumentException("Invalid user message: must be a string.");
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Optional: Convert emojis or special patterns in the message
|
|
91
|
-
$userMessage = Validator::emojis($userMessage);
|
|
92
|
-
|
|
93
|
-
// Prepare the conversation, including a system-level instruction to return valid HTML
|
|
94
|
-
$systemInstruction = [
|
|
95
|
-
'role' => 'system',
|
|
96
|
-
'content' => 'You are ChatGPT. Please provide your response in valid HTML format.'
|
|
97
|
-
];
|
|
98
|
-
|
|
99
|
-
// Format existing history, then prepend the system message
|
|
100
|
-
$formattedHistory = $this->formatConversationHistory($conversationHistory);
|
|
101
|
-
array_unshift($formattedHistory, $systemInstruction);
|
|
102
|
-
|
|
103
|
-
// Append the new user message
|
|
104
|
-
$formattedHistory[] = ['role' => 'user', 'content' => $userMessage];
|
|
105
|
-
|
|
106
|
-
// Check cache
|
|
107
|
-
$cacheKey = md5(serialize($formattedHistory));
|
|
108
|
-
if (isset($this->cache[$cacheKey])) {
|
|
109
|
-
return $this->cache[$cacheKey];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Determine the appropriate model to use
|
|
113
|
-
$model = $this->determineModel($formattedHistory);
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
// Sending a POST request to the AI API
|
|
117
|
-
$response = $this->client->request('POST', $this->apiUrl, [
|
|
118
|
-
'headers' => [
|
|
119
|
-
'Authorization' => 'Bearer ' . $this->apiKey,
|
|
120
|
-
'Content-Type' => 'application/json',
|
|
121
|
-
],
|
|
122
|
-
'json' => [
|
|
123
|
-
'model' => $model,
|
|
124
|
-
'messages' => $formattedHistory,
|
|
125
|
-
'max_tokens' => 500,
|
|
126
|
-
],
|
|
127
|
-
]);
|
|
128
|
-
|
|
129
|
-
$responseBody = $response->getBody();
|
|
130
|
-
$responseContent = json_decode((string) $responseBody, true);
|
|
131
|
-
|
|
132
|
-
// Check if response is in expected format
|
|
133
|
-
if (isset($responseContent['choices'][0]['message']['content'])) {
|
|
134
|
-
$aiMessage = $responseContent['choices'][0]['message']['content'];
|
|
135
|
-
|
|
136
|
-
// Cache the result
|
|
137
|
-
$this->cache[$cacheKey] = $aiMessage;
|
|
138
|
-
|
|
139
|
-
return $aiMessage;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
throw new RuntimeException('Unexpected API response format.');
|
|
143
|
-
} catch (RequestException $e) {
|
|
144
|
-
throw new RuntimeException("API request failed: " . $e->getMessage(), 0, $e);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
@@ -1,544 +0,0 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Lib\Auth;
|
|
6
|
-
|
|
7
|
-
use Firebase\JWT\JWT;
|
|
8
|
-
use Firebase\JWT\Key;
|
|
9
|
-
use DateInterval;
|
|
10
|
-
use DateTime;
|
|
11
|
-
use Lib\Validator;
|
|
12
|
-
use GuzzleHttp\Client;
|
|
13
|
-
use GuzzleHttp\Exception\RequestException;
|
|
14
|
-
use Lib\Request;
|
|
15
|
-
use Exception;
|
|
16
|
-
use InvalidArgumentException;
|
|
17
|
-
use ArrayObject;
|
|
18
|
-
|
|
19
|
-
class Auth
|
|
20
|
-
{
|
|
21
|
-
public const PAYLOAD_NAME = 'payload_name_8639D';
|
|
22
|
-
public const ROLE_NAME = '';
|
|
23
|
-
public const PAYLOAD_SESSION_KEY = 'payload_session_key_2183A';
|
|
24
|
-
|
|
25
|
-
public static string $cookieName = '';
|
|
26
|
-
|
|
27
|
-
private static ?Auth $instance = null;
|
|
28
|
-
private const PPHPAUTH = 'pphpauth';
|
|
29
|
-
private string $secretKey;
|
|
30
|
-
private string $defaultTokenValidity = '1h'; // Default to 1 hour
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Private constructor to prevent direct instantiation.
|
|
34
|
-
* Use Auth::getInstance() to get the singleton instance.
|
|
35
|
-
*/
|
|
36
|
-
private function __construct()
|
|
37
|
-
{
|
|
38
|
-
$this->secretKey = $_ENV['AUTH_SECRET'] ?? 'CD24eEv4qbsC5LOzqeaWbcr58mBMSvA4Mkii8GjRiHkt';
|
|
39
|
-
self::$cookieName = self::getCookieName();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Returns the singleton instance of the Auth class.
|
|
44
|
-
*
|
|
45
|
-
* @return Auth The singleton instance.
|
|
46
|
-
*/
|
|
47
|
-
public static function getInstance(): Auth
|
|
48
|
-
{
|
|
49
|
-
if (self::$instance === null) {
|
|
50
|
-
self::$instance = new self();
|
|
51
|
-
}
|
|
52
|
-
return self::$instance;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Authenticates a user and generates a JWT (JSON Web Token) based on the specified user data
|
|
57
|
-
* and token validity duration. The method first checks if the secret key is set, calculates
|
|
58
|
-
* the token's expiration time, sets the necessary payload, and encodes it into a JWT.
|
|
59
|
-
* If possible (HTTP headers not yet sent), it also sets cookies with the JWT for client-side storage.
|
|
60
|
-
*
|
|
61
|
-
* @param mixed $data User data which can be a simple string or an instance of AuthRole.
|
|
62
|
-
* If an instance of AuthRole is provided, its `value` property will be used as the role in the token.
|
|
63
|
-
* @param string|null $tokenValidity Optional parameter specifying the duration the token is valid for (e.g., '10m', '1h').
|
|
64
|
-
* If null, the default validity period set in the class property is used, which is 1 hour.
|
|
65
|
-
* The format should be a number followed by a time unit ('s' for seconds, 'm' for minutes,
|
|
66
|
-
* 'h' for hours, 'd' for days), and this is parsed to calculate the exact expiration time.
|
|
67
|
-
*
|
|
68
|
-
* @return string Returns the encoded JWT as a string.
|
|
69
|
-
*
|
|
70
|
-
* @throws InvalidArgumentException Thrown if the secret key is not set or if the duration format is invalid.
|
|
71
|
-
*
|
|
72
|
-
* Example:
|
|
73
|
-
* $auth = Auth::getInstance();
|
|
74
|
-
* $auth->setSecretKey('your_secret_key');
|
|
75
|
-
* try {
|
|
76
|
-
* $jwt = $auth->signIn('Admin', '1h');
|
|
77
|
-
* echo "JWT: " . $jwt;
|
|
78
|
-
* } catch (InvalidArgumentException $e) {
|
|
79
|
-
* echo "Error: " . $e->getMessage();
|
|
80
|
-
* }
|
|
81
|
-
*/
|
|
82
|
-
public function signIn($data, ?string $tokenValidity = null): string
|
|
83
|
-
{
|
|
84
|
-
if (!$this->secretKey) {
|
|
85
|
-
throw new InvalidArgumentException("Secret key is required for authentication.");
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
$expirationTime = $this->calculateExpirationTime($tokenValidity ?? $this->defaultTokenValidity);
|
|
89
|
-
|
|
90
|
-
if ($data instanceof AuthRole) {
|
|
91
|
-
$data = $data->value;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
$payload = [
|
|
95
|
-
self::PAYLOAD_NAME => $data,
|
|
96
|
-
'exp' => $expirationTime,
|
|
97
|
-
];
|
|
98
|
-
|
|
99
|
-
// Set the payload in the session
|
|
100
|
-
$_SESSION[self::PAYLOAD_SESSION_KEY] = $payload;
|
|
101
|
-
|
|
102
|
-
// Encode the JWT
|
|
103
|
-
$jwt = JWT::encode($payload, $this->secretKey, 'HS256');
|
|
104
|
-
|
|
105
|
-
if (!headers_sent()) {
|
|
106
|
-
$this->setCookies($jwt, $expirationTime);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return $jwt;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Checks if the user is authenticated based on the presence of the payload in the session.
|
|
114
|
-
* Returns true if the user is authenticated, false otherwise.
|
|
115
|
-
*
|
|
116
|
-
* @return bool Returns true if the user is authenticated, false otherwise.
|
|
117
|
-
*/
|
|
118
|
-
public function isAuthenticated(): bool
|
|
119
|
-
{
|
|
120
|
-
if (!isset($_COOKIE[self::$cookieName])) {
|
|
121
|
-
unset($_SESSION[self::PAYLOAD_SESSION_KEY]);
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (Request::$fileToInclude === 'route.php') {
|
|
126
|
-
$bearerToken = Request::getBearerToken();
|
|
127
|
-
$verifyBearerToken = $this->verifyToken($bearerToken);
|
|
128
|
-
if (!$verifyBearerToken) {
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
$jwt = $_COOKIE[self::$cookieName];
|
|
134
|
-
$verifyToken = $this->verifyToken($jwt);
|
|
135
|
-
if ($verifyToken === false) {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (!isset($_SESSION[self::PAYLOAD_SESSION_KEY])) {
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
private function calculateExpirationTime(string $duration): int
|
|
147
|
-
{
|
|
148
|
-
$now = new DateTime();
|
|
149
|
-
$interval = $this->convertDurationToInterval($duration);
|
|
150
|
-
$futureDate = $now->add($interval);
|
|
151
|
-
return $futureDate->getTimestamp();
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
private function convertDurationToInterval(string $duration): DateInterval
|
|
155
|
-
{
|
|
156
|
-
if (preg_match('/^(\d+)(s|m|h|d)$/', $duration, $matches)) {
|
|
157
|
-
$value = (int)$matches[1];
|
|
158
|
-
$unit = $matches[2];
|
|
159
|
-
|
|
160
|
-
switch ($unit) {
|
|
161
|
-
case 's':
|
|
162
|
-
return new DateInterval("PT{$value}S");
|
|
163
|
-
case 'm':
|
|
164
|
-
return new DateInterval("PT{$value}M");
|
|
165
|
-
case 'h':
|
|
166
|
-
return new DateInterval("PT{$value}H");
|
|
167
|
-
case 'd':
|
|
168
|
-
return new DateInterval("P{$value}D");
|
|
169
|
-
default:
|
|
170
|
-
throw new InvalidArgumentException("Invalid duration format: {$duration}");
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
throw new InvalidArgumentException("Invalid duration format: {$duration}");
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Verifies the JWT token and returns the decoded payload if the token is valid.
|
|
179
|
-
* If the token is invalid or expired, null is returned.
|
|
180
|
-
*
|
|
181
|
-
* @param string $jwt The JWT token to verify.
|
|
182
|
-
* @return object|null Returns the decoded payload if the token is valid, or null if invalid or expired.
|
|
183
|
-
*/
|
|
184
|
-
public function verifyToken(?string $jwt): ?object
|
|
185
|
-
{
|
|
186
|
-
try {
|
|
187
|
-
if (!$jwt) {
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
$token = JWT::decode($jwt, new Key($this->secretKey, 'HS256'));
|
|
192
|
-
|
|
193
|
-
if (empty($token->{Auth::PAYLOAD_NAME})) {
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (isset($token->exp) && time() >= $token->exp) {
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return $token;
|
|
202
|
-
} catch (Exception) {
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Refreshes the JWT token by updating the expiration time and encoding the new payload into a JWT.
|
|
209
|
-
* If the token validity duration is not specified, the default token validity period is used.
|
|
210
|
-
* If possible (HTTP headers not yet sent), it also sets cookies with the new JWT for client-side storage.
|
|
211
|
-
*
|
|
212
|
-
* @param string $jwt The JWT token to refresh.
|
|
213
|
-
* @param string|null $tokenValidity Optional parameter specifying the duration the token is valid for (e.g., '10m', '1h').
|
|
214
|
-
* If null, the default validity period set in the class property is used.
|
|
215
|
-
* The format should be a number followed by a time unit ('s' for seconds, 'm' for minutes,
|
|
216
|
-
* 'h' for hours, 'd' for days), and this is parsed to calculate the exact expiration time.
|
|
217
|
-
*
|
|
218
|
-
* @return string Returns the refreshed JWT as a string.
|
|
219
|
-
*
|
|
220
|
-
* @throws InvalidArgumentException Thrown if the token is invalid.
|
|
221
|
-
*/
|
|
222
|
-
public function refreshToken(string $jwt, ?string $tokenValidity = null): string
|
|
223
|
-
{
|
|
224
|
-
$decodedToken = $this->verifyToken($jwt);
|
|
225
|
-
|
|
226
|
-
if (!$decodedToken) {
|
|
227
|
-
throw new InvalidArgumentException("Invalid token.");
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
$expirationTime = $this->calculateExpirationTime($tokenValidity ?? $this->defaultTokenValidity);
|
|
231
|
-
|
|
232
|
-
$decodedToken->exp = $expirationTime;
|
|
233
|
-
$newJwt = JWT::encode((array)$decodedToken, $this->secretKey, 'HS256');
|
|
234
|
-
|
|
235
|
-
if (!headers_sent()) {
|
|
236
|
-
$this->setCookies($newJwt, $expirationTime);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return $newJwt;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
protected function setCookies(string $jwt, int $expirationTime)
|
|
243
|
-
{
|
|
244
|
-
if (!headers_sent()) {
|
|
245
|
-
setcookie(self::$cookieName, $jwt, [
|
|
246
|
-
'expires' => $expirationTime,
|
|
247
|
-
'path' => '/', // Set the path to '/' to make the cookie available site-wide
|
|
248
|
-
'domain' => '', // Specify your domain
|
|
249
|
-
'secure' => true, // Set to true if using HTTPS
|
|
250
|
-
'httponly' => true, // Prevent JavaScript access to the cookie
|
|
251
|
-
'samesite' => 'Lax', // or 'Strict' depending on your requirements
|
|
252
|
-
]);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Logs out the user by unsetting the session payload and deleting the authentication cookie.
|
|
258
|
-
* If a redirect URL is provided, the user is redirected to that URL after logging out.
|
|
259
|
-
*
|
|
260
|
-
* @param string|null $redirect Optional parameter specifying the URL to redirect to after logging out.
|
|
261
|
-
*
|
|
262
|
-
* Example:
|
|
263
|
-
* $auth = Auth::getInstance();
|
|
264
|
-
* $auth->signOut('/login');
|
|
265
|
-
*
|
|
266
|
-
* @return void
|
|
267
|
-
*/
|
|
268
|
-
public function signOut(?string $redirect = null)
|
|
269
|
-
{
|
|
270
|
-
if (isset($_COOKIE[self::$cookieName])) {
|
|
271
|
-
unset($_COOKIE[self::$cookieName]);
|
|
272
|
-
setcookie(self::$cookieName, '', time() - 3600, '/');
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (isset($_SESSION[self::PAYLOAD_SESSION_KEY])) {
|
|
276
|
-
unset($_SESSION[self::PAYLOAD_SESSION_KEY]);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if ($redirect) {
|
|
280
|
-
Request::redirect($redirect);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Returns the role of the authenticated user based on the payload stored in the session.
|
|
286
|
-
* If the user is not authenticated, null is returned.
|
|
287
|
-
*
|
|
288
|
-
* @return mixed|null Returns the role of the authenticated user or null if the user is not authenticated.
|
|
289
|
-
*/
|
|
290
|
-
public function getPayload()
|
|
291
|
-
{
|
|
292
|
-
if (isset($_SESSION[self::PAYLOAD_SESSION_KEY])) {
|
|
293
|
-
$value = $_SESSION[self::PAYLOAD_SESSION_KEY][self::PAYLOAD_NAME];
|
|
294
|
-
return is_array($value) ? new ArrayObject($value, ArrayObject::ARRAY_AS_PROPS) : $value;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return null;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
private function exchangeCode($data, $apiUrl)
|
|
301
|
-
{
|
|
302
|
-
try {
|
|
303
|
-
$client = new Client();
|
|
304
|
-
$response = $client->post($apiUrl, [
|
|
305
|
-
'headers' => [
|
|
306
|
-
'Accept' => 'application/json',
|
|
307
|
-
],
|
|
308
|
-
'form_params' => $data,
|
|
309
|
-
]);
|
|
310
|
-
|
|
311
|
-
if ($response->getStatusCode() === 200) {
|
|
312
|
-
return json_decode($response->getBody()->getContents());
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return false;
|
|
316
|
-
} catch (RequestException) {
|
|
317
|
-
return false;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
private function saveAuthInfo($responseInfo, $accountData)
|
|
322
|
-
{
|
|
323
|
-
// Save user data to the database
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
private function findProvider(array $providers, string $type): ?object
|
|
327
|
-
{
|
|
328
|
-
foreach ($providers as $provider) {
|
|
329
|
-
if (is_object($provider) && get_class($provider) === $type) {
|
|
330
|
-
return $provider;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Authenticates a user using OAuth providers such as Google or GitHub.
|
|
338
|
-
* The method first checks if the request is a GET request and if the route is a sign-in route.
|
|
339
|
-
* It then processes the authentication code received from the provider and retrieves the user's data.
|
|
340
|
-
* The user data is saved to the database, and the user is authenticated using the authenticate method.
|
|
341
|
-
*
|
|
342
|
-
* @param mixed ...$providers An array of provider objects such as GoogleProvider or GithubProvider.
|
|
343
|
-
*
|
|
344
|
-
* Example:
|
|
345
|
-
* $auth = Auth::getInstance();
|
|
346
|
-
* $auth->authProviders(new GoogleProvider('client_id', 'client_secret', 'redirect_uri'));
|
|
347
|
-
*/
|
|
348
|
-
public function authProviders(...$providers)
|
|
349
|
-
{
|
|
350
|
-
$dynamicRouteParams = Request::$dynamicParams[self::PPHPAUTH] ?? [];
|
|
351
|
-
|
|
352
|
-
if (Request::$isGet && in_array('signin', $dynamicRouteParams)) {
|
|
353
|
-
foreach ($providers as $provider) {
|
|
354
|
-
if ($provider instanceof GithubProvider && in_array('github', $dynamicRouteParams)) {
|
|
355
|
-
$githubAuthUrl = "https://github.com/login/oauth/authorize?scope=user:email%20read:user&client_id={$provider->clientId}";
|
|
356
|
-
Request::redirect($githubAuthUrl);
|
|
357
|
-
} elseif ($provider instanceof GoogleProvider && in_array('google', $dynamicRouteParams)) {
|
|
358
|
-
$googleAuthUrl = "https://accounts.google.com/o/oauth2/v2/auth?"
|
|
359
|
-
. "scope=" . urlencode('email profile') . "&"
|
|
360
|
-
. "response_type=code&"
|
|
361
|
-
. "client_id=" . urlencode($provider->clientId) . "&"
|
|
362
|
-
. "redirect_uri=" . urlencode($provider->redirectUri);
|
|
363
|
-
Request::redirect($googleAuthUrl);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
$authCode = Validator::string($_GET['code'] ?? '');
|
|
369
|
-
|
|
370
|
-
if (Request::$isGet && in_array('callback', $dynamicRouteParams) && isset($authCode)) {
|
|
371
|
-
if (in_array('github', $dynamicRouteParams)) {
|
|
372
|
-
$provider = $this->findProvider($providers, GithubProvider::class);
|
|
373
|
-
|
|
374
|
-
if (!$provider) {
|
|
375
|
-
exit("Error occurred. Please try again.");
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return $this->githubProvider($provider, $authCode);
|
|
379
|
-
} elseif (in_array('google', $dynamicRouteParams)) {
|
|
380
|
-
$provider = $this->findProvider($providers, GoogleProvider::class);
|
|
381
|
-
|
|
382
|
-
if (!$provider) {
|
|
383
|
-
exit("Error occurred. Please try again.");
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return $this->googleProvider($provider, $authCode);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
exit("Error occurred. Please try again.");
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
private function githubProvider(GithubProvider $githubProvider, string $authCode)
|
|
394
|
-
{
|
|
395
|
-
$gitToken = [
|
|
396
|
-
'client_id' => $githubProvider->clientId,
|
|
397
|
-
'client_secret' => $githubProvider->clientSecret,
|
|
398
|
-
'code' => $authCode,
|
|
399
|
-
];
|
|
400
|
-
|
|
401
|
-
$apiUrl = 'https://github.com/login/oauth/access_token';
|
|
402
|
-
$tokenData = (object)$this->exchangeCode($gitToken, $apiUrl);
|
|
403
|
-
|
|
404
|
-
if (!$tokenData) {
|
|
405
|
-
exit("Error occurred. Please try again.");
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (isset($tokenData->error)) {
|
|
409
|
-
exit("Error occurred. Please try again.");
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (isset($tokenData->access_token)) {
|
|
413
|
-
$client = new Client();
|
|
414
|
-
$emailResponse = $client->get('https://api.github.com/user/emails', [
|
|
415
|
-
'headers' => [
|
|
416
|
-
'Authorization' => 'Bearer ' . $tokenData->access_token,
|
|
417
|
-
'Accept' => 'application/json',
|
|
418
|
-
],
|
|
419
|
-
]);
|
|
420
|
-
|
|
421
|
-
$emails = json_decode($emailResponse->getBody()->getContents(), true);
|
|
422
|
-
|
|
423
|
-
$primaryEmail = array_reduce($emails, function ($carry, $item) {
|
|
424
|
-
return ($item['primary'] && $item['verified']) ? $item['email'] : $carry;
|
|
425
|
-
}, null);
|
|
426
|
-
|
|
427
|
-
$response = $client->get('https://api.github.com/user', [
|
|
428
|
-
'headers' => [
|
|
429
|
-
'Accept' => 'application/json',
|
|
430
|
-
'Authorization' => 'Bearer ' . $tokenData->access_token,
|
|
431
|
-
],
|
|
432
|
-
]);
|
|
433
|
-
|
|
434
|
-
if ($response->getStatusCode() == 200) {
|
|
435
|
-
$responseInfo = json_decode($response->getBody()->getContents());
|
|
436
|
-
|
|
437
|
-
$accountData = [
|
|
438
|
-
'provider' => 'github',
|
|
439
|
-
'type' => 'oauth',
|
|
440
|
-
'providerAccountId' => "$responseInfo->id",
|
|
441
|
-
'access_token' => $tokenData->access_token,
|
|
442
|
-
'expires_at' => $tokenData->expires_at ?? null,
|
|
443
|
-
'token_type' => $tokenData->token_type,
|
|
444
|
-
'scope' => $tokenData->scope,
|
|
445
|
-
];
|
|
446
|
-
|
|
447
|
-
$this->saveAuthInfo($responseInfo, $accountData);
|
|
448
|
-
|
|
449
|
-
$userToAuthenticate = [
|
|
450
|
-
'name' => $responseInfo->login,
|
|
451
|
-
'email' => $primaryEmail,
|
|
452
|
-
'image' => $responseInfo->avatar_url,
|
|
453
|
-
'Account' => (object)$accountData
|
|
454
|
-
];
|
|
455
|
-
$userToAuthenticate = (object)$userToAuthenticate;
|
|
456
|
-
|
|
457
|
-
$this->signIn($userToAuthenticate, $githubProvider->maxAge);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
private function googleProvider(GoogleProvider $googleProvider, string $authCode)
|
|
463
|
-
{
|
|
464
|
-
$googleToken = [
|
|
465
|
-
'client_id' => $googleProvider->clientId,
|
|
466
|
-
'client_secret' => $googleProvider->clientSecret,
|
|
467
|
-
'code' => $authCode,
|
|
468
|
-
'grant_type' => 'authorization_code',
|
|
469
|
-
'redirect_uri' => $googleProvider->redirectUri
|
|
470
|
-
];
|
|
471
|
-
|
|
472
|
-
$apiUrl = 'https://oauth2.googleapis.com/token';
|
|
473
|
-
$tokenData = (object)$this->exchangeCode($googleToken, $apiUrl);
|
|
474
|
-
|
|
475
|
-
if (!$tokenData) {
|
|
476
|
-
exit("Error occurred. Please try again.");
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
if (isset($tokenData->error)) {
|
|
480
|
-
exit("Error occurred. Please try again.");
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (isset($tokenData->access_token)) {
|
|
484
|
-
$client = new Client();
|
|
485
|
-
$response = $client->get('https://www.googleapis.com/oauth2/v1/userinfo', [
|
|
486
|
-
'headers' => [
|
|
487
|
-
'Authorization' => 'Bearer ' . $tokenData->access_token,
|
|
488
|
-
'Accept' => 'application/json',
|
|
489
|
-
],
|
|
490
|
-
]);
|
|
491
|
-
|
|
492
|
-
if ($response->getStatusCode() == 200) {
|
|
493
|
-
$responseInfo = json_decode($response->getBody()->getContents());
|
|
494
|
-
|
|
495
|
-
$accountData = [
|
|
496
|
-
'provider' => 'google',
|
|
497
|
-
'type' => 'oauth',
|
|
498
|
-
'providerAccountId' => "$responseInfo->id",
|
|
499
|
-
'access_token' => $tokenData->access_token,
|
|
500
|
-
'expires_at' => $tokenData->expires_at ?? null,
|
|
501
|
-
'token_type' => $tokenData->token_type,
|
|
502
|
-
'scope' => $tokenData->scope,
|
|
503
|
-
];
|
|
504
|
-
|
|
505
|
-
$this->saveAuthInfo($responseInfo, $accountData);
|
|
506
|
-
|
|
507
|
-
$userToAuthenticate = [
|
|
508
|
-
'name' => $responseInfo->name,
|
|
509
|
-
'email' => $responseInfo->email,
|
|
510
|
-
'image' => $responseInfo->picture,
|
|
511
|
-
'Account' => (object)$accountData
|
|
512
|
-
];
|
|
513
|
-
$userToAuthenticate = (object)$userToAuthenticate;
|
|
514
|
-
|
|
515
|
-
$this->signIn($userToAuthenticate, $googleProvider->maxAge);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
private static function getCookieName(): string
|
|
521
|
-
{
|
|
522
|
-
$authCookieName = $_ENV['AUTH_COOKIE_NAME'] ?? 'auth_cookie_name_d36e5';
|
|
523
|
-
return strtolower(preg_replace('/\s+/', '_', trim($authCookieName)));
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
class GoogleProvider
|
|
528
|
-
{
|
|
529
|
-
public function __construct(
|
|
530
|
-
public string $clientId,
|
|
531
|
-
public string $clientSecret,
|
|
532
|
-
public string $redirectUri,
|
|
533
|
-
public string $maxAge = '30d'
|
|
534
|
-
) {}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
class GithubProvider
|
|
538
|
-
{
|
|
539
|
-
public function __construct(
|
|
540
|
-
public string $clientId,
|
|
541
|
-
public string $clientSecret,
|
|
542
|
-
public string $maxAge = '30d'
|
|
543
|
-
) {}
|
|
544
|
-
}
|