TRF Certest first commit

This commit is contained in:
2025-02-26 08:57:46 +01:00
commit 3ce064a108
2524 changed files with 475404 additions and 0 deletions
@@ -0,0 +1,77 @@
<?php
namespace Vanguard\Http\Controllers\Api;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Vanguard\Http\Controllers\Controller;
abstract class ApiController extends Controller
{
protected int $statusCode = Response::HTTP_OK;
public function getStatusCode(): int
{
return $this->statusCode;
}
public function setStatusCode($statusCode): self
{
$this->statusCode = $statusCode;
return $this;
}
protected function respondWithSuccess($statusCode = Response::HTTP_OK): JsonResponse
{
return $this->setStatusCode($statusCode)
->respondWithArray(['success' => true]);
}
protected function respondWithArray(array $array, array $headers = []): JsonResponse
{
$response = \Response::json($array, $this->statusCode, $headers);
$response->header('Content-Type', 'application/json');
return $response;
}
protected function respondWithError($message): JsonResponse
{
if ($this->statusCode === Response::HTTP_OK) {
trigger_error(
'You better have a really good reason for erroring on a 200...',
E_USER_WARNING
);
}
return $this->respondWithArray([
'message' => $message,
]);
}
public function errorForbidden(string $message = 'Forbidden'): JsonResponse
{
return $this->setStatusCode(Response::HTTP_FORBIDDEN)
->respondWithError($message);
}
public function errorInternalError(string $message = 'Internal Error'): JsonResponse
{
return $this->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR)
->respondWithError($message);
}
public function errorNotFound(string $message = 'Resource Not Found'): JsonResponse
{
return $this->setStatusCode(Response::HTTP_NOT_FOUND)
->respondWithError($message);
}
public function errorUnauthorized(string $message = 'Unauthorized'): JsonResponse
{
return $this->setStatusCode(Response::HTTP_UNAUTHORIZED)
->respondWithError($message);
}
}
@@ -0,0 +1,77 @@
<?php
namespace Vanguard\Http\Controllers\Api\Auth;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
use Vanguard\Events\User\LoggedIn;
use Vanguard\Events\User\LoggedOut;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Requests\Auth\ApiLoginRequest;
use Vanguard\User;
class AuthController extends ApiController
{
public function __construct()
{
$this->middleware('guest')->only('login');
$this->middleware('auth')->only('logout');
}
/**
* Attempt to log the user in and generate unique JWT token on successful authentication.
*
* @throws BindingResolutionException
* @throws ValidationException
*/
public function token(ApiLoginRequest $request): JsonResponse
{
$user = $this->findUser($request);
if ($user->isBanned()) {
return $this->errorUnauthorized(trans('auth.banned'));
}
Auth::setUser($user);
event(new LoggedIn);
return $this->respondWithArray([
'token' => $user->createToken($request->device_name)->plainTextToken,
]);
}
/**
* Find the user instance from the API request.
*
* @throws BindingResolutionException
* @throws ValidationException
*/
private function findUser(ApiLoginRequest $request): ?User
{
$user = User::where($request->getCredentials())->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'username' => [trans('auth.failed')],
]);
}
return $user;
}
/**
* Logout user and invalidate token.
*/
public function logout(): JsonResponse
{
event(new LoggedOut);
auth()->user()->currentAccessToken()->delete();
return $this->respondWithSuccess();
}
}
@@ -0,0 +1,30 @@
<?php
namespace Vanguard\Http\Controllers\Api\Auth\Password;
use Illuminate\Http\JsonResponse;
use Password;
use Vanguard\Events\User\RequestedPasswordResetEmail;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Requests\Auth\PasswordRemindRequest;
use Vanguard\Mail\ResetPassword;
use Vanguard\Repositories\User\UserRepository;
class RemindController extends ApiController
{
/**
* Send a reset link to the given user.
*/
public function index(PasswordRemindRequest $request, UserRepository $users): JsonResponse
{
$user = $users->findByEmail($request->email);
$token = Password::getRepository()->create($user);
\Mail::to($user)->send(new ResetPassword($token));
event(new RequestedPasswordResetEmail($user));
return $this->respondWithSuccess();
}
}
@@ -0,0 +1,39 @@
<?php
namespace Vanguard\Http\Controllers\Api\Auth\Password;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\JsonResponse;
use Password;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Requests\Auth\PasswordResetRequest;
class ResetController extends ApiController
{
/**
* Reset the given user's password.
*/
public function index(PasswordResetRequest $request): JsonResponse
{
$response = Password::reset($request->credentials(), function ($user, $password) {
$this->resetPassword($user, $password);
});
return match ($response) {
Password::PASSWORD_RESET, Password::INVALID_USER => $this->respondWithSuccess(),
default => $this->setStatusCode(400)
->respondWithError(trans($response)),
};
}
/**
* Reset the given user's password.
*/
protected function resetPassword(\Illuminate\Contracts\Auth\CanResetPassword $user, string $password): void
{
$user->password = $password;
$user->save();
event(new PasswordReset($user));
}
}
@@ -0,0 +1,57 @@
<?php
namespace Vanguard\Http\Controllers\Api\Auth;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\JsonResponse;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Requests\Auth\RegisterRequest;
use Vanguard\Repositories\Role\RoleRepository;
use Vanguard\Repositories\User\UserRepository;
use Vanguard\Role;
use Vanguard\Support\Enum\UserStatus;
class RegistrationController extends ApiController
{
public function __construct(private readonly UserRepository $users, private readonly RoleRepository $roles)
{
}
public function index(RegisterRequest $request): JsonResponse
{
$role = $this->roles->findByName(Role::DEFAULT_USER_ROLE);
$user = $this->users->create(
array_merge($request->validFormData(), ['role_id' => $role->id])
);
event(new Registered($user));
return $this->setStatusCode(201)
->respondWithArray([
'requires_email_confirmation' => (bool) setting('reg_email_confirmation'),
]);
}
/**
* Verify email via email confirmation token.
*/
public function verifyEmail($token): JsonResponse
{
if (! setting('reg_email_confirmation')) {
return $this->errorNotFound();
}
if ($user = $this->users->findByConfirmationToken($token)) {
$this->users->update($user->id, [
'status' => UserStatus::ACTIVE,
'confirmation_token' => null,
]);
return $this->respondWithSuccess();
}
return $this->setStatusCode(400)
->respondWithError('Invalid confirmation token.');
}
}
@@ -0,0 +1,54 @@
<?php
namespace Vanguard\Http\Controllers\Api\Auth;
use Auth;
use Exception;
use Illuminate\Http\JsonResponse;
use Socialite;
use Vanguard\Events\User\LoggedIn;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Requests\Auth\Social\ApiAuthenticateRequest;
use Vanguard\Repositories\User\UserRepository;
use Vanguard\Services\Auth\Social\SocialManager;
class SocialLoginController extends ApiController
{
public function __construct(private readonly UserRepository $users, private readonly SocialManager $socialManager)
{
}
public function index(ApiAuthenticateRequest $request): JsonResponse
{
try {
$socialUser = Socialite::driver($request->network)->userFromToken($request->social_token);
} catch (Exception $e) {
return $this->errorInternalError('Could not connect to specified social network.');
}
$user = $this->users->findBySocialId(
$request->network,
$socialUser->getId()
);
if (! $user) {
if (! setting('reg_enabled')) {
return $this->errorForbidden('Only users who already created an account can log in.');
}
$user = $this->socialManager->associate($socialUser, $request->network);
}
if ($user->isBanned()) {
return $this->errorForbidden(__('Your account is banned by administrators.'));
}
Auth::setUser($user);
event(new LoggedIn);
return $this->respondWithArray([
'token' => $user->createToken($request->device_name)->plainTextToken,
]);
}
}
@@ -0,0 +1,90 @@
<?php
namespace Vanguard\Http\Controllers\Api\Auth;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\Events\Verified;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Routing\Exceptions\InvalidSignatureException;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Requests\Auth\ApiVerifyEmailRequest;
class VerificationController extends ApiController
{
public function __construct()
{
$this->middleware('throttle:6,1')->only('resend');
}
/**
* Mark the authenticated user's email address as verified.
*
* @throws AuthorizationException
*/
public function verify(ApiVerifyEmailRequest $request): JsonResponse
{
if (! setting('reg_email_confirmation')) {
return $this->errorNotFound();
}
$this->verifySignature($request);
if ($request->user()->hasVerifiedEmail()) {
return $this->emailAlreadyVerifiedResponse();
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return $this->respondWithSuccess();
}
/**
* Verify request signature.
*
* @throws AuthorizationException
*/
private function verifySignature(ApiVerifyEmailRequest $baseRequest): void
{
$request = Request::create(
route('verification.verify', $baseRequest->only('id', 'hash')),
Request::METHOD_GET,
$baseRequest->only('expires', 'signature')
);
if (! $request->hasValidSignature()) {
throw new InvalidSignatureException;
}
if (! hash_equals((string) $baseRequest->id, (string) auth()->user()->getKey())) {
throw new AuthorizationException;
}
if (! hash_equals((string) $baseRequest->hash, sha1(auth()->user()->getEmailForVerification()))) {
throw new AuthorizationException;
}
}
protected function emailAlreadyVerifiedResponse(): JsonResponse
{
return $this->setStatusCode(Response::HTTP_BAD_REQUEST)
->respondWithError(__('E-Mail already verified.'));
}
/**
* Resend the email verification notification.
*/
public function resend(Request $request): JsonResponse
{
if ($request->user()->hasVerifiedEmail()) {
return $this->emailAlreadyVerifiedResponse();
}
$request->user()->sendEmailVerificationNotification();
return $this->respondWithSuccess(Response::HTTP_ACCEPTED);
}
}
@@ -0,0 +1,69 @@
<?php
namespace Vanguard\Http\Controllers\Api\Authorization;
use Spatie\QueryBuilder\AllowedFilter;
use Spatie\QueryBuilder\QueryBuilder;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Requests\Permission\CreatePermissionRequest;
use Vanguard\Http\Requests\Permission\RemovePermissionRequest;
use Vanguard\Http\Requests\Permission\UpdatePermissionRequest;
use Vanguard\Http\Resources\PermissionResource;
use Vanguard\Permission;
use Vanguard\Repositories\Permission\PermissionRepository;
class PermissionsController extends ApiController
{
public function __construct(private readonly PermissionRepository $permissions)
{
$this->middleware('permission:permissions.manage');
}
public function index(): \Illuminate\Http\Resources\Json\AnonymousResourceCollection
{
$permissions = QueryBuilder::for(Permission::class)
->allowedFilters([
AllowedFilter::partial('name'),
AllowedFilter::partial('display_name'),
AllowedFilter::exact('role', 'role_id'),
])
->allowedSorts(['name', 'created_at'])
->defaultSort('created_at')
->paginate();
return PermissionResource::collection($permissions);
}
public function store(CreatePermissionRequest $request): PermissionResource
{
$permission = $this->permissions->create(
$request->only(['name', 'display_name', 'description'])
);
return new PermissionResource($permission);
}
public function show(Permission $permission): PermissionResource
{
return new PermissionResource($permission);
}
public function update(Permission $permission, UpdatePermissionRequest $request): PermissionResource
{
$input = collect($request->all());
$permission = $this->permissions->update(
$permission->id,
$input->only(['name', 'display_name', 'description'])->toArray()
);
return new PermissionResource($permission);
}
public function destroy(Permission $permission, RemovePermissionRequest $request): \Illuminate\Http\JsonResponse
{
$this->permissions->delete($permission->id);
return $this->respondWithSuccess();
}
}
@@ -0,0 +1,36 @@
<?php
namespace Vanguard\Http\Controllers\Api\Authorization;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Vanguard\Events\Role\PermissionsUpdated;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Requests\Role\UpdateRolePermissionsRequest;
use Vanguard\Http\Resources\PermissionResource;
use Vanguard\Repositories\Role\RoleRepository;
use Vanguard\Role;
class RolePermissionsController extends ApiController
{
public function __construct(private RoleRepository $roles)
{
$this->middleware('permission:permissions.manage');
}
public function show(Role $role): AnonymousResourceCollection
{
return PermissionResource::collection($role->cachedPermissions());
}
public function update(Role $role, UpdateRolePermissionsRequest $request): AnonymousResourceCollection
{
$this->roles->updatePermissions(
roleId: $role->id,
permissions: $request->permissions
);
event(new PermissionsUpdated);
return PermissionResource::collection($role->cachedPermissions());
}
}
@@ -0,0 +1,78 @@
<?php
namespace Vanguard\Http\Controllers\Api\Authorization;
use Cache;
use Illuminate\Http\JsonResponse;
use Spatie\QueryBuilder\QueryBuilder;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Requests\Role\CreateRoleRequest;
use Vanguard\Http\Requests\Role\RemoveRoleRequest;
use Vanguard\Http\Requests\Role\UpdateRoleRequest;
use Vanguard\Http\Resources\RoleResource;
use Vanguard\Repositories\Role\RoleRepository;
use Vanguard\Repositories\User\UserRepository;
use Vanguard\Role;
class RolesController extends ApiController
{
public function __construct(private RoleRepository $roles)
{
$this->middleware('permission:roles.manage');
}
public function index(): \Illuminate\Http\Resources\Json\AnonymousResourceCollection
{
$roles = QueryBuilder::for(Role::class)
->allowedIncludes(RoleResource::allowedIncludes())
->allowedFilters(['name'])
->allowedSorts(['name', 'created_at'])
->defaultSort('created_at')
->paginate();
return RoleResource::collection($roles);
}
public function store(CreateRoleRequest $request): RoleResource
{
$role = $this->roles->create(
$request->only(['name', 'display_name', 'description'])
);
return new RoleResource($role);
}
public function show($id): RoleResource
{
$role = QueryBuilder::for(Role::where('id', $id))
->allowedIncludes(RoleResource::allowedIncludes())
->first();
return new RoleResource($role);
}
public function update(Role $role, UpdateRoleRequest $request): RoleResource
{
$input = collect($request->all());
$role = $this->roles->update(
$role->id,
$input->only(['name', 'display_name', 'description'])->toArray()
);
return new RoleResource($role);
}
public function destroy(Role $role, UserRepository $users, RemoveRoleRequest $request): JsonResponse
{
$userRole = $this->roles->findByName(Role::DEFAULT_USER_ROLE);
$users->switchRolesForUsers($role->id, $userRole->id);
$this->roles->delete($role->id);
Cache::flush();
return $this->respondWithSuccess();
}
}
@@ -0,0 +1,18 @@
<?php
namespace Vanguard\Http\Controllers\Api;
use Vanguard\Http\Resources\CountryResource;
use Vanguard\Repositories\Country\CountryRepository;
class CountriesController extends ApiController
{
public function __construct(private readonly CountryRepository $countries)
{
}
public function index(): \Illuminate\Http\Resources\Json\AnonymousResourceCollection
{
return CountryResource::collection($this->countries->all());
}
}
@@ -0,0 +1,22 @@
<?php
namespace Vanguard\Http\Controllers\Api\Profile;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Requests\User\UpdateProfileLoginDetailsRequest;
use Vanguard\Http\Resources\UserResource;
use Vanguard\Repositories\User\UserRepository;
class AuthDetailsController extends ApiController
{
public function update(UpdateProfileLoginDetailsRequest $request, UserRepository $users): UserResource
{
$user = $request->user();
$data = $request->only(['email', 'username', 'password']);
$user = $users->update($user->id, $data);
return new UserResource($user);
}
}
@@ -0,0 +1,72 @@
<?php
namespace Vanguard\Http\Controllers\Api\Profile;
use Illuminate\Http\Request;
use Vanguard\Events\User\ChangedAvatar;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Requests\User\UploadAvatarRawRequest;
use Vanguard\Http\Resources\UserResource;
use Vanguard\Repositories\User\UserRepository;
use Vanguard\Services\Upload\UserAvatarManager;
class AvatarController extends ApiController
{
public function __construct(
private readonly UserRepository $users,
private readonly UserAvatarManager $avatarManager
) {
}
public function update(UploadAvatarRawRequest $request): UserResource
{
$name = $this->avatarManager->uploadAndCropAvatar(
$request->file('file')
);
$user = $this->users->update(
auth()->id(),
['avatar' => $name]
);
event(new ChangedAvatar);
return new UserResource($user);
}
public function updateExternal(Request $request): UserResource
{
$this->validate($request, [
'url' => 'required|url',
]);
$this->avatarManager->deleteAvatarIfUploaded(
auth()->user()
);
$user = $this->users->update(
auth()->id(),
['avatar' => $request->url]
);
event(new ChangedAvatar);
return new UserResource($user);
}
public function destroy(): UserResource
{
$user = auth()->user();
$this->avatarManager->deleteAvatarIfUploaded($user);
$user = $this->users->update(
$user->id,
['avatar' => null]
);
event(new ChangedAvatar);
return new UserResource($user);
}
}
@@ -0,0 +1,39 @@
<?php
namespace Vanguard\Http\Controllers\Api\Profile;
use Vanguard\Events\User\UpdatedProfileDetails;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Requests\User\UpdateProfileDetailsRequest;
use Vanguard\Http\Resources\UserResource;
use Vanguard\Repositories\User\UserRepository;
class DetailsController extends ApiController
{
public function index(): UserResource
{
return new UserResource(auth()->user());
}
public function update(UpdateProfileDetailsRequest $request, UserRepository $users): UserResource
{
$user = $request->user();
$data = collect($request->all());
$data = $data->only([
'first_name', 'last_name', 'birthday',
'phone', 'address', 'country_id',
])->toArray();
if (! isset($data['country_id'])) {
$data['country_id'] = $user->country_id;
}
$user = $users->update($user->id, $data);
event(new UpdatedProfileDetails);
return new UserResource($user);
}
}
@@ -0,0 +1,23 @@
<?php
namespace Vanguard\Http\Controllers\Api\Profile;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Resources\SessionResource;
use Vanguard\Repositories\Session\SessionRepository;
class SessionsController extends ApiController
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('session.database');
}
public function index(SessionRepository $sessions): \Illuminate\Http\Resources\Json\AnonymousResourceCollection
{
$sessions = $sessions->getUserSessions(auth()->id());
return SessionResource::collection($sessions);
}
}
@@ -0,0 +1,74 @@
<?php
namespace Vanguard\Http\Controllers\Api\Profile;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication;
use Laravel\Fortify\Actions\EnableTwoFactorAuthentication;
use Vanguard\Events\User\TwoFactorDisabled;
use Vanguard\Events\User\TwoFactorEnabled;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Requests\TwoFactor\VerifyTwoFactorTokenRequest;
use Vanguard\Http\Resources\UserResource;
class TwoFactorController extends ApiController
{
public function update(EnableTwoFactorAuthentication $enable)
{
$user = auth()->user();
if ($user->twoFactorEnabled()) {
return $this->setStatusCode(422)
->respondWithError(trans('auth.2fa.already_enabled'));
}
$enable($user);
return $this->respondWithArray([
'message' => trans('auth.2fa.token_sent'),
'qrcode' => $user->twoFactorQrCodeSvg(),
]);
}
/**
* Verify provided 2FA token.
*/
public function verify(VerifyTwoFactorTokenRequest $request, ConfirmTwoFactorAuthentication $confirm): UserResource|\Illuminate\Http\JsonResponse
{
$user = auth()->user();
try {
$confirm($user, $request->input('code'));
} catch (ValidationException $e) {
return $this->setStatusCode(422)
->respondWithError(trans('auth.2fa.invalid_token'));
}
event(new TwoFactorEnabled);
return new UserResource($user);
}
/**
* Disable 2FA for currently authenticated user.
*/
public function destroy(): UserResource|\Illuminate\Http\JsonResponse
{
$user = auth()->user();
if (!$user->twoFactorEnabled()) {
return $this->setStatusCode(422)
->respondWithError(trans('auth.2fa.not_enabled'));
}
$user->forceFill([
'two_factor_secret' => null,
'two_factor_recovery_codes' => null,
'two_factor_confirmed_at' => null,
])->save();
event(new TwoFactorDisabled);
return new UserResource($user);
}
}
@@ -0,0 +1,37 @@
<?php
namespace Vanguard\Http\Controllers\Api;
use Illuminate\Auth\Access\AuthorizationException;
use Vanguard\Http\Resources\SessionResource;
use Vanguard\Repositories\Session\SessionRepository;
class SessionsController extends ApiController
{
public function __construct(private readonly SessionRepository $sessions)
{
$this->middleware('session.database');
}
/**
* @throws AuthorizationException
*/
public function show($session): SessionResource
{
$this->authorize('manage-session', $session);
return new SessionResource($session);
}
/**
* @throws AuthorizationException
*/
public function destroy($session): \Illuminate\Http\JsonResponse
{
$this->authorize('manage-session', $session);
$this->sessions->invalidateSession($session->id);
return $this->respondWithSuccess();
}
}
@@ -0,0 +1,18 @@
<?php
namespace Vanguard\Http\Controllers\Api;
use Setting;
class SettingsController extends ApiController
{
public function __construct()
{
$this->middleware('permission:settings.general');
}
public function index(): \Illuminate\Http\JsonResponse
{
return response()->json(Setting::all());
}
}
@@ -0,0 +1,39 @@
<?php
namespace Vanguard\Http\Controllers\Api;
use Carbon\Carbon;
use Vanguard\Http\Resources\UserResource;
use Vanguard\Repositories\User\UserRepository;
use Vanguard\Support\Enum\UserStatus;
class StatsController extends ApiController
{
public function __construct(private UserRepository $users)
{
$this->middleware('role:Admin');
}
public function index(): \Illuminate\Http\JsonResponse
{
$usersPerMonth = $this->users->countOfNewUsersPerMonthPerRole(
Carbon::now()->subYear()->startOfMonth(),
Carbon::now()->endOfMonth()
);
$usersPerStatus = [
'total' => $this->users->count(),
'new' => $this->users->newUsersCount(),
'banned' => $this->users->countByStatus(UserStatus::BANNED),
'unconfirmed' => $this->users->countByStatus(UserStatus::UNCONFIRMED),
];
$users = UserResource::collection($this->users->latest(7));
return $this->respondWithArray([
'users_per_month' => $usersPerMonth,
'users_per_status' => $usersPerStatus,
'latest_registrations' => $users->resolve(),
]);
}
}
@@ -0,0 +1,60 @@
<?php
namespace Vanguard\Http\Controllers\Api\Users;
use Illuminate\Http\Request;
use Vanguard\Events\User\UpdatedByAdmin;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Requests\User\UploadAvatarRawRequest;
use Vanguard\Http\Resources\UserResource;
use Vanguard\Repositories\User\UserRepository;
use Vanguard\Services\Upload\UserAvatarManager;
use Vanguard\User;
class AvatarController extends ApiController
{
public function __construct(
private readonly UserRepository $users,
private readonly UserAvatarManager $avatarManager
) {
$this->middleware('permission:users.manage');
}
public function update(User $user, UploadAvatarRawRequest $request): UserResource
{
$name = $this->avatarManager->uploadAndCropAvatar($request->file('file'));
$user = $this->users->update($user->id, ['avatar' => $name]);
event(new UpdatedByAdmin($user));
return new UserResource($user);
}
public function updateExternal(User $user, Request $request): UserResource
{
$this->validate($request, ['url' => 'required|url']);
$this->avatarManager->deleteAvatarIfUploaded($user);
$user = $this->users->update($user->id, ['avatar' => $request->url]);
event(new UpdatedByAdmin($user));
return new UserResource($user);
}
/**
* Remove user's avatar and set it to null.
*/
public function destroy(User $user): UserResource
{
$this->avatarManager->deleteAvatarIfUploaded($user);
$user = $this->users->update($user->id, ['avatar' => null]);
event(new UpdatedByAdmin($user));
return new UserResource($user);
}
}
@@ -0,0 +1,25 @@
<?php
namespace Vanguard\Http\Controllers\Api\Users;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Resources\SessionResource;
use Vanguard\Repositories\Session\SessionRepository;
use Vanguard\User;
class SessionsController extends ApiController
{
public function __construct()
{
$this->middleware('permission:users.manage');
$this->middleware('session.database');
}
public function index(User $user, SessionRepository $sessions): AnonymousResourceCollection
{
return SessionResource::collection(
$sessions->getUserSessions($user->id)
);
}
}
@@ -0,0 +1,78 @@
<?php
namespace Vanguard\Http\Controllers\Api\Users;
use Illuminate\Http\JsonResponse;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication;
use Laravel\Fortify\Actions\EnableTwoFactorAuthentication;
use Vanguard\Events\User\TwoFactorDisabledByAdmin;
use Vanguard\Events\User\TwoFactorEnabledByAdmin;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Requests\TwoFactor\VerifyTwoFactorTokenRequest;
use Vanguard\Http\Resources\UserResource;
use Vanguard\User;
class TwoFactorController extends ApiController
{
public function __construct()
{
$this->middleware('permission:users.manage');
}
/**
* Enable 2FA for the specified user.
*/
public function update(User $user, EnableTwoFactorAuthentication $enable): JsonResponse
{
if ($user->twoFactorEnabled()) {
return $this->setStatusCode(422)
->respondWithError(trans('auth.2fa.already_enabled'));
}
$enable($user, false);
return $this->respondWithArray([
'message' => trans('auth.2fa.token_sent'),
'qrcode' => $user->twoFactorQrCodeSvg(),
]);
}
/**
* Verify provided 2FA token.
*/
public function verify(VerifyTwoFactorTokenRequest $request, User $user, ConfirmTwoFactorAuthentication $confirm): UserResource|JsonResponse
{
try {
$confirm($user, $request->input('code'));
} catch (ValidationException $e) {
return $this->setStatusCode(422)
->respondWithError(trans('auth.2fa.invalid_token'));
}
event(new TwoFactorEnabledByAdmin($user));
return new UserResource($user);
}
/**
* Disable 2FA for specified user.
*/
public function destroy(User $user): UserResource|JsonResponse
{
if (!$user->twoFactorEnabled()) {
return $this->setStatusCode(422)
->respondWithError(trans('auth.2fa.not_enabled'));
}
$user->forceFill([
'two_factor_secret' => null,
'two_factor_recovery_codes' => null,
'two_factor_confirmed_at' => null,
])->save();
event(new TwoFactorDisabledByAdmin($user));
return new UserResource($user);
}
}
@@ -0,0 +1,111 @@
<?php
namespace Vanguard\Http\Controllers\Api\Users;
use Illuminate\Http\Request;
use Spatie\QueryBuilder\AllowedFilter;
use Spatie\QueryBuilder\QueryBuilder;
use Vanguard\Events\User\Banned;
use Vanguard\Events\User\Deleted;
use Vanguard\Events\User\UpdatedByAdmin;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\Http\Filters\UserKeywordSearch;
use Vanguard\Http\Requests\User\CreateUserRequest;
use Vanguard\Http\Requests\User\UpdateUserRequest;
use Vanguard\Http\Resources\UserResource;
use Vanguard\Repositories\User\UserRepository;
use Vanguard\Support\Enum\UserStatus;
use Vanguard\User;
class UsersController extends ApiController
{
public function __construct(private UserRepository $users)
{
$this->middleware('permission:users.manage');
}
/**
* Paginate all users.
*/
public function index(Request $request): \Illuminate\Http\Resources\Json\AnonymousResourceCollection
{
$users = QueryBuilder::for(User::class)
->allowedIncludes(UserResource::allowedIncludes())
->allowedFilters([
AllowedFilter::custom('search', new UserKeywordSearch),
AllowedFilter::exact('status'),
])
->allowedSorts(['id', 'first_name', 'last_name', 'email', 'created_at', 'updated_at'])
->defaultSort('id')
->paginate($request->per_page ?: 20);
return UserResource::collection($users);
}
public function store(CreateUserRequest $request): UserResource
{
$data = $request->only([
'email', 'password', 'username', 'first_name', 'last_name',
'phone', 'address', 'country_id', 'birthday', 'role_id',
]);
$data += [
'status' => UserStatus::ACTIVE,
'email_verified_at' => $request->verified ? now() : null,
];
$user = $this->users->create($data);
return new UserResource($user);
}
public function show($id): UserResource
{
$user = QueryBuilder::for(User::where('id', $id))
->allowedIncludes(UserResource::allowedIncludes())
->firstOrFail();
return new UserResource($user);
}
public function update(User $user, UpdateUserRequest $request): UserResource
{
$data = $request->only([
'email', 'password', 'username', 'first_name', 'last_name',
'phone', 'address', 'country_id', 'birthday', 'status', 'role_id',
]);
$user = $this->users->update($user->id, $data);
event(new UpdatedByAdmin($user));
// If user status was updated to "Banned",
// fire the appropriate event.
if ($this->userIsBanned($user, $request)) {
event(new Banned($user));
}
return new UserResource($user);
}
/**
* Check if user is banned during last update.
*/
private function userIsBanned(User $user, Request $request): bool
{
return $user->status != $request->status && $request->status == UserStatus::BANNED;
}
public function destroy(User $user): \Illuminate\Http\JsonResponse
{
if ($user->id == auth()->id()) {
return $this->errorForbidden(__('You cannot delete yourself.'));
}
event(new Deleted($user));
$this->users->delete($user->id);
return $this->respondWithSuccess();
}
}