create Socialite Login/Register

This commit is contained in:
Milos Holba
2021-12-16 14:07:33 +01:00
parent 73cd950054
commit f37c4f62b7
18 changed files with 509 additions and 1504 deletions

View File

@@ -25,6 +25,7 @@
"laravel/fortify": "^1.8.3",
"laravel/framework": "^8.69.0",
"laravel/sanctum": "^2.12.1",
"laravel/socialite": "^5.2",
"laravel/tinker": "^2.6.2",
"laravel/ui": "^3.3.1",
"league/flysystem-aws-s3-v3": "^1.0.29",

147
composer.lock generated
View File

@@ -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": "c617bc30d5d9ccd44d3ec626ca2670d9",
"content-hash": "5180bfc14ac207e6f3ac4baefab85ecc",
"packages": [
{
"name": "amphp/amp",
@@ -3387,6 +3387,75 @@
},
"time": "2021-10-07T14:00:57+00:00"
},
{
"name": "laravel/socialite",
"version": "v5.2.6",
"source": {
"type": "git",
"url": "https://github.com/laravel/socialite.git",
"reference": "b5c67f187ddcf15529ff7217fa735b132620dfac"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/socialite/zipball/b5c67f187ddcf15529ff7217fa735b132620dfac",
"reference": "b5c67f187ddcf15529ff7217fa735b132620dfac",
"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-12-07T16:32:57+00:00"
},
{
"name": "laravel/tinker",
"version": "v2.6.2",
@@ -3958,6 +4027,82 @@
],
"time": "2021-09-25T08:23:19+00:00"
},
{
"name": "league/oauth1-client",
"version": "v1.10.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/oauth1-client.git",
"reference": "88dd16b0cff68eb9167bfc849707d2c40ad91ddc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/88dd16b0cff68eb9167bfc849707d2c40ad91ddc",
"reference": "88dd16b0cff68eb9167bfc849707d2c40ad91ddc",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-openssl": "*",
"guzzlehttp/guzzle": "^6.0|^7.0",
"guzzlehttp/psr7": "^1.7|^2.0",
"php": ">=7.1||>=8.0"
},
"require-dev": {
"ext-simplexml": "*",
"friendsofphp/php-cs-fixer": "^2.17",
"mockery/mockery": "^1.3.3",
"phpstan/phpstan": "^0.12.42",
"phpunit/phpunit": "^7.5||9.5"
},
"suggest": {
"ext-simplexml": "For decoding XML-based responses."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev",
"dev-develop": "2.0-dev"
}
},
"autoload": {
"psr-4": {
"League\\OAuth1\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ben Corlett",
"email": "bencorlett@me.com",
"homepage": "http://www.webcomm.com.au",
"role": "Developer"
}
],
"description": "OAuth 1.0 Client Library",
"keywords": [
"Authentication",
"SSO",
"authorization",
"bitbucket",
"identity",
"idp",
"oauth",
"oauth1",
"single sign on",
"trello",
"tumblr",
"twitter"
],
"support": {
"issues": "https://github.com/thephpleague/oauth1-client/issues",
"source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.0"
},
"time": "2021-08-15T23:05:49+00:00"
},
{
"name": "maennchen/zipstream-php",
"version": "2.1.0",

View File

@@ -161,6 +161,7 @@ return [
Illuminate\View\ViewServiceProvider::class,
TeamTNT\Scout\TNTSearchScoutServiceProvider::class,
Laravel\Socialite\SocialiteServiceProvider::class,
Intervention\Image\ImageServiceProvider::class,
App\Providers\FortifyServiceProvider::class,
@@ -228,6 +229,7 @@ return [
'Image' => Intervention\Image\Facades\Image::class,
'Stripe' => Cartalyst\Stripe\Laravel\Facades\Stripe::class,
'Crawler' => Jaybizzle\LaravelCrawlerDetect\Facades\LaravelCrawlerDetect::class,
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
],
'deploy_secret' => env('APP_DEPLOY_SECRET'),

View File

@@ -33,4 +33,22 @@ return [
'client_id' => env('PASSPORT_CLIENT_ID'),
'client_secret' => env('PASSPORT_CLIENT_SECRET'),
],
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('APP_URL') . '/socialite/google/callback'
],
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => env('APP_URL') . '/socialite/github/callback',
],
'facebook' => [
'client_id' => env('FACEBOOK_CLIENT_ID'),
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
'redirect' => env('APP_URL') . '/socialite/facebook/callback',
],
];

View File

@@ -18,7 +18,7 @@ class CreateUsersTable extends Migration
$table->enum('role', ['admin', 'user'])->default('user');
$table->string('email')->unique()->index();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('password')->nullable();
$table->rememberToken();
$table->timestamps();
$table->charset = 'utf8mb4';

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,59 @@
<template>
<div class="wrapper">
<div class="icon-wrapper">
<facebook-icon @click="socialite('facebook')" class="icon"/>
<github-icon @click="socialite('github')" class="icon"/>
<h1 @click="socialite('google')" class="icon">G</h1>
</div>
</div>
</template>
<script>
import { FacebookIcon, GithubIcon } from 'vue-feather-icons'
export default {
name:'SocialiteAuthenticationButtons',
components: {
FacebookIcon,
GithubIcon,
},
methods: {
socialite(provider) {
this.isLoading = true
axios
.get(`/api/socialite/${provider}/redirect`)
.then((response) => {
if(response.data.url) {
window.location.href = response.data.url
}
})
.catch(() => this.$isSomethingWrong())
},
}
}
</script>
<style lang="scss" scoped>
.wrapper {
display: flex;
justify-content: center;
margin: 50px 0px 0px 0px;
.icon-wrapper {
width: 200px;
display: flex;
align-content: center;
justify-content: space-between;
.icon {
align-self: center;
cursor: pointer;
}
}
}
</style>

View File

@@ -1,4 +1,13 @@
const routesAuth = [
{
name: 'SocialiteCallback',
path: '/socialite/:provider/callback',
component: () =>
import(/* webpackChunkName: "chunks/email-verified" */ '../views/Auth/SocialiteCallback'),
meta: {
requiresAuth: false
},
},
{
name: 'SuccessfullyVerified',
path: '/successfully-verified',

View File

@@ -22,6 +22,8 @@
:disabled="isLoading" />
</ValidationObserver>
<SocialiteAuthenticationButtons/>
<span v-if="config.userRegistration" class="additional-link">
{{ $t('page_login.registration_text') }}
<router-link class="text-theme" :to="{name: 'SignUp'}">
@@ -151,6 +153,7 @@
<script>
import AuthContentWrapper from '/resources/js/components/Auth/AuthContentWrapper'
import {ValidationObserver, ValidationProvider} from 'vee-validate/dist/vee-validate.full'
import SocialiteAuthenticationButtons from '/resources/js/components/Auth/SocialiteAuthenticationButtons'
import AuthContent from '/resources/js/components/Auth/AuthContent'
import AuthButton from '/resources/js/components/Auth/AuthButton'
import Spinner from '/resources/js/components/FilesView/Spinner'
@@ -162,10 +165,11 @@
export default {
name: 'SignIn',
components: {
Headline,
Headline,
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SocialiteAuthenticationButtons,
AuthContent,
AuthButton,
Spinner,

View File

@@ -62,6 +62,8 @@
</div>
</ValidationObserver>
<SocialiteAuthenticationButtons/>
<span class="additional-link">{{ $t('page_registration.have_an_account') }}
<router-link :to="{name: 'SignIn'}" class="text-theme">
{{ $t('page_forgotten_password.password_remember_button') }}
@@ -78,6 +80,7 @@
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContent from '/resources/js/components/Auth/AuthContent'
import AuthButton from '/resources/js/components/Auth/AuthButton'
import SocialiteAuthenticationButtons from '/resources/js/components/Auth/SocialiteAuthenticationButtons'
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import {events} from '/resources/js/bus'
@@ -86,6 +89,7 @@
export default {
name: 'SignUp',
components: {
SocialiteAuthenticationButtons,
AuthContentWrapper,
ValidationProvider,
ValidationObserver,

View File

@@ -0,0 +1,33 @@
<template>
<div>
<Spinner/>
</div>
</template>
<script>
import Spinner from '/resources/js/components/FilesView/Spinner'
export default {
name: 'SocialiteCallback',
components: {Spinner},
created () {
axios
.get(`/api${this.$route.fullPath}`)
.then(() => {
// Set login state
this.$store.commit('SET_AUTHORIZED', true)
// Go to files page
this.$router.push({name: 'Files'})
})
.catch((error) => {
this.$isSomethingWrong()
this.$router.push({name: 'Homepage'})
})
}
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<PageTab>
<PageTabGroup>
<PageTabGroup v-if="! user.data.attributes.socialite_account">
<ValidationObserver ref="password" @submit.prevent="resetPassword" v-slot="{ invalid }" tag="form" class="form block-form">
<FormLabel>{{ $t('user_password.title') }}</FormLabel>
<div class="block-wrapper">
@@ -32,7 +32,7 @@
</div>
</ValidationObserver>
</PageTabGroup>
<PageTabGroup class="form block-form">
<PageTabGroup v-if="! user.data.attributes.socialite_account" class="form block-form">
<FormLabel icon="smartphone">{{ $t('2fa.settings.title') }}</FormLabel>
<div class="block-wrapper">
<div class="input-wrapper">

View File

@@ -1,7 +1,6 @@
<?php
use Domain\Zip\Controllers\ZipController;
use App\Users\Actions\CreateNewUserAction;
use Domain\Pages\Controllers\PagesController;
use Domain\Sharing\Controllers\ShareController;
use Domain\Trash\Controllers\DumpTrashController;
@@ -24,6 +23,8 @@ use Domain\Browsing\Controllers\BrowseSharedItemsController;
use Domain\Browsing\Controllers\BrowseTrashContentController;
use Domain\Homepage\Controllers\SendContactMessageController;
use Domain\Browsing\Controllers\SearchFilesAndFoldersController;
use App\Users\Controllers\Authentication\RegisterAuthenticationController;
use App\Users\Controllers\Authentication\SocialiteAuthenticationController;
// Pages
Route::apiResource('/page', PagesController::class);
@@ -34,7 +35,13 @@ Route::get('/pricing', ActivePlansController::class);
Route::get('/settings', GetSettingsValueController::class);
// Register user
Route::post('/register', CreateNewUserAction::class);
Route::post('/register', RegisterAuthenticationController::class);
// Login via socialite
Route::group(['prefix' => 'socialite'], function () {
Route::get('/{provider}/redirect', [SocialiteAuthenticationController::class, 'redirect']);
Route::get('/{provider}/callback', [SocialiteAuthenticationController::class, 'callback']);
});
// Password reset
Route::group(['prefix' => 'password'], function () {

View File

@@ -22,12 +22,14 @@ class CreateNewUserAction extends Controller
* Validate and create a new user.
*/
public function __invoke(
RegisterUserRequest $request
$data
): Application | ResponseFactory | Response {
$settings = get_settings([
'storage_default', 'registration', 'user_verification',
]);
$socialite_auth = ! isset($data['password']) ?? true;
// Check if account registration is enabled
if (! intval($settings['registration'])) {
abort(401);
@@ -35,12 +37,12 @@ class CreateNewUserAction extends Controller
// Create user
$user = User::create([
'password' => bcrypt($request->input('password')),
'email' => $request->input('email'),
'password' => ! $socialite_auth ? bcrypt($data['password']) : null,
'email' => $data['email'],
]);
// Mark as verified if verification is disabled
if (! intval($settings['user_verification'])) {
if (! intval($settings['user_verification']) || $socialite_auth) {
$user->markEmailAsVerified();
}
@@ -49,8 +51,9 @@ class CreateNewUserAction extends Controller
$user
->settings()
->create([
'name' => $request->input('name'),
'name' => $data['name'],
'storage_capacity' => $settings['storage_default'],
'avatar' => $data->avatar ? $data->avatar : null,
]);
UserSettings::reguard();
@@ -58,7 +61,7 @@ class CreateNewUserAction extends Controller
event(new Registered($user));
// Log in if verification is disabled
if (! intval($settings['user_verification'])) {
if (! intval($settings['user_verification']) || $socialite_auth) {
$this->guard->login($user);
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Users\Controllers\Authentication;
use App\Http\Controllers\Controller;
use App\Users\Actions\CreateNewUserAction;
use App\Users\Requests\RegisterUserRequest;
class RegisterAuthenticationController extends Controller
{
public function __construct(
public CreateNewUserAction $createNewUser,
) {
}
public function __invoke(RegisterUserRequest $request)
{
($this->createNewUser)($request);
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Users\Controllers\Authentication;
use App\Users\Models\User;
use App\Http\Controllers\Controller;
use Laravel\Socialite\Facades\Socialite;
use App\Users\Actions\CreateNewUserAction;
use Illuminate\Contracts\Auth\StatefulGuard;
class SocialiteAuthenticationController extends Controller
{
public function __construct(
protected StatefulGuard $guard,
public CreateNewUserAction $createNewUser,
) {
}
public function redirect($provider)
{
$url = Socialite::driver($provider)->stateless()->redirect()->getTargetUrl();
return response()->json([
'url' => $url
]);
}
public function callback($provider)
{
// Get socialite user
$provider_user = Socialite::driver($provider)->stateless()->user();
// Check if user exist already
$user = User::whereEmail($provider_user->email)->first();
if($user) {
// Login User
$this->guard->login($user);
} else {
// Add user avatar from socialite
$provider_user->avatar = get_socialite_avatar($provider_user->avatar);
// Create User
($this->createNewUser)($provider_user);
}
return response('Loged in', 200);
}
}

View File

@@ -27,6 +27,7 @@ class UserResource extends JsonResource
'email' => is_demo() ? obfuscate_email($this->email) : $this->email,
'role' => $this->role,
'two_factor_authentication' => $this->two_factor_secret ? true : false,
'socialite_account' => $this->password ? false : true,
'folders' => $this->folder_tree,
'storage' => $this->storage,
'created_at_formatted' => format_date($this->created_at, '%d. %B. %Y'),

View File

@@ -1012,4 +1012,35 @@ if (! function_exists('replace_occurrence')) {
$string
);
}
if(! function_exists('get_socialite_avatar')) {
/**
* Get socialite avatar create and store his thumbnails
*/
function get_socialite_avatar($avatar)
{
$image = file_get_contents($avatar);
// Generate avatar name
$avatar_name = Str::uuid() . '.jpg';
// Create intervention image
$intervention = Image::make($image);
// Generate avatar sizes
collect(config('vuefilemanager.avatar_sizes'))
->each(function ($size) use ($intervention, $avatar_name) {
// fit thumbnail
$intervention->fit($size['size'], $size['size'])->stream();
// Store thumbnail to disk
Storage::put("avatars/{$size['name']}-{$avatar_name}", $intervention);
});
// Return path to image
return $avatar_name;
}
}
}