diff --git a/app/Config/Aauth.php b/app/Config/Aauth.php index 1bd3584..305bffc 100644 --- a/app/Config/Aauth.php +++ b/app/Config/Aauth.php @@ -292,6 +292,49 @@ class Aauth extends BaseConfig public $captchaSiteKey = ''; public $captchaSecret = ''; + /* + |-------------------------------------------------------------------------- + | Social Login Variables + |-------------------------------------------------------------------------- + | + | 'socialEnabled' + | + | Enables the oAuth2 functionalities to login or createUser trough social + | logins like google, facebook, twitter, github and many more. + | (default: false) + | + | 'socialRemeber' + | + | Remember social login user. + | Available Options: + | - false (disabled) + | - true (use social expires date) + | - Relative date format (e.g. '+ 1 week', '+ 1 month') for details + | see http://php.net/manual/de/datetime.formats.relative.php + | (default: true) + | + | 'socialProviders' + | + | Social Provider list + | (default: []) + | + */ + public $socialEnabled = false; + public $socialRemember = true; + public $socialProviders = []; + + // public $socialEnabled = true; + // public $socialRemember = true; + // public $socialProviders = [ + // 'Facebook' => [ + // 'enabled' => true, + // 'keys' => [ + // 'id' => '307655649901891', + // 'secret' => 'fb814dea3a38d36aa66222efab35c337', + // ], + // ], + // ]; + /* |-------------------------------------------------------------------------- | Group Variables diff --git a/app/Controllers/Account/Edit.php b/app/Controllers/Account/Edit.php index 8287528..d24277a 100644 --- a/app/Controllers/Account/Edit.php +++ b/app/Controllers/Account/Edit.php @@ -85,6 +85,16 @@ class Edit extends Controller } } + if ($this->config->socialEnabled) + { + $data['providers'] = []; + + foreach ($this->aauth->getProviders() as $provider) + { + $data['providers'][$provider] = $this->aauth->getSocialIdentifier($provider, $userId); + } + } + $data['useUsername'] = $this->config->loginUseUsername; echo view('Account/Edit', $data); diff --git a/app/Controllers/Account/Home.php b/app/Controllers/Account/Home.php index 7ab1d27..7c3394e 100644 --- a/app/Controllers/Account/Home.php +++ b/app/Controllers/Account/Home.php @@ -53,6 +53,16 @@ class Home extends Controller { $data['user'] = $this->aauth->getUser(); + if ($this->config->socialEnabled) + { + $data['providers'] = []; + + foreach ($this->aauth->getProviders() as $provider) + { + $data['providers'][$provider] = $this->aauth->getSocialIdentifier($provider, $data['user']['id']); + } + } + echo view('Account/Home', $data); } } diff --git a/app/Controllers/Account/Login.php b/app/Controllers/Account/Login.php index b992074..3e8db26 100644 --- a/app/Controllers/Account/Login.php +++ b/app/Controllers/Account/Login.php @@ -71,6 +71,11 @@ class Login extends Controller '/assets/css/login.css' ]; + if ($this->config->socialEnabled) + { + $data['providers'] = $this->aauth->getProviders(); + } + echo view('Account/Login', $data); } } diff --git a/app/Controllers/Account/Register.php b/app/Controllers/Account/Register.php index 06ef87b..9c63310 100644 --- a/app/Controllers/Account/Register.php +++ b/app/Controllers/Account/Register.php @@ -74,6 +74,11 @@ class Register extends Controller '/assets/css/login.css' ]; + if ($this->config->socialEnabled) + { + $data['providers'] = $this->aauth->getProviders(); + } + echo view('Account/Register', $data); } } diff --git a/app/Controllers/Account/Social.php b/app/Controllers/Account/Social.php new file mode 100644 index 0000000..ef78e40 --- /dev/null +++ b/app/Controllers/Account/Social.php @@ -0,0 +1,170 @@ +config = new AauthConfig(); + $this->aauth = new Aauth(); + $this->request = Services::request(); + helper('form'); + } + + /** + * Index + * + * @param string $provider Provider Name + * + * @return redirect + */ + public function connect(string $provider = null) + { + if ($provider) + { + session()->setFlashdata('social_provider', $provider); + } + else + { + $provider = session('social_provider'); + } + + if ($userId = $this->aauth->getUserId()) + { + if ($this->aauth->authenticateProvider($provider, 'account/social/connect/')) + { + if ($userId = $this->aauth->getUserId()) + { + helper('text'); + $userProfile = $this->aauth->getSocialDetails($provider); + $password = random_string('alnum', (config('Aauth')->passwordMin + 2)); + $username = preg_replace('/[^A-Za-z0-9]/', '', $userProfile->displayName); + + $this->aauth->linkSocial($userId, $provider); + } + } + + return redirect()->to(site_url('/account')); + } + + return redirect()->to('/'); + } + /** + * Index + * + * @param string $provider Provider Name + * + * @return redirect + */ + public function disconnect(string $provider) + { + if ($userId = $this->aauth->getUserId()) + { + $this->aauth->unlinkSocial($userId, $provider); + + return redirect()->to(site_url('/account')); + } + + return redirect()->to('/'); + } + + /** + * Index + * + * @param string $provider Provider Name + * + * @return redirect + */ + public function login(string $provider = null) + { + if ($provider) + { + session()->setFlashdata('social_provider', $provider); + } + else + { + $provider = session('social_provider'); + } + + if ($this->aauth->authenticateProvider($provider, 'account/social/login/')) + { + if ($this->aauth->loginSocial($provider)) + { + return redirect()->to(site_url('/account')); + } + } + + return redirect()->to(site_url('/account/login'))->with('errors', lang('Aauth.notFoundUser')); + } + + /** + * Index + * + * @param string $provider Provider Name + * + * @return redirect + */ + public function register(string $provider = null) + { + if ($provider) + { + session()->setFlashdata('social_provider', $provider); + } + else + { + $provider = session('social_provider'); + } + + if ($this->aauth->authenticateProvider($provider, 'account/social/register/')) + { + if (! $this->aauth->loginSocial($provider)) + { + helper('text'); + $userProfile = $this->aauth->getSocialDetails($provider); + $password = random_string('alnum', (config('Aauth')->passwordMin + 2)); + $username = preg_replace('/[^A-Za-z0-9]/', '', $userProfile->displayName); + + if ($userId = $this->aauth->createUser($userProfile->email, $password, $username)) + { + $this->aauth->linkSocial($userId, $provider); + $this->aauth->loginSocial($provider); + + return redirect()->to(site_url('/account')); + } + + return redirect()->to(site_url('/account/register'))->with('errors', $this->aauth->printErrors('
', true)); + } + + return redirect()->to(site_url('/account')); + } + } +} diff --git a/app/Language/en/Account.php b/app/Language/en/Account.php index c86098a..973dd3b 100644 --- a/app/Language/en/Account.php +++ b/app/Language/en/Account.php @@ -28,12 +28,15 @@ return [ 'homeText' => 'Account Details', 'homeLabelUsername' => 'Username', 'homeLabelEmail' => 'Email address', + 'homeLabelSocial' => 'Connected Social Accounts', 'editHeader' => 'Edit Profile', 'editLabelUsername' => 'Username', 'editLabelEmail' => 'Email address', 'editLabelPassword' => 'Password', 'editLabelSubmit' => 'Save changes', + 'editLabelSocialConnect' => 'Connect with ', + 'editLabelSocialDisconnect' => 'Disconnect with ', 'linkBackToLogin' => 'Back to Login', 'linkLogin' => 'Login', @@ -46,6 +49,7 @@ return [ 'loginLabelPassword' => 'Password', 'loginLabelRemember' => 'Remember me', 'loginLabelSubmit' => 'Login', + 'loginLabelSocial' => 'Login with ', 'registerHeader' => 'Create new Account', 'registerLabelUsername' => 'Username', @@ -53,6 +57,7 @@ return [ 'registerLabelPassword' => 'Password', 'registerLabelRemember' => 'Remember me', 'registerLabelSubmit' => 'Create Account', + 'registerLabelSocial' => 'Create Account with ', 'registerRequired' => 'Required', 'verificationHeader' => 'Account Verification', diff --git a/app/Libraries/Aauth.php b/app/Libraries/Aauth.php index db76d14..463698d 100644 --- a/app/Libraries/Aauth.php +++ b/app/Libraries/Aauth.php @@ -131,6 +131,10 @@ class Aauth if ($this->config->totpEnabled) { $this->modules = array_merge($this->config->modules, ['TOTP']); + + if ($this->config->socialEnabled) + { + $this->modules = array_merge($this->modules, ['Social']); } $this->cachePermIds = []; @@ -378,23 +382,7 @@ class Aauth if ($remember) { - helper('text'); - $expire = $this->config->loginRemember; - $userId = base64_encode($user['id']); - $randomString = random_string('alnum', 32); - $selectorString = random_string('alnum', 16); - - $cookieData['name'] = $this->config->loginRememberCookie; - $cookieData['value'] = $userId . ';' . $randomString . ';' . $selectorString; - $cookieData['expire'] = YEAR; - - $tokenData['user_id'] = $user['id']; - $tokenData['random_hash'] = password_hash($randomString, PASSWORD_DEFAULT); - $tokenData['selector_hash'] = password_hash($selectorString, PASSWORD_DEFAULT); - $tokenData['expires_at'] = date('Y-m-d H:i:s', strtotime($expire)); - - set_cookie($cookieData); - $loginTokenModel->insert($tokenData); + $this->generateRemember($user['id']); } $userModel->updateLastLogin($user['id']); @@ -428,6 +416,43 @@ class Aauth } } + /** + * Generate Remember + * + * @param integer $userId User Id + * @param string|integer $expire Expire Date, relative Date or Timestamp + * + * @return void + */ + protected function generateRemember(int $userId, string $expire = null) + { + helper('cookie'); + helper('text'); + + if (! $expire) + { + $expire = $this->config->loginRemember; + } + + $userIdEncoded = base64_encode($userId); + $randomString = random_string('alnum', 32); + $selectorString = random_string('alnum', 16); + + $cookieData['name'] = $this->config->loginRememberCookie; + $cookieData['value'] = $userIdEncoded . ';' . $randomString . ';' . $selectorString; + $cookieData['expire'] = YEAR; + + \Config\Services::response()->setCookie($cookieData)->send(); + + $tokenData['user_id'] = $userId; + $tokenData['random_hash'] = password_hash($randomString, PASSWORD_DEFAULT); + $tokenData['selector_hash'] = password_hash($selectorString, PASSWORD_DEFAULT); + $tokenData['expires_at'] = date('Y-m-d H:i:s', strtotime($expire)); + + $loginTokenModel = $this->getModel('LoginToken'); + $loginTokenModel->insert($tokenData); + } + /** * Logout * @@ -515,6 +540,11 @@ class Aauth { $loginTokenModel->update($loginToken['id']); + if ($this->config->socialEnabled && $this->config->socialRemember) + { + $this->rebuildSocialStorage($loginToken['user_id']); + } + return $this->loginFast($loginToken['user_id']); } else diff --git a/app/Libraries/Aauth/Social.php b/app/Libraries/Aauth/Social.php new file mode 100644 index 0000000..d72c1ec --- /dev/null +++ b/app/Libraries/Aauth/Social.php @@ -0,0 +1,286 @@ +configHybridAuth['providers'] = $config->socialProviders; + $this->configHybridAuth['callback'] = site_url(); + } + + /** + * Login Social + * + * @param string $provider Provider Name + * + * @return boolean|object + */ + public function loginSocial(string $provider = null) + { + $userProfile = $this->getSocialDetails($provider); + + if ($userId = $this->getSocialUserId($provider, $userProfile->identifier)) + { + $session = service('session'); + $storage = $session->get($this->storageHybridAuth); + + $this->updateSocialProviderIdentifier($userId, 'storage', json_encode($storage)); + + if ($this->config->socialRemember) + { + $expires = $this->config->socialRemember; + + if ($expires === true) + { + $expires = $storage[strtolower($provider) . '.expires_at']; + } + + $this->generateRemember($userId, $expires); + } + return $this->loginFast($userId); + } + + return false; + } + + /** + * Link Social + * + * @param integer $userId User Id + * @param string $provider Provider Name + * + * @return boolean + */ + public function linkSocial(int $userId, string $provider) + { + $userProfile = $this->getSocialDetails($provider); + $this->updateSocialStorage($userId); + + return $this->updateSocialProviderIdentifier($userId, $provider, $userProfile->identifier); + } + + /** + * Unlink Social + * + * @param integer $userId User Id + * @param string $provider Provider Name + * + * @return boolean + */ + public function unlinkSocial(int $userId, string $provider) + { + $session = service('session'); + $session->remove($this->storageHybridAuth); + + $userVariableModel = $this->getModel('UserVariable'); + $userVariableModel->delete($userId, 'social_storage', true); + + return $userVariableModel->delete($userId, 'social_' . strtolower($provider), true); + } + + /** + * Rebuild Social Storage + * + * @param integer $userId User Id + * + * @return void + */ + public function rebuildSocialStorage(int $userId) + { + $userVariableModel = $this->getModel('UserVariable'); + $providers = $this->getProviders(); + + if ($storedData = $userVariableModel->find($userId, 'social_storage', true)) + { + $storedData = json_decode($storedData, true); + + foreach ($providers as $provider) + { + if ($storedData[strtolower($provider) . '.expires_at'] > time()) + { + $session = service('session'); + $session->set($this->storageHybridAuth, $storedData); + } + } + } + } + + /** + * Get Social User Id + * + * @param string $provider Provider Name + * @param string $identifier Identifier + * + * @return integer|boolean + */ + public function getSocialUserId(string $provider, string $identifier) + { + $userVariableModel = $this->getModel('UserVariable'); + $whereArray = [ + 'data_key' => 'social_' . strtolower($provider), + 'data_value' => $identifier, + 'system' => 1, + ]; + + if ($user = $userVariableModel->select('user_id')->where($whereArray)->first()) + { + return (int) $user['user_id']; + } + + return false; + } + + /** + * Get Social Identifier + * + * @param string $provider Provider Name + * @param integer $userId User Id + * + * @return integer|boolean + */ + public function getSocialIdentifier(string $provider, int $userId) + { + $userVariableModel = $this->getModel('UserVariable'); + $whereArray = [ + 'data_key' => 'social_' . strtolower($provider), + 'user_id' => $userId, + 'system' => 1, + ]; + + if ($user = $userVariableModel->select('data_value')->where($whereArray)->first()) + { + return $user['data_value']; + } + + return false; + } + + /** + * Get Social Details + * + * @param string $provider Provider Name + * + * @return \Hybridauth\User\Profile + */ + public function getSocialDetails(string $provider) + { + $hybridauth = new \Hybridauth\Hybridauth($this->configHybridAuth); + + $adapter = $hybridauth->getAdapter($provider); + + return $adapter->getUserProfile(); + } + + /** + * Get Providers + * + * @return array + */ + public function getProviders() + { + $hybridauth = new \Hybridauth\Hybridauth($this->configHybridAuth); + + return $hybridauth->getProviders(); + } + + /** + * Authenticate Provider + * + * @param string $provider Provider Name + * @param string $callbackUrl Callback Link + * + * @return \Hybridauth\Adapter\AdapterInterface + */ + public function authenticateProvider(string $provider, string $callbackUrl) + { + $this->configHybridAuth['callback'] = site_url($callbackUrl); + + $hybridauth = new \Hybridauth\Hybridauth($this->configHybridAuth); + $adapter = $hybridauth->authenticate($provider); + + return $adapter; + } + + /** + * Update Social Storage + * + * @param integer $userId User Id + * + * @return void + */ + private function updateSocialStorage(int $userId) + { + $session = service('session'); + $storage = $session->get($this->storageHybridAuth); + + $this->updateSocialProviderIdentifier($userId, 'storage', json_encode($storage)); + } + + /** + * Update Social Provider Identifier + * + * @param integer $userId User Id + * @param string $provider Provider Name + * @param string $identifier Identifier + * + * @return boolean + */ + private function updateSocialProviderIdentifier(int $userId, string $provider, string $identifier) + { + $userVariableModel = $this->getModel('UserVariable'); + + return $userVariableModel->save($userId, 'social_' . strtolower($provider), $identifier, true); + } +} diff --git a/app/Views/Account/Edit.php b/app/Views/Account/Edit.php index 590c3a0..b85100b 100644 --- a/app/Views/Account/Edit.php +++ b/app/Views/Account/Edit.php @@ -31,6 +31,16 @@ + +
+ $state): ?> + + + + + + + endSection() ?> diff --git a/app/Views/Account/Home.php b/app/Views/Account/Home.php index 766a695..16668b2 100644 --- a/app/Views/Account/Home.php +++ b/app/Views/Account/Home.php @@ -22,6 +22,18 @@ +
+
+ +
+
+ $state): ?> + +
+ + +
+
diff --git a/app/Views/Account/Login.php b/app/Views/Account/Login.php index b5caf76..9aacddf 100644 --- a/app/Views/Account/Login.php +++ b/app/Views/Account/Login.php @@ -36,6 +36,12 @@ + +
+ + + +