diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 714a632..f576327 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -3,11 +3,13 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; +use App\Profile; use App\Providers\RouteServiceProvider; use App\User; use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; +use function GuzzleHttp\Psr7\str; class RegisterController extends Controller { @@ -50,6 +52,7 @@ class RegisterController extends Controller protected function validator(array $data) { return Validator::make($data, [ + 'uuid' => ['required', 'string', 'unique:users', 'min:32', 'max:32'], 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'password' => ['required', 'string', 'min:8', 'confirmed'], @@ -64,11 +67,24 @@ class RegisterController extends Controller */ protected function create(array $data) { - return User::create([ + + $user = User::create([ + 'uuid' => $data['uuid'], 'name' => $data['name'], 'email' => $data['email'], 'password' => Hash::make($data['password']), 'originalIP' => request()->ip() ]); + + Profile::create([ + + 'profileShortBio' => 'Write a one-liner about you here!', + 'profileAboutMe' => 'Tell us a bit about you.', + 'socialLinks' => '{}', + 'userID' => $user->id + + ]); + + return $user; } } diff --git a/app/Http/Controllers/LogController.php b/app/Http/Controllers/LogController.php deleted file mode 100644 index cd26756..0000000 --- a/app/Http/Controllers/LogController.php +++ /dev/null @@ -1,10 +0,0 @@ -profile->socialLinks, true); + + return view('dashboard.user.profile.userprofile') + ->with([ + 'profile' => Auth::user()->profile, + 'github' => $socialMediaProfiles['links']['github'] ?? 'UpdateMe', + 'twitter' => $socialMediaProfiles['links']['twitter'] ?? 'UpdateMe', + 'insta' => $socialMediaProfiles['links']['insta'] ?? 'UpdateMe', + 'discord' => $socialMediaProfiles['links']['discord'] ?? 'UpdateMe#12345', + ]); + + } + + public function saveProfile(ProfileSave $request) + { + // TODO: Implement profile security policy for logged in users + + $profile = Profile::find(Auth::user()->id); + $social = []; + + if (!is_null($profile)) + { + switch ($request->avatarPref) + { + case 'MOJANG': + $avatarPref = 'crafatar'; + + break; + case 'GRAVATAR': + $avatarPref = strtolower($request->avatarPref); + + break; + } + + $social['links']['github'] = $request->socialGithub; + $social['links']['twitter'] = $request->socialTwitter; + $social['links']['insta'] = $request->socialInsta; + $social['links']['discord'] = $request->socialDiscord; + + $profile->profileShortBio = $request->shortBio; + $profile->profileAboutMe = $request->aboutMe; + $profile->avatarPreference = $avatarPref; + $profile->socialLinks = json_encode($social); + + $profile->save(); + + $request->session()->flash('success', 'Profile settings saved successfully.'); + + } + else + { + $gm = 'Guru Meditation #' . rand(0, 1000); + Log::alert('[GURU MEDITATION]: Could not find profile for authenticated user ' . Auth::user()->name . 'whilst trying to update it! Please verify that profiles are being created automatically during signup.', + [ + 'uuid' => Auth::user()->uuid, + 'timestamp' => now(), + 'route' => $request->route()->getName(), + 'gmcode' => $gm // If this error is reported, the GM code, denoting a severe error, will help us find this entry in the logs + + ]); + $request->session()->flash('error', 'A technical error has occurred whilst trying to save your profile. Incident details have been recorded. Please report this incident to administrators with the following case number: ' . $gm); + } + + return redirect()->back(); } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 19ee9ae..49e4492 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -2,7 +2,14 @@ namespace App\Http\Controllers; +use App\Http\Requests\ChangeEmailRequest; +use App\Http\Requests\ChangePasswordRequest; +use App\Http\Requests\FlushSessionsRequest; +use App\User; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Log; class UserController extends Controller { @@ -16,4 +23,76 @@ class UserController extends Controller { return view('dashboard.administration.players'); } + + public function showAccount() + { + return view('dashboard.user.profile.useraccount') + ->with('ip', request()->ip()); + } + + + 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 + // This will allow for other actions to be performed on certain events (like login failed event) + + Auth::logoutOtherDevices($request->currentPasswordFlush); + Log::notice('User ' . Auth::user()->name . ' has logged out other devices in their account', + [ + 'originIPAddress' => $request->ip(), + 'userID' => Auth::user()->id, + 'timestamp' => now() + ]); + + $request->session()->flash('success', 'Successfully logged out other devices. Remember to change your password if you think you\'ve been compromised.'); + return redirect()->back(); + } + + public function changePassword(ChangePasswordRequest $request) + { + $user = User::find(Auth::user()->id); + + if (!is_null($user)) + { + $user->password = Hash::make($request->newPassword); + $user->save(); + + Log::info('User ' . $user->name . ' has changed their password', [ + 'originIPAddress' => $request->ip(), + 'userID' => $user->id, + 'timestamp' => now() + ]); + Auth::logout(); + + // After logout, the user gets caught by the auth filter, and it automatically redirects back to the previous page + return redirect()->back(); + } + + } + + public function changeEmail(ChangeEmailRequest $request) + { + $user = User::find(Auth::user()->id); + + if (!is_null($user)) + { + $user->email = $request->newEmail; + $user->save(); + + Log::notice('User ' . $user->name . ' has just changed their contact email address', [ + 'originIPAddress' => $request->ip(), + 'userID' => $user->id, + 'timestamp' => now() + ]); + + $request->session()->flash('success', 'Your email address has been changed!'); + } + else + { + $request->session()->flash('error', 'There has been an error whilst trying to update your account. Please contact administrators.'); + } + + return redirect()->back(); + + } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index b81271b..ca9256a 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -62,6 +62,7 @@ class Kernel extends HttpKernel 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, - 'eligibility' => \App\Http\Middleware\ApplicationEligibility::class + 'eligibility' => \App\Http\Middleware\ApplicationEligibility::class, + 'usernameUUID' => \App\Http\Middleware\UsernameUUID::class ]; } diff --git a/app/Http/Middleware/UsernameUUID.php b/app/Http/Middleware/UsernameUUID.php new file mode 100644 index 0000000..4df5965 --- /dev/null +++ b/app/Http/Middleware/UsernameUUID.php @@ -0,0 +1,34 @@ +all(); + if (isset($input['uuid'])) + { + // TODO: Switch to custom Facade + $username = $input['uuid']; + + $conversionRequest = Http::get(config('general.urls.mojang.api') . '/users/profiles/minecraft/' . $username)->body(); + $decodedConversionRequest = json_decode($conversionRequest, true); + + $input['uuid'] = $decodedConversionRequest['id']; + + $request->replace($input); + } + return $next($request); + } +} diff --git a/app/Http/Requests/ChangeEmailRequest.php b/app/Http/Requests/ChangeEmailRequest.php new file mode 100644 index 0000000..128a3eb --- /dev/null +++ b/app/Http/Requests/ChangeEmailRequest.php @@ -0,0 +1,31 @@ + 'required|password', + 'newEmail' => 'required|email|unique:users,email' + ]; + } +} diff --git a/app/Http/Requests/ChangePasswordRequest.php b/app/Http/Requests/ChangePasswordRequest.php new file mode 100644 index 0000000..3feb69b --- /dev/null +++ b/app/Http/Requests/ChangePasswordRequest.php @@ -0,0 +1,31 @@ + 'required|string|confirmed', + 'oldPassword' => 'required|string|password' + ]; + } +} diff --git a/app/Http/Requests/FlushSessionsRequest.php b/app/Http/Requests/FlushSessionsRequest.php new file mode 100644 index 0000000..f274ebd --- /dev/null +++ b/app/Http/Requests/FlushSessionsRequest.php @@ -0,0 +1,30 @@ + 'required|password' + ]; + } +} diff --git a/app/Http/Requests/ProfileSave.php b/app/Http/Requests/ProfileSave.php new file mode 100644 index 0000000..75278be --- /dev/null +++ b/app/Http/Requests/ProfileSave.php @@ -0,0 +1,39 @@ + 'nullable|string|max:100', + 'aboutMe' => 'nullable|string|max:2000', + 'avatarPref' => 'required|string', + 'socialInsta' => 'nullable|string', + 'socialTwitter' => 'nullable|string', + 'socialDiscord' => 'nullable|string', + 'socialGithub' => 'nullable|string' + ]; + } +} diff --git a/app/Listeners/OnUserRegistration.php b/app/Listeners/OnUserRegistration.php new file mode 100644 index 0000000..9456846 --- /dev/null +++ b/app/Listeners/OnUserRegistration.php @@ -0,0 +1,33 @@ +user->name . ' has just registered for an account.'); + } +} diff --git a/app/Log.php b/app/Log.php deleted file mode 100644 index eb84181..0000000 --- a/app/Log.php +++ /dev/null @@ -1,10 +0,0 @@ -belongsTo('App\User', 'userID', 'id'); + } + } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 723a290..693b9b5 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use App\Listeners\OnUserRegistration; use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; @@ -17,6 +18,7 @@ class EventServiceProvider extends ServiceProvider protected $listen = [ Registered::class => [ SendEmailVerificationNotification::class, + OnUserRegistration::class ], ]; diff --git a/app/Providers/MojangStatusProvider.php b/app/Providers/MojangStatusProvider.php index 106c731..4a83e79 100644 --- a/app/Providers/MojangStatusProvider.php +++ b/app/Providers/MojangStatusProvider.php @@ -27,7 +27,7 @@ class MojangStatusProvider extends ServiceProvider */ public function boot() { - + // TODO: (IMPORTANT) Switch this to Middleware if (!Cache::has('mojang_status')) { Log::info("Mojang Status Provider: Mojang Status not found in the cache; Sending new request."); diff --git a/app/User.php b/app/User.php index f2d62db..cc30701 100644 --- a/app/User.php +++ b/app/User.php @@ -41,4 +41,9 @@ class User extends Authenticatable { return $this->hasMany('App\Application', 'applicantUserID', 'id'); } + + public function profile() + { + return $this->hasOne('App\Profile', 'userID', 'id'); + } } diff --git a/app/View/Components/GlobalErrors.php b/app/View/Components/GlobalErrors.php new file mode 100644 index 0000000..ab130be --- /dev/null +++ b/app/View/Components/GlobalErrors.php @@ -0,0 +1,28 @@ + [ - 'My Account', + 'Applications', [ 'text' => 'My Applications', 'icon' => 'fas fa-fw fa-list-ul', @@ -218,7 +218,7 @@ return [ 'icon' => 'fas fa-fw fa-check-double', 'url' => '/applications/current' ] - ] + ], ], 'My Profile', @@ -227,6 +227,11 @@ return [ 'url' => '/profile/settings', 'icon' => 'fas fa-fw fa-cog' ], + [ + 'text' => 'My Account Settings', + 'icon' => 'fas fa-user-circle', + 'url' => '/profile/settings/account' + ], 'Application Management', [ 'text' => 'Outstanding Applications', diff --git a/config/general.php b/config/general.php index 3b6f109..a1943ad 100644 --- a/config/general.php +++ b/config/general.php @@ -7,7 +7,8 @@ return [ 'mojang' => [ - 'statuscheck' => env('MOJANG_STATUS_URL') ?? 'https://status.mojang.com/check' + 'statuscheck' => env('MOJANG_STATUS_URL') ?? 'https://status.mojang.com/check', + 'api' => env('MOJANG_API_URL') ?? ' https://api.mojang.com' ] ] diff --git a/database/migrations/2020_04_29_022245_create_profiles_table.php b/database/migrations/2020_04_29_022245_create_profiles_table.php index 3d8339a..17d195e 100644 --- a/database/migrations/2020_04_29_022245_create_profiles_table.php +++ b/database/migrations/2020_04_29_022245_create_profiles_table.php @@ -15,17 +15,20 @@ class CreateProfilesTable extends Migration { Schema::create('profiles', function (Blueprint $table) { $table->id(); - $table->string('profileShortBio'); - $table->text('profileAboutMe'); + $table->string('profileShortBio')->nullable(); + $table->text('profileAboutMe')->nullable(); $table->enum('avatarPreference', [ 'crafatar', // Mojang Profile 'gravatar' // Email profile - ]); - $table->text('socialLinks'); + ])->default('gravatar'); + $table->text('socialLinks')->nullable(); $table->bigInteger('userID')->unsigned(); $table->timestamps(); - $table->foreign('userID')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('userID') + ->references('id') + ->on('users') + ->onDelete('cascade'); }); } diff --git a/database/migrations/2020_04_29_023317_create_forms_table.php b/database/migrations/2020_04_29_022541_create_forms_table.php similarity index 100% rename from database/migrations/2020_04_29_023317_create_forms_table.php rename to database/migrations/2020_04_29_022541_create_forms_table.php diff --git a/database/migrations/2020_04_29_022642_create_vacancies_table.php b/database/migrations/2020_04_29_022542_create_vacancies_table.php similarity index 93% rename from database/migrations/2020_04_29_022642_create_vacancies_table.php rename to database/migrations/2020_04_29_022542_create_vacancies_table.php index 788206a..b14c0cd 100644 --- a/database/migrations/2020_04_29_022642_create_vacancies_table.php +++ b/database/migrations/2020_04_29_022542_create_vacancies_table.php @@ -19,7 +19,7 @@ class CreateVacanciesTable extends Migration $table->longText('vacancyDescription'); $table->string('permissionGroupName'); $table->string('discordRoleID'); - $table->bigInteger('vacancyFormID')->unsigned(); + $table->unsignedBigInteger('vacancyFormID'); $table->integer('vacancyCount')->default(3); $table->timestamps(); diff --git a/public/css/acc.css b/public/css/acc.css new file mode 100644 index 0000000..69b66e8 --- /dev/null +++ b/public/css/acc.css @@ -0,0 +1,43 @@ +.card { + background-color: #ffffff; + border: 1px solid rgba(0, 34, 51, 0.1); + box-shadow: 2px 4px 10px 0 rgba(0, 34, 51, 0.05), 2px 4px 10px 0 rgba(0, 34, 51, 0.05); + border-radius: 0.15rem; +} + +/* Tabs Card */ + +.tab-card { + border:1px solid #eee; +} + +.tab-card-header { + background:none; +} +/* Default mode */ +.tab-card-header > .nav-tabs { + border: none; + margin: 0px; +} +.tab-card-header > .nav-tabs > li { + margin-right: 2px; +} +.tab-card-header > .nav-tabs > li > a { + border: 0; + border-bottom:2px solid transparent; + margin-right: 0; + color: #737373; + padding: 2px 15px; +} + +.tab-card-header > .nav-tabs > li > a.show { + border-bottom:2px solid #007bff; + color: #007bff; +} +.tab-card-header > .nav-tabs > li > a:hover { + color: #007bff; +} + +.tab-card-header > .tab-content { + padding-bottom: 0; +} diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php index d236a48..91d9cfb 100644 --- a/resources/views/auth/register.blade.php +++ b/resources/views/auth/register.blade.php @@ -61,6 +61,20 @@ +
+ + +
+ + + @error('uuid') + + {{ $message }} + + @enderror +
+
+
- - - -
-
- - - - - -@stop diff --git a/resources/views/dashboard/user/deniedapplications.blade.php b/resources/views/dashboard/user/deniedapplications.blade.php deleted file mode 100644 index 1490c99..0000000 --- a/resources/views/dashboard/user/deniedapplications.blade.php +++ /dev/null @@ -1,83 +0,0 @@ -@extends('adminlte::page') - -@section('title', 'Raspberry Network | Applications') - -@section('content_header') - -

My Account / Denied Applications

- -@stop - -@section('content') - -
- -
- -
-
Info on denied applications
- -

Please note that all applications listed on this page have been denied by the staff team / applications team.

-

The system will only let you apply every thirty days. Your previous applications will be kept for your reference, but you can always delete them here.

-
- -
- -
- -
- -
- -
-
-

My Denied Applications

-
- -
- - - - - - - - - - - - - - - - - - - - - - -
#ApplicantApplication DateDenial DateStatusActions
1.Jonathan Smith2020-04-282020-04-30Denied - - - - -
- -
- - - -
- -
- -
- -@stop diff --git a/resources/views/dashboard/user/profile/useraccount.blade.php b/resources/views/dashboard/user/profile/useraccount.blade.php new file mode 100644 index 0000000..6e5fcc5 --- /dev/null +++ b/resources/views/dashboard/user/profile/useraccount.blade.php @@ -0,0 +1,166 @@ +@extends('adminlte::page') + +@section('title', 'Raspberry Network | Account Settings') + +@section('content_header') + +

My Profile / Account / Settings

+ +@stop + +@section('js') + + + +@stop + +@section('css') + +@stop + +@section('content') + + +
+ +
+ +
+ +
+ +

Welcome back, {{Auth::user()->name}}

+ +

{{Auth::user()->email}}

+ View @ NameMC +
+ +
+ +
+ +
+ +
+ +
+
+ + +
+
+
Change Password
+

Change your password here. This will log you out from all existing sessions for your security.

+ +
+ + @csrf + @method('PATCH') + + +

Forgot your password? Reset it here

+ +
+ + + + + + + +
+ +
+ + +
+
+
Two-factor Authentication
+

This feature is not yet available. Support for Google Authenticator, Authy, Microsoft Authenticator and other compatible apps is coming soon, as well as fingerprint login for android devices.

+ +
+
+
Session Manager
+

Terminating other sessions is generally a good idea if your account has been compromised.

+

Your current session: Logged in from {{ $ip }}

+ +
+
+
Contact Settings
+

Need to change personal data? You can do so here.

+ +
+ + @csrf + @method('PATCH') +
+ + + + + + + + + +
+ +
+ + + +

For security reasons, you cannot make important account changes without confirming your password. You'll also need to verify your new email.

+
+
+ + +
+ +
+
+
+
+ + + +@stop diff --git a/resources/views/dashboard/user/profile/userprofile.blade.php b/resources/views/dashboard/user/profile/userprofile.blade.php index 52428d0..01ab922 100644 --- a/resources/views/dashboard/user/profile/userprofile.blade.php +++ b/resources/views/dashboard/user/profile/userprofile.blade.php @@ -14,6 +14,24 @@ @stop +@section('js') + + @if (session()->has('success')) + + + + @elseif(session()->has('error')) + + + + @endif + +@stop + @section('content') @@ -28,20 +46,24 @@
- User profile picture + @if($profile->avatarPreference == 'gravatar') + User profile picture + @else + User profile picture + @endif

{{Auth::user()->name}}

-

[short bio]

+

{{$profile->profileShortBio}}

@@ -59,7 +81,10 @@
-
+ + + @method('PATCH') + @csrf
@@ -79,15 +104,8 @@
- - - -
- -
- - - + +
@@ -96,15 +114,14 @@
- +
- +

Github-flavored Markdown supported

@@ -130,12 +147,12 @@
@@ -147,28 +164,28 @@
- +
- +
- +
- +
@@ -183,7 +200,7 @@
- +
diff --git a/routes/web.php b/routes/web.php index 0970412..2ae8325 100644 --- a/routes/web.php +++ b/routes/web.php @@ -12,9 +12,16 @@ use Illuminate\Support\Facades\Route; | contains the "web" middleware group. Now create something great! | */ -Auth::routes(); -Route::get('/','HomeController@index')->middleware('eligibility'); +Route::group(['prefix' => 'auth', 'middleware' => ['usernameUUID']], function (){ + + Auth::routes(); + +}); + +Route::get('/','HomeController@index') + ->middleware('eligibility'); + Route::post('/form/contact', 'ContactController@create') ->name('sendSubmission'); @@ -56,7 +63,26 @@ Route::group(['middleware' => 'auth'], function(){ Route::group(['prefix' => '/profile'], function (){ - Route::get('/settings', 'ProfileController@index'); + Route::get('/settings', 'ProfileController@showProfile') + ->name('showProfileSettings'); + + Route::patch('/settings/save', 'ProfileController@saveProfile') + ->name('saveProfileSettings'); + + + + Route::get('/settings/account', 'UserController@showAccount') + ->name('showAccountSettings'); + + + Route::patch('/settings/account/change-password', 'UserController@changePassword') + ->name('changePassword'); + + Route::patch('/settings/account/change-email', 'UserController@changeEmail') + ->name('changeEmail'); + + Route::post('/settings/account/flush-sessions', 'UserController@flushSessions') + ->name('flushSessions'); });