Added social sign in

This commit is contained in:
Dan Brown 2015-09-04 17:16:58 +01:00
parent 48814b8984
commit 2dcc5105ad
20 changed files with 578 additions and 32 deletions

View File

@ -23,7 +23,6 @@ class Handler extends ExceptionHandler
* This is a great spot to send exceptions to Sentry, Bugsnag, etc. * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
* *
* @param \Exception $e * @param \Exception $e
* @return void
*/ */
public function report(Exception $e) public function report(Exception $e)
{ {
@ -39,6 +38,11 @@ class Handler extends ExceptionHandler
*/ */
public function render($request, Exception $e) 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); return parent::render($request, $e);
} }
} }

View File

@ -0,0 +1,21 @@
<?php namespace Oxbow\Exceptions;
class NotifyException extends \Exception
{
public $message;
public $redirectLocation;
/**
* NotifyException constructor.
* @param string $message
* @param string $redirectLocation
*/
public function __construct($message, $redirectLocation)
{
$this->message = $message;
$this->redirectLocation = $redirectLocation;
parent::__construct();
}
}

View File

@ -0,0 +1,6 @@
<?php namespace Oxbow\Exceptions;
class SocialDriverNotConfigured extends \Exception
{
}

View File

@ -0,0 +1,7 @@
<?php namespace Oxbow\Exceptions;
class UserNotFound extends NotifyException
{
}

View File

@ -2,11 +2,15 @@
namespace Oxbow\Http\Controllers\Auth; namespace Oxbow\Http\Controllers\Auth;
use Oxbow\Exceptions\SocialDriverNotConfigured;
use Oxbow\Exceptions\UserNotFound;
use Oxbow\Repos\UserRepo;
use Oxbow\User; use Oxbow\User;
use Validator; use Validator;
use Oxbow\Http\Controllers\Controller; use Oxbow\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins; use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers; use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
use Laravel\Socialite\Contracts\Factory as Socialite;
class AuthController extends Controller class AuthController extends Controller
{ {
@ -27,15 +31,21 @@ class AuthController extends Controller
protected $redirectPath = '/'; protected $redirectPath = '/';
protected $redirectAfterLogout = '/login'; protected $redirectAfterLogout = '/login';
protected $validSocialDrivers = ['google', 'github'];
protected $socialite;
protected $userRepo;
/** /**
* Create a new authentication controller instance. * Create a new authentication controller instance.
* * @param Socialite $socialite
* @return void * @param UserRepo $userRepo
*/ */
public function __construct() public function __construct(Socialite $socialite, UserRepo $userRepo)
{ {
$this->middleware('guest', ['except' => 'getLogout']); $this->middleware('guest', ['except' => 'getLogout']);
$this->socialite = $socialite;
$this->userRepo = $userRepo;
} }
/** /**
@ -67,4 +77,99 @@ class AuthController extends Controller
'password' => bcrypt($data['password']), '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;
}
} }

View File

@ -82,6 +82,10 @@ Route::group(['middleware' => 'auth'], function () {
Route::get('/login', 'Auth\AuthController@getLogin'); Route::get('/login', 'Auth\AuthController@getLogin');
Route::post('/login', 'Auth\AuthController@postLogin'); Route::post('/login', 'Auth\AuthController@postLogin');
Route::get('/logout', 'Auth\AuthController@getLogout'); 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... // Password reset link request routes...
Route::get('/password/email', 'Auth\PasswordController@getEmail'); Route::get('/password/email', 'Auth\PasswordController@getEmail');
Route::post('/password/email', 'Auth\PasswordController@postEmail'); Route::post('/password/email', 'Auth\PasswordController@postEmail');

24
app/Repos/UserRepo.php Normal file
View File

@ -0,0 +1,24 @@
<?php namespace Oxbow\Repos;
use Oxbow\User;
class UserRepo
{
protected $user;
/**
* UserRepo constructor.
* @param $user
*/
public function __construct(User $user)
{
$this->user = $user;
}
public function getByEmail($email) {
return $this->user->where('email', '=', $email)->first();
}
}

View File

@ -8,7 +8,8 @@
"php": ">=5.5.9", "php": ">=5.5.9",
"laravel/framework": "5.1.*", "laravel/framework": "5.1.*",
"intervention/image": "^2.3", "intervention/image": "^2.3",
"barryvdh/laravel-ide-helper": "^2.1" "barryvdh/laravel-ide-helper": "^2.1",
"laravel/socialite": "^2.0"
}, },
"require-dev": { "require-dev": {
"fzaninotto/faker": "~1.4", "fzaninotto/faker": "~1.4",

327
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"hash": "16de3a44150d9425a501c9873cb28eaf", "hash": "7d7e80e9f1c13417c35195f79e41c038",
"packages": [ "packages": [
{ {
"name": "barryvdh/laravel-ide-helper", "name": "barryvdh/laravel-ide-helper",
@ -279,6 +279,214 @@
], ],
"time": "2014-12-20 21:24:13" "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", "name": "guzzlehttp/psr7",
"version": "1.2.0", "version": "1.2.0",
@ -672,6 +880,60 @@
], ],
"time": "2015-08-12 18:16:08" "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", "name": "league/flysystem",
"version": "1.0.11", "version": "1.0.11",
@ -753,6 +1015,69 @@
], ],
"time": "2015-07-28 20:41:58" "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", "name": "monolog/monolog",
"version": "1.16.0", "version": "1.16.0",

View File

@ -26,7 +26,7 @@ return [
| |
*/ */
'url' => 'http://localhost', 'url' => env('APP_URL', 'http://localhost'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -136,6 +136,7 @@ return [
Illuminate\Translation\TranslationServiceProvider::class, Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class, Illuminate\View\ViewServiceProvider::class,
Laravel\Socialite\SocialiteServiceProvider::class,
/** /**
* Third Party * Third Party
@ -199,6 +200,7 @@ return [
'URL' => Illuminate\Support\Facades\URL::class, 'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class, 'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class, 'View' => Illuminate\Support\Facades\View::class,
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
/** /**
* Third Party * Third Party

View File

@ -35,4 +35,16 @@ return [
'secret' => '', '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',
],
]; ];

View File

@ -3,6 +3,7 @@ $(function () {
// Notification hiding // Notification hiding
$('.notification').click(function () { $('.notification').click(function () {
$(this).fadeOut(100); $(this).fadeOut(100);
}); });
// Dropdown toggles // Dropdown toggles

View File

@ -42,6 +42,9 @@
animation-duration: 3s; animation-duration: 3s;
animation-timing-function: ease-in-out; animation-timing-function: ease-in-out;
animation-fill-mode: forwards; animation-fill-mode: forwards;
&.stopped {
animation-name: notificationStopped;
}
} }
@keyframes notification { @keyframes notification {
@ -58,6 +61,17 @@
transform: translate3d(580px, 0, 0); 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 { @keyframes menuIn {
from { from {

View File

@ -30,7 +30,6 @@ $button-border-radius: 2px;
border-radius: $button-border-radius; border-radius: $button-border-radius;
cursor: pointer; cursor: pointer;
transition: all ease-in-out 120ms; transition: all ease-in-out 120ms;
text-transform: uppercase;
box-shadow: 0 0.5px 1.5px 0 rgba(0, 0, 0, 0.21); box-shadow: 0 0.5px 1.5px 0 rgba(0, 0, 0, 0.21);
@include generate-button-colors(#EEE, $primary); @include generate-button-colors(#EEE, $primary);
} }

View File

@ -1,6 +1,6 @@
@extends('public') @extends('public')
@section('sidebar') @section('content')
<div class="center-box"> <div class="center-box">
<h1>Log In</h1> <h1>Log In</h1>
@ -23,6 +23,16 @@
<button class="button block pos">Sign In</button> <button class="button block pos">Sign In</button>
</div> </div>
</form> </form>
@if(count($socialDrivers) > 0)
<hr class="margin-top">
<h3 class="text-muted">Social Login</h3>
@if(isset($socialDrivers['google']))
<a href="/login/service/google" style="color: #DC4E41;"><i class="zmdi zmdi-google-plus-box zmdi-hc-4x"></i></a>
@endif
@if(isset($socialDrivers['github']))
<a href="/login/service/github" style="color:#000;"><i class="zmdi zmdi-github zmdi-hc-4x"></i></a>
@endif
@endif
</div> </div>
@stop @stop

View File

@ -2,7 +2,7 @@
@section('body-class', 'image-cover login') @section('body-class', 'image-cover login')
@section('sidebar') @section('content')
<div class="text-center"> <div class="text-center">

View File

@ -2,7 +2,7 @@
@section('body-class', 'image-cover login') @section('body-class', 'image-cover login')
@section('sidebar') @section('content')
<div class="text-center"> <div class="text-center">

View File

@ -31,7 +31,7 @@
@endif @endif
@if(Session::has('error')) @if(Session::has('error'))
<div class="notification anim neg"> <div class="notification anim neg stopped">
<i class="zmdi zmdi-alert-circle"></i> <span>{{ Session::get('error') }}</span> <i class="zmdi zmdi-alert-circle"></i> <span>{{ Session::get('error') }}</span>
</div> </div>
@endif @endif

View File

@ -6,14 +6,25 @@
<link rel="stylesheet" href="/css/app.css"> <link rel="stylesheet" href="/css/app.css">
<link href='//fonts.googleapis.com/css?family=Roboto:400,400italic,500,500italic,700,700italic,300italic,100,300' rel='stylesheet' type='text/css'> <link href='//fonts.googleapis.com/css?family=Roboto:400,400italic,500,500italic,700,700italic,300italic,100,300' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="/bower/material-design-iconic-font/dist/css/material-design-iconic-font.min.css"> <link rel="stylesheet" href="/bower/material-design-iconic-font/dist/css/material-design-iconic-font.min.css">
<!-- Scripts -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="/js/common.js"></script>
</head> </head>
<body class="@yield('body-class')"> <body class="@yield('body-class')">
<section id="sidebar"> @if(Session::has('success'))
@yield('sidebar') <div class="notification anim pos">
</section> <i class="zmdi zmdi-mood"></i> <span>{{ Session::get('success') }}</span>
</div>
@endif
@if(Session::has('error'))
<div class="notification anim neg stopped">
<i class="zmdi zmdi-alert-circle"></i> <span>{{ Session::get('error') }}</span>
</div>
@endif
<section class="container"> <section class="container">
@yield('content') @yield('content')

View File

@ -8,6 +8,13 @@
@include('form/text', ['name' => 'email']) @include('form/text', ['name' => 'email'])
</div> </div>
@if($currentUser->can('user-update'))
<div class="form-group">
<label for="role">User Role</label>
@include('form.role-select', ['name' => 'role', 'options' => \Oxbow\Role::all(), 'displayKey' => 'display_name'])
</div>
@endif
@if(isset($model)) @if(isset($model))
<div class="form-group"> <div class="form-group">
<span class="text-muted"> <span class="text-muted">
@ -16,13 +23,6 @@
</div> </div>
@endif @endif
@if($currentUser->can('user-update'))
<div class="form-group">
<label for="role">User Role</label>
@include('form.role-select', ['name' => 'role', 'options' => \Oxbow\Role::all(), 'displayKey' => 'display_name'])
</div>
@endif
<div class="form-group"> <div class="form-group">
<label for="password">Password</label> <label for="password">Password</label>
@include('form/password', ['name' => 'password']) @include('form/password', ['name' => 'password'])