create-prisma-php-app 4.0.0-alpha.2 → 4.0.0-alpha.21
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/.htaccess +54 -41
- package/dist/bootstrap.php +143 -98
- package/dist/index.js +264 -99
- package/dist/settings/auto-swagger-docs.ts +196 -95
- package/dist/settings/bs-config.ts +56 -58
- package/dist/settings/files-list.json +1 -1
- package/dist/settings/restart-mcp.ts +58 -0
- package/dist/settings/restart-websocket.ts +51 -45
- package/dist/settings/utils.ts +240 -0
- package/dist/src/Lib/AI/ChatGPTClient.php +147 -0
- package/dist/src/Lib/Auth/Auth.php +544 -0
- package/dist/src/Lib/Auth/AuthConfig.php +89 -0
- package/dist/src/Lib/CacheHandler.php +121 -0
- package/dist/src/Lib/ErrorHandler.php +322 -0
- package/dist/src/Lib/FileManager/UploadFile.php +383 -0
- package/dist/src/Lib/Headers/Boom.php +192 -0
- package/dist/src/Lib/IncludeTracker.php +59 -0
- package/dist/src/Lib/MCP/WeatherTools.php +104 -0
- package/dist/src/Lib/MCP/mcp-server.php +80 -0
- package/dist/src/Lib/MainLayout.php +230 -0
- package/dist/src/Lib/Middleware/AuthMiddleware.php +154 -0
- package/dist/src/Lib/Middleware/CorsMiddleware.php +145 -0
- package/dist/src/Lib/PHPMailer/Mailer.php +169 -0
- package/dist/src/Lib/PHPX/Exceptions/ComponentValidationException.php +49 -0
- package/dist/src/Lib/PHPX/Fragment.php +32 -0
- package/dist/src/Lib/PHPX/IPHPX.php +22 -0
- package/dist/src/Lib/PHPX/PHPX.php +287 -0
- package/dist/src/Lib/PHPX/TemplateCompiler.php +641 -0
- package/dist/src/Lib/PHPX/TwMerge.php +346 -0
- package/dist/src/Lib/PHPX/TypeCoercer.php +490 -0
- package/dist/src/Lib/PartialRenderer.php +40 -0
- package/dist/src/Lib/PrismaPHPSettings.php +181 -0
- package/dist/src/Lib/Request.php +479 -0
- package/dist/src/Lib/Security/RateLimiter.php +33 -0
- package/dist/src/Lib/Set.php +102 -0
- package/dist/src/Lib/StateManager.php +127 -0
- package/dist/src/Lib/Validator.php +752 -0
- package/dist/src/{Websocket → Lib/Websocket}/ConnectionManager.php +1 -1
- package/dist/src/Lib/Websocket/websocket-server.php +118 -0
- package/dist/src/app/error.php +1 -1
- package/dist/src/app/index.php +24 -5
- package/dist/src/app/js/index.js +1 -1
- package/dist/src/app/layout.php +2 -2
- package/package.json +1 -1
- package/dist/settings/restart-websocket.bat +0 -28
- package/dist/src/app/assets/images/prisma-php-black.svg +0 -6
- package/dist/websocket-server.php +0 -22
- package/vendor/autoload.php +0 -25
- package/vendor/composer/ClassLoader.php +0 -579
- package/vendor/composer/InstalledVersions.php +0 -359
- package/vendor/composer/LICENSE +0 -21
- package/vendor/composer/autoload_classmap.php +0 -10
- package/vendor/composer/autoload_namespaces.php +0 -9
- package/vendor/composer/autoload_psr4.php +0 -10
- package/vendor/composer/autoload_real.php +0 -38
- package/vendor/composer/autoload_static.php +0 -25
- package/vendor/composer/installed.json +0 -825
- package/vendor/composer/installed.php +0 -132
- package/vendor/composer/platform_check.php +0 -26
|
@@ -0,0 +1,544 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Lib\Auth;
|
|
6
|
+
|
|
7
|
+
use ArrayObject;
|
|
8
|
+
|
|
9
|
+
enum AuthRole: string
|
|
10
|
+
{
|
|
11
|
+
case Admin = 'Admin';
|
|
12
|
+
case User = 'User';
|
|
13
|
+
|
|
14
|
+
public function equals($role)
|
|
15
|
+
{
|
|
16
|
+
return $this->value === $role;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
final class AuthConfig
|
|
21
|
+
{
|
|
22
|
+
public const ROLE_IDENTIFIER = 'role';
|
|
23
|
+
public const IS_ROLE_BASE = false;
|
|
24
|
+
public const IS_TOKEN_AUTO_REFRESH = false;
|
|
25
|
+
public const IS_ALL_ROUTES_PRIVATE = false;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* This is the (default) option for authentication. If IS_ALL_ROUTES_PRIVATE is set to false,
|
|
29
|
+
* An array of private routes that are accessible to all authenticated users
|
|
30
|
+
* without specific role-based access control. Routes should be listed as string paths.
|
|
31
|
+
* Example: public static $privateRoutes = ['/']; // This makes the home page private
|
|
32
|
+
* Example: public static $privateRoutes = ['/profile', '/dashboard/settings']; // These routes are private
|
|
33
|
+
*/
|
|
34
|
+
public static array $privateRoutes = [];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* This is the (default) option for authentication. If IS_ALL_ROUTES_PRIVATE is set to true,
|
|
38
|
+
* An array of public routes that are accessible to all users, authenticated or not.
|
|
39
|
+
*/
|
|
40
|
+
public const DEFAULT_SIGNIN_REDIRECT = '/dashboard'; // Default redirect route after sign in
|
|
41
|
+
public const API_AUTH_PREFIX = '/api/auth'; // Prefix for third-party API authentication routes (github, google, etc.)
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* An array of public routes that are accessible to all users, authenticated or not.
|
|
45
|
+
* Routes should be listed as string paths.
|
|
46
|
+
* Example: public static $publicRoutes = ['/']; // This makes the home page public
|
|
47
|
+
* Example: public static $publicRoutes = ['/about', '/contact']; // These routes are public
|
|
48
|
+
*/
|
|
49
|
+
public static array $publicRoutes = ['/'];
|
|
50
|
+
public static array $authRoutes = [
|
|
51
|
+
'/signin',
|
|
52
|
+
'/signup',
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* An associative array mapping specific routes to required user roles for access control.
|
|
57
|
+
* Each route is a key with an array of roles that are allowed access.
|
|
58
|
+
* Format:
|
|
59
|
+
* 'route_path' => [self::ROLE_IDENTIFIER => [AuthRole::Role1, AuthRole::Role2, ...]],
|
|
60
|
+
* Example:
|
|
61
|
+
* public static $roleBasedRoutes = [
|
|
62
|
+
* 'dashboard' => [self::ROLE_IDENTIFIER => [AuthRole::Admin, AuthRole::User]],
|
|
63
|
+
* 'dashboard/users' => [self::ROLE_IDENTIFIER => [AuthRole::Admin]],
|
|
64
|
+
* 'sales' => [self::ROLE_IDENTIFIER => [AuthRole::Admin, AuthRole::User]]
|
|
65
|
+
* ];
|
|
66
|
+
*/
|
|
67
|
+
public static array $roleBasedRoutes = [];
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Checks if the given user role is authorized to access a set of roles.
|
|
71
|
+
*
|
|
72
|
+
* @param ArrayObject|string $userRole The user's role to check.
|
|
73
|
+
* @param array<AuthRole> $roles An array of AuthRole instances specifying allowed roles.
|
|
74
|
+
* @return bool Returns true if the user's role matches any of the allowed roles, false otherwise.
|
|
75
|
+
*/
|
|
76
|
+
public static function checkAuthRole(ArrayObject|string $userRole, array $roles): bool
|
|
77
|
+
{
|
|
78
|
+
if ($userRole instanceof ArrayObject) {
|
|
79
|
+
$userRole = $userRole[Auth::ROLE_NAME] ?? '';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
foreach ($roles as $role) {
|
|
83
|
+
if ($userRole === $role->value) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|