WIP: Road to 1.0.0 #1
|
@ -172,6 +172,8 @@ class UserController extends Controller
|
||||||
|
|
||||||
if (! is_null($user)) {
|
if (! is_null($user)) {
|
||||||
$user->password = Hash::make($request->newPassword);
|
$user->password = Hash::make($request->newPassword);
|
||||||
|
$user->password_last_updated = now();
|
||||||
|
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
Log::info('User '.$user->name.' has changed their password', [
|
Log::info('User '.$user->name.' has changed their password', [
|
||||||
|
|
|
@ -85,6 +85,8 @@ class Kernel extends HttpKernel
|
||||||
'usernameUUID' => \App\Http\Middleware\UsernameUUID::class,
|
'usernameUUID' => \App\Http\Middleware\UsernameUUID::class,
|
||||||
'forcelogout' => \App\Http\Middleware\ForceLogoutMiddleware::class,
|
'forcelogout' => \App\Http\Middleware\ForceLogoutMiddleware::class,
|
||||||
'2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
|
'2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
|
||||||
|
'passwordexpiration' => \App\Http\Middleware\PasswordExpirationMiddleware::class,
|
||||||
|
'passwordredirect' => \App\Http\Middleware\PasswordExpirationRedirectMiddleware::class,
|
||||||
'localize' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class,
|
'localize' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class,
|
||||||
'localizationRedirect' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class,
|
'localizationRedirect' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class,
|
||||||
'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class,
|
'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class,
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Facades\Options;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class PasswordExpirationMiddleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Closure $next
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
if(Auth::check())
|
||||||
|
{
|
||||||
|
$sinceUpdate = Carbon::parse(Auth::user()->password_last_updated)->diffInDays(now());
|
||||||
|
$updateThreshold = Options::getOption('password_expiry');
|
||||||
|
|
||||||
|
if ($updateThreshold !== 0 && $sinceUpdate > $updateThreshold)
|
||||||
|
{
|
||||||
|
session()->put('passwordExpired', true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
session()->put('passwordExpired', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class PasswordExpirationRedirectMiddleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Closure $next
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
if (Auth::check() && session('passwordExpired'))
|
||||||
|
{
|
||||||
|
// WARNING!! Routes under the profile group must not have this middleware, because it'll result in an infinite redirect loop.
|
||||||
|
return redirect(route('showAccountSettings'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class AddPasswordLastUpdatedToUsers extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->timestamp('password_last_updated')->after('remember_token')->nullable();
|
||||||
|
$table->boolean('administratively_locked')->after('email')->default(false)->comment('Account locked by settings changes, e.g. 2fa grace period timeout');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('password_last_updated');
|
||||||
|
$table->dropColumn('administratively_locked');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -100,8 +100,7 @@
|
||||||
|
|
||||||
<p><b><i class="fas fa-exclamation-triangle"></i> DANGER: </b> Insecure security policy</p>
|
<p><b><i class="fas fa-exclamation-triangle"></i> DANGER: </b> Insecure security policy</p>
|
||||||
|
|
||||||
<p>Your current password security policy is set to <b>off</b>. This allows users to choose potentially unsafe passwords.</p>
|
<p>Your current password security policy is set to <b>off</b>. This allows users to choose potentially unsafe passwords. We strongly recommend you update this value to <b>Medium</b>.</p>
|
||||||
<p>We strongly recommend you update this value to <b>Low</b> or <b>Medium</b>.</p>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -205,6 +205,23 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if(session('passwordExpired'))
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<p><i class="fas fa-exclamation-triangle"></i><b> Your password has expired</b></p>
|
||||||
|
<p>
|
||||||
|
You've been redirected here because your <b>password has expired.</b> All users must change their password every {{ \App\Facades\Options::getOption('password_expiry') }} days.
|
||||||
|
This is put in place to make sure user accounts remain secure.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Please change update your password now. You won't be able to use the application until you do this.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
|
|
@ -67,45 +67,50 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo
|
||||||
Route::get('/accounts/danger-zone/{ID}/{action}/{token}', [UserController::class, 'processDeleteConfirmation'])
|
Route::get('/accounts/danger-zone/{ID}/{action}/{token}', [UserController::class, 'processDeleteConfirmation'])
|
||||||
->name('processDeleteConfirmation');
|
->name('processDeleteConfirmation');
|
||||||
|
|
||||||
Route::group(['middleware' => ['auth', 'forcelogout', '2fa', 'verified']], function () {
|
Route::group(['middleware' => ['auth', 'forcelogout', 'passwordexpiration', '2fa', 'verified']], function () {
|
||||||
Route::get('/dashboard', [DashboardController::class, 'index'])
|
|
||||||
|
|
||||||
|
|
||||||
|
Route::group(['middleware' => ['passwordredirect']], function(){
|
||||||
|
|
||||||
|
Route::get('/dashboard', [DashboardController::class, 'index'])
|
||||||
->name('dashboard')
|
->name('dashboard')
|
||||||
->middleware('eligibility');
|
->middleware('eligibility');
|
||||||
|
|
||||||
Route::get('users/directory', [ProfileController::class, 'index'])
|
Route::get('users/directory', [ProfileController::class, 'index'])
|
||||||
->name('directory');
|
->name('directory');
|
||||||
|
|
||||||
Route::resource('teams', TeamController::class);
|
Route::resource('teams', TeamController::class);
|
||||||
|
|
||||||
Route::post('teams/{team}/invites/send', [TeamController::class, 'invite'])
|
Route::post('teams/{team}/invites/send', [TeamController::class, 'invite'])
|
||||||
->name('sendInvite');
|
->name('sendInvite');
|
||||||
|
|
||||||
Route::get('teams/{team}/switch', [TeamController::class, 'switchTeam'])
|
Route::get('teams/{team}/switch', [TeamController::class, 'switchTeam'])
|
||||||
->name('switchTeam');
|
->name('switchTeam');
|
||||||
|
|
||||||
Route::patch('teams/{team}/vacancies/update', [TeamController::class, 'assignVacancies'])
|
Route::patch('teams/{team}/vacancies/update', [TeamController::class, 'assignVacancies'])
|
||||||
->name('assignVacancies');
|
->name('assignVacancies');
|
||||||
|
|
||||||
Route::get('teams/invites/{action}/{token}', [TeamController::class, 'processInviteAction'])
|
Route::get('teams/invites/{action}/{token}', [TeamController::class, 'processInviteAction'])
|
||||||
->name('processInvite');
|
->name('processInvite');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Route::get('team/files', [TeamFileController::class, 'index'])
|
Route::get('team/files', [TeamFileController::class, 'index'])
|
||||||
->name('showTeamFiles');
|
->name('showTeamFiles');
|
||||||
|
|
||||||
Route::post('team/files/upload', [TeamFileController::class, 'store'])
|
Route::post('team/files/upload', [TeamFileController::class, 'store'])
|
||||||
->name('uploadTeamFile');
|
->name('uploadTeamFile');
|
||||||
|
|
||||||
Route::delete('team/files/{teamFile}/delete', [TeamFileController::class, 'destroy'])
|
Route::delete('team/files/{teamFile}/delete', [TeamFileController::class, 'destroy'])
|
||||||
->name('deleteTeamFile');
|
->name('deleteTeamFile');
|
||||||
|
|
||||||
Route::get('team/files/{teamFile}/download', [TeamFileController::class, 'download'])
|
Route::get('team/files/{teamFile}/download', [TeamFileController::class, 'download'])
|
||||||
->name('downloadTeamFile');
|
->name('downloadTeamFile');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::group(['prefix' => '/applications', 'middleware' => ['passwordredirect']], function () {
|
||||||
Route::group(['prefix' => '/applications'], function () {
|
|
||||||
Route::get('/my-applications', [ApplicationController::class, 'showUserApps'])
|
Route::get('/my-applications', [ApplicationController::class, 'showUserApps'])
|
||||||
->name('showUserApps')
|
->name('showUserApps')
|
||||||
->middleware('eligibility');
|
->middleware('eligibility');
|
||||||
|
@ -136,7 +141,7 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo
|
||||||
->name('voteApplication');
|
->name('voteApplication');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => 'appointments'], function () {
|
Route::group(['prefix' => 'appointments', 'middleware' => ['passwordredirect']], function () {
|
||||||
Route::post('schedule/appointments/{application}', [AppointmentController::class, 'saveAppointment'])
|
Route::post('schedule/appointments/{application}', [AppointmentController::class, 'saveAppointment'])
|
||||||
->name('scheduleAppointment');
|
->name('scheduleAppointment');
|
||||||
|
|
||||||
|
@ -144,7 +149,7 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo
|
||||||
->name('updateAppointment');
|
->name('updateAppointment');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => 'apply', 'middleware' => ['eligibility']], function () {
|
Route::group(['prefix' => 'apply', 'middleware' => ['eligibility', 'passwordredirect']], function () {
|
||||||
Route::get('positions/{vacancySlug}', [ApplicationController::class, 'renderApplicationForm'])
|
Route::get('positions/{vacancySlug}', [ApplicationController::class, 'renderApplicationForm'])
|
||||||
->name('renderApplicationForm');
|
->name('renderApplicationForm');
|
||||||
|
|
||||||
|
@ -152,15 +157,21 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo
|
||||||
->name('saveApplicationForm');
|
->name('saveApplicationForm');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Further locking down the profile section by adding the middleware to everything but the required routes
|
||||||
Route::group(['prefix' => '/profile'], function () {
|
Route::group(['prefix' => '/profile'], function () {
|
||||||
Route::get('/settings', [ProfileController::class, 'showProfile'])
|
Route::get('/settings', [ProfileController::class, 'showProfile'])
|
||||||
->name('showProfileSettings');
|
->name('showProfileSettings')
|
||||||
|
->middleware('passwordredirect');
|
||||||
|
|
||||||
Route::patch('/settings/save', [ProfileController::class, 'saveProfile'])
|
Route::patch('/settings/save', [ProfileController::class, 'saveProfile'])
|
||||||
->name('saveProfileSettings');
|
->name('saveProfileSettings')
|
||||||
|
->middleware('passwordredirect');
|
||||||
|
|
||||||
Route::get('user/{user}', [ProfileController::class, 'showSingleProfile'])
|
Route::get('user/{user}', [ProfileController::class, 'showSingleProfile'])
|
||||||
->name('showSingleProfile');
|
->name('showSingleProfile')
|
||||||
|
->middleware('passwordredirect');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Route::get('/settings/account', [UserController::class, 'showAccount'])
|
Route::get('/settings/account', [UserController::class, 'showAccount'])
|
||||||
->name('showAccountSettings');
|
->name('showAccountSettings');
|
||||||
|
@ -169,23 +180,30 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo
|
||||||
Route::patch('/settings/account/change-password', [UserController::class, 'changePassword'])
|
Route::patch('/settings/account/change-password', [UserController::class, 'changePassword'])
|
||||||
->name('changePassword');
|
->name('changePassword');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Route::patch('/settings/account/change-email', [UserController::class, 'changeEmail'])
|
Route::patch('/settings/account/change-email', [UserController::class, 'changeEmail'])
|
||||||
->name('changeEmail');
|
->name('changeEmail')
|
||||||
|
->middleware('passwordredirect');
|
||||||
|
|
||||||
Route::post('/settings/account/flush-sessions', [UserController::class, 'flushSessions'])
|
Route::post('/settings/account/flush-sessions', [UserController::class, 'flushSessions'])
|
||||||
->name('flushSessions');
|
->name('flushSessions')
|
||||||
|
->middleware('passwordredirect');
|
||||||
|
|
||||||
Route::patch('/settings/account/twofa/enable', [UserController::class, 'add2FASecret'])
|
Route::patch('/settings/account/twofa/enable', [UserController::class, 'add2FASecret'])
|
||||||
->name('enable2FA');
|
->name('enable2FA')
|
||||||
|
->middleware('passwordredirect');
|
||||||
|
|
||||||
Route::patch('/settings/account/twofa/disable', [UserController::class, 'remove2FASecret'])
|
Route::patch('/settings/account/twofa/disable', [UserController::class, 'remove2FASecret'])
|
||||||
->name('disable2FA');
|
->name('disable2FA')
|
||||||
|
->middleware('passwordredirect');
|
||||||
|
|
||||||
Route::patch('/settings/account/dg/delete', [UserController::class, 'userDelete'])
|
Route::patch('/settings/account/dg/delete', [UserController::class, 'userDelete'])
|
||||||
->name('userDelete');
|
->name('userDelete')
|
||||||
|
->middleware('passwordredirect');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => '/hr'], function () {
|
Route::group(['prefix' => '/hr', 'middleware' => ['passwordredirect']], function () {
|
||||||
Route::get('staff-members', [UserController::class, 'showStaffMembers'])
|
Route::get('staff-members', [UserController::class, 'showStaffMembers'])
|
||||||
->name('staffMemberList');
|
->name('staffMemberList');
|
||||||
|
|
||||||
|
@ -199,7 +217,7 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo
|
||||||
->name('terminateStaffMember');
|
->name('terminateStaffMember');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => 'admin'], function () {
|
Route::group(['prefix' => 'admin', 'middleware' => ['passwordredirect']], function () {
|
||||||
Route::get('settings', [OptionsController::class, 'index'])
|
Route::get('settings', [OptionsController::class, 'index'])
|
||||||
->name('showSettings');
|
->name('showSettings');
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue