Page MenuHomePhorge

D3698.1775265136.diff
No OneTemporary

Authored By
Unknown
Size
9 KB
Referenced Files
None
Subscribers
None

D3698.1775265136.diff

diff --git a/src/app/AuthAttempt.php b/src/app/AuthAttempt.php
--- a/src/app/AuthAttempt.php
+++ b/src/app/AuthAttempt.php
@@ -17,12 +17,12 @@
use NullableFields;
use UuidStrKeyTrait;
- // No specific reason
public const REASON_NONE = '';
- // Password mismatch
public const REASON_PASSWORD = 'password';
- // Geolocation whitelist mismatch
public const REASON_GEOLOCATION = 'geolocation';
+ public const REASON_NOTFOUND = 'notfound';
+ public const REASON_2FA = '2fa';
+ public const REASON_2FA_GENERIC = '2fa-generic';
private const STATUS_ACCEPTED = 'ACCEPTED';
private const STATUS_DENIED = 'DENIED';
diff --git a/src/app/Http/Controllers/API/V4/NGINXController.php b/src/app/Http/Controllers/API/V4/NGINXController.php
--- a/src/app/Http/Controllers/API/V4/NGINXController.php
+++ b/src/app/Http/Controllers/API/V4/NGINXController.php
@@ -12,7 +12,7 @@
/**
* Authorize with the provided credentials.
*
- * @param string $login The login name
+ * @param string $login The login name
* @param string $password The password
*
* @return \App\User The user
@@ -67,47 +67,18 @@
throw new \Exception("No client ip");
}
- $user = \App\User::where('email', $login)->first();
- if (!$user) {
- throw new \Exception("User not found");
+ $result = \App\User::findAndAuthenticate($login, $password, $clientIP);
+
+ if (empty($result['user'])) {
+ throw new \Exception($result['errorMessage'] ?? "Unknown error");
}
// TODO: validate the user's domain is A-OK (active, confirmed, not suspended, ldapready)
// TODO: validate the user is A-OK (active, not suspended, ldapready, imapready)
- // TODO: we could use User::findAndAuthenticate() with some modifications here
-
- if (!Hash::check($password, $user->password)) {
- $attempt = \App\AuthAttempt::recordAuthAttempt($user, $clientIP);
- // Avoid setting a password failure reason if we previously accepted the location.
- if (!$attempt->isAccepted()) {
- $attempt->reason = \App\AuthAttempt::REASON_PASSWORD;
- $attempt->save();
- $attempt->notify();
- }
- throw new \Exception("Password mismatch");
- }
-
- // validate country of origin against restrictions, otherwise bye bye
- if (!$user->validateLocation($clientIP)) {
- \Log::info("Failed authentication attempt due to country code mismatch for user: {$login}");
- $attempt = \App\AuthAttempt::recordAuthAttempt($user, $clientIP);
- $attempt->deny(\App\AuthAttempt::REASON_GEOLOCATION);
- $attempt->notify();
- throw new \Exception("Country code mismatch");
- }
-
// TODO: Apply some sort of limit for Auth-Login-Attempt -- docs say it is the number of
- // attempts over the same authAttempt.
+ // attempts over the same authAttempt.
- // Check 2fa
- if (\App\CompanionApp::where('user_id', $user->id)->exists()) {
- $authAttempt = \App\AuthAttempt::recordAuthAttempt($user, $clientIP);
- if (!$authAttempt->waitFor2FA()) {
- throw new \Exception("2fa failed");
- }
- }
-
- return $user;
+ return $result['user'];
}
@@ -338,7 +309,7 @@
* Create an imap authentication response.
*
* @param \Illuminate\Http\Request $request The API request.
- * @param bool $prefGuam Wether or not guam is enabled.
+ * @param bool $prefGuam Whether or not Guam is enabled.
* @param string $password The password to include in the response.
*
* @return \Illuminate\Http\Response The response
diff --git a/src/app/User.php b/src/app/User.php
--- a/src/app/User.php
+++ b/src/app/User.php
@@ -2,6 +2,7 @@
namespace App;
+use App\AuthAttempt;
use App\Traits\AliasesTrait;
use App\Traits\BelongsToTenantTrait;
use App\Traits\EntitleableTrait;
@@ -657,16 +658,11 @@
}
if ($authenticated) {
- \Log::info("Successful authentication for {$this->email}");
-
// TODO: update last login time
if ($updatePassword && (empty($this->password) || empty($this->password_ldap))) {
$this->password = $password;
$this->save();
}
- } else {
- // TODO: Try actual LDAP?
- \Log::info("Authentication failed for {$this->email}");
}
return $authenticated;
@@ -693,43 +689,73 @@
/**
* Retrieve and authenticate a user
*
- * @param string $username The username.
- * @param string $password The password in plain text.
- * @param string $secondFactor The second factor (secondfactor from current request is used as fallback).
+ * @param string $username The username
+ * @param string $password The password in plain text
+ * @param ?string $clientIP The IP address of the client
*
* @return array ['user', 'reason', 'errorMessage']
*/
- public static function findAndAuthenticate($username, $password, $secondFactor = null): ?array
+ public static function findAndAuthenticate($username, $password, $clientIP = null): array
{
- $user = User::where('email', $username)->first();
+ $error = null;
- // TODO: 'reason' below could be AuthAttempt::REASON_*
- // TODO: $secondFactor argument is not used anywhere
+ if (!$clientIP) {
+ $clientIP = request()->ip();
+ }
+
+ $user = User::where('email', $username)->first();
if (!$user) {
- return ['reason' => 'notfound', 'errorMessage' => "User not found."];
+ $error = AuthAttempt::REASON_NOTFOUND;
}
- if (!$user->validateCredentials($username, $password)) {
- return ['reason' => 'credentials', 'errorMessage' => "Invalid password."];
+ // Check user password
+ if (!$error && !$user->validateCredentials($username, $password)) {
+ $error = AuthAttempt::REASON_PASSWORD;
}
- if (!$user->validateLocation(request()->ip())) {
- return ['reason' => 'geolocation', 'errorMessage' => "Country code mismatch."];
+ // Check user (request) location
+ if (!$error && !$user->validateLocation($clientIP)) {
+ $error = AuthAttempt::REASON_GEOLOCATION;
}
- if (!$secondFactor) {
- // Check the request if there is a second factor provided
- // as fallback.
- $secondFactor = request()->secondfactor;
+ // Check 2FA
+ if (!$error) {
+ try {
+ (new \App\Auth\SecondFactor($user))->validate(request()->secondfactor);
+ } catch (\Exception $e) {
+ $error = AuthAttempt::REASON_2FA_GENERIC;
+ $message = $e->getMessage();
+ }
}
- try {
- (new \App\Auth\SecondFactor($user))->validate($secondFactor);
- } catch (\Exception $e) {
- return ['reason' => 'secondfactor', 'errorMessage' => $e->getMessage()];
+ // Check 2FA - Companion App
+ if (!$error && \App\CompanionApp::where('user_id', $user->id)->exists()) {
+ $attempt = \App\AuthAttempt::recordAuthAttempt($user, $clientIP);
+ if (!$attempt->waitFor2FA()) {
+ $error = AuthAttempt::REASON_2FA;
+ }
}
+ if ($error) {
+ if ($user && empty($attempt)) {
+ $attempt = \App\AuthAttempt::recordAuthAttempt($user, $clientIP);
+ if (!$attempt->isAccepted()) {
+ $attempt->deny($error);
+ $attempt->save();
+ $attempt->notify();
+ }
+ }
+
+ if ($user) {
+ \Log::info("Authentication failed for {$user->email}");
+ }
+
+ return ['reason' => $error, 'errorMessage' => $message ?? \trans("auth.error.{$error}")];
+ }
+
+ \Log::info("Successful authentication for {$user->email}");
+
return ['user' => $user];
}
@@ -740,18 +766,18 @@
*
* @return \App\User User model object if found
*/
- public function findAndValidateForPassport($username, $password): User
+ public static function findAndValidateForPassport($username, $password): User
{
$result = self::findAndAuthenticate($username, $password);
if (isset($result['reason'])) {
- // TODO: Shouldn't we create AuthAttempt record here?
-
- if ($result['reason'] == 'secondfactor') {
+ if ($result['reason'] == AuthAttempt::REASON_2FA_GENERIC) {
// This results in a json response of {'error': 'secondfactor', 'error_description': '$errorMessage'}
throw new OAuthServerException($result['errorMessage'], 6, 'secondfactor', 401);
}
+ // TODO: Display specific error message if 2FA via Companion App was expected?
+
throw OAuthServerException::invalidCredentials();
}
diff --git a/src/resources/lang/en/auth.php b/src/resources/lang/en/auth.php
--- a/src/resources/lang/en/auth.php
+++ b/src/resources/lang/en/auth.php
@@ -18,4 +18,10 @@
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
'logoutsuccess' => 'Successfully logged out.',
+ 'error.password' => "Invalid password",
+ 'error.geolocation' => "Country code mismatch",
+ 'error.nofound' => "User not found",
+ 'error.2fa' => "Second factor failure",
+ 'error.2fa-generic' => "Second factor failure",
+
];

File Metadata

Mime Type
text/plain
Expires
Sat, Apr 4, 1:12 AM (1 d, 10 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18827537
Default Alt Text
D3698.1775265136.diff (9 KB)

Event Timeline