diff --git a/app/Http/Controllers/Contact/LoginController.php b/app/Http/Controllers/Contact/LoginController.php new file mode 100644 index 000000000000..0c11104e572b --- /dev/null +++ b/app/Http/Controllers/Contact/LoginController.php @@ -0,0 +1,308 @@ +setCurrentCompanyId($user->companies()->first()->account->default_company_id); + } + + + /** + * Login via API + * + * @param \Illuminate\Http\Request $request The request + * + * @return Response|User Process user login. + */ + public function apiLogin(Request $request) + { + /* + if (auth()->guard('contact')->attempt(['email' => $request->email, 'password' => $request->password], false) { + + return redirect()->intended('/admin'); + } +*/ + Auth::shouldUse('contact'); + + $this->validateLogin($request); + + if ($this->hasTooManyLoginAttempts($request)) { + $this->fireLockoutEvent($request); + + return response()->json(['message' => 'Too many login attempts, you are being throttled']); + } + + if ($this->attemptLogin($request)) + return $this->itemResponse($this->guard()->user()); + else { + + $this->incrementLoginAttempts($request); + + return response()->json(['message' => ctrans('texts.invalid_credentials')]); + } + + } + + /** + * Redirect the user to the provider authentication page + * + * @return void + */ + public function redirectToProvider(string $provider) + { + //'https://www.googleapis.com/auth/gmail.send','email','profile','openid' + // + if(request()->has('code')) + return $this->handleProviderCallback($provider); + else + return Socialite::driver($provider)->scopes()->redirect(); + } + + + public function redirectToProviderAndCreate(string $provider) + { + + $redirect_url = config('services.' . $provider . '.redirect') . '/create'; + + if(request()->has('code')) + return $this->handleProviderCallbackAndCreate($provider); + else + return Socialite::driver($provider)->redirectUrl($redirect_url)->redirect(); + + + } + + + /* + public function handleProviderCallbackAndCreate(string $provider) + { + $socialite_user = Socialite::driver($provider) + ->stateless() + ->user(); + + if($user = OAuth::handleAuth($socialite_user, $provider)) + { + Auth::login($user, true); + + return redirect($this->redirectTo); + } + else if(MultiDB::checkUserEmailExists($socialite_user->getEmail())) + { + Session::flash('error', 'User exists in system, but not with this authentication method'); //todo add translations + + return view('auth.login'); + } + else { + //todo + $name = OAuth::splitName($socialite_user->getName()); + + $new_account = [ + 'first_name' => $name[0], + 'last_name' => $name[1], + 'password' => '', + 'email' => $socialite_user->getEmail(), + 'oauth_user_id' => $socialite_user->getId(), + 'oauth_provider_id' => $provider + ]; + + $account = CreateAccount::dispatchNow($new_account); + + Auth::login($account->default_company->owner(), true); + + $cookie = cookie('db', $account->default_company->db); + + return redirect($this->redirectTo)->withCookie($cookie); + } + + } + */ + + /** + * We use this function when OAUTHING via the web interface + * + * @return redirect + + public function handleProviderCallback(string $provider) + { + $socialite_user = Socialite::driver($provider) + ->stateless() + ->user(); + + if($user = OAuth::handleAuth($socialite_user, $provider)) + { + Auth::login($user, true); + + return redirect($this->redirectTo); + } + else if(MultiDB::checkUserEmailExists($socialite_user->getEmail())) + { + Session::flash('error', 'User exists in system, but not with this authentication method'); //todo add translations + + return view('auth.login'); + } + else { + //todo + $name = OAuth::splitName($socialite_user->getName()); + + $new_account = [ + 'first_name' => $name[0], + 'last_name' => $name[1], + 'password' => '', + 'email' => $socialite_user->getEmail(), + 'oauth_user_id' => $socialite_user->getId(), + 'oauth_provider_id' => $provider + ]; + + $account = CreateAccount::dispatchNow($new_account); + + Auth::login($account->default_company->owner(), true); + + $cookie = cookie('db', $account->default_company->db); + + return redirect($this->redirectTo)->withCookie($cookie); + } + + } + */ + /** + * A client side authentication has taken place. + * We now digest the token and confirm authentication with + * the authentication server, the correct user object + * is returned to us here and we send back the correct + * user object payload - or error. + * + * This can be extended to a create route also - need to pass a ?create query parameter and + * then process the signup + * + * return User $user + */ + public function oauthApiLogin() + { + + $user = false; + + $oauth = new OAuth(); + + $user = $oauth->getProvider(request()->input('provider'))->getTokenResponse(request()->input('token')); + + if ($user) + return $this->itemResponse($user); + else + return $this->errorResponse(['message' => 'Invalid credentials'], 401); + + } + + + /** + * Received the returning object from the provider + * which we will use to resolve the user, we return the response in JSON format + * + * @return json + + public function handleProviderCallbackApiUser(string $provider) + { + $socialite_user = Socialite::driver($provider)->stateless()->user(); + + if($user = OAuth::handleAuth($socialite_user, $provider)) + { + return $this->itemResponse($user); + } + else if(MultiDB::checkUserEmailExists($socialite_user->getEmail())) + { + + return $this->errorResponse(['message'=>'User exists in system, but not with this authentication method'], 400); + + } + else { + //todo + $name = OAuth::splitName($socialite_user->getName()); + + $new_account = [ + 'first_name' => $name[0], + 'last_name' => $name[1], + 'password' => '', + 'email' => $socialite_user->getEmail(), + ]; + + $account = CreateAccount::dispatchNow($new_account); + + return $this->itemResponse($account->default_company->owner()); + } + + + } + */ +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 8cd305857d7c..9c63b72d3a42 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -11,6 +11,7 @@ namespace App\Http; +use App\Http\Middleware\ContactTokenAuth; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel @@ -53,6 +54,11 @@ class Kernel extends HttpKernel 'bindings', 'query_logging', ], + 'contact' => [ + 'throttle:60,1', + 'bindings', + 'query_logging', + ], 'db' => [ \App\Http\Middleware\SetDb::class, ], @@ -84,6 +90,7 @@ class Kernel extends HttpKernel 'query_logging' => \App\Http\Middleware\QueryLogging::class, 'token_auth' => \App\Http\Middleware\TokenAuth::class, 'api_secret_check' => \App\Http\Middleware\ApiSecretCheck::class, - + 'contact_token_auth' => \App\Http\Middleware\ContactTokenAuth::class, + 'contact_db' => \App\Http\Middleware\ContactSetDb::class, ]; } diff --git a/app/Http/Middleware/ContactTokenAuth.php b/app/Http/Middleware/ContactTokenAuth.php new file mode 100644 index 000000000000..df2208ce73f0 --- /dev/null +++ b/app/Http/Middleware/ContactTokenAuth.php @@ -0,0 +1,58 @@ +header('X-API-TOKEN') && ($client_contact = ClientContact::with(['company'])->whereRaw("BINARY `token`= ?",[$request->header('X-API-TOKEN')])->first() ) ) + { + + //client_contact who once existed, but has been soft deleted + if(!$client_contact) + return response()->json(json_encode(['message' => 'Authentication disabled for user.'], JSON_PRETTY_PRINT) ,403); + + + //client_contact who has been disabled + if($client_contact->is_locked) + return response()->json(json_encode(['message' => 'Access is locked.'], JSON_PRETTY_PRINT) ,403); + + //stateless, don't remember the contact. + auth()->guard('contact')->login($client_contact, false); + + //event(new UserLoggedIn($user)); //todo + + } + else { + + return response()->json(json_encode(['message' => 'Invalid token'], JSON_PRETTY_PRINT) ,403); + } + + return $next($request); + } + +} diff --git a/app/Http/Middleware/SetContactDb.php b/app/Http/Middleware/SetContactDb.php new file mode 100644 index 000000000000..d95b47b7b51c --- /dev/null +++ b/app/Http/Middleware/SetContactDb.php @@ -0,0 +1,58 @@ + 'Database could not be set']; + + // we must have a token passed, that matched a token in the db, and multiDB is enabled. + // todo i don't think we can call the DB prior to setting it???? i think this if statement needs to be rethought + //if( $request->header('X-API-TOKEN') && (CompanyToken::whereRaw("BINARY `token`= ?",[$request->header('X-API-TOKEN')])->first()) && config('ninja.db.multi_db_enabled')) + if( $request->header('X-API-TOKEN') && config('ninja.db.multi_db_enabled')) + { + + if(! MultiDB::contactFindAndSetDb($request->header('X-API-TOKEN'))) + { + + return response()->json(json_encode($error, JSON_PRETTY_PRINT) ,403); + + } + + } + else { + + + return response()->json(json_encode($error, JSON_PRETTY_PRINT) ,403); + + } + + return $next($request); + } + + +} diff --git a/app/Libraries/MultiDB.php b/app/Libraries/MultiDB.php index 18efb7fdad8b..f8b58d715ceb 100644 --- a/app/Libraries/MultiDB.php +++ b/app/Libraries/MultiDB.php @@ -11,6 +11,7 @@ namespace App\Libraries; +use App\Models\ClientContact; use App\Models\CompanyToken; use App\Models\User; @@ -84,6 +85,25 @@ class MultiDB return null; } + public static function contactFindAndSetDb($token) :bool + { + + foreach (self::$dbs as $db) + { + + if($ct = ClientContact::on($db)->whereRaw("BINARY `token`= ?", [$token])->first()) + { + + self::setDb($ct->company->db); + + return true; + } + + } + return false; + + } + public static function findAndSetDb($token) :bool { diff --git a/app/Models/Client.php b/app/Models/Client.php index d0653b14e5dd..9b47714e4d32 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -128,9 +128,7 @@ class Client extends BaseModel public function getMergedSettings() { - return ClientSettings::buildClientSettings(new CompanySettings($this->company->settings), new ClientSettings($this->settings)); - } public function documents() diff --git a/app/Models/ClientContact.php b/app/Models/ClientContact.php index ee9287f26aa3..38b78d93cfb8 100644 --- a/app/Models/ClientContact.php +++ b/app/Models/ClientContact.php @@ -11,6 +11,8 @@ namespace App\Models; +use App\Models\Company; +use App\Models\User; use App\Utils\Traits\MakesHash; use Hashids\Hashids; use Illuminate\Contracts\Auth\MustVerifyEmail; @@ -73,4 +75,14 @@ class ClientContact extends Authenticatable $this->where('is_primary', true); } + public function company() + { + return $this->belongsTo(Company::class); + } + + public function user() + { + return $this->belongsTo(User::class); + } + } diff --git a/app/Models/Company.php b/app/Models/Company.php index 144b0c261467..46cee31dc391 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -99,7 +99,7 @@ class Company extends BaseModel */ public function contacts() { - return $this->hasMany(Contact::class); + return $this->hasMany(ClientContact::class); } /** diff --git a/app/Models/User.php b/app/Models/User.php index ea1cd322f467..af6fa7ac758a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -222,7 +222,7 @@ class User extends Authenticatable implements MustVerifyEmail public function contacts() { - return $this->hasMany(Contact::class); + return $this->hasMany(ClientContact::class); } diff --git a/app/Providers/MultiDatabaseUserProvider.php b/app/Providers/MultiDatabaseUserProvider.php index f349169deae0..7b5c08b4d63c 100644 --- a/app/Providers/MultiDatabaseUserProvider.php +++ b/app/Providers/MultiDatabaseUserProvider.php @@ -227,10 +227,14 @@ Log::error($query->count()); private function setDefaultDatabase($id = false, $email = false, $token = false) : void { Log::error('setting DB'); +Log::error('model = '.$this->model); + foreach (MultiDB::getDbs() as $database) { $this->setDB($database); - $query = $this->conn->table('users'); +// $query = $this->conn->table('users'); + + $query = $this->conn->table((new $this->model)->getTable()); if ($id) $query->where('id', '=', $id); diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 4734a255db04..8030b8190b70 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -136,7 +136,7 @@ class RouteServiceProvider extends ServiceProvider $this->mapWebRoutes(); - // + $this->mapContactApiRoutes(); } /** @@ -168,6 +168,19 @@ class RouteServiceProvider extends ServiceProvider ->group(base_path('routes/api.php')); } - + /** + * Define the "api" routes for the application. + * + * These routes are typically stateless. + * + * @return void + */ + protected function mapContactApiRoutes() + { + Route::prefix('') + ->middleware('contact') + ->namespace($this->namespace) + ->group(base_path('routes/contact.php')); + } } diff --git a/config/auth.php b/config/auth.php index 62996984ffab..fa2a042ea431 100644 --- a/config/auth.php +++ b/config/auth.php @@ -78,7 +78,7 @@ return [ ], 'contacts' => [ 'driver' => env('CONTACT_AUTH_PROVIDER', 'eloquent'), - 'model' => App\Models\Contact::class, + 'model' => App\Models\ClientContact::class, ], // 'users' => [ diff --git a/database/factories/ClientContactFactory.php b/database/factories/ClientContactFactory.php index 57b88024511a..aaeb1ecf2ae9 100644 --- a/database/factories/ClientContactFactory.php +++ b/database/factories/ClientContactFactory.php @@ -21,7 +21,8 @@ $factory->define(App\Models\ClientContact::class, function (Faker $faker) { 'email_verified_at' => now(), 'email' => $faker->unique()->safeEmail, 'password' => bcrypt('password'), - 'remember_token' => str_random(10) + 'remember_token' => str_random(10), + 'token' => str_random(64), ]; }); diff --git a/database/migrations/2014_10_13_000000_create_users_table.php b/database/migrations/2014_10_13_000000_create_users_table.php index 45e2ca4edcd0..6f2a01e71a04 100644 --- a/database/migrations/2014_10_13_000000_create_users_table.php +++ b/database/migrations/2014_10_13_000000_create_users_table.php @@ -332,6 +332,8 @@ class CreateUsersTable extends Migration $table->unsignedInteger('avatar_height')->nullable(); $table->unsignedInteger('avatar_size')->nullable(); $table->string('password'); + $table->string('token')->nullable(); + $table->boolean('is_locked')->default(false); $table->rememberToken(); $table->timestamps(6); $table->softDeletes(); diff --git a/tests/Unit/SystemHealthTest.php b/tests/Unit/SystemHealthTest.php new file mode 100644 index 000000000000..19a59b9ff551 --- /dev/null +++ b/tests/Unit/SystemHealthTest.php @@ -0,0 +1,43 @@ +assertTrue(is_array($results)); + + $this->assertTrue(count($results) > 1); + + $this->assertTrue($results['system_health']); + + $this->assertTrue($results['extensions'][0]['mysqli']); + $this->assertTrue($results['extensions'][1]['gd']); + $this->assertTrue($results['extensions'][2]['curl']); + $this->assertTrue($results['extensions'][3]['zip']); + + + $this->assertTrue($results['dbs'][0]['db-ninja-01']); + + } +}