@@ -33,11 +33,6 @@ class Application extends Model
|
||||
|
||||
];
|
||||
|
||||
public function oneoffApplicant()
|
||||
{
|
||||
return $this->hasOne('App\OneoffApplicant', 'application_id', 'id');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('App\User', 'applicantUserID', 'id');
|
||||
@@ -70,11 +65,4 @@ class Application extends Model
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function isOneoff()
|
||||
{
|
||||
return $this->user->id == 1; // ID 1 is always the ghost
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ class Ban extends Model
|
||||
];
|
||||
|
||||
public $dates = [
|
||||
'suspendedUntil',
|
||||
'bannedUntil',
|
||||
];
|
||||
|
||||
public function user()
|
||||
|
@@ -22,6 +22,7 @@
|
||||
namespace App\Console;
|
||||
|
||||
use App\Jobs\ProcessDueSuspensions;
|
||||
use App\Jobs\ProcessExpiredAbsences;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
@@ -54,6 +55,11 @@ class Kernel extends ConsoleKernel
|
||||
->daily();
|
||||
// Production value: Every day
|
||||
// Development value: Every minute
|
||||
|
||||
$schedule->job(new ProcessExpiredAbsences)
|
||||
->daily();
|
||||
// Production value: Every day
|
||||
// Development value: Every minute
|
||||
}
|
||||
|
||||
/**
|
||||
|
10
app/Exceptions/AccountNotLinkedException.php
Executable file
10
app/Exceptions/AccountNotLinkedException.php
Executable file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class AccountNotLinkedException extends Exception
|
||||
{
|
||||
//
|
||||
}
|
9
app/Exceptions/DiscordAccountRequiredException.php
Normal file
9
app/Exceptions/DiscordAccountRequiredException.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class DiscordAccountRequiredException extends Exception
|
||||
{
|
||||
}
|
9
app/Exceptions/IncompatibleAgeException.php
Normal file
9
app/Exceptions/IncompatibleAgeException.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class IncompatibleAgeException extends Exception
|
||||
{
|
||||
}
|
9
app/Exceptions/InvalidAgeException.php
Normal file
9
app/Exceptions/InvalidAgeException.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidAgeException extends Exception
|
||||
{
|
||||
}
|
10
app/Exceptions/ProfileAlreadyExistsException.php
Executable file
10
app/Exceptions/ProfileAlreadyExistsException.php
Executable file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ProfileAlreadyExistsException extends Exception
|
||||
{
|
||||
//
|
||||
}
|
10
app/Exceptions/ProfileCreationFailedException.php
Executable file
10
app/Exceptions/ProfileCreationFailedException.php
Executable file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ProfileCreationFailedException extends Exception
|
||||
{
|
||||
//
|
||||
}
|
32
app/Facades/Discord.php
Executable file
32
app/Facades/Discord.php
Executable file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright © 2020 Miguel Nogueira
|
||||
*
|
||||
* This file is part of Raspberry Staff Manager.
|
||||
*
|
||||
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Raspberry Staff Manager is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class Discord extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor()
|
||||
{
|
||||
return 'discordServiceFacade';
|
||||
}
|
||||
}
|
@@ -40,6 +40,6 @@ class Form extends Model
|
||||
|
||||
public function responses()
|
||||
{
|
||||
return $this->belongsTo('App\Response', 'id', 'id');
|
||||
return $this->belongsTo('App\Response', 'id', 'responseFormID');
|
||||
}
|
||||
}
|
||||
|
236
app/Helpers/Discord.php
Executable file
236
app/Helpers/Discord.php
Executable file
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright © 2020 Miguel Nogueira
|
||||
*
|
||||
* This file is part of Raspberry Staff Manager.
|
||||
*
|
||||
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Raspberry Staff Manager is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Exceptions\AccountNotLinkedException;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
// Small wrapper for the necessary sections of the Discord API; A library is overkill here
|
||||
class Discord
|
||||
{
|
||||
|
||||
/**
|
||||
* The current working guild. Default is Home guild from app config
|
||||
* @var string
|
||||
*/
|
||||
protected string $workingGuild;
|
||||
|
||||
|
||||
/**
|
||||
* Current user.
|
||||
*
|
||||
* @var User The user all methods will affect.
|
||||
*/
|
||||
protected User $user;
|
||||
|
||||
|
||||
|
||||
public function __construct() {
|
||||
if (isset($this->workingGuild)) {
|
||||
$this->setWorkingGuild(config('services.discord.home_guild'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the working guild
|
||||
*
|
||||
* @param string $workingGuild
|
||||
* @return Discord
|
||||
*/
|
||||
public function setWorkingGuild(string $workingGuild): Discord
|
||||
{
|
||||
$this->workingGuild = $workingGuild;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current user, upon validation
|
||||
*
|
||||
* @param User $user
|
||||
* @return Discord
|
||||
* @throws AccountNotLinkedException
|
||||
*/
|
||||
public function setUser(User $user): Discord
|
||||
{
|
||||
if ($user->hasDiscordConnection()) {
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
throw new AccountNotLinkedException('Specified website user has not linked their Discord account yet.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the current user's authentication info. Caches the data for the access_token's TTL, thus
|
||||
* preventing unnecessary API requests.
|
||||
*
|
||||
* @return object Current user's authentication info (has no sensitive fields)
|
||||
* @throws RequestException
|
||||
*/
|
||||
public function getAuthorizationInfo(): object {
|
||||
|
||||
if (Cache::has($this->user->discord_user_id)) {
|
||||
return unserialize(Cache::get($this->user->discord_user_id));
|
||||
}
|
||||
else {
|
||||
$authInfo = (object) Http::withToken($this->user->discord_token)
|
||||
->get(config('services.discord.base_url') . '/oauth2/@me')
|
||||
->throw()
|
||||
->json();
|
||||
|
||||
Cache::put($this->user->discord_user_id, serialize($authInfo), Carbon::parse($authInfo->expires));
|
||||
|
||||
return $authInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the user's token is close to expiring.
|
||||
* Tokens should be refreshed the day before they expire.
|
||||
*
|
||||
* @return bool Whether the user's token needs to be refreshed
|
||||
* @throws RequestException
|
||||
*/
|
||||
public function needsRefresh(): bool {
|
||||
return Carbon::parse($this->getAuthorizationInfo()->expires)->diffInDays() == 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function exchangeRefreshToken() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Adds current working user to current working guild. Bot must be member of target guild, and account must be linked
|
||||
*
|
||||
* @return object|bool A GuildMember object; false if member is already in guild
|
||||
* @throws RequestException Any client and server errors
|
||||
* @see https://discord.com/developers/docs/resources/guild#guild-member-object
|
||||
*/
|
||||
public function addGuildMember(): object|bool
|
||||
{
|
||||
$params = [
|
||||
'access_token' => $this->user->discord_token
|
||||
];
|
||||
|
||||
$member = Http::withBody(json_encode($params), 'application/json')
|
||||
->withHeaders([
|
||||
'Authorization' => 'Bot ' . config('services.discord.token')
|
||||
])->put(config('services.discord.base_url') . "/guilds/{$this->workingGuild}/members/{$this->user->discord_user_id}")
|
||||
->throw();
|
||||
|
||||
if ($member->successful() && $member->status() == 204) {
|
||||
return false;
|
||||
} else {
|
||||
return (object) $member->json();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Bans a specified user from the guild.
|
||||
* May be called from the suspension service optionally by the banning user
|
||||
*
|
||||
* @param string $reason The reason to supply Discord with
|
||||
* @return void Nothing on success
|
||||
* @throws RequestException
|
||||
* @throws AccountNotLinkedException
|
||||
*/
|
||||
public function addGuildBan(string $reason): void {
|
||||
Http::withHeaders([
|
||||
'Authorization' => 'Bot ' . config('services.discord.token'),
|
||||
'X-Audit-Log-Reason' => $reason
|
||||
])->put(config('services.discord.base_url') . "/guilds/{$this->workingGuild}/bans/{$this->user->discord_user_id}")
|
||||
->throw();
|
||||
|
||||
throw new AccountNotLinkedException('Specified website user has not linked their Discord account yet.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $reason The removal reason to provide Discord with (e.g. ban expired)
|
||||
* @return null|bool Null on unnan, false if user is not banned
|
||||
* @throws RequestException
|
||||
*/
|
||||
public function removeGuildBan(string $reason): null|bool {
|
||||
|
||||
if ($this->getGuildBan($this->user)) {
|
||||
Http::withHeaders([
|
||||
'Authorization' => 'Bot ' . config('services.discord.token'),
|
||||
'X-Audit-Log-Reason' => $reason
|
||||
])->delete(config('services.discord.base_url') . "/guilds/{$this->workingGuild}/bans/{$this->user->discord_user_id}")
|
||||
->throw();
|
||||
|
||||
return null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets (possible) ban for current user.
|
||||
*
|
||||
* @return object|bool Ban object if user is banned. Null
|
||||
* @throws RequestException
|
||||
* @see https://discord.com/developers/docs/resources/guild#ban-object
|
||||
*/
|
||||
public function getGuildBan(): object|bool
|
||||
{
|
||||
$ban = Http::withHeaders([
|
||||
'Authorization' => 'Bot ' . config('services.discord.token')
|
||||
])->get(config('services.discord.base_url') . "/guilds/{$this->workingGuild}/bans/{$this->user->discord_user_id}");
|
||||
|
||||
if ($ban->status() == 404) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ($ban->successful()) ? (object) $ban->json() : $ban->throwIf($ban->status() !== 404);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves list of Role objects
|
||||
* @see https://discord.com/developers/docs/topics/permissions#role-object
|
||||
* @return array List of role objects
|
||||
*
|
||||
* @throws RequestException
|
||||
*/
|
||||
public function getGuildRoles(): array {
|
||||
return Http::withHeaders([
|
||||
'Authorization' => 'Bot ' . config('services.discord.token')
|
||||
])->get(config('services.discord.base_url') . "/guilds/{$this->workingGuild}/roles")
|
||||
->throw()
|
||||
->json();
|
||||
}
|
||||
|
||||
}
|
@@ -37,8 +37,9 @@ class Options
|
||||
/**
|
||||
* Returns an assortment of settings found in the mentioned category
|
||||
*
|
||||
* @param $category The category
|
||||
* @param string $category The category
|
||||
* @return Collection The settings in this category
|
||||
* @throws EmptyOptionsException
|
||||
*/
|
||||
public function getCategory(string $category): Collection
|
||||
{
|
||||
|
@@ -6,7 +6,9 @@ use App\Absence;
|
||||
use App\Exceptions\AbsenceNotActionableException;
|
||||
use App\Http\Requests\StoreAbsenceRequest;
|
||||
use App\Http\Requests\UpdateAbsenceRequest;
|
||||
use App\Services\AbsenceService;
|
||||
use App\User;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Carbon;
|
||||
@@ -15,28 +17,12 @@ use Illuminate\Support\Facades\Auth;
|
||||
class AbsenceController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Determines whether someone already has an active leave of absence request
|
||||
*
|
||||
* @param User $user The user to check
|
||||
* @return bool Their status
|
||||
*/
|
||||
private function hasActiveRequest(Authenticatable $user): bool {
|
||||
private AbsenceService $absenceService;
|
||||
|
||||
$absences = Absence::where('requesterID', $user->id)->get();
|
||||
public function __construct (AbsenceService $absenceService) {
|
||||
|
||||
foreach ($absences as $absence) {
|
||||
$this->absenceService = $absenceService;
|
||||
|
||||
// Or we could adjust the query (using a model scope) to only return valid absences;
|
||||
// If there are any, refuse to store more, but this approach also works
|
||||
// A model scope that only returns cancelled, declined and ended absences could also be implemented for future use
|
||||
if (in_array($absence->getRawOriginal('status'), ['PENDING', 'APPROVED']))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +43,7 @@ class AbsenceController extends Controller
|
||||
* Display a listing of absences belonging to the current user.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function showUserAbsences()
|
||||
{
|
||||
@@ -76,14 +62,14 @@ class AbsenceController extends Controller
|
||||
/**
|
||||
* Show the form for creating a new absence request.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->authorize('create', Absence::class);
|
||||
|
||||
return view('dashboard.absences.create')
|
||||
->with('activeRequest', $this->hasActiveRequest(Auth::user()));
|
||||
->with('activeRequest', $this->absenceService->hasActiveRequest(Auth::user()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,21 +82,13 @@ class AbsenceController extends Controller
|
||||
{
|
||||
$this->authorize('create', Absence::class);
|
||||
|
||||
if ($this->hasActiveRequest(Auth::user())) {
|
||||
if ($this->absenceService->hasActiveRequest(Auth::user())) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', __('You already have an active request. Cancel it or let it expire first.'));
|
||||
}
|
||||
|
||||
|
||||
$absence = Absence::create([
|
||||
'requesterID' => Auth::user()->id,
|
||||
'start' => $request->start_date,
|
||||
'predicted_end' => $request->predicted_end,
|
||||
'available_assist' => $request->available_assist == "on",
|
||||
'reason' => $request->reason,
|
||||
'status' => 'PENDING',
|
||||
]);
|
||||
$absence = $this->absenceService->createAbsence(Auth::user(), $request);
|
||||
|
||||
return redirect()
|
||||
->to(route('absences.show', ['absence' => $absence->id]))
|
||||
@@ -120,7 +98,8 @@ class AbsenceController extends Controller
|
||||
/**
|
||||
* Display the specified absence request.
|
||||
*
|
||||
* @param \App\Absence $absence
|
||||
* @param \App\Absence $absence
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function show(Absence $absence)
|
||||
{
|
||||
@@ -138,7 +117,7 @@ class AbsenceController extends Controller
|
||||
*
|
||||
* @param Absence $absence
|
||||
* @return RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function approveAbsence(Absence $absence): RedirectResponse
|
||||
{
|
||||
@@ -146,7 +125,7 @@ class AbsenceController extends Controller
|
||||
|
||||
try
|
||||
{
|
||||
$absence->setApproved();
|
||||
$this->absenceService->approveAbsence($absence);
|
||||
}
|
||||
catch (AbsenceNotActionableException $notActionableException)
|
||||
{
|
||||
@@ -166,7 +145,7 @@ class AbsenceController extends Controller
|
||||
*
|
||||
* @param Absence $absence
|
||||
* @return RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function declineAbsence(Absence $absence): RedirectResponse
|
||||
{
|
||||
@@ -174,7 +153,7 @@ class AbsenceController extends Controller
|
||||
|
||||
try
|
||||
{
|
||||
$absence->setDeclined();
|
||||
$this->absenceService->declineAbsence($absence);
|
||||
} catch (AbsenceNotActionableException $notActionableException)
|
||||
{
|
||||
return redirect()
|
||||
@@ -193,7 +172,7 @@ class AbsenceController extends Controller
|
||||
*
|
||||
* @param Absence $absence
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function cancelAbsence(Absence $absence): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
@@ -201,7 +180,7 @@ class AbsenceController extends Controller
|
||||
|
||||
try
|
||||
{
|
||||
$absence->setCancelled();
|
||||
$this->absenceService->cancelAbsence($absence);
|
||||
}
|
||||
catch (AbsenceNotActionableException $notActionableException)
|
||||
{
|
||||
@@ -225,7 +204,7 @@ class AbsenceController extends Controller
|
||||
{
|
||||
$this->authorize('delete', $absence);
|
||||
|
||||
if ($absence->delete()) {
|
||||
if ($this->absenceService->removeAbsence($absence)) {
|
||||
return redirect()
|
||||
->to(route('absences.index'))
|
||||
->with('success', __('Absence request deleted.'));
|
||||
|
@@ -23,11 +23,15 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Application;
|
||||
use App\Exceptions\ApplicationNotFoundException;
|
||||
use App\Exceptions\DiscordAccountRequiredException;
|
||||
use App\Exceptions\IncompatibleAgeException;
|
||||
use App\Exceptions\IncompleteApplicationException;
|
||||
use App\Exceptions\InvalidAgeException;
|
||||
use App\Exceptions\UnavailableApplicationException;
|
||||
use App\Exceptions\VacancyNotFoundException;
|
||||
use App\Facades\IP;
|
||||
use App\Services\ApplicationService;
|
||||
use App\Vacancy;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
@@ -82,6 +86,13 @@ class ApplicationController extends Controller
|
||||
}
|
||||
|
||||
|
||||
public function discordApply(Request $request, $vacancySlug) {
|
||||
|
||||
$request->session()->put('discordApplicationRedirectedSlug', $vacancySlug);
|
||||
return redirect(route('discordRedirect'));
|
||||
|
||||
}
|
||||
|
||||
public function renderApplicationForm($vacancySlug)
|
||||
{
|
||||
try {
|
||||
@@ -91,25 +102,47 @@ class ApplicationController extends Controller
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', $ex->getMessage());
|
||||
|
||||
} catch (DiscordAccountRequiredException $e) {
|
||||
\Log::info('Redirecting user: ' . $e->getMessage(), [
|
||||
'user' => Auth::user()->email
|
||||
]);
|
||||
|
||||
request()->session()->put('discordApplicationRedirectedSlug', $vacancySlug);
|
||||
return redirect(route('discordRedirect'));
|
||||
} catch (IncompatibleAgeException $e) {
|
||||
|
||||
return redirect()
|
||||
->to(route('dashboard'))
|
||||
->with('error', $e->getMessage());
|
||||
|
||||
} catch (InvalidAgeException $e) {
|
||||
|
||||
return view('dashboard.application-rendering.add-age');
|
||||
}
|
||||
}
|
||||
|
||||
public function saveApplicationAnswers(Request $request, $vacancySlug)
|
||||
{
|
||||
try {
|
||||
if (Auth::user()->isEligible()) {
|
||||
try {
|
||||
$this->applicationService->fillForm(Auth::user(), $request->all(), $vacancySlug);
|
||||
|
||||
$this->applicationService->fillForm(Auth::user(), $request->all(), $vacancySlug);
|
||||
} catch (VacancyNotFoundException | IncompleteApplicationException | UnavailableApplicationException $e) {
|
||||
|
||||
} catch (VacancyNotFoundException | IncompleteApplicationException | UnavailableApplicationException $e) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', $e->getMessage());
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', $e->getMessage());
|
||||
->to(route('showUserApps'))
|
||||
->with('success', __('Thank you! Your application has been processed and our team will get to it shortly.'));
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->to(route('showUserApps'))
|
||||
->with('success', __('Thank you! Your application has been processed and our team will get to it shortly.'));
|
||||
->with('error', __('Your account is not eligible to submit a new application.'));
|
||||
}
|
||||
|
||||
public function updateApplicationStatus(Request $request, Application $application, $newStatus)
|
||||
|
101
app/Http/Controllers/Auth/DiscordController.php
Executable file
101
app/Http/Controllers/Auth/DiscordController.php
Executable file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright © 2020 Miguel Nogueira
|
||||
*
|
||||
* This file is part of Raspberry Staff Manager.
|
||||
*
|
||||
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Raspberry Staff Manager is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Facades\Discord;
|
||||
use App\Facades\Options;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Laravel\Socialite\Two\InvalidStateException;
|
||||
|
||||
class DiscordController extends Controller
|
||||
{
|
||||
|
||||
|
||||
public function discordRedirect() {
|
||||
return Socialite::driver('discord')
|
||||
->scopes(['email', 'guilds.join', 'guilds.members.read', 'guilds'])
|
||||
->redirect();
|
||||
}
|
||||
|
||||
public function discordCallback() {
|
||||
|
||||
try {
|
||||
|
||||
$discordUser = Socialite::driver('discord')->user();
|
||||
|
||||
} catch (InvalidStateException $stateException) {
|
||||
Log::warning('Invalid state for social authentication: ', [
|
||||
'message' => $stateException->getMessage(),
|
||||
'ua' => request()->userAgent(),
|
||||
'ip' => request()->ip()
|
||||
]);
|
||||
return redirect(route('discordRedirect'));
|
||||
}
|
||||
|
||||
$appUser = User::where('email', $discordUser->getEmail())->first();
|
||||
|
||||
if ($appUser) {
|
||||
|
||||
$appUser->discord_token = $discordUser->token;
|
||||
$appUser->discord_refresh_token = $discordUser->refreshToken;
|
||||
$appUser->discord_user_id = $discordUser->getId();
|
||||
$appUser->discord_pfp = $discordUser->getAvatar();
|
||||
$appUser->save();
|
||||
|
||||
Auth::login($appUser, true);
|
||||
|
||||
} else {
|
||||
|
||||
$oAuthUser = User::create([
|
||||
'uuid' => null,
|
||||
'name' => $discordUser->getName(),
|
||||
'email' => $discordUser->getEmail(),
|
||||
'email_verified_at' => now(), // verify the account since it came from a trusted provider
|
||||
'username' => $discordUser->getNickname(),
|
||||
'currentIp' => \request()->ip(),
|
||||
'registrationIp' => request()->ip(),
|
||||
'discord_user_id' => $discordUser->getId(),
|
||||
'discord_pfp' => $discordUser->getAvatar(),
|
||||
'discord_token' => $discordUser->token,
|
||||
'discord_refresh_token' => $discordUser->refreshToken
|
||||
]);
|
||||
|
||||
$oAuthUser->assignRole('user');
|
||||
|
||||
Auth::login($oAuthUser, true);
|
||||
}
|
||||
|
||||
if (session()->has('discordApplicationRedirectedSlug')) {
|
||||
return redirect(route('renderApplicationForm', ['vacancySlug' => session()->pull('discordApplicationRedirectedSlug')]));
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('dashboard');
|
||||
}
|
||||
|
||||
}
|
@@ -26,8 +26,11 @@ use App\Services\AccountSuspensionService;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use App\Facades\IP;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
|
||||
class LoginController extends Controller
|
||||
@@ -76,6 +79,14 @@ class LoginController extends Controller
|
||||
$isLocked = $service->isLocked($user);
|
||||
|
||||
if ($isBanned || $isLocked) {
|
||||
|
||||
Log::alert('Restricted user attempting to login.', [
|
||||
'ip' => $request->ip(),
|
||||
'email' => $user->email,
|
||||
'isBanned' => $isBanned,
|
||||
'isLocked' => $isLocked
|
||||
]);
|
||||
|
||||
return false;
|
||||
} else {
|
||||
return $this->originalAttemptLogin($request);
|
||||
@@ -94,17 +105,11 @@ class LoginController extends Controller
|
||||
'prev' => $user->originalIP,
|
||||
'new' => $request->ip()
|
||||
]);
|
||||
$user->originalIP = $request->ip();
|
||||
$user->currentIp = $request->ip();
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function discordRedirect() {
|
||||
return Socialite::driver('discord')->redirect();
|
||||
}
|
||||
|
||||
public function discordCallback() {
|
||||
// TODO;
|
||||
}
|
||||
}
|
||||
|
@@ -23,11 +23,13 @@ namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Profile;
|
||||
use App\Services\AccountSuspensionService;
|
||||
use App\User;
|
||||
use App\Facades\Options;
|
||||
use App\Facades\IP;
|
||||
use Illuminate\Foundation\Auth\RegistersUsers;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class RegisterController extends Controller
|
||||
@@ -62,19 +64,6 @@ class RegisterController extends Controller
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
public function showRegistrationForm()
|
||||
{
|
||||
$users = User::where('originalIP', \request()->ip())->get();
|
||||
|
||||
foreach ($users as $user) {
|
||||
if ($user && $user->isBanned()) {
|
||||
abort(403, 'You do not have permission to access this page.');
|
||||
}
|
||||
}
|
||||
|
||||
return view('auth.register');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a validator for an incoming registration request.
|
||||
*
|
||||
@@ -106,9 +95,14 @@ class RegisterController extends Controller
|
||||
'uuid' => (Options::getOption('requireGameLicense') && Options::getOption('currentGame') == 'MINECRAFT') ? ['required', 'string', 'unique:users', 'min:32', 'max:32'] : ['nullable', 'string'],
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
||||
'dob' => ['required', 'string', 'date_format:Y-m-d', 'before:-13 years'],
|
||||
'acceptTerms' => ['required', 'accepted'],
|
||||
'password' => $password,
|
||||
], [
|
||||
'uuid.required' => 'Please enter a valid (and Premium) Minecraft username! We do not support cracked users.',
|
||||
'dob.before' => __('You must be 13 years of age or older in order to sign up for an account.'),
|
||||
'dob.required' => __('Please enter your date of birth.'),
|
||||
'uuid.required' => __('Please enter a valid (and Premium) Minecraft username! We do not support cracked users.'),
|
||||
'acceptTerms.required' => __('Please accept the Community Guidelines, Terms of Service and Privacy Policy to continue.')
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -120,12 +114,16 @@ class RegisterController extends Controller
|
||||
*/
|
||||
protected function create(array $data)
|
||||
{
|
||||
$ip = IP::shouldCollect() ? request()->ip() : '0.0.0.0';
|
||||
|
||||
$user = User::create([
|
||||
'uuid' => $data['uuid'] ?? "disabled",
|
||||
'name' => $data['name'],
|
||||
'email' => $data['email'],
|
||||
'password' => Hash::make($data['password']),
|
||||
'originalIP' => IP::shouldCollect() ? request()->ip() : '0.0.0.0',
|
||||
'registrationIp' => $ip,
|
||||
'currentIp' => $ip,
|
||||
'dob' => $data['dob']
|
||||
]);
|
||||
|
||||
$user->assignRole('user');
|
||||
|
@@ -1,88 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright © 2020 Miguel Nogueira
|
||||
*
|
||||
* This file is part of Raspberry Staff Manager.
|
||||
*
|
||||
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Raspberry Staff Manager is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Ban;
|
||||
use App\Events\UserBannedEvent;
|
||||
use App\Http\Requests\BanUserRequest;
|
||||
use App\Services\AccountSuspensionService;
|
||||
use App\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class BanController extends Controller
|
||||
{
|
||||
|
||||
protected $suspensionService;
|
||||
|
||||
public function __construct(AccountSuspensionService $suspensionService)
|
||||
{
|
||||
// Inject the service via DI
|
||||
$this->suspensionService = $suspensionService;
|
||||
}
|
||||
|
||||
public function insert(BanUserRequest $request, User $user)
|
||||
{
|
||||
if (config('demo.is_enabled')) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', __('This feature is disabled'));
|
||||
}
|
||||
|
||||
$this->authorize('create', [Ban::class, $user]);
|
||||
|
||||
|
||||
if (!$this->suspensionService->isSuspended($user)) {
|
||||
|
||||
$this->suspensionService->suspend($request->reason, $request->duration, $user, $request->suspensionType);
|
||||
$request->session()->flash('success', __('Account suspended.'));
|
||||
|
||||
} else {
|
||||
|
||||
$request->session()->flash('error', __('Account already suspended!'));
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function delete(Request $request, User $user)
|
||||
{
|
||||
if (config('demo.is_enabled')) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', __('This feature is disabled'));
|
||||
}
|
||||
|
||||
$this->authorize('delete', $user->bans);
|
||||
|
||||
if ($this->suspensionService->isSuspended($user)) {
|
||||
|
||||
$this->suspensionService->unsuspend($user);
|
||||
$request->session()->flash('success', __('Account unsuspended successfully!'));
|
||||
|
||||
} else {
|
||||
$request->session()->flash('error', __('This account isn\'t suspended!'));
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright © 2020 Miguel Nogueira
|
||||
*
|
||||
* This file is part of Raspberry Staff Manager.
|
||||
*
|
||||
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Raspberry Staff Manager is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\FailedCaptchaException;
|
||||
use App\Http\Requests\HomeContactRequest;
|
||||
use App\Notifications\NewContact;
|
||||
use App\Services\ContactService;
|
||||
use App\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class ContactController extends Controller
|
||||
{
|
||||
protected $users;
|
||||
|
||||
private $contactService;
|
||||
|
||||
public function __construct(User $users, ContactService $contactService)
|
||||
{
|
||||
$this->contactService = $contactService;
|
||||
$this->users = $users;
|
||||
}
|
||||
|
||||
public function create(HomeContactRequest $request)
|
||||
{
|
||||
try {
|
||||
|
||||
$email = $request->email;
|
||||
$msg = $request->msg;
|
||||
$challenge = $request->input('captcha');
|
||||
|
||||
$this->contactService->sendMessage($request->ip(), $msg, $email, $challenge);
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('success',__('Message sent successfully! We usually respond within 48 hours.'));
|
||||
|
||||
} catch (FailedCaptchaException $ex) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', $ex->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@@ -24,10 +24,12 @@ namespace App\Http\Controllers;
|
||||
use App\Application;
|
||||
use App\Events\ApplicationApprovedEvent;
|
||||
use App\Events\ApplicationDeniedEvent;
|
||||
use App\Services\AbsenceService;
|
||||
use App\Services\AccountSuspensionService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class DevToolsController extends Controller
|
||||
{
|
||||
@@ -105,4 +107,16 @@ class DevToolsController extends Controller
|
||||
->with('error', __('There were no expired suspensions (or no suspensions at all) to purge.'));
|
||||
|
||||
}
|
||||
|
||||
public function endAbsencesNow(AbsenceService $service)
|
||||
{
|
||||
$this->singleAuthorise();
|
||||
|
||||
$service->endExpired();
|
||||
Log::alert('(absence cleaner) Forcefully started absence expiration check!');
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('success', 'Cleaned up expired absences.');
|
||||
}
|
||||
}
|
||||
|
@@ -41,11 +41,4 @@ class HomeController extends Controller
|
||||
return view('home')
|
||||
->with('positions', $positions);
|
||||
}
|
||||
|
||||
public function pageGiveaway()
|
||||
{
|
||||
|
||||
return view('giveaway');
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -21,8 +21,12 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\ProfileAlreadyExistsException;
|
||||
use App\Exceptions\ProfileCreationFailedException;
|
||||
use App\Exceptions\ProfileNotFoundException;
|
||||
use App\Facades\IP;
|
||||
use App\Http\Requests\ProfileSave;
|
||||
use App\Services\AccountSuspensionService;
|
||||
use App\Services\ProfileService;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
@@ -32,18 +36,12 @@ use Spatie\Permission\Models\Role;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
private $profileService;
|
||||
private ProfileService $profileService;
|
||||
|
||||
public function __construct(ProfileService $profileService) {
|
||||
$this->profileService = $profileService;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
return view('dashboard.user.directory')
|
||||
->with('users', User::with('profile', 'bans')->paginate(9));
|
||||
}
|
||||
|
||||
public function showProfile()
|
||||
{
|
||||
// TODO: Come up with cleaner social media solution, e.g. social media object
|
||||
@@ -60,26 +58,23 @@ class ProfileController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function showSingleProfile(User $user)
|
||||
public function showSingleProfile(AccountSuspensionService $accountSuspensionService, User $user)
|
||||
{
|
||||
|
||||
if (is_null($user->profile)) {
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', "This user doesn't have a profile.");
|
||||
|
||||
}
|
||||
|
||||
$socialMediaProfiles = json_decode($user->profile->socialLinks, true);
|
||||
$createdDate = Carbon::parse($user->created_at);
|
||||
|
||||
$systemRoles = Role::all()->pluck('name')->all();
|
||||
$userRoles = $user->roles->pluck('name')->all();
|
||||
|
||||
$roleList = [];
|
||||
|
||||
foreach ($systemRoles as $role) {
|
||||
if (in_array($role, $userRoles)) {
|
||||
$roleList[$role] = true;
|
||||
} else {
|
||||
$roleList[$role] = false;
|
||||
}
|
||||
}
|
||||
|
||||
$suspensionInfo = null;
|
||||
if ($user->isBanned())
|
||||
if ($accountSuspensionService->isSuspended($user))
|
||||
{
|
||||
$suspensionInfo = [
|
||||
|
||||
@@ -98,8 +93,7 @@ class ProfileController extends Controller
|
||||
'insta' => $socialMediaProfiles['links']['insta'] ?? 'UpdateMe',
|
||||
'discord' => $socialMediaProfiles['links']['discord'] ?? 'UpdateMe#12345',
|
||||
'since' => $createdDate->englishMonth.' '.$createdDate->year,
|
||||
'ipInfo' => IP::lookup($user->originalIP),
|
||||
'roles' => $roleList,
|
||||
'ipInfo' => IP::lookup($user->currentIp),
|
||||
'suspensionInfo' => $suspensionInfo
|
||||
]);
|
||||
} else {
|
||||
@@ -114,4 +108,44 @@ class ProfileController extends Controller
|
||||
->back()
|
||||
->with('success', __('Profile updated.'));
|
||||
}
|
||||
|
||||
|
||||
public function createProfile(Request $request)
|
||||
{
|
||||
|
||||
try {
|
||||
$this->profileService->createProfile($request->user());
|
||||
} catch (\Exception $e) {
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', $e->getMessage());
|
||||
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('success', __('Your profile has been created.'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function deleteProfile(Request $request)
|
||||
{
|
||||
|
||||
try {
|
||||
$this->profileService->deleteProfile($request->user());
|
||||
} catch (ProfileNotFoundException $e) {
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', $e->getMessage());
|
||||
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('success', __('Profile deleted successfully.'));
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -22,22 +22,36 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Ban;
|
||||
use App\Facades\IP;
|
||||
use App\Facades\Options;
|
||||
use App\Http\Requests\Add2FASecretRequest;
|
||||
use App\Http\Requests\AddDobRequest;
|
||||
use App\Http\Requests\BanUserRequest;
|
||||
use App\Http\Requests\ChangeEmailRequest;
|
||||
use App\Http\Requests\ChangePasswordRequest;
|
||||
use App\Http\Requests\DeleteUserRequest;
|
||||
use App\Http\Requests\FlushSessionsRequest;
|
||||
use App\Http\Requests\Remove2FASecretRequest;
|
||||
use App\Http\Requests\Reset2FASecretRequest;
|
||||
use App\Http\Requests\SearchPlayerRequest;
|
||||
use App\Http\Requests\SetNewPasswordRequest;
|
||||
use App\Http\Requests\UpdateUserRequest;
|
||||
use App\Notifications\ChangedPassword;
|
||||
use App\Notifications\EmailChanged;
|
||||
use App\Notifications\PasswordAdminResetNotification;
|
||||
use App\Notifications\TwoFactorResetNotification;
|
||||
use App\Services\AccountSuspensionService;
|
||||
use App\Services\DiscordService;
|
||||
use App\Traits\DisablesFeatures;
|
||||
use App\Traits\HandlesAccountDeletion;
|
||||
use App\Traits\ReceivesAccountTokens;
|
||||
use App\User;
|
||||
use Google2FA;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -45,13 +59,20 @@ use Spatie\Permission\Models\Role;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
use HandlesAccountDeletion;
|
||||
use HandlesAccountDeletion, DisablesFeatures;
|
||||
|
||||
|
||||
/**
|
||||
* Shows list of users
|
||||
*
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function showUsers()
|
||||
{
|
||||
$this->authorize('viewPlayers', User::class);
|
||||
|
||||
return view('dashboard.administration.players')
|
||||
return view('dashboard.administration.users')
|
||||
->with([
|
||||
'users' => User::with('roles')->paginate('6'),
|
||||
'numUsers' => count(User::all()),
|
||||
@@ -59,6 +80,15 @@ class UserController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Searches for a player with the given search query.
|
||||
*
|
||||
* @deprecated Until Algolia implementation
|
||||
* @param SearchPlayerRequest $request
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function showPlayersLike(SearchPlayerRequest $request)
|
||||
{
|
||||
$this->authorize('viewPlayers', User::class);
|
||||
@@ -72,7 +102,7 @@ class UserController extends Controller
|
||||
if (! $matchingUsers->isEmpty()) {
|
||||
$request->session()->flash('success', __('There were :usersCount user(s) matching your search.', ['usersCount' => $matchingUsers->count()]));
|
||||
|
||||
return view('dashboard.administration.players')
|
||||
return view('dashboard.administration.users')
|
||||
->with([
|
||||
'users' => $matchingUsers,
|
||||
'numUsers' => count(User::all()),
|
||||
@@ -85,6 +115,16 @@ class UserController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shows the user account's settings page
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
|
||||
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
|
||||
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
|
||||
*/
|
||||
public function showAccount(Request $request)
|
||||
{
|
||||
$QRCode = null;
|
||||
@@ -109,6 +149,58 @@ class UserController extends Controller
|
||||
->with('twofaQRCode', $QRCode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show account management screen
|
||||
*
|
||||
* @param AccountSuspensionService $suspensionService
|
||||
* @param Request $request
|
||||
* @param User $user
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function showAcocuntManagement(AccountSuspensionService $suspensionService, Request $request, User $user)
|
||||
{
|
||||
|
||||
$this->authorize('adminEdit', $user);
|
||||
|
||||
$systemRoles = Role::all()->pluck('name')->all();
|
||||
$userRoles = $user->roles->pluck('name')->all();
|
||||
|
||||
$roleList = [];
|
||||
|
||||
foreach ($systemRoles as $role) {
|
||||
if (in_array($role, $userRoles)) {
|
||||
$roleList[$role] = true;
|
||||
} else {
|
||||
$roleList[$role] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return view('dashboard.user.manage')
|
||||
->with([
|
||||
'user' => $user,
|
||||
'roles' => $roleList,
|
||||
'isVerified' => $user->isVerified(),
|
||||
'isLocked' => $suspensionService->isLocked($user),
|
||||
'isSuspended' => $suspensionService->isSuspended($user),
|
||||
'hasDiscord' => $user->hasDiscordConnection(),
|
||||
'hasPassword' => $user->hasPassword(),
|
||||
'requireLicense' => Options::getOption('requireGameLicense'),
|
||||
'suspensionReason' => $suspensionService->getSuspensionReason($user),
|
||||
'suspensionDuration' => $suspensionService->getSuspensionDuration($user),
|
||||
'has2FA' => $user->has2FA(),
|
||||
'applications' => $user->applications()->get()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out other sessions for the current user
|
||||
*
|
||||
* @param FlushSessionsRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\AuthenticationException
|
||||
*/
|
||||
public function flushSessions(FlushSessionsRequest $request)
|
||||
{
|
||||
// TODO: Move all log calls to a listener, which binds to an event fired by each significant event, such as this one
|
||||
@@ -127,6 +219,14 @@ class UserController extends Controller
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Change the current user's password
|
||||
*
|
||||
* @param ChangePasswordRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse|void
|
||||
*/
|
||||
public function changePassword(ChangePasswordRequest $request)
|
||||
{
|
||||
if (config('demo.is_enabled')) {
|
||||
@@ -155,13 +255,80 @@ class UserController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets a new password for the user.
|
||||
*
|
||||
* @param SetNewPasswordRequest $request
|
||||
* @return Application|RedirectResponse|Redirector
|
||||
*/
|
||||
public function setPassword(SetNewPasswordRequest $request) {
|
||||
|
||||
if (!Auth::user()->hasPassword()) {
|
||||
|
||||
Auth::user()->password = Hash::make($request->newpass);
|
||||
Auth::user()->save();
|
||||
|
||||
Auth::logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect(route('login'));
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', __('Your account already has a password.'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets a user's password and removes their discord information from storage
|
||||
*
|
||||
* @param User $user
|
||||
* @param SetNewPasswordRequest $request
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function unlinkDiscordAccount(Request $request, DiscordService $discordService)
|
||||
{
|
||||
if ($request->user()->hasPassword()) {
|
||||
try {
|
||||
$discordService->revokeAccountTokens(Auth::user());
|
||||
Log::warning('Revoking social account tokens, user initiated', [
|
||||
'user' => Auth::user()->email
|
||||
]);
|
||||
} catch (RequestException $requestException) {
|
||||
|
||||
if ($requestException->getCode() == 401) {
|
||||
return redirect(route('discordRedirect'));
|
||||
}
|
||||
|
||||
Log::error('Error while trying to revoke Discord credentials', [$requestException->getMessage()]);
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', __('An unknown error ocurred. Please try again later.'));
|
||||
}
|
||||
|
||||
$request->session()->flash('success', __('Discord account unlinked successfully. Link it again by re-authorizing the app with the same account in the login screen, or through your account settings.'));
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', __('Please set a password for your account first before trying to unlink Discord.'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Change the current user's email address
|
||||
*
|
||||
* @param ChangeEmailRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function changeEmail(ChangeEmailRequest $request)
|
||||
{
|
||||
if (config('demo.is_enabled')) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', __('This feature is disabled'));
|
||||
}
|
||||
$this->disable();
|
||||
|
||||
$user = User::find(Auth::user()->id);
|
||||
|
||||
@@ -184,14 +351,69 @@ class UserController extends Controller
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function delete(DeleteUserRequest $request, User $user)
|
||||
{
|
||||
if (config('demo.is_enabled')) {
|
||||
|
||||
/**
|
||||
* Removes the user's password and notifies them.
|
||||
*
|
||||
* @param User $user The user to remove the password for
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function forcePasswordReset(User $user) {
|
||||
|
||||
$this->authorize('adminEdit', $user);
|
||||
|
||||
if ($user->hasPassword()) {
|
||||
$user->notify(new PasswordAdminResetNotification());
|
||||
|
||||
$user->password = null;
|
||||
$user->save();
|
||||
|
||||
|
||||
Log::alert("Removed account password", [
|
||||
'target' => $user,
|
||||
'actor' => Auth::user()
|
||||
]);
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', _('This feature is disabled'));
|
||||
->with('success', __('Account password removed.'));
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', __('This user doesn\'t have a password to reset.'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a user's date of birth if they don't have one.
|
||||
*
|
||||
* @param AddDobRequest $request
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function addDob(AddDobRequest $request) {
|
||||
|
||||
Auth::user()->dob = $request->dob;
|
||||
Auth::user()->save();
|
||||
|
||||
return redirect()
|
||||
->back();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete the given user's account
|
||||
*
|
||||
* @param DeleteUserRequest $request
|
||||
* @param User $user
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function delete(DeleteUserRequest $request, User $user)
|
||||
{
|
||||
$this->disable();
|
||||
|
||||
$this->authorize('delete', $user);
|
||||
|
||||
if ($request->confirmPrompt == 'DELETE ACCOUNT') {
|
||||
@@ -204,14 +426,19 @@ class UserController extends Controller
|
||||
return redirect()->route('registeredPlayerList');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update a given user's details
|
||||
*
|
||||
* @param UpdateUserRequest $request
|
||||
* @param User $user
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function update(UpdateUserRequest $request, User $user)
|
||||
{
|
||||
if (config('demo.is_enabled')) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', __('This feature is disabled'));
|
||||
}
|
||||
$this->authorize('adminEdit', $user);
|
||||
$this->disable();
|
||||
|
||||
// Mass update would not be possible here without extra code, making route model binding useless
|
||||
$user->email = $request->email;
|
||||
@@ -243,6 +470,16 @@ class UserController extends Controller
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate and add a 2FA secret for the current user
|
||||
*
|
||||
* @param Add2FASecretRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
|
||||
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
|
||||
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
|
||||
*/
|
||||
public function add2FASecret(Add2FASecretRequest $request)
|
||||
{
|
||||
if (config('demo.is_enabled')) {
|
||||
@@ -285,6 +522,13 @@ class UserController extends Controller
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the current user's two factor secret key
|
||||
*
|
||||
* @param Remove2FASecretRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function remove2FASecret(Remove2FASecretRequest $request)
|
||||
{
|
||||
Log::warning('SECURITY: Disabling two factor authentication (user initiated)', [
|
||||
@@ -300,34 +544,94 @@ class UserController extends Controller
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function terminate(Request $request, User $user)
|
||||
{
|
||||
$this->authorize('terminate', User::class);
|
||||
if (config('demo.is_enabled')) {
|
||||
|
||||
/**
|
||||
* Remove the given user's two factor secret key
|
||||
*
|
||||
* @param Reset2FASecretRequest $request
|
||||
* @param User $user
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function reset2FASecret(Reset2FASecretRequest $request, User $user) {
|
||||
// note: could invalidate other sessions for increased security
|
||||
if ($user->has2FA()) {
|
||||
Log::warning('SECURITY: Disabling two factor authentication (admin initiated)', [
|
||||
'initiator' => $request->user()->email,
|
||||
'target' => $user->email,
|
||||
'ip' => $request->ip(),
|
||||
]);
|
||||
|
||||
$user->twofa_secret = null;
|
||||
$user->password = null;
|
||||
$user->save();
|
||||
|
||||
$user->notify(new TwoFactorResetNotification());
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', __('This feature is disabled'));
|
||||
->with('success', __('Two factor removed & user notified.'));
|
||||
}
|
||||
|
||||
// TODO: move logic to policy
|
||||
if (! $user->isStaffMember() || $user->is(Auth::user())) {
|
||||
$request->session()->flash('error', __('You cannot terminate this user.'));
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', 'This user does not have two-factor authentication enabled.');
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
/**
|
||||
* Suspend the given user
|
||||
*
|
||||
* @param AccountSuspensionService $suspensionService
|
||||
* @param BanUserRequest $request
|
||||
* @param User $user
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function suspend(AccountSuspensionService $suspensionService, BanUserRequest $request, User $user)
|
||||
{
|
||||
$this->authorize('create', [Ban::class, $user]);
|
||||
$this->disable();
|
||||
|
||||
if ($suspensionService->isSuspended($user))
|
||||
{
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', __('Account already suspended.'));
|
||||
}
|
||||
|
||||
foreach ($user->roles as $role) {
|
||||
if ($role->name == 'user') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$user->removeRole($role->name);
|
||||
if ($request->suspensionType = "on") {
|
||||
$suspensionService->suspend($user, $request->reason, $request->duration);
|
||||
}
|
||||
else {
|
||||
$suspensionService->suspend($user, $request->reason);
|
||||
}
|
||||
|
||||
Log::info('User '.$user->name.' has just been demoted.');
|
||||
$request->session()->flash('success', __('User terminated successfully.'));
|
||||
|
||||
//TODO: Dispatch event
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsuspend the given user
|
||||
*
|
||||
* @param AccountSuspensionService $suspensionService
|
||||
* @param Request $request
|
||||
* @param User $user
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function unsuspend(AccountSuspensionService $suspensionService, Request $request, User $user)
|
||||
{
|
||||
$this->authorize('delete', $user->bans);
|
||||
$this->disable();
|
||||
|
||||
if ($suspensionService->isSuspended($user)) {
|
||||
|
||||
$suspensionService->unsuspend($user);
|
||||
$request->session()->flash('success', __('Account unsuspended successfully!'));
|
||||
|
||||
} else {
|
||||
$request->session()->flash('error', __('This account isn\'t suspended!'));
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -70,6 +70,8 @@ class VacancyController extends Controller
|
||||
'discordRoleID' => $request->discordRole,
|
||||
'vacancyFormID' => $request->vacancyFormID,
|
||||
'vacancyCount' => $request->vacancyCount,
|
||||
'requiresDiscord' => $request->requireDiscordAccount == 'on',
|
||||
'requiredAge' => $request->requiredAge
|
||||
|
||||
]);
|
||||
|
||||
@@ -142,6 +144,8 @@ class VacancyController extends Controller
|
||||
$vacancy->vacancyFullDescription = $request->vacancyFullDescription;
|
||||
$vacancy->vacancyDescription = $request->vacancyDescription;
|
||||
$vacancy->vacancyCount = $request->vacancyCount;
|
||||
$vacancy->requiresDiscord = $request->requireDiscordAccount == 'on';
|
||||
$vacancy->requiredAge = $request->requiredAge;
|
||||
|
||||
$vacancy->save();
|
||||
|
||||
@@ -153,10 +157,18 @@ class VacancyController extends Controller
|
||||
public function delete(Request $request, Vacancy $vacancy)
|
||||
{
|
||||
$this->authorize('delete', $vacancy);
|
||||
$vacancy->delete();
|
||||
|
||||
if ($vacancy->teams->isEmpty()) {
|
||||
|
||||
$vacancy->delete();
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('success', __('Vacancy deleted. All applications associated with it are now gone too.'));
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('success', __('Vacancy deleted. All applications associated with it are now gone too.'));
|
||||
->with('error', __('Please detach any teams that may be using this vacancy first.'));
|
||||
}
|
||||
}
|
||||
|
@@ -89,10 +89,10 @@ class Kernel extends HttpKernel
|
||||
'2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
|
||||
'passwordexpiration' => \App\Http\Middleware\PasswordExpirationMiddleware::class,
|
||||
'passwordredirect' => \App\Http\Middleware\PasswordExpirationRedirectMiddleware::class,
|
||||
'localize' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class,
|
||||
'localizationRedirect' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class,
|
||||
'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class,
|
||||
'localeCookieRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleCookieRedirect::class,
|
||||
'localeViewPath' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationViewPath::class,
|
||||
'localize' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class,
|
||||
'localizationRedirect' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class,
|
||||
'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class,
|
||||
'localeCookieRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleCookieRedirect::class,
|
||||
'localeViewPath' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationViewPath::class,
|
||||
];
|
||||
}
|
||||
|
@@ -22,8 +22,11 @@
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Application;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\View;
|
||||
@@ -33,34 +36,42 @@ class ApplicationEligibility
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @deprecated Deprecated in 0.9.0
|
||||
* @see User::isEligible()
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$curtime = new Carbon(now());
|
||||
$eligible = false;
|
||||
$daysRemaining = __('N/A');
|
||||
|
||||
if (Auth::check()) {
|
||||
$applications = Application::where('applicantUserID', Auth::user()->id)->get();
|
||||
$eligible = true;
|
||||
|
||||
$daysRemaining = 0;
|
||||
$lastApplication = Application::where('applicantUserID', Auth::user()->id)->latest()->first();
|
||||
|
||||
if (! $applications->isEmpty()) {
|
||||
foreach ($applications as $application) {
|
||||
$appTime = Carbon::parse($application->created_at);
|
||||
if ($appTime->isSameMonth($curtime)) {
|
||||
Log::warning('Notice: Application ID '.$application->id.' was found to be in the same month as today\'s time, making the user '.Auth::user()->name.' ineligible for application');
|
||||
$eligible = false;
|
||||
}
|
||||
}
|
||||
if (is_null($lastApplication)) {
|
||||
View::share('isEligibleForApplication', true);
|
||||
View::share('eligibilityDaysRemaining', 0);
|
||||
|
||||
$allowedTime = Carbon::parse($applications->last()->created_at)->addMonth();
|
||||
$daysRemaining = $allowedTime->diffInDays(now());
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$daysRemaining = $lastApplication->created_at->addMonth()->diffInDays(now());
|
||||
if ($lastApplication->created_at->diffInMonths(now()) > 1 && in_array($lastApplication->applicationStatus, ['DENIED', 'APPROVED'])) {
|
||||
|
||||
$eligible = true;
|
||||
}
|
||||
|
||||
Log::debug('Perfomed application eligibility check', [
|
||||
'eligible' => $eligible,
|
||||
'daysRemaining' => $daysRemaining,
|
||||
'ipAddress' => Auth::user()->originalIP,
|
||||
'checkUserID' => Auth::user()->id
|
||||
]);
|
||||
|
||||
View::share('isEligibleForApplication', $eligible);
|
||||
View::share('eligibilityDaysRemaining', $daysRemaining);
|
||||
}
|
||||
|
@@ -21,12 +21,21 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Services\AccountSuspensionService;
|
||||
use Closure;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\View;
|
||||
|
||||
class Bancheck
|
||||
{
|
||||
private $suspensionService;
|
||||
|
||||
|
||||
public function __construct(AccountSuspensionService $suspensionService) {
|
||||
$this->suspensionService = $suspensionService;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
@@ -37,11 +46,11 @@ class Bancheck
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$userIP = $request->ip();
|
||||
$anonymousUser = User::where('ipAddress', $userIP)->get();
|
||||
$anonymousUser = User::where('currentIp', $userIP)->get();
|
||||
|
||||
if (Auth::check() && Auth::user()->isBanned()) {
|
||||
if (Auth::check() && $this->suspensionService->isSuspended($anonymousUser)) {
|
||||
View::share('isBanned', true);
|
||||
} elseif (! $anonymousUser->isEmpty() && User::find($anonymousUser->id)->isBanned()) {
|
||||
} elseif (! $anonymousUser->isEmpty() && $this->suspensionService->isSuspended(User::find($anonymousUser->id))) {
|
||||
View::share('isBanned', true);
|
||||
} else {
|
||||
View::share('isBanned', false);
|
||||
|
@@ -21,6 +21,7 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Services\AccountSuspensionService;
|
||||
use Closure;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
@@ -35,10 +36,10 @@ class ForceLogoutMiddleware
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
if (Auth::user()->isBanned()) {
|
||||
Auth::logout();
|
||||
|
||||
$request->session()->flash('error', __('Your account is suspended. You will not be able to login or register until the suspension is lifted.'));
|
||||
if ((new AccountSuspensionService())->isSuspended(Auth::user())) {
|
||||
Auth::logout();
|
||||
$request->session()->flash('error', __('Your account is suspended. If you think this was a mistake, please contact an admin.'));
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
|
25
app/Http/Requests/AddDobRequest.php
Normal file
25
app/Http/Requests/AddDobRequest.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class AddDobRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'dob' => 'required|string|date_format:Y-m-d|before:-13 years',
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
if (is_null(Auth::user()->dob)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
28
app/Http/Requests/AdminPasswordResetRequest.php
Executable file
28
app/Http/Requests/AdminPasswordResetRequest.php
Executable file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class AdminPasswordResetRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
if (Auth::user()->has2FA()) {
|
||||
return [
|
||||
'currentPassword' => 'required|current_password:web',
|
||||
'otp' => 'required|integer|max:6',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'currentPassword' => 'required|current_password:web',
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -44,7 +44,6 @@ class Remove2FASecretRequest extends FormRequest
|
||||
{
|
||||
return [
|
||||
'currentPassword' => 'required|current_password',
|
||||
'consent' => 'required|accepted',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
20
app/Http/Requests/Reset2FASecretRequest.php
Normal file
20
app/Http/Requests/Reset2FASecretRequest.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class Reset2FASecretRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'currentPassword' => 'required|current_password',
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -4,7 +4,7 @@ namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class HomeContactRequest extends FormRequest
|
||||
class SetNewPasswordRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
@@ -13,7 +13,11 @@ class HomeContactRequest extends FormRequest
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
if (\Auth::user()->hasDiscordConnection()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,9 +28,7 @@ class HomeContactRequest extends FormRequest
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'email' => 'required|email',
|
||||
'msg' => 'required|string',
|
||||
'captcha' => 'required|string'
|
||||
'newpass' => 'required|string|min:10|confirmed',
|
||||
];
|
||||
}
|
||||
}
|
@@ -46,7 +46,7 @@ class UpdateUserRequest extends FormRequest
|
||||
return [
|
||||
'email' => 'required|email',
|
||||
'name' => 'required|string',
|
||||
'uuid' => 'required|max:32|min:32',
|
||||
'uuid' => 'nullable|max:32|min:32',
|
||||
'roles' => 'required_without_all',
|
||||
];
|
||||
}
|
||||
|
@@ -47,6 +47,8 @@ class VacancyEditRequest extends FormRequest
|
||||
'vacancyDescription' => 'required|string',
|
||||
'vacancyFullDescription' => 'nullable|string',
|
||||
'vacancyCount' => 'required|integer|min:1',
|
||||
'requireDiscordAccount' => 'required|string',
|
||||
'requiredAge' => 'required|integer|numeric|min:13|max:100'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,8 @@ use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class VacancyRequest extends FormRequest
|
||||
{
|
||||
public mixed $requiresDiscordAccount;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
@@ -46,10 +48,12 @@ class VacancyRequest extends FormRequest
|
||||
'vacancyName' => 'required|string',
|
||||
'vacancyDescription' => 'required|string',
|
||||
'vacancyFullDescription' => 'nullable|string',
|
||||
'permissionGroup' => 'required|string',
|
||||
'discordRole' => 'required|string',
|
||||
'permissionGroup' => 'nullable|string',
|
||||
'discordRole' => 'nullable|string',
|
||||
'vacancyCount' => 'required|integer',
|
||||
'vacancyFormID' => 'required|integer',
|
||||
'requireDiscordAccount' => 'required|string',
|
||||
'requiredAge' => 'required|integer|numeric|min:13|max:100'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
0
app/Jobs/ProcessAccountDelete.php
Normal file → Executable file
0
app/Jobs/ProcessAccountDelete.php
Normal file → Executable file
39
app/Jobs/ProcessExpiredAbsences.php
Executable file
39
app/Jobs/ProcessExpiredAbsences.php
Executable file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Services\AbsenceService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ProcessExpiredAbsences implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(AbsenceService $absenceService)
|
||||
{
|
||||
Log::info('(absence cleaner) Ending all expired absences.');
|
||||
|
||||
$absenceService->endExpired();
|
||||
}
|
||||
}
|
30
app/Listeners/NewUser.php
Executable file
30
app/Listeners/NewUser.php
Executable file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class NewUser
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@@ -49,10 +49,11 @@ class OnUserRegistration
|
||||
// TODO: Send push notification to online admins via browser (w/ pusher)
|
||||
Log::info('User '.$event->user->name.' has just registered for an account.');
|
||||
|
||||
foreach (User::all() as $user) {
|
||||
if ($user->hasRole('admin')) {
|
||||
$user->notify(new NewUser($event->user));
|
||||
}
|
||||
}
|
||||
User::whereHas('roles', function ($q) {
|
||||
$q->where('name', 'admin');
|
||||
})->get()->each(function ($user, $key) use ($event) {
|
||||
$user->notify(new NewUser($event->user));
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -45,11 +45,15 @@ class PromoteUser
|
||||
*/
|
||||
public function handle(ApplicationApprovedEvent $event)
|
||||
{
|
||||
Log::info('User '.$event->application->user->name . 'has just been promoted (application approved)');
|
||||
Log::info('User promoted automatically (application approved)', [
|
||||
'user' => $event->application->user->name,
|
||||
'vacancy' => $event->application->response->vacancy->vacancyName,
|
||||
'role' => 'staff'
|
||||
]);
|
||||
|
||||
$event->application->setStatus('APPROVED');
|
||||
$event->application->response->vacancy->decrease();
|
||||
$event->application->user->assignRole('reviewer');
|
||||
$event->application->user->assignRole('staff');
|
||||
|
||||
$event->application->user->notify(new ApplicationApproved($event->application));
|
||||
}
|
||||
|
68
app/Notifications/AbsenceRequestApproved.php
Executable file
68
app/Notifications/AbsenceRequestApproved.php
Executable file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Absence;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class AbsenceRequestApproved extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public Absence $absence;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Absence $absence)
|
||||
{
|
||||
$this->absence = $absence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
return (new MailMessage)
|
||||
->greeting('Hi ' . $notifiable->name . ',')
|
||||
->from(config('notification.sender.address'), config('notification.sender.name'))
|
||||
->subject(config('app.name').' - absence request approved')
|
||||
->line("Your recent Leave of Absence request from {$this->absence->created_at} has just been approved by an admin.")
|
||||
->line('Your inactivity during the period you selected won\'t be counted. You will receive another email notification when your request ends, or if you decide to cancel it.')
|
||||
->action('View your request', url(route('absences.show', ['absence' => $this->absence->id])))
|
||||
->salutation('The team at ' . config('app.name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($notifiable)
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
68
app/Notifications/AbsenceRequestCancelled.php
Executable file
68
app/Notifications/AbsenceRequestCancelled.php
Executable file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Absence;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class AbsenceRequestCancelled extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public Absence $absence;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Absence $absence)
|
||||
{
|
||||
$this->absence = $absence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
return (new MailMessage)
|
||||
->greeting('Hi ' . $notifiable->name . ',')
|
||||
->from(config('notification.sender.address'), config('notification.sender.name'))
|
||||
->subject(config('app.name').' - absence request cancelled')
|
||||
->line("This notification confirms that your recent Leave of Absence from {$this->absence->created_at} has just been cancelled by you.")
|
||||
->line('Please note that any inactivity will be counted in our activity metrics. You may also make a new request if you wish.')
|
||||
->action('Send new request', url(route('absences.create')))
|
||||
->salutation('The team at ' . config('app.name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($notifiable)
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
68
app/Notifications/AbsenceRequestDeclined.php
Executable file
68
app/Notifications/AbsenceRequestDeclined.php
Executable file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Absence;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class AbsenceRequestDeclined extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public Absence $absence;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Absence $absence)
|
||||
{
|
||||
$this->absence = $absence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
return (new MailMessage)
|
||||
->greeting('Hi ' . $notifiable->name . ',')
|
||||
->from(config('notification.sender.address'), config('notification.sender.name'))
|
||||
->subject(config('app.name').' - absence request declined')
|
||||
->line("Your recent Leave of Absence request from {$this->absence->created_at} has just been declined by an admin.")
|
||||
->line('Please note that any inactivity will be counted in our activity metrics. You may make a new request, but we recommend you ask your team lead regarding your declined request.')
|
||||
->action('Send new request', url(route('absences.create')))
|
||||
->salutation('The team at ' . config('app.name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($notifiable)
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
68
app/Notifications/AbsenceRequestEnded.php
Executable file
68
app/Notifications/AbsenceRequestEnded.php
Executable file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Absence;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class AbsenceRequestEnded extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public Absence $absence;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Absence $absence)
|
||||
{
|
||||
$this->absence = $absence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
return (new MailMessage)
|
||||
->greeting('Hi ' . $notifiable->name . ',')
|
||||
->from(config('notification.sender.address'), config('notification.sender.name'))
|
||||
->subject(config('app.name').' - absence request expired')
|
||||
->line("Your Leave of Absence request from {$this->absence->created_at} (until {$this->absence->predicted_end}) has expired today.")
|
||||
->line('Please note that any inactivity will be counted in our activity metrics. You may now make a new request if you still need more time.')
|
||||
->action('Send new request', url(route('absences.create')))
|
||||
->salutation('The team at ' . config('app.name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($notifiable)
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
0
app/Notifications/AccountDeleted.php
Normal file → Executable file
0
app/Notifications/AccountDeleted.php
Normal file → Executable file
0
app/Notifications/AccountLocked.php
Normal file → Executable file
0
app/Notifications/AccountLocked.php
Normal file → Executable file
0
app/Notifications/AccountUnlocked.php
Normal file → Executable file
0
app/Notifications/AccountUnlocked.php
Normal file → Executable file
68
app/Notifications/NewAbsenceRequest.php
Executable file
68
app/Notifications/NewAbsenceRequest.php
Executable file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Absence;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class NewAbsenceRequest extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public Absence $absence;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Absence $absence)
|
||||
{
|
||||
$this->absence = $absence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
return (new MailMessage)
|
||||
->greeting('Hi ' . $notifiable->name . ',')
|
||||
->from(config('notification.sender.address'), config('notification.sender.name'))
|
||||
->subject(config('app.name').' - new absence request pending review')
|
||||
->line("A new absence request has just been submitted, scheduled to end {$this->absence->predicted_end}. Please review this request and take the appropriate action(s). The requester will be notified of your decision by email.")
|
||||
->line("You are receiving this email because you're a site admin.")
|
||||
->action('Review request', url(route('absences.show', ['absence' => $this->absence->id])))
|
||||
->salutation('The team at ' . config('app.name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($notifiable)
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
35
app/Notifications/PasswordAdminResetNotification.php
Executable file
35
app/Notifications/PasswordAdminResetNotification.php
Executable file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class PasswordAdminResetNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function via($notifiable): array
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
public function toMail($notifiable): MailMessage
|
||||
{
|
||||
return (new MailMessage)
|
||||
->from(config('notification.sender.address'), config('notification.sender.name'))
|
||||
->subject(config('app.name').' - account password invalidated')
|
||||
->markdown('mail.adminreset', ['name' => $notifiable->name]);
|
||||
}
|
||||
|
||||
public function toArray($notifiable): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
35
app/Notifications/TwoFactorResetNotification.php
Normal file
35
app/Notifications/TwoFactorResetNotification.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class TwoFactorResetNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function via($notifiable): array
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
public function toMail($notifiable): MailMessage
|
||||
{
|
||||
return (new MailMessage)
|
||||
->from(config('notification.sender.address'), config('notification.sender.name'))
|
||||
->subject(config('app.name').' - your second factor has been reset')
|
||||
->markdown('mail.two-factor-reset', ['name' => $notifiable->name]);
|
||||
}
|
||||
|
||||
public function toArray($notifiable): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
0
app/Notifications/UserDeletedAccount.php
Normal file → Executable file
0
app/Notifications/UserDeletedAccount.php
Normal file → Executable file
@@ -21,7 +21,10 @@
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Exceptions\ProfileAlreadyExistsException;
|
||||
use App\Exceptions\ProfileCreationFailedException;
|
||||
use App\Profile;
|
||||
use App\Services\ProfileService;
|
||||
use App\User;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -40,15 +43,24 @@ class UserObserver
|
||||
*/
|
||||
public function created(User $user)
|
||||
{
|
||||
// TODO: Make sure that the profile is created, throw an exception if otherwise, or try to recreate the profile.
|
||||
Profile::create([
|
||||
$profileService = new ProfileService();
|
||||
|
||||
'profileShortBio' => 'Write a one-liner about you here!',
|
||||
'profileAboutMe' => 'Tell us a bit about you.',
|
||||
'socialLinks' => '{}',
|
||||
'userID' => $user->id,
|
||||
|
||||
]);
|
||||
try
|
||||
{
|
||||
$profileService->createProfile($user);
|
||||
}
|
||||
catch (ProfileAlreadyExistsException $exception)
|
||||
{
|
||||
Log::error('Attempting to create profile that already exists!', [
|
||||
'trace' => $exception->getTrace()
|
||||
]);
|
||||
}
|
||||
catch (ProfileCreationFailedException $e)
|
||||
{
|
||||
Log::error('Failed creating a new profile!', [
|
||||
'trace' => $e->getTrace()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -47,6 +47,7 @@ use App\Vote;
|
||||
use Illuminate\Auth\Notifications\VerifyEmail;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
@@ -90,5 +91,17 @@ class AuthServiceProvider extends ServiceProvider
|
||||
->salutation('The team at ' . config('app.name'));
|
||||
|
||||
});
|
||||
|
||||
Gate::define('viewLogViewer', function (?User $user){
|
||||
return $user->hasPermissionTo('admin.developertools.use');
|
||||
});
|
||||
|
||||
Gate::define('downloadLogFile', function (User $user){
|
||||
return $user->hasPermissionTo('admin.developertools.use');
|
||||
});
|
||||
|
||||
Gate::define('deleteLogFile', function (User $user){
|
||||
return $user->hasPermissionTo('admin.developertools.use');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
31
app/Providers/DiscordOuthProvider.php
Executable file
31
app/Providers/DiscordOuthProvider.php
Executable file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Helpers\Discord;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class DiscordOuthProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
\App::bind('discordServiceFacade', function () {
|
||||
return new Discord();
|
||||
});
|
||||
}
|
||||
}
|
@@ -70,6 +70,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
'App\Events\UserBannedEvent' => [
|
||||
'App\Listeners\OnUserBanned',
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
/**
|
||||
|
@@ -31,7 +31,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const HOME = '/home';
|
||||
public const HOME = '/dashboard';
|
||||
|
||||
/**
|
||||
* Define your route model bindings, pattern filters, etc.
|
||||
|
183
app/Services/AbsenceService.php
Executable file
183
app/Services/AbsenceService.php
Executable file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Absence;
|
||||
use App\Exceptions\AbsenceNotActionableException;
|
||||
use App\Notifications\AbsenceRequestApproved;
|
||||
use App\Notifications\AbsenceRequestCancelled;
|
||||
use App\Notifications\AbsenceRequestDeclined;
|
||||
use App\Notifications\AbsenceRequestEnded;
|
||||
use App\Notifications\NewAbsenceRequest;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
|
||||
class AbsenceService
|
||||
{
|
||||
|
||||
/**
|
||||
* Determines whether someone already has an active leave of absence request
|
||||
*
|
||||
* @param User $user The user to check
|
||||
* @return bool Their status
|
||||
*/
|
||||
public function hasActiveRequest(Authenticatable $user): bool {
|
||||
|
||||
$absences = Absence::where('requesterID', $user->id)->get();
|
||||
|
||||
foreach ($absences as $absence) {
|
||||
|
||||
// Or we could adjust the query (using a model scope) to only return valid absences;
|
||||
// If there are any, refuse to store more, but this approach also works
|
||||
// A model scope that only returns cancelled, declined and ended absences could also be implemented for future use
|
||||
if (in_array($absence->getRawOriginal('status'), ['PENDING', 'APPROVED']))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function createAbsence(Authenticatable $requester, Request $request)
|
||||
{
|
||||
|
||||
$absence = Absence::create([
|
||||
'requesterID' => $requester->id,
|
||||
'start' => $request->start_date,
|
||||
'predicted_end' => $request->predicted_end,
|
||||
'available_assist' => $request->available_assist == "on",
|
||||
'reason' => $request->reason,
|
||||
'status' => 'PENDING',
|
||||
]);
|
||||
|
||||
foreach(User::role('admin')->get() as $admin) {
|
||||
$admin->notify(new NewAbsenceRequest($absence));
|
||||
}
|
||||
|
||||
Log::info('Processing new leave of absence request.', [
|
||||
'requesting_user' => $requester->email,
|
||||
'absenceid' => $absence->id,
|
||||
'reason' => $request->reason
|
||||
]);
|
||||
|
||||
return $absence;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets an absence as Approved.
|
||||
*
|
||||
* @param Absence $absence The absence to approve.
|
||||
* @return Absence The approved absence.
|
||||
* @throws AbsenceNotActionableException
|
||||
*/
|
||||
public function approveAbsence(Absence $absence)
|
||||
{
|
||||
Log::info('An absence request has just been approved.', [
|
||||
'absenceid' => $absence->id,
|
||||
'reviewing_admim' => Auth::user()->email,
|
||||
'new_status' => 'APPROVED'
|
||||
]);
|
||||
|
||||
return $absence
|
||||
->setApproved()
|
||||
->requester->notify(new AbsenceRequestApproved($absence));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets an absence as Declined.
|
||||
*
|
||||
* @param Absence $absence The absence to decline.
|
||||
* @return Absence The declined absence.
|
||||
* @throws AbsenceNotActionableException
|
||||
*/
|
||||
public function declineAbsence(Absence $absence)
|
||||
{
|
||||
Log::warning('An absence request has just been declined.', [
|
||||
'absenceid' => $absence->id,
|
||||
'reviewing_admim' => Auth::user()->email,
|
||||
'new_status' => 'DECLINED'
|
||||
]);
|
||||
|
||||
return $absence
|
||||
->setDeclined()
|
||||
->requester->notify(new AbsenceRequestDeclined($absence));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets an absence as Cancelled.
|
||||
*
|
||||
* @param Absence $absence The absence to cancel.
|
||||
* @return Absence The cancelled absence.
|
||||
* @throws AbsenceNotActionableException
|
||||
*/
|
||||
public function cancelAbsence(Absence $absence)
|
||||
{
|
||||
Log::warning('An absence request has just been cancelled (only cancellable by requester).', [
|
||||
'absenceid' => $absence->id,
|
||||
'new_status' => 'CANCELLED'
|
||||
]);
|
||||
|
||||
return $absence
|
||||
->setCancelled()
|
||||
->requester->notify(new AbsenceRequestCancelled($absence));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an absence as Ended.
|
||||
*
|
||||
* @param Absence $absence
|
||||
* @return bool
|
||||
*/
|
||||
public function endAbsence(Absence $absence)
|
||||
{
|
||||
Log::info('An absence request has just expired.', [
|
||||
'absenceid' => $absence->id,
|
||||
'new_status' => 'ENDED'
|
||||
]);
|
||||
|
||||
return $absence
|
||||
->setEnded()
|
||||
->requester->notify(new AbsenceRequestEnded($absence));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an absence
|
||||
*
|
||||
* @param Absence $absence The absence to remove.
|
||||
* @return bool Whether the absence was removed.
|
||||
*/
|
||||
public function removeAbsence(Absence $absence): bool
|
||||
{
|
||||
Log::alert('An absence request has just been removed.', [
|
||||
'absence_details' => $absence,
|
||||
'reviewing_admim' => Auth::user()->email,
|
||||
]);
|
||||
|
||||
return $absence->delete();
|
||||
}
|
||||
|
||||
|
||||
public function endExpired()
|
||||
{
|
||||
foreach (Absence::all() as $absence)
|
||||
{
|
||||
if (!Carbon::parse($absence->predicted_end)->isFuture()) {
|
||||
$this->endAbsence($absence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@@ -16,18 +16,14 @@ class AccountSuspensionService
|
||||
|
||||
/**
|
||||
* Suspends a user account, with given $reason.
|
||||
* Permanent if no duration given.
|
||||
*
|
||||
* This method will take the target user and add a suspension to the database,
|
||||
* effectively banning the user from the app. Suspensions may be temporary or permanent.
|
||||
* Suspensions also block registration attempts.
|
||||
*
|
||||
* @param string $reason Suspension reason.
|
||||
* @param string $duration Duration. This is a timestamp.
|
||||
* @param User $target Who to suspend.
|
||||
* @param string $type Permanent or temporary?
|
||||
* @param string $reason Suspension reason.
|
||||
* @param int|null $duration Duration in days
|
||||
* @return Ban The ban itself
|
||||
*/
|
||||
public function suspend($reason, $duration, User $target, $type = "on"): Ban {
|
||||
public function suspend(User $target, string $reason, int $duration = null): Ban {
|
||||
|
||||
Log::alert("An user account has just been suspended.", [
|
||||
'taget_email' => $target->email,
|
||||
@@ -35,19 +31,17 @@ class AccountSuspensionService
|
||||
'reason' => $reason
|
||||
]);
|
||||
|
||||
if ($type == "on") {
|
||||
if ($duration > 0) {
|
||||
$expiryDate = now()->addDays($duration);
|
||||
}
|
||||
|
||||
$ban = Ban::create([
|
||||
return Ban::create([
|
||||
'userID' => $target->id,
|
||||
'reason' => $reason,
|
||||
'bannedUntil' => ($type == "on") ? $expiryDate->format('Y-m-d H:i:s') : null,
|
||||
'bannedUntil' => ($duration > 0) ? $expiryDate->format('Y-m-d H:i:s') : null,
|
||||
'authorUserID' => Auth::user()->id,
|
||||
'isPermanent' => ($type == "off") ? true : false
|
||||
'isPermanent' => ($duration == 0) ? true : false
|
||||
]);
|
||||
|
||||
return $ban;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,6 +58,16 @@ class AccountSuspensionService
|
||||
$user->bans->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a user is suspended
|
||||
*
|
||||
* @param User $user The user to check
|
||||
* @return bool Whether the mentioned user is suspended
|
||||
*/
|
||||
public function isSuspended(User $user): bool {
|
||||
return !is_null($user->bans);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -107,19 +111,6 @@ class AccountSuspensionService
|
||||
return $user->save();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether a user is suspended
|
||||
*
|
||||
* @param User $user The user to check
|
||||
* @return bool Whether the mentioned user is suspended
|
||||
*/
|
||||
public function isSuspended(User $user): bool {
|
||||
return !is_null($user->bans);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether an account is locked
|
||||
*
|
||||
@@ -131,21 +122,21 @@ class AccountSuspensionService
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a suspension directly and makes it permanent.
|
||||
* Retrieves the reason for the user's suspension.
|
||||
*
|
||||
* @param Ban $ban The suspension to make permanent
|
||||
* @param User $user The user account to check
|
||||
* @return string|bool Reason for the suspension, false if not suspended
|
||||
*/
|
||||
public function makePermanent(Ban $ban): void {
|
||||
public function getSuspensionReason(User $user): string|bool {
|
||||
return ($this->isSuspended($user)) ? $user->bans->reason : false;
|
||||
}
|
||||
|
||||
Log::alert('A suspension has just been made permanent.', [
|
||||
'target_email' => $ban->user->email
|
||||
]);
|
||||
|
||||
$ban->bannedUntil = null;
|
||||
$ban->isPermanent = true;
|
||||
|
||||
$ban->save();
|
||||
public function getSuspensionDuration(User $user): string|null {
|
||||
if ($this->isSuspended($user) && !is_null($user->bans->bannedUntil)) {
|
||||
return $user->bans->bannedUntil->diffForHumans();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -3,7 +3,11 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Exceptions\DiscordAccountRequiredException;
|
||||
use App\Exceptions\IncompatibleAgeException;
|
||||
use App\Exceptions\InvalidAgeException;
|
||||
use App\Notifications\ApplicationConfirmed;
|
||||
use Carbon\Carbon;
|
||||
use ContextAwareValidator;
|
||||
use App\Application;
|
||||
use App\Events\ApplicationDeniedEvent;
|
||||
@@ -22,12 +26,27 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ApplicationService
|
||||
{
|
||||
/**
|
||||
* @throws DiscordAccountRequiredException
|
||||
* @throws IncompatibleAgeException
|
||||
* @throws InvalidAgeException
|
||||
*/
|
||||
public function renderForm($vacancySlug)
|
||||
{
|
||||
$vacancyWithForm = Vacancy::with('forms')->where('vacancySlug', $vacancySlug)->get();
|
||||
|
||||
$firstVacancy = $vacancyWithForm->first();
|
||||
|
||||
if (is_null(Auth::user()->dob)) {
|
||||
throw new InvalidAgeException("User must have added their age to apply for this vacancy.");
|
||||
} elseif(Carbon::parse(Auth::user()->dob)->age < $firstVacancy->requiredAge) {
|
||||
throw new IncompatibleAgeException("Sorry, you must be {$firstVacancy->requiredAge} or older to apply to {$firstVacancy->vacancyName}.");
|
||||
}
|
||||
|
||||
|
||||
if ($firstVacancy->requiresDiscord && !Auth::user()->hasDiscordConnection()) {
|
||||
throw new DiscordAccountRequiredException('A discord account is required beyond this point.');
|
||||
}
|
||||
|
||||
if (!$vacancyWithForm->isEmpty() && $firstVacancy->vacancyCount !== 0 && $firstVacancy->vacancyStatus == 'OPEN') {
|
||||
return view('dashboard.application-rendering.apply')
|
||||
->with([
|
||||
@@ -36,7 +55,7 @@ class ApplicationService
|
||||
]);
|
||||
} else {
|
||||
|
||||
throw new ApplicationNotFoundException('The application you\'re looking for could not be found or it is currently unavailable.', 404);
|
||||
throw new ApplicationNotFoundException(__('The application you\'re looking for could not be found or it is currently unavailable.'), 404);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -94,11 +113,12 @@ class ApplicationService
|
||||
'applicant' => $applicant->name
|
||||
]);
|
||||
|
||||
foreach (User::all() as $user) {
|
||||
if ($user->hasRole('admin')) {
|
||||
$user->notify((new NewApplicant($application, $vacancy->first())));
|
||||
}
|
||||
}
|
||||
User::whereHas('roles', function ($q) {
|
||||
$q->where('name', 'admin');
|
||||
})->get()->each(function ($user, $key) use ($application, $vacancy) {
|
||||
$user->notify((new NewApplicant($application, $vacancy->first())));
|
||||
});
|
||||
|
||||
$application->user->notify(new ApplicationConfirmed($application));
|
||||
|
||||
return true;
|
||||
|
42
app/Services/DiscordService.php
Normal file
42
app/Services/DiscordService.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\User;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class DiscordService
|
||||
{
|
||||
|
||||
/**
|
||||
* Sends a token revocation request to Discord to invalidate a specific $user's tokens.
|
||||
* Please ensure you have the user set a password for their account after this, or request new tokens.
|
||||
*
|
||||
* @see https://www.rfc-editor.org/rfc/rfc7009
|
||||
* @param User $user
|
||||
* @return bool
|
||||
* @throws RequestException
|
||||
*/
|
||||
public function revokeAccountTokens(User $user): bool
|
||||
{
|
||||
$req = Http::asForm()->post(config('services.discord.base_url') . '/oauth2/token/revoke', [
|
||||
'client_id' => config('services.discord.client_id'),
|
||||
'client_secret' => config('services.discord.client_secret'),
|
||||
'token' => $user->discord_token,
|
||||
])->throw();
|
||||
|
||||
|
||||
|
||||
$user->discord_token = null;
|
||||
$user->discord_user_id = null;
|
||||
$user->discord_refresh_token = null;
|
||||
$user->discord_pfp = null;
|
||||
$user->save();
|
||||
|
||||
return $req->ok();
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -39,7 +39,7 @@ class FormManagementService
|
||||
|
||||
$deletable = true;
|
||||
|
||||
if (! is_null($form) && ! is_null($form->vacancies) && $form->vacancies->count() !== 0 || ! is_null($form->responses)) {
|
||||
if (! is_null($form->vacancies) && $form->vacancies->count() !== 0 || ! is_null($form->responses)) {
|
||||
$deletable = false;
|
||||
}
|
||||
|
||||
|
@@ -4,15 +4,56 @@
|
||||
namespace App\Services;
|
||||
|
||||
|
||||
use App\Exceptions\ProfileAlreadyExistsException;
|
||||
use App\Exceptions\ProfileCreationFailedException;
|
||||
use App\Exceptions\ProfileNotFoundException;
|
||||
use App\Profile;
|
||||
use App\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ProfileService
|
||||
{
|
||||
|
||||
/**
|
||||
* Creates a new profile for the specified $targetUser.
|
||||
*
|
||||
* @param User $targetUser The user to create the profile for.
|
||||
* @return bool
|
||||
* @throws ProfileAlreadyExistsException
|
||||
* @throws ProfileCreationFailedException
|
||||
*/
|
||||
public function createProfile(User $targetUser): Profile {
|
||||
|
||||
if (is_null($targetUser->profile)) {
|
||||
|
||||
$profile = Profile::create([
|
||||
|
||||
'profileShortBio' => 'Write a one-liner about you here!',
|
||||
'profileAboutMe' => 'Tell us a bit about you.',
|
||||
'socialLinks' => '{}',
|
||||
'userID' => $targetUser->id,
|
||||
|
||||
]);
|
||||
|
||||
if (is_null($profile)) {
|
||||
throw new ProfileCreationFailedException(__('Could not create profile! Please try again later.'));
|
||||
}
|
||||
|
||||
Log::info('Created profile for new user', [
|
||||
'userid' => $targetUser->id
|
||||
]);
|
||||
|
||||
return $profile;
|
||||
}
|
||||
|
||||
throw new ProfileAlreadyExistsException(__('Profile already exists!'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user's profile.
|
||||
*
|
||||
* @throws ProfileNotFoundException
|
||||
*/
|
||||
public function updateProfile($userID, Request $request) {
|
||||
@@ -47,4 +88,26 @@ class ProfileService
|
||||
throw new ProfileNotFoundException("This profile does not exist.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete specified user's profile
|
||||
*
|
||||
* @param User $targetUser
|
||||
* @return bool
|
||||
* @throws ProfileNotFoundException
|
||||
*/
|
||||
public function deleteProfile(User $targetUser): bool
|
||||
{
|
||||
|
||||
if (!is_null($targetUser->profile)) {
|
||||
|
||||
Log::alert('Deleted user profile', [
|
||||
'userid' => $targetUser->id
|
||||
]);
|
||||
|
||||
return $targetUser->profile->delete();
|
||||
}
|
||||
|
||||
throw new ProfileNotFoundException(__('Attempting to delete non-existant profile!'));
|
||||
}
|
||||
|
||||
}
|
||||
|
25
app/Traits/DisablesFeatures.php
Executable file
25
app/Traits/DisablesFeatures.php
Executable file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
||||
trait DisablesFeatures
|
||||
{
|
||||
|
||||
/**
|
||||
* Checks if demo mode is active. If so, it stops any more logic from running.
|
||||
*
|
||||
* @return RedirectResponse|null
|
||||
*/
|
||||
protected function disable(): RedirectResponse|null
|
||||
{
|
||||
if (config('demo.is_enabled')) {
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', __('This feature is disabled'));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
97
app/User.php
97
app/User.php
@@ -24,15 +24,21 @@ namespace App;
|
||||
use App\Services\AccountSuspensionService;
|
||||
use App\Traits\HandlesAccountTokens;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Mpociot\Teamwork\Traits\UserHasTeams;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
class User extends Authenticatable implements MustVerifyEmail
|
||||
{
|
||||
use UserHasTeams, Notifiable, HasRoles;
|
||||
use UserHasTeams, Notifiable, HasRoles, HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@@ -40,7 +46,19 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name', 'email', 'password', 'originalIP', 'username', 'uuid', 'dob',
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
'originalIP',
|
||||
'registrationIp',
|
||||
'username',
|
||||
'uuid',
|
||||
'dob',
|
||||
'email_verified_at',
|
||||
'currentIp',
|
||||
'discord_user_id',
|
||||
'discord_token',
|
||||
'discord_refresh_token'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -49,7 +67,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password', 'remember_token',
|
||||
'password', 'remember_token', 'discord_token', 'discord_refresh_token'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -59,6 +77,8 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
*/
|
||||
protected $casts = [
|
||||
'email_verified_at' => 'datetime',
|
||||
'discord_token' => 'encrypted',
|
||||
'discord_refresh_token' => 'encrypted'
|
||||
];
|
||||
|
||||
// RELATIONSHIPS
|
||||
@@ -71,6 +91,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
public function votes()
|
||||
{
|
||||
return $this->hasMany('App\Vote', 'userID', 'id');
|
||||
|
||||
}
|
||||
|
||||
public function profile()
|
||||
@@ -99,32 +120,52 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
}
|
||||
|
||||
|
||||
public function isEligible(): bool {
|
||||
$lastApplication = Application::where('applicantUserID', $this->getAttribute('id'))->latest()->first();
|
||||
|
||||
// UTILITY LOGIC
|
||||
if (is_null($lastApplication)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a user is banned.
|
||||
*
|
||||
* @deprecated This method is obsolete, as it has been replaced by the suspension service.
|
||||
* @see AccountSuspensionService::isSuspended()
|
||||
*
|
||||
* @return bool Whether the user is banned
|
||||
*/
|
||||
public function isBanned(): bool
|
||||
{
|
||||
return ! $this->bans()->get()->isEmpty();
|
||||
if ($lastApplication->created_at->diffInMonths(now()) > 1 && in_array($lastApplication->applicationStatus, ['DENIED', 'APPROVED'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isStaffMember()
|
||||
|
||||
public function isVerified(): bool {
|
||||
return !is_null($this->email_verified_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if user is staff
|
||||
*
|
||||
* @deprecated This method is being replaced by a better way of checking permissions, rather than checking for group name.
|
||||
* @return bool
|
||||
*/
|
||||
public function isStaffMember(): bool
|
||||
{
|
||||
return $this->hasAnyRole('reviewer', 'admin', 'hiringManager');
|
||||
}
|
||||
|
||||
public function has2FA()
|
||||
/**
|
||||
* Checks if user has 2fa enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has2FA(): bool
|
||||
{
|
||||
return ! is_null($this->twofa_secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if user has team
|
||||
*
|
||||
* @param $team
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTeam($team): bool
|
||||
{
|
||||
if ($team instanceof Team || is_int($team))
|
||||
@@ -140,9 +181,23 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function routeNotificationForSlack($notification)
|
||||
{
|
||||
return config('slack.webhook.integrationURL');
|
||||
/**
|
||||
* Check if user linked their Discord account
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDiscordConnection(): bool {
|
||||
return !is_null($this->discord_token) && !is_null($this->discord_refresh_token);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if user has a password
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPassword(): bool {
|
||||
return !is_null($this->password);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -44,6 +44,8 @@ class Vacancy extends Model
|
||||
'vacancyStatus',
|
||||
'vacancySlug',
|
||||
'team_id',
|
||||
'requiresDiscord',
|
||||
'requiredAge'
|
||||
|
||||
];
|
||||
|
||||
|
@@ -7,16 +7,27 @@ use Illuminate\View\Component;
|
||||
|
||||
class AccountStatus extends Component
|
||||
{
|
||||
public $user;
|
||||
public bool
|
||||
$isVerified,
|
||||
$isSuspended,
|
||||
$isLocked,
|
||||
$has2FA,
|
||||
$hasDiscord,
|
||||
$hasPassword;
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($userId)
|
||||
public function __construct($isVerified, $isSuspended, $isLocked, $has2FA, $hasDiscord, $hasPassword)
|
||||
{
|
||||
$this->user = User::findOrFail($userId);
|
||||
$this->isVerified = $isVerified;
|
||||
$this->isSuspended = $isSuspended;
|
||||
$this->isLocked = $isLocked;
|
||||
$this->has2FA = $has2FA;
|
||||
$this->hasDiscord = $hasDiscord;
|
||||
$this->hasPassword = $hasPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
|
14
app/View/Components/ConfirmPassword.php
Executable file
14
app/View/Components/ConfirmPassword.php
Executable file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class ConfirmPassword extends Component
|
||||
{
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.confirm-password');
|
||||
}
|
||||
}
|
14
app/View/Components/ConfirmSecondFactor.php
Executable file
14
app/View/Components/ConfirmSecondFactor.php
Executable file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class ConfirmSecondFactor extends Component
|
||||
{
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.confirm-second-factor');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user