From 3a7cea849c54961bc4087b5d221a30e6822946de Mon Sep 17 00:00:00 2001
From: Dominik Hebeler <dominik@hebeler.club>
Date: Wed, 21 Aug 2024 13:09:34 +0200
Subject: [PATCH] centrqalize login code storage

---
 .../Http/Controllers/LogsApiController.php    | 31 +++++----
 .../Models/Authorization/LogsAuthGuard.php    | 69 +++++++++++++++++--
 metager/app/Models/Authorization/LogsUser.php | 14 ++--
 metager/app/Providers/LogsUserProvider.php    |  2 +-
 metager/resources/views/logs/login.blade.php  |  3 +
 5 files changed, 96 insertions(+), 23 deletions(-)

diff --git a/metager/app/Http/Controllers/LogsApiController.php b/metager/app/Http/Controllers/LogsApiController.php
index 0320d4725..d01a70d34 100644
--- a/metager/app/Http/Controllers/LogsApiController.php
+++ b/metager/app/Http/Controllers/LogsApiController.php
@@ -7,6 +7,7 @@ use App\Models\Authorization\LogsUser;
 use Auth;
 use DB;
 use Illuminate\Http\Request;
+use Illuminate\Support\MessageBag;
 use Mail;
 use Validator;
 
@@ -71,12 +72,19 @@ class LogsApiController extends Controller
 
     public function login(Request $request)
     {
+        if (!is_null(Auth::guard("logs")->user()->getAuthIdentifier())) {
+            session()->flash("email", Auth::guard("logs")->user()->getAuthIdentifier());
+        }
         return view("logs.login", ['title' => __('titles.logs.login')]);
     }
     public function login_post(Request $request)
     {
+        $email_validation = "email:rfc,dns";
+        if (!$request->filled("code")) {
+            $email_validation = "required|" . $email_validation;
+        }
         $validator = Validator::make($request->input(), [
-            "email" => "required|email:rfc,dns",
+            "email" => $email_validation,
             'code' => 'regex:/^\d{6}$/i'
         ]);
 
@@ -86,19 +94,18 @@ class LogsApiController extends Controller
         }
 
         $validated = $validator->validated();
-        $user = app(LogsUser::class)->fetchUserByMail($validated["email"]);
-
-        if (!is_null($user->getAuthIdentifier()) && !is_null($user->getAuthPassword())) {
-            if (!empty($validated["code"])) {
-                if (Auth::guard("logs")->attempt(["username" => $validated["email"], "password" => $validated["code"]])) {
-                    session(["logs_authenticated" => true]);
-                    $url = session("url.intended");
-                    return redirect($url);
-                }
+        if (isset($validated["code"])) {
+            if (Auth::guard("logs")->validate(["username" => $validated["email"], "password" => $validated["code"]])) {
+                $url = session("url.intended");
+                return redirect($url);
             } else {
-                // Send Email with login Code
-                Mail::to($validated["email"])->send(new LogsLoginCode($user->getAuthPassword()));
+                // Code wasn't correct
+                $errors = new MessageBag();
+                $errors->add("invalid_logincode", "Login Code was invalid");
+                return redirect(route("logs:login"))->withErrors($errors);
             }
+        } else {
+            Auth::guard("logs")->init($validated["email"]);
         }
         return redirect(route("logs:login"))->with(["email" => $validated["email"]]);
     }
diff --git a/metager/app/Models/Authorization/LogsAuthGuard.php b/metager/app/Models/Authorization/LogsAuthGuard.php
index eca6b6058..9a7fc1ea1 100644
--- a/metager/app/Models/Authorization/LogsAuthGuard.php
+++ b/metager/app/Models/Authorization/LogsAuthGuard.php
@@ -1,21 +1,71 @@
 <?php
 
 namespace App\Models\Authorization;
+use App\Mail\LogsLoginCode;
+use Cache;
 use Illuminate\Contracts\Auth\Authenticatable;
 use Illuminate\Contracts\Auth\UserProvider;
 use Illuminate\Http\Request;
+use Mail;
 
 class LogsAuthGuard implements \Illuminate\Contracts\Auth\Guard
 {
+    const SESSION_LOGS_USERNAME_KEY = "logs_username";
+    const CACHE_LOGS_PASSWORD_KEY = "logs_password";
+    const SESSION_LOGS_AUTHORIZED_KEY = "logs_authorized";
+    const SESSION_LOGS_AUTH_TRIES_KEY = "logs_authroization_tries";
     private UserProvider $userProvider;
     private Request $request;
     private Authenticatable|null $user;
+    private bool $authorized = false;
     public function __construct(UserProvider $userProvider, Request $request)
     {
         $this->userProvider = $userProvider;
         $this->request = $request;
         $this->user = null;
+        if (!is_null(session(self::SESSION_LOGS_AUTHORIZED_KEY))) {
+            $this->authorized = session(self::SESSION_LOGS_AUTHORIZED_KEY);
+        }
+        if (!is_null(session(self::SESSION_LOGS_USERNAME_KEY))) {
+            $credentials = ["username" => session(self::SESSION_LOGS_USERNAME_KEY)];
+            if (Cache::has(self::CACHE_LOGS_PASSWORD_KEY . ":" . $credentials["username"])) {
+                $credentials["password"] = Cache::get(self::CACHE_LOGS_PASSWORD_KEY . ":" . $credentials["username"]);
+            }
+            $this->user = $this->userProvider->retrieveByCredentials($credentials);
+            $this->sendPassword();
+        }
+    }
+
+    /**
+     * Initializes a new authorization providing an email address.
+     * That email address has to be authorized with a logincode furtheron.
+     * 
+     * 
+     * @param string $email
+     * @return void
+     */
+    public function init(string $email)
+    {
+        $this->user = $this->userProvider->retrieveByCredentials(["username" => $email]);
+        session([
+            self::SESSION_LOGS_USERNAME_KEY => $this->user->getAuthIdentifier(),
+        ]);
+        $this->sendPassword();
     }
+    /**
+     * Checks if a logincode needs to be sent out to the user by mail
+     * @return void
+     */
+    private function sendPassword()
+    {
+        // Store the password
+        if (!$this->authorized && !is_null($this->user->getAuthPassword()) && !Cache::has(self::CACHE_LOGS_PASSWORD_KEY . ":" . $this->user->getAuthIdentifier())) {
+            Cache::put(self::CACHE_LOGS_PASSWORD_KEY . ":" . $this->user->getAuthIdentifier(), $this->user->getAuthPassword(), now()->addMinutes(5));
+            // Send Email with login Code
+            Mail::to($this->user->getAuthIdentifier())->send(new LogsLoginCode($this->user->getAuthPassword()));
+        }
+    }
+
     /**
      * Determine if the current user is authenticated.
      *
@@ -23,7 +73,7 @@ class LogsAuthGuard implements \Illuminate\Contracts\Auth\Guard
      */
     public function check(): bool
     {
-        return !is_null($this->user);
+        return $this->authorized;
     }
 
     /**
@@ -67,13 +117,24 @@ class LogsAuthGuard implements \Illuminate\Contracts\Auth\Guard
     public function validate(array $credentials = [])
     {
         if (empty($credentials["username"]) || empty($credentials["password"])) {
+            session([self::SESSION_LOGS_AUTHORIZED_KEY => false]);
+            $this->authorized = false;
             return false;
         }
-        $user = $this->userProvider->retrieveByCredentials($credentials);
-        if (!is_null($user) && $this->userProvider->validateCredentials($user, $credentials)) {
-            $this->setUser($user);
+
+        if (!is_null($this->user) && $this->userProvider->validateCredentials($this->user, $credentials)) {
+            session([self::SESSION_LOGS_AUTHORIZED_KEY => true]);
+            $this->authorized = true;
             return true;
         } else {
+            session([self::SESSION_LOGS_AUTHORIZED_KEY => false]);
+            $tries = session(self::SESSION_LOGS_AUTH_TRIES_KEY, 0);
+            if ($tries < 5) {
+                session([self::SESSION_LOGS_AUTH_TRIES_KEY => ($tries + 1)]);
+            } else {
+                Cache::forget(self::CACHE_LOGS_PASSWORD_KEY . ":" . $this->user->getAuthIdentifier());
+            }
+            $this->authorized = false;
             return false;
         }
     }
diff --git a/metager/app/Models/Authorization/LogsUser.php b/metager/app/Models/Authorization/LogsUser.php
index 37643fbe9..4eaa51e50 100644
--- a/metager/app/Models/Authorization/LogsUser.php
+++ b/metager/app/Models/Authorization/LogsUser.php
@@ -11,23 +11,25 @@ class LogsUser implements Authenticatable
     private int $discount;
 
 
-    public function fetchUserByMail(string $username)
+    public function fetchUserByCredentials(array $credentials)
     {
-        $user = DB::table("logs_users")->where("email", "=", $username)->first();
+        $user = DB::table("logs_users")->where("email", "=", $credentials["username"])->first();
+        $this->email = $credentials["username"];
         if (!is_null($user)) {
-            $this->email = $username;
-            if (!empty(session("login_token"))) {
-                $this->login_token = session("login_token");
+            if (isset($credentials["password"])) {
+                $this->login_token = $credentials["password"];
             } else {
                 $this->login_token = (string) rand(0, 999999);
                 while (strlen($this->login_token) < 6) {
                     $this->login_token = '0' . $this->login_token;
                 }
-                session(["login_token" => $this->login_token]);
             }
             $this->discount = $user->discount;
+        } else {
+            $this->login_token = "";
         }
         return $this;
+
     }
     /**
      * Get the name of the unique identifier for the user.
diff --git a/metager/app/Providers/LogsUserProvider.php b/metager/app/Providers/LogsUserProvider.php
index f5ac5bcf6..6f025a8de 100644
--- a/metager/app/Providers/LogsUserProvider.php
+++ b/metager/app/Providers/LogsUserProvider.php
@@ -60,7 +60,7 @@ class LogsUserProvider implements UserProvider
         if (empty($credentials)) {
             return;
         }
-        return $this->user->fetchUserByMail($credentials["username"]);
+        return $this->user->fetchUserByCredentials($credentials);
     }
 
     /**
diff --git a/metager/resources/views/logs/login.blade.php b/metager/resources/views/logs/login.blade.php
index 38c0511f8..21df09d45 100644
--- a/metager/resources/views/logs/login.blade.php
+++ b/metager/resources/views/logs/login.blade.php
@@ -16,6 +16,9 @@
 @endif
 <form action="{{ route("logs:login:post")}}" method="post">
     <input type="hidden" name="_token" value="{{ csrf_token() }}" />
+    @if(session("email"))
+        <input type="hidden" name="email" value="{{session('email')}}">
+    @endif
     <div class="input-group">
         <label for="email">Email Addresse</label>
         @if(session("email"))
-- 
GitLab