Dan Brown

Got registration process working with social accounts

...@@ -7,9 +7,8 @@ use Oxbow\Exceptions\SocialSignInException; ...@@ -7,9 +7,8 @@ use Oxbow\Exceptions\SocialSignInException;
7 use Oxbow\Exceptions\UserRegistrationException; 7 use Oxbow\Exceptions\UserRegistrationException;
8 use Oxbow\Repos\UserRepo; 8 use Oxbow\Repos\UserRepo;
9 use Oxbow\Services\EmailConfirmationService; 9 use Oxbow\Services\EmailConfirmationService;
10 -use Oxbow\Services\Facades\Setting;
11 use Oxbow\Services\SocialAuthService; 10 use Oxbow\Services\SocialAuthService;
12 -use Oxbow\User; 11 +use Oxbow\SocialAccount;
13 use Validator; 12 use Validator;
14 use Oxbow\Http\Controllers\Controller; 13 use Oxbow\Http\Controllers\Controller;
15 use Illuminate\Foundation\Auth\ThrottlesLogins; 14 use Illuminate\Foundation\Auth\ThrottlesLogins;
...@@ -46,7 +45,7 @@ class AuthController extends Controller ...@@ -46,7 +45,7 @@ class AuthController extends Controller
46 */ 45 */
47 public function __construct(SocialAuthService $socialAuthService, EmailConfirmationService $emailConfirmationService, UserRepo $userRepo) 46 public function __construct(SocialAuthService $socialAuthService, EmailConfirmationService $emailConfirmationService, UserRepo $userRepo)
48 { 47 {
49 - $this->middleware('guest', ['only' => ['getLogin', 'postLogin', 'getRegister']]); 48 + $this->middleware('guest', ['only' => ['getLogin', 'postLogin', 'getRegister', 'postRegister']]);
50 $this->socialAuthService = $socialAuthService; 49 $this->socialAuthService = $socialAuthService;
51 $this->emailConfirmationService = $emailConfirmationService; 50 $this->emailConfirmationService = $emailConfirmationService;
52 $this->userRepo = $userRepo; 51 $this->userRepo = $userRepo;
...@@ -55,7 +54,6 @@ class AuthController extends Controller ...@@ -55,7 +54,6 @@ class AuthController extends Controller
55 54
56 /** 55 /**
57 * Get a validator for an incoming registration request. 56 * Get a validator for an incoming registration request.
58 - *
59 * @param array $data 57 * @param array $data
60 * @return \Illuminate\Contracts\Validation\Validator 58 * @return \Illuminate\Contracts\Validation\Validator
61 */ 59 */
...@@ -68,31 +66,15 @@ class AuthController extends Controller ...@@ -68,31 +66,15 @@ class AuthController extends Controller
68 ]); 66 ]);
69 } 67 }
70 68
71 - /**
72 - * Create a new user instance after a valid registration.
73 - *
74 - * @param array $data
75 - * @return User
76 - */
77 - protected function create(array $data)
78 - {
79 - return User::create([
80 - 'name' => $data['name'],
81 - 'email' => $data['email'],
82 - 'password' => bcrypt($data['password']),
83 - ]);
84 - }
85 -
86 protected function checkRegistrationAllowed() 69 protected function checkRegistrationAllowed()
87 { 70 {
88 - if(!\Setting::get('registration-enabled')) { 71 + if (!\Setting::get('registration-enabled')) {
89 throw new UserRegistrationException('Registrations are currently disabled.', '/login'); 72 throw new UserRegistrationException('Registrations are currently disabled.', '/login');
90 } 73 }
91 } 74 }
92 75
93 /** 76 /**
94 * Show the application registration form. 77 * Show the application registration form.
95 - *
96 * @return \Illuminate\Http\Response 78 * @return \Illuminate\Http\Response
97 */ 79 */
98 public function getRegister() 80 public function getRegister()
...@@ -104,7 +86,6 @@ class AuthController extends Controller ...@@ -104,7 +86,6 @@ class AuthController extends Controller
104 86
105 /** 87 /**
106 * Handle a registration request for the application. 88 * Handle a registration request for the application.
107 - *
108 * @param \Illuminate\Http\Request $request 89 * @param \Illuminate\Http\Request $request
109 * @return \Illuminate\Http\Response 90 * @return \Illuminate\Http\Response
110 * @throws UserRegistrationException 91 * @throws UserRegistrationException
...@@ -120,18 +101,54 @@ class AuthController extends Controller ...@@ -120,18 +101,54 @@ class AuthController extends Controller
120 ); 101 );
121 } 102 }
122 103
123 - if(\Setting::get('registration-restrict')) { 104 + $userData = $request->all();
105 + return $this->registerUser($userData);
106 + }
107 +
108 + /**
109 + * Register a new user after a registration callback.
110 + * @param $socialDriver
111 + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
112 + * @throws UserRegistrationException
113 + */
114 + protected function socialRegisterCallback($socialDriver)
115 + {
116 + $socialUser = $this->socialAuthService->handleRegistrationCallback($socialDriver);
117 + $socialAccount = $this->socialAuthService->fillSocialAccount($socialDriver, $socialUser);
118 +
119 + // Create an array of the user data to create a new user instance
120 + $userData = [
121 + 'name' => $socialUser->getName(),
122 + 'email' => $socialUser->getEmail(),
123 + 'password' => str_random(30)
124 + ];
125 + return $this->registerUser($userData, $socialAccount);
126 + }
127 +
128 + /**
129 + * The registrations flow for all users.
130 + * @param array $userData
131 + * @param bool|false|SocialAccount $socialAccount
132 + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
133 + * @throws UserRegistrationException
134 + * @throws \Oxbow\Exceptions\ConfirmationEmailException
135 + */
136 + protected function registerUser(array $userData, $socialAccount = false)
137 + {
138 + if (\Setting::get('registration-restrict')) {
124 $restrictedEmailDomains = explode(',', str_replace(' ', '', \Setting::get('registration-restrict'))); 139 $restrictedEmailDomains = explode(',', str_replace(' ', '', \Setting::get('registration-restrict')));
125 - $userEmailDomain = $domain = substr(strrchr($request->get('email'), "@"), 1); 140 + $userEmailDomain = $domain = substr(strrchr($userData['email'], "@"), 1);
126 - if(!in_array($userEmailDomain, $restrictedEmailDomains)) { 141 + if (!in_array($userEmailDomain, $restrictedEmailDomains)) {
127 throw new UserRegistrationException('That email domain does not have access to this application', '/register'); 142 throw new UserRegistrationException('That email domain does not have access to this application', '/register');
128 } 143 }
129 } 144 }
130 145
131 - $newUser = $this->create($request->all()); 146 + $newUser = $this->userRepo->registerNew($userData);
132 - $newUser->attachRoleId(\Setting::get('registration-role'), 1); 147 + if ($socialAccount) {
148 + $newUser->socialAccounts()->save($socialAccount);
149 + }
133 150
134 - if(\Setting::get('registration-confirmation') || \Setting::get('registration-restrict')) { 151 + if (\Setting::get('registration-confirmation') || \Setting::get('registration-restrict')) {
135 $newUser->email_confirmed = false; 152 $newUser->email_confirmed = false;
136 $newUser->save(); 153 $newUser->save();
137 $this->emailConfirmationService->sendConfirmation($newUser); 154 $this->emailConfirmationService->sendConfirmation($newUser);
...@@ -139,6 +156,7 @@ class AuthController extends Controller ...@@ -139,6 +156,7 @@ class AuthController extends Controller
139 } 156 }
140 157
141 auth()->login($newUser); 158 auth()->login($newUser);
159 + session()->flash('success', 'Thanks for signing up! You are now registered and signed in.');
142 return redirect($this->redirectPath()); 160 return redirect($this->redirectPath());
143 } 161 }
144 162
...@@ -197,7 +215,6 @@ class AuthController extends Controller ...@@ -197,7 +215,6 @@ class AuthController extends Controller
197 215
198 /** 216 /**
199 * Show the application login form. 217 * Show the application login form.
200 - *
201 * @return \Illuminate\Http\Response 218 * @return \Illuminate\Http\Response
202 */ 219 */
203 public function getLogin() 220 public function getLogin()
...@@ -218,19 +235,41 @@ class AuthController extends Controller ...@@ -218,19 +235,41 @@ class AuthController extends Controller
218 */ 235 */
219 public function getSocialLogin($socialDriver) 236 public function getSocialLogin($socialDriver)
220 { 237 {
238 + session()->put('social-callback', 'login');
221 return $this->socialAuthService->startLogIn($socialDriver); 239 return $this->socialAuthService->startLogIn($socialDriver);
222 } 240 }
223 241
224 /** 242 /**
243 + * Redirect to the social site for authentication initended to register.
244 + * @param $socialDriver
245 + * @return mixed
246 + */
247 + public function socialRegister($socialDriver)
248 + {
249 + $this->checkRegistrationAllowed();
250 + session()->put('social-callback', 'register');
251 + return $this->socialAuthService->startRegister($socialDriver);
252 + }
253 +
254 + /**
225 * The callback for social login services. 255 * The callback for social login services.
226 - *
227 * @param $socialDriver 256 * @param $socialDriver
228 * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector 257 * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
229 * @throws SocialSignInException 258 * @throws SocialSignInException
230 */ 259 */
231 public function socialCallback($socialDriver) 260 public function socialCallback($socialDriver)
232 { 261 {
233 - return $this->socialAuthService->handleCallback($socialDriver); 262 + if (session()->has('social-callback')) {
263 + $action = session()->pull('social-callback');
264 + if ($action == 'login') {
265 + return $this->socialAuthService->handleLoginCallback($socialDriver);
266 + } elseif ($action == 'register') {
267 + return $this->socialRegisterCallback($socialDriver);
268 + }
269 + } else {
270 + throw new SocialSignInException('No action defined', '/login');
271 + }
272 + return redirect()->back();
234 } 273 }
235 274
236 /** 275 /**
......
...@@ -22,7 +22,6 @@ class SettingController extends Controller ...@@ -22,7 +22,6 @@ class SettingController extends Controller
22 } 22 }
23 23
24 24
25 -
26 /** 25 /**
27 * Update the specified settings in storage. 26 * Update the specified settings in storage.
28 * 27 *
......
...@@ -6,6 +6,7 @@ use Illuminate\Http\Request; ...@@ -6,6 +6,7 @@ use Illuminate\Http\Request;
6 6
7 use Illuminate\Support\Facades\Hash; 7 use Illuminate\Support\Facades\Hash;
8 use Oxbow\Http\Requests; 8 use Oxbow\Http\Requests;
9 +use Oxbow\Repos\UserRepo;
9 use Oxbow\Services\SocialAuthService; 10 use Oxbow\Services\SocialAuthService;
10 use Oxbow\User; 11 use Oxbow\User;
11 12
...@@ -13,14 +14,16 @@ class UserController extends Controller ...@@ -13,14 +14,16 @@ class UserController extends Controller
13 { 14 {
14 15
15 protected $user; 16 protected $user;
17 + protected $userRepo;
16 18
17 /** 19 /**
18 * UserController constructor. 20 * UserController constructor.
19 * @param $user 21 * @param $user
20 */ 22 */
21 - public function __construct(User $user) 23 + public function __construct(User $user, UserRepo $userRepo)
22 { 24 {
23 $this->user = $user; 25 $this->user = $user;
26 + $this->userRepo = $userRepo;
24 parent::__construct(); 27 parent::__construct();
25 } 28 }
26 29
...@@ -150,8 +153,12 @@ class UserController extends Controller ...@@ -150,8 +153,12 @@ class UserController extends Controller
150 $this->checkPermissionOr('user-delete', function () use ($id) { 153 $this->checkPermissionOr('user-delete', function () use ($id) {
151 return $this->currentUser->id == $id; 154 return $this->currentUser->id == $id;
152 }); 155 });
153 - $user = $this->user->findOrFail($id); 156 + $user = $this->userRepo->getById($id);
154 // Delete social accounts 157 // Delete social accounts
158 + if($this->userRepo->isOnlyAdmin($user)) {
159 + session()->flash('error', 'You cannot delete the only admin');
160 + return redirect($user->getEditUrl());
161 + }
155 $user->socialAccounts()->delete(); 162 $user->socialAccounts()->delete();
156 $user->delete(); 163 $user->delete();
157 return redirect('/users'); 164 return redirect('/users');
......
...@@ -92,6 +92,7 @@ Route::get('/register/confirm', 'Auth\AuthController@getRegisterConfirmation'); ...@@ -92,6 +92,7 @@ Route::get('/register/confirm', 'Auth\AuthController@getRegisterConfirmation');
92 Route::get('/register/confirm/awaiting', 'Auth\AuthController@showAwaitingConfirmation'); 92 Route::get('/register/confirm/awaiting', 'Auth\AuthController@showAwaitingConfirmation');
93 Route::post('/register/confirm/resend', 'Auth\AuthController@resendConfirmation'); 93 Route::post('/register/confirm/resend', 'Auth\AuthController@resendConfirmation');
94 Route::get('/register/confirm/{token}', 'Auth\AuthController@confirmEmail'); 94 Route::get('/register/confirm/{token}', 'Auth\AuthController@confirmEmail');
95 +Route::get('/register/service/{socialDriver}', 'Auth\AuthController@socialRegister');
95 Route::post('/register', 'Auth\AuthController@postRegister'); 96 Route::post('/register', 'Auth\AuthController@postRegister');
96 97
97 // Password reset link request routes... 98 // Password reset link request routes...
......
1 +<?php namespace Oxbow\Providers;
2 +
3 +
4 +use Illuminate\Support\ServiceProvider;
5 +
6 +class SocialiteServiceProvider extends ServiceProvider
7 +{
8 + /**
9 + * Indicates if loading of the provider is deferred.
10 + *
11 + * @var bool
12 + */
13 + protected $defer = true;
14 +
15 + /**
16 + * Register the service provider.
17 + *
18 + * @return void
19 + */
20 + public function register()
21 + {
22 + $this->app->bindShared('Laravel\Socialite\Contracts\Factory', function ($app) {
23 + return new SocialiteManager($app);
24 + });
25 + }
26 +
27 + /**
28 + * Get the services provided by the provider.
29 + *
30 + * @return array
31 + */
32 + public function provides()
33 + {
34 + return ['Laravel\Socialite\Contracts\Factory'];
35 + }
36 +}
...\ No newline at end of file ...\ No newline at end of file
1 <?php namespace Oxbow\Repos; 1 <?php namespace Oxbow\Repos;
2 2
3 3
4 +use Oxbow\Role;
4 use Oxbow\User; 5 use Oxbow\User;
5 6
6 class UserRepo 7 class UserRepo
7 { 8 {
8 9
9 protected $user; 10 protected $user;
11 + protected $role;
10 12
11 /** 13 /**
12 * UserRepo constructor. 14 * UserRepo constructor.
13 * @param $user 15 * @param $user
14 */ 16 */
15 - public function __construct(User $user) 17 + public function __construct(User $user, Role $role)
16 { 18 {
17 $this->user = $user; 19 $this->user = $user;
20 + $this->role = $role;
18 } 21 }
19 22
20 - 23 + /**
21 - public function getByEmail($email) { 24 + * @param string $email
25 + * @return User|null
26 + */
27 + public function getByEmail($email)
28 + {
22 return $this->user->where('email', '=', $email)->first(); 29 return $this->user->where('email', '=', $email)->first();
23 } 30 }
24 31
32 + /**
33 + * @param int $id
34 + * @return User
35 + */
25 public function getById($id) 36 public function getById($id)
26 { 37 {
27 return $this->user->findOrFail($id); 38 return $this->user->findOrFail($id);
28 } 39 }
40 +
41 + /**
42 + * Creates a new user and attaches a role to them.
43 + * @param array $data
44 + * @return User
45 + */
46 + public function registerNew(array $data)
47 + {
48 + $user = $this->create($data);
49 + $roleId = \Setting::get('registration-role');
50 +
51 + if ($roleId === false) {
52 + $roleId = $this->role->getDefault()->id;
53 + }
54 +
55 + $user->attachRoleId($roleId);
56 + return $user;
57 + }
58 +
59 + /**
60 + * Checks if the give user is the only admin.
61 + * @param User $user
62 + * @return bool
63 + */
64 + public function isOnlyAdmin(User $user)
65 + {
66 + if ($user->role->name != 'admin') {
67 + return false;
68 + }
69 +
70 + $adminRole = $this->role->where('name', '=', 'admin')->first();
71 + if (count($adminRole->users) > 1) {
72 + return false;
73 + }
74 +
75 + return true;
76 + }
77 +
78 + /**
79 + * Create a new basic instance of user.
80 + * @param array $data
81 + * @return User
82 + */
83 + public function create(array $data)
84 + {
85 + return $this->user->create([
86 + 'name' => $data['name'],
87 + 'email' => $data['email'],
88 + 'password' => bcrypt($data['password'])
89 + ]);
90 + }
29 } 91 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -35,7 +35,7 @@ class EmailConfirmationService ...@@ -35,7 +35,7 @@ class EmailConfirmationService
35 */ 35 */
36 public function sendConfirmation(User $user) 36 public function sendConfirmation(User $user)
37 { 37 {
38 - if($user->email_confirmed) { 38 + if ($user->email_confirmed) {
39 throw new ConfirmationEmailException('Email has already been confirmed, Try logging in.', '/login'); 39 throw new ConfirmationEmailException('Email has already been confirmed, Try logging in.', '/login');
40 } 40 }
41 $this->deleteConfirmationsByUser($user); 41 $this->deleteConfirmationsByUser($user);
...@@ -66,7 +66,7 @@ class EmailConfirmationService ...@@ -66,7 +66,7 @@ class EmailConfirmationService
66 } 66 }
67 67
68 // If more than a day old 68 // If more than a day old
69 - if(Carbon::now()->subDay()->gt($emailConfirmation->created_at)) { 69 + if (Carbon::now()->subDay()->gt($emailConfirmation->created_at)) {
70 $this->sendConfirmation($emailConfirmation->user); 70 $this->sendConfirmation($emailConfirmation->user);
71 throw new UserRegistrationException('The confirmation token has expired, A new confirmation email has been sent.', '/register/confirm'); 71 throw new UserRegistrationException('The confirmation token has expired, A new confirmation email has been sent.', '/register/confirm');
72 } 72 }
......
1 <?php namespace Oxbow\Services; 1 <?php namespace Oxbow\Services;
2 2
3 +use GuzzleHttp\Exception\ClientException;
3 use Laravel\Socialite\Contracts\Factory as Socialite; 4 use Laravel\Socialite\Contracts\Factory as Socialite;
4 use Oxbow\Exceptions\SocialDriverNotConfigured; 5 use Oxbow\Exceptions\SocialDriverNotConfigured;
5 use Oxbow\Exceptions\SocialSignInException; 6 use Oxbow\Exceptions\SocialSignInException;
7 +use Oxbow\Exceptions\UserRegistrationException;
8 +use Oxbow\Http\Controllers\Auth\AuthController;
6 use Oxbow\Repos\UserRepo; 9 use Oxbow\Repos\UserRepo;
7 use Oxbow\SocialAccount; 10 use Oxbow\SocialAccount;
8 use Oxbow\User; 11 use Oxbow\User;
...@@ -29,9 +32,10 @@ class SocialAuthService ...@@ -29,9 +32,10 @@ class SocialAuthService
29 $this->socialAccount = $socialAccount; 32 $this->socialAccount = $socialAccount;
30 } 33 }
31 34
35 +
32 /** 36 /**
33 * Start the social login path. 37 * Start the social login path.
34 - * @param $socialDriver 38 + * @param string $socialDriver
35 * @return \Symfony\Component\HttpFoundation\RedirectResponse 39 * @return \Symfony\Component\HttpFoundation\RedirectResponse
36 * @throws SocialDriverNotConfigured 40 * @throws SocialDriverNotConfigured
37 */ 41 */
...@@ -42,14 +46,52 @@ class SocialAuthService ...@@ -42,14 +46,52 @@ class SocialAuthService
42 } 46 }
43 47
44 /** 48 /**
45 - * Get a user from socialite after a oAuth callback. 49 + * Start the social registration process
46 - * 50 + * @param string $socialDriver
51 + * @return \Symfony\Component\HttpFoundation\RedirectResponse
52 + * @throws SocialDriverNotConfigured
53 + */
54 + public function startRegister($socialDriver)
55 + {
56 + $driver = $this->validateDriver($socialDriver);
57 + return $this->socialite->driver($driver)->redirect();
58 + }
59 +
60 + /**
61 + * Handle the social registration process on callback.
47 * @param $socialDriver 62 * @param $socialDriver
48 - * @return User 63 + * @return \Laravel\Socialite\Contracts\User
64 + * @throws SocialDriverNotConfigured
65 + * @throws UserRegistrationException
66 + */
67 + public function handleRegistrationCallback($socialDriver)
68 + {
69 + $driver = $this->validateDriver($socialDriver);
70 +
71 + // Get user details from social driver
72 + $socialUser = $this->socialite->driver($driver)->user();
73 +
74 + // Check social account has not already been used
75 + if ($this->socialAccount->where('driver_id', '=', $socialUser->getId())->exists()) {
76 + throw new UserRegistrationException('This ' . $socialDriver . ' account is already in use, Try logging in via the ' . $socialDriver . ' option.', '/login');
77 + }
78 +
79 + if($this->userRepo->getByEmail($socialUser->getEmail())) {
80 + $email = $socialUser->getEmail();
81 + throw new UserRegistrationException('The email '. $email.' is already in use. If you already have an account you can connect your ' . $socialDriver .' account from your profile settings.', '/login');
82 + }
83 +
84 + return $socialUser;
85 + }
86 +
87 + /**
88 + * Handle the login process on a oAuth callback.
89 + * @param $socialDriver
90 + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
49 * @throws SocialDriverNotConfigured 91 * @throws SocialDriverNotConfigured
50 * @throws SocialSignInException 92 * @throws SocialSignInException
51 */ 93 */
52 - public function handleCallback($socialDriver) 94 + public function handleLoginCallback($socialDriver)
53 { 95 {
54 $driver = $this->validateDriver($socialDriver); 96 $driver = $this->validateDriver($socialDriver);
55 97
...@@ -93,12 +135,13 @@ class SocialAuthService ...@@ -93,12 +135,13 @@ class SocialAuthService
93 135
94 // Otherwise let the user know this social account is not used by anyone. 136 // Otherwise let the user know this social account is not used by anyone.
95 $message = 'This ' . $socialDriver . ' account is not linked to any users. Please attach it in your profile settings'; 137 $message = 'This ' . $socialDriver . ' account is not linked to any users. Please attach it in your profile settings';
96 - if(\Setting::get('registration-enabled')) { 138 + if (\Setting::get('registration-enabled')) {
97 - $message .= 'or, If you do not yet have an account, You can register an account using the ' . $socialDriver . ' option'; 139 + $message .= ' or, If you do not yet have an account, You can register an account using the ' . $socialDriver . ' option';
98 } 140 }
99 throw new SocialSignInException($message . '.', '/login'); 141 throw new SocialSignInException($message . '.', '/login');
100 } 142 }
101 143
144 +
102 private function logUserIn($user) 145 private function logUserIn($user)
103 { 146 {
104 auth()->login($user); 147 auth()->login($user);
...@@ -150,16 +193,18 @@ class SocialAuthService ...@@ -150,16 +193,18 @@ class SocialAuthService
150 } 193 }
151 194
152 /** 195 /**
153 - * @param $socialDriver 196 + * @param string $socialDriver
154 - * @param $socialUser 197 + * @param \Laravel\Socialite\Contracts\User $socialUser
198 + * @return SocialAccount
155 */ 199 */
156 - private function fillSocialAccount($socialDriver, $socialUser) 200 + public function fillSocialAccount($socialDriver, $socialUser)
157 { 201 {
158 $this->socialAccount->fill([ 202 $this->socialAccount->fill([
159 'driver' => $socialDriver, 203 'driver' => $socialDriver,
160 'driver_id' => $socialUser->getId(), 204 'driver_id' => $socialUser->getId(),
161 'avatar' => $socialUser->getAvatar() 205 'avatar' => $socialUser->getAvatar()
162 ]); 206 ]);
207 + return $this->socialAccount;
163 } 208 }
164 209
165 /** 210 /**
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
25 25
26 <div class="form-group"> 26 <div class="form-group">
27 <label for="password">Password</label> 27 <label for="password">Password</label>
28 - @include('form/password', ['name' => 'password']) 28 + @include('form/password', ['name' => 'password', 'placeholder' => 'Must be over 5 characters'])
29 </div> 29 </div>
30 30
31 <div class="from-group"> 31 <div class="from-group">
......