Made Profile & Account Settings functional

Also moved redundant HTML markup to component file for reuse.
Username to UUID converter also added as Middleware
This commit is contained in:
Miguel Nogueira 2020-05-13 22:47:51 +01:00
parent 7635f8e2f4
commit 2ff0da3e4f
33 changed files with 799 additions and 258 deletions

View File

@ -3,11 +3,13 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Profile;
use App\Providers\RouteServiceProvider; use App\Providers\RouteServiceProvider;
use App\User; use App\User;
use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use function GuzzleHttp\Psr7\str;
class RegisterController extends Controller class RegisterController extends Controller
{ {
@ -50,6 +52,7 @@ class RegisterController extends Controller
protected function validator(array $data) protected function validator(array $data)
{ {
return Validator::make($data, [ return Validator::make($data, [
'uuid' => ['required', 'string', 'unique:users', 'min:32', 'max:32'],
'name' => ['required', 'string', 'max:255'], 'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'], 'password' => ['required', 'string', 'min:8', 'confirmed'],
@ -64,11 +67,24 @@ class RegisterController extends Controller
*/ */
protected function create(array $data) protected function create(array $data)
{ {
return User::create([
$user = User::create([
'uuid' => $data['uuid'],
'name' => $data['name'], 'name' => $data['name'],
'email' => $data['email'], 'email' => $data['email'],
'password' => Hash::make($data['password']), 'password' => Hash::make($data['password']),
'originalIP' => request()->ip() '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;
} }
} }

View File

@ -1,10 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class LogController extends Controller
{
//
}

View File

@ -2,15 +2,81 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Requests\ProfileSave;
use Illuminate\Support\Facades\Log;
use App\Profile;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ProfileController extends Controller class ProfileController extends Controller
{ {
public function showProfile()
public function index()
{ {
return view('dashboard.user.profile.userprofile'); $socialMediaProfiles = json_decode(Auth::user()->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();
} }

View File

@ -2,7 +2,14 @@
namespace App\Http\Controllers; 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\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
class UserController extends Controller class UserController extends Controller
{ {
@ -16,4 +23,76 @@ class UserController extends Controller
{ {
return view('dashboard.administration.players'); 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();
}
} }

View File

@ -62,6 +62,7 @@ class Kernel extends HttpKernel
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::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
]; ];
} }

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Http;
class UsernameUUID
{
/**
* Converts a Minecraft username found in the request body to a UUID
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$input = $request->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);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ChangeEmailRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'currentPassword' => 'required|password',
'newEmail' => 'required|email|unique:users,email'
];
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ChangePasswordRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'newPassword' => 'required|string|confirmed',
'oldPassword' => 'required|string|password'
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class FlushSessionsRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'currentPasswordFlush' => 'required|password'
];
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Http\Requests;
use App\Profile;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class ProfileSave extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @param Profile $profile
* @return bool
*/
public function authorize(Profile $profile)
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'shortBio' => 'nullable|string|max:100',
'aboutMe' => 'nullable|string|max:2000',
'avatarPref' => 'required|string',
'socialInsta' => 'nullable|string',
'socialTwitter' => 'nullable|string',
'socialDiscord' => 'nullable|string',
'socialGithub' => 'nullable|string'
];
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Listeners;
use Illuminate\Support\Facades\Log;
use Illuminate\Auth\Events\Registered;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class OnUserRegistration
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param Registered $event
* @return void
*/
public function handle(Registered $event)
{
// TODO: Send push notification to online admins via browser (w/ pusher)
Log::info('User ' . $event->user->name . ' has just registered for an account.');
}
}

View File

@ -1,10 +0,0 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Log extends Model
{
//
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Policies;
use App\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class ProfilePolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* @return void
*/
public function __construct()
{
//
}
}

View File

@ -6,5 +6,20 @@ use Illuminate\Database\Eloquent\Model;
class Profile extends Model class Profile extends Model
{ {
//
public $fillable = [
'profileShortBio',
'profileAboutMe',
'avatarPreference',
'socialLinks',
'userID'
];
public function user()
{
return $this->belongsTo('App\User', 'userID', 'id');
}
} }

View File

@ -2,6 +2,7 @@
namespace App\Providers; namespace App\Providers;
use App\Listeners\OnUserRegistration;
use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
@ -17,6 +18,7 @@ class EventServiceProvider extends ServiceProvider
protected $listen = [ protected $listen = [
Registered::class => [ Registered::class => [
SendEmailVerificationNotification::class, SendEmailVerificationNotification::class,
OnUserRegistration::class
], ],
]; ];

View File

@ -27,7 +27,7 @@ class MojangStatusProvider extends ServiceProvider
*/ */
public function boot() public function boot()
{ {
// TODO: (IMPORTANT) Switch this to Middleware
if (!Cache::has('mojang_status')) if (!Cache::has('mojang_status'))
{ {
Log::info("Mojang Status Provider: Mojang Status not found in the cache; Sending new request."); Log::info("Mojang Status Provider: Mojang Status not found in the cache; Sending new request.");

View File

@ -41,4 +41,9 @@ class User extends Authenticatable
{ {
return $this->hasMany('App\Application', 'applicantUserID', 'id'); return $this->hasMany('App\Application', 'applicantUserID', 'id');
} }
public function profile()
{
return $this->hasOne('App\Profile', 'userID', 'id');
}
} }

View File

@ -0,0 +1,28 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class GlobalErrors extends Component
{
/**
* Create a new component instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\View\View|string
*/
public function render()
{
return view('components.global-errors');
}
}

View File

@ -208,7 +208,7 @@ return [
*/ */
'menu' => [ 'menu' => [
'My Account', 'Applications',
[ [
'text' => 'My Applications', 'text' => 'My Applications',
'icon' => 'fas fa-fw fa-list-ul', 'icon' => 'fas fa-fw fa-list-ul',
@ -218,7 +218,7 @@ return [
'icon' => 'fas fa-fw fa-check-double', 'icon' => 'fas fa-fw fa-check-double',
'url' => '/applications/current' 'url' => '/applications/current'
] ]
] ],
], ],
'My Profile', 'My Profile',
@ -227,6 +227,11 @@ return [
'url' => '/profile/settings', 'url' => '/profile/settings',
'icon' => 'fas fa-fw fa-cog' 'icon' => 'fas fa-fw fa-cog'
], ],
[
'text' => 'My Account Settings',
'icon' => 'fas fa-user-circle',
'url' => '/profile/settings/account'
],
'Application Management', 'Application Management',
[ [
'text' => 'Outstanding Applications', 'text' => 'Outstanding Applications',

View File

@ -7,7 +7,8 @@ return [
'mojang' => [ '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'
] ]
] ]

View File

@ -15,17 +15,20 @@ class CreateProfilesTable extends Migration
{ {
Schema::create('profiles', function (Blueprint $table) { Schema::create('profiles', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('profileShortBio'); $table->string('profileShortBio')->nullable();
$table->text('profileAboutMe'); $table->text('profileAboutMe')->nullable();
$table->enum('avatarPreference', [ $table->enum('avatarPreference', [
'crafatar', // Mojang Profile 'crafatar', // Mojang Profile
'gravatar' // Email profile 'gravatar' // Email profile
]); ])->default('gravatar');
$table->text('socialLinks'); $table->text('socialLinks')->nullable();
$table->bigInteger('userID')->unsigned(); $table->bigInteger('userID')->unsigned();
$table->timestamps(); $table->timestamps();
$table->foreign('userID')->references('id')->on('users')->onDelete('cascade'); $table->foreign('userID')
->references('id')
->on('users')
->onDelete('cascade');
}); });
} }

View File

@ -19,7 +19,7 @@ class CreateVacanciesTable extends Migration
$table->longText('vacancyDescription'); $table->longText('vacancyDescription');
$table->string('permissionGroupName'); $table->string('permissionGroupName');
$table->string('discordRoleID'); $table->string('discordRoleID');
$table->bigInteger('vacancyFormID')->unsigned(); $table->unsignedBigInteger('vacancyFormID');
$table->integer('vacancyCount')->default(3); $table->integer('vacancyCount')->default(3);
$table->timestamps(); $table->timestamps();

43
public/css/acc.css vendored Normal file
View File

@ -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;
}

View File

@ -61,6 +61,20 @@
</div> </div>
</div> </div>
<div class="form-group row mt-5">
<label for="minecraftUsername" class="col-md-4 col-form-label text-md-right">Minecraft Username</label>
<div class="col-md-6">
<input type="text" id="minecraftUsername" name="uuid" class="form-control" required>
@error('uuid')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row mb-0"> <div class="form-group row mb-0">
<div class="col-md-6 offset-md-4"> <div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">

View File

@ -0,0 +1,23 @@
<!-- I begin to speak only when I am certain what I will say is not better left unsaid - Cato the Younger -->
@if (session()->has('success'))
<script>
toastr.success("{{session('success')}}")
</script>
@elseif(session()->has('error'))
<script>
toastr.error("{{session('error')}}")
</script>
@elseif($errors->any())
@foreach($errors->all() as $error)
<script>
toastr.error('{{$error}}')
</script>
@endforeach
@endif

View File

@ -10,20 +10,7 @@
@section('js') @section('js')
@if (session()->has('success')) <x-global-errors></x-global-errors>
<script>
toastr.success("{{session('success')}}")
</script>
@elseif(session()->has('error'))
<script>
toastr.error("{{session('error')}}")
</script>
@endif
@stop @stop

View File

@ -66,6 +66,8 @@
<!-- /.card-header --> <!-- /.card-header -->
<div class="card-body p-0"> <!-- move to dedi css --> <div class="card-body p-0"> <!-- move to dedi css -->
@if (!$applications->isEmpty())
<table class="table" style="white-space: nowrap"> <table class="table" style="white-space: nowrap">
<thead> <thead>
<tr> <tr>
@ -128,6 +130,14 @@
</tbody> </tbody>
</table> </table>
@else
<div class="alert alert-warning">
<p><i class="fa fa-info-circle"></i> <b>Nothing to show</b></p>
<p>You currently have no applications to display. If you're eligible, you may apply once every month.</p>
</div>
@endif
</div> </div>
<!-- /.card-body --> <!-- /.card-body -->

View File

@ -1,82 +0,0 @@
@extends('adminlte::page')
@section('title', 'Raspberry Network | Applications')
@section('content_header')
<h4>My Account / Approved Applications</h4>
@stop
@section('content')
<div class="row">
<div class="col">
<div class="callout callout-success">
<h5>Info on approved applications</h5>
<p>Your approved applications will appear here. Approved applicants will be promoted and notified automatically by the system.</p>
<p>Moderators will be able to review other applications.</p>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">
<h3 class="card-title">My Denied Applications</h3>
</div>
<!-- /.card-header -->
<div class="card-body p-0"> <!-- move to dedi css -->
<table class="table" style="white-space: nowrap">
<thead>
<tr>
<th style="width: 10px">#</th>
<th>Applicant</th>
<th>Application Date</th>
<th>Approval Date</th>
<th>Status</th>
<th style="width: 40px">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>1.</td>
<td>Jonathan Smith</td>
<td>2020-04-28</td>
<td>2020-04-30</td>
<td><span class="badge bg-success">Approved</span></td>
<td>
<button type="button" class="btn btn-success btn-sm">View</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- /.card-body -->
<div class="card-footer">
<button type="button" class="btn btn-default mr-2">Back</button>
<button type="button" class="btn btn-info mr-2" onclick="window.location.href='{{route('userDeniedApps')}}'">Denied Applications</button>
<button type="button" class="btn btn-info mr-2" onclick="window.location.href='{{route('userPendingApps')}}'">Active Applications</button>
</div>
</div>
</div>
</div>
@stop

View File

@ -1,83 +0,0 @@
@extends('adminlte::page')
@section('title', 'Raspberry Network | Applications')
@section('content_header')
<h4>My Account / Denied Applications</h4>
@stop
@section('content')
<div class="row">
<div class="col">
<div class="callout callout-danger">
<h5>Info on denied applications</h5>
<p>Please note that all applications listed on this page have been denied by the staff team / applications team.</p>
<p>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.</p>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">
<h3 class="card-title">My Denied Applications</h3>
</div>
<!-- /.card-header -->
<div class="card-body p-0"> <!-- move to dedi css -->
<table class="table" style="white-space: nowrap">
<thead>
<tr>
<th style="width: 10px">#</th>
<th>Applicant</th>
<th>Application Date</th>
<th>Denial Date</th>
<th>Status</th>
<th style="width: 40px">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>1.</td>
<td>Jonathan Smith</td>
<td>2020-04-28</td>
<td>2020-04-30</td>
<td><span class="badge bg-danger">Denied</span></td>
<td>
<button type="button" class="btn btn-success btn-sm">View</button>
<button type="button" class="btn btn-danger btn-sm">Delete</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- /.card-body -->
<div class="card-footer">
<button type="button" class="btn btn-default mr-2">Back</button>
<button type="button" class="btn btn-info mr-2">Approved Applications</button>
<button type="button" class="btn btn-info mr-2">Active Applications</button>
</div>
</div>
</div>
</div>
@stop

View File

@ -0,0 +1,166 @@
@extends('adminlte::page')
@section('title', 'Raspberry Network | Account Settings')
@section('content_header')
<h4>My Profile / Account / Settings</h4>
@stop
@section('js')
<x-global-errors></x-global-errors>
@stop
@section('css')
<link rel="stylesheet" href="/css/acc.css">
@stop
@section('content')
<div class="modal fade" tabindex="-1" id="authenticationForm" role="dialog" aria-labelledby="authenticationFormLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="authenticationFormLabel">Please authenticate</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p class="text-muted">For your security, you'll need to re-enter your password before logging out other devices. If you believe your account has been compromised, please change your password instead, as that will automatically log out anyone else who might using your account, and prevent them from signing back in.</p>
<form method="POST" action="{{route('flushSessions')}}" id="flushSessions">
@csrf
<label for="reenter">Re-enter your password</label>
<input type="password" name="currentPasswordFlush" id="currentPasswordFlush" class="form-control" autocomplete="current-password">
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" onclick="document.getElementById('flushSessions').submit()">Confirm</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col text-center">
<div class="card">
<div class="card-body">
<h3>Welcome back, {{Auth::user()->name}}</h3>
<p class="text-muted">{{Auth::user()->email}}</p>
<a href="https://namemc.com/profile/{{Auth::user()->uuid}}" target="_blank">View @ NameMC</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="card mt-3 tab-card">
<div class="card-header tab-card-header">
<ul class="nav nav-tabs card-header-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link" id="accountSecurityTab" data-toggle="tab" href="#accountSecurity" role="tab" aria-controls="AccountSecurity" aria-selected="true">Account Security</a>
</li>
<li class="nav-item">
<a class="nav-link" id="twofaTab" data-toggle="tab" href="#twofa" role="tab" aria-controls="TwoFa" aria-selected="false">Two Factor Authentication</a>
</li>
<li class="nav-item">
<a class="nav-link" id="sessionsTab" data-toggle="tab" href="#sessions" role="tab" aria-controls="Sessions" aria-selected="false">Sessions</a>
</li>
<li class="nav-item">
<a class="nav-link" id="contactSettingsTab" data-toggle="tab" href="#contactSettings" role="tab" aria-controls="ContactSettings" aria-selected="false">Contact Settings (E-mail)</a>
</li>
</ul>
</div>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active p-3" id="accountSecurity" role="tabpanel" aria-labelledby="accountSecurityTab">
<h5 class="card-title">Change Password</h5>
<p class="card-text">Change your password here. This will log you out from all existing sessions for your security.</p>
<form method="POST" action="{{route('changePassword')}}" id="changePassword">
@csrf
@method('PATCH')
<label for="oldpassword">Old Password</label>
<input class="form-control" name="oldPassword" type="password" id="oldpassword" autocomplete="current-password">
<p class="text-sm text-muted">Forgot your password? Reset it <a href="/auth/password/reset">here</a></p>
<div class="form-group mt-5">
<label for="newpassword">New Password</label>
<input type="password" name="newPassword" id="newpassword" class="form-control" autocomplete="new-password">
<label for="newpassword_confirmation">Confirm Password</label>
<input type="password" name="newPassword_confirmation" id="newpassword_confirmation" autocomplete="new-password" class="form-control">
</div>
</form>
<button class="btn btn-success" type="button" onclick="document.getElementById('changePassword').submit()">Change Password</button>
</div>
<div class="tab-pane fade p-3" id="twofa" role="tabpanel" aria-labelledby="twofaTab">
<h5 class="card-title">Two-factor Authentication</h5>
<p class="card-text"><b>This feature is not yet available.</b> Support for Google Authenticator, Authy, Microsoft Authenticator and other compatible apps is coming soon, as well as fingerprint login for android devices.</p>
<button type="button" class="btn btn-primary" disabled>Enable 2FA</button>
</div>
<div class="tab-pane fade p-3" id="sessions" role="tabpanel" aria-labelledby="sessionsTab">
<h5 class="card-title">Session Manager</h5>
<p class="card-text">Terminating other sessions is generally a good idea if your account has been compromised.</p>
<p>Your current session: Logged in from {{ $ip }}</p>
<button type="button" class="btn btn-warning" onclick="$('#authenticationForm').modal('show')">Flush Sessions</button>
</div>
<div class="tab-pane fade p-3" id="contactSettings" role="tabpanel" aria-labelledby="contactSettingsTab">
<h5 class="card-title">Contact Settings</h5>
<p class="card-text">Need to change personal data? You can do so here.</p>
<form method="POST" action="{{route('changeEmail')}}" id="changeEmail">
@csrf
@method('PATCH')
<div class="form-group">
<label for="oldEmail">Current Email Address</label>
<input type="text" class="form-control" id="oldEmail" disabled value="{{Auth::user()->email}}">
<label for="newEmail">New Email Address</label>
<input type="email" name="newEmail" class="form-control mb-3" id="newEmail">
</div>
<div class="form-group mt-5">
<label for="currentPassword">Current Password</label>
<input type="password" name="currentPassword" class="form-control" id="currentPassword" autocomplete="current-password">
<p class="text-sm text-muted">For security reasons, you cannot make important account changes without confirming your password. You'll also need to verify your new email.</p>
</div>
</form>
<button class="btn btn-success" type="button" onclick="document.getElementById('changeEmail').submit()">Change Email Address</button>
</div>
</div>
</div>
</div>
</div>
</div>
@stop

File diff suppressed because one or more lines are too long

View File

@ -12,9 +12,16 @@ use Illuminate\Support\Facades\Route;
| contains the "web" middleware group. Now create something great! | contains the "web" middleware group. Now create something great!
| |
*/ */
Route::group(['prefix' => 'auth', 'middleware' => ['usernameUUID']], function (){
Auth::routes(); Auth::routes();
Route::get('/','HomeController@index')->middleware('eligibility'); });
Route::get('/','HomeController@index')
->middleware('eligibility');
Route::post('/form/contact', 'ContactController@create') Route::post('/form/contact', 'ContactController@create')
->name('sendSubmission'); ->name('sendSubmission');
@ -56,7 +63,26 @@ Route::group(['middleware' => 'auth'], function(){
Route::group(['prefix' => '/profile'], 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');
}); });