Put back google social login
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 31s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s

This commit is contained in:
Deon George 2024-08-23 17:28:00 +10:00
parent 89fb347806
commit 84619113c5
8 changed files with 338 additions and 119 deletions

View File

@ -24,6 +24,6 @@ class CollectionOrNull implements CastsAttributes
*/ */
public function set(Model $model, string $key, mixed $value, array $attributes): mixed public function set(Model $model, string $key, mixed $value, array $attributes): mixed
{ {
return $value->count() ? json_encode($value) : NULL; return count($value) ? json_encode($value) : NULL;
} }
} }

View File

@ -3,10 +3,15 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Laravel\Socialite\Facades\Socialite; use Laravel\Socialite\Facades\Socialite;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Mail\SocialLink;
use App\Models\{ProviderOauth,ProviderToken,User,UserOauth}; use App\Models\{ProviderOauth,ProviderToken,User,UserOauth};
class SocialLoginController extends Controller class SocialLoginController extends Controller
@ -22,54 +27,58 @@ class SocialLoginController extends Controller
$openiduser = Socialite::with($provider)->user(); $openiduser = Socialite::with($provider)->user();
if (! $openiduser) if (! $openiduser)
return redirect('/home')->with('error','No user details obtained.'); return redirect('/home')
->with('error','No user details obtained.');
$oo = ProviderOauth::firstOrCreate(['name'=>$provider,'active'=>TRUE]); $oo = ProviderOauth::firstOrCreate(['name'=>$provider,'active'=>TRUE]);
// See if this user has connected and linked previously // See if this user has connected and linked previously
$aoo = $oo->users->where('userid',$openiduser->id); $aoo = $oo->users->where('userid',$openiduser->id);
if ($aoo->count() == 1) { if ($aoo->count() === 1) {
$aoo = $aoo->first(); $aoo = $aoo->first();
if ((is_null($user=$aoo->user) AND (is_null($aoo->account) OR is_null($user=$aoo->account->user))) OR ! $user->active) { if ((is_null($user=$aoo->user) && (is_null($aoo->account) || is_null($user=$aoo->account->user))) || ! $user->active) {
if (! $user) { if (! $user)
$user = User::where('email',$openiduser->email)->first(); $user = User::where('email',$openiduser->email)->first();
}
if (! $user OR ! $user->active) { if ((! $user) || (! $user->active))
return redirect('/login')->with('error','Invalid account, or account inactive, please contact an admin.'); return redirect('/login')
} ->with('error','Invalid account, or account inactive, please contact an admin.');
return $this->link($provider,$aoo,$user); return $this->link($provider,$aoo,$user);
} }
// All Set to login // All Set to login
Auth::login($user,FALSE); Auth::login($user);
// If there are too many users, then we have a problem // If there are too many users, then we have a problem
} elseif ($aoo->count() > 1) { } elseif ($aoo->count() > 1) {
return redirect('/login')->with('error','Seems you have multiple oauth IDs, please contact an admin.'); return redirect('/login')
->with('error','Seems you have multiple oauth IDs, please contact an admin.');
// User is using OAUTH for the first time. // User is using OAUTH for the first time.
} else { } else {
$uo = User::active()->where('email',$openiduser->email); $uo = User::active()->where('email',$openiduser->email);
// See if their is an account with this email address // See if their is an account with this email address
if ($uo->count() == 1) { if ($uo->count() === 1) {
$aoo = new UserOauth; $aoo = new UserOauth;
$aoo->userid = $openiduser->id; $aoo->userid = $openiduser->id;
$aoo->oauth_data = $openiduser->user; $aoo->oauth_data = $openiduser->user;
$oo->users()->save($aoo); $oo->users()->save($aoo);
return $this->link($provider,$aoo,$uo->first()); return $this->link($provider,$aoo,$uo->first());
// If there are too many users, then we have a problem // If there are too many users, then we have a problem
} elseif ($uo->count() > 1) { } elseif ($uo->count() > 1) {
return redirect('/login')->with('error','Seems you have multiple accounts, please contact an admin.'); return redirect('/login')
->with('error','Seems you have multiple accounts, please contact an admin.');
} else { } else {
return redirect('/login')->with('error','Seems you dont have an account with that email, please contact an admin.'); return redirect('/login')
->with('error','Seems you dont have an account with that email, please contact an admin.');
} }
} }
@ -82,7 +91,8 @@ class SocialLoginController extends Controller
$openiduser = Socialite::with($provider)->user(); $openiduser = Socialite::with($provider)->user();
if (! $openiduser) if (! $openiduser)
return redirect('/home')->with('error','No user details obtained.'); return redirect('/home')
->with('error','No user details obtained.');
$po = ProviderOauth::where('name',$provider)->singleOrFail(); $po = ProviderOauth::where('name',$provider)->singleOrFail();
@ -101,4 +111,48 @@ class SocialLoginController extends Controller
->intended('/home') ->intended('/home')
->with('success','Token refreshed.'); ->with('success','Token refreshed.');
} }
/**
* We have identified the user and oauth, just need them to confirm the link
*
* @param $provider
* @param UserOauth $ao
* @param User $uo
* @return View
*/
public function link($provider,UserOauth $ao,User $uo): View
{
// @note If this is sent now (send()), it results in the caller to be executed a second time (handleProviderCallback()).
Mail::to($uo->email)->queue(new SocialLink($ao));
return view('theme.backend.adminlte.auth.social_link')
->with('oauthid',$ao->id)
->with('provider',$provider);
}
public function linkcomplete(Request $request,$provider)
{
// Load our oauth id
$aoo = UserOauth::findOrFail($request->post('oauthid'));
// Check our email matches
if (Arr::get($aoo->oauth_data,'email','invalid') !== $request->post('email'))
return redirect('/login')
->with('error','Account details didnt match to make link.');
// Check our token matches
if ($aoo->link_token !== $request->post('token'))
return redirect('/login')
->with('error','Token details didnt match to make link.');
// Load our email.
$uo = User::where('email',$request->post('email'))->firstOrFail();
$aoo->user_id = $uo->id;
$aoo->save();
Auth::login($uo);
return redirect()
->intended('/home');
}
} }

48
app/Mail/SocialLink.php Normal file
View File

@ -0,0 +1,48 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use App\Models\{Site,User,UserOauth};
class SocialLink extends Mailable
{
use Queueable, SerializesModels;
public string $token;
public Site $site;
public ?User $user;
/**
* Create a new message instance.
*
* @param UserOauth $o
*/
public function __construct(UserOauth $o)
{
$this->site = $o->site;
$this->token = $o->link_token;
$this->user = $o->user;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
Config::set('site',$this->site);
return $this
->markdown('email.system.social_link')
->subject('Link your Account')
->with([
'site'=>$this->site,
]);
}
}

View File

@ -2,120 +2,128 @@
return [ return [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Authentication Defaults | Authentication Defaults
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| This option defines the default authentication "guard" and password | This option defines the default authentication "guard" and password
| reset "broker" for your application. You may change these values | reset "broker" for your application. You may change these values
| as required, but they're a perfect start for most applications. | as required, but they're a perfect start for most applications.
| |
*/ */
'defaults' => [ 'defaults' => [
'guard' => env('AUTH_GUARD', 'web'), 'guard' => env('AUTH_GUARD', 'web'),
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
], ],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Authentication Guards | Authentication Guards
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Next, you may define every authentication guard for your application. | Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you | Of course, a great default configuration has been defined for you
| which utilizes session storage plus the Eloquent user provider. | which utilizes session storage plus the Eloquent user provider.
| |
| All authentication guards have a user provider, which defines how the | All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage | users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized. | system used by the application. Typically, Eloquent is utilized.
| |
| Supported: "session" | Supported: "session"
| |
*/ */
'guards' => [ 'guards' => [
'web' => [ 'web' => [
'driver' => 'session', 'driver' => 'session',
'provider' => 'users', 'provider' => 'users',
], ],
'api' => [ 'api' => [
'driver' => 'passport', 'driver' => 'passport',
'provider' => 'users', 'provider' => 'users',
], ],
], ],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| User Providers | User Providers
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| All authentication guards have a user provider, which defines how the | All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage | users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized. | system used by the application. Typically, Eloquent is utilized.
| |
| If you have multiple user tables or models you may configure multiple | If you have multiple user tables or models you may configure multiple
| providers to represent the model / table. These providers may then | providers to represent the model / table. These providers may then
| be assigned to any extra authentication guards you have defined. | be assigned to any extra authentication guards you have defined.
| |
| Supported: "database", "eloquent" | Supported: "database", "eloquent"
| |
*/ */
'providers' => [ 'providers' => [
'users' => [ 'users' => [
'driver' => 'eloquent', 'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class), 'model' => env('AUTH_MODEL', App\Models\User::class),
], ],
// 'users' => [ // 'users' => [
// 'driver' => 'database', // 'driver' => 'database',
// 'table' => 'users', // 'table' => 'users',
// ], // ],
], ],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Resetting Passwords | Resetting Passwords
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| These configuration options specify the behavior of Laravel's password | These configuration options specify the behavior of Laravel's password
| reset functionality, including the table utilized for token storage | reset functionality, including the table utilized for token storage
| and the user provider that is invoked to actually retrieve users. | and the user provider that is invoked to actually retrieve users.
| |
| The expiry time is the number of minutes that each reset token will be | The expiry time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so | considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed. | they have less time to be guessed. You may change this as needed.
| |
| The throttle setting is the number of seconds a user must wait before | The throttle setting is the number of seconds a user must wait before
| generating more password reset tokens. This prevents the user from | generating more password reset tokens. This prevents the user from
| quickly generating a very large amount of password reset tokens. | quickly generating a very large amount of password reset tokens.
| |
*/ */
'passwords' => [ 'passwords' => [
'users' => [ 'users' => [
'provider' => 'users', 'provider' => 'users',
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
'expire' => 60, 'expire' => 60,
'throttle' => 60, 'throttle' => 60,
], ],
], ],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Password Confirmation Timeout | Password Confirmation Timeout
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Here you may define the amount of seconds before a password confirmation | Here you may define the amount of seconds before a password confirmation
| window expires and users are asked to re-enter their password via the | window expires and users are asked to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours. | confirmation screen. By default, the timeout lasts for three hours.
| |
*/ */
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
]; 'social' => [
'google' => [
'name' => 'Google',
'id' => 'google',
'class' => 'btn-danger',
'icon' => 'fab fa-google',
],
],
];

View File

@ -42,6 +42,12 @@ return [
'guid' => env('EZYPAY_GUID'), 'guid' => env('EZYPAY_GUID'),
], ],
'google' => [
'client_id' => env('AUTH_GOOGLE_CLIENT_ID'),
'client_secret' => env('AUTH_GOOGLE_SECRET'),
'redirect' => '/auth/google/callback',
],
'provider' => [ 'provider' => [
'intuit' => [ 'intuit' => [
'api'=> \Intuit\API::class, 'api'=> \Intuit\API::class,

View File

@ -0,0 +1,21 @@
@component('mail::message',['site'=>$site,'heading'=>'Link Your Account'])
Hi {{ isset($user) ? $user->full_name.',' : '' }}
A request was made to link your account to a social login.
If you didnt make this request, you can ignore this, and the request will be ignored.
If you did make the request, then please enter the code displayed below.
@component('mail::panel')
{{ $token }}
@endcomponent
Once you've keyed in this code, you'll be able to login to your account using your social login instead of a username and a password.
Thanks,
{{ config('mail.from.name') }}
@component('mail::subcopy')
If you didnt make this request, you can safely ignore this email - no change was made to your account, nor was it accessed by an unauthorised person.
@endcomponent
@endcomponent

View File

@ -0,0 +1,80 @@
@extends('adminlte::layouts.auth')
@section('htmlheader_title')
Link Account
@endsection
@section('content')
<div class="login-box">
<div class="login-logo">
<a>{!! config('app.name_html_long') !!}</a>
</div>
<div class="alert alert-success">
<strong>NOTE:</strong> Link your account.<br><br>
<ul>
<li>An email has been sent to you with a token, please use those details here:</li>
</ul>
</div>
@if (count($errors) > 0)
<div class="alert alert-danger">
<strong>Whoops!</strong> {{ trans('adminlte_lang::message.someproblems') }}<br><br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<!-- /.login-logo -->
<div class="card">
<div class="card-body login-card-body">
<p class="login-box-msg">Link your account</p>
<form method="post" action="{{ url('/auth/'.$provider.'/linkcomplete') }}">
{{ csrf_field() }}
<input type="hidden" name="oauthid" value="{{ $oauthid }}">
<div class="input-group mb-3">
<input type="email" name="email" class="form-control" placeholder="Email">
<div class="input-group-append">
<span class="fa fa-envelope input-group-text"></span>
</div>
</div>
<div class="input-group mb-3">
<input type="password" name="token" class="form-control" placeholder="Token">
<div class="input-group-append">
<span class="fa fa-lock input-group-text"></span>
</div>
</div>
<div class="row">
<div class="col-8">
&nbsp;
</div>
<!-- /.col -->
<div class="col-4">
<button type="submit" name="submit" class="btn btn-primary btn-block btn-flat">Link</button>
</div>
<!-- /.col -->
</div>
</form>
<p class="mb-1">
<a name="reset" href="{{ url('/password/reset') }}">{{ trans('adminlte_lang::message.forgotpassword') }}</a>
</p>
@isset($register)
<p class="mb-0">
<a href="register.html" class="text-center">Register a new account</a>
</p>
@endisset
</div>
<!-- /.login-card-body -->
</div>
</div>
<!-- /.login-box -->
@endsection

View File

@ -62,6 +62,8 @@ Route::get('pay/paypal/capture',[PaypalController::class,'capture']);
Route::get('auth/{socialProvider}',[SocialLoginController::class,'redirectToProvider']); Route::get('auth/{socialProvider}',[SocialLoginController::class,'redirectToProvider']);
Route::get('auth/{socialProvider}/callback',[SocialLoginController::class,'handleProviderCallback']); Route::get('auth/{socialProvider}/callback',[SocialLoginController::class,'handleProviderCallback']);
Route::get('auth/{socialProvider}/token',[SocialLoginController::class,'handleBearerTokenCallback']); Route::get('auth/{socialProvider}/token',[SocialLoginController::class,'handleBearerTokenCallback']);
Route::get('auth/{socialProvider}/link',[SocialLoginController::class,'link']);
Route::post('auth/{socialProvider}/linkcomplete',[SocialLoginController::class,'linkcomplete']);
// Return from user switch // Return from user switch
Route::get('admin/switch/stop',[SwitchUserController::class,'switch_stop']) Route::get('admin/switch/stop',[SwitchUserController::class,'switch_stop'])