diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 7401affed..d18a9bd2c 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -22,8 +22,7 @@ class Handler extends ExceptionHandler * * This is a great spot to send exceptions to Sentry, Bugsnag, etc. * - * @param \Exception $e - * @return void + * @param \Exception $e */ public function report(Exception $e) { @@ -39,6 +38,11 @@ class Handler extends ExceptionHandler */ public function render($request, Exception $e) { + if($e instanceof NotifyException) { + \Session::flash('error', $e->message); + return response()->redirectTo($e->redirectLocation); + } + return parent::render($request, $e); } } diff --git a/app/Exceptions/NotifyException.php b/app/Exceptions/NotifyException.php new file mode 100644 index 000000000..c9ff9975d --- /dev/null +++ b/app/Exceptions/NotifyException.php @@ -0,0 +1,21 @@ +message = $message; + $this->redirectLocation = $redirectLocation; + parent::__construct(); + } +} \ No newline at end of file diff --git a/app/Exceptions/SocialDriverNotConfigured.php b/app/Exceptions/SocialDriverNotConfigured.php new file mode 100644 index 000000000..35fd59e01 --- /dev/null +++ b/app/Exceptions/SocialDriverNotConfigured.php @@ -0,0 +1,6 @@ +middleware('guest', ['except' => 'getLogout']); + $this->socialite = $socialite; + $this->userRepo = $userRepo; } /** * Get a validator for an incoming registration request. * - * @param array $data + * @param array $data * @return \Illuminate\Contracts\Validation\Validator */ protected function validator(array $data) { return Validator::make($data, [ - 'name' => 'required|max:255', - 'email' => 'required|email|max:255|unique:users', + 'name' => 'required|max:255', + 'email' => 'required|email|max:255|unique:users', 'password' => 'required|confirmed|min:6', ]); } @@ -56,15 +66,110 @@ class AuthController extends Controller /** * Create a new user instance after a valid registration. * - * @param array $data + * @param array $data * @return User */ protected function create(array $data) { return User::create([ - 'name' => $data['name'], - 'email' => $data['email'], + 'name' => $data['name'], + 'email' => $data['email'], 'password' => bcrypt($data['password']), ]); } + + /** + * Show the application login form. + * + * @return \Illuminate\Http\Response + */ + public function getLogin() + { + + if (view()->exists('auth.authenticate')) { + return view('auth.authenticate'); + } + + $socialDrivers = $this->getActiveSocialDrivers(); + + return view('auth.login', ['socialDrivers' => $socialDrivers]); + } + + /** + * Redirect to the relevant social site. + * @param $socialDriver + * @return \Symfony\Component\HttpFoundation\RedirectResponse + */ + public function getSocialLogin($socialDriver) + { + $driver = $this->validateSocialDriver($socialDriver); + return $this->socialite->driver($driver)->redirect(); + } + + /** + * The callback for social login services. + * + * @param $socialDriver + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws UserNotFound + */ + public function socialCallback($socialDriver) + { + $driver = $this->validateSocialDriver($socialDriver); + // Get user details from social driver + $socialUser = $this->socialite->driver($driver)->user(); + $user = $this->userRepo->getByEmail($socialUser->getEmail()); + + // Redirect if the email is not a current user. + if ($user === null) { + throw new UserNotFound('A user with the email ' . $socialUser->getEmail() . ' was not found.', '/login'); + } + + \Auth::login($user, true); + return redirect($this->redirectPath); + } + + /** + * Ensure the social driver is correct and supported. + * + * @param $socialDriver + * @return string + * @throws SocialDriverNotConfigured + */ + protected function validateSocialDriver($socialDriver) + { + $driver = trim(strtolower($socialDriver)); + + if (!in_array($driver, $this->validSocialDrivers)) abort(404, 'Social Driver Not Found'); + if(!$this->checkSocialDriverConfigured($driver)) throw new SocialDriverNotConfigured; + + return $driver; + } + + /** + * Check a social driver has been configured correctly. + * @param $driver + * @return bool + */ + protected function checkSocialDriverConfigured($driver) + { + $upperName = strtoupper($driver); + $config = [env($upperName . '_APP_ID', false), env($upperName . '_APP_SECRET', false), env('APP_URL', false)]; + return (!in_array(false, $config) && !in_array(null, $config)); + } + + /** + * Gets the names of the active social drivers. + * @return array + */ + protected function getActiveSocialDrivers() + { + $activeDrivers = []; + foreach($this->validSocialDrivers as $driverName) { + if($this->checkSocialDriverConfigured($driverName)) { + $activeDrivers[$driverName] = true; + } + } + return $activeDrivers; + } } diff --git a/app/Http/routes.php b/app/Http/routes.php index 92e416da8..075f49851 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -82,6 +82,10 @@ Route::group(['middleware' => 'auth'], function () { Route::get('/login', 'Auth\AuthController@getLogin'); Route::post('/login', 'Auth\AuthController@postLogin'); Route::get('/logout', 'Auth\AuthController@getLogout'); +// Login using social authentication +Route::get('/login/service/{socialService}', 'Auth\AuthController@getSocialLogin'); +Route::get('/login/service/{socialService}/callback', 'Auth\AuthController@socialCallback'); + // Password reset link request routes... Route::get('/password/email', 'Auth\PasswordController@getEmail'); Route::post('/password/email', 'Auth\PasswordController@postEmail'); diff --git a/app/Repos/UserRepo.php b/app/Repos/UserRepo.php new file mode 100644 index 000000000..f6c80ff42 --- /dev/null +++ b/app/Repos/UserRepo.php @@ -0,0 +1,24 @@ +user = $user; + } + + + public function getByEmail($email) { + return $this->user->where('email', '=', $email)->first(); + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index 09d0fffd7..ef5379d46 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,8 @@ "php": ">=5.5.9", "laravel/framework": "5.1.*", "intervention/image": "^2.3", - "barryvdh/laravel-ide-helper": "^2.1" + "barryvdh/laravel-ide-helper": "^2.1", + "laravel/socialite": "^2.0" }, "require-dev": { "fzaninotto/faker": "~1.4", diff --git a/composer.lock b/composer.lock index ec5ebc543..e847bcb4a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "16de3a44150d9425a501c9873cb28eaf", + "hash": "7d7e80e9f1c13417c35195f79e41c038", "packages": [ { "name": "barryvdh/laravel-ide-helper", @@ -279,6 +279,214 @@ ], "time": "2014-12-20 21:24:13" }, + { + "name": "guzzle/guzzle", + "version": "v3.9.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2015-03-18 18:23:50" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "a8dfeff00eb84616a17fea7a4d72af35e750410f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/a8dfeff00eb84616a17fea7a4d72af35e750410f", + "reference": "a8dfeff00eb84616a17fea7a4d72af35e750410f", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "~1.0", + "guzzlehttp/psr7": "~1.1", + "php": ">=5.5.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "~4.0", + "psr/log": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2015-07-04 20:09:24" + }, + { + "name": "guzzlehttp/promises", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "97fe7210def29451ec74923b27e552238defd75a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/97fe7210def29451ec74923b27e552238defd75a", + "reference": "97fe7210def29451ec74923b27e552238defd75a", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2015-08-15 19:37:21" + }, { "name": "guzzlehttp/psr7", "version": "1.2.0", @@ -672,6 +880,60 @@ ], "time": "2015-08-12 18:16:08" }, + { + "name": "laravel/socialite", + "version": "v2.0.12", + "source": { + "type": "git", + "url": "https://github.com/laravel/socialite.git", + "reference": "0bb08c8666f4c01e55e3b3b0e42f2b5075be6a6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/socialite/zipball/0bb08c8666f4c01e55e3b3b0e42f2b5075be6a6e", + "reference": "0bb08c8666f4c01e55e3b3b0e42f2b5075be6a6e", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "~5.0|~6.0", + "illuminate/contracts": "~5.0", + "illuminate/http": "~5.0", + "illuminate/support": "~5.0", + "league/oauth1-client": "~1.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Socialite\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.", + "keywords": [ + "laravel", + "oauth" + ], + "time": "2015-08-30 01:12:56" + }, { "name": "league/flysystem", "version": "1.0.11", @@ -753,6 +1015,69 @@ ], "time": "2015-07-28 20:41:58" }, + { + "name": "league/oauth1-client", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth1-client.git", + "reference": "4d4edd9b6014f882e319231a9b3351e3a1dfdc81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/4d4edd9b6014f882e319231a9b3351e3a1dfdc81", + "reference": "4d4edd9b6014f882e319231a9b3351e3a1dfdc81", + "shasum": "" + }, + "require": { + "guzzle/guzzle": "3.*", + "php": ">=5.3.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\OAuth1\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Corlett", + "email": "bencorlett@me.com", + "homepage": "http://www.webcomm.com.au", + "role": "Developer" + } + ], + "description": "OAuth 1.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "bitbucket", + "identity", + "idp", + "oauth", + "oauth1", + "single sign on", + "trello", + "tumblr", + "twitter" + ], + "time": "2015-08-22 09:49:14" + }, { "name": "monolog/monolog", "version": "1.16.0", diff --git a/config/app.php b/config/app.php index 80012a3ed..9780558b9 100644 --- a/config/app.php +++ b/config/app.php @@ -26,7 +26,7 @@ return [ | */ - 'url' => 'http://localhost', + 'url' => env('APP_URL', 'http://localhost'), /* |-------------------------------------------------------------------------- @@ -136,6 +136,7 @@ return [ Illuminate\Translation\TranslationServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, + Laravel\Socialite\SocialiteServiceProvider::class, /** * Third Party @@ -199,6 +200,7 @@ return [ 'URL' => Illuminate\Support\Facades\URL::class, 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, + 'Socialite' => Laravel\Socialite\Facades\Socialite::class, /** * Third Party diff --git a/config/services.php b/config/services.php index 45603c99a..be9d6eb15 100644 --- a/config/services.php +++ b/config/services.php @@ -14,7 +14,7 @@ return [ | */ - 'mailgun' => [ + 'mailgun' => [ 'domain' => '', 'secret' => '', ], @@ -23,16 +23,28 @@ return [ 'secret' => '', ], - 'ses' => [ + 'ses' => [ 'key' => '', 'secret' => '', 'region' => 'us-east-1', ], - 'stripe' => [ + 'stripe' => [ 'model' => Oxbow\User::class, 'key' => '', 'secret' => '', ], + 'github' => [ + 'client_id' => env('GITHUB_APP_ID', false), + 'client_secret' => env('GITHUB_APP_SECRET', false), + 'redirect' => env('APP_URL') . '/login/service/github/callback', + ], + + 'google' => [ + 'client_id' => env('GOOGLE_APP_ID', false), + 'client_secret' => env('GOOGLE_APP_SECRET', false), + 'redirect' => env('APP_URL') . '/login/service/google/callback', + ], + ]; diff --git a/resources/assets/js/global.js b/resources/assets/js/global.js index 41002f4f5..d078946c5 100644 --- a/resources/assets/js/global.js +++ b/resources/assets/js/global.js @@ -3,6 +3,7 @@ $(function () { // Notification hiding $('.notification').click(function () { $(this).fadeOut(100); + }); // Dropdown toggles diff --git a/resources/assets/sass/_animations.scss b/resources/assets/sass/_animations.scss index f2434b482..e6e85ef8e 100644 --- a/resources/assets/sass/_animations.scss +++ b/resources/assets/sass/_animations.scss @@ -42,6 +42,9 @@ animation-duration: 3s; animation-timing-function: ease-in-out; animation-fill-mode: forwards; + &.stopped { + animation-name: notificationStopped; + } } @keyframes notification { @@ -58,6 +61,17 @@ transform: translate3d(580px, 0, 0); } } +@keyframes notificationStopped { + 0% { + transform: translate3d(580px, 0, 0); + } + 10% { + transform: translate3d(0, 0, 0); + } + 100% { + transform: translate3d(0, 0, 0); + } +} @keyframes menuIn { from { diff --git a/resources/assets/sass/_buttons.scss b/resources/assets/sass/_buttons.scss index 373726c00..19c7b84e4 100644 --- a/resources/assets/sass/_buttons.scss +++ b/resources/assets/sass/_buttons.scss @@ -30,7 +30,6 @@ $button-border-radius: 2px; border-radius: $button-border-radius; cursor: pointer; transition: all ease-in-out 120ms; - text-transform: uppercase; box-shadow: 0 0.5px 1.5px 0 rgba(0, 0, 0, 0.21); @include generate-button-colors(#EEE, $primary); } diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 5d738a7ad..2c545f281 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -1,6 +1,6 @@ @extends('public') -@section('sidebar') +@section('content')

Log In

@@ -23,6 +23,16 @@
+ @if(count($socialDrivers) > 0) +
+

Social Login

+ @if(isset($socialDrivers['google'])) + + @endif + @if(isset($socialDrivers['github'])) + + @endif + @endif @stop \ No newline at end of file diff --git a/resources/views/auth/password.blade.php b/resources/views/auth/password.blade.php index 6b9f46ec7..d3ea08e1c 100644 --- a/resources/views/auth/password.blade.php +++ b/resources/views/auth/password.blade.php @@ -2,7 +2,7 @@ @section('body-class', 'image-cover login') -@section('sidebar') +@section('content')
diff --git a/resources/views/auth/reset.blade.php b/resources/views/auth/reset.blade.php index 6762ef2f0..c440f64e7 100644 --- a/resources/views/auth/reset.blade.php +++ b/resources/views/auth/reset.blade.php @@ -2,7 +2,7 @@ @section('body-class', 'image-cover login') -@section('sidebar') +@section('content')
diff --git a/resources/views/base.blade.php b/resources/views/base.blade.php index 76447b5f5..4dc3251ca 100644 --- a/resources/views/base.blade.php +++ b/resources/views/base.blade.php @@ -31,7 +31,7 @@ @endif @if(Session::has('error')) -
+
{{ Session::get('error') }}
@endif diff --git a/resources/views/public.blade.php b/resources/views/public.blade.php index f311aefae..b11971104 100644 --- a/resources/views/public.blade.php +++ b/resources/views/public.blade.php @@ -6,14 +6,25 @@ + + + - +@if(Session::has('success')) +
+ {{ Session::get('success') }} +
+@endif + +@if(Session::has('error')) +
+ {{ Session::get('error') }} +
+@endif
@yield('content') diff --git a/resources/views/users/form.blade.php b/resources/views/users/form.blade.php index 5e55145fd..05c4ac02b 100644 --- a/resources/views/users/form.blade.php +++ b/resources/views/users/form.blade.php @@ -8,6 +8,13 @@ @include('form/text', ['name' => 'email'])
+@if($currentUser->can('user-update')) +
+ + @include('form.role-select', ['name' => 'role', 'options' => \Oxbow\Role::all(), 'displayKey' => 'display_name']) +
+@endif + @if(isset($model))
@@ -16,13 +23,6 @@
@endif -@if($currentUser->can('user-update')) -
- - @include('form.role-select', ['name' => 'role', 'options' => \Oxbow\Role::all(), 'displayKey' => 'display_name']) -
-@endif -
@include('form/password', ['name' => 'password'])