Skip to content

Commit

Permalink
Add ability to create user on the fly on Oauth auth step (#2475)
Browse files Browse the repository at this point in the history
  • Loading branch information
ildyria committed Jun 25, 2024
1 parent a0bcb4d commit 446432b
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 19 deletions.
3 changes: 2 additions & 1 deletion app/Actions/User/Create.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Create
* @throws InvalidPropertyException
* @throws ModelDBException
*/
public function do(string $username, string $password, bool $mayUpload, bool $mayEditOwnSettings): User
public function do(string $username, string $password, ?string $email = null, bool $mayUpload = false, bool $mayEditOwnSettings = false): User
{
if (User::query()->where('username', '=', $username)->count() !== 0) {
throw new ConflictingPropertyException('Username already exists');
Expand All @@ -24,6 +24,7 @@ public function do(string $username, string $password, bool $mayUpload, bool $ma
$user->may_edit_own_settings = $mayEditOwnSettings;
$user->may_administrate = false;
$user->username = $username;
$user->email = $email;
$user->password = Hash::make($password);
$user->save();

Expand Down
6 changes: 5 additions & 1 deletion app/Console/Commands/UserManagment/CreateUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ public function handle(): int
$mayEditOwnSettings = $mayAdministrate || $this->option('may-edit-own-settings') === true;
$mayUpload = $mayAdministrate || $this->option('may-upload') === true;

$user = $this->create->do($username, $password, $mayUpload, $mayEditOwnSettings);
$user = $this->create->do(
username: $username,
password: $password,
mayUpload: $mayUpload,
mayEditOwnSettings: $mayEditOwnSettings);
$user->may_administrate = $mayAdministrate;
$user->save();

Expand Down
6 changes: 5 additions & 1 deletion app/Http/Controllers/Administration/UsersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ public function delete(DeleteUserRequest $request): void
*/
public function create(AddUserRequest $request, Create $create): UserManagementResource
{
$user = $create->do($request->username(), $request->password(), $request->mayUpload(), $request->mayEditOwnSettings());
$user = $create->do(
username: $request->username(),
password: $request->password(),
mayUpload: $request->mayUpload(),
mayEditOwnSettings: $request->mayEditOwnSettings());

return UserManagementResource::make($user)->setStatus(201);
}
Expand Down
97 changes: 85 additions & 12 deletions app/Http/Controllers/Oauth.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

namespace App\Http\Controllers;

use App\Actions\User\Create;
use App\Enum\OauthProvidersType;
use App\Exceptions\Internal\LycheeInvalidArgumentException;
use App\Exceptions\Internal\LycheeLogicException;
use App\Exceptions\UnauthenticatedException;
use App\Exceptions\UnauthorizedException;
use App\Models\Configs;
use App\Models\OauthCredential;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
Expand All @@ -15,6 +17,7 @@
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Session;
use Laravel\Socialite\Contracts\User as ContractsUser;
use Laravel\Socialite\Facades\Socialite;
use Symfony\Component\HttpFoundation\RedirectResponse as HttpFoundationRedirectResponse;

Expand Down Expand Up @@ -109,23 +112,76 @@ public function register(string $provider)
*/
private function authenticateOrDie(OauthProvidersType $provider)
{
$user = Socialite::driver($provider->value)->user();
/** @var ContractsUser */
$user = $this->getUserFromOauth($provider);

$credential = OauthCredential::query()
->with(['user'])
->where('token_id', '=', $user->getId())
->where('provider', '=', $provider)
->first();
$credential = $this->fetchAssociatedUserFromDB($provider, $user->getId());

if ($credential !== null) {
Auth::login($credential->user);

if ($credential === null) {
return redirect(route('livewire-gallery'));
}

if (!Configs::getValueAsBool('oauth_create_user_on_first_attempt')) {
throw new UnauthorizedException('User not found!');
}

Auth::login($credential->user);
if (User::query()->where('username', '=', $user->getName() ?? $user->getEmail() ?? $user->getId())
->when(
$user->getEmail() !== null && $user->getEmail() !== '',
fn ($q) => $q->orWhere('email', '=', $user->getEmail())
)->exists()) {
throw new UnauthorizedException('User already exists!');
}

$create = resolve(Create::class);
$new_user = $create->do(
username: $user->getName() ?? $user->getEmail() ?? $user->getId(),
email: $user->getEmail(),
password: strtr(base64_encode(random_bytes(8)), '+/', '-_'),
mayUpload: Configs::getValueAsBool('oauth_grant_new_user_upload_rights'),
mayEditOwnSettings: Configs::getValueAsBool('oauth_grant_new_user_modification_rights'));

Auth::login($new_user);

$this->saveOauth(
provider: $provider,
authedUser_id: $new_user->id,
oauth_id: $user->getId());

return redirect(route('livewire-gallery'));
}

/**
* Get the user from the driver.
*
* @param OauthProvidersType $provider
*
* @return ContractsUser
*/
private function getUserFromOauth(OauthProvidersType $provider): ContractsUser
{
return Socialite::driver($provider->value)->user();
}

/**
* Fetch the Oauth credential and user associated.
*
* @param OauthProvidersType $provider Oauth provider
* @param string $user_id to fetch with
*
* @return OauthCredential|null credential if found
*/
private function fetchAssociatedUserFromDB(OauthProvidersType $provider, string $user_id): OauthCredential|null
{
return OauthCredential::query()
->with(['user'])
->where('token_id', '=', $user_id)
->where('provider', '=', $provider)
->first();
}

/**
* Authenticate and redirect.
*
Expand All @@ -152,13 +208,30 @@ private function registerOrDie(OauthProvidersType $provider)
throw new LycheeLogicException('Oauth credential for that provider already exists.');
}

$this->saveOauth(
provider: $provider,
authedUser_id: $authedUser->id,
oauth_id: $user->getId());

return redirect(route('profile'));
}

/**
* Save a credential for a user.
*
* @param OauthProvidersType $provider of credential
* @param int $authedUser_id user ID already existing in the database
* @param string $oauth_id oauth id on the Oauth server side
*
* @return void
*/
private function saveOauth(OauthProvidersType $provider, int $authedUser_id, string $oauth_id): void
{
$credential = OauthCredential::create([
'provider' => $provider,
'user_id' => $authedUser->id,
'token_id' => $user->getId(),
'user_id' => $authedUser_id,
'token_id' => $oauth_id,
]);
$credential->save();

return redirect(route('profile'));
}
}
8 changes: 4 additions & 4 deletions app/Livewire/Components/Pages/Users.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ public function create(): void

// Create user
$this->create->do(
$this->username,
$this->password,
$this->may_upload,
$this->may_edit_own_settings);
username: $this->username,
password: $this->password,
mayUpload: $this->may_upload,
mayEditOwnSettings: $this->may_edit_own_settings);

// reset attributes and reload user list (triggers refresh)
$this->username = '';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

use App\Models\Extensions\BaseConfigMigration;

return new class() extends BaseConfigMigration {
public const OAUTH = 'OAuth & SSO';

public function getConfigs(): array
{
return [
[
'key' => 'oauth_create_user_on_first_attempt',
'value' => '0',
'is_secret' => true,
'cat' => self::OAUTH,
'type_range' => '0|1',
'description' => 'Allow user creation when oauth id does not exist.',
],
[
'key' => 'oauth_grant_new_user_upload_rights',
'value' => '0',
'is_secret' => true,
'cat' => self::OAUTH,
'type_range' => '0|1',
'description' => 'Newly created user are allowed to upload content.',
],
[
'key' => 'oauth_grant_new_user_modification_rights',
'value' => '0',
'is_secret' => true,
'cat' => self::OAUTH,
'type_range' => '0|1',
'description' => 'Newly created user are allowed to edit their profile.',
],
];
}
};
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ parameters:
- '#Dynamic call to static method (Illuminate\\Database\\Query\\Builder|Illuminate\\Database\\Eloquent\\(Builder|Relations\\.*)|App\\Models\\Builders\\.*|App\\Eloquent\\FixedQueryBuilder|App\\Relations\\.*)(<.*>)?::latest\(\).#'
- '#Dynamic call to static method (Illuminate\\Database\\Query\\Builder|Illuminate\\Database\\Eloquent\\(Builder|Relations\\.*)|App\\Models\\Builders\\.*|App\\Eloquent\\FixedQueryBuilder|App\\Relations\\.*)(<.*>)?::first\(\).#'
- '#Dynamic call to static method (Illuminate\\Database\\Query\\Builder|Illuminate\\Database\\Eloquent\\(Builder|Relations\\.*)|App\\Models\\Builders\\.*|App\\Eloquent\\FixedQueryBuilder|App\\Relations\\.*)(<.*>)?::skip\(\).#'
- '#Dynamic call to static method (Illuminate\\Database\\Query\\Builder|Illuminate\\Database\\Eloquent\\(Builder|Relations\\.*)|App\\Models\\Builders\\.*|App\\Eloquent\\FixedQueryBuilder|App\\Relations\\.*)(<.*>)?::exists\(\).#'
- '#Dynamic call to static method App\\Models\\Builders\\.*::orderByDesc\(\).#'
- '#Dynamic call to static method App\\Models\\Builders\\.*::selectRaw\(\).#'
- '#Call to an undefined method Illuminate\\Database\\Eloquent\\.*::with(Only)?\(\)#'
Expand Down

0 comments on commit 446432b

Please sign in to comment.