diff --git a/.env.example b/.env.example index 30941439..3ca2fea9 100644 --- a/.env.example +++ b/.env.example @@ -32,3 +32,31 @@ MAIL_PASSWORD= # MAIL_DKIM_PRIVATE = '/path/to/private/key'; # MAIL_DKIM_SELECTOR = 'default'; # Match your DKIM DNS selector # MAIL_DKIM_PASSPHRASE = ''; # Only if your key has a passphrase + +# List of socialite providers separated by a space. Possible value : keycloak, oidc +SOCIALITE_PROVIDERS="" + +KEYCLAOK_DISPLAY_NAME="Keycloak" +KEYCLOAK_ALLOW_CREATE_USER=false +KEYCLOAK_ALLOW_UPDATE_USER=false +KEYCLOAK_DEFAULT_ROLE="auditee" +KEYCLOAK_ROLE_CLAIM="resource_access.deming.roles.0" +KEYCLOAK_ADDITIONAL_SCOPES="roles" + +KEYCLOAK_CLIENT_ID=deming +KEYCLOAK_CLIENT_SECRET=secret +KEYCLOAK_REDIRECT_URI=${APP_URL}auth/callback/keycloak +KEYCLOAK_BASE_URL=https://keycloak.local +KEYCLOAK_REALM=main + +OIDC_DISPLAY_NAME="Generic OIDC" +OIDC_ALLOW_CREATE_USER=false +OIDC_ALLOW_UPDATE_USER=false +OIDC_DEFAULT_ROLE="auditee" +OIDC_ROLE_CLAIM="" +OIDC_ADDITIONAL_SCOPES="deming_role" + +OIDC_CLIENT_ID=deming +OIDC_CLIENT_SECRET=deming +OIDC_BASE_URL=http://auth.lan +OIDC_REDIRECT_URI=${APP_URL}auth/callback/oidc diff --git a/app/Http/Controllers/SocialiteController.php b/app/Http/Controllers/SocialiteController.php new file mode 100644 index 00000000..e9fad280 --- /dev/null +++ b/app/Http/Controllers/SocialiteController.php @@ -0,0 +1,251 @@ + '1', + 'user' => '2', + 'auditee' => '5', + 'auditor' => '3', + //'api' => '4', + ]; + + public const LOCALES = ['en', 'fr']; + + public function __construct() + { + $this->middleware('auth:api', ['except' => ['redirect', 'callback']]); + } + + /** + * Redirect action use to redirect user to OIDC provider. + */ + public function redirect(string $provider) + { + $providers = config('services.socialite_controller.providers', []); + + if (in_array($provider, $providers)) { + Log::debug("Redirect with '$provider' provider"); + $config_name = 'services.socialite_controller.'.$provider; + $additional_scopes = config($config_name.'.additional_scopes'); + return Socialite::with($provider)->scopes($additional_scopes)->redirect(); + } + + Log::warning("Redirect: Provider '$provider' not found."); + abort(404); + } + + /** + * Callback action use when OIDC provider redirect user to app. + */ + public function callback(Request $request, string $provider) + { + $providers = config('services.socialite_controller.providers', []); + + if (! in_array($provider, $providers)) { + Log::warning("Callback: Provider '$provider' not found."); + abort(404); + } + + Log::debug("Callback provider : '$provider'"); + + // Get additionnal config for current provider + $config_name = 'services.socialite_controller.'.$provider; + $allow_create_user = false; + $allow_update_user = false; + if(config($config_name)){ + $allow_create_user = config($config_name.'.allow_create_user', $allow_create_user); + $allow_update_user = config($config_name.'.allow_update_user', $allow_update_user); + } + Log::debug('CONFIG: allow_create_user='.($allow_create_user ? 'true' : 'false')); + Log::debug('CONFIG: allow_update_user='.($allow_update_user ? 'true' : 'false')); + if($allow_create_user || $allow_update_user){ + $role_claim = config($config_name.'.role_claim', ''); + Log::debug('CONFIG: role_claim='.$role_claim); + $default_role = config($config_name.'.default_role', ''); + Log::debug('CONFIG: default_role='.$default_role); + } + + try { + $socialite_user = Socialite::with($provider)->user(); + $user = null; + + // Search user by email + if($socialite_user->email){ + $user = User::query()->whereEmail($socialite_user->email)->first(); + } else { + Log::warning("User has no attribute email"); + } + + // If not exist and allow to create user then create it + if (!$user && $allow_create_user) { + $user = $this->create_user($socialite_user, $provider, $role_claim, $default_role); + } + + // If no user redirect to login with error message + if (!$user) { + Log::warning("User [$socialite_user->id, $socialite_user->email] not found in deming database"); + return redirect('login')->withErrors(['socialite' => trans('cruds.login.error.user_not_exist') ]); + } + + if($allow_update_user){ + $this->update_user($user, $socialite_user, $provider, $role_claim, $default_role); + } + + Log::info("User '$user->login' login with $provider provider"); + + Auth::guard('web')->login($user); + + return redirect('/'); + } catch (Exception $exception) { + return redirect('login'); + } + } + + /** + * Create user with claims provided. + */ + protected function create_user(SocialiteUser $socialite_user, string $provider, string $role_claim, string $default_role) + { + $user = new User(); + + $user->login = $this->get_user_login($socialite_user); + $user->name = $socialite_user->name; + $user->email = $socialite_user->email; + $user->title = "User provide by $provider"; + $user->role = $this->get_user_role($socialite_user, $role_claim, $default_role); + $user->language = $this->get_user_langage($socialite_user); + + // TODO allow null password + $user->password = bin2hex(random_bytes(32)); + + Log::info("Create new user '$user->login' with role '$user->role' from $provider provider"); + try { + $user->save(); + } catch(QueryException $exception){ + Log::debug($exception->getMessage()); + Log::error("Unable to create user"); + return null; + } + + return $user; + } + + /** + * Update user with claims providid. + */ + protected function update_user(User $user, SocialiteUser $socialite_user, string $provider, string $role_claim, string $default_role) + { + $updated = false; + + $login = $this->get_user_login($socialite_user); + if ($login !== $user->login) { + Log::debug("Login changed $user->login => $login"); + $user->login = $login; + $updated = true; + } + + if ($socialite_user->name !== $user->name) { + Log::debug("Name changed $user->name => $socialite_user->name"); + $user->name = $socialite_user->name; + $updated = true; + } + + $role = $this->get_user_role($socialite_user, $role_claim, $default_role); + if($role != $user->role){ + Log::debug("Role changed $user->role => $role"); + $user->role = $role; + $updated = true; + } + + $language = $this->get_user_langage($socialite_user); + if ($language !== $user->language) { + Log::debug("Lauguage change $user->language => $language"); + $user->language = $language; + $updated = true; + } + + if ($updated) { + Log::info("Update user '$user->login' with role '$user->role' from $provider provider"); + $user->save(); + } + return $user; + } + + /** + * Return user's login. + */ + private function get_user_login(SocialiteUser $socialite_user) + { + // set login with preferred_username, otherwise use id + if($socialite_user->offsetExists('preferred_username')){ + return $socialite_user->offsetGet("preferred_username"); + } + return $socialite_user->id; + } + + /** + * Return user's role. + * If no role provided, use $default_role value. + * If $default_role is null and no role provided, null return. + */ + private function get_user_role(SocialiteUser $socialite_user, string $role_claim, string $default_role) + { + $role_name = ""; + if(!empty($role_claim)){ + $role_name = $this->get_claim_value($socialite_user, $role_claim); + Log::debug("Provided claim '$role_claim'='$role_name'"); + } + if(!array_key_exists($role_name, self::ROLES_MAP)){ + if(!empty($default_role)){ + $role_name = $default_role; + } else { + Log::error("No default role set! A valid role must be provided. role='$role_name'"); + return null; + } + } + return self::ROLES_MAP[$role_name]; + } + + /** + * Return user's language. + * Use locale claim to dertermine user's language. + */ + private function get_user_langage(SocialiteUser $socialite_user) + { + if ($socialite_user->offsetExists('locale')){ + $locale = explode('-', $socialite_user->offsetGet('locale'))[0]; + if (in_array($locale, self::LOCALES)) return $locale; + } + return self::LOCALES[0]; + } + + private function get_claim_value(SocialiteUser $user, string $claim){ + $value = null; + foreach(explode('.', $claim) as $offset) { + if(! $value){ + if (! $user->offsetExists($offset)) return null; + $value = $user->offsetGet($offset); + continue; + } + if (! array_key_exists($offset, $value)) return null; + $value = $value[$offset]; + } + return $value; + } +} \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 0c84d575..ed42e24b 100755 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,6 +4,7 @@ use DB; use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\URL; use Illuminate\Support\ServiceProvider; @@ -40,5 +41,30 @@ public function boot() ); }); } + + if (in_array('keycloak', Config::get('services.socialite_controller.providers'))){ + Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) { + $event->extendSocialite('keycloak', \SocialiteProviders\Keycloak\Provider::class); + }); + } + + if (in_array('oidc', Config::get('services.socialite_controller.providers'))){ + $this->bootOIDCSocialite(); + } + } + + /** + * Register Generic OpenID Connect Provider. + */ + private function bootOIDCSocialite() + { + $socialite = $this->app->make('Laravel\Socialite\Contracts\Factory'); + $socialite->extend( + 'oidc', + function ($app) use ($socialite) { + $config = $app['config']['services.oidc']; + return $socialite->buildProvider(\App\Providers\Socialite\GenericSocialiteProvider::class, $config); + } + ); } } diff --git a/app/Providers/Socialite/GenericSocialiteProvider.php b/app/Providers/Socialite/GenericSocialiteProvider.php new file mode 100644 index 00000000..2f03709c --- /dev/null +++ b/app/Providers/Socialite/GenericSocialiteProvider.php @@ -0,0 +1,143 @@ +getOIDCUrl().'/authorize'; + // If authorize endpoint set, use it instead + if (config('services.oidc.authorize_endpoint')){ + $base_url = config('services.oidc.authorize_endpoint'); + } + Log::debug('Buiild auth url from base : '.$base_url); + return $this->buildAuthUrlFromBase($base_url, $state); + } + + /** + * @return string + */ + protected function getTokenUrl() + { + // If token endpoint set, use it instead + if (config('services.oidc.token_endpoint')){ + return config('services.oidc.token_endpoint'); + } + return $this->getOIDCUrl() . '/token'; + } + + /** + * @param string $token + * + * @throws GuzzleException + * + * @return array|mixed + */ + protected function getUserByToken($token) + { + $base_url = $this->getOIDCUrl() . '/userinfo'; + // If userinfo endpoint set, use it instead + if (config('services.oidc.userinfo_endpoint')){ + $base_url = config('services.oidc.userinfo_endpoint'); + } + + Log::debug('Get user info from '.$base_url); + $response = $this->getHttpClient()->post($base_url, [ + 'headers' => [ + 'cache-control' => 'no-cache', + 'Authorization' => 'Bearer ' . $token, + 'Content-Type' => 'application/x-www-form-urlencoded', + ], + ]); + + return json_decode($response->getBody()->getContents(), true); + } + + /** + * @return User + */ + protected function mapUserToObject(array $user) + { + Log::debug('Provider return user :'.var_export($user, true)); + $socialite_user = []; + foreach(config('services.oidc.map_user_attr') as $socialite_attr => $provider_attr){ + if ( ! array_key_exists($provider_attr, $user)){ + Log::debug("'$provider_attr' not provided"); + continue; + } + Log::debug("Map socialite_user['$socialite_attr']=".$user[$provider_attr]); + $socialite_user[$socialite_attr] = $user[$provider_attr]; + } + return (new User())->setRaw($user)->map($socialite_user); + } + + /** + * {@inheritdoc} + */ + public static function additionalConfigKeys(): array + { + return [ + 'client_id', + 'host', + 'logout_uri', + 'redirect', + 'authorize_endpoint', + 'userinfo_endpoint', + 'token_endpoint', + 'map_user_attr', + ]; + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index e28c9ad6..86d11d71 100644 --- a/composer.json +++ b/composer.json @@ -10,11 +10,13 @@ "laravel/framework": "^11.9", "laravel/passport": "^12.3", "laravel/sanctum": "^4.0", + "laravel/socialite": "^5.16", "laravel/tinker": "^2.9", "laravel/ui": "^4.5", "maatwebsite/excel": "^3.1", + "phpmailer/phpmailer": "^6.9.2", "phpoffice/phpword": "^1.3", - "phpmailer/phpmailer": "^6.9.2" + "socialiteproviders/keycloak": "^5.3" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/composer.lock b/composer.lock index 7371d090..d39badfc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "24e1dc9b787f5455925005c70616e2bc", + "content-hash": "24e0bcdfbf7496d757375087555668a7", "packages": [ { "name": "brick/math", @@ -1845,6 +1845,78 @@ }, "time": "2024-09-23T13:33:08+00:00" }, + { + "name": "laravel/socialite", + "version": "v5.16.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/socialite.git", + "reference": "40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/socialite/zipball/40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf", + "reference": "40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf", + "shasum": "" + }, + "require": { + "ext-json": "*", + "firebase/php-jwt": "^6.4", + "guzzlehttp/guzzle": "^6.0|^7.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "league/oauth1-client": "^1.10.1", + "php": "^7.2|^8.0", + "phpseclib/phpseclib": "^3.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.0|^9.3|^10.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Socialite\\SocialiteServiceProvider" + ], + "aliases": { + "Socialite": "Laravel\\Socialite\\Facades\\Socialite" + } + } + }, + "autoload": { + "psr-4": { + "Laravel\\Socialite\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.", + "homepage": "https://laravel.com", + "keywords": [ + "laravel", + "oauth" + ], + "support": { + "issues": "https://github.com/laravel/socialite/issues", + "source": "https://github.com/laravel/socialite" + }, + "time": "2024-09-03T09:46:57+00:00" + }, { "name": "laravel/tinker", "version": "v2.10.0", @@ -2541,6 +2613,82 @@ ], "time": "2024-09-21T08:32:55+00:00" }, + { + "name": "league/oauth1-client", + "version": "v1.10.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth1-client.git", + "reference": "d6365b901b5c287dd41f143033315e2f777e1167" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/d6365b901b5c287dd41f143033315e2f777e1167", + "reference": "d6365b901b5c287dd41f143033315e2f777e1167", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "guzzlehttp/guzzle": "^6.0|^7.0", + "guzzlehttp/psr7": "^1.7|^2.0", + "php": ">=7.1||>=8.0" + }, + "require-dev": { + "ext-simplexml": "*", + "friendsofphp/php-cs-fixer": "^2.17", + "mockery/mockery": "^1.3.3", + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5||9.5" + }, + "suggest": { + "ext-simplexml": "For decoding XML-based responses." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev", + "dev-develop": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\OAuth1\\Client\\": "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" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth1-client/issues", + "source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.1" + }, + "time": "2022-04-15T14:02:14+00:00" + }, { "name": "league/oauth2-server", "version": "8.5.4", @@ -5011,6 +5159,130 @@ ], "time": "2024-04-27T21:32:50+00:00" }, + { + "name": "socialiteproviders/keycloak", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/SocialiteProviders/Keycloak.git", + "reference": "87d13f8a411a6f8f5010ecbaff9aedd4494863e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SocialiteProviders/Keycloak/zipball/87d13f8a411a6f8f5010ecbaff9aedd4494863e4", + "reference": "87d13f8a411a6f8f5010ecbaff9aedd4494863e4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.4 || ^8.0", + "socialiteproviders/manager": "~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "SocialiteProviders\\Keycloak\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oleg Kuchumov", + "email": "voenniy@gmail.com" + } + ], + "description": "Keycloak OAuth2 Provider for Laravel Socialite", + "keywords": [ + "keycloak", + "laravel", + "oauth", + "provider", + "socialite" + ], + "support": { + "docs": "https://socialiteproviders.com/keycloak", + "issues": "https://github.com/socialiteproviders/providers/issues", + "source": "https://github.com/socialiteproviders/providers" + }, + "time": "2023-04-10T05:50:49+00:00" + }, + { + "name": "socialiteproviders/manager", + "version": "v4.6.0", + "source": { + "type": "git", + "url": "https://github.com/SocialiteProviders/Manager.git", + "reference": "dea5190981c31b89e52259da9ab1ca4e2b258b21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/dea5190981c31b89e52259da9ab1ca4e2b258b21", + "reference": "dea5190981c31b89e52259da9ab1ca4e2b258b21", + "shasum": "" + }, + "require": { + "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0", + "laravel/socialite": "^5.5", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "SocialiteProviders\\Manager\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "SocialiteProviders\\Manager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andy Wendt", + "email": "andy@awendt.com" + }, + { + "name": "Anton Komarev", + "email": "a.komarev@cybercog.su" + }, + { + "name": "Miguel Piedrafita", + "email": "soy@miguelpiedrafita.com" + }, + { + "name": "atymic", + "email": "atymicq@gmail.com", + "homepage": "https://atymic.dev" + } + ], + "description": "Easily add new or override built-in providers in Laravel Socialite.", + "homepage": "https://socialiteproviders.com", + "keywords": [ + "laravel", + "manager", + "oauth", + "providers", + "socialite" + ], + "support": { + "issues": "https://github.com/socialiteproviders/manager/issues", + "source": "https://github.com/socialiteproviders/manager" + }, + "time": "2024-05-04T07:57:39+00:00" + }, { "name": "symfony/clock", "version": "v7.1.6", @@ -9733,12 +10005,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.2" }, - "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform-dev": {}, + "plugin-api-version": "2.6.0" } diff --git a/config/services.php b/config/services.php index 2a1d616c..24be951d 100644 --- a/config/services.php +++ b/config/services.php @@ -30,4 +30,51 @@ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], + 'socialite_controller' => [ + 'providers' => ! empty(env('SOCIALITE_PROVIDERS', "")) + ? explode(' ', env('SOCIALITE_PROVIDERS', "")) + : [], + 'keycloak' => [ + 'display_name' => env('KEYCLAOK_DISPLAY_NAME', 'Keycloak'), + 'allow_create_user' => env("KEYCLOAK_ALLOW_CREATE_USER", false), + 'allow_update_user' => env("KEYCLOAK_ALLOW_UPDATE_USER", false), + // Set to null if you want role to be set explicitily + 'default_role' => env('KEYCLOAK_DEFAULT_ROLE', 'auditee'), + 'role_claim' => env('KEYCLOAK_ROLE_CLAIM', ''), + 'additional_scopes' => explode(' ', env('KEYCLOAK_ADDITIONAL_SCOPES', "")), + ], + 'oidc' => [ + 'display_name' => env('OIDC_DISPLAY_NAME', 'Generic OIDC'), + 'allow_create_user' => env("OIDC_ALLOW_CREATE_USER", false), + 'allow_update_user' => env("OIDC_ALLOW_UPDATE_USER", false), + // Set to null if you want role to be set explicitily + 'default_role' => env('OIDC_DEFAULT_ROLE', 'auditee'), + 'role_claim' => env('OIDC_ROLE_CLAIM', 'role'), + 'additional_scopes' => explode(' ', env('OIDC_ADDITIONAL_SCOPES', "")), + ], + ], + + 'keycloak' => [ + 'client_id' => env('KEYCLOAK_CLIENT_ID'), + 'client_secret' => env('KEYCLOAK_CLIENT_SECRET'), + 'redirect' => env('KEYCLOAK_REDIRECT_URI'), + 'base_url' => env('KEYCLOAK_BASE_URL'), // Specify your keycloak server URL here + 'realms' => env('KEYCLOAK_REALM'), // Specify your keycloak realm + ], + + 'oidc' => [ + 'client_id' => env('OIDC_CLIENT_ID'), + 'client_secret' => env('OIDC_CLIENT_SECRET'), + 'host' => env('OIDC_BASE_URL'), + 'redirect' => env('OIDC_REDIRECT_URI', rtrim(env('APP_URL'), '/').'/auth/callback/oidc'), + 'authorize_endpoint' => env('OIDC_AUTHORIZE_ENDPOINT', null), + 'token_endpoint' => env('OIDC_TOKEN_ENDPOINT', null), + 'userinfo_endpoint' => env('OIDC_USERINFO_ENDPOINT', null), + 'map_user_attr' => [ + 'id' => 'sub', + 'name' => 'name', + 'locale' => 'locale', + 'email' => 'email' + ], + ], ]; diff --git a/resources/lang/en/cruds.php b/resources/lang/en/cruds.php index b8be02d6..6ac9ee4b 100644 --- a/resources/lang/en/cruds.php +++ b/resources/lang/en/cruds.php @@ -169,6 +169,10 @@ 'title' => 'Enter a password', 'identification' => 'Login', 'connection' => 'Connection', + 'connection_with' => 'Connection with', + 'error' => [ + 'user_not_exist' => 'User not exist', + ], ], 'report' => [ 'action_plan' => [ diff --git a/resources/lang/fr/cruds.php b/resources/lang/fr/cruds.php index 3135cb38..90f82424 100644 --- a/resources/lang/fr/cruds.php +++ b/resources/lang/fr/cruds.php @@ -168,6 +168,10 @@ 'title' => 'Entrez un mot de passe', 'identification' => 'Identification', 'connection' => 'Connexion', + 'connection_with' => 'Connexion avec', + 'error' => [ + 'user_not_exist' => 'L\'utilisateur n\'existe pas', + ], ], 'report' => [ 'action_plan' => [ diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index a7051890..0ca9ff05 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -39,43 +39,62 @@
-