mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-11-22 10:54:07 +08:00
Users: Built out auth page for my-account section
This commit is contained in:
parent
a9d0f36766
commit
a868012048
|
@ -214,6 +214,7 @@ class SocialAuthService
|
|||
|
||||
/**
|
||||
* Gets the names of the active social drivers.
|
||||
* @returns array<string, string>
|
||||
*/
|
||||
public function getActiveDrivers(): array
|
||||
{
|
||||
|
|
|
@ -2,18 +2,25 @@
|
|||
|
||||
namespace BookStack\Users\Controllers;
|
||||
|
||||
use BookStack\Access\SocialAuthService;
|
||||
use BookStack\Http\Controller;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
use BookStack\Settings\UserNotificationPreferences;
|
||||
use BookStack\Settings\UserShortcutMap;
|
||||
use BookStack\Users\UserRepo;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class UserAccountController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected UserRepo $userRepo
|
||||
protected UserRepo $userRepo,
|
||||
) {
|
||||
$this->middleware(function (Request $request, Closure $next) {
|
||||
$this->preventGuestAccess();
|
||||
return $next($request);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -21,8 +28,7 @@ class UserAccountController extends Controller
|
|||
*/
|
||||
public function index()
|
||||
{
|
||||
$guest = user()->isGuest();
|
||||
$mfaMethods = $guest ? [] : user()->mfaValues->groupBy('method');
|
||||
$mfaMethods = user()->mfaValues->groupBy('method');
|
||||
|
||||
return view('users.account.index', [
|
||||
'mfaMethods' => $mfaMethods,
|
||||
|
@ -40,6 +46,7 @@ class UserAccountController extends Controller
|
|||
$this->setPageTitle(trans('preferences.shortcuts_interface'));
|
||||
|
||||
return view('users.account.shortcuts', [
|
||||
'category' => 'shortcuts',
|
||||
'shortcuts' => $shortcuts,
|
||||
'enabled' => $enabled,
|
||||
]);
|
||||
|
@ -68,7 +75,6 @@ class UserAccountController extends Controller
|
|||
public function showNotifications(PermissionApplicator $permissions)
|
||||
{
|
||||
$this->checkPermission('receive-notifications');
|
||||
$this->preventGuestAccess();
|
||||
|
||||
$preferences = (new UserNotificationPreferences(user()));
|
||||
|
||||
|
@ -79,6 +85,7 @@ class UserAccountController extends Controller
|
|||
|
||||
$this->setPageTitle(trans('preferences.notifications'));
|
||||
return view('users.account.notifications', [
|
||||
'category' => 'notifications',
|
||||
'preferences' => $preferences,
|
||||
'watches' => $watches,
|
||||
]);
|
||||
|
@ -90,7 +97,6 @@ class UserAccountController extends Controller
|
|||
public function updateNotifications(Request $request)
|
||||
{
|
||||
$this->checkPermission('receive-notifications');
|
||||
$this->preventGuestAccess();
|
||||
$data = $this->validate($request, [
|
||||
'preferences' => ['required', 'array'],
|
||||
'preferences.*' => ['required', 'string'],
|
||||
|
@ -102,4 +108,42 @@ class UserAccountController extends Controller
|
|||
|
||||
return redirect('/my-account/notifications');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the view for the "Access & Security" account options.
|
||||
*/
|
||||
public function showAuth(SocialAuthService $socialAuthService)
|
||||
{
|
||||
$mfaMethods = user()->mfaValues->groupBy('method');
|
||||
|
||||
$this->setPageTitle(trans('preferences.auth'));
|
||||
|
||||
return view('users.account.auth', [
|
||||
'category' => 'auth',
|
||||
'mfaMethods' => $mfaMethods,
|
||||
'authMethod' => config('auth.method'),
|
||||
'activeSocialDrivers' => $socialAuthService->getActiveDrivers(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the submission for the auth change password form.
|
||||
*/
|
||||
public function updatePassword(Request $request)
|
||||
{
|
||||
if (config('auth.method') !== 'standard') {
|
||||
$this->showPermissionError();
|
||||
}
|
||||
|
||||
$validated = $this->validate($request, [
|
||||
'password' => ['required_with:password_confirm', Password::default()],
|
||||
'password-confirm' => ['same:password', 'required_with:password'],
|
||||
]);
|
||||
|
||||
$this->userRepo->update(user(), $validated, false);
|
||||
|
||||
$this->showSuccessNotification(trans('preferences.auth_change_password_success'));
|
||||
|
||||
return redirect('/my-account/auth');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,5 +29,11 @@ return [
|
|||
'notifications_watched' => 'Watched & Ignored Items',
|
||||
'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.',
|
||||
|
||||
'auth' => 'Access & Security',
|
||||
'auth_change_password' => 'Change Password',
|
||||
'auth_change_password_desc' => 'Change the password you use to log-in to the application. This must be at least 8 characters long.',
|
||||
'auth_change_password_success' => 'Password has been updated!',
|
||||
|
||||
'profile' => 'Profile Details',
|
||||
'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.',
|
||||
];
|
||||
|
|
|
@ -194,7 +194,7 @@ return [
|
|||
'users_send_invite_option' => 'Send user invite email',
|
||||
'users_external_auth_id' => 'External Authentication ID',
|
||||
'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your external authentication system.',
|
||||
'users_password_warning' => 'Only fill the below if you would like to change your password.',
|
||||
'users_password_warning' => 'Only fill the below if you would like to change the password for this user.',
|
||||
'users_system_public' => 'This user represents any guest users that visit your instance. It cannot be used to log in but is assigned automatically.',
|
||||
'users_delete' => 'Delete User',
|
||||
'users_delete_named' => 'Delete user :userName',
|
||||
|
@ -210,12 +210,14 @@ return [
|
|||
'users_preferred_language' => 'Preferred Language',
|
||||
'users_preferred_language_desc' => 'This option will change the language used for the user-interface of the application. This will not affect any user-created content.',
|
||||
'users_social_accounts' => 'Social Accounts',
|
||||
'users_social_accounts_desc' => 'View the status of the connected social accounts for this user. Social accounts can be used in addition to the primary authentication system for system access.',
|
||||
'users_social_accounts_info' => 'Here you can connect your other accounts for quicker and easier login. Disconnecting an account here does not revoke previously authorized access. Revoke access from your profile settings on the connected social account.',
|
||||
'users_social_connect' => 'Connect Account',
|
||||
'users_social_disconnect' => 'Disconnect Account',
|
||||
'users_social_connected' => ':socialAccount account was successfully attached to your profile.',
|
||||
'users_social_disconnected' => ':socialAccount account was successfully disconnected from your profile.',
|
||||
'users_api_tokens' => 'API Tokens',
|
||||
'users_api_tokens_desc' => 'Create and manage the access tokens used to authenticate with the BookStack REST API. Permissions for the API are managed via the user that the token belongs to.',
|
||||
'users_api_tokens_none' => 'No API tokens have been created for this user',
|
||||
'users_api_tokens_create' => 'Create Token',
|
||||
'users_api_tokens_expires' => 'Expires',
|
||||
|
|
1
resources/icons/security.svg
Normal file
1
resources/icons/security.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/></svg>
|
After Width: | Height: | Size: 303 B |
|
@ -12,7 +12,7 @@
|
|||
<nav class="active-link-list in-sidebar">
|
||||
<a href="{{ url('/settings/features') }}" class="{{ $category === 'features' ? 'active' : '' }}">@icon('star') {{ trans('settings.app_features_security') }}</a>
|
||||
<a href="{{ url('/settings/customization') }}" class="{{ $category === 'customization' ? 'active' : '' }}">@icon('palette') {{ trans('settings.app_customization') }}</a>
|
||||
<a href="{{ url('/settings/registration') }}" class="{{ $category === 'registration' ? 'active' : '' }}">@icon('lock') {{ trans('settings.reg_settings') }}</a>
|
||||
<a href="{{ url('/settings/registration') }}" class="{{ $category === 'registration' ? 'active' : '' }}">@icon('security') {{ trans('settings.reg_settings') }}</a>
|
||||
</nav>
|
||||
|
||||
<h5 class="mt-xl">{{ trans('settings.system_version') }}</h5>
|
||||
|
|
87
resources/views/users/account/auth.blade.php
Normal file
87
resources/views/users/account/auth.blade.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
@extends('users.account.layout')
|
||||
|
||||
@section('main')
|
||||
|
||||
@if($authMethod === 'standard')
|
||||
<section class="card content-wrap auto-height">
|
||||
<form action="{{ url('/my-account/auth/password') }}" method="post">
|
||||
{{ method_field('put') }}
|
||||
{{ csrf_field() }}
|
||||
|
||||
<h2 class="list-heading">{{ trans('preferences.auth_change_password') }}</h2>
|
||||
|
||||
<p class="text-muted text-small">
|
||||
{{ trans('preferences.auth_change_password_desc') }}
|
||||
</p>
|
||||
|
||||
<div class="grid half mt-m gap-xl wrap stretch-inputs mb-m">
|
||||
<div>
|
||||
<label for="password">{{ trans('auth.password') }}</label>
|
||||
@include('form.password', ['name' => 'password', 'autocomplete' => 'new-password'])
|
||||
</div>
|
||||
<div>
|
||||
<label for="password-confirm">{{ trans('auth.password_confirm') }}</label>
|
||||
@include('form.password', ['name' => 'password-confirm'])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group text-right">
|
||||
<button class="button">{{ trans('common.update') }}</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</section>
|
||||
@endif
|
||||
|
||||
<section class="card content-wrap auto-height items-center flex-container-row gap-m gap-x-l wrap justify-space-between">
|
||||
<div class="flex-min-width-m">
|
||||
<h2 class="list-heading">{{ trans('settings.users_mfa') }}</h2>
|
||||
<p class="text-muted text-small">{{ trans('settings.users_mfa_desc') }}</p>
|
||||
<p class="text-muted">
|
||||
@if ($mfaMethods->count() > 0)
|
||||
<span class="text-pos">@icon('check-circle')</span>
|
||||
@else
|
||||
<span class="text-neg">@icon('cancel')</span>
|
||||
@endif
|
||||
{{ trans_choice('settings.users_mfa_x_methods', $mfaMethods->count()) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<a href="{{ url('/mfa/setup') }}"
|
||||
class="button outline">{{ trans('common.manage') }}</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@if(count($activeSocialDrivers) > 0)
|
||||
<section id="social-accounts" class="card content-wrap auto-height">
|
||||
<h2 class="list-heading">{{ trans('settings.users_social_accounts') }}</h2>
|
||||
<p class="text-muted text-small">{{ trans('settings.users_social_accounts_info') }}</p>
|
||||
<div class="container">
|
||||
<div class="grid third">
|
||||
@foreach($activeSocialDrivers as $driver => $enabled)
|
||||
<div class="text-center mb-m">
|
||||
<div role="presentation">@icon('auth/'. $driver, ['style' => 'width: 56px;height: 56px;'])</div>
|
||||
<div>
|
||||
@if(user()->hasSocialAccount($driver))
|
||||
<form action="{{ url("/login/service/{$driver}/detach") }}" method="POST">
|
||||
{{ csrf_field() }}
|
||||
<button aria-label="{{ trans('settings.users_social_disconnect') }} - {{ $driver }}"
|
||||
class="button small outline">{{ trans('settings.users_social_disconnect') }}</button>
|
||||
</form>
|
||||
@else
|
||||
<a href="{{ url("/login/service/{$driver}") }}"
|
||||
aria-label="{{ trans('settings.users_social_connect') }} - {{ $driver }}"
|
||||
class="button small outline">{{ trans('settings.users_social_connect') }}</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
|
||||
@if(userCan('access-api'))
|
||||
@include('users.api-tokens.parts.list', ['user' => user()])
|
||||
@endif
|
||||
@stop
|
|
@ -9,9 +9,10 @@
|
|||
<div class="sticky-top-m">
|
||||
<h5>{{ trans('preferences.my_account') }}</h5>
|
||||
<nav class="active-link-list in-sidebar">
|
||||
<a href="{{ url('/my-account/shortcuts') }}" class="{{ 'shortcuts' === 'shortcuts' ? 'active' : '' }}">@icon('shortcuts') {{ trans('preferences.shortcuts_interface') }}</a>
|
||||
<a href="{{ url('/my-account/notifications') }}" class="{{ '' === 'notifications' ? 'active' : '' }}">@icon('notifications') {{ trans('preferences.notifications') }}</a>
|
||||
<a href="{{ url('/my-account/auth') }}" class="{{ '' === 'auth' ? 'active' : '' }}">@icon('lock') {{ 'Access & Security' }}</a>
|
||||
<a href="{{ url('/my-account/profile') }}" class="{{ $category === 'profile' ? 'active' : '' }}">@icon('user') {{ trans('preferences.profile') }}</a>
|
||||
<a href="{{ url('/my-account/auth') }}" class="{{ $category === 'auth' ? 'active' : '' }}">@icon('security') {{ trans('preferences.auth') }}</a>
|
||||
<a href="{{ url('/my-account/shortcuts') }}" class="{{ $category === 'shortcuts' ? 'active' : '' }}">@icon('shortcuts') {{ trans('preferences.shortcuts_interface') }}</a>
|
||||
<a href="{{ url('/my-account/notifications') }}" class="{{ $category === 'notifications' ? 'active' : '' }}">@icon('notifications') {{ trans('preferences.notifications') }}</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-small text-muted">{{ trans('settings.users_api_tokens_desc') }}</p>
|
||||
@if (count($user->apiTokens) > 0)
|
||||
<div class="item-list my-m">
|
||||
@foreach($user->apiTokens as $token)
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
|
||||
<section class="card content-wrap auto-height">
|
||||
<h2 class="list-heading">{{ trans('settings.users_mfa') }}</h2>
|
||||
<p>{{ trans('settings.users_mfa_desc') }}</p>
|
||||
<p class="text-small">{{ trans('settings.users_mfa_desc') }}</p>
|
||||
<div class="grid half gap-xl v-center pb-s">
|
||||
<div>
|
||||
@if ($mfaMethods->count() > 0)
|
||||
|
@ -71,28 +71,28 @@
|
|||
|
||||
</section>
|
||||
|
||||
@if(user()->id === $user->id && count($activeSocialDrivers) > 0)
|
||||
@if(count($activeSocialDrivers) > 0)
|
||||
<section class="card content-wrap auto-height">
|
||||
<h2 class="list-heading">{{ trans('settings.users_social_accounts') }}</h2>
|
||||
<p class="text-muted">{{ trans('settings.users_social_accounts_info') }}</p>
|
||||
<div class="flex-container-row items-center justify-space-between wrap">
|
||||
<h2 class="list-heading">{{ trans('settings.users_social_accounts') }}</h2>
|
||||
<div>
|
||||
@if(user()->id === $user->id)
|
||||
<a class="button outline" href="{{ url('/my-account/auth#social-accounts') }}">{{ trans('common.manage') }}</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted text-small">{{ trans('settings.users_social_accounts_desc') }}</p>
|
||||
<div class="container">
|
||||
<div class="grid third">
|
||||
@foreach($activeSocialDrivers as $driver => $enabled)
|
||||
@foreach($activeSocialDrivers as $driver => $driverName)
|
||||
<div class="text-center mb-m">
|
||||
<div role="presentation">@icon('auth/'. $driver, ['style' => 'width: 56px;height: 56px;'])</div>
|
||||
<div>
|
||||
@if($user->hasSocialAccount($driver))
|
||||
<form action="{{ url("/login/service/{$driver}/detach") }}" method="POST">
|
||||
{{ csrf_field() }}
|
||||
<button aria-label="{{ trans('settings.users_social_disconnect') }} - {{ $driver }}"
|
||||
class="button small outline">{{ trans('settings.users_social_disconnect') }}</button>
|
||||
</form>
|
||||
@else
|
||||
<a href="{{ url("/login/service/{$driver}") }}"
|
||||
aria-label="{{ trans('settings.users_social_connect') }} - {{ $driver }}"
|
||||
class="button small outline">{{ trans('settings.users_social_connect') }}</a>
|
||||
@endif
|
||||
</div>
|
||||
<p class="my-none bold">{{ $driverName }}</p>
|
||||
@if($user->hasSocialAccount($driver))
|
||||
<p class="text-pos bold text-small my-none">Connected</p>
|
||||
@else
|
||||
<p class="text-neg bold text-small my-none">Disconnected</p>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
@endif
|
||||
|
||||
<div refs="new-user-password@input-container" @if(!isset($model)) style="display: none;" @endif>
|
||||
<p class="small">{{ trans('settings.users_password_desc') }}</p>
|
||||
<p class="small mb-none">{{ trans('settings.users_password_desc') }}</p>
|
||||
@if(isset($model))
|
||||
<p class="small">
|
||||
{{ trans('settings.users_password_warning') }}
|
||||
|
|
|
@ -238,6 +238,8 @@ Route::middleware('auth')->group(function () {
|
|||
Route::put('/my-account/shortcuts', [UserControllers\UserAccountController::class, 'updateShortcuts']);
|
||||
Route::get('/my-account/notifications', [UserControllers\UserAccountController::class, 'showNotifications']);
|
||||
Route::put('/my-account/notifications', [UserControllers\UserAccountController::class, 'updateNotifications']);
|
||||
Route::get('/my-account/auth', [UserControllers\UserAccountController::class, 'showAuth']);
|
||||
Route::put('/my-account/auth/password', [UserControllers\UserAccountController::class, 'updatePassword']);
|
||||
Route::patch('/preferences/change-view/{type}', [UserControllers\UserPreferencesController::class, 'changeView']);
|
||||
Route::patch('/preferences/change-sort/{type}', [UserControllers\UserPreferencesController::class, 'changeSort']);
|
||||
Route::patch('/preferences/change-expansion/{type}', [UserControllers\UserPreferencesController::class, 'changeExpansion']);
|
||||
|
|
Loading…
Reference in New Issue
Block a user