Dan Brown

Added social sign in

...@@ -22,8 +22,7 @@ class Handler extends ExceptionHandler ...@@ -22,8 +22,7 @@ class Handler extends ExceptionHandler
22 * 22 *
23 * This is a great spot to send exceptions to Sentry, Bugsnag, etc. 23 * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
24 * 24 *
25 - * @param \Exception $e 25 + * @param \Exception $e
26 - * @return void
27 */ 26 */
28 public function report(Exception $e) 27 public function report(Exception $e)
29 { 28 {
...@@ -39,6 +38,11 @@ class Handler extends ExceptionHandler ...@@ -39,6 +38,11 @@ class Handler extends ExceptionHandler
39 */ 38 */
40 public function render($request, Exception $e) 39 public function render($request, Exception $e)
41 { 40 {
41 + if($e instanceof NotifyException) {
42 + \Session::flash('error', $e->message);
43 + return response()->redirectTo($e->redirectLocation);
44 + }
45 +
42 return parent::render($request, $e); 46 return parent::render($request, $e);
43 } 47 }
44 } 48 }
......
1 +<?php namespace Oxbow\Exceptions;
2 +
3 +
4 +class NotifyException extends \Exception
5 +{
6 +
7 + public $message;
8 + public $redirectLocation;
9 +
10 + /**
11 + * NotifyException constructor.
12 + * @param string $message
13 + * @param string $redirectLocation
14 + */
15 + public function __construct($message, $redirectLocation)
16 + {
17 + $this->message = $message;
18 + $this->redirectLocation = $redirectLocation;
19 + parent::__construct();
20 + }
21 +}
...\ No newline at end of file ...\ No newline at end of file
1 +<?php namespace Oxbow\Exceptions;
2 +
3 +
4 +class SocialDriverNotConfigured extends \Exception
5 +{
6 +}
...\ No newline at end of file ...\ No newline at end of file
1 +<?php namespace Oxbow\Exceptions;
2 +
3 +
4 +class UserNotFound extends NotifyException
5 +{
6 +
7 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -2,11 +2,15 @@ ...@@ -2,11 +2,15 @@
2 2
3 namespace Oxbow\Http\Controllers\Auth; 3 namespace Oxbow\Http\Controllers\Auth;
4 4
5 +use Oxbow\Exceptions\SocialDriverNotConfigured;
6 +use Oxbow\Exceptions\UserNotFound;
7 +use Oxbow\Repos\UserRepo;
5 use Oxbow\User; 8 use Oxbow\User;
6 use Validator; 9 use Validator;
7 use Oxbow\Http\Controllers\Controller; 10 use Oxbow\Http\Controllers\Controller;
8 use Illuminate\Foundation\Auth\ThrottlesLogins; 11 use Illuminate\Foundation\Auth\ThrottlesLogins;
9 use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers; 12 use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
13 +use Laravel\Socialite\Contracts\Factory as Socialite;
10 14
11 class AuthController extends Controller 15 class AuthController extends Controller
12 { 16 {
...@@ -27,28 +31,34 @@ class AuthController extends Controller ...@@ -27,28 +31,34 @@ class AuthController extends Controller
27 protected $redirectPath = '/'; 31 protected $redirectPath = '/';
28 protected $redirectAfterLogout = '/login'; 32 protected $redirectAfterLogout = '/login';
29 33
34 + protected $validSocialDrivers = ['google', 'github'];
35 +
36 + protected $socialite;
37 + protected $userRepo;
30 38
31 /** 39 /**
32 * Create a new authentication controller instance. 40 * Create a new authentication controller instance.
33 - * 41 + * @param Socialite $socialite
34 - * @return void 42 + * @param UserRepo $userRepo
35 */ 43 */
36 - public function __construct() 44 + public function __construct(Socialite $socialite, UserRepo $userRepo)
37 { 45 {
38 $this->middleware('guest', ['except' => 'getLogout']); 46 $this->middleware('guest', ['except' => 'getLogout']);
47 + $this->socialite = $socialite;
48 + $this->userRepo = $userRepo;
39 } 49 }
40 50
41 /** 51 /**
42 * Get a validator for an incoming registration request. 52 * Get a validator for an incoming registration request.
43 * 53 *
44 - * @param array $data 54 + * @param array $data
45 * @return \Illuminate\Contracts\Validation\Validator 55 * @return \Illuminate\Contracts\Validation\Validator
46 */ 56 */
47 protected function validator(array $data) 57 protected function validator(array $data)
48 { 58 {
49 return Validator::make($data, [ 59 return Validator::make($data, [
50 - 'name' => 'required|max:255', 60 + 'name' => 'required|max:255',
51 - 'email' => 'required|email|max:255|unique:users', 61 + 'email' => 'required|email|max:255|unique:users',
52 'password' => 'required|confirmed|min:6', 62 'password' => 'required|confirmed|min:6',
53 ]); 63 ]);
54 } 64 }
...@@ -56,15 +66,110 @@ class AuthController extends Controller ...@@ -56,15 +66,110 @@ class AuthController extends Controller
56 /** 66 /**
57 * Create a new user instance after a valid registration. 67 * Create a new user instance after a valid registration.
58 * 68 *
59 - * @param array $data 69 + * @param array $data
60 * @return User 70 * @return User
61 */ 71 */
62 protected function create(array $data) 72 protected function create(array $data)
63 { 73 {
64 return User::create([ 74 return User::create([
65 - 'name' => $data['name'], 75 + 'name' => $data['name'],
66 - 'email' => $data['email'], 76 + 'email' => $data['email'],
67 'password' => bcrypt($data['password']), 77 'password' => bcrypt($data['password']),
68 ]); 78 ]);
69 } 79 }
80 +
81 + /**
82 + * Show the application login form.
83 + *
84 + * @return \Illuminate\Http\Response
85 + */
86 + public function getLogin()
87 + {
88 +
89 + if (view()->exists('auth.authenticate')) {
90 + return view('auth.authenticate');
91 + }
92 +
93 + $socialDrivers = $this->getActiveSocialDrivers();
94 +
95 + return view('auth.login', ['socialDrivers' => $socialDrivers]);
96 + }
97 +
98 + /**
99 + * Redirect to the relevant social site.
100 + * @param $socialDriver
101 + * @return \Symfony\Component\HttpFoundation\RedirectResponse
102 + */
103 + public function getSocialLogin($socialDriver)
104 + {
105 + $driver = $this->validateSocialDriver($socialDriver);
106 + return $this->socialite->driver($driver)->redirect();
107 + }
108 +
109 + /**
110 + * The callback for social login services.
111 + *
112 + * @param $socialDriver
113 + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
114 + * @throws UserNotFound
115 + */
116 + public function socialCallback($socialDriver)
117 + {
118 + $driver = $this->validateSocialDriver($socialDriver);
119 + // Get user details from social driver
120 + $socialUser = $this->socialite->driver($driver)->user();
121 + $user = $this->userRepo->getByEmail($socialUser->getEmail());
122 +
123 + // Redirect if the email is not a current user.
124 + if ($user === null) {
125 + throw new UserNotFound('A user with the email ' . $socialUser->getEmail() . ' was not found.', '/login');
126 + }
127 +
128 + \Auth::login($user, true);
129 + return redirect($this->redirectPath);
130 + }
131 +
132 + /**
133 + * Ensure the social driver is correct and supported.
134 + *
135 + * @param $socialDriver
136 + * @return string
137 + * @throws SocialDriverNotConfigured
138 + */
139 + protected function validateSocialDriver($socialDriver)
140 + {
141 + $driver = trim(strtolower($socialDriver));
142 +
143 + if (!in_array($driver, $this->validSocialDrivers)) abort(404, 'Social Driver Not Found');
144 + if(!$this->checkSocialDriverConfigured($driver)) throw new SocialDriverNotConfigured;
145 +
146 + return $driver;
147 + }
148 +
149 + /**
150 + * Check a social driver has been configured correctly.
151 + * @param $driver
152 + * @return bool
153 + */
154 + protected function checkSocialDriverConfigured($driver)
155 + {
156 + $upperName = strtoupper($driver);
157 + $config = [env($upperName . '_APP_ID', false), env($upperName . '_APP_SECRET', false), env('APP_URL', false)];
158 + return (!in_array(false, $config) && !in_array(null, $config));
159 + }
160 +
161 + /**
162 + * Gets the names of the active social drivers.
163 + * @return array
164 + */
165 + protected function getActiveSocialDrivers()
166 + {
167 + $activeDrivers = [];
168 + foreach($this->validSocialDrivers as $driverName) {
169 + if($this->checkSocialDriverConfigured($driverName)) {
170 + $activeDrivers[$driverName] = true;
171 + }
172 + }
173 + return $activeDrivers;
174 + }
70 } 175 }
......
...@@ -82,6 +82,10 @@ Route::group(['middleware' => 'auth'], function () { ...@@ -82,6 +82,10 @@ Route::group(['middleware' => 'auth'], function () {
82 Route::get('/login', 'Auth\AuthController@getLogin'); 82 Route::get('/login', 'Auth\AuthController@getLogin');
83 Route::post('/login', 'Auth\AuthController@postLogin'); 83 Route::post('/login', 'Auth\AuthController@postLogin');
84 Route::get('/logout', 'Auth\AuthController@getLogout'); 84 Route::get('/logout', 'Auth\AuthController@getLogout');
85 +// Login using social authentication
86 +Route::get('/login/service/{socialService}', 'Auth\AuthController@getSocialLogin');
87 +Route::get('/login/service/{socialService}/callback', 'Auth\AuthController@socialCallback');
88 +
85 // Password reset link request routes... 89 // Password reset link request routes...
86 Route::get('/password/email', 'Auth\PasswordController@getEmail'); 90 Route::get('/password/email', 'Auth\PasswordController@getEmail');
87 Route::post('/password/email', 'Auth\PasswordController@postEmail'); 91 Route::post('/password/email', 'Auth\PasswordController@postEmail');
......
1 +<?php namespace Oxbow\Repos;
2 +
3 +
4 +use Oxbow\User;
5 +
6 +class UserRepo
7 +{
8 +
9 + protected $user;
10 +
11 + /**
12 + * UserRepo constructor.
13 + * @param $user
14 + */
15 + public function __construct(User $user)
16 + {
17 + $this->user = $user;
18 + }
19 +
20 +
21 + public function getByEmail($email) {
22 + return $this->user->where('email', '=', $email)->first();
23 + }
24 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
8 "php": ">=5.5.9", 8 "php": ">=5.5.9",
9 "laravel/framework": "5.1.*", 9 "laravel/framework": "5.1.*",
10 "intervention/image": "^2.3", 10 "intervention/image": "^2.3",
11 - "barryvdh/laravel-ide-helper": "^2.1" 11 + "barryvdh/laravel-ide-helper": "^2.1",
12 + "laravel/socialite": "^2.0"
12 }, 13 },
13 "require-dev": { 14 "require-dev": {
14 "fzaninotto/faker": "~1.4", 15 "fzaninotto/faker": "~1.4",
......
...@@ -26,7 +26,7 @@ return [ ...@@ -26,7 +26,7 @@ return [
26 | 26 |
27 */ 27 */
28 28
29 - 'url' => 'http://localhost', 29 + 'url' => env('APP_URL', 'http://localhost'),
30 30
31 /* 31 /*
32 |-------------------------------------------------------------------------- 32 |--------------------------------------------------------------------------
...@@ -136,6 +136,7 @@ return [ ...@@ -136,6 +136,7 @@ return [
136 Illuminate\Translation\TranslationServiceProvider::class, 136 Illuminate\Translation\TranslationServiceProvider::class,
137 Illuminate\Validation\ValidationServiceProvider::class, 137 Illuminate\Validation\ValidationServiceProvider::class,
138 Illuminate\View\ViewServiceProvider::class, 138 Illuminate\View\ViewServiceProvider::class,
139 + Laravel\Socialite\SocialiteServiceProvider::class,
139 140
140 /** 141 /**
141 * Third Party 142 * Third Party
...@@ -199,6 +200,7 @@ return [ ...@@ -199,6 +200,7 @@ return [
199 'URL' => Illuminate\Support\Facades\URL::class, 200 'URL' => Illuminate\Support\Facades\URL::class,
200 'Validator' => Illuminate\Support\Facades\Validator::class, 201 'Validator' => Illuminate\Support\Facades\Validator::class,
201 'View' => Illuminate\Support\Facades\View::class, 202 'View' => Illuminate\Support\Facades\View::class,
203 + 'Socialite' => Laravel\Socialite\Facades\Socialite::class,
202 204
203 /** 205 /**
204 * Third Party 206 * Third Party
......
...@@ -14,7 +14,7 @@ return [ ...@@ -14,7 +14,7 @@ return [
14 | 14 |
15 */ 15 */
16 16
17 - 'mailgun' => [ 17 + 'mailgun' => [
18 'domain' => '', 18 'domain' => '',
19 'secret' => '', 19 'secret' => '',
20 ], 20 ],
...@@ -23,16 +23,28 @@ return [ ...@@ -23,16 +23,28 @@ return [
23 'secret' => '', 23 'secret' => '',
24 ], 24 ],
25 25
26 - 'ses' => [ 26 + 'ses' => [
27 'key' => '', 27 'key' => '',
28 'secret' => '', 28 'secret' => '',
29 'region' => 'us-east-1', 29 'region' => 'us-east-1',
30 ], 30 ],
31 31
32 - 'stripe' => [ 32 + 'stripe' => [
33 'model' => Oxbow\User::class, 33 'model' => Oxbow\User::class,
34 'key' => '', 34 'key' => '',
35 'secret' => '', 35 'secret' => '',
36 ], 36 ],
37 37
38 + 'github' => [
39 + 'client_id' => env('GITHUB_APP_ID', false),
40 + 'client_secret' => env('GITHUB_APP_SECRET', false),
41 + 'redirect' => env('APP_URL') . '/login/service/github/callback',
42 + ],
43 +
44 + 'google' => [
45 + 'client_id' => env('GOOGLE_APP_ID', false),
46 + 'client_secret' => env('GOOGLE_APP_SECRET', false),
47 + 'redirect' => env('APP_URL') . '/login/service/google/callback',
48 + ],
49 +
38 ]; 50 ];
......
...@@ -3,6 +3,7 @@ $(function () { ...@@ -3,6 +3,7 @@ $(function () {
3 // Notification hiding 3 // Notification hiding
4 $('.notification').click(function () { 4 $('.notification').click(function () {
5 $(this).fadeOut(100); 5 $(this).fadeOut(100);
6 +
6 }); 7 });
7 8
8 // Dropdown toggles 9 // Dropdown toggles
......
...@@ -42,6 +42,9 @@ ...@@ -42,6 +42,9 @@
42 animation-duration: 3s; 42 animation-duration: 3s;
43 animation-timing-function: ease-in-out; 43 animation-timing-function: ease-in-out;
44 animation-fill-mode: forwards; 44 animation-fill-mode: forwards;
45 + &.stopped {
46 + animation-name: notificationStopped;
47 + }
45 } 48 }
46 49
47 @keyframes notification { 50 @keyframes notification {
...@@ -58,6 +61,17 @@ ...@@ -58,6 +61,17 @@
58 transform: translate3d(580px, 0, 0); 61 transform: translate3d(580px, 0, 0);
59 } 62 }
60 } 63 }
64 +@keyframes notificationStopped {
65 + 0% {
66 + transform: translate3d(580px, 0, 0);
67 + }
68 + 10% {
69 + transform: translate3d(0, 0, 0);
70 + }
71 + 100% {
72 + transform: translate3d(0, 0, 0);
73 + }
74 +}
61 75
62 @keyframes menuIn { 76 @keyframes menuIn {
63 from { 77 from {
......
...@@ -30,7 +30,6 @@ $button-border-radius: 2px; ...@@ -30,7 +30,6 @@ $button-border-radius: 2px;
30 border-radius: $button-border-radius; 30 border-radius: $button-border-radius;
31 cursor: pointer; 31 cursor: pointer;
32 transition: all ease-in-out 120ms; 32 transition: all ease-in-out 120ms;
33 - text-transform: uppercase;
34 box-shadow: 0 0.5px 1.5px 0 rgba(0, 0, 0, 0.21); 33 box-shadow: 0 0.5px 1.5px 0 rgba(0, 0, 0, 0.21);
35 @include generate-button-colors(#EEE, $primary); 34 @include generate-button-colors(#EEE, $primary);
36 } 35 }
......
1 @extends('public') 1 @extends('public')
2 2
3 -@section('sidebar') 3 +@section('content')
4 4
5 <div class="center-box"> 5 <div class="center-box">
6 <h1>Log In</h1> 6 <h1>Log In</h1>
...@@ -23,6 +23,16 @@ ...@@ -23,6 +23,16 @@
23 <button class="button block pos">Sign In</button> 23 <button class="button block pos">Sign In</button>
24 </div> 24 </div>
25 </form> 25 </form>
26 + @if(count($socialDrivers) > 0)
27 + <hr class="margin-top">
28 + <h3 class="text-muted">Social Login</h3>
29 + @if(isset($socialDrivers['google']))
30 + <a href="/login/service/google" style="color: #DC4E41;"><i class="zmdi zmdi-google-plus-box zmdi-hc-4x"></i></a>
31 + @endif
32 + @if(isset($socialDrivers['github']))
33 + <a href="/login/service/github" style="color:#000;"><i class="zmdi zmdi-github zmdi-hc-4x"></i></a>
34 + @endif
35 + @endif
26 </div> 36 </div>
27 37
28 @stop 38 @stop
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 2
3 @section('body-class', 'image-cover login') 3 @section('body-class', 'image-cover login')
4 4
5 -@section('sidebar') 5 +@section('content')
6 6
7 7
8 <div class="text-center"> 8 <div class="text-center">
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 2
3 @section('body-class', 'image-cover login') 3 @section('body-class', 'image-cover login')
4 4
5 -@section('sidebar') 5 +@section('content')
6 6
7 7
8 <div class="text-center"> 8 <div class="text-center">
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
31 @endif 31 @endif
32 32
33 @if(Session::has('error')) 33 @if(Session::has('error'))
34 - <div class="notification anim neg"> 34 + <div class="notification anim neg stopped">
35 <i class="zmdi zmdi-alert-circle"></i> <span>{{ Session::get('error') }}</span> 35 <i class="zmdi zmdi-alert-circle"></i> <span>{{ Session::get('error') }}</span>
36 </div> 36 </div>
37 @endif 37 @endif
......
...@@ -6,14 +6,25 @@ ...@@ -6,14 +6,25 @@
6 <link rel="stylesheet" href="/css/app.css"> 6 <link rel="stylesheet" href="/css/app.css">
7 <link href='//fonts.googleapis.com/css?family=Roboto:400,400italic,500,500italic,700,700italic,300italic,100,300' rel='stylesheet' type='text/css'> 7 <link href='//fonts.googleapis.com/css?family=Roboto:400,400italic,500,500italic,700,700italic,300italic,100,300' rel='stylesheet' type='text/css'>
8 <link rel="stylesheet" href="/bower/material-design-iconic-font/dist/css/material-design-iconic-font.min.css"> 8 <link rel="stylesheet" href="/bower/material-design-iconic-font/dist/css/material-design-iconic-font.min.css">
9 +
10 + <!-- Scripts -->
9 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script> 11 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
12 + <script src="/js/common.js"></script>
10 13
11 </head> 14 </head>
12 <body class="@yield('body-class')"> 15 <body class="@yield('body-class')">
13 16
14 -<section id="sidebar"> 17 +@if(Session::has('success'))
15 - @yield('sidebar') 18 + <div class="notification anim pos">
16 -</section> 19 + <i class="zmdi zmdi-mood"></i> <span>{{ Session::get('success') }}</span>
20 + </div>
21 +@endif
22 +
23 +@if(Session::has('error'))
24 + <div class="notification anim neg stopped">
25 + <i class="zmdi zmdi-alert-circle"></i> <span>{{ Session::get('error') }}</span>
26 + </div>
27 +@endif
17 28
18 <section class="container"> 29 <section class="container">
19 @yield('content') 30 @yield('content')
......
...@@ -8,18 +8,18 @@ ...@@ -8,18 +8,18 @@
8 @include('form/text', ['name' => 'email']) 8 @include('form/text', ['name' => 'email'])
9 </div> 9 </div>
10 10
11 -@if(isset($model)) 11 +@if($currentUser->can('user-update'))
12 <div class="form-group"> 12 <div class="form-group">
13 - <span class="text-muted"> 13 + <label for="role">User Role</label>
14 - Only fill the below if you would like <br>to change your password: 14 + @include('form.role-select', ['name' => 'role', 'options' => \Oxbow\Role::all(), 'displayKey' => 'display_name'])
15 - </span>
16 </div> 15 </div>
17 @endif 16 @endif
18 17
19 -@if($currentUser->can('user-update')) 18 +@if(isset($model))
20 <div class="form-group"> 19 <div class="form-group">
21 - <label for="role">User Role</label> 20 + <span class="text-muted">
22 - @include('form.role-select', ['name' => 'role', 'options' => \Oxbow\Role::all(), 'displayKey' => 'display_name']) 21 + Only fill the below if you would like <br>to change your password:
22 + </span>
23 </div> 23 </div>
24 @endif 24 @endif
25 25
......