mirror of
https://github.com/HDInnovations/UNIT3D-Community-Edition.git
synced 2026-02-08 12:38:53 -06:00
update: use fortify for authentication
This commit is contained in:
115
app/Actions/Fortify/CreateNewUser.php
Normal file
115
app/Actions/Fortify/CreateNewUser.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\Group;
|
||||
use App\Models\Invite;
|
||||
use App\Models\PrivateMessage;
|
||||
use App\Models\User;
|
||||
use App\Repositories\ChatRepository;
|
||||
use App\Rules\EmailBlacklist;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laravel\Fortify\Contracts\CreatesNewUsers;
|
||||
|
||||
class CreateNewUser implements CreatesNewUsers
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
public function __construct(private readonly ChatRepository $chatRepository)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and create a newly registered user.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function create(array $input): User
|
||||
{
|
||||
Validator::make($input, [
|
||||
'username' => 'required|alpha_dash|string|between:3,25|unique:users',
|
||||
'password' => [
|
||||
'required',
|
||||
'confirmed',
|
||||
$this->passwordRules(),
|
||||
],
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:70',
|
||||
'unique:users',
|
||||
Rule::when(config('email-blacklist.enabled') === true, fn () => new EmailBlacklist()),
|
||||
],
|
||||
'captcha' => [
|
||||
Rule::excludeIf(config('captcha.enabled') === false),
|
||||
Rule::when(config('captcha.enabled') === true, 'hiddencaptcha'),
|
||||
],
|
||||
'code' => 'required',
|
||||
])->validate();
|
||||
|
||||
// Make sure open reg is off and invite code exists and has not been used already
|
||||
|
||||
$invite = Invite::query()->where('code', '=', $input['code'])->first();
|
||||
|
||||
if (config('other.invite-only') === true && ($invite === null || $invite->accepted_by !== null)) {
|
||||
return to_route('registrationForm', ['code' => $input['code']])
|
||||
->withErrors(trans('auth.invalid-key'));
|
||||
}
|
||||
|
||||
$validatingGroup = cache()->rememberForever('validating_group', fn () => Group::query()->where('slug', '=', 'validating')->pluck('id'));
|
||||
|
||||
$user = User::create([
|
||||
'username' => $input['username'],
|
||||
'email' => $input['email'],
|
||||
'password' => Hash::make($input['password']),
|
||||
'passkey' => md5(random_bytes(60)),
|
||||
'rsskey' => md5(random_bytes(60)),
|
||||
'uploaded' => config('other.default_upload'),
|
||||
'downloaded' => config('other.default_download'),
|
||||
'style' => config('other.default_style', 0),
|
||||
'locale' => config('app.locale'),
|
||||
'group_id' => $validatingGroup[0],
|
||||
]);
|
||||
|
||||
if ($invite !== null) {
|
||||
$invite->update([
|
||||
'accepted_by' => $user->id,
|
||||
'accepted_at' => new Carbon(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Select A Random Welcome Message
|
||||
|
||||
$profileUrl = href_profile($user);
|
||||
|
||||
$welcomeArray = [
|
||||
sprintf('[url=%s]%s[/url], Welcome to ', $profileUrl, $user->username).config('other.title').'! Hope you enjoy the community :rocket:',
|
||||
sprintf("[url=%s]%s[/url], We've been expecting you :space_invader:", $profileUrl, $user->username),
|
||||
sprintf("[url=%s]%s[/url] has arrived. Party's over. :cry:", $profileUrl, $user->username),
|
||||
sprintf("It's a bird! It's a plane! Nevermind, it's just [url=%s]%s[/url].", $profileUrl, $user->username),
|
||||
sprintf('Ready player [url=%s]%s[/url].', $profileUrl, $user->username),
|
||||
sprintf('A wild [url=%s]%s[/url] appeared.', $profileUrl, $user->username),
|
||||
'Welcome to '.config('other.title').sprintf(' [url=%s]%s[/url]. We were expecting you ( ͡° ͜ʖ ͡°)', $profileUrl, $user->username),
|
||||
];
|
||||
|
||||
$this->chatRepository->systemMessage(
|
||||
$welcomeArray[array_rand($welcomeArray)]
|
||||
);
|
||||
|
||||
// Send Welcome PM
|
||||
|
||||
PrivateMessage::create([
|
||||
'sender_id' => 1,
|
||||
'receiver_id' => $user->id,
|
||||
'subject' => config('welcomepm.subject'),
|
||||
'message' => config('welcomepm.message'),
|
||||
]);
|
||||
|
||||
return to_route('login')
|
||||
->withSuccess(trans('auth.register-thanks'));
|
||||
}
|
||||
}
|
||||
22
app/Actions/Fortify/PasswordValidationRules.php
Normal file
22
app/Actions/Fortify/PasswordValidationRules.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use Illuminate\Validation\Rules\Password as Password;
|
||||
|
||||
trait PasswordValidationRules
|
||||
{
|
||||
/**
|
||||
* Get the validation rules used to validate passwords.
|
||||
*
|
||||
* @return array<int, \Illuminate\Contracts\Validation\Rule|array|string>
|
||||
*/
|
||||
protected function passwordRules(): array
|
||||
{
|
||||
return [
|
||||
'required',
|
||||
'string',
|
||||
Password::min(12)->mixedCase()->letters()->numbers()->uncompromised(),
|
||||
];
|
||||
}
|
||||
}
|
||||
43
app/Actions/Fortify/ResetUserPassword.php
Normal file
43
app/Actions/Fortify/ResetUserPassword.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\Group;
|
||||
use App\Models\User;
|
||||
use App\Models\UserActivation;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Laravel\Fortify\Contracts\ResetsUserPasswords;
|
||||
|
||||
class ResetUserPassword implements ResetsUserPasswords
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and reset the user's forgotten password.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function reset(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'password' => $this->passwordRules(),
|
||||
])->validate();
|
||||
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($input['password']),
|
||||
])->save();
|
||||
|
||||
$validatingGroup = cache()->rememberForever('validating_group', fn () => Group::query()->where('slug', '=', 'validating')->pluck('id'));
|
||||
$memberGroup = cache()->rememberForever('member_group', fn () => Group::query()->where('slug', '=', 'user')->pluck('id'));
|
||||
|
||||
if ($user->group_id === $validatingGroup[0]) {
|
||||
$user->group_id = $memberGroup[0];
|
||||
}
|
||||
|
||||
$user->active = true;
|
||||
$user->save();
|
||||
|
||||
UserActivation::query()->where('user_id', '=', $user->id)->delete();
|
||||
}
|
||||
}
|
||||
32
app/Actions/Fortify/UpdateUserPassword.php
Normal file
32
app/Actions/Fortify/UpdateUserPassword.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserPasswords;
|
||||
|
||||
class UpdateUserPassword implements UpdatesUserPasswords
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and update the user's password.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'current_password' => ['required', 'string', 'current_password:web'],
|
||||
'password' => $this->passwordRules(),
|
||||
], [
|
||||
'current_password.current_password' => __('The provided password does not match your current password.'),
|
||||
])->validateWithBag('updatePassword');
|
||||
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($input['password']),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
58
app/Actions/Fortify/UpdateUserProfileInformation.php
Normal file
58
app/Actions/Fortify/UpdateUserProfileInformation.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||
|
||||
class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
||||
{
|
||||
/**
|
||||
* Validate and update the given user's profile information.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique('users')->ignore($user->id),
|
||||
],
|
||||
])->validateWithBag('updateProfileInformation');
|
||||
|
||||
if ($input['email'] !== $user->email &&
|
||||
$user instanceof MustVerifyEmail) {
|
||||
$this->updateVerifiedUser($user, $input);
|
||||
} else {
|
||||
$user->forceFill([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given verified user's profile information.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
protected function updateVerifiedUser(User $user, array $input): void
|
||||
{
|
||||
$user->forceFill([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'email_verified_at' => null,
|
||||
])->save();
|
||||
|
||||
$user->sendEmailVerificationNotification();
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* NOTICE OF LICENSE.
|
||||
*
|
||||
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
|
||||
* The details is bundled with this project in the file LICENSE.txt.
|
||||
*
|
||||
* @project UNIT3D Community Edition
|
||||
*
|
||||
* @author HDVinnie <hdinnovations@protonmail.com>
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Group;
|
||||
use App\Models\UserActivation;
|
||||
use App\Services\Unit3dAnnounce;
|
||||
|
||||
/**
|
||||
* @see \Tests\Feature\Http\Controllers\Auth\ActivationControllerTest
|
||||
*/
|
||||
class ActivationController extends Controller
|
||||
{
|
||||
public function activate($token): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$bannedGroup = cache()->rememberForever('banned_group', fn () => Group::where('slug', '=', 'banned')->pluck('id'));
|
||||
$memberGroup = cache()->rememberForever('member_group', fn () => Group::where('slug', '=', 'user')->pluck('id'));
|
||||
|
||||
$activation = UserActivation::with('user')->where('token', '=', $token)->firstOrFail();
|
||||
if ($activation->user->id && $activation->user->group->id != $bannedGroup[0]) {
|
||||
$activation->user->active = 1;
|
||||
$activation->user->can_upload = 1;
|
||||
$activation->user->can_download = 1;
|
||||
$activation->user->can_request = 1;
|
||||
$activation->user->can_comment = 1;
|
||||
$activation->user->can_invite = 1;
|
||||
$activation->user->group_id = $memberGroup[0];
|
||||
$activation->user->save();
|
||||
|
||||
$activation->delete();
|
||||
|
||||
Unit3dAnnounce::addUser($activation->user);
|
||||
|
||||
return to_route('login')
|
||||
->withSuccess(trans('auth.activation-success'));
|
||||
}
|
||||
|
||||
return to_route('login')
|
||||
->withErrors(trans('auth.activation-error'));
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* NOTICE OF LICENSE.
|
||||
*
|
||||
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
|
||||
* The details is bundled with this project in the file LICENSE.txt.
|
||||
*
|
||||
* @project UNIT3D Community Edition
|
||||
*
|
||||
* @author HDVinnie <hdinnovations@protonmail.com>
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ForgotPasswordController extends Controller
|
||||
{
|
||||
use SendsPasswordResetEmails;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
protected function validateEmail(Request $request): void
|
||||
{
|
||||
if (! config('captcha.enabled')) {
|
||||
$request->validate(['email' => 'required|email']);
|
||||
} else {
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
'captcha' => 'hiddencaptcha',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* NOTICE OF LICENSE.
|
||||
*
|
||||
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
|
||||
* The details is bundled with this project in the file LICENSE.txt.
|
||||
*
|
||||
* @project UNIT3D Community Edition
|
||||
*
|
||||
* @author HDVinnie <hdinnovations@protonmail.com>
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Notifications\UsernameReminder;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* @see \Tests\Feature\Http\Controllers\Auth\ForgotUsernameControllerTest
|
||||
*/
|
||||
class ForgotUsernameController extends Controller
|
||||
{
|
||||
/**
|
||||
* Forgot Username Form.
|
||||
*/
|
||||
public function showForgotUsernameForm(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
{
|
||||
return view('auth.username');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Username Reminder.
|
||||
*/
|
||||
public function sendUsernameReminder(Request $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$email = $request->get('email');
|
||||
|
||||
if (! config('captcha.enabled')) {
|
||||
$v = validator($request->all(), [
|
||||
'email' => 'required',
|
||||
]);
|
||||
} else {
|
||||
$v = validator($request->all(), [
|
||||
'email' => 'required',
|
||||
'captcha' => 'hiddencaptcha',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($v->fails()) {
|
||||
return to_route('username.request')
|
||||
->withErrors($v->errors());
|
||||
}
|
||||
|
||||
$user = User::where('email', '=', $email)->first();
|
||||
if (empty($user)) {
|
||||
return to_route('username.request')
|
||||
->withErrors(trans('email.no-email-found'));
|
||||
}
|
||||
|
||||
//send username reminder notification
|
||||
$user->notify(new UsernameReminder());
|
||||
|
||||
return to_route('login')
|
||||
->withSuccess(trans('email.username-sent'));
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* NOTICE OF LICENSE.
|
||||
*
|
||||
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
|
||||
* The details is bundled with this project in the file LICENSE.txt.
|
||||
*
|
||||
* @project UNIT3D Community Edition
|
||||
*
|
||||
* @author HDVinnie <hdinnovations@protonmail.com>
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Group;
|
||||
use App\Services\Unit3dAnnounce;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
use AuthenticatesUsers;
|
||||
|
||||
// Upon Successful Login
|
||||
protected string $redirectTo = '/';
|
||||
|
||||
// Max Attempts Until Lockout
|
||||
public int $maxAttempts = 3;
|
||||
|
||||
// Minutes Lockout
|
||||
public int $decayMinutes = 60;
|
||||
|
||||
/**
|
||||
* LoginController Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('guest', ['except' => 'logout']);
|
||||
}
|
||||
|
||||
public function username(): string
|
||||
{
|
||||
return 'username';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate The User Login Request.
|
||||
*
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
protected function validateLogin(Request $request): void
|
||||
{
|
||||
if (config('captcha.enabled')) {
|
||||
$this->validate($request, [
|
||||
$this->username() => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'captcha' => 'hiddencaptcha',
|
||||
]);
|
||||
} else {
|
||||
$this->validate($request, [
|
||||
$this->username() => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function authenticated(Request $request, $user): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$bannedGroup = cache()->rememberForever('banned_group', fn () => Group::where('slug', '=', 'banned')->pluck('id'));
|
||||
$validatingGroup = cache()->rememberForever('validating_group', fn () => Group::where('slug', '=', 'validating')->pluck('id'));
|
||||
$disabledGroup = cache()->rememberForever('disabled_group', fn () => Group::where('slug', '=', 'disabled')->pluck('id'));
|
||||
$memberGroup = cache()->rememberForever('member_group', fn () => Group::where('slug', '=', 'user')->pluck('id'));
|
||||
|
||||
if ($user->active == 0 || $user->group_id == $validatingGroup[0]) {
|
||||
$this->guard()->logout();
|
||||
$request->session()->invalidate();
|
||||
|
||||
return to_route('login')
|
||||
->withErrors(trans('auth.not-activated'));
|
||||
}
|
||||
|
||||
if ($user->group_id == $bannedGroup[0]) {
|
||||
$this->guard()->logout();
|
||||
$request->session()->invalidate();
|
||||
|
||||
return to_route('login')
|
||||
->withErrors(trans('auth.banned'));
|
||||
}
|
||||
|
||||
if ($user->group_id == $disabledGroup[0]) {
|
||||
$user->group_id = $memberGroup[0];
|
||||
$user->can_upload = 1;
|
||||
$user->can_download = 1;
|
||||
$user->can_comment = 1;
|
||||
$user->can_invite = 1;
|
||||
$user->can_request = 1;
|
||||
$user->can_chat = 1;
|
||||
$user->disabled_at = null;
|
||||
$user->save();
|
||||
|
||||
cache()->forget('user:'.$user->passkey);
|
||||
Unit3dAnnounce::addUser($user);
|
||||
|
||||
return to_route('home.index')
|
||||
->withSuccess(trans('auth.welcome-restore'));
|
||||
}
|
||||
|
||||
if (auth()->viaRemember() && $user->group_id == $disabledGroup[0]) {
|
||||
$user->group_id = $memberGroup[0];
|
||||
$user->can_upload = 1;
|
||||
$user->can_download = 1;
|
||||
$user->can_comment = 1;
|
||||
$user->can_invite = 1;
|
||||
$user->can_request = 1;
|
||||
$user->can_chat = 1;
|
||||
$user->disabled_at = null;
|
||||
$user->save();
|
||||
|
||||
cache()->forget('user:'.$user->passkey);
|
||||
Unit3dAnnounce::addUser($user);
|
||||
|
||||
return to_route('home.index')
|
||||
->withSuccess(trans('auth.welcome-restore'));
|
||||
}
|
||||
|
||||
if ($user->read_rules == 0) {
|
||||
return redirect()->to(config('other.rules_url'))
|
||||
->withWarning(trans('auth.require-rules'));
|
||||
}
|
||||
|
||||
return redirect()->intended()
|
||||
->withSuccess(trans('auth.welcome'));
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* NOTICE OF LICENSE.
|
||||
*
|
||||
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
|
||||
* The details is bundled with this project in the file LICENSE.txt.
|
||||
*
|
||||
* @project UNIT3D Community Edition
|
||||
*
|
||||
* @author HDVinnie <hdinnovations@protonmail.com>
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\SendActivationMail;
|
||||
use App\Models\Group;
|
||||
use App\Models\Invite;
|
||||
use App\Models\PrivateMessage;
|
||||
use App\Models\User;
|
||||
use App\Models\UserActivation;
|
||||
use App\Models\UserNotification;
|
||||
use App\Models\UserPrivacy;
|
||||
use App\Repositories\ChatRepository;
|
||||
use App\Rules\EmailBlacklist;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class RegisterController extends Controller
|
||||
{
|
||||
/**
|
||||
* RegisterController Constructor.
|
||||
*/
|
||||
public function __construct(private readonly ChatRepository $chatRepository)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Registration Form.
|
||||
*/
|
||||
public function registrationForm($code = null): \Illuminate\Contracts\View\Factory|\Illuminate\View\View|\Illuminate\Http\RedirectResponse
|
||||
{
|
||||
// Make sure open reg is off, invite code is not present and application signups enabled
|
||||
if ($code === 'null' && config('other.invite-only') == 1 && config('other.application_signups')) {
|
||||
return to_route('application.create')
|
||||
->withInfo(trans('auth.allow-invite-appl'));
|
||||
}
|
||||
|
||||
// Make sure open reg is off and invite code is not present
|
||||
if ($code === 'null' && config('other.invite-only') == 1) {
|
||||
return to_route('login')
|
||||
->withWarning(trans('auth.allow-invite'));
|
||||
}
|
||||
|
||||
return view('auth.register', ['code' => $code]);
|
||||
}
|
||||
|
||||
public function register(Request $request, $code = null): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
// Make sure open reg is off and invite code exist and has not been used already
|
||||
$key = Invite::where('code', '=', $code)->first();
|
||||
if (config('other.invite-only') == 1 && (! $key || $key->accepted_by !== null)) {
|
||||
return to_route('registrationForm', ['code' => $code])
|
||||
->withErrors(trans('auth.invalid-key'));
|
||||
}
|
||||
|
||||
$validatingGroup = cache()->rememberForever('validating_group', fn () => Group::where('slug', '=', 'validating')->pluck('id'));
|
||||
|
||||
$user = new User();
|
||||
$user->username = $request->input('username');
|
||||
$user->email = $request->input('email');
|
||||
$user->password = Hash::make($request->input('password'));
|
||||
$user->passkey = md5(random_bytes(60).$user->password);
|
||||
$user->rsskey = md5(random_bytes(60).$user->password);
|
||||
$user->uploaded = config('other.default_upload');
|
||||
$user->downloaded = config('other.default_download');
|
||||
$user->style = config('other.default_style', 0);
|
||||
$user->locale = config('app.locale');
|
||||
$user->group_id = $validatingGroup[0];
|
||||
|
||||
if (config('email-blacklist.enabled')) {
|
||||
if (! config('captcha.enabled')) {
|
||||
$v = validator($request->all(), [
|
||||
'username' => 'required|alpha_dash|string|between:3,25|unique:users',
|
||||
'password' => 'required|string|between:8,16',
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:70',
|
||||
'unique:users',
|
||||
new EmailBlacklist(),
|
||||
],
|
||||
]);
|
||||
} else {
|
||||
$v = validator($request->all(), [
|
||||
'username' => 'required|alpha_dash|string|between:3,25|unique:users',
|
||||
'password' => 'required|string|between:8,16',
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:70',
|
||||
'unique:users',
|
||||
new EmailBlacklist(),
|
||||
],
|
||||
'captcha' => 'hiddencaptcha',
|
||||
]);
|
||||
}
|
||||
} elseif (! config('captcha.enabled')) {
|
||||
$v = validator($request->all(), [
|
||||
'username' => 'required|alpha_dash|string|between:3,25|unique:users',
|
||||
'password' => 'required|string|between:8,16',
|
||||
'email' => 'required|string|email|max:70|unique:users',
|
||||
]);
|
||||
} else {
|
||||
$v = validator($request->all(), [
|
||||
'username' => 'required|alpha_dash|string|between:3,25|unique:users',
|
||||
'password' => 'required|string|between:6,16',
|
||||
'email' => 'required|string|email|max:70|unique:users',
|
||||
'captcha' => 'hiddencaptcha',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($v->fails()) {
|
||||
return to_route('registrationForm', ['code' => $code])
|
||||
->withErrors($v->errors());
|
||||
}
|
||||
|
||||
$user->save();
|
||||
$userPrivacy = new UserPrivacy();
|
||||
$userPrivacy->setDefaultValues();
|
||||
$userPrivacy->user_id = $user->id;
|
||||
$userPrivacy->save();
|
||||
$userNotification = new UserNotification();
|
||||
$userNotification->setDefaultValues();
|
||||
$userNotification->user_id = $user->id;
|
||||
$userNotification->save();
|
||||
if ($key) {
|
||||
// Update The Invite Record
|
||||
$key->accepted_by = $user->id;
|
||||
$key->accepted_at = new Carbon();
|
||||
$key->save();
|
||||
}
|
||||
|
||||
// Handle The Activation System
|
||||
$token = hash_hmac('sha256', $user->username.$user->email.Str::random(16), config('app.key'));
|
||||
$userActivation = new UserActivation();
|
||||
$userActivation->user_id = $user->id;
|
||||
$userActivation->token = $token;
|
||||
$userActivation->save();
|
||||
dispatch(new SendActivationMail($user, $token));
|
||||
|
||||
// Select A Random Welcome Message
|
||||
$profileUrl = href_profile($user);
|
||||
$welcomeArray = [
|
||||
sprintf('[url=%s]%s[/url], Welcome to ', $profileUrl, $user->username).config('other.title').'! Hope you enjoy the community :rocket:',
|
||||
sprintf("[url=%s]%s[/url], We've been expecting you :space_invader:", $profileUrl, $user->username),
|
||||
sprintf("[url=%s]%s[/url] has arrived. Party's over. :cry:", $profileUrl, $user->username),
|
||||
sprintf("It's a bird! It's a plane! Nevermind, it's just [url=%s]%s[/url].", $profileUrl, $user->username),
|
||||
sprintf('Ready player [url=%s]%s[/url].', $profileUrl, $user->username),
|
||||
sprintf('A wild [url=%s]%s[/url] appeared.', $profileUrl, $user->username),
|
||||
'Welcome to '.config('other.title').sprintf(' [url=%s]%s[/url]. We were expecting you ( ͡° ͜ʖ ͡°)', $profileUrl, $user->username),
|
||||
];
|
||||
$selected = random_int(0, \count($welcomeArray) - 1);
|
||||
$this->chatRepository->systemMessage(
|
||||
$welcomeArray[$selected]
|
||||
);
|
||||
|
||||
// Send Welcome PM
|
||||
$privateMessage = new PrivateMessage();
|
||||
$privateMessage->sender_id = 1;
|
||||
$privateMessage->receiver_id = $user->id;
|
||||
$privateMessage->subject = config('welcomepm.subject');
|
||||
$privateMessage->message = config('welcomepm.message');
|
||||
$privateMessage->save();
|
||||
|
||||
return to_route('login')
|
||||
->withSuccess(trans('auth.register-thanks'));
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* NOTICE OF LICENSE.
|
||||
*
|
||||
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
|
||||
* The details is bundled with this project in the file LICENSE.txt.
|
||||
*
|
||||
* @project UNIT3D Community Edition
|
||||
*
|
||||
* @author HDVinnie <hdinnovations@protonmail.com>
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Group;
|
||||
use App\Models\UserActivation;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ResetPasswordController extends Controller
|
||||
{
|
||||
use ResetsPasswords;
|
||||
|
||||
protected string $redirectTo = '/';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
protected function resetPassword($user, $password): void
|
||||
{
|
||||
$validatingGroup = cache()->rememberForever('validating_group', fn () => Group::where('slug', '=', 'validating')->pluck('id'));
|
||||
$memberGroup = cache()->rememberForever('member_group', fn () => Group::where('slug', '=', 'user')->pluck('id'));
|
||||
$user->password = bcrypt($password);
|
||||
$user->remember_token = Str::random(60);
|
||||
|
||||
if ($user->group_id === $validatingGroup[0]) {
|
||||
$user->group_id = $memberGroup[0];
|
||||
}
|
||||
|
||||
$user->active = true;
|
||||
$user->save();
|
||||
|
||||
UserActivation::where('user_id', '=', $user->id)->delete();
|
||||
|
||||
$this->guard()->login($user);
|
||||
}
|
||||
}
|
||||
@@ -18,18 +18,22 @@ use App\Helpers\Linkify;
|
||||
use App\Helpers\StringHelper;
|
||||
use App\Traits\UsersOnlineTrait;
|
||||
use Assada\Achievements\Achiever;
|
||||
use Illuminate\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||
use voku\helper\AntiXSS;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use Achiever;
|
||||
use HasFactory;
|
||||
use MustVerifyEmail;
|
||||
use Notifiable;
|
||||
use SoftDeletes;
|
||||
use TwoFactorAuthenticatable;
|
||||
use UsersOnlineTrait;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* NOTICE OF LICENSE.
|
||||
*
|
||||
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
|
||||
* The details is bundled with this project in the file LICENSE.txt.
|
||||
*
|
||||
* @project UNIT3D Community Edition
|
||||
*
|
||||
* @author HDVinnie <hdinnovations@protonmail.com>
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
||||
*/
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class UsernameReminder extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* UsernameReminderEmail constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// nothing special to do
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*/
|
||||
public function via($notifiable): array
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*/
|
||||
public function toMail($notifiable): MailMessage
|
||||
{
|
||||
return (new MailMessage())
|
||||
->subject(trans('common.your').' '.config('app.name').' '.trans('common.username'))
|
||||
->greeting(trans('common.contact-header').', '.$notifiable->username)
|
||||
->line(trans('email.username-reminder').' '.$notifiable->username)
|
||||
->action('Login as '.$notifiable->username, route('login'))
|
||||
->line(trans('email.thanks').' '.config('app.name'));
|
||||
}
|
||||
}
|
||||
184
app/Providers/FortifyServiceProvider.php
Normal file
184
app/Providers/FortifyServiceProvider.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Actions\Fortify\CreateNewUser;
|
||||
use App\Actions\Fortify\ResetUserPassword;
|
||||
use App\Actions\Fortify\UpdateUserPassword;
|
||||
use App\Actions\Fortify\UpdateUserProfileInformation;
|
||||
use App\Models\Group;
|
||||
use App\Models\User;
|
||||
use App\Models\UserActivation;
|
||||
use App\Services\Unit3dAnnounce;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Laravel\Fortify\Contracts\LoginResponse;
|
||||
use Laravel\Fortify\Contracts\RegisterViewResponse;
|
||||
use Laravel\Fortify\Contracts\VerifyEmailResponse;
|
||||
use Laravel\Fortify\Fortify;
|
||||
|
||||
class FortifyServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
// Handle redirects after successful login
|
||||
$this->app->instance(LoginResponse::class, new class () implements LoginResponse {
|
||||
public function toResponse($request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
// Check if user is disabled
|
||||
|
||||
$disabledGroup = cache()->rememberForever('disabled_group', fn () => Group::query()->where('slug', '=', 'disabled')->pluck('id'));
|
||||
$memberGroup = cache()->rememberForever('member_group', fn () => Group::query()->where('slug', '=', 'user')->pluck('id'));
|
||||
|
||||
if ($user->group_id == $disabledGroup[0]) {
|
||||
$user->group_id = $memberGroup[0];
|
||||
$user->can_upload = 1;
|
||||
$user->can_download = 1;
|
||||
$user->can_comment = 1;
|
||||
$user->can_invite = 1;
|
||||
$user->can_request = 1;
|
||||
$user->can_chat = 1;
|
||||
$user->disabled_at = null;
|
||||
$user->save();
|
||||
|
||||
return to_route('home.index')
|
||||
->withSuccess('auth.welcome-restore');
|
||||
}
|
||||
|
||||
// Check if user has read the rules
|
||||
|
||||
if ($request->user()->read_rules == 0) {
|
||||
return redirect()->to(config('other.rules_url'))
|
||||
->withWarning(trans('auth.require-rules'));
|
||||
}
|
||||
|
||||
// Redirect to home page
|
||||
|
||||
return redirect()->intended()
|
||||
->withSuccess(trans('auth.welcome'));
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Handle redirects before the registration form is shown
|
||||
$this->app->instance(RegisterViewResponse::class, new class () implements RegisterViewResponse {
|
||||
public function toResponse($request): \Illuminate\Http\RedirectResponse|\Illuminate\View\View
|
||||
{
|
||||
// Make sure open reg is off, invite code is not present and application signups enabled
|
||||
if (! $request->has('code') && config('other.invite-only') && config('other.application_signups')) {
|
||||
return to_route('application.create')
|
||||
->withInfo(trans('auth.allow-invite-appl'));
|
||||
}
|
||||
|
||||
// Make sure open reg is off and invite code is not present
|
||||
if (! $request->has('code') && config('other.invite-only')) {
|
||||
return to_route('login')
|
||||
->withWarning(trans('auth.allow-invite'));
|
||||
}
|
||||
|
||||
return view('auth.register', ['code' => $request->query('code')]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->app->instance(VerifyEmailResponse::class, new class () implements VerifyEmailResponse {
|
||||
public function toResponse($request): \Illuminate\Http\RedirectResponse|\Illuminate\View\View
|
||||
{
|
||||
$bannedGroup = cache()->rememberForever('banned_group', fn () => Group::query()->where('slug', '=', 'banned')->pluck('id'));
|
||||
$memberGroup = cache()->rememberForever('member_group', fn () => Group::query()->where('slug', '=', 'user')->pluck('id'));
|
||||
|
||||
$activation = UserActivation::with('user')->where('token', '=', $request->token)->firstOrFail();
|
||||
if ($activation->user->id && $activation->user->group->id != $bannedGroup[0]) {
|
||||
$activation->user->active = 1;
|
||||
$activation->user->can_upload = 1;
|
||||
$activation->user->can_download = 1;
|
||||
$activation->user->can_request = 1;
|
||||
$activation->user->can_comment = 1;
|
||||
$activation->user->can_invite = 1;
|
||||
$activation->user->group_id = $memberGroup[0];
|
||||
$activation->user->save();
|
||||
|
||||
$activation->delete();
|
||||
|
||||
Unit3dAnnounce::addUser($activation->user);
|
||||
|
||||
return to_route('login')
|
||||
->withSuccess(trans('auth.activation-success'));
|
||||
}
|
||||
|
||||
return to_route('login')
|
||||
->withErrors(trans('auth.activation-error'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
Fortify::loginView(fn () => view('auth.login'));
|
||||
Fortify::requestPasswordResetLinkView(fn () => view('auth.passwords.email'));
|
||||
Fortify::resetPasswordView(fn (Request $request) => view('auth.passwords.reset', ['request' => $request]));
|
||||
Fortify::confirmPasswordView(fn () => view('auth.confirm-password'));
|
||||
Fortify::twoFactorChallengeView(fn () => view('auth.two-factor-challenge'));
|
||||
Fortify::verifyEmailView(fn () => view('auth.verify-email'));
|
||||
|
||||
Fortify::createUsersUsing(CreateNewUser::class);
|
||||
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
|
||||
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
|
||||
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
|
||||
|
||||
Fortify::authenticateUsing(function (Request $request): User|\Illuminate\Database\Eloquent\ModelNotFoundException {
|
||||
$request->validate([
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'captcha' => Rule::when(config('captcha.enabled'), 'hiddencaptcha')
|
||||
]);
|
||||
|
||||
$user = User::query()->where('username', $request->username)->sole();
|
||||
|
||||
// Check if user is activated
|
||||
|
||||
$validatingGroup = cache()->rememberForever('validating_group', fn () => Group::query()->where('slug', '=', 'validating')->pluck('id'));
|
||||
|
||||
if ($user->active == 0 || $user->group_id == $validatingGroup[0]) {
|
||||
$request->session()->invalidate();
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
Fortify::username() => trans('auth.not-activated'),
|
||||
]);
|
||||
}
|
||||
|
||||
// Check if user is banned
|
||||
|
||||
$bannedGroup = cache()->rememberForever('banned_group', fn () => Group::query()->where('slug', '=', 'banned')->pluck('id'));
|
||||
|
||||
if ($user->group_id == $bannedGroup[0]) {
|
||||
$request->session()->invalidate();
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
Fortify::username() => trans('auth.banned'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $user;
|
||||
});
|
||||
|
||||
RateLimiter::for('login', function (Request $request) {
|
||||
$email = (string) $request->email;
|
||||
|
||||
return Limit::perMinute(5)->by($email.$request->ip());
|
||||
});
|
||||
|
||||
RateLimiter::for('two-factor', fn (Request $request) => Limit::perMinute(5)->by($request->session()->get('login.id')));
|
||||
}
|
||||
}
|
||||
@@ -26,9 +26,9 @@
|
||||
"hootlex/laravel-moderation": "^1.1",
|
||||
"intervention/image": "^2.7.2",
|
||||
"joypixels/assets": "^6.6",
|
||||
"laravel/fortify": "^1.17",
|
||||
"laravel/framework": "^10.11",
|
||||
"laravel/tinker": "^2.8.1",
|
||||
"laravel/ui": "^4.2.2",
|
||||
"league/flysystem-sftp-v3": "^3.15",
|
||||
"livewire/livewire": "^2.12.3",
|
||||
"marcreichel/igdb-laravel": "3.7.0",
|
||||
|
||||
284
composer.lock
generated
284
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": "cc01f23993de5cb950743a26db1036bb",
|
||||
"content-hash": "e8f113daa066c3707475dfedac0529c9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "appstract/laravel-opcache",
|
||||
@@ -127,6 +127,60 @@
|
||||
},
|
||||
"time": "2022-09-06T10:55:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
"version": "2.0.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Bacon/BaconQrCode.git",
|
||||
"reference": "8674e51bb65af933a5ffaf1c308a660387c35c22"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22",
|
||||
"reference": "8674e51bb65af933a5ffaf1c308a660387c35c22",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"dasprid/enum": "^1.0.3",
|
||||
"ext-iconv": "*",
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phly/keep-a-changelog": "^2.1",
|
||||
"phpunit/phpunit": "^7 | ^8 | ^9",
|
||||
"spatie/phpunit-snapshot-assertions": "^4.2.9",
|
||||
"squizlabs/php_codesniffer": "^3.4"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-imagick": "to generate QR code images"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"BaconQrCode\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ben Scholzen 'DASPRiD'",
|
||||
"email": "mail@dasprids.de",
|
||||
"homepage": "https://dasprids.de/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "BaconQrCode is a QR code generator for PHP.",
|
||||
"homepage": "https://github.com/Bacon/BaconQrCode",
|
||||
"support": {
|
||||
"issues": "https://github.com/Bacon/BaconQrCode/issues",
|
||||
"source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8"
|
||||
},
|
||||
"time": "2022-12-07T17:46:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bjeavons/zxcvbn-php",
|
||||
"version": "1.3.1",
|
||||
@@ -237,6 +291,56 @@
|
||||
],
|
||||
"time": "2023-01-15T23:15:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dasprid/enum",
|
||||
"version": "1.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/DASPRiD/Enum.git",
|
||||
"reference": "8e6b6ea76eabbf19ea2bf5b67b98e1860474012f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8e6b6ea76eabbf19ea2bf5b67b98e1860474012f",
|
||||
"reference": "8e6b6ea76eabbf19ea2bf5b67b98e1860474012f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1 <9.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7 | ^8 | ^9",
|
||||
"squizlabs/php_codesniffer": "*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"DASPRiD\\Enum\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ben Scholzen 'DASPRiD'",
|
||||
"email": "mail@dasprids.de",
|
||||
"homepage": "https://dasprids.de/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "PHP 7.1 enum implementation",
|
||||
"keywords": [
|
||||
"enum",
|
||||
"map"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/DASPRiD/Enum/issues",
|
||||
"source": "https://github.com/DASPRiD/Enum/tree/1.0.4"
|
||||
},
|
||||
"time": "2023-03-01T18:44:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dflydev/dot-access-data",
|
||||
"version": "v3.0.2",
|
||||
@@ -2049,6 +2153,70 @@
|
||||
},
|
||||
"time": "2021-07-08T22:11:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/fortify",
|
||||
"version": "v1.17.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/fortify.git",
|
||||
"reference": "e99f7cb135bb6e05e4c49e9224c9c9a33c27cfa0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/fortify/zipball/e99f7cb135bb6e05e4c49e9224c9c9a33c27cfa0",
|
||||
"reference": "e99f7cb135bb6e05e4c49e9224c9c9a33c27cfa0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"bacon/bacon-qr-code": "^2.0",
|
||||
"ext-json": "*",
|
||||
"illuminate/support": "^8.82|^9.0|^10.0",
|
||||
"php": "^7.3|^8.0",
|
||||
"pragmarx/google2fa": "^7.0|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.0",
|
||||
"orchestra/testbench": "^6.0|^7.0|^8.0",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^9.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Fortify\\FortifyServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Fortify\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "Backend controllers and scaffolding for Laravel authentication.",
|
||||
"keywords": [
|
||||
"auth",
|
||||
"laravel"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/fortify/issues",
|
||||
"source": "https://github.com/laravel/fortify"
|
||||
},
|
||||
"time": "2023-06-02T12:58:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v10.11.0",
|
||||
@@ -2377,68 +2545,6 @@
|
||||
},
|
||||
"time": "2023-02-15T16:40:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/ui",
|
||||
"version": "v4.2.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/ui.git",
|
||||
"reference": "a58ec468db4a340b33f3426c778784717a2c144b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/ui/zipball/a58ec468db4a340b33f3426c778784717a2c144b",
|
||||
"reference": "a58ec468db4a340b33f3426c778784717a2c144b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/console": "^9.21|^10.0",
|
||||
"illuminate/filesystem": "^9.21|^10.0",
|
||||
"illuminate/support": "^9.21|^10.0",
|
||||
"illuminate/validation": "^9.21|^10.0",
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"orchestra/testbench": "^7.0|^8.0",
|
||||
"phpunit/phpunit": "^9.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.x-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Ui\\UiServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Ui\\": "src/",
|
||||
"Illuminate\\Foundation\\Auth\\": "auth-backend/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "Laravel UI utilities and presets.",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"ui"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/laravel/ui/tree/v4.2.2"
|
||||
},
|
||||
"time": "2023-05-09T19:47:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/commonmark",
|
||||
"version": "2.4.0",
|
||||
@@ -3899,6 +4005,58 @@
|
||||
],
|
||||
"time": "2023-03-05T17:13:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa",
|
||||
"version": "v8.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/antonioribeiro/google2fa.git",
|
||||
"reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/80c3d801b31fe165f8fe99ea085e0a37834e1be3",
|
||||
"reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"paragonie/constant_time_encoding": "^1.0|^2.0",
|
||||
"php": "^7.1|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^0.12.18",
|
||||
"phpunit/phpunit": "^7.5.15|^8.5|^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PragmaRX\\Google2FA\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Antonio Carlos Ribeiro",
|
||||
"email": "acr@antoniocarlosribeiro.com",
|
||||
"role": "Creator & Designer"
|
||||
}
|
||||
],
|
||||
"description": "A One Time Password Authentication package, compatible with Google Authenticator.",
|
||||
"keywords": [
|
||||
"2fa",
|
||||
"Authentication",
|
||||
"Two Factor Authentication",
|
||||
"google2fa"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/antonioribeiro/google2fa/issues",
|
||||
"source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.1"
|
||||
},
|
||||
"time": "2022-06-13T21:57:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "predis/predis",
|
||||
"version": "v2.1.2",
|
||||
|
||||
@@ -219,6 +219,7 @@ return [
|
||||
App\Providers\AuthServiceProvider::class,
|
||||
App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\FortifyServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
|
||||
],
|
||||
|
||||
147
config/fortify.php
Normal file
147
config/fortify.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Laravel\Fortify\Features;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Fortify Guard
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which authentication guard Fortify will use while
|
||||
| authenticating users. This value should correspond with one of your
|
||||
| guards that is already present in your "auth" configuration file.
|
||||
|
|
||||
*/
|
||||
|
||||
'guard' => 'web',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Fortify Password Broker
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which password broker Fortify can use when a user
|
||||
| is resetting their password. This configured value should match one
|
||||
| of your password brokers setup in your "auth" configuration file.
|
||||
|
|
||||
*/
|
||||
|
||||
'passwords' => 'users',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Username / Email
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value defines which model attribute should be considered as your
|
||||
| application's "username" field. Typically, this might be the email
|
||||
| address of the users but you are free to change this value here.
|
||||
|
|
||||
| Out of the box, Fortify expects forgot password and reset password
|
||||
| requests to have a field named 'email'. If the application uses
|
||||
| another name for the field you may define it below as needed.
|
||||
|
|
||||
*/
|
||||
|
||||
'username' => 'username',
|
||||
|
||||
'email' => 'email',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Home Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the path where users will get redirected during
|
||||
| authentication or password reset when the operations are successful
|
||||
| and the user is authenticated. You are free to change this value.
|
||||
|
|
||||
*/
|
||||
|
||||
'home' => RouteServiceProvider::HOME,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Fortify Routes Prefix / Subdomain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which prefix Fortify will assign to all the routes
|
||||
| that it registers with the application. If necessary, you may change
|
||||
| subdomain under which all of the Fortify routes will be available.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => '',
|
||||
|
||||
'domain' => null,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Fortify Routes Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which middleware Fortify will assign to the routes
|
||||
| that it registers with the application. If necessary, you may change
|
||||
| these middleware but typically this provided default is preferred.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => ['web'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Rate Limiting
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| By default, Fortify will throttle logins to five requests per minute for
|
||||
| every email and IP address combination. However, if you would like to
|
||||
| specify a custom rate limiter to call then you may specify it here.
|
||||
|
|
||||
*/
|
||||
|
||||
'limiters' => [
|
||||
'login' => 'login',
|
||||
'two-factor' => 'two-factor',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Register View Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify if the routes returning views should be disabled as
|
||||
| you may not need them when building your own application. This may be
|
||||
| especially true if you're writing a custom single-page application.
|
||||
|
|
||||
*/
|
||||
|
||||
'views' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Features
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Some of the Fortify features are optional. You may disable the features
|
||||
| by removing them from this array. You're free to only remove some of
|
||||
| these features or you can even remove all of these if you need to.
|
||||
|
|
||||
*/
|
||||
|
||||
'features' => [
|
||||
Features::registration(),
|
||||
Features::resetPasswords(),
|
||||
Features::emailVerification(),
|
||||
Features::updateProfileInformation(),
|
||||
Features::updatePasswords(),
|
||||
Features::twoFactorAuthentication([
|
||||
'confirm' => true,
|
||||
'confirmPassword' => true,
|
||||
// 'window' => 0,
|
||||
]),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Laravel\Fortify\Fortify;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->text('two_factor_secret')
|
||||
->after('password')
|
||||
->nullable();
|
||||
|
||||
$table->text('two_factor_recovery_codes')
|
||||
->after('two_factor_secret')
|
||||
->nullable();
|
||||
|
||||
if (Fortify::confirmsTwoFactorAuthentication()) {
|
||||
$table->timestamp('two_factor_confirmed_at')
|
||||
->after('two_factor_recovery_codes')
|
||||
->nullable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn(array_merge([
|
||||
'two_factor_secret',
|
||||
'two_factor_recovery_codes',
|
||||
], Fortify::confirmsTwoFactorAuthentication() ? [
|
||||
'two_factor_confirmed_at',
|
||||
] : []));
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class () extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::drop('user_activations');
|
||||
Schema::table('users', function (Blueprint $table): void {
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
});
|
||||
}
|
||||
};
|
||||
31
resources/views/auth/confirm-password.blade.php
Normal file
31
resources/views/auth/confirm-password.blade.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<title>Password Confirmation - {{ config('other.title') }}</title>
|
||||
<section class="panelV2">
|
||||
<h2 class="panel__heading">Password Confirmation</h2>
|
||||
<div class="panel__body">
|
||||
<form
|
||||
class="form"
|
||||
action="{{ route('auth.confirm-password') }}"
|
||||
method="POST"
|
||||
>
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<p>Please confirm your password before continuing.</p>
|
||||
<p class="form__group">
|
||||
<input
|
||||
type="password"
|
||||
class="form__text"
|
||||
id="password"
|
||||
name="password"
|
||||
>
|
||||
<label class="form__label" for="password">Password</label>
|
||||
@error('error')
|
||||
<span class="form__hint">{{ $error }}</span>
|
||||
@enderror
|
||||
</p>
|
||||
<p class="form__group">
|
||||
<button class="form__button form__button--filled">
|
||||
{{ __('common.confirm') }}
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
@@ -52,7 +52,7 @@
|
||||
<a href="{{ route('login') }}">
|
||||
<h2 class="active">{{ __('auth.login') }} </h2>
|
||||
</a>
|
||||
<a href="{{ route('registrationForm', ['code' => 'null']) }}">
|
||||
<a href="{{ route('register') }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.signup') }} </h2>
|
||||
</a>
|
||||
|
||||
@@ -99,9 +99,6 @@
|
||||
<a href="{{ route('password.request') }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.lost-password') }} </h2>
|
||||
</a>
|
||||
<a href="{{ route('username.request') }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.lost-username') }} </h2>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<a href="{{ route('login') }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.login') }}</h2>
|
||||
</a>
|
||||
<a href="{{ route('registrationForm', ['code' => 'null']) }}">
|
||||
<a href="{{ route('register') }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.signup') }}</h2>
|
||||
</a>
|
||||
|
||||
@@ -65,6 +65,12 @@
|
||||
@hiddencaptcha
|
||||
@endif
|
||||
|
||||
@if (session('status'))
|
||||
<div class="form__hint">
|
||||
{{ session('status') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<button type="submit" class="fadeIn fourth">{{ __('common.submit') }}</button>
|
||||
</form>
|
||||
|
||||
@@ -72,9 +78,6 @@
|
||||
<a href="{{ route('password.request') }}">
|
||||
<h2 class="active">{{ __('auth.lost-password') }} </h2>
|
||||
</a>
|
||||
<a href="{{ route('username.request') }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.lost-username') }} </h2>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<a href="{{ route('login') }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.login') }} </h2>
|
||||
</a>
|
||||
<a href="{{ route('registrationForm', ['code' => 'null']) }}">
|
||||
<a href="{{ route('register') }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.signup') }} </h2>
|
||||
</a>
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
<form class="form-horizontal" role="form" method="POST" action="{{ route('password.request') }}">
|
||||
@csrf
|
||||
<input type="hidden" name="token" value="{{ $token }}">
|
||||
<input type="hidden" name="token" value="{{ request()->route('token') }}">
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label for="email"></label><input type="email" id="email" class="fadeIn third" name="email"
|
||||
@@ -87,9 +87,6 @@
|
||||
<a href="{{ route('password.request') }}">
|
||||
<h2 class="active">{{ __('auth.lost-password') }} </h2>
|
||||
</a>
|
||||
<a href="{{ route('username.request') }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.lost-username') }} </h2>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</div>
|
||||
@endif
|
||||
<div class="wrapper fadeInDown">
|
||||
@if (config('other.invite-only') == true && !$code)
|
||||
@if (config('other.invite-only') == true && ! request()->has('code'))
|
||||
<div class="alert alert-info">
|
||||
{{ __('auth.need-invite') }}
|
||||
</div>
|
||||
@@ -53,7 +53,7 @@
|
||||
<a href="{{ route('login') }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.login') }} </h2>
|
||||
</a>
|
||||
<a href="{{ route('registrationForm', ['code' => $code]) }}">
|
||||
<a href="{{ route('register', ['code' => request()->query('code')]) }}">
|
||||
<h2 class="active">{{ __('auth.signup') }} </h2>
|
||||
</a>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<img src="{{ url('/img/icon.svg') }}" id="icon" alt="{{ __('auth.user-icon') }}"/>
|
||||
</div>
|
||||
|
||||
<form role="form" method="POST" action="{{ route('register', ['code' => $code]) }}">
|
||||
<form role="form" method="POST" action="{{ route('register', ['code' => request()->query('code')]) }}">
|
||||
@csrf
|
||||
<label for="username"></label><input type="text" id="username" class="fadeIn second" name="username"
|
||||
placeholder="{{ __('auth.username') }}" required autofocus>
|
||||
@@ -79,9 +79,6 @@
|
||||
<a href="{{ route('password.request') }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.lost-password') }} </h2>
|
||||
</a>
|
||||
<a href="{{ route('username.request') }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.lost-username') }} </h2>
|
||||
</a>
|
||||
@if (config('email-white-blacklist.enabled') == 'block')
|
||||
<br>
|
||||
<a href="{{ route('public.email') }}">
|
||||
|
||||
102
resources/views/auth/two-factor-challenge.blade.php
Normal file
102
resources/views/auth/two-factor-challenge.blade.php
Normal file
@@ -0,0 +1,102 @@
|
||||
@extends('layout.default')
|
||||
|
||||
@section('title')
|
||||
<title>{{ __('auth.title') }} - {{ config('other.title') }}</title>
|
||||
@endsection
|
||||
|
||||
@section('meta')
|
||||
<meta name="description" content="{{ __('auth.title') }} - {{ config('other.title') }}">
|
||||
@endsection
|
||||
|
||||
@section('stylesheets')
|
||||
<link rel="stylesheet" href="{{ mix('css/main/twostep.css') }}" crossorigin="anonymous">
|
||||
@endsection
|
||||
|
||||
@section('breadcrumbs')
|
||||
<li class="breadcrumb--active">
|
||||
{{ __('auth.title') }}
|
||||
</li>
|
||||
@endsection
|
||||
|
||||
@php
|
||||
switch ($remainingAttempts) {
|
||||
case 0:
|
||||
case 1:
|
||||
$remainingAttemptsClass = 'danger';
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$remainingAttemptsClass = 'warning';
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$remainingAttemptsClass = 'info';
|
||||
break;
|
||||
|
||||
default:
|
||||
$remainingAttemptsClass = 'success';
|
||||
break;
|
||||
}
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<div class="panel panel verification-form-panel">
|
||||
<div class="panel__heading text-center" id="verification_status_title">
|
||||
<h3>
|
||||
{{ __('auth.title') }}
|
||||
</h3>
|
||||
<p class="text-center">
|
||||
<em>
|
||||
{{ __('auth.subtitle') }}
|
||||
</em>
|
||||
</p>
|
||||
</div>
|
||||
<div class="panel__body">
|
||||
<form
|
||||
class="form"
|
||||
action="{{ route('two-factor.login') }}"
|
||||
method="POST"
|
||||
>
|
||||
@csrf
|
||||
<p class="form__group">
|
||||
<input
|
||||
id="code"
|
||||
class="form__text"
|
||||
autofocus
|
||||
name="code"
|
||||
type="text"
|
||||
/>
|
||||
<label class="form__label form__label--floating">
|
||||
{{ __('auth.code') }}
|
||||
</label>
|
||||
@error('error')
|
||||
<span class="form__hint">{{ $error }}</span>
|
||||
@enderror
|
||||
</p>
|
||||
<p class="form__group">
|
||||
<input
|
||||
id="code"
|
||||
class="form__text"
|
||||
autofocus
|
||||
name="receover_code"
|
||||
type="text"
|
||||
/>
|
||||
<label class="form__label form__label--floating">
|
||||
{{ __('auth.recovery-code') }}
|
||||
</label>
|
||||
</p>
|
||||
<p class="form__group">
|
||||
<button class="form__button form__button--filled">
|
||||
{{ __('common.submit') }}
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -1,117 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ config('app.locale') }}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ __('auth.lost-username') }} - {{ config('other.title') }}</title>
|
||||
@section('meta')
|
||||
<meta name="description"
|
||||
content="{{ __('auth.login-now-on') }} {{ config('other.title') }} . {{ __('auth.not-a-member') }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta property="og:title" content="{{ __('auth.login') }}">
|
||||
<meta property="og:site_name" content="{{ config('other.title') }}">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="{{ url('/img/og.png') }}">
|
||||
<meta property="og:description" content="{{ config('unit3d.powered-by') }}">
|
||||
<meta property="og:url" content="{{ url('/') }}">
|
||||
<meta property="og:locale" content="{{ config('app.locale') }}">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
@show
|
||||
<link rel="shortcut icon" href="{{ url('/favicon.ico') }}" type="image/x-icon">
|
||||
<link rel="icon" href="{{ url('/favicon.ico') }}" type="image/x-icon">
|
||||
|
||||
<link rel="stylesheet" href="{{ mix('css/main/login.css') }}" crossorigin="anonymous">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@if ($errors->any())
|
||||
<div id="ERROR_COPY" style="display: none;">
|
||||
@foreach ($errors->all() as $error)
|
||||
{{ $error }}<br>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
<div class="wrapper fadeInDown">
|
||||
<svg viewBox="0 0 800 100" class="sitebanner">
|
||||
|
||||
<symbol id="s-text">
|
||||
<text text-anchor="middle" x="50%" y="50%" dy=".35em">
|
||||
{{ config('other.title') }}
|
||||
</text>
|
||||
</symbol>
|
||||
|
||||
<use xlink:href="#s-text" class="text"></use>
|
||||
<use xlink:href="#s-text" class="text"></use>
|
||||
<use xlink:href="#s-text" class="text"></use>
|
||||
<use xlink:href="#s-text" class="text"></use>
|
||||
<use xlink:href="#s-text" class="text"></use>
|
||||
|
||||
</svg>
|
||||
<div id="formContent">
|
||||
<a href="{{ route('login') }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.login') }}</h2>
|
||||
</a>
|
||||
<a href="{{ route('registrationForm', ['code' => 'null']) }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.signup') }}</h2>
|
||||
</a>
|
||||
|
||||
<div class="fadeIn first">
|
||||
<img src="{{ url('/img/icon.svg') }}" id="icon" alt="{{ __('auth.user-icon') }}"/>
|
||||
</div>
|
||||
|
||||
<form class="form-horizontal" role="form" method="POST" action="{{ route('username.email') }}">
|
||||
@csrf
|
||||
<label for="email"></label><input type="email" id="email" class="fadeIn third" name="email"
|
||||
placeholder="{{ __('auth.email') }}" required autofocus>
|
||||
@if (config('captcha.enabled') == true)
|
||||
@hiddencaptcha
|
||||
@endif
|
||||
<button type="submit" class="fadeIn fourth">{{ __('common.submit') }}</button>
|
||||
</form>
|
||||
|
||||
<div id="formFooter">
|
||||
<a href="{{ route('password.request') }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.lost-password') }} </h2>
|
||||
</a>
|
||||
<a href="{{ route('username.request') }}">
|
||||
<h2 class="active">{{ __('auth.lost-username') }} </h2>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script src="{{ mix('js/app.js') }}" crossorigin="anonymous"></script>
|
||||
|
||||
@foreach (['warning', 'success', 'info'] as $key)
|
||||
@if (Session::has($key))
|
||||
<script nonce="{{ HDVinnie\SecureHeaders\SecureHeaders::nonce('script') }}">
|
||||
const Toast = Swal.mixin({
|
||||
toast: true,
|
||||
position: 'top-end',
|
||||
showConfirmButton: false,
|
||||
timer: 3000
|
||||
})
|
||||
|
||||
Toast.fire({
|
||||
icon: '{{ $key }}',
|
||||
title: '{{ Session::get($key) }}'
|
||||
})
|
||||
|
||||
</script>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
@if (Session::has('errors'))
|
||||
<script nonce="{{ HDVinnie\SecureHeaders\SecureHeaders::nonce('script') }}">
|
||||
Swal.fire({
|
||||
title: '<strong style=" color: rgb(17,17,17);">Error</strong>',
|
||||
icon: 'error',
|
||||
html: document.getElementById('ERROR_COPY').innerHTML,
|
||||
showCloseButton: true,
|
||||
})
|
||||
|
||||
</script>
|
||||
@endif
|
||||
</body>
|
||||
|
||||
</html>
|
||||
59
resources/views/auth/verify-email.blade.php
Normal file
59
resources/views/auth/verify-email.blade.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ config('app.locale') }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ __('auth.verify-email') }} - {{ config('other.title') }}</title>
|
||||
@section('meta')
|
||||
<meta name="description" content="{{ __('auth.login-now-on') }} {{ config('other.title') }} . {{ __('auth.not-a-member') }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta property="og:title" content="{{ __('auth.login') }}">
|
||||
<meta property="og:site_name" content="{{ config('other.title') }}">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="{{ url('/img/og.png') }}">
|
||||
<meta property="og:description" content="{{ config('unit3d.powered-by') }}">
|
||||
<meta property="og:url" content="{{ url('/') }}">
|
||||
<meta property="og:locale" content="{{ config('app.locale') }}">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
@show
|
||||
<link rel="shortcut icon" href="{{ url('/favicon.ico') }}" type="image/x-icon">
|
||||
<link rel="icon" href="{{ url('/favicon.ico') }}" type="image/x-icon">
|
||||
<link rel="stylesheet" href="{{ mix('css/main/login.css') }}" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper fadeInDown">
|
||||
<svg viewBox="0 0 800 100" class="sitebanner">
|
||||
<symbol id="s-text">
|
||||
<text text-anchor="middle" x="50%" y="50%" dy=".35em">
|
||||
{{ config('other.title') }}
|
||||
</text>
|
||||
</symbol>
|
||||
<use xlink:href="#s-text" class="text"></use>
|
||||
<use xlink:href="#s-text" class="text"></use>
|
||||
<use xlink:href="#s-text" class="text"></use>
|
||||
<use xlink:href="#s-text" class="text"></use>
|
||||
<use xlink:href="#s-text" class="text"></use>
|
||||
</svg>
|
||||
<div id="formContent">
|
||||
<a href="{{ route('login') }}">
|
||||
<h2 class="inactive underlineHover">{{ __('auth.login') }} </h2>
|
||||
</a>
|
||||
<a href="{{ route('register', ['code' => request()->query('code')]) }}">
|
||||
<h2 class="active">{{ __('auth.signup') }} </h2>
|
||||
</a>
|
||||
<div class="fadeIn first">
|
||||
<img src="{{ url('/img/icon.svg') }}" id="icon" alt="{{ __('auth.user-icon') }}"/>
|
||||
</div>
|
||||
<p>
|
||||
Almost done...
|
||||
<br>
|
||||
We'll send you an email shortly. Open it up to activate your account.
|
||||
</p>
|
||||
<div id="formFooter">
|
||||
<a href="{{ route('verification.send') }}">
|
||||
<h2 class="active">Resend confirmation email</h2>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,7 +1,7 @@
|
||||
@component('mail::message')
|
||||
# {{ __('email.invite-header') }} {{ config('other.title') }} !
|
||||
**{{ __('email.invite-message') }}:** {{ __('email.invite-invited') }} {{ config('other.title') }}. {{ $invite->custom }}
|
||||
@component('mail::button', ['url' => route('register', $invite->code), 'color' => 'blue'])
|
||||
@component('mail::button', ['url' => route('register', ['code' => $invite->code]), 'color' => 'blue'])
|
||||
{{ __('email.invite-signup') }}
|
||||
@endcomponent
|
||||
<p>{{ __('email.register-footer') }}</p>
|
||||
|
||||
@@ -25,8 +25,42 @@
|
||||
@endsection
|
||||
|
||||
@section('main')
|
||||
@if (session('status') == 'two-factor-authentication-enabled')
|
||||
<section class="panelV2">
|
||||
<h2 class="panel__heading">{{ __('common.configuration') }}</h2>
|
||||
<div class="panel__body">
|
||||
Please finish configuring two factor authentication:
|
||||
{!! auth()->user()->twoFactorQrCodeSvg() !!}
|
||||
<form
|
||||
class="form"
|
||||
action="{{ route('two-factor.confirm') }}"
|
||||
method="POST"
|
||||
>
|
||||
<p class="form__group">
|
||||
<input
|
||||
type="text"
|
||||
class="form__checkbox"
|
||||
id="code"
|
||||
name="code"
|
||||
/>
|
||||
</p>
|
||||
<p class="form__group">
|
||||
<button class="form__button form__button--filled">
|
||||
{{ __('common.save') }}
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
Recovery codes:
|
||||
<ul>
|
||||
@foreach(auth()->user()->recoveryCodes() as $code)
|
||||
<li>$code</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
<section class="panelV2">
|
||||
<h2 class="panel__heading">Two Step Authentication</h2>
|
||||
<h2 class="panel__heading">Email-based Two Step Authentication</h2>
|
||||
<div class="panel__body">
|
||||
<form
|
||||
class="form"
|
||||
@@ -35,7 +69,7 @@
|
||||
>
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<p>Currently, only email-based two step authentication is supported. Upon enabling, you will receive an email including a code to your registered email address.</p>
|
||||
<p>Upon enabling, you will receive an email including a code to your registered email address.</p>
|
||||
<p>Token-based two factor authentication is planned for a future update.</p>
|
||||
<p class="form__group">
|
||||
<input type="hidden" name="twostep" value="0">
|
||||
@@ -57,4 +91,41 @@
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<section class="panelV2">
|
||||
<h2 class="panel__heading">Enable Two Factor Authentication</h2>
|
||||
<div class="panel__body">
|
||||
<form
|
||||
class="form"
|
||||
action="{{ route('two-factor.enable') }}"
|
||||
method="POST"
|
||||
>
|
||||
@csrf
|
||||
<p>Requires password confirmation</p>
|
||||
<p>Upon enabling, you will be required to enter a valid two factor authentication code.</p>
|
||||
<p class="form__group">
|
||||
<button class="form__button form__button--filled">
|
||||
{{ __('common.enable') }}
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<section class="panelV2">
|
||||
<h2 class="panel__heading">Disable Two Factor Authentication</h2>
|
||||
<form
|
||||
class="form"
|
||||
action="{{ route('two-factor.disable') }}"
|
||||
method="POST"
|
||||
>
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<p>Requires password confirmation.</p>
|
||||
<p class="form__group">
|
||||
<button class="form__button form__button--filled">
|
||||
{{ __('common.disable') }}
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
|
||||
@@ -38,30 +38,13 @@ Route::group(['middleware' => 'language'], function (): void {
|
||||
|---------------------------------------------------------------------------------
|
||||
*/
|
||||
Route::group(['before' => 'auth', 'middleware' => 'guest'], function (): void {
|
||||
// Activation
|
||||
Route::get('/activate/{token}', [App\Http\Controllers\Auth\ActivationController::class, 'activate'])->name('activate');
|
||||
|
||||
// Application Signup
|
||||
Route::get('/application', [App\Http\Controllers\Auth\ApplicationController::class, 'create'])->name('application.create');
|
||||
Route::post('/application', [App\Http\Controllers\Auth\ApplicationController::class, 'store'])->name('application.store');
|
||||
|
||||
// Authentication
|
||||
Route::get('login', [App\Http\Controllers\Auth\LoginController::class, 'showLoginForm'])->name('login');
|
||||
Route::post('login', [App\Http\Controllers\Auth\LoginController::class, 'login'])->name('');
|
||||
|
||||
// Forgot Username
|
||||
Route::get('username/reminder', [App\Http\Controllers\Auth\ForgotUsernameController::class, 'showForgotUsernameForm'])->name('username.request');
|
||||
Route::post('username/reminder', [App\Http\Controllers\Auth\ForgotUsernameController::class, 'sendUsernameReminder'])->name('username.email');
|
||||
|
||||
// Password Reset
|
||||
Route::post('password/email', [App\Http\Controllers\Auth\ForgotPasswordController::class, 'sendResetLinkEmail'])->name('password.email');
|
||||
Route::get('password/reset', [App\Http\Controllers\Auth\ForgotPasswordController::class, 'showLinkRequestForm'])->name('password.request');
|
||||
Route::post('password/reset', [App\Http\Controllers\Auth\ResetPasswordController::class, 'reset'])->name('');
|
||||
Route::get('/password/reset/{token}', [App\Http\Controllers\Auth\ResetPasswordController::class, 'showResetForm'])->name('password.reset');
|
||||
|
||||
// Registration
|
||||
Route::get('/register/{code?}', [App\Http\Controllers\Auth\RegisterController::class, 'registrationForm'])->name('registrationForm');
|
||||
Route::post('/register/{code?}', [App\Http\Controllers\Auth\RegisterController::class, 'register'])->name('register');
|
||||
// This redirect must be kept until all invite emails that use the old syntax have expired
|
||||
// Hack so that Fortify can be used (allows query parameters but not route parameters)
|
||||
Route::get('/register/{code?}', fn (string $code) => to_route('register', ['code' => $code]));
|
||||
});
|
||||
|
||||
/*
|
||||
@@ -69,9 +52,8 @@ Route::group(['middleware' => 'language'], function (): void {
|
||||
| Website (When Authorized) (Alpha Ordered)
|
||||
|---------------------------------------------------------------------------------
|
||||
*/
|
||||
Route::group(['middleware' => ['auth', 'twostep', 'banned']], function (): void {
|
||||
Route::group(['middleware' => ['auth', 'twostep', 'banned', 'verified']], function (): void {
|
||||
// General
|
||||
Route::post('/logout', [App\Http\Controllers\Auth\LoginController::class, 'logout'])->name('logout');
|
||||
Route::get('/', [App\Http\Controllers\HomeController::class, 'index'])->name('home.index');
|
||||
|
||||
// Articles System
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Http\Controllers\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @see \App\Http\Controllers\Auth\ForgotUsernameController
|
||||
*/
|
||||
class ForgotUsernameControllerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function send_username_reminder_returns_an_ok_response(): void
|
||||
{
|
||||
config(['captcha.enabled' => false]);
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->post(route('username.email'), [
|
||||
'email' => $user->email,
|
||||
])
|
||||
->assertRedirect(route('login'))
|
||||
->assertSessionHas('success', trans('email.username-sent'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function show_forgot_username_form_returns_an_ok_response(): void
|
||||
{
|
||||
$this->get(route('username.request'))
|
||||
->assertOk()
|
||||
->assertViewIs('auth.username');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user