Skip to content

Commit aa5cfd1

Browse files
committed
feat: enhance authentication flow with 2FA setup and validation improvements
1 parent a164616 commit aa5cfd1

7 files changed

Lines changed: 137 additions & 18 deletions

File tree

app/Http/Controllers/API/AuthController.php

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,11 @@ public function login(Request $request)
142142

143143
if (auth('api')->user()->google2fa_secret == null) {
144144
$secret = $tfa->generateSecretKey();
145+
auth('api')->user()->update([
146+
'google2fa_secret' => $secret,
147+
]);
145148
return response()->json([
146149
'message' => 'İki faktörlü doğrulama için Google Authenticator uygulaması ile QR kodunu okutunuz.',
147-
'secret' => $secret,
148150
'image' => $tfa->getQRCodeInline(
149151
"Liman",
150152
auth('api')->user()->email,
@@ -184,7 +186,7 @@ public function setupTwoFactorAuthentication(Request $request)
184186
$validator = Validator::make($request->all(), [
185187
'email' => 'required|string',
186188
'password' => 'required|string',
187-
'secret' => 'required'
189+
'token' => 'required|string',
188190
]);
189191

190192
if ($validator->fails()) {
@@ -200,17 +202,26 @@ public function setupTwoFactorAuthentication(Request $request)
200202
return response()->json(['message' => 'Kullanıcı adı veya şifreniz yanlış.'], 401);
201203
}
202204

203-
$token = auth('api')->attempt([
205+
$authToken = auth('api')->attempt([
204206
'email' => $user->email,
205207
'password' => $validator->validated()["password"],
206208
]);
207-
if (! $token) {
209+
if (! $authToken) {
208210
return response()->json(['message' => 'Kullanıcı adı veya şifreniz yanlış.'], 401);
209211
}
210212

211-
User::find(auth('api')->user()->id)->update([
213+
$authenticatedUser = auth('api')->user();
214+
if (! $authenticatedUser->google2fa_secret) {
215+
return response()->json(['message' => '2FA kurulum süreci başlatılmamış. Lütfen önce giriş yapınız.'], 422);
216+
}
217+
218+
$tfa = app('pragmarx.google2fa');
219+
if (! $tfa->verifyGoogle2FA($authenticatedUser->google2fa_secret, $request->token)) {
220+
return response()->json(['message' => 'OTP doğrulama başarısız. QR kodunu tekrar okutup deneyiniz.'], 422);
221+
}
222+
223+
$authenticatedUser->update([
212224
'otp_enabled' => true,
213-
'google2fa_secret' => $request->secret
214225
]);
215226

216227
return response()->json(['message' => '2FA kurulumu başarıyla yapıldı.']);

app/Http/Controllers/API/ServerController.php

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,19 +229,55 @@ private function grantPermissions(Server $server)
229229
*/
230230
public function checkAccess()
231231
{
232-
if (request('port') == -1) {
232+
validate([
233+
'ip_address' => 'required|string|max:255',
234+
'port' => 'required|integer|min:-1|max:65535',
235+
]);
236+
237+
$ip = request('ip_address');
238+
$port = (int) request('port');
239+
240+
// Port -1 means no port check needed (portless server)
241+
if ($port === -1) {
233242
return response()->json([
234243
'message' => 'Sunucuya başarıyla erişim sağlandı.',
235244
]);
236245
}
246+
247+
// Validate port range for actual connections
248+
if ($port < 1) {
249+
return response()->json(['port' => 'Geçersiz port numarası.'], 422);
250+
}
251+
252+
// Resolve hostname to IP for validation
253+
$resolvedIp = $ip;
254+
if (! filter_var($ip, FILTER_VALIDATE_IP)) {
255+
$resolvedIp = gethostbyname($ip);
256+
if ($resolvedIp === $ip) {
257+
return response()->json(['ip_address' => 'Sunucu adresi çözümlenemedi.'], 422);
258+
}
259+
}
260+
261+
// Block metadata endpoints and cloud-internal addresses (169.254.x.x link-local)
262+
if (filter_var($resolvedIp, FILTER_VALIDATE_IP) && str_starts_with($resolvedIp, '169.254.')) {
263+
return response()->json(['ip_address' => 'Link-local adresleri kullanılamaz.'], 422);
264+
}
265+
266+
// Restrict to safe port range — block well-known internal service ports
267+
$blockedPorts = [6379, 11211, 27017, 9200, 9300, 2379, 5432, 3306];
268+
if (in_array($port, $blockedPorts)) {
269+
return response()->json(['port' => 'Bu port numarası güvenlik nedeniyle engellenmiştir.'], 422);
270+
}
271+
237272
$status = @fsockopen(
238-
request('ip_address'),
239-
request('port'),
273+
$ip,
274+
$port,
240275
$errno,
241276
$errstr,
242277
intval(config('liman.server_connection_timeout')) / 1000
243278
);
244279
if (is_resource($status)) {
280+
fclose($status);
245281
return response()->json([
246282
'message' => 'Sunucuya başarıyla erişim sağlandı.',
247283
]);

app/Http/Controllers/API/Settings/MailController.php

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,24 @@ public function getConfiguration()
3636
public function saveConfiguration(Request $request)
3737
{
3838
validate([
39-
'host' => 'required|string',
40-
'port' => 'required|integer',
41-
'username' => 'required|string',
42-
'password' => 'nullable|string',
43-
'encryption' => 'required|string',
39+
'host' => 'required|string|max:255',
40+
'port' => 'required|integer|min:1|max:65535',
41+
'username' => 'required|string|max:255',
42+
'password' => 'nullable|string|max:255',
43+
'encryption' => 'required|string|in:tls,ssl,null',
4444
]);
4545

46+
$fields = ['host', 'username', 'password', 'encryption'];
47+
foreach ($fields as $field) {
48+
if ($request->has($field) && $request->$field !== null) {
49+
if (preg_match('/[\n\r]/', $request->$field)) {
50+
return response()->json([
51+
'message' => 'Geçersiz karakter tespit edildi.',
52+
], 422);
53+
}
54+
}
55+
}
56+
4657
setEnv([
4758
'MAIL_ENABLED' => (bool) $request->active,
4859
'MAIL_HOST' => $request->host,

app/Http/Controllers/API/Settings/VaultController.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ public function delete(Request $request)
151151
if (! $first) {
152152
return response()->json(['status' => false], 404);
153153
}
154+
155+
// Ownership check: only admins or the owner can delete
156+
if (! auth('api')->user()->isAdmin() && auth('api')->user()->id != $first->user_id) {
157+
return response()->json(['status' => false, 'message' => 'Bu kayıt üzerinde yetkiniz bulunmamaktadır.'], 403);
158+
}
159+
154160
if (
155161
$first->name == 'clientUsername' ||
156162
$first->name == 'clientPassword'

app/Http/Helpers.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,12 @@ function setEnv(array $values): bool
721721

722722
$editor = $editor->load(base_path('.env'));
723723
foreach ($values as $key => $value) {
724+
if (is_string($key) && preg_match('/[\n\r]/', $key)) {
725+
return false;
726+
}
727+
if (is_string($value) && preg_match('/[\n\r]/', $value)) {
728+
return false;
729+
}
724730
$editor->set($key, $value);
725731
}
726732
try {

app/Http/Middleware/TusAuthenticated.php

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Http\Middleware;
44

5+
use App\Models\Permission;
56
use Illuminate\Support\Facades\Log;
67
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
78
use TusPhp\Middleware\TusMiddleware;
@@ -22,10 +23,20 @@ class TusAuthenticated implements TusMiddleware
2223
*/
2324
public function handle(Request $request, Response $response)
2425
{
26+
// 1. Check if already authenticated via JWT Authorization header
2527
if (auth('api')->check()) {
28+
$this->checkExtensionPermission();
2629
return;
2730
}
2831

32+
// 2. Check session/cookie-based auth (web guard)
33+
if (auth('web')->check()) {
34+
auth('api')->login(auth('web')->user());
35+
$this->checkExtensionPermission();
36+
return;
37+
}
38+
39+
// 3. Try Extension-Token (JWT from sandbox customRequestData['token'])
2940
$token = "";
3041
if (request()->token) {
3142
$token = request()->token;
@@ -34,14 +45,52 @@ public function handle(Request $request, Response $response)
3445
}
3546

3647
if (! $token) {
37-
if (auth('api')->check()) {
38-
return true;
48+
// 4. Try cookie-based JWT (web middleware group doesn't run CookieJWTAuthenticator)
49+
if (request()->hasCookie('token')) {
50+
request()->headers->set('Authorization', 'Bearer ' . request()->cookie('token'));
51+
if (auth('api')->check()) {
52+
$this->checkExtensionPermission();
53+
return;
54+
}
3955
}
4056

4157
throw new UnauthorizedHttpException('', 'Extension-Token header is missing.');
4258
}
4359

44-
Log::info('Extension-Token is valid. User ip: ' . request()->ip);
60+
// Validate the JWT token
61+
try {
62+
request()->headers->set('Authorization', 'Bearer ' . $token);
63+
if (! auth('api')->check()) {
64+
throw new UnauthorizedHttpException('', 'Invalid Extension-Token.');
65+
}
66+
} catch (\Exception $e) {
67+
throw new UnauthorizedHttpException('', 'Invalid Extension-Token.');
68+
}
69+
70+
$this->checkExtensionPermission();
71+
72+
Log::info('Extension-Token validated for user ' . auth('api')->user()->id . '. IP: ' . request()->ip());
4573
return true;
4674
}
75+
76+
/**
77+
* Check if the authenticated user has permission to upload to the specified extension
78+
*/
79+
private function checkExtensionPermission(): void
80+
{
81+
$extensionId = request()->headers->get('extension-id');
82+
83+
if (! $extensionId) {
84+
return;
85+
}
86+
87+
$user = auth('api')->user();
88+
if (! $user) {
89+
return;
90+
}
91+
92+
if (! Permission::can($user->id, 'extension', 'id', $extensionId)) {
93+
throw new UnauthorizedHttpException('', 'You do not have permission to upload to this extension.');
94+
}
95+
}
4796
}

storage/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.2.1
1+
2.2.2

0 commit comments

Comments
 (0)