Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
Clubphp
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Clubphp
Commits
cbd6fc0a
Commit
cbd6fc0a
authored
Apr 07, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 5 files via Son of Anton
parent
035fbfe8
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
177 additions
and
85 deletions
+177
-85
CSRF.php
app/Core/CSRF.php
+22
-12
Session.php
app/Core/Session.php
+55
-39
CSRFMiddleware.php
app/Middleware/CSRFMiddleware.php
+43
-15
auth.php
app/Shared/Layout/auth.php
+57
-19
No files found.
app/Core/CSRF.php
View file @
cbd6fc0a
...
@@ -10,30 +10,40 @@ final class CSRF
...
@@ -10,30 +10,40 @@ final class CSRF
public
static
function
generate
()
:
string
public
static
function
generate
()
:
string
{
{
$token
=
bin2hex
(
random_bytes
(
32
));
$token
=
bin2hex
(
random_bytes
(
32
));
$session
=
App
::
getInstance
()
->
session
();
$_SESSION
[
self
::
$tokenKey
]
=
$token
;
$session
->
set
(
self
::
$tokenKey
,
$token
);
return
$token
;
return
$token
;
}
}
public
static
function
token
()
:
string
public
static
function
token
()
:
string
{
{
$session
=
App
::
getInstance
()
->
session
();
if
(
empty
(
$_SESSION
[
self
::
$tokenKey
]))
{
$token
=
$session
->
get
(
self
::
$tokenKey
);
return
self
::
generate
();
if
(
$token
===
null
)
{
$token
=
self
::
generate
();
}
}
return
$
token
;
return
$
_SESSION
[
self
::
$tokenKey
]
;
}
}
public
static
function
validate
(
string
$token
)
:
bool
public
static
function
validate
(
?
string
$token
)
:
bool
{
{
$session
=
App
::
getInstance
()
->
session
();
if
(
$token
===
null
||
$token
===
''
)
{
$stored
=
$session
->
get
(
self
::
$tokenKey
,
''
);
return
false
;
return
hash_equals
(
$stored
,
$token
);
}
$sessionToken
=
$_SESSION
[
self
::
$tokenKey
]
??
''
;
if
(
$sessionToken
===
''
)
{
return
false
;
}
return
hash_equals
(
$sessionToken
,
$token
);
}
}
public
static
function
field
()
:
string
public
static
function
field
()
:
string
{
{
return
'<input type="hidden" name="_csrf_token" value="'
.
self
::
token
()
.
'">'
;
return
'<input type="hidden" name="_csrf_token" value="'
.
htmlspecialchars
(
self
::
token
(),
ENT_QUOTES
,
'UTF-8'
)
.
'">'
;
}
// Regenerate token after successful validation (prevents reuse)
public
static
function
regenerate
()
:
void
{
self
::
generate
();
}
}
}
}
\ No newline at end of file
app/Core/Session.php
View file @
cbd6fc0a
...
@@ -5,45 +5,55 @@ namespace App\Core;
...
@@ -5,45 +5,55 @@ namespace App\Core;
final
class
Session
final
class
Session
{
{
private
int
$lifetim
e
;
private
bool
$started
=
fals
e
;
public
function
__construct
(
int
$lifetimeMinutes
=
30
)
public
function
__construct
()
{
{
$this
->
lifetime
=
$lifetimeMinutes
*
60
;
$this
->
start
()
;
}
}
p
ublic
function
start
()
:
void
p
rivate
function
start
()
:
void
{
{
if
(
session_status
()
===
PHP_SESSION_ACTIVE
)
{
if
(
$this
->
started
||
session_status
()
===
PHP_SESSION_ACTIVE
)
{
$this
->
started
=
true
;
return
;
return
;
}
}
if
(
php_sapi_name
()
===
'cli'
)
{
// Detect if behind HTTPS proxy (CapRover/nginx)
return
;
$secure
=
false
;
if
(
(
!
empty
(
$_SERVER
[
'HTTPS'
])
&&
$_SERVER
[
'HTTPS'
]
!==
'off'
)
||
(
!
empty
(
$_SERVER
[
'HTTP_X_FORWARDED_PROTO'
])
&&
$_SERVER
[
'HTTP_X_FORWARDED_PROTO'
]
===
'https'
)
||
(
!
empty
(
$_SERVER
[
'HTTP_X_FORWARDED_SSL'
])
&&
$_SERVER
[
'HTTP_X_FORWARDED_SSL'
]
===
'on'
)
||
(
isset
(
$_SERVER
[
'SERVER_PORT'
])
&&
(
int
)
$_SERVER
[
'SERVER_PORT'
]
===
443
)
)
{
$secure
=
true
;
}
}
ini_set
(
'session.gc_maxlifetime'
,
(
string
)
$this
->
lifetime
);
$savePath
=
dirname
(
__DIR__
,
2
)
.
'/storage/sessions'
;
ini_set
(
'session.cookie_httponly'
,
'1'
);
if
(
!
is_dir
(
$savePath
))
{
ini_set
(
'session.use_strict_mode'
,
'1'
);
mkdir
(
$savePath
,
0775
,
true
);
$storagePath
=
Autoloader
::
basePath
()
.
'/storage/sessions'
;
if
(
is_dir
(
$storagePath
)
&&
is_writable
(
$storagePath
))
{
ini_set
(
'session.save_path'
,
$storagePath
);
}
}
ini_set
(
'session.save_handler'
,
'files'
);
ini_set
(
'session.save_path'
,
$savePath
);
ini_set
(
'session.gc_maxlifetime'
,
'7200'
);
ini_set
(
'session.cookie_lifetime'
,
'0'
);
ini_set
(
'session.use_strict_mode'
,
'1'
);
ini_set
(
'session.use_only_cookies'
,
'1'
);
session_set_cookie_params
([
'lifetime'
=>
0
,
'path'
=>
'/'
,
'domain'
=>
''
,
'secure'
=>
$secure
,
'httponly'
=>
true
,
'samesite'
=>
'Lax'
,
]);
session_name
(
'club_erp_session'
);
session_start
();
session_start
();
$this
->
started
=
true
;
$this
->
processFlash
();
}
private
function
processFlash
()
:
void
{
$previousFlash
=
$_SESSION
[
'_flash_keys'
]
??
[];
foreach
(
$previousFlash
as
$key
)
{
unset
(
$_SESSION
[
$key
]);
}
$_SESSION
[
'_flash_keys'
]
=
$_SESSION
[
'_next_flash_keys'
]
??
[];
$_SESSION
[
'_next_flash_keys'
]
=
[];
}
}
public
function
get
(
string
$key
,
$default
=
null
)
public
function
get
(
string
$key
,
$default
=
null
)
...
@@ -68,29 +78,26 @@ final class Session
...
@@ -68,29 +78,26 @@ final class Session
public
function
flash
(
string
$key
,
$value
)
:
void
public
function
flash
(
string
$key
,
$value
)
:
void
{
{
$_SESSION
[
$key
]
=
$value
;
$_SESSION
[
'_flash'
][
$key
]
=
$value
;
$flashKeys
=
$_SESSION
[
'_next_flash_keys'
]
??
[];
if
(
!
in_array
(
$key
,
$flashKeys
))
{
$flashKeys
[]
=
$key
;
}
$_SESSION
[
'_next_flash_keys'
]
=
$flashKeys
;
}
}
public
function
getFlash
(
string
$key
,
$default
=
null
)
public
function
getFlash
(
string
$key
,
$default
=
null
)
{
{
$value
=
$_SESSION
[
$key
]
??
$default
;
$value
=
$_SESSION
[
'_flash'
][
$key
]
??
$default
;
unset
(
$_SESSION
[
'_flash'
][
$key
]);
return
$value
;
return
$value
;
}
}
public
function
getAlerts
()
:
array
public
function
getAlerts
()
:
array
{
{
$alerts
=
$this
->
get
(
'_alerts'
,
[]);
$alerts
=
$_SESSION
[
'_flash'
][
'_alerts'
]
??
[];
unset
(
$_SESSION
[
'_flash'
][
'_alerts'
]);
return
$alerts
;
return
$alerts
;
}
}
public
function
regenerate
()
:
void
public
function
regenerate
()
:
void
{
{
if
(
php_sapi_name
()
!==
'cli'
)
{
if
(
session_status
()
===
PHP_SESSION_ACTIVE
)
{
session_regenerate_id
(
true
);
session_regenerate_id
(
true
);
}
}
}
}
...
@@ -98,16 +105,25 @@ final class Session
...
@@ -98,16 +105,25 @@ final class Session
public
function
destroy
()
:
void
public
function
destroy
()
:
void
{
{
$_SESSION
=
[];
$_SESSION
=
[];
if
(
ini_get
(
'session.use_cookies'
))
{
if
(
ini_get
(
'session.use_cookies'
))
{
$params
=
session_get_cookie_params
();
$params
=
session_get_cookie_params
();
setcookie
(
session_name
(),
''
,
time
()
-
42000
,
setcookie
(
$params
[
'path'
],
$params
[
'domain'
],
session_name
(),
$params
[
'secure'
],
$params
[
'httponly'
]
''
,
time
()
-
42000
,
$params
[
'path'
],
$params
[
'domain'
],
$params
[
'secure'
],
$params
[
'httponly'
]
);
);
}
}
if
(
php_sapi_name
()
!==
'cli'
)
{
if
(
session_status
()
===
PHP_SESSION_ACTIVE
)
{
session_destroy
();
session_destroy
();
}
}
$this
->
started
=
false
;
}
}
public
function
id
()
:
string
public
function
id
()
:
string
...
...
app/Middleware/CSRFMiddleware.php
View file @
cbd6fc0a
...
@@ -8,27 +8,55 @@ use App\Core\Request;
...
@@ -8,27 +8,55 @@ use App\Core\Request;
use
App\Core\Response
;
use
App\Core\Response
;
use
App\Core\CSRF
;
use
App\Core\CSRF
;
final
class
CSRFMiddleware
implements
MiddlewareInterface
class
CSRFMiddleware
implements
MiddlewareInterface
{
{
public
function
handle
(
Request
$request
,
callable
$next
)
:
Response
public
function
handle
(
Request
$request
,
callable
$next
)
:
Response
{
{
if
(
in_array
(
$request
->
method
(),
[
'POST'
,
'PUT'
,
'PATCH'
,
'DELETE'
]))
{
// Only validate on state-changing methods
$token
=
$request
->
post
(
'_csrf_token'
)
??
$request
->
header
(
'X-CSRF-TOKEN'
);
$method
=
strtoupper
(
$request
->
method
());
if
(
$token
===
null
||
!
CSRF
::
validate
(
$token
))
{
if
(
in_array
(
$method
,
[
'GET'
,
'HEAD'
,
'OPTIONS'
]))
{
if
(
$request
->
isAjax
())
{
return
$next
(
$request
);
return
(
new
Response
())
->
json
([
'error'
=>
'CSRF token mismatch'
],
419
);
}
}
return
(
new
Response
())
->
html
(
// Skip CSRF for API routes that use token auth
'<html dir="rtl" lang="ar"><head><meta charset="utf-8"><title>خطأ أمني</title>'
$path
=
$request
->
path
();
.
'<style>body{font-family:Cairo,sans-serif;text-align:center;padding:60px;}'
if
(
str_starts_with
(
$path
,
'/api/'
)
&&
$request
->
bearerToken
())
{
.
'h1{color:#DC2626;}</style></head>'
return
$next
(
$request
);
.
'<body><h1>419</h1><p>انتهت صلاحية الجلسة. يرجى إعادة تحميل الصفحة.</p>'
}
.
'<a href="javascript:location.reload()">إعادة تحميل</a></body></html>'
,
419
// Get token from POST data or header
);
$token
=
$request
->
post
(
'_csrf_token'
,
''
)
?:
$request
->
header
(
'X-CSRF-TOKEN'
)
?:
''
;
if
(
!
CSRF
::
validate
(
$token
))
{
// If AJAX request, return JSON error
if
(
$request
->
isAjax
()
||
$request
->
isJson
())
{
$response
=
new
Response
();
return
$response
->
json
([
'error'
=>
true
,
'message'
=>
'انتهت صلاحية الجلسة. يرجى إعادة تحميل الصفحة.'
,
],
419
);
}
}
// For regular form submissions, show error page
http_response_code
(
419
);
echo
'<!DOCTYPE html><html lang="ar" dir="rtl"><head><meta charset="UTF-8"><title>419</title>'
.
'<style>body{font-family:Cairo,Arial,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#F3F4F6;}'
.
'.box{text-align:center;padding:40px;}'
.
'.box h1{color:#DC2626;font-size:60px;margin:0;}'
.
'.box p{color:#4B5563;font-size:18px;margin:15px 0;}'
.
'.box a{color:#0D7377;font-size:16px;}</style></head>'
.
'<body><div class="box"><h1>419</h1>'
.
'<p>انتهت صلاحية الجلسة. يرجى إعادة تحميل الصفحة.</p>'
.
'<a href="'
.
htmlspecialchars
(
$path
)
.
'">إعادة تحميل</a>'
.
'</div></body></html>'
;
exit
;
}
}
// Token valid — regenerate for next request
CSRF
::
regenerate
();
return
$next
(
$request
);
return
$next
(
$request
);
}
}
}
}
\ No newline at end of file
app/Shared/Layout/auth.php
View file @
cbd6fc0a
<?php
use
App\Core\CSRF
;
?>
<!DOCTYPE html>
<!DOCTYPE html>
<html
lang=
"ar"
dir=
"rtl"
>
<html
lang=
"ar"
dir=
"rtl"
>
<head>
<head>
<meta
charset=
"UTF-8"
>
<meta
charset=
"UTF-8"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
>
<meta
name=
"csrf-token"
content=
"
<?=
\App\Core\CSRF
::
token
(
)
?>
"
>
<meta
name=
"csrf-token"
content=
"
<?=
e
(
CSRF
::
token
()
)
?>
"
>
<title>
<?=
$__template
->
yield
(
'title'
,
'تسجيل الدخول
— نادي النادي شيراتون'
)
?>
</title>
<title>
<?=
$__template
->
yield
(
'title'
,
'تسجيل الدخول
'
)
?>
— نادي النادي شيراتون
</title>
<link
rel=
"stylesheet"
href=
"
<?=
url
(
'assets/css/main.css'
)
?>
"
>
<link
rel=
"stylesheet"
href=
"
<?=
url
(
'assets/css/main.css'
)
?>
"
>
<style>
<style>
body
.auth-body
{
background
:
linear-gradient
(
135deg
,
#0D7377
0%
,
#095355
100%
);
min-height
:
100vh
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
margin
:
0
;
padding
:
20px
;
}
body
{
.auth-card
{
background
:
#fff
;
border-radius
:
12px
;
box-shadow
:
0
20px
60px
rgba
(
0
,
0
,
0
,
0.3
);
padding
:
40px
;
width
:
100%
;
max-width
:
420px
;
}
display
:
flex
;
.auth-logo
{
text-align
:
center
;
margin-bottom
:
30px
;
}
justify-content
:
center
;
.auth-logo
h1
{
color
:
#0D7377
;
font-size
:
24px
;
margin
:
0
;
}
align-items
:
center
;
.auth-logo
p
{
color
:
#6B7280
;
font-size
:
14px
;
margin-top
:
5px
;
}
min-height
:
100vh
;
margin
:
0
;
background
:
linear-gradient
(
135deg
,
#1A1A2E
0%
,
#0D7377
100%
);
font-family
:
'Cairo'
,
'Segoe UI'
,
Tahoma
,
Arial
,
sans-serif
;
direction
:
rtl
;
}
.auth-card
{
background
:
#fff
;
border-radius
:
12px
;
box-shadow
:
0
20px
60px
rgba
(
0
,
0
,
0
,
0.3
);
padding
:
40px
;
width
:
100%
;
max-width
:
420px
;
}
.auth-header
{
text-align
:
center
;
margin-bottom
:
30px
;
}
.auth-header
h1
{
color
:
#0D7377
;
font-size
:
24px
;
margin
:
0
0
5px
;
}
.auth-header
p
{
color
:
#6B7280
;
font-size
:
14px
;
margin
:
0
;
}
</style>
</style>
</head>
</head>
<body
class=
"auth-body"
>
<body>
<div
class=
"auth-card"
>
<div
class=
"auth-card"
>
<div
class=
"auth-
logo
"
>
<div
class=
"auth-
header
"
>
<h1>
THE CLUB
</h1>
<h1>
نادي النادي شيراتون
</h1>
<p>
نادي النادي شيراتون
</p>
<p>
THE CLUB Sheraton
</p>
</div>
</div>
<?php
<?php
$alerts
=
$app
->
session
()
->
getAlerts
();
$session
=
\App\Core\App
::
getInstance
()
->
session
();
$alerts
=
$session
->
getAlerts
();
if
(
!
empty
(
$alerts
))
:
if
(
!
empty
(
$alerts
))
:
foreach
(
$alerts
as
$alert
)
:
?>
foreach
(
$alerts
as
$alert
)
:
<div
class=
"alert alert-
<?=
e
(
$alert
[
'type'
]
??
'info'
)
?>
"
style=
"margin-bottom:15px;padding:10px 15px;border-radius:6px;font-size:14px;
<?=
(
$alert
[
'type'
]
??
''
)
===
'error'
?
'background:#FEF2F2;color:#DC2626;border:1px solid #FECACA;'
:
'background:#F0FDF4;color:#059669;border:1px solid #BBF7D0;'
?>
"
>
?>
<?=
e
(
$alert
[
'message'
]
??
''
)
?>
<div
style=
"padding:10px 15px;border-radius:6px;margin-bottom:15px;font-size:13px;
</div>
background:
<?=
(
$alert
[
'type'
]
??
''
)
===
'error'
?
'#FEF2F2'
:
'#F0FDF4'
?>
;
<?php
endforeach
;
color:
<?=
(
$alert
[
'type'
]
??
''
)
===
'error'
?
'#DC2626'
:
'#059669'
?>
;
endif
;
?>
border:1px solid
<?=
(
$alert
[
'type'
]
??
''
)
===
'error'
?
'#FECACA'
:
'#BBF7D0'
?>
;"
>
<?=
e
(
$alert
[
'message'
]
??
''
)
?>
</div>
<?php
endforeach
;
endif
;
?>
<?=
$__template
->
yield
(
'content'
,
''
)
?>
<?=
$__template
->
yield
(
'content'
,
''
)
?>
</div>
</div>
<script
src=
"
<?=
url
(
'assets/js/app.js'
)
?>
"
></script>
</body>
</body>
</html>
</html>
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment