Social Login Feature
* Added 3rd party identity providers: * Facebook * Google * Linkedin * Apple * UI changes ( 2 steps login) * ReactJS integration * Webpack Update * Meta Document update (oauth2/.well-known/openid-configuration) * Added provider param on oauth2 flow Depends-On: https://review.opendev.org/c/osf/openstackid/+/772531 Change-Id: I86cef9379fcd6ca5320f080e062fc2abaa36203c
This commit is contained in:
parent
3ab448a332
commit
74f56ffb26
17
.babelrc
17
.babelrc
@ -1,17 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "current"
|
||||
}
|
||||
}
|
||||
],
|
||||
"flow",
|
||||
"react"
|
||||
],
|
||||
"plugins": [
|
||||
"transform-object-rest-spread"
|
||||
]
|
||||
}
|
18
.env.example
18
.env.example
@ -80,3 +80,21 @@ RABBITMQ_SSL_CAFILE=/certs/rabbit/ca-osf.pem
|
||||
RABBITMQ_SSL_LOCALCERT=/certs/rabbit/client-cert-osf.pem
|
||||
RABBITMQ_SSL_LOCALKEY=/certs/rabbit/client-key-osf.pem
|
||||
RABBITMQ_SSL_VERIFY_PEER=false
|
||||
|
||||
# 3rd party idps
|
||||
|
||||
FACEBOOK_CLIENT_ID=
|
||||
FACEBOOK_CLIENT_SECRET=
|
||||
FACEBOOK_REDIRECT_URI=/auth/login/facebook/callback
|
||||
|
||||
GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
GOOGLE_REDIRECT_URI=/auth/login/google/callback
|
||||
|
||||
APPLE_CLIENT_ID=
|
||||
APPLE_CLIENT_SECRET=
|
||||
APPLE_REDIRECT_URI=/auth/login/apple/callback
|
||||
|
||||
LINKEDIN_CLIENT_ID=
|
||||
LINKEDIN_CLIENT_SECRET=
|
||||
LINKEDIN_REDIRECT_URI=/auth/login/linkedin/callback
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -42,3 +42,6 @@ routes.txt
|
||||
model.sql
|
||||
.phpunit.result.cache
|
||||
!/public/web.config
|
||||
/public/assets/dist/
|
||||
/public/assets/login.js
|
||||
/public/assets/css/login.css
|
||||
|
@ -1,5 +1,4 @@
|
||||
<?php namespace App\Http\Controllers\Api;
|
||||
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -13,10 +12,8 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Traits\JsonResponses;
|
||||
use Utils\Services\ILogService;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Exception;
|
||||
/**
|
||||
* Class JsonController
|
||||
* @package App\Http\Controllers
|
||||
@ -30,74 +27,5 @@ abstract class JsonController extends Controller {
|
||||
$this->log_service = $log_service;
|
||||
}
|
||||
|
||||
protected function error500(Exception $ex){
|
||||
$this->log_service->error($ex);
|
||||
return Response::json(array( 'error' => 'server error'), 500);
|
||||
}
|
||||
|
||||
protected function created($data='ok'){
|
||||
$res = Response::json($data, 201);
|
||||
//jsonp
|
||||
if(Request::has('callback'))
|
||||
$res->setCallback(Request::input('callback'));
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function updated($data = 'ok', $has_content = true)
|
||||
{
|
||||
$res = Response::json($data, $has_content ? 201 : 204);
|
||||
//jsonp
|
||||
if (Request::has('callback')) {
|
||||
$res->setCallback(Request::input('callback'));
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function deleted($data='ok'){
|
||||
$res = Response::json($data, 204);
|
||||
//jsonp
|
||||
if(Request::has('callback'))
|
||||
$res->setCallback(Request::input('callback'));
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function ok($data = 'ok'){
|
||||
$res = Response::json($data, 200);
|
||||
//jsonp
|
||||
if(Request::has('callback'))
|
||||
$res->setCallback(Request::input('callback'));
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function error400($data = ['message' => 'Bad Request']){
|
||||
return Response::json($data, 400);
|
||||
}
|
||||
|
||||
protected function error404($data = array('message' => 'Entity Not Found')){
|
||||
return Response::json($data, 404);
|
||||
}
|
||||
|
||||
protected function error403($data = array('message' => 'Forbidden'))
|
||||
{
|
||||
return Response::json($data, 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* {
|
||||
"message": "Validation Failed",
|
||||
"errors": [
|
||||
{
|
||||
"resource": "Issue",
|
||||
"field": "title",
|
||||
"code": "missing_field"
|
||||
}
|
||||
]
|
||||
}
|
||||
* @param $messages
|
||||
* @return mixed
|
||||
*/
|
||||
protected function error412($messages){
|
||||
|
||||
return Response::json(array('error'=>'validation' , 'messages' => $messages), 412);
|
||||
}
|
||||
use JsonResponses;
|
||||
}
|
@ -25,7 +25,6 @@ use OAuth2\Factories\OAuth2AuthorizationRequestFactory;
|
||||
use OAuth2\OAuth2Message;
|
||||
use OAuth2\Repositories\IClientRepository;
|
||||
use OAuth2\Services\IMementoOAuth2SerializerService;
|
||||
use Sokil\IsoCodes\IsoCodesFactory;
|
||||
use Exception;
|
||||
/**
|
||||
* Class RegisterController
|
||||
|
129
app/Http/Controllers/SocialLoginController.php
Normal file
129
app/Http/Controllers/SocialLoginController.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use App\libs\Auth\SocialLoginProviders;
|
||||
use App\Services\Auth\IUserService;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use models\exceptions\ValidationException;
|
||||
use Strategies\ILoginStrategy;
|
||||
use Strategies\ILoginStrategyFactory;
|
||||
use Utils\Services\IAuthService;
|
||||
/**
|
||||
* Class SocialLoginController
|
||||
* @package App\Http\Controllers
|
||||
*/
|
||||
final class SocialLoginController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @var IAuthService
|
||||
*/
|
||||
private $auth_service;
|
||||
/**
|
||||
* @var IUserService
|
||||
*/
|
||||
private $user_service;
|
||||
/**
|
||||
* @var ILoginStrategy
|
||||
*/
|
||||
private $login_strategy;
|
||||
|
||||
/**
|
||||
* SocialLoginController constructor.
|
||||
* @param IAuthService $auth_service
|
||||
* @param IUserService $user_service
|
||||
* @param ILoginStrategyFactory $login_strategy_factory
|
||||
*/
|
||||
public function __construct(
|
||||
IAuthService $auth_service,
|
||||
IUserService $user_service,
|
||||
ILoginStrategyFactory $login_strategy_factory
|
||||
){
|
||||
$this->auth_service = $auth_service;
|
||||
$this->user_service = $user_service;
|
||||
$this->middleware(function ($request, $next) use($login_strategy_factory){
|
||||
// we do it here just to ensure that user session is loaded
|
||||
Log::debug(sprintf("SocialLoginController::middleware"));
|
||||
$this->login_strategy = $login_strategy_factory->build();
|
||||
return $next($request);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $provider
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*/
|
||||
public function redirect($provider)
|
||||
{
|
||||
try {
|
||||
Log::debug(sprintf("SocialLoginController::redirect provider %s", $provider));
|
||||
if(!SocialLoginProviders::isSupportedProvider($provider))
|
||||
throw new ValidationException(sprintf("Provider %s is not supported.", $provider));
|
||||
return Socialite::driver($provider)->redirect();
|
||||
}
|
||||
catch (\Exception $ex){
|
||||
Log::error($ex);
|
||||
}
|
||||
return view("auth.register_error");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $provider
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|mixed
|
||||
*/
|
||||
public function callback($provider)
|
||||
{
|
||||
try {
|
||||
Log::debug(sprintf("SocialLoginController::callback provider %s", $provider));
|
||||
// validate provider
|
||||
if(!SocialLoginProviders::isSupportedProvider($provider))
|
||||
throw new ValidationException(sprintf("Provider %s is not supported.", $provider));
|
||||
|
||||
$social_user = Socialite::driver($provider)->user();
|
||||
// try to get user by primary email from our db
|
||||
Log::debug(sprintf("SocialLoginController::callback provider %s trying to get user using email %s", $provider, $social_user->getEmail()));
|
||||
|
||||
$user = $this->auth_service->getUserByUsername($social_user->getEmail());
|
||||
|
||||
if (is_null($user)) {
|
||||
Log::debug(sprintf("SocialLoginController::callback provider %s user does not exists for email %s, creating ...", $provider, $social_user->getEmail()));
|
||||
// if does not exists , registered it with email verified and active
|
||||
|
||||
|
||||
$user = $this->user_service->registerUser([
|
||||
'email' => $social_user->getEmail(),
|
||||
'full_name' => $social_user->getName(),
|
||||
'external_pic' => $social_user->getAvatar(),
|
||||
'external_id' => $social_user->getId(),
|
||||
'email_verified' => true,
|
||||
'active' => true,
|
||||
'external_provider' => $provider
|
||||
]);
|
||||
}
|
||||
|
||||
// do login
|
||||
Auth::login($user, true);
|
||||
// and continue the usual flow
|
||||
return $this->login_strategy->postLogin([ 'provider'=> $provider ]);
|
||||
}
|
||||
catch (\Exception $ex){
|
||||
Log::error($ex);
|
||||
}
|
||||
return view("auth.register_error");
|
||||
}
|
||||
|
||||
}
|
94
app/Http/Controllers/Traits/JsonResponses.php
Normal file
94
app/Http/Controllers/Traits/JsonResponses.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php namespace App\Http\Controllers\Traits;
|
||||
/**
|
||||
* Copyright 2019 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Exception;
|
||||
/**
|
||||
* Trait JsonResponses
|
||||
* @package App\Http\Controllers\Traits
|
||||
*/
|
||||
trait JsonResponses
|
||||
{
|
||||
protected function error500(Exception $ex){
|
||||
Log::error($ex);
|
||||
return Response::json(array( 'error' => 'server error'), 500);
|
||||
}
|
||||
|
||||
protected function created($data='ok'){
|
||||
$res = Response::json($data, 201);
|
||||
//jsonp
|
||||
if(Request::has('callback'))
|
||||
$res->setCallback(Request::input('callback'));
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function updated($data = 'ok', $has_content = true)
|
||||
{
|
||||
$res = Response::json($data, $has_content ? 201 : 204);
|
||||
//jsonp
|
||||
if (Request::has('callback')) {
|
||||
$res->setCallback(Request::input('callback'));
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function deleted($data='ok'){
|
||||
$res = Response::json($data, 204);
|
||||
//jsonp
|
||||
if(Request::has('callback'))
|
||||
$res->setCallback(Request::input('callback'));
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function ok($data = 'ok'){
|
||||
$res = Response::json($data, 200);
|
||||
//jsonp
|
||||
if(Request::has('callback'))
|
||||
$res->setCallback(Request::input('callback'));
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function error400($data = ['message' => 'Bad Request']){
|
||||
return Response::json($data, 400);
|
||||
}
|
||||
|
||||
protected function error404($data = array('message' => 'Entity Not Found')){
|
||||
return Response::json($data, 404);
|
||||
}
|
||||
|
||||
protected function error403($data = array('message' => 'Forbidden'))
|
||||
{
|
||||
return Response::json($data, 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* {
|
||||
"message": "Validation Failed",
|
||||
"errors": [
|
||||
{
|
||||
"resource": "Issue",
|
||||
"field": "title",
|
||||
"code": "missing_field"
|
||||
}
|
||||
]
|
||||
}
|
||||
* @param $messages
|
||||
* @return mixed
|
||||
*/
|
||||
protected function error412($messages){
|
||||
|
||||
return Response::json(array('error'=>'validation' , 'messages' => $messages), 412);
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@
|
||||
|
||||
use App\Http\Controllers\OpenId\DiscoveryController;
|
||||
use App\Http\Controllers\OpenId\OpenIdController;
|
||||
use App\Http\Controllers\Traits\JsonResponses;
|
||||
use App\Http\Utils\CountryList;
|
||||
use Auth\Exceptions\AuthenticationException;
|
||||
use Auth\Exceptions\UnverifiedEmailMemberException;
|
||||
@ -25,6 +26,8 @@ use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use models\exceptions\EntityNotFoundException;
|
||||
use models\exceptions\ValidationException;
|
||||
use OAuth2\Repositories\IApiScopeRepository;
|
||||
use OAuth2\Repositories\IClientRepository;
|
||||
use OpenId\Services\IUserService;
|
||||
@ -224,28 +227,70 @@ final class UserController extends OpenIdController
|
||||
return $this->login_strategy->cancelLogin();
|
||||
}
|
||||
|
||||
use JsonResponses;
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\JsonResponse|mixed
|
||||
*/
|
||||
public function getAccount(){
|
||||
try {
|
||||
$email = Request::input("email", "");
|
||||
if(empty($email)){
|
||||
throw new ValidationException("empty email.");
|
||||
}
|
||||
|
||||
$user = $this->auth_service->getUserByUsername($email);
|
||||
|
||||
if(is_null($user))
|
||||
throw new EntityNotFoundException();
|
||||
|
||||
return $this->ok(
|
||||
[
|
||||
'pic' => $user->getPic(),
|
||||
'full_name' => $user->getFullName()
|
||||
]
|
||||
);
|
||||
}
|
||||
catch (ValidationException $ex){
|
||||
Log::warning($ex);
|
||||
return $this->error412($ex->getMessages());
|
||||
}
|
||||
catch (EntityNotFoundException $ex){
|
||||
Log::warning($ex);
|
||||
return $this->error404();
|
||||
}
|
||||
catch (Exception $ex){
|
||||
Log::error($ex);
|
||||
return $this->error500($ex);
|
||||
}
|
||||
}
|
||||
|
||||
public function postLogin()
|
||||
{
|
||||
$max_login_attempts_2_show_captcha = $this->server_configuration_service->getConfigValue("MaxFailed.LoginAttempts.2ShowCaptcha");
|
||||
$login_attempts = 0;
|
||||
$username = '';
|
||||
try {
|
||||
$user = null;
|
||||
try
|
||||
{
|
||||
|
||||
$data = Request::all();
|
||||
|
||||
if (isset($data['username']))
|
||||
$data['username'] = trim($data['username']);
|
||||
if (isset($data['password']))
|
||||
|
||||
if(isset($data['password']))
|
||||
$data['password'] = trim($data['password']);
|
||||
|
||||
$login_attempts = intval(Request::input('login_attempts'));
|
||||
// Build the validation constraint set.
|
||||
$rules = array
|
||||
(
|
||||
$rules = [
|
||||
'username' => 'required|email',
|
||||
'password' => 'required',
|
||||
);
|
||||
if ($login_attempts >= $max_login_attempts_2_show_captcha) {
|
||||
];
|
||||
|
||||
if ($login_attempts >= $max_login_attempts_2_show_captcha)
|
||||
{
|
||||
$rules['g-recaptcha-response'] = 'required|recaptcha';
|
||||
}
|
||||
// Create a new validator instance.
|
||||
@ -255,9 +300,10 @@ final class UserController extends OpenIdController
|
||||
$username = $data['username'];
|
||||
$password = $data['password'];
|
||||
$remember = Request::input("remember");
|
||||
|
||||
$remember = !is_null($remember);
|
||||
if ($this->auth_service->login($username, $password, $remember)) {
|
||||
|
||||
if ($this->auth_service->login($username, $password, $remember))
|
||||
{
|
||||
return $this->login_strategy->postLogin();
|
||||
}
|
||||
|
||||
@ -270,36 +316,56 @@ final class UserController extends OpenIdController
|
||||
|
||||
return $this->login_strategy->errorLogin
|
||||
(
|
||||
array
|
||||
(
|
||||
[
|
||||
'max_login_attempts_2_show_captcha' => $max_login_attempts_2_show_captcha,
|
||||
'login_attempts' => $login_attempts,
|
||||
'error_message' => "We are sorry, your username or password does not match an existing record.",
|
||||
'user_fullname' => $user->getFullName(),
|
||||
'user_pic' => $user->getPic(),
|
||||
'user_verified' => true,
|
||||
'username' => $username,
|
||||
'error_message' => "We are sorry, your username or password does not match an existing record."
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// validator errors
|
||||
return $this->login_strategy->errorLogin
|
||||
(
|
||||
array
|
||||
(
|
||||
$response_data = [
|
||||
'max_login_attempts_2_show_captcha' => $max_login_attempts_2_show_captcha,
|
||||
'login_attempts' => $login_attempts,
|
||||
'validator' => $validator
|
||||
)
|
||||
];
|
||||
|
||||
if(!is_null($user)){
|
||||
$response_data['user_fullname'] = $user->getFullName();
|
||||
$response_data['user_pic'] = $user->getPic();
|
||||
$response_data['user_verified'] = true;
|
||||
}
|
||||
|
||||
return $this->login_strategy->errorLogin
|
||||
(
|
||||
$response_data
|
||||
);
|
||||
} catch (UnverifiedEmailMemberException $ex1) {
|
||||
Log::warning($ex1);
|
||||
return $this->login_strategy->errorLogin
|
||||
(
|
||||
array
|
||||
(
|
||||
|
||||
$user = $this->auth_service->getUserByUsername($username);
|
||||
|
||||
$response_data = [
|
||||
'max_login_attempts_2_show_captcha' => $max_login_attempts_2_show_captcha,
|
||||
'login_attempts' => $login_attempts,
|
||||
'username' => $username,
|
||||
'error_message' => $ex1->getMessage()
|
||||
)
|
||||
];
|
||||
|
||||
if(!is_null($user)){
|
||||
$response_data['user_fullname'] = $user->getFullName();
|
||||
$response_data['user_pic'] = $user->getPic();
|
||||
$response_data['user_verified'] = true;
|
||||
}
|
||||
|
||||
return $this->login_strategy->errorLogin
|
||||
(
|
||||
$response_data
|
||||
);
|
||||
} catch (AuthenticationException $ex2) {
|
||||
Log::warning($ex2);
|
||||
|
@ -50,6 +50,13 @@ final class EventServiceProvider extends ServiceProvider
|
||||
protected $listen = [
|
||||
'Illuminate\Database\Events\QueryExecuted' => [
|
||||
],
|
||||
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
|
||||
// ... other providers
|
||||
'SocialiteProviders\\Facebook\\FacebookExtendSocialite@handle',
|
||||
'SocialiteProviders\\Google\\GoogleExtendSocialite@handle',
|
||||
'SocialiteProviders\\Apple\\AppleExtendSocialite@handle',
|
||||
'SocialiteProviders\\LinkedIn\\LinkedInExtendSocialite@handle',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -11,6 +11,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use App\libs\Auth\SocialLoginProviders;
|
||||
use Utils\IPHelper;
|
||||
use Services\IUserActionService;
|
||||
use Utils\Services\IAuthService;
|
||||
@ -44,15 +46,26 @@ class DefaultLoginStrategy implements ILoginStrategy
|
||||
public function getLogin()
|
||||
{
|
||||
if (Auth::guest())
|
||||
return View::make("auth.login");
|
||||
return View::make("auth.login", [
|
||||
'supported_providers' => SocialLoginProviders::buildSupportedProviders()
|
||||
]);
|
||||
return Redirect::action("UserController@getProfile");
|
||||
}
|
||||
|
||||
public function postLogin()
|
||||
public function postLogin(array $params = [])
|
||||
{
|
||||
$user = $this->auth_service->getCurrentUser();
|
||||
$identifier = $user->getIdentifier();
|
||||
$this->user_action_service->addUserAction($this->auth_service->getCurrentUser()->getId(), IPHelper::getUserIp(), IUserActionService::LoginAction);
|
||||
$realm = "From Site";
|
||||
if(isset($params['provider']))
|
||||
$realm .= " using ".strtoupper($params['provider']);
|
||||
$this->user_action_service->addUserAction
|
||||
(
|
||||
$this->auth_service->getCurrentUser()->getId(),
|
||||
IPHelper::getUserIp(),
|
||||
IUserActionService::LoginAction,
|
||||
$realm
|
||||
);
|
||||
$default_url = URL::action("UserController@getIdentity", array("identifier" => $identifier));
|
||||
return Redirect::intended($default_url);
|
||||
}
|
||||
@ -68,11 +81,11 @@ class DefaultLoginStrategy implements ILoginStrategy
|
||||
*/
|
||||
public function errorLogin(array $params)
|
||||
{
|
||||
$response = Redirect::action('UserController@getLogin')
|
||||
->with('max_login_attempts_2_show_captcha', $params['max_login_attempts_2_show_captcha'])
|
||||
->with('login_attempts', $params['login_attempts']);
|
||||
if(isset($params['username']))
|
||||
$response= $response->with('username', $params['username']);
|
||||
$response = Redirect::action('UserController@getLogin');
|
||||
|
||||
foreach ($params as $key => $val)
|
||||
$response = $response->with($key, $val);
|
||||
|
||||
if(isset($params['error_message']))
|
||||
$response = $response->with('flash_notice', $params['error_message']);
|
||||
if(isset($params['validator']))
|
||||
|
@ -11,9 +11,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use App\libs\Auth\SocialLoginProviders;
|
||||
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
|
||||
/**
|
||||
* Class DisplayResponseUserAgentStrategy
|
||||
* @package Strategies
|
||||
@ -36,6 +39,12 @@ class DisplayResponseUserAgentStrategy implements IDisplayResponseStrategy
|
||||
*/
|
||||
public function getLoginResponse(array $data = [])
|
||||
{
|
||||
$provider = $data["provider"] ?? null;
|
||||
|
||||
if(!empty($provider)) {
|
||||
return redirect()->route('social_login', ['provider' => $provider]);
|
||||
}
|
||||
$data['supported_providers'] = SocialLoginProviders::buildSupportedProviders();
|
||||
return Response::view("auth.login", $data, 200);
|
||||
}
|
||||
|
||||
@ -45,12 +54,11 @@ class DisplayResponseUserAgentStrategy implements IDisplayResponseStrategy
|
||||
*/
|
||||
public function getLoginErrorResponse(array $data = [])
|
||||
{
|
||||
$response = Redirect::action('UserController@getLogin')
|
||||
->with('max_login_attempts_2_show_captcha', $data['max_login_attempts_2_show_captcha'])
|
||||
->with('login_attempts', $data['login_attempts']);
|
||||
$response = Redirect::action('UserController@getLogin');
|
||||
|
||||
foreach ($data as $key => $val)
|
||||
$response= $response->with($key, $val);
|
||||
|
||||
if(isset($data['username']))
|
||||
$response= $response->with('username', $data['username']);
|
||||
if(isset($data['error_message']))
|
||||
$response = $response->with('flash_notice', $data['error_message']);
|
||||
if(isset($data['validator']))
|
||||
|
@ -11,9 +11,10 @@ interface ILoginStrategy
|
||||
public function getLogin();
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
* @return mixed
|
||||
*/
|
||||
public function postLogin();
|
||||
public function postLogin(array $params = []);
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
|
23
app/Strategies/ILoginStrategyFactory.php
Normal file
23
app/Strategies/ILoginStrategyFactory.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php namespace Strategies;
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Strategies\ILoginStrategy;
|
||||
|
||||
/**
|
||||
* Interface ILoginStrategyFactory
|
||||
* @package Strategies
|
||||
*/
|
||||
interface ILoginStrategyFactory
|
||||
{
|
||||
public function build():ILoginStrategy;
|
||||
}
|
112
app/Strategies/LoginStrategyFactory.php
Normal file
112
app/Strategies/LoginStrategyFactory.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php namespace Strategies;
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use App\Services\Auth\IUserService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use OAuth2\Services\IMementoOAuth2SerializerService;
|
||||
use OAuth2\Services\ISecurityContextService;
|
||||
use OpenId\Services\IMementoOpenIdSerializerService;
|
||||
use Services\IUserActionService;
|
||||
use Utils\Services\IAuthService;
|
||||
|
||||
/**
|
||||
* Class LoginStrategyFactory
|
||||
* @package Strategies
|
||||
*/
|
||||
final class LoginStrategyFactory implements ILoginStrategyFactory
|
||||
{
|
||||
/**
|
||||
* @var IMementoOpenIdSerializerService
|
||||
*/
|
||||
private $openid_memento_service;
|
||||
/**
|
||||
* @var IMementoOAuth2SerializerService
|
||||
*/
|
||||
private $oauth2_memento_service;
|
||||
/**
|
||||
* @var IAuthService
|
||||
*/
|
||||
private $auth_service;
|
||||
|
||||
/**
|
||||
* @var IUserService
|
||||
*/
|
||||
private $user_service;
|
||||
|
||||
/**
|
||||
* @var IUserActionService
|
||||
*/
|
||||
private $user_action_service;
|
||||
|
||||
/**
|
||||
* @var ISecurityContextService
|
||||
*/
|
||||
private $security_context_service;
|
||||
|
||||
/**
|
||||
* LoginStrategyFactory constructor.
|
||||
* @param IMementoOpenIdSerializerService $openid_memento_service
|
||||
* @param IMementoOAuth2SerializerService $oauth2_memento_service
|
||||
* @param IAuthService $auth_service
|
||||
* @param IUserService $user_service
|
||||
* @param IUserActionService $user_action_service
|
||||
* @param ISecurityContextService $security_context_service
|
||||
*/
|
||||
public function __construct
|
||||
(
|
||||
IMementoOpenIdSerializerService $openid_memento_service,
|
||||
IMementoOAuth2SerializerService $oauth2_memento_service,
|
||||
IAuthService $auth_service,
|
||||
IUserService $user_service,
|
||||
IUserActionService $user_action_service,
|
||||
ISecurityContextService $security_context_service)
|
||||
{
|
||||
$this->openid_memento_service = $openid_memento_service;
|
||||
$this->oauth2_memento_service = $oauth2_memento_service;
|
||||
$this->auth_service = $auth_service;
|
||||
$this->user_service = $user_service;
|
||||
$this->user_action_service = $user_action_service;
|
||||
$this->security_context_service = $security_context_service;
|
||||
}
|
||||
|
||||
public function build():ILoginStrategy{
|
||||
$res = null;
|
||||
Log::debug(sprintf("LoginStrategyFactory::build"));
|
||||
if ($this->openid_memento_service->exists())
|
||||
{
|
||||
//openid stuff
|
||||
Log::debug(sprintf("LoginStrategyFactory::build OIDC"));
|
||||
return new OpenIdLoginStrategy
|
||||
(
|
||||
$this->openid_memento_service,
|
||||
$this->user_action_service,
|
||||
$this->auth_service
|
||||
);
|
||||
|
||||
}
|
||||
else if ($this->oauth2_memento_service->exists())
|
||||
{
|
||||
Log::debug(sprintf("LoginStrategyFactory::build OAUTH2"));
|
||||
return new OAuth2LoginStrategy
|
||||
(
|
||||
$this->auth_service,
|
||||
$this->oauth2_memento_service,
|
||||
$this->user_action_service,
|
||||
$this->security_context_service
|
||||
);
|
||||
}
|
||||
//default stuff
|
||||
Log::debug(sprintf("LoginStrategyFactory::build DEFAULT"));
|
||||
return new DefaultLoginStrategy($this->user_action_service, $this->auth_service);
|
||||
}
|
||||
}
|
@ -76,10 +76,12 @@ class OAuth2LoginStrategy extends DefaultLoginStrategy
|
||||
|
||||
$response_strategy = DisplayResponseStrategyFactory::build($auth_request->getDisplay());
|
||||
|
||||
return $response_strategy->getLoginResponse();
|
||||
return $response_strategy->getLoginResponse([
|
||||
'provider' => $auth_request->getProvider()
|
||||
]);
|
||||
}
|
||||
|
||||
public function postLogin()
|
||||
public function postLogin(array $params = [])
|
||||
{
|
||||
$auth_request = OAuth2AuthorizationRequestFactory::getInstance()->build(
|
||||
OAuth2Message::buildFromMemento(
|
||||
@ -87,8 +89,12 @@ class OAuth2LoginStrategy extends DefaultLoginStrategy
|
||||
)
|
||||
);
|
||||
|
||||
$realm = "From ".$auth_request->getRedirectUri();
|
||||
if(isset($params['provider']))
|
||||
$realm .= " using ".strtoupper($params['provider']);
|
||||
|
||||
$this->user_action_service->addUserAction($this->auth_service->getCurrentUser()->getId(), IPHelper::getUserIp(),
|
||||
IUserActionService::LoginAction, $auth_request->getRedirectUri());
|
||||
IUserActionService::LoginAction, $realm);
|
||||
|
||||
return Redirect::action("OAuth2\OAuth2ProviderController@auth");
|
||||
}
|
||||
|
@ -11,6 +11,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use App\libs\Auth\SocialLoginProviders;
|
||||
use OpenId\OpenIdMessage;
|
||||
use OpenId\OpenIdProtocol;
|
||||
use OpenId\Requests\OpenIdAuthenticationRequest;
|
||||
@ -62,23 +64,27 @@ final class OpenIdLoginStrategy extends DefaultLoginStrategy
|
||||
} else {
|
||||
$params['identity_select'] = true;
|
||||
}
|
||||
|
||||
$params['supported_providers'] = SocialLoginProviders::buildSupportedProviders();
|
||||
return View::make("auth.login", $params);
|
||||
}
|
||||
return Redirect::action("UserController@getProfile");
|
||||
}
|
||||
|
||||
public function postLogin()
|
||||
public function postLogin(array $params = [])
|
||||
{
|
||||
//go to authentication flow again
|
||||
$msg = OpenIdMessage::buildFromMemento($this->memento_service->load());
|
||||
|
||||
$realm = "From ". $msg->getParam(OpenIdProtocol::OpenIDProtocol_Realm);
|
||||
if(isset($params['provider']))
|
||||
$realm .= " using ".strtoupper($params['provider']);
|
||||
|
||||
$this->user_action_service->addUserAction
|
||||
(
|
||||
$this->auth_service->getCurrentUser()->getId(),
|
||||
IPHelper::getUserIp(),
|
||||
IUserActionService::LoginAction,
|
||||
$msg->getParam(OpenIdProtocol::OpenIDProtocol_Realm)
|
||||
$realm
|
||||
);
|
||||
|
||||
return Redirect::action("OpenId\OpenIdProviderController@endpoint");
|
||||
|
@ -48,6 +48,10 @@ final class StrategyProvider extends ServiceProvider implements DeferrableProvid
|
||||
// authentication strategies
|
||||
App::singleton(OAuth2ServiceCatalog::AuthenticationStrategy, \Strategies\OAuth2AuthenticationStrategy::class);
|
||||
App::singleton(OpenIdServiceCatalog::AuthenticationStrategy, \Strategies\OpenIdAuthenticationStrategy::class);
|
||||
|
||||
// factories
|
||||
|
||||
App::singleton(ILoginStrategyFactory::class, LoginStrategyFactory::class);
|
||||
}
|
||||
|
||||
public function provides()
|
||||
@ -61,6 +65,7 @@ final class StrategyProvider extends ServiceProvider implements DeferrableProvid
|
||||
OAuth2IndirectFragmentResponse::OAuth2IndirectFragmentResponse,
|
||||
OAuth2ServiceCatalog::AuthenticationStrategy,
|
||||
OpenIdServiceCatalog::AuthenticationStrategy,
|
||||
ILoginStrategyFactory::class,
|
||||
];
|
||||
}
|
||||
}
|
@ -166,6 +166,18 @@ final class UserFactory
|
||||
if(isset($payload['email_verified']) && boolval($payload['email_verified']) === true && !$user->isEmailVerified())
|
||||
$user->verifyEmail();
|
||||
|
||||
if(isset($payload['full_name']))
|
||||
$user->setFullName(trim($payload['full_name']));
|
||||
|
||||
if(isset($payload['external_id']))
|
||||
$user->setExternalId(trim($payload['external_id']));
|
||||
|
||||
if(isset($payload['external_pic']))
|
||||
$user->setExternalPic(trim($payload['external_pic']));
|
||||
|
||||
if(isset($payload['external_provider']))
|
||||
$user->setExternalProvider(trim($payload['external_provider']));
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
@ -306,6 +306,24 @@ class User extends BaseEntity
|
||||
*/
|
||||
private $pic;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="external_id", type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $external_id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="external_provider", type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $external_provider;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="external_pic", type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $external_pic;
|
||||
|
||||
// relations
|
||||
|
||||
/**
|
||||
@ -412,6 +430,9 @@ class User extends BaseEntity
|
||||
$this->spam_type = self::SpamTypeNone;
|
||||
$this->company = null;
|
||||
$this->phone_number = null;
|
||||
$this->external_id = null;
|
||||
$this->external_provider = null;
|
||||
$this->external_pic = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -823,6 +844,9 @@ class User extends BaseEntity
|
||||
if (!empty($this->pic)) {
|
||||
return Storage::disk('swift')->url(sprintf("%s/%s", self::getProfilePicFolder(), $this->pic));
|
||||
}
|
||||
if(!empty($this->external_pic))
|
||||
return $this->external_pic;
|
||||
|
||||
return $this->getGravatarUrl();
|
||||
}
|
||||
catch (\Exception $ex) {
|
||||
@ -1780,4 +1804,61 @@ SQL;
|
||||
$this->job_title = $job_title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getExternalProvider(): ?string
|
||||
{
|
||||
return $this->external_provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $external_provider
|
||||
*/
|
||||
public function setExternalProvider(string $external_provider): void
|
||||
{
|
||||
$this->external_provider = $external_provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getExternalPic(): ?string
|
||||
{
|
||||
return $this->external_pic;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $external_pic
|
||||
*/
|
||||
public function setExternalPic(string $external_pic): void
|
||||
{
|
||||
$this->external_pic = $external_pic;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getExternalId(): string
|
||||
{
|
||||
return $this->external_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $external_id
|
||||
*/
|
||||
public function setExternalId(string $external_id): void
|
||||
{
|
||||
$this->external_id = $external_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $full_name
|
||||
*/
|
||||
public function setFullName(string $full_name):void{
|
||||
$name_parts = explode(" ", $full_name);
|
||||
$this->first_name = $name_parts[0];
|
||||
$this->last_name = $name_parts[1];
|
||||
}
|
||||
|
||||
}
|
52
app/libs/Auth/SocialLoginProviders.php
Normal file
52
app/libs/Auth/SocialLoginProviders.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php namespace App\libs\Auth;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
/**
|
||||
* Class SocialLoginProviders
|
||||
* @package App\libs\Auth
|
||||
*/
|
||||
final class SocialLoginProviders
|
||||
{
|
||||
const Facebook = "facebook";
|
||||
const Apple = "apple";
|
||||
const LinkedIn = "linkedin";
|
||||
const Google = "google";
|
||||
|
||||
const ValidProviders = [
|
||||
self::Facebook,
|
||||
self::Apple,
|
||||
self::LinkedIn,
|
||||
self::Google
|
||||
];
|
||||
|
||||
/**
|
||||
* @param string $provider
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSupportedProvider(string $provider):bool{
|
||||
return in_array($provider, self::ValidProviders);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public static function buildSupportedProviders():array{
|
||||
return [
|
||||
self::Facebook => "Facebook",
|
||||
self::Apple => "Apple",
|
||||
self::LinkedIn => "LinkedIn",
|
||||
self::Google => "Google",
|
||||
];
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
<?php namespace OAuth2\Discovery;
|
||||
use App\libs\Auth\SocialLoginProviders;
|
||||
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -254,6 +256,15 @@ final class DiscoveryDocumentBuilder
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function addAvailableThirdPartyIdentityProviders(){
|
||||
foreach(SocialLoginProviders::ValidProviders as $provider)
|
||||
$this->addArrayValue("third_party_identity_providers", $provider);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
|
@ -231,6 +231,11 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
||||
// http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||
const OAuth2Protocol_Nonce = 'nonce';
|
||||
|
||||
/**
|
||||
* custom param - social login
|
||||
*/
|
||||
const OAuth2Protocol_Provider = 'provider';
|
||||
|
||||
/**
|
||||
* Time when the End-User authentication occurred. Its value is a JSON number representing the number of seconds
|
||||
* from 1970-01-01T0:0:0Z as measured in UTC until the date/time. When a max_age request is made or when auth_time
|
||||
@ -1403,6 +1408,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
||||
->addDisplayValueSupported(self::OAuth2Protocol_Display_Touch)
|
||||
->addDisplayValueSupported(self::OAuth2Protocol_Display_Wap)
|
||||
->addDisplayValueSupported(self::OAuth2Protocol_Display_Native)
|
||||
->addAvailableThirdPartyIdentityProviders()
|
||||
->render();
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,7 @@ class OAuth2AuthenticationRequest extends OAuth2AuthorizationRequest
|
||||
OAuth2Protocol::OAuth2Protocol_IDTokenHint,
|
||||
OAuth2Protocol::OAuth2Protocol_LoginHint,
|
||||
OAuth2Protocol::OAuth2Protocol_ACRValues,
|
||||
OAuth2Protocol::OAuth2Protocol_Provider,
|
||||
);
|
||||
|
||||
/**
|
||||
@ -111,6 +112,10 @@ class OAuth2AuthenticationRequest extends OAuth2AuthorizationRequest
|
||||
return str_contains($this->getScope(), OAuth2Protocol::OfflineAccess_Scope);
|
||||
}
|
||||
|
||||
public function getProvider():?string{
|
||||
return $this->getParam(OAuth2Protocol::OAuth2Protocol_Provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OAuth2AuthorizationRequest $auth_request
|
||||
*/
|
||||
|
26
babel.config.js
Normal file
26
babel.config.js
Normal file
@ -0,0 +1,26 @@
|
||||
// babel.config.js
|
||||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"edge": "17",
|
||||
"firefox": "60",
|
||||
"chrome": "67",
|
||||
"safari": "11.1",
|
||||
"node":"current"
|
||||
},
|
||||
"useBuiltIns": "usage",
|
||||
"corejs": "3.9.1"
|
||||
}
|
||||
],
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-flow"
|
||||
],
|
||||
plugins: [
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
]
|
||||
};
|
||||
|
@ -29,31 +29,36 @@
|
||||
"php": "^7.3|^8.0",
|
||||
"ext-json": "*",
|
||||
"ext-pdo": "*",
|
||||
"laravel/framework": "^8.0",
|
||||
"laravel/helpers": "^1.4",
|
||||
"laravel/tinker": "^2.5",
|
||||
"laravelcollective/html": "6.2.*",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
"laravel-doctrine/orm": "1.7.*",
|
||||
"laravel-doctrine/extensions": "1.4.*",
|
||||
"laravel-doctrine/migrations": "2.3.*",
|
||||
"beberlei/doctrineextensions": "1.3.*",
|
||||
"behat/transliterator": "^1.2",
|
||||
"vladimir-yuldashev/laravel-queue-rabbitmq": "v11.1.*",
|
||||
"s-ichikawa/laravel-sendgrid-driver": "~3.0",
|
||||
"ezyang/htmlpurifier": "v4.12.0",
|
||||
"fideloper/proxy": "^4.4",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
"get-stream/stream-chat": "^1.1",
|
||||
"glenscott/url-normalizer": "1.4.0",
|
||||
"greggilbert/recaptcha": "dev-feature/laravel8.x",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"ircmaxell/random-lib": "1.1.0",
|
||||
"jenssegers/agent": "2.6.3",
|
||||
"laravel-doctrine/extensions": "1.4.*",
|
||||
"laravel-doctrine/migrations": "2.3.*",
|
||||
"laravel-doctrine/orm": "1.7.*",
|
||||
"laravel/framework": "^8.0",
|
||||
"laravel/helpers": "^1.4",
|
||||
"laravel/socialite": "^5.2",
|
||||
"laravel/tinker": "^2.5",
|
||||
"laravelcollective/html": "6.2.*",
|
||||
"php-opencloud/openstack": "dev-feature/guzzle_7_x",
|
||||
"phpseclib/phpseclib": "2.0.11",
|
||||
"predis/predis": "v1.1.6",
|
||||
"s-ichikawa/laravel-sendgrid-driver": "~3.0",
|
||||
"smarcet/jose4php": "1.0.17",
|
||||
"socialiteproviders/apple": "^5.0",
|
||||
"socialiteproviders/facebook": "^4.1",
|
||||
"socialiteproviders/google": "^4.1",
|
||||
"socialiteproviders/linkedin": "^4.1",
|
||||
"sokil/php-isocodes": "^3.0",
|
||||
"vladimir-yuldashev/laravel-queue-rabbitmq": "v11.1.*",
|
||||
"zendframework/zend-crypt": "3.3.0",
|
||||
"zendframework/zend-math": "3.1.1"
|
||||
},
|
||||
|
538
composer.lock
generated
538
composer.lock
generated
@ -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": "c28f890e5a6afe1c57a1e232f3be5e5c",
|
||||
"content-hash": "eaa4d26a199112e2689bbd94bb8e0715",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asm89/stack-cors",
|
||||
@ -3362,6 +3362,75 @@
|
||||
},
|
||||
"time": "2021-02-16T15:27:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/socialite",
|
||||
"version": "v5.2.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/socialite.git",
|
||||
"reference": "1960802068f81e44b2ae9793932181cf1cb91b5c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/socialite/zipball/1960802068f81e44b2ae9793932181cf1cb91b5c",
|
||||
"reference": "1960802068f81e44b2ae9793932181cf1cb91b5c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/guzzle": "^6.0|^7.0",
|
||||
"illuminate/http": "^6.0|^7.0|^8.0",
|
||||
"illuminate/support": "^6.0|^7.0|^8.0",
|
||||
"league/oauth1-client": "^1.0",
|
||||
"php": "^7.2|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"illuminate/contracts": "^6.0|^7.0",
|
||||
"mockery/mockery": "^1.0",
|
||||
"orchestra/testbench": "^4.0|^5.0|^6.0",
|
||||
"phpunit/phpunit": "^8.0|^9.3"
|
||||
},
|
||||
"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": "2021-04-06T14:38:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/tinker",
|
||||
"version": "v2.6.1",
|
||||
@ -3502,6 +3571,141 @@
|
||||
},
|
||||
"time": "2020-12-15T20:20:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "lcobucci/clock",
|
||||
"version": "2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/lcobucci/clock.git",
|
||||
"reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/lcobucci/clock/zipball/353d83fe2e6ae95745b16b3d911813df6a05bfb3",
|
||||
"reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"infection/infection": "^0.17",
|
||||
"lcobucci/coding-standard": "^6.0",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpstan/phpstan-deprecation-rules": "^0.12",
|
||||
"phpstan/phpstan-phpunit": "^0.12",
|
||||
"phpstan/phpstan-strict-rules": "^0.12",
|
||||
"phpunit/php-code-coverage": "9.1.4",
|
||||
"phpunit/phpunit": "9.3.7"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Lcobucci\\Clock\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Luís Cobucci",
|
||||
"email": "lcobucci@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Yet another clock abstraction",
|
||||
"support": {
|
||||
"issues": "https://github.com/lcobucci/clock/issues",
|
||||
"source": "https://github.com/lcobucci/clock/tree/2.0.x"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/lcobucci",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/lcobucci",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2020-08-27T18:56:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "lcobucci/jwt",
|
||||
"version": "4.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/lcobucci/jwt.git",
|
||||
"reference": "71cf170102c8371ccd933fa4df6252086d144de6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/71cf170102c8371ccd933fa4df6252086d144de6",
|
||||
"reference": "71cf170102c8371ccd933fa4df6252086d144de6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-hash": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-sodium": "*",
|
||||
"lcobucci/clock": "^2.0",
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"infection/infection": "^0.21",
|
||||
"lcobucci/coding-standard": "^6.0",
|
||||
"mikey179/vfsstream": "^1.6.7",
|
||||
"phpbench/phpbench": "^1.0@alpha",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpstan/phpstan-deprecation-rules": "^0.12",
|
||||
"phpstan/phpstan-phpunit": "^0.12",
|
||||
"phpstan/phpstan-strict-rules": "^0.12",
|
||||
"phpunit/php-invoker": "^3.1",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Lcobucci\\JWT\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Luís Cobucci",
|
||||
"email": "lcobucci@gmail.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A simple library to work with JSON Web Token and JSON Web Signature",
|
||||
"keywords": [
|
||||
"JWS",
|
||||
"jwt"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/lcobucci/jwt/issues",
|
||||
"source": "https://github.com/lcobucci/jwt/tree/4.1.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/lcobucci",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/lcobucci",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2021-03-23T23:53:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/commonmark",
|
||||
"version": "1.5.7",
|
||||
@ -3754,6 +3958,81 @@
|
||||
],
|
||||
"time": "2021-01-18T20:58:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/oauth1-client",
|
||||
"version": "v1.9.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/oauth1-client.git",
|
||||
"reference": "1e7e6be2dc543bf466236fb171e5b20e1b06aee6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/1e7e6be2dc543bf466236fb171e5b20e1b06aee6",
|
||||
"reference": "1e7e6be2dc543bf466236fb171e5b20e1b06aee6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"ext-openssl": "*",
|
||||
"guzzlehttp/guzzle": "^6.0|^7.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.9.0"
|
||||
},
|
||||
"time": "2021-01-20T01:40:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mobiledetect/mobiledetectlib",
|
||||
"version": "2.8.37",
|
||||
@ -5307,6 +5586,263 @@
|
||||
},
|
||||
"time": "2019-07-12T23:35:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "socialiteproviders/apple",
|
||||
"version": "5.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/SocialiteProviders/Apple.git",
|
||||
"reference": "3a626202628b27fbe8500251cd609a9062a00f9e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/SocialiteProviders/Apple/zipball/3a626202628b27fbe8500251cd609a9062a00f9e",
|
||||
"reference": "3a626202628b27fbe8500251cd609a9062a00f9e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"ext-openssl": "*",
|
||||
"firebase/php-jwt": "^5.2",
|
||||
"lcobucci/jwt": "^4.0",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"socialiteproviders/manager": "~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ahilmurugesan/socialite-apple-helper": "Automatic Apple client key generation and management."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SocialiteProviders\\Apple\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ahilan",
|
||||
"email": "ahilmurugesan@gmail.com",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Vamsi Krishna V",
|
||||
"email": "vamsi@vonectech.com",
|
||||
"homepage": "https://vonectech.com/",
|
||||
"role": "Farmer"
|
||||
}
|
||||
],
|
||||
"description": "Apple OAuth2 Provider for Laravel Socialite",
|
||||
"keywords": [
|
||||
"apple",
|
||||
"apple client key",
|
||||
"apple sign in",
|
||||
"client key generator",
|
||||
"client key refresh",
|
||||
"laravel",
|
||||
"laravel apple",
|
||||
"laravel socialite",
|
||||
"sign in with apple",
|
||||
"socialite",
|
||||
"socialite apple"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/SocialiteProviders/Apple/tree/5.0.2"
|
||||
},
|
||||
"time": "2021-03-25T22:34:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "socialiteproviders/facebook",
|
||||
"version": "4.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/SocialiteProviders/Facebook.git",
|
||||
"reference": "9b94a9334b5d0f61de8f5a20928d63d4d8f4e00d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/SocialiteProviders/Facebook/zipball/9b94a9334b5d0f61de8f5a20928d63d4d8f4e00d",
|
||||
"reference": "9b94a9334b5d0f61de8f5a20928d63d4d8f4e00d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": "^7.2 || ^8.0",
|
||||
"socialiteproviders/manager": "~4.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SocialiteProviders\\Facebook\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oleksandr Prypkhan (Alex Wells)",
|
||||
"email": "autaut03@googlemail.com"
|
||||
}
|
||||
],
|
||||
"description": "Facebook (facebook.com) OAuth2 Provider for Laravel Socialite",
|
||||
"support": {
|
||||
"source": "https://github.com/SocialiteProviders/Facebook/tree/4.1.0"
|
||||
},
|
||||
"time": "2020-12-01T23:10:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "socialiteproviders/google",
|
||||
"version": "4.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/SocialiteProviders/Google-Plus.git",
|
||||
"reference": "1cb8f6fb2c0dd0fc8b34e95f69865663fdf0b401"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/SocialiteProviders/Google-Plus/zipball/1cb8f6fb2c0dd0fc8b34e95f69865663fdf0b401",
|
||||
"reference": "1cb8f6fb2c0dd0fc8b34e95f69865663fdf0b401",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": "^7.2 || ^8.0",
|
||||
"socialiteproviders/manager": "~4.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SocialiteProviders\\Google\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "xstoop",
|
||||
"email": "myenglishnameisx@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Google OAuth2 Provider for Laravel Socialite",
|
||||
"support": {
|
||||
"source": "https://github.com/SocialiteProviders/Google-Plus/tree/4.1.0"
|
||||
},
|
||||
"time": "2020-12-01T23:10:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "socialiteproviders/linkedin",
|
||||
"version": "4.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/SocialiteProviders/LinkedIn.git",
|
||||
"reference": "bcf8a732328e868261416a5f04135acb0b94bf9a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/SocialiteProviders/LinkedIn/zipball/bcf8a732328e868261416a5f04135acb0b94bf9a",
|
||||
"reference": "bcf8a732328e868261416a5f04135acb0b94bf9a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": "^7.2 || ^8.0",
|
||||
"socialiteproviders/manager": "~4.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SocialiteProviders\\LinkedIn\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Brian Faust",
|
||||
"email": "hello@brianfaust.de"
|
||||
}
|
||||
],
|
||||
"description": "LinkedIn OAuth2 Provider for Laravel Socialite",
|
||||
"support": {
|
||||
"source": "https://github.com/SocialiteProviders/LinkedIn/tree/4.1.0"
|
||||
},
|
||||
"time": "2020-12-01T23:10:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "socialiteproviders/manager",
|
||||
"version": "4.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/SocialiteProviders/Manager.git",
|
||||
"reference": "0f5e82af0404df0080bdc5c105cef936c1711524"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/0f5e82af0404df0080bdc5c105cef936c1711524",
|
||||
"reference": "0f5e82af0404df0080bdc5c105cef936c1711524",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/support": "^6.0|^7.0|^8.0",
|
||||
"laravel/socialite": "~4.0|~5.0",
|
||||
"php": "^7.2 || ^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/",
|
||||
"support": {
|
||||
"issues": "https://github.com/SocialiteProviders/Manager/issues",
|
||||
"source": "https://github.com/SocialiteProviders/Manager/tree/4.0.1"
|
||||
},
|
||||
"time": "2020-12-01T23:09:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sokil/php-isocodes",
|
||||
"version": "3.3.4",
|
||||
|
@ -170,6 +170,8 @@ return [
|
||||
LaravelDoctrine\Extensions\BeberleiExtensionsServiceProvider::class,
|
||||
\App\Models\Utils\MySQLExtensionsServiceProvider::class,
|
||||
\App\libs\Utils\FileSystem\SwiftServiceProvider::class,
|
||||
// remove 'Laravel\Socialite\SocialiteServiceProvider',
|
||||
\SocialiteProviders\Manager\ServiceProvider::class, // add
|
||||
],
|
||||
|
||||
/*
|
||||
@ -223,6 +225,7 @@ return [
|
||||
'EntityManager' => LaravelDoctrine\ORM\Facades\EntityManager::class,
|
||||
'Registry' => LaravelDoctrine\ORM\Facades\Registry::class,
|
||||
'Doctrine' => LaravelDoctrine\ORM\Facades\Doctrine::class,
|
||||
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
|
||||
],
|
||||
|
||||
'version' => env('APP_VERSION', 'XX.XX.XX'),
|
||||
|
@ -39,4 +39,25 @@ return [
|
||||
'api_key' => env('SENDGRID_API_KEY'),
|
||||
],
|
||||
|
||||
// 3rd party idps
|
||||
'facebook' => [
|
||||
'client_id' => env('FACEBOOK_CLIENT_ID'),
|
||||
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
|
||||
'redirect' => env('FACEBOOK_REDIRECT_URI')
|
||||
],
|
||||
'google' => [
|
||||
'client_id' => env('GOOGLE_CLIENT_ID'),
|
||||
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
|
||||
'redirect' => env('GOOGLE_REDIRECT_URI')
|
||||
],
|
||||
'apple' => [
|
||||
'client_id' => env('APPLE_CLIENT_ID'),
|
||||
'client_secret' => env('APPLE_CLIENT_SECRET'),
|
||||
'redirect' => env('APPLE_REDIRECT_URI')
|
||||
],
|
||||
'linkedin' => [
|
||||
'client_id' => env('LINKEDIN_CLIENT_ID'),
|
||||
'client_secret' => env('LINKEDIN_CLIENT_SECRET'),
|
||||
'redirect' => env('LINKEDIN_REDIRECT_URI')
|
||||
],
|
||||
];
|
||||
|
53
database/migrations/Version20210512181715.php
Normal file
53
database/migrations/Version20210512181715.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php namespace Database\Migrations;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema as Schema;
|
||||
use LaravelDoctrine\Migrations\Schema\Builder;
|
||||
use LaravelDoctrine\Migrations\Schema\Table;
|
||||
/**
|
||||
* Class Version20210512181715
|
||||
* @package Database\Migrations
|
||||
*/
|
||||
class Version20210512181715 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$builder = new Builder($schema);
|
||||
if($schema->hasTable("users") && !$builder->hasColumn("users","external_provider") ) {
|
||||
$builder->table('users', function (Table $table) {
|
||||
$table->string('external_provider')->setNotnull(false)->setLength(254);
|
||||
$table->string('external_id')->setNotnull(false)->setLength(254);
|
||||
$table->string('external_pic')->setNotnull(false)->setLength(254);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$builder = new Builder($schema);
|
||||
if($schema->hasTable("users") && $builder->hasColumn("users","external_provider") ) {
|
||||
$builder->table('users', function (Table $table) {
|
||||
$table->dropColumn('external_provider');
|
||||
$table->dropColumn('external_id');
|
||||
$table->dropColumn('external_pic');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
14069
package-lock.json
generated
14069
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
83
package.json
83
package.json
@ -5,38 +5,77 @@
|
||||
"main": "resources/assets/js/index.js",
|
||||
"author": "smarcet@gmail.com",
|
||||
"scripts": {
|
||||
"build-dev": "./node_modules/.bin/webpack --config webpack.config.js",
|
||||
"build": "NODE_ENV=production ./node_modules/.bin/webpack --config webpack.config.js"
|
||||
"build-dev": "./node_modules/.bin/webpack --config webpack.dev.js",
|
||||
"build": "./node_modules/.bin/webpack --config webpack.prod.js",
|
||||
"serve": "webpack-dev-server --open --port=8888 --https --config webpack.dev.js",
|
||||
"test": "jest --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^3.11.0",
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-loader": "^7.1.4",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-preset-flow": "^6.23.0",
|
||||
"css-loader": "^0.28.10",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "^1.1.11",
|
||||
"json-loader": "^0.5.7",
|
||||
"less": "^2.7.3",
|
||||
"less-loader": "^4.1.0",
|
||||
"postcss-loader": "^2.1.3",
|
||||
"@babel/cli": "^7.13.10",
|
||||
"@babel/core": "^7.13.10",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
|
||||
"@babel/preset-env": "^7.13.12",
|
||||
"@babel/preset-flow": "^7.0.0",
|
||||
"@babel/preset-react": "^7.13.13",
|
||||
"@testing-library/jest-dom": "^5.12.0",
|
||||
"@testing-library/react": "^11.2.6",
|
||||
"babel-jest": "^26.6.3",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-plugin-react-remove-properties": "^0.3.0",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"copy-webpack-plugin": "^6.4.1",
|
||||
"css-loader": "^3.1.0",
|
||||
"dotenv-webpack": "^1.7.0",
|
||||
"file-loader": "^4.1.0",
|
||||
"font-awesome": "4.7.0",
|
||||
"history": "^4.7.2",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"i18n-react": "^0.6.4",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"immutability-helper": "^2.7.1",
|
||||
"jest": "^26.6.3",
|
||||
"mini-css-extract-plugin": "^0.8.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"path": "^0.12.7",
|
||||
"react": "^16.6.3",
|
||||
"react-bootstrap": "^0.33.1",
|
||||
"react-datetime": "^2.15.0",
|
||||
"react-dom": "^16.4.1",
|
||||
"react-redux": "^5.0.7",
|
||||
"react-rte-ref-fix": "^0.16.2",
|
||||
"react-test-renderer": "^17.0.2",
|
||||
"react-transition-group": "^1.2.1",
|
||||
"redux": "^3.7.2",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
"redux-persist": "^5.10.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"sass-loader": "^6.0.7",
|
||||
"style-loader": "^0.19.1",
|
||||
"url-loader": "^0.6.2"
|
||||
"superagent": "^6.1.0",
|
||||
"timesync": "^1.0.5",
|
||||
"urijs": "^1.19.1",
|
||||
"url-loader": "^0.6.2",
|
||||
"webpack": "^4.29.0",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"webpack-dev-server": "^3.1.14",
|
||||
"webpack-merge": "^4.2.1",
|
||||
"webpack-node-externals": "^1.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@github/clipboard-copy-element": "^1.1.2",
|
||||
"@material-ui/core": "^4.11.4",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"bootstrap": "^3.3.7",
|
||||
"bootstrap-datepicker": "^1.8.0",
|
||||
"bootstrap-sass": "^3.0.0",
|
||||
"bootstrap-tagsinput": "^0.7.1",
|
||||
"chosen-js": "^1.8.7",
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"file-loader": "^1.1.11",
|
||||
"font-awesome": "^4.7.0",
|
||||
"jquery": "~2.1.4",
|
||||
"jquery-migrate": "1.2.1",
|
||||
@ -44,11 +83,13 @@
|
||||
"jquery-validation": "^1.17.0",
|
||||
"jquery.cookie": "^1.4.1",
|
||||
"jqueryui": "^1.11.1",
|
||||
"laravel-elixir": "^5.0.0",
|
||||
"jsdom": "^16.5.3",
|
||||
"moment": "^2.24.0",
|
||||
"moment-timezone": "^0.5.21",
|
||||
"popper.js": "^1.14.3",
|
||||
"pure": "^2.85.0",
|
||||
"pwstrength-bootstrap": "^2.2.1",
|
||||
"pwstrength-bootstrap": "^3.0.10",
|
||||
"react-google-recaptcha": "^2.1.0",
|
||||
"simplemde": "^1.11.2",
|
||||
"sweetalert2": "7.3.5",
|
||||
"typeahead.js": "^0.11.1",
|
||||
|
BIN
public/assets/img/third_party_identity_providers/icon_apple.png
Normal file
BIN
public/assets/img/third_party_identity_providers/icon_apple.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 549 B |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
public/assets/img/third_party_identity_providers/icon_google.png
Normal file
BIN
public/assets/img/third_party_identity_providers/icon_google.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 375 B |
70
resources/js/base_actions.js
Normal file
70
resources/js/base_actions.js
Normal file
@ -0,0 +1,70 @@
|
||||
import request from 'superagent';
|
||||
import URI from "urijs";
|
||||
let http = request;
|
||||
import Swal from 'sweetalert2';
|
||||
|
||||
export const createAction = type => payload => ({
|
||||
type,
|
||||
payload
|
||||
});
|
||||
|
||||
export const RESET_LOADING = 'RESET_LOADING';
|
||||
export const START_LOADING = 'START_LOADING';
|
||||
export const STOP_LOADING = 'STOP_LOADING';
|
||||
|
||||
export const resetLoading = createAction(RESET_LOADING);
|
||||
export const startLoading = createAction(START_LOADING);
|
||||
export const stopLoading = createAction(STOP_LOADING);
|
||||
|
||||
const xhrs = {};
|
||||
|
||||
const cancel = (key) => {
|
||||
if(xhrs[key]) {
|
||||
xhrs[key].abort();
|
||||
console.log(`aborted request ${key}`);
|
||||
delete xhrs[key];
|
||||
}
|
||||
}
|
||||
|
||||
const schedule = (key, req) => {
|
||||
// console.log(`scheduling ${key}`);
|
||||
xhrs[key] = req;
|
||||
};
|
||||
|
||||
const isObjectEmpty = (obj) => {
|
||||
return Object.keys(obj).length === 0 && obj.constructor === Object ;
|
||||
}
|
||||
|
||||
export const getRawRequest = (endpoint, errorHandler = null) => (params) => {
|
||||
let url = URI(endpoint);
|
||||
|
||||
if(!isObjectEmpty(params))
|
||||
url = url.query(params);
|
||||
|
||||
let key = url.toString();
|
||||
|
||||
cancel(key);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let req = http.get(url.toString())
|
||||
.timeout({
|
||||
response: 60000,
|
||||
deadline: 60000,
|
||||
})
|
||||
.end(
|
||||
(err, res) => {
|
||||
if (err || !res.ok) {
|
||||
if(errorHandler) {
|
||||
errorHandler(err, res);
|
||||
}
|
||||
return reject({ err, res })
|
||||
}
|
||||
let json = res.body;
|
||||
return resolve({response: json});
|
||||
}
|
||||
)
|
||||
|
||||
schedule(key, req);
|
||||
});
|
||||
}
|
||||
|
20
resources/js/bootstrap.js
vendored
Normal file
20
resources/js/bootstrap.js
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
import 'font-awesome/css/font-awesome.min.css';
|
||||
import 'jquery-ui-themes/themes/ui-darkness/jquery-ui.css';
|
||||
import 'sweetalert2/dist/sweetalert2.css';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import 'bootstrap-datepicker/dist/css/bootstrap-datepicker.css'
|
||||
window._ = require('lodash');
|
||||
|
||||
import $ from 'jquery';
|
||||
window.jQuery = $;
|
||||
window.$ = $;
|
||||
|
||||
import 'bootstrap'
|
||||
import 'jqueryui'
|
||||
import 'jquery-validation';
|
||||
import 'jquery-validation/dist/additional-methods.js';
|
||||
import swal from 'sweetalert2';
|
||||
import 'pure';
|
||||
import 'pwstrength-bootstrap/dist/pwstrength-bootstrap.js';
|
||||
import 'bootstrap-datepicker/dist/js/bootstrap-datepicker.js';
|
||||
|
34
resources/js/components/divider_with_text.js
Normal file
34
resources/js/components/divider_with_text.js
Normal file
@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
import { makeStyles } from "@material-ui/core";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
container: {
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
},
|
||||
border: {
|
||||
borderBottom: "1px solid #3fa2f7",
|
||||
width: "100%"
|
||||
},
|
||||
content: {
|
||||
paddingTop: theme.spacing(0.5),
|
||||
paddingBottom: theme.spacing(0.5),
|
||||
paddingRight: theme.spacing(2),
|
||||
paddingLeft: theme.spacing(2),
|
||||
fontWeight: 500,
|
||||
fontSize: 16,
|
||||
color: "#3fa2f7"
|
||||
}
|
||||
}));
|
||||
|
||||
const DividerWithText = ({ children }) => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<div className={classes.border} />
|
||||
<span className={classes.content}>{children}</span>
|
||||
<div className={classes.border} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default DividerWithText;
|
@ -10,20 +10,4 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
import 'font-awesome/css/font-awesome.min.css';
|
||||
import 'jquery-ui-themes/themes/ui-darkness/jquery-ui.css';
|
||||
import 'sweetalert2/dist/sweetalert2.css';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import 'bootstrap-datepicker/dist/css/bootstrap-datepicker.css'
|
||||
|
||||
import $ from 'jquery';
|
||||
window.jQuery = $;
|
||||
window.$ = $;
|
||||
import 'bootstrap'
|
||||
import 'jqueryui'
|
||||
import 'jquery-validation';
|
||||
import 'jquery-validation/dist/additional-methods.js';
|
||||
import swal from 'sweetalert2';
|
||||
import 'pure';
|
||||
import 'pwstrength-bootstrap/dist/pwstrength-bootstrap.js';
|
||||
import 'bootstrap-datepicker/dist/js/bootstrap-datepicker.js';
|
||||
import './bootstrap';
|
||||
|
12
resources/js/login/actions.js
Normal file
12
resources/js/login/actions.js
Normal file
@ -0,0 +1,12 @@
|
||||
import {getRawRequest} from '../base_actions'
|
||||
|
||||
|
||||
|
||||
export const verifyAccount = (email) => {
|
||||
const params = {
|
||||
email: email
|
||||
};
|
||||
|
||||
return getRawRequest(window.VERIFY_ACCOUNT_ENDPOINT)(params);
|
||||
|
||||
}
|
427
resources/js/login/login.js
Normal file
427
resources/js/login/login.js
Normal file
@ -0,0 +1,427 @@
|
||||
import styles from './login.module.scss'
|
||||
import "./third_party_identity_providers.scss";
|
||||
import React from 'react';
|
||||
import ReCAPTCHA from "react-google-recaptcha";
|
||||
import ReactDOM from 'react-dom';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Link from '@material-ui/core/Link';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Container from '@material-ui/core/Container';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import Checkbox from '@material-ui/core/Checkbox';
|
||||
import {verifyAccount} from './actions';
|
||||
import {MuiThemeProvider, createMuiTheme} from '@material-ui/core/styles';
|
||||
import DividerWithText from '../components/divider_with_text';
|
||||
import Visibility from '@material-ui/icons/Visibility';
|
||||
import VisibilityOff from '@material-ui/icons/VisibilityOff';
|
||||
import InputAdornment from '@material-ui/core/InputAdornment';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import {emailValidator} from '../validator';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
|
||||
|
||||
const EmailInputForm = ({onValidateEmail, onHandleUserNameChange, disableInput, emailError}) => {
|
||||
|
||||
return (
|
||||
<Paper elevation={0} component="form"
|
||||
target="_self"
|
||||
className={styles.paper_root}
|
||||
onSubmit={onValidateEmail}>
|
||||
<TextField
|
||||
id="email"
|
||||
name="email"
|
||||
autoComplete="email"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
disabled={disableInput}
|
||||
label="Email Address"
|
||||
autoFocus
|
||||
onChange={onHandleUserNameChange}
|
||||
error={emailError != ""}
|
||||
helperText={emailError}
|
||||
/>
|
||||
{emailError == "" &&
|
||||
<Button variant="contained"
|
||||
color="primary"
|
||||
title="Continue"
|
||||
className={styles.apply_button}
|
||||
disabled={disableInput}
|
||||
onClick={onValidateEmail}>
|
||||
>
|
||||
</Button>
|
||||
}
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
const PasswordInputForm = ({
|
||||
formAction,
|
||||
onAuthenticate,
|
||||
disableInput,
|
||||
showPassword,
|
||||
passwordValue,
|
||||
passwordError,
|
||||
onUserPasswordChange,
|
||||
handleClickShowPassword,
|
||||
handleMouseDownPassword,
|
||||
userNameValue,
|
||||
csrfToken,
|
||||
shouldShowCaptcha,
|
||||
captchaPublicKey,
|
||||
onChangeRecaptcha
|
||||
}) => {
|
||||
return(
|
||||
<form method="post" action={formAction} onSubmit={onAuthenticate} target="_self">
|
||||
<TextField
|
||||
id="password"
|
||||
name="password"
|
||||
disabled={disableInput}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={passwordValue}
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
label="Enter Your Password"
|
||||
autoComplete="current-password"
|
||||
error={passwordError != ""}
|
||||
helperText={passwordError}
|
||||
onChange={onUserPasswordChange}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={handleClickShowPassword}
|
||||
onMouseDown={handleMouseDownPassword}
|
||||
edge="end"
|
||||
>
|
||||
{showPassword ? <Visibility/> : <VisibilityOff/>}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
|
||||
/>
|
||||
<FormControlLabel
|
||||
disabled={disableInput}
|
||||
control={<Checkbox value="remember" name="remember" id="remember" color="primary"/>}
|
||||
label="Remember me"
|
||||
/>
|
||||
<input type="hidden" value={userNameValue} id="username" name="username"/>
|
||||
<input type="hidden" value={csrfToken} id="_token" name="_token"/>
|
||||
{shouldShowCaptcha() &&
|
||||
<ReCAPTCHA
|
||||
className={styles.recaptcha}
|
||||
sitekey={captchaPublicKey}
|
||||
onChange={onChangeRecaptcha}
|
||||
/>
|
||||
}
|
||||
<Button variant="contained"
|
||||
disabled={disableInput}
|
||||
className={styles.continue_btn}
|
||||
color="primary"
|
||||
type="submit"
|
||||
onClick={onAuthenticate}>
|
||||
Continue
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
const HelpLinks = ({forgotPasswordAction, verifyEmailAction, helpAction, appName}) => {
|
||||
return (
|
||||
<>
|
||||
<hr className={styles.separator}/>
|
||||
<Link href={forgotPasswordAction} target="_self" variant="body2">
|
||||
Forgot password?
|
||||
</Link>
|
||||
<Link href={verifyEmailAction} target="_self" variant="body2">
|
||||
Verify {appName}
|
||||
</Link>
|
||||
<Link href={helpAction} variant="body2" target="_self">
|
||||
Having trouble?
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const EmailErrorActions = ({createAccountAction, onValidateEmail, disableInput}) => {
|
||||
return(
|
||||
<Grid container style={{alignItems: 'center', marginTop: "20%"}}>
|
||||
<Grid item xs>
|
||||
<Link href={createAccountAction} variant="body2" target="_self">
|
||||
Create Account
|
||||
</Link>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button variant="contained"
|
||||
onClick={onValidateEmail}
|
||||
disabled={disableInput}
|
||||
color="primary">
|
||||
Continue
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
const ThirdPartyIdentityProviders = ({thirdPartyProviders, formAction, disableInput}) => {
|
||||
return(
|
||||
<>
|
||||
<DividerWithText>Or</DividerWithText>
|
||||
{
|
||||
thirdPartyProviders.map((provider) => {
|
||||
return (
|
||||
<Button
|
||||
disabled={disableInput}
|
||||
key={provider.name}
|
||||
variant="contained"
|
||||
className={styles.third_party_idp_button+` ${provider.name}`}
|
||||
color="primary"
|
||||
title={`Sign In with ${provider.label}`}
|
||||
href={`${formAction}/${provider.name}`}>
|
||||
{provider.label}
|
||||
</Button>
|
||||
);
|
||||
})
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
class LoginPage extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
user_name: props.userName,
|
||||
user_password: '',
|
||||
user_pic: props.hasOwnProperty('user_pic') ? props.user_pic : null,
|
||||
user_fullname: props.hasOwnProperty('user_fullname') ? props.user_fullname : null,
|
||||
user_verified: props.hasOwnProperty('user_verified') ? props.user_verified : false,
|
||||
errors: {
|
||||
email: "",
|
||||
password: props.authError != "" ? props.authError : "",
|
||||
},
|
||||
captcha_value: '',
|
||||
showPassword: false,
|
||||
disableInput: false,
|
||||
}
|
||||
this.onHandleUserNameChange = this.onHandleUserNameChange.bind(this);
|
||||
this.onValidateEmail = this.onValidateEmail.bind(this);
|
||||
this.handleDelete = this.handleDelete.bind(this);
|
||||
this.onAuthenticate = this.onAuthenticate.bind(this);
|
||||
this.onChangeRecaptcha = this.onChangeRecaptcha.bind(this);
|
||||
this.onUserPasswordChange = this.onUserPasswordChange.bind(this);
|
||||
this.shouldShowCaptcha = this.shouldShowCaptcha.bind(this);
|
||||
this.handleClickShowPassword = this.handleClickShowPassword.bind(this);
|
||||
this.handleMouseDownPassword = this.handleMouseDownPassword.bind(this);
|
||||
}
|
||||
|
||||
shouldShowCaptcha() {
|
||||
return (
|
||||
this.props.hasOwnProperty('maxLoginAttempts2ShowCaptcha') &&
|
||||
this.props.hasOwnProperty('loginAttempts') &&
|
||||
this.props.loginAttempts >= this.props.maxLoginAttempts2ShowCaptcha
|
||||
)
|
||||
}
|
||||
|
||||
onAuthenticate(ev) {
|
||||
if (this.state.user_password == '') {
|
||||
this.setState({...this.state, errors: {...this.state.errors, password: 'Password is empty'}});
|
||||
ev.preventDefault();
|
||||
return false;
|
||||
}
|
||||
if (this.state.captcha_value == '' && this.shouldShowCaptcha()) {
|
||||
this.setState({...this.state, errors: {...this.state.errors, password: 'you must check CAPTCHA'}});
|
||||
ev.preventDefault();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
onChangeRecaptcha(value) {
|
||||
this.setState({...this.state, captcha_value: value});
|
||||
}
|
||||
|
||||
onHandleUserNameChange(ev) {
|
||||
let {value, id} = ev.target;
|
||||
this.setState({...this.state, user_name: value});
|
||||
}
|
||||
|
||||
onUserPasswordChange(ev) {
|
||||
let {errors} = this.state;
|
||||
let {value, id} = ev.target;
|
||||
if(value == "") // clean error
|
||||
errors[id] = '';
|
||||
this.setState({...this.state, user_password: value, errors: {...errors}});
|
||||
}
|
||||
|
||||
onValidateEmail(ev) {
|
||||
|
||||
ev.preventDefault();
|
||||
|
||||
if (this.state.user_name == '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!emailValidator(this.state.user_name)) {
|
||||
return false;
|
||||
}
|
||||
this.setState({...this.state, disableInput: true});
|
||||
verifyAccount(this.state.user_name).then((payload) => {
|
||||
let {response} = payload;
|
||||
this.setState({
|
||||
...this.state,
|
||||
user_pic: response.pic,
|
||||
user_fullname: response.full_name,
|
||||
user_verified: true,
|
||||
errors: {
|
||||
email: '',
|
||||
password: ''
|
||||
},
|
||||
disableInput: false
|
||||
})
|
||||
}, (error) => {
|
||||
let {body} = error.res;
|
||||
let newErrors = {}
|
||||
newErrors['password'] = '';
|
||||
newErrors['email'] = "We could not find an Account with that email Address";
|
||||
this.setState({
|
||||
...this.state,
|
||||
user_pic: null,
|
||||
user_fullname: null,
|
||||
user_verified: false,
|
||||
errors: newErrors,
|
||||
disableInput: false
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
handleDelete() {
|
||||
this.setState({...this.state, user_name: null, user_pic: null, user_fullname: null, user_verified: false});
|
||||
}
|
||||
|
||||
handleClickShowPassword(ev) {
|
||||
this.setState({...this.state, showPassword: !this.state.showPassword})
|
||||
}
|
||||
|
||||
handleMouseDownPassword(ev) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Container component="main" maxWidth="xs" className={styles.main_container}>
|
||||
<CssBaseline/>
|
||||
<div className={styles.inner_container}>
|
||||
<Typography component="h1">
|
||||
<a href={window.location.href}><img className={styles.app_logo} alt="appLogo" src={this.props.appLogo}/></a>
|
||||
</Typography>
|
||||
<Typography component="h1" variant="h5">
|
||||
Sign in {this.state.user_fullname && <Chip
|
||||
avatar={<Avatar alt={this.state.user_fullname} src={this.state.user_pic}/>}
|
||||
variant="outlined"
|
||||
className={styles.valid_user_name_chip}
|
||||
label={this.state.user_fullname}
|
||||
onDelete={this.handleDelete}/>}
|
||||
</Typography>
|
||||
{!this.state.user_verified &&
|
||||
<>
|
||||
<EmailInputForm
|
||||
onValidateEmail={this.onValidateEmail}
|
||||
onHandleUserNameChange={this.onHandleUserNameChange}
|
||||
disableInput={this.state.disableInput}
|
||||
emailError={this.state.errors.email}/>
|
||||
|
||||
{ this.state.errors.email == '' &&
|
||||
this.props.thirdPartyProviders.length > 0 &&
|
||||
<ThirdPartyIdentityProviders
|
||||
thirdPartyProviders={this.props.thirdPartyProviders}
|
||||
formAction={this.props.formAction}
|
||||
disableInput={this.state.disableInput}
|
||||
/>
|
||||
}
|
||||
{
|
||||
// we already had an interaction and got an user error...
|
||||
this.state.errors.email != '' &&
|
||||
<>
|
||||
<EmailErrorActions
|
||||
onValidateEmail={this.onValidateEmail}
|
||||
disableInput={this.state.disableInput}
|
||||
createAccountAction={(this.state.user_name) ? `${this.props.createAccountAction}?email=${encodeURIComponent(this.state.user_name)}`: this.props.createAccountAction}
|
||||
/>
|
||||
<HelpLinks
|
||||
appName={this.props.appName}
|
||||
forgotPasswordAction={this.props.forgotPasswordAction}
|
||||
verifyEmailAction={this.props.verifyEmailAction}
|
||||
helpAction={this.props.helpAction}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
}
|
||||
{this.state.user_verified &&
|
||||
// proceed to ask for password ( 2nd step )
|
||||
<>
|
||||
<PasswordInputForm
|
||||
formAction={this.props.formAction}
|
||||
onAuthenticate={this.onAuthenticate}
|
||||
disableInput={this.state.disableInput}
|
||||
showPassword={this.state.showPassword}
|
||||
passwordValue={this.state.user_password}
|
||||
passwordError={this.state.errors.password}
|
||||
onUserPasswordChange={this.onUserPasswordChange}
|
||||
handleClickShowPassword={this.handleClickShowPassword}
|
||||
handleMouseDownPassword={this.handleMouseDownPassword}
|
||||
userNameValue={this.state.user_name}
|
||||
csrfToken={this.props.token}
|
||||
shouldShowCaptcha={this.shouldShowCaptcha}
|
||||
captchaPublicKey={this.props.captchaPublicKey}
|
||||
onChangeRecaptcha={this.onChangeRecaptcha}
|
||||
/>
|
||||
<HelpLinks
|
||||
appName={this.props.appName}
|
||||
forgotPasswordAction={this.props.forgotPasswordAction}
|
||||
verifyEmailAction={this.props.verifyEmailAction}
|
||||
helpAction={this.props.helpAction}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Or Create your Own theme:
|
||||
const theme = createMuiTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#3fa2f7'
|
||||
},
|
||||
},
|
||||
overrides: {
|
||||
MuiButton: {
|
||||
containedPrimary: {
|
||||
color: 'white'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ReactDOM.render(
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<LoginPage {...config}/>
|
||||
</MuiThemeProvider>,
|
||||
document.querySelector('#root')
|
||||
);
|
74
resources/js/login/login.module.scss
Normal file
74
resources/js/login/login.module.scss
Normal file
@ -0,0 +1,74 @@
|
||||
$border-color: #e3e3e3;
|
||||
$base-color:#3fa2f7;
|
||||
|
||||
.main_container{
|
||||
border: solid 1px $border-color;
|
||||
margin-top: 10%;
|
||||
margin-bottom: 10%;
|
||||
color: $base-color;
|
||||
}
|
||||
|
||||
.inner_container {
|
||||
display: flex;
|
||||
margin-top: 64px;
|
||||
padding-bottom: 20%;
|
||||
flex-direction: column;
|
||||
.app_logo{
|
||||
max-width: 40%;
|
||||
float: right;
|
||||
margin-bottom: 15%;
|
||||
}
|
||||
.continue_btn{
|
||||
float: right;
|
||||
margin-top: 30%;
|
||||
}
|
||||
.recaptcha{
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.separator{
|
||||
color:$base-color;
|
||||
width: 100%;
|
||||
border: 1px solid;
|
||||
margin-bottom: 5%;
|
||||
margin-top: 5%;
|
||||
}
|
||||
}
|
||||
|
||||
.paper_root {
|
||||
padding: 2px 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.apply_button {
|
||||
height: 55px;
|
||||
margin-bottom: 8px;
|
||||
margin-top: 16px;
|
||||
margin-left: 10px;
|
||||
min-width: 20px !important;
|
||||
font-size: 20pt;
|
||||
}
|
||||
}
|
||||
|
||||
.third_party_idp_button {
|
||||
width: 100%;
|
||||
margin-top: 5px !important;
|
||||
margin-bottom: 5px !important;
|
||||
position:relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.third_party_idp_button:before{
|
||||
content: "";
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 34px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.valid_user_name_chip{
|
||||
float: right;
|
||||
}
|
60
resources/js/login/third_party_identity_providers.scss
Normal file
60
resources/js/login/third_party_identity_providers.scss
Normal file
@ -0,0 +1,60 @@
|
||||
$facebook_base_color:#3B5998;
|
||||
$apple_base_color:#000000;
|
||||
$google_base_color:#DD4B39;
|
||||
$linkedin_base_color:#2867B2;
|
||||
|
||||
.facebook{
|
||||
background-color: $facebook_base_color !important;
|
||||
}
|
||||
|
||||
.facebook:hover,
|
||||
.facebook:focus {
|
||||
background-color: #5B7BD5;
|
||||
background-image: linear-gradient(#5B7BD5, #4864B1);
|
||||
}
|
||||
.facebook:before {
|
||||
border-right: #364e92 1px solid !important;
|
||||
background: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/14082/icon_facebook.png') 6px 6px no-repeat !important;
|
||||
}
|
||||
|
||||
.apple{
|
||||
background-color: $apple_base_color !important;
|
||||
}
|
||||
|
||||
.apple:before {
|
||||
border-right: #000001 1px solid !important;
|
||||
background: url('/assets/img/third_party_identity_providers/icon_apple.png') 6px 6px no-repeat !important;
|
||||
}
|
||||
.google:before {
|
||||
border-right: #BB3F30 1px solid;
|
||||
background: url('/assets/img/third_party_identity_providers/icon_google.png') 6px 6px no-repeat;
|
||||
}
|
||||
|
||||
.google {
|
||||
background-color: $google_base_color !important;
|
||||
}
|
||||
|
||||
.google:hover,
|
||||
.google:focus {
|
||||
background: #E74B37;
|
||||
}
|
||||
|
||||
.google:before {
|
||||
border-right: #BB3F30 1px solid;
|
||||
background: url('/assets/img/third_party_identity_providers/icon_google.png') 6px 6px no-repeat;
|
||||
}
|
||||
|
||||
.linkedin{
|
||||
background-color: $linkedin_base_color !important;
|
||||
}
|
||||
|
||||
.linkedin:hover,
|
||||
.linkedin:focus {
|
||||
background-color: #2867B3;
|
||||
background-image: linear-gradient(#5B7BD5, #4864B1);
|
||||
}
|
||||
|
||||
.linkedin:before {
|
||||
border-right: #4864B1 1px solid;
|
||||
background: url('/assets/img/third_party_identity_providers/icon_linkedin.png') 6px 6px no-repeat;
|
||||
}
|
3
resources/js/validator.js
Normal file
3
resources/js/validator.js
Normal file
@ -0,0 +1,3 @@
|
||||
export const emailValidator = (value) => {
|
||||
return /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test( value )
|
||||
}
|
@ -34,7 +34,9 @@
|
||||
</div>
|
||||
@endif
|
||||
<div class="signup-form">
|
||||
<form id="form-verification" method="POST" action="{{ URL::action('Auth\EmailVerificationController@resend') }}">
|
||||
<form id="form-verification"
|
||||
target="_self"
|
||||
method="POST" action="{{ URL::action('Auth\EmailVerificationController@resend') }}">
|
||||
@csrf
|
||||
<h2>{{ __('Email Verification') }}</h2>
|
||||
<div class="form-group">
|
||||
|
@ -1,114 +1,71 @@
|
||||
@extends('layout')
|
||||
@extends('auth_layout')
|
||||
@section('title')
|
||||
<title>Welcome to {{ Config::get("app.app_name") }} - Sign in </title>
|
||||
@append
|
||||
@section('meta')
|
||||
<meta http-equiv="X-XRDS-Location" content="{!! URL::action("OpenId\DiscoveryController@idp") !!}"/>
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
@append
|
||||
@section('css')
|
||||
{!! HTML::style('assets/css/auth/login.css') !!}
|
||||
{!! HTML::style('assets/css/login.css') !!}
|
||||
@append
|
||||
@section('content')
|
||||
@if(isset($identity_select))
|
||||
<legend style="margin-left: 15px;">
|
||||
@if(!$identity_select)
|
||||
Sign in to <b>{!! $realm !!}</b> using <b>{!! $identity !!}</b>
|
||||
@else
|
||||
Sign in to <b>{!! $realm !!} </b> using your {{ Config::get("app.app_name") }}
|
||||
@endif
|
||||
</legend>
|
||||
@endif
|
||||
|
||||
<div id="cookies-disabled-dialog" class="alert alert-warning alert-dismissible" style="display: none;" role="alert">
|
||||
<button type="button" class="close" onclick="$('#cookies-disabled-dialog').hide()" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<strong>Warning!</strong> Cookies are not enabled, please enabled them in order to use {{ Config::get("app.app_name") }}.
|
||||
</div>
|
||||
|
||||
<div class="col-md-4" id="sidebar">
|
||||
<div class="well">
|
||||
{!! Form::open(array('id'=>'login_form','url' => URL::action('UserController@postLogin'), 'method' => 'post', "autocomplete" => "off")) !!}
|
||||
<legend>
|
||||
Welcome to {{ Config::get("app.app_name") }}! <span aria-hidden="true" style="font-size: 10pt;"
|
||||
class="glyphicon glyphicon-info-sign pointable"
|
||||
title="Please use your {{ Config::get("app.app_name") }} to log in"></span>
|
||||
</legend>
|
||||
@if(Config::get("app.app_info"))
|
||||
<p class="help-block">
|
||||
{{Config::get("app.app_info")}}
|
||||
</p>
|
||||
@endif
|
||||
<div class="form-group">
|
||||
{!! Form::email('username',Session::has('username')? Session::get('username'):null, array
|
||||
(
|
||||
'placeholder' => 'Username',
|
||||
'class' =>'form-control',
|
||||
'required' => 'true',
|
||||
'autocomplete' => 'username'
|
||||
)) !!}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input placeholder="Password" class="form-control" required="true" autocomplete="current-password" name="password" id="password" type="password" value="">
|
||||
<span toggle="#password" class="fa fa-fw fa-eye fa-eye-slash field-icon toggle-password" title="Show Password"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@if(Session::has('flash_notice'))
|
||||
<span class="error-message"><i
|
||||
class="fa fa-exclamation-triangle"> {!! Session::get('flash_notice') !!}</i></span>
|
||||
@else
|
||||
@foreach($errors->all() as $message)
|
||||
<span class="error-message"><i
|
||||
class="fa fa-exclamation-triangle"> {!! $message !!}</i></span>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
@if(Session::has('login_attempts') && Session::has('max_login_attempts_2_show_captcha') && Session::get('login_attempts') > Session::get('max_login_attempts_2_show_captcha'))
|
||||
{!! Recaptcha::render(array('id'=>'captcha','class'=>'input-block-level')) !!}
|
||||
{!! Form::hidden('login_attempts', Session::get('login_attempts')) !!}
|
||||
@else
|
||||
{!! Form::hidden('login_attempts', '0') !!}
|
||||
@endif
|
||||
|
||||
<div class="checkbox">
|
||||
<label class="checkbox">
|
||||
{!! Form::checkbox('remember', '1', false) !!}Remember me
|
||||
</label>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{!! Form::submit('Sign In',array('id'=>'login','class'=>'btn btn-primary')) !!}
|
||||
<a class="btn btn-primary" href="{!! URL::action('UserController@cancelLogin') !!} ">Cancel</a>
|
||||
</div>
|
||||
<div style="clear:both;padding-top:15px;" class="row">
|
||||
<div class="col-md-12">
|
||||
<a title="register new account" target="_blank"
|
||||
href="{!! URL::action("Auth\RegisterController@showRegistrationForm") !!}">Create an
|
||||
{!! Config::get("app.app_name") !!} </a>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear:both;padding-top:15px;" class="row">
|
||||
<div class="col-md-12">
|
||||
<a title="forgot password" target="_blank"
|
||||
href="{!! URL::action("Auth\ForgotPasswordController@showLinkRequestForm") !!}">Forgot password?</a>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear:both;padding-top:15px;" class="row">
|
||||
<div class="col-md-12">
|
||||
<a title="verify account" target="_blank" href="{!! URL::action("Auth\EmailVerificationController@showVerificationForm") !!}">Verify
|
||||
{!! Config::get("app.app_name") !!}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear:both;padding-top:15px;" class="row">
|
||||
<div class="col-md-12">
|
||||
<a title="help" target="_blank" href="mailto:{!! Config::get("app.help_email") !!}">Help</a>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
{!! Form::close() !!}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
</div>
|
||||
@append
|
||||
@section('scripts')
|
||||
{!! HTML::script('assets/js/login.js') !!}
|
||||
<script>
|
||||
|
||||
let authError = '';
|
||||
@if(Session::has('flash_notice'))
|
||||
authError = '{!! Session::get("flash_notice") !!}';
|
||||
@else
|
||||
@foreach($errors->all() as $message)
|
||||
authError = '{!! $message !!}';
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
let config = {
|
||||
token : document.head.querySelector('meta[name="csrf-token"]').content,
|
||||
userName:'{{ Session::has('username') ? Session::get('username') : ""}}',
|
||||
realm: '{{isset($identity_select) ? $realm : ""}}',
|
||||
appName: '{{ Config::get("app.app_name") }}',
|
||||
appLogo: '{{ Config::get("app.logo_url") }}',
|
||||
formAction: '{{ URL::action("UserController@postLogin") }}',
|
||||
accountVerifyAction : '{{URL::action("UserController@getAccount")}}',
|
||||
authError: authError,
|
||||
captchaPublicKey: '{{ Config::get("recaptcha.public_key") }}',
|
||||
thirdPartyProviders: [
|
||||
@foreach($supported_providers as $provider => $label)
|
||||
{label: "{{$label}}", name:"{{$provider}}"},
|
||||
@endforeach
|
||||
],
|
||||
forgotPasswordAction:'{{ URL::action("Auth\ForgotPasswordController@showLinkRequestForm") }}',
|
||||
verifyEmailAction:'{{ URL::action("Auth\EmailVerificationController@showVerificationForm") }}',
|
||||
helpAction:'mailto:{!! Config::get("app.help_email") !!}',
|
||||
createAccountAction:'{{ URL::action("Auth\RegisterController@showRegistrationForm") }}',
|
||||
}
|
||||
|
||||
@if(Session::has('max_login_attempts_2_show_captcha'))
|
||||
config.maxLoginAttempts2ShowCaptcha = {{Session::get("max_login_attempts_2_show_captcha")}};
|
||||
@endif
|
||||
|
||||
@if(Session::has('login_attempts'))
|
||||
config.loginAttempts = {{Session::get("login_attempts")}};
|
||||
@endif
|
||||
|
||||
@if(Session::has('user_fullname'))
|
||||
config.user_fullname = '{{Session::get("user_fullname")}}';
|
||||
@endif
|
||||
|
||||
@if(Session::has('user_pic'))
|
||||
config.user_pic = '{{Session::get("user_pic")}}';
|
||||
@endif
|
||||
@if(Session::has('user_verified'))
|
||||
config.user_verified = {{Session::get('user_verified')}};
|
||||
@endif
|
||||
|
||||
window.VERIFY_ACCOUNT_ENDPOINT = config.accountVerifyAction;
|
||||
|
||||
</script>
|
||||
{!! HTML::script('assets/login.js') !!}
|
||||
@append
|
@ -35,7 +35,9 @@
|
||||
</div>
|
||||
@endif
|
||||
<div class="signup-form">
|
||||
<form id="form-send-password-reset-link" method="POST" action="{{ URL::action('Auth\ForgotPasswordController@sendResetLinkEmail') }}">
|
||||
<form id="form-send-password-reset-link"
|
||||
target="_self"
|
||||
method="POST" action="{{ URL::action('Auth\ForgotPasswordController@sendResetLinkEmail') }}">
|
||||
@csrf
|
||||
<h2>Forgot Password?</h2>
|
||||
<p class="hint-text">You can reset your password here.</p>
|
||||
|
@ -36,6 +36,7 @@
|
||||
@endif
|
||||
<div class="signup-form">
|
||||
<form id="form-password-reset" class="form-horizontal" method="POST"
|
||||
target="_self"
|
||||
action="{{ URL::action('Auth\ResetPasswordController@reset') }}">
|
||||
@csrf
|
||||
<input type="hidden" name="token" value="{{ $token }}">
|
||||
|
@ -36,6 +36,7 @@
|
||||
@endif
|
||||
<div class="signup-form">
|
||||
<form id="form-password-set" class="form-horizontal" method="POST"
|
||||
target="_self"
|
||||
action="{{ URL::action('Auth\PasswordSetController@setPassword') }}">
|
||||
@csrf
|
||||
<input type="hidden" name="token" value="{{ $token }}">
|
||||
|
@ -29,7 +29,9 @@
|
||||
</div>
|
||||
@endif
|
||||
<div class="col-xs-12 col-md-5 col-md-offset-3 signup-form">
|
||||
<form id="form-registration" method="POST" autocomplete="off" action="{{ URL::action('Auth\RegisterController@register') }}">
|
||||
<form id="form-registration"
|
||||
target="_self"
|
||||
method="POST" autocomplete="off" action="{{ URL::action('Auth\RegisterController@register') }}">
|
||||
@csrf
|
||||
<h2>Register</h2>
|
||||
<p class="hint-text">Create your account. It's free and only takes a minute.</p>
|
||||
|
29
resources/views/auth_layout.blade.php
Normal file
29
resources/views/auth_layout.blade.php
Normal file
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@yield('title')
|
||||
<base href="{!! Config::get('app.url') !!}" target="_self">
|
||||
<meta
|
||||
name="viewport"
|
||||
content="minimum-scale=1, initial-scale=1, width=device-width"
|
||||
/>
|
||||
<link rel="shortcut icon" href="{!! Config::get('app.tenant_favicon') !!}" />
|
||||
@yield('meta')
|
||||
@yield('css')
|
||||
<!--https://material-ui.com/getting-started/installation/-->
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
||||
<style type="text/css">
|
||||
#logo a {
|
||||
background: url("{!! Config::get('app.logo_url') !!}") no-repeat scroll left center rgba(0, 0, 0, 0);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
@yield('content')
|
||||
</div>
|
||||
@yield('scripts')
|
||||
<span style="display: none">{!! Config::get('app.version') !!}</span>
|
||||
</body>
|
||||
</html>
|
@ -16,12 +16,12 @@
|
||||
<div class="col-md-12">
|
||||
<div class="row" style="padding-top: 5px;padding-bottom: 5px;">
|
||||
<div class="col-md-12">
|
||||
<a href="{!! URL::action("UserController@getLogin") !!}" class="btn btn-default btn-md active">Sign in to your account</a>
|
||||
<a target="_self" href="{!! URL::action("UserController@getLogin") !!}" class="btn btn-default btn-md active">Sign in to your account</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="padding-top: 5px;padding-bottom: 5px;">
|
||||
<div class="col-md-12">
|
||||
<a href="{!! URL::action("Auth\RegisterController@showRegistrationForm") !!}" class="btn btn-default btn-md active">Register for an {{ Config::get('app.app_name') }}</a>
|
||||
<a target="_self" href="{!! URL::action("Auth\RegisterController@showRegistrationForm") !!}" class="btn btn-default btn-md active">Register for an {{ Config::get('app.app_name') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
@yield('title')
|
||||
<base href="{!! Config::get('app.url') !!}" target="_blank">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="{!! Config::get('app.tenant_favicon') !!}" />
|
||||
@yield('meta')
|
||||
@ -33,7 +34,6 @@
|
||||
</div>
|
||||
<footer class="row"></footer>
|
||||
</div>
|
||||
{!! HTML::script('assets/__common__.js')!!}
|
||||
{!! HTML::script('assets/index.js')!!}
|
||||
{!! HTML::script('assets/js/ajax.utils.js')!!}
|
||||
{!! HTML::script('assets/js/jquery.cleanform.js')!!}
|
||||
|
@ -28,7 +28,8 @@
|
||||
<li> {!!$scope->getShortDescription()!!} <span class="glyphicon glyphicon-info-sign pointable info" aria-hidden="true" data-content="{!! $scope->getDescription() !!}" title="Scope Info"></span></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
{!! Form::open(array('url' => URL::action("UserController@postConsent") ,'id'=>'authorization_form', 'method' => 'post', "autocomplete" => "off")) !!}
|
||||
{!! Form::open(array('url' => URL::action("UserController@postConsent") ,'id'=>'authorization_form', 'method' => 'post', "autocomplete" => "off", "target"=> "_self" )) !!}
|
||||
|
||||
<input type="hidden" name='trust' id='trust' value=""/>
|
||||
<button class="btn btn-default btn-md btn-consent-action" id="cancel-authorization" type="button">Cancel</button>
|
||||
<button class="btn btn-primary btn-md btn-consent-action" id="approve-authorization" type="button">Accept</button>
|
||||
|
@ -12,7 +12,7 @@ Welcome, <a href="{!! URL::action("UserController@getProfile") !!}">{!!Auth::use
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<h4>{!! Config::get('app.app_name') !!} - Openid verification</h4>
|
||||
{!! Form::open(array('url' => URL::action("UserController@postConsent"),'id'=>'authorization_form', 'method' => 'post', "autocomplete" => "off")) !!}
|
||||
{!! Form::open(array('url' => URL::action("UserController@postConsent"),'id'=>'authorization_form', 'method' => 'post', "autocomplete" => "off", "target" => "_self")) !!}
|
||||
<legend>
|
||||
Sign in to <b>{!! $realm !!}</b> using your {{ Config::get('app.app_name') }}
|
||||
</legend>
|
||||
|
@ -41,10 +41,16 @@ Route::group(array('middleware' => ['ssl']), function () {
|
||||
|
||||
//user interaction
|
||||
Route::group(array('prefix' => 'auth'), function () {
|
||||
|
||||
Route::group(array('prefix' => 'login'), function () {
|
||||
Route::get('', "UserController@getLogin");
|
||||
Route::get('account-verify', "UserController@getAccount");
|
||||
Route::post('', ['middleware' => 'csrf', 'uses' => 'UserController@postLogin']);
|
||||
Route::get('cancel', "UserController@cancelLogin");
|
||||
Route::group(array('prefix' => '{provider}'), function () {
|
||||
Route::get('', 'SocialLoginController@redirect')->name("social_login");
|
||||
Route::any('callback','SocialLoginController@callback')->name("social_login_callback");
|
||||
});
|
||||
});
|
||||
|
||||
// registration routes
|
||||
|
@ -156,7 +156,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
|
||||
$url = $response->getTargetUrl();
|
||||
|
||||
$response = $this->call('GET', $url);
|
||||
$response = $this->call('GET', $url, [], [$response->cookie("openstackid_session")]);
|
||||
|
||||
$this->assertResponseStatus(200);
|
||||
|
||||
|
139
webpack.common.js
Normal file
139
webpack.common.js
Normal file
@ -0,0 +1,139 @@
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const CopyPlugin = require("copy-webpack-plugin");
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
/*
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
commons: {
|
||||
name: 'commons',
|
||||
chunks: 'initial',
|
||||
minChunks: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
*/
|
||||
entry: {
|
||||
login: './resources/js/login/login.js',
|
||||
index: './resources/js/index.js',
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: path.resolve(__dirname, 'public/assets'),
|
||||
publicPath: '/assets/',
|
||||
pathinfo: false
|
||||
},
|
||||
node: {
|
||||
fs: 'empty',
|
||||
child_process: 'empty',
|
||||
tls: 'empty',
|
||||
net: 'empty',
|
||||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
$: 'jquery',
|
||||
jQuery: 'jquery'
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: './css/[name].css',
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{from: './node_modules/bootstrap-tagsinput/dist', to: 'bootstrap-tagsinput'},
|
||||
{from: './node_modules/typeahead.js/dist', to: 'typeahead'},
|
||||
{from: './node_modules/jquery.cookie/jquery.cookie.js', to: 'jquery-cookie/jquery.cookie.js'},
|
||||
{from: './node_modules/crypto-js/crypto-js.js', to: 'crypto-js/crypto-js.js'},
|
||||
{from: './node_modules/pwstrength-bootstrap/dist', to: 'pwstrength-bootstrap'},
|
||||
{from: './node_modules/sweetalert2/dist', to: 'sweetalert2'},
|
||||
{from: './node_modules/urijs/src', to: 'urijs'},
|
||||
{from: './node_modules/chosen-js', to: 'chosen-js'},
|
||||
{from: './node_modules/moment', to: 'moment'},
|
||||
{from: './node_modules/@github/clipboard-copy-element/dist', to: 'clipboard-copy-element'},
|
||||
{from: './node_modules/simplemde/dist', to: 'simplemde'},
|
||||
],
|
||||
}
|
||||
),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{"targets": {"node": "current"}}
|
||||
],
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-flow'
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-proposal-class-properties'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [MiniCssExtractPlugin.loader, "css-loader"]
|
||||
},
|
||||
{
|
||||
test: /\.module\.scss/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: {
|
||||
localIdentName: "[local]___[hash:base64:5]",
|
||||
hashPrefix: 'schedule-filter-widget',
|
||||
},
|
||||
sourceMap: false
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
sourceMap: false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.scss/,
|
||||
exclude: /\.module\.scss/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
// Translates CSS into CommonJS
|
||||
"css-loader",
|
||||
// Compiles Sass to CSS
|
||||
"sass-loader",
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: "file-loader?name=fonts/[name].[ext]"
|
||||
},
|
||||
{
|
||||
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: "url-loader?limit=10000&minetype=application/font-woff&name=fonts/[name].[ext]"
|
||||
},
|
||||
{
|
||||
test: /\.svg/,
|
||||
use: "file-loader?name=svg/[name].[ext]!svgo-loader"
|
||||
},
|
||||
{
|
||||
test: /\.jpg|\.png|\.gif$/,
|
||||
use: "file-loader?name=images/[name].[ext]"
|
||||
},
|
||||
]
|
||||
}
|
||||
};
|
18
webpack.dev.js
Normal file
18
webpack.dev.js
Normal file
@ -0,0 +1,18 @@
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
|
||||
module.exports = merge(common, {
|
||||
watch:true,
|
||||
plugins: [
|
||||
//new CleanWebpackPlugin(),
|
||||
],
|
||||
mode: 'development',
|
||||
devtool: 'inline-source-map',
|
||||
devServer: {
|
||||
contentBase: './public/assets',
|
||||
historyApiFallback: true
|
||||
},
|
||||
optimization: {
|
||||
minimize: false
|
||||
},
|
||||
});
|
12
webpack.prod.js
Normal file
12
webpack.prod.js
Normal file
@ -0,0 +1,12 @@
|
||||
const path = require('path');
|
||||
//const TerserJSPlugin = require('terser-webpack-plugin');
|
||||
//const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'production',
|
||||
optimization: {
|
||||
minimize: true
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue
Block a user