mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-21 19:47:48 +00:00
Add ability to invite users
This commit is contained in:
49
app/Events/Admin/InvitationCreated.php
Normal file
49
app/Events/Admin/InvitationCreated.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* InvitationCreated.php
|
||||||
|
* Copyright (c) 2022 james@firefly-iii.org
|
||||||
|
*
|
||||||
|
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FireflyIII\Events\Admin;
|
||||||
|
|
||||||
|
use FireflyIII\Events\Event;
|
||||||
|
use FireflyIII\Models\InvitedUser;
|
||||||
|
use FireflyIII\Models\TransactionGroup;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class InvitationCreated
|
||||||
|
*/
|
||||||
|
class InvitationCreated extends Event
|
||||||
|
{
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public InvitedUser $invitee;
|
||||||
|
|
||||||
|
public TransactionGroup $transactionGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*
|
||||||
|
* @param InvitedUser $invitee
|
||||||
|
*/
|
||||||
|
public function __construct(InvitedUser $invitee)
|
||||||
|
{
|
||||||
|
$this->invitee = $invitee;
|
||||||
|
}
|
||||||
|
}
|
@@ -22,9 +22,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Handlers\Events;
|
namespace FireflyIII\Handlers\Events;
|
||||||
|
|
||||||
|
use FireflyIII\Events\Admin\InvitationCreated;
|
||||||
use FireflyIII\Events\AdminRequestedTestMessage;
|
use FireflyIII\Events\AdminRequestedTestMessage;
|
||||||
use FireflyIII\Events\NewVersionAvailable;
|
use FireflyIII\Events\NewVersionAvailable;
|
||||||
use FireflyIII\Notifications\Admin\TestNotification;
|
use FireflyIII\Notifications\Admin\TestNotification;
|
||||||
|
use FireflyIII\Notifications\Admin\UserInvitation;
|
||||||
use FireflyIII\Notifications\Admin\VersionCheckResult;
|
use FireflyIII\Notifications\Admin\VersionCheckResult;
|
||||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||||
use FireflyIII\Support\Facades\FireflyConfig;
|
use FireflyIII\Support\Facades\FireflyConfig;
|
||||||
@@ -57,6 +59,27 @@ class AdminEventHandler
|
|||||||
Notification::send($event->user, new TestNotification($event->user->email));
|
Notification::send($event->user, new TestNotification($event->user->email));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InvitationCreated $event
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function sendInvitationNotification(InvitationCreated $event): void
|
||||||
|
{
|
||||||
|
$sendMail = FireflyConfig::get('notification_invite_created', true)->data;
|
||||||
|
if (false === $sendMail) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var UserRepositoryInterface $repository */
|
||||||
|
$repository = app(UserRepositoryInterface::class);
|
||||||
|
$all = $repository->all();
|
||||||
|
foreach ($all as $user) {
|
||||||
|
if ($repository->hasRole($user, 'owner')) {
|
||||||
|
Notification::send($user, new UserInvitation($event->invitee));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send new version message to admin.
|
* Send new version message to admin.
|
||||||
*
|
*
|
||||||
|
@@ -22,14 +22,17 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Http\Controllers\Admin;
|
namespace FireflyIII\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use FireflyIII\Events\Admin\InvitationCreated;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
use FireflyIII\Http\Middleware\IsDemoUser;
|
use FireflyIII\Http\Middleware\IsDemoUser;
|
||||||
|
use FireflyIII\Http\Requests\InviteUserFormRequest;
|
||||||
use FireflyIII\Http\Requests\UserFormRequest;
|
use FireflyIII\Http\Requests\UserFormRequest;
|
||||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Illuminate\Contracts\Foundation\Application;
|
use Illuminate\Contracts\Foundation\Application;
|
||||||
use Illuminate\Contracts\View\Factory;
|
use Illuminate\Contracts\View\Factory;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Routing\Redirector;
|
use Illuminate\Routing\Redirector;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
use Log;
|
use Log;
|
||||||
@@ -145,6 +148,14 @@ class UserController extends Controller
|
|||||||
$subTitle = (string) trans('firefly.user_administration');
|
$subTitle = (string) trans('firefly.user_administration');
|
||||||
$subTitleIcon = 'fa-users';
|
$subTitleIcon = 'fa-users';
|
||||||
$users = $this->repository->all();
|
$users = $this->repository->all();
|
||||||
|
$singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||||
|
$allowInvites = false;
|
||||||
|
if (!$this->externalIdentity && $singleUserMode) {
|
||||||
|
// also registration enabled.
|
||||||
|
$allowInvites = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$invitedUsers = $this->repository->getInvitedUsers();
|
||||||
|
|
||||||
// add meta stuff.
|
// add meta stuff.
|
||||||
$users->each(
|
$users->each(
|
||||||
@@ -154,7 +165,7 @@ class UserController extends Controller
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return view('admin.users.index', compact('subTitle', 'subTitleIcon', 'users'));
|
return view('admin.users.index', compact('subTitle', 'subTitleIcon', 'users', 'allowInvites', 'invitedUsers'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -185,6 +196,22 @@ class UserController extends Controller
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InviteUserFormRequest $request
|
||||||
|
* @return RedirectResponse
|
||||||
|
*/
|
||||||
|
public function invite(InviteUserFormRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$address = (string) $request->get('invited_user');
|
||||||
|
$invitee = $this->repository->inviteUser(auth()->user(), $address);
|
||||||
|
session()->flash('info', trans('firefly.user_is_invited', ['address' => $address]));
|
||||||
|
|
||||||
|
// event!
|
||||||
|
event(new InvitationCreated($invitee));
|
||||||
|
|
||||||
|
return redirect(route('admin.users'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update single user.
|
* Update single user.
|
||||||
*
|
*
|
||||||
|
@@ -25,6 +25,7 @@ namespace FireflyIII\Http\Controllers\Auth;
|
|||||||
use FireflyIII\Events\RegisteredUser;
|
use FireflyIII\Events\RegisteredUser;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
|
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||||
use FireflyIII\Support\Http\Controllers\CreateStuff;
|
use FireflyIII\Support\Http\Controllers\CreateStuff;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Illuminate\Contracts\Foundation\Application;
|
use Illuminate\Contracts\Foundation\Application;
|
||||||
@@ -88,11 +89,15 @@ class RegisterController extends Controller
|
|||||||
public function register(Request $request)
|
public function register(Request $request)
|
||||||
{
|
{
|
||||||
$allowRegistration = $this->allowedToRegister();
|
$allowRegistration = $this->allowedToRegister();
|
||||||
|
$inviteCode = (string) $request->get('invite_code');
|
||||||
|
$repository = app(UserRepositoryInterface::class);
|
||||||
|
$validCode = $repository->validateInviteCode($inviteCode);
|
||||||
|
|
||||||
if (false === $allowRegistration) {
|
if (false === $allowRegistration && false === $validCode) {
|
||||||
throw new FireflyException('Registration is currently not available :(');
|
throw new FireflyException('Registration is currently not available :(');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$this->validator($request->all())->validate();
|
$this->validator($request->all())->validate();
|
||||||
$user = $this->createUser($request->all());
|
$user = $this->createUser($request->all());
|
||||||
Log::info(sprintf('Registered new user %s', $user->email));
|
Log::info(sprintf('Registered new user %s', $user->email));
|
||||||
@@ -104,6 +109,10 @@ class RegisterController extends Controller
|
|||||||
|
|
||||||
$this->registered($request, $user);
|
$this->registered($request, $user);
|
||||||
|
|
||||||
|
if ($validCode) {
|
||||||
|
$repository->redeemCode($inviteCode);
|
||||||
|
}
|
||||||
|
|
||||||
return redirect($this->redirectPath());
|
return redirect($this->redirectPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,4 +166,39 @@ class RegisterController extends Controller
|
|||||||
|
|
||||||
return view('auth.register', compact('isDemoSite', 'email', 'pageTitle'));
|
return view('auth.register', compact('isDemoSite', 'email', 'pageTitle'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the application registration form if the invitation code is valid.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return Factory|View
|
||||||
|
* @throws ContainerExceptionInterface
|
||||||
|
* @throws FireflyException
|
||||||
|
* @throws NotFoundExceptionInterface
|
||||||
|
*/
|
||||||
|
public function showInviteForm(Request $request, string $code)
|
||||||
|
{
|
||||||
|
$isDemoSite = app('fireflyconfig')->get('is_demo_site', config('firefly.configuration.is_demo_site'))->data;
|
||||||
|
$pageTitle = (string) trans('firefly.register_page_title');
|
||||||
|
$repository = app(UserRepositoryInterface::class);
|
||||||
|
$allowRegistration = $this->allowedToRegister();
|
||||||
|
$inviteCode = $code;
|
||||||
|
$validCode = $repository->validateInviteCode($inviteCode);
|
||||||
|
|
||||||
|
if (true === $allowRegistration) {
|
||||||
|
$message = 'You do not need an invite code on this installation.';
|
||||||
|
|
||||||
|
return view('error', compact('message'));
|
||||||
|
}
|
||||||
|
if(false === $validCode) {
|
||||||
|
$message = 'Invalid code.';
|
||||||
|
|
||||||
|
return view('error', compact('message'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$email = $request->old('email');
|
||||||
|
|
||||||
|
return view('auth.register', compact('isDemoSite', 'email', 'pageTitle', 'inviteCode'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
43
app/Http/Requests/InviteUserFormRequest.php
Normal file
43
app/Http/Requests/InviteUserFormRequest.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* InviteUserFormRequest.php
|
||||||
|
* Copyright (c) 2022 james@firefly-iii.org
|
||||||
|
*
|
||||||
|
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FireflyIII\Http\Requests;
|
||||||
|
|
||||||
|
use FireflyIII\Support\Request\ChecksLogin;
|
||||||
|
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class InviteUserFormRequest extends FormRequest
|
||||||
|
{
|
||||||
|
use ConvertsDataTypes, ChecksLogin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rules for this request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'invited_user' => 'required|email|unique:invited_users,email',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
49
app/Models/InvitedUser.php
Normal file
49
app/Models/InvitedUser.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* InvitedUser.php
|
||||||
|
* Copyright (c) 2022 james@firefly-iii.org
|
||||||
|
*
|
||||||
|
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FireflyIII\Models;
|
||||||
|
|
||||||
|
use FireflyIII\User;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class InvitedUser
|
||||||
|
*/
|
||||||
|
class InvitedUser extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = ['user_id', 'email', 'invite_code', 'expires', 'redeemed'];
|
||||||
|
|
||||||
|
protected $casts
|
||||||
|
= [
|
||||||
|
'expires' => 'datetime',
|
||||||
|
'redeemed' => 'boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
* @return BelongsTo
|
||||||
|
*/
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
@@ -22,7 +22,76 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Notifications\Admin;
|
namespace FireflyIII\Notifications\Admin;
|
||||||
|
|
||||||
class UserInvitation
|
use FireflyIII\Models\InvitedUser;
|
||||||
{
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Messages\SlackMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class UserInvitation
|
||||||
|
*/
|
||||||
|
class UserInvitation extends Notification
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
private InvitedUser $invitee;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new notification instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(InvitedUser $invitee)
|
||||||
|
{
|
||||||
|
$this->invitee = $invitee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the notification's delivery channels.
|
||||||
|
*
|
||||||
|
* @param mixed $notifiable
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function via($notifiable)
|
||||||
|
{
|
||||||
|
return ['mail', 'slack'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the mail representation of the notification.
|
||||||
|
*
|
||||||
|
* @param mixed $notifiable
|
||||||
|
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||||
|
*/
|
||||||
|
public function toMail($notifiable)
|
||||||
|
{
|
||||||
|
return (new MailMessage)
|
||||||
|
->markdown('emails.invitation-created', ['email' => $this->invitee->user->email, 'invitee' => $this->invitee->email])
|
||||||
|
->subject((string) trans('email.invitation_created_subject'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Slack representation of the notification.
|
||||||
|
*
|
||||||
|
* @param mixed $notifiable
|
||||||
|
* @return SlackMessage
|
||||||
|
*/
|
||||||
|
public function toSlack($notifiable)
|
||||||
|
{
|
||||||
|
return (new SlackMessage)->content((string) trans('email.invitation_created_body', ['email' => $this->invitee->user->email, 'invitee' => $this->invitee->email]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the array representation of the notification.
|
||||||
|
*
|
||||||
|
* @param mixed $notifiable
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function toArray($notifiable)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@ namespace FireflyIII\Providers;
|
|||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use FireflyIII\Events\ActuallyLoggedIn;
|
use FireflyIII\Events\ActuallyLoggedIn;
|
||||||
|
use FireflyIII\Events\Admin\InvitationCreated;
|
||||||
use FireflyIII\Events\AdminRequestedTestMessage;
|
use FireflyIII\Events\AdminRequestedTestMessage;
|
||||||
use FireflyIII\Events\DestroyedTransactionGroup;
|
use FireflyIII\Events\DestroyedTransactionGroup;
|
||||||
use FireflyIII\Events\DetectedNewIPAddress;
|
use FireflyIII\Events\DetectedNewIPAddress;
|
||||||
@@ -113,6 +114,11 @@ class EventServiceProvider extends ServiceProvider
|
|||||||
NewVersionAvailable::class => [
|
NewVersionAvailable::class => [
|
||||||
'FireflyIII\Handlers\Events\AdminEventHandler@sendNewVersion',
|
'FireflyIII\Handlers\Events\AdminEventHandler@sendNewVersion',
|
||||||
],
|
],
|
||||||
|
InvitationCreated::class => [
|
||||||
|
'FireflyIII\Handlers\Events\AdminEventHandler@sendInvitationNotification',
|
||||||
|
//'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationInvite',
|
||||||
|
],
|
||||||
|
|
||||||
// is a Transaction Journal related event.
|
// is a Transaction Journal related event.
|
||||||
StoredTransactionGroup::class => [
|
StoredTransactionGroup::class => [
|
||||||
'FireflyIII\Handlers\Events\StoredGroupEventHandler@processRules',
|
'FireflyIII\Handlers\Events\StoredGroupEventHandler@processRules',
|
||||||
|
@@ -22,9 +22,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Repositories\User;
|
namespace FireflyIII\Repositories\User;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
use Exception;
|
use Exception;
|
||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Models\BudgetLimit;
|
use FireflyIII\Models\BudgetLimit;
|
||||||
|
use FireflyIII\Models\InvitedUser;
|
||||||
use FireflyIII\Models\Role;
|
use FireflyIII\Models\Role;
|
||||||
use FireflyIII\Models\UserGroup;
|
use FireflyIII\Models\UserGroup;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
@@ -103,22 +105,6 @@ class UserRepository implements UserRepositoryInterface
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function count(): int
|
|
||||||
{
|
|
||||||
return $this->all()->count();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection
|
|
||||||
*/
|
|
||||||
public function all(): Collection
|
|
||||||
{
|
|
||||||
return User::orderBy('id', 'DESC')->get(['users.*']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param string $displayName
|
* @param string $displayName
|
||||||
@@ -164,6 +150,22 @@ class UserRepository implements UserRepositoryInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return $this->all()->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function all(): Collection
|
||||||
|
{
|
||||||
|
return User::orderBy('id', 'DESC')->get(['users.*']);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $userId
|
* @param int $userId
|
||||||
*
|
*
|
||||||
@@ -265,6 +267,24 @@ class UserRepository implements UserRepositoryInterface
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function inviteUser(User $user, string $email): InvitedUser
|
||||||
|
{
|
||||||
|
$now = Carbon::now();
|
||||||
|
$now->addDays(2);
|
||||||
|
$invitee = new InvitedUser;
|
||||||
|
$invitee->user()->associate($user);
|
||||||
|
$invitee->invite_code = Str::random(64);
|
||||||
|
$invitee->email = $email;
|
||||||
|
$invitee->redeemed = false;
|
||||||
|
$invitee->expires = $now;
|
||||||
|
$invitee->save();
|
||||||
|
|
||||||
|
return $invitee;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set MFA code.
|
* Set MFA code.
|
||||||
*
|
*
|
||||||
@@ -416,4 +436,34 @@ class UserRepository implements UserRepositoryInterface
|
|||||||
{
|
{
|
||||||
return Role::where('name', $role)->first();
|
return Role::where('name', $role)->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getInvitedUsers(): Collection
|
||||||
|
{
|
||||||
|
return InvitedUser::with('user')->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function validateInviteCode(string $code): bool
|
||||||
|
{
|
||||||
|
$now = Carbon::now();
|
||||||
|
$invitee = InvitedUser::where('invite_code', $code)->where('expires', '<=', $now)->where('redeemed', 0)->first();
|
||||||
|
return null !== $invitee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function redeemCode(string $code): void
|
||||||
|
{
|
||||||
|
$obj = InvitedUser::where('invite_code', $code)->where('redeemed', 0)->first();
|
||||||
|
if ($obj) {
|
||||||
|
$obj->redeemed = true;
|
||||||
|
$obj->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Repositories\User;
|
namespace FireflyIII\Repositories\User;
|
||||||
|
|
||||||
|
use FireflyIII\Models\InvitedUser;
|
||||||
use FireflyIII\Models\Role;
|
use FireflyIII\Models\Role;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
@@ -159,6 +160,30 @@ interface UserRepositoryInterface
|
|||||||
*/
|
*/
|
||||||
public function hasRole(User $user, string $role): bool;
|
public function hasRole(User $user, string $role): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param User $user
|
||||||
|
* @param string $email
|
||||||
|
* @return InvitedUser
|
||||||
|
*/
|
||||||
|
public function inviteUser(User $user, string $email): InvitedUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function getInvitedUsers(): Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $code
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validateInviteCode(string $code): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $code
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function redeemCode(string $code): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove any role the user has.
|
* Remove any role the user has.
|
||||||
*
|
*
|
||||||
|
@@ -49,6 +49,7 @@ use FireflyIII\Models\TransactionJournal;
|
|||||||
use FireflyIII\Models\UserGroup;
|
use FireflyIII\Models\UserGroup;
|
||||||
use FireflyIII\Models\Webhook;
|
use FireflyIII\Models\Webhook;
|
||||||
use FireflyIII\Notifications\Admin\TestNotification;
|
use FireflyIII\Notifications\Admin\TestNotification;
|
||||||
|
use FireflyIII\Notifications\Admin\UserInvitation;
|
||||||
use FireflyIII\Notifications\Admin\UserRegistration;
|
use FireflyIII\Notifications\Admin\UserRegistration;
|
||||||
use FireflyIII\Notifications\Admin\VersionCheckResult;
|
use FireflyIII\Notifications\Admin\VersionCheckResult;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
@@ -599,6 +600,9 @@ class User extends Authenticatable
|
|||||||
if ($notification instanceof VersionCheckResult) {
|
if ($notification instanceof VersionCheckResult) {
|
||||||
return app('fireflyconfig')->get('slack_webhook_url', '')->data;
|
return app('fireflyconfig')->get('slack_webhook_url', '')->data;
|
||||||
}
|
}
|
||||||
|
if ($notification instanceof UserInvitation) {
|
||||||
|
return app('fireflyconfig')->get('slack_webhook_url', '')->data;
|
||||||
|
}
|
||||||
return app('preferences')->getForUser($this, 'slack_webhook_url', '')->data;
|
return app('preferences')->getForUser($this, 'slack_webhook_url', '')->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -150,7 +150,7 @@ return [
|
|||||||
|
|
||||||
// notifications
|
// notifications
|
||||||
'available_notifications' => ['bill_reminder', 'new_access_token', 'transaction_creation', 'user_login'],
|
'available_notifications' => ['bill_reminder', 'new_access_token', 'transaction_creation', 'user_login'],
|
||||||
'admin_notifications' => ['admin_new_reg', 'user_new_reg', 'new_version'],
|
'admin_notifications' => ['admin_new_reg', 'user_new_reg', 'new_version', 'invite_created', 'invite_redeemed'],
|
||||||
|
|
||||||
// enabled languages
|
// enabled languages
|
||||||
'languages' => [
|
'languages' => [
|
||||||
|
37
database/migrations/2022_10_01_074908_invited_users.php
Normal file
37
database/migrations/2022_10_01_074908_invited_users.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('invited_users', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->integer('user_id', false, true);
|
||||||
|
$table->string('email', 255);
|
||||||
|
$table->string('invite_code', 64);
|
||||||
|
$table->dateTime('expires');
|
||||||
|
$table->boolean('redeemed');
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('invited_users');
|
||||||
|
}
|
||||||
|
};
|
@@ -11,14 +11,14 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@popperjs/core": "^2.11.2",
|
"@popperjs/core": "^2.11.2",
|
||||||
"@quasar/extras": "^1.14.3",
|
"@quasar/extras": "^1.15.4",
|
||||||
"apexcharts": "^3.32.1",
|
"apexcharts": "^3.32.1",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"axios-cache-adapter": "^2.7.3",
|
"axios-cache-adapter": "^2.7.3",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"pinia": "^2.0.14",
|
"pinia": "^2.0.14",
|
||||||
"quasar": "^2.7.5",
|
"quasar": "^2.8.4",
|
||||||
"vue": "3",
|
"vue": "3",
|
||||||
"vue-i18n": "^9.0.0",
|
"vue-i18n": "^9.0.0",
|
||||||
"vue-router": "^4.0.0",
|
"vue-router": "^4.0.0",
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.13.14",
|
"@babel/eslint-parser": "^7.13.14",
|
||||||
"@quasar/app-webpack": "^3.5.7",
|
"@quasar/app-webpack": "^3.6.1",
|
||||||
"@types/node": "^12.20.21",
|
"@types/node": "^12.20.21",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.16.1",
|
"@typescript-eslint/eslint-plugin": "^4.16.1",
|
||||||
"@typescript-eslint/parser": "^4.16.1",
|
"@typescript-eslint/parser": "^4.16.1",
|
||||||
|
@@ -1165,10 +1165,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@positron/stack-trace/-/stack-trace-1.0.0.tgz#14fcc712a530038ef9be1ce6952315a839f466a8"
|
resolved "https://registry.yarnpkg.com/@positron/stack-trace/-/stack-trace-1.0.0.tgz#14fcc712a530038ef9be1ce6952315a839f466a8"
|
||||||
integrity sha1-FPzHEqUwA475vhzmlSMVqDn0Zqg=
|
integrity sha1-FPzHEqUwA475vhzmlSMVqDn0Zqg=
|
||||||
|
|
||||||
"@quasar/app-webpack@^3.5.7":
|
"@quasar/app-webpack@^3.6.1":
|
||||||
version "3.5.7"
|
version "3.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/@quasar/app-webpack/-/app-webpack-3.5.7.tgz#1c314288dfa8fd0b4167fd46d74d3e90c21d0ce1"
|
resolved "https://registry.yarnpkg.com/@quasar/app-webpack/-/app-webpack-3.6.1.tgz#f2189316e931b77df9c3c63bf21f6108c3111a2a"
|
||||||
integrity sha512-VvOOzvYjJa1ibl/w+dcvnBP9r97dtVHqaf3N7tTj7QaT667QX/BN6wCx2qKh8KOFODvKVs8vt/Nvvw02pWn7YA==
|
integrity sha512-dwHs34fMXPaAY6iO4B0Qs6zexIS8kv9kI9LuYDEiU9uHndn6roH0Wsnk6MjiHk/4zPqFAKIVn6zF6FdE5nD9nA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@quasar/babel-preset-app" "2.0.1"
|
"@quasar/babel-preset-app" "2.0.1"
|
||||||
"@quasar/fastclick" "1.1.5"
|
"@quasar/fastclick" "1.1.5"
|
||||||
@@ -1215,7 +1215,7 @@
|
|||||||
ouch "^2.0.1"
|
ouch "^2.0.1"
|
||||||
postcss "^8.4.4"
|
postcss "^8.4.4"
|
||||||
postcss-loader "6.2.1"
|
postcss-loader "6.2.1"
|
||||||
postcss-rtlcss "3.6.3"
|
postcss-rtlcss "3.7.2"
|
||||||
pretty-error "4.0.0"
|
pretty-error "4.0.0"
|
||||||
register-service-worker "1.7.2"
|
register-service-worker "1.7.2"
|
||||||
sass "1.32.12"
|
sass "1.32.12"
|
||||||
@@ -1231,7 +1231,7 @@
|
|||||||
webpack "^5.58.1"
|
webpack "^5.58.1"
|
||||||
webpack-bundle-analyzer "4.5.0"
|
webpack-bundle-analyzer "4.5.0"
|
||||||
webpack-chain "6.5.1"
|
webpack-chain "6.5.1"
|
||||||
webpack-dev-server "4.9.2"
|
webpack-dev-server "4.9.3"
|
||||||
webpack-merge "5.8.0"
|
webpack-merge "5.8.0"
|
||||||
webpack-node-externals "3.0.0"
|
webpack-node-externals "3.0.0"
|
||||||
|
|
||||||
@@ -1261,10 +1261,10 @@
|
|||||||
core-js "^3.6.5"
|
core-js "^3.6.5"
|
||||||
core-js-compat "^3.6.5"
|
core-js-compat "^3.6.5"
|
||||||
|
|
||||||
"@quasar/extras@^1.14.3":
|
"@quasar/extras@^1.15.4":
|
||||||
version "1.14.3"
|
version "1.15.4"
|
||||||
resolved "https://registry.yarnpkg.com/@quasar/extras/-/extras-1.14.3.tgz#2a4d7a2f773a789ca43e3a02d5b797c9d2888884"
|
resolved "https://registry.yarnpkg.com/@quasar/extras/-/extras-1.15.4.tgz#c3c78416c2c39e4d4e791e8bd4dd6ff4b7e14b14"
|
||||||
integrity sha512-OHyR/pfW0R8E5DvnsV1wg9ISnLL/yXHyOMZsqPY3gUtmfdF2634x2osdVg4YpBYW29vIQeV5feGWGIx8nuprdA==
|
integrity sha512-GGURiH/K/IZM41RD9hcNG0Zly63sFGFZ97Q+doVMFSGBqNySfVNsb3WFSovOyL5K/Lfnb/sjzslroVIUoDVTKw==
|
||||||
|
|
||||||
"@quasar/fastclick@1.1.5":
|
"@quasar/fastclick@1.1.5":
|
||||||
version "1.1.5"
|
version "1.1.5"
|
||||||
@@ -2481,10 +2481,10 @@ concat-map@0.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||||
|
|
||||||
connect-history-api-fallback@^1.6.0:
|
connect-history-api-fallback@^2.0.0:
|
||||||
version "1.6.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
|
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8"
|
||||||
integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
|
integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==
|
||||||
|
|
||||||
content-disposition@0.5.4:
|
content-disposition@0.5.4:
|
||||||
version "0.5.4"
|
version "0.5.4"
|
||||||
@@ -5158,10 +5158,10 @@ postcss-reduce-transforms@^5.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
postcss-value-parser "^4.2.0"
|
postcss-value-parser "^4.2.0"
|
||||||
|
|
||||||
postcss-rtlcss@3.6.3:
|
postcss-rtlcss@3.7.2:
|
||||||
version "3.6.3"
|
version "3.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-rtlcss/-/postcss-rtlcss-3.6.3.tgz#aabd1122a5b082157ea06d606c441002c1060030"
|
resolved "https://registry.yarnpkg.com/postcss-rtlcss/-/postcss-rtlcss-3.7.2.tgz#0a06cbfd74aec36ad1fe6c6fd1ec74069c62ce45"
|
||||||
integrity sha512-jJlS7gM5JPH8n/hcHqqekK8wusdFEFYi79mBvAK2GWvl3aehOFgj9vEMwFzUTJrrErakYTgiQ+uuGAzdL98g0g==
|
integrity sha512-GurrGedCKvOTe1QrifI+XpDKXA3bJky1v8KiOa/TYYHs1bfJOxI53GIRvVSqLJLly7e1WcNMz8KMESTN01vbZQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
rtlcss "^3.5.0"
|
rtlcss "^3.5.0"
|
||||||
|
|
||||||
@@ -5259,10 +5259,10 @@ qs@6.9.7:
|
|||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
|
||||||
integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==
|
integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==
|
||||||
|
|
||||||
quasar@^2.7.5:
|
quasar@^2.8.4:
|
||||||
version "2.7.5"
|
version "2.8.4"
|
||||||
resolved "https://registry.yarnpkg.com/quasar/-/quasar-2.7.5.tgz#a3feb5d50647313c4d6e1451223c158e10792902"
|
resolved "https://registry.yarnpkg.com/quasar/-/quasar-2.8.4.tgz#d32d7f0c1c4f313ee45f8f3d72028f3085727172"
|
||||||
integrity sha512-DWI0S+bXASfMSPrB8c/LVsXpA4dF7cBUbaJlcrM+1ioTNBHtiudma2Nhk2SDd5bzk9AYVHh5A8JCZuKqQAXt7g==
|
integrity sha512-bygg0GgSwQyrUJJTaHmYV50nVrz779QsNeH/cg2R/SHOQ4UmJI2FBA1hxU/nlpJ6DbmezNab1COa5ID57PvKfw==
|
||||||
|
|
||||||
queue-microtask@^1.2.2:
|
queue-microtask@^1.2.2:
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
@@ -6404,10 +6404,10 @@ webpack-dev-middleware@^5.3.1:
|
|||||||
range-parser "^1.2.1"
|
range-parser "^1.2.1"
|
||||||
schema-utils "^4.0.0"
|
schema-utils "^4.0.0"
|
||||||
|
|
||||||
webpack-dev-server@4.9.2:
|
webpack-dev-server@4.9.3:
|
||||||
version "4.9.2"
|
version "4.9.3"
|
||||||
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.9.2.tgz#c188db28c7bff12f87deda2a5595679ebbc3c9bc"
|
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.9.3.tgz#2360a5d6d532acb5410a668417ad549ee3b8a3c9"
|
||||||
integrity sha512-H95Ns95dP24ZsEzO6G9iT+PNw4Q7ltll1GfJHV4fKphuHWgKFzGHWi4alTlTnpk1SPPk41X+l2RB7rLfIhnB9Q==
|
integrity sha512-3qp/eoboZG5/6QgiZ3llN8TUzkSpYg1Ko9khWX1h40MIEUNS2mDoIa8aXsPfskER+GbTvs/IJZ1QTBBhhuetSw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/bonjour" "^3.5.9"
|
"@types/bonjour" "^3.5.9"
|
||||||
"@types/connect-history-api-fallback" "^1.3.5"
|
"@types/connect-history-api-fallback" "^1.3.5"
|
||||||
@@ -6421,7 +6421,7 @@ webpack-dev-server@4.9.2:
|
|||||||
chokidar "^3.5.3"
|
chokidar "^3.5.3"
|
||||||
colorette "^2.0.10"
|
colorette "^2.0.10"
|
||||||
compression "^1.7.4"
|
compression "^1.7.4"
|
||||||
connect-history-api-fallback "^1.6.0"
|
connect-history-api-fallback "^2.0.0"
|
||||||
default-gateway "^6.0.3"
|
default-gateway "^6.0.3"
|
||||||
express "^4.17.3"
|
express "^4.17.3"
|
||||||
graceful-fs "^4.2.6"
|
graceful-fs "^4.2.6"
|
||||||
|
@@ -33,6 +33,10 @@ return [
|
|||||||
'admin_test_subject' => 'A test message from your Firefly III installation',
|
'admin_test_subject' => 'A test message from your Firefly III installation',
|
||||||
'admin_test_body' => 'This is a test message from your Firefly III instance. It was sent to :email.',
|
'admin_test_body' => 'This is a test message from your Firefly III instance. It was sent to :email.',
|
||||||
|
|
||||||
|
// invite
|
||||||
|
'invitation_created_subject' => 'An invitation has been created',
|
||||||
|
'invitation_created_body' => 'Admin user ":email" created a user invitation which can be used by whoever is behind email address ":invitee". The invite will be valid for 48hrs.',
|
||||||
|
|
||||||
// new IP
|
// new IP
|
||||||
'login_from_new_ip' => 'New login on Firefly III',
|
'login_from_new_ip' => 'New login on Firefly III',
|
||||||
'slack_login_from_new_ip' => 'New Firefly III login from IP :ip (:host)',
|
'slack_login_from_new_ip' => 'New Firefly III login from IP :ip (:host)',
|
||||||
|
@@ -2231,7 +2231,13 @@ return [
|
|||||||
'number_of_decimals' => 'Number of decimals',
|
'number_of_decimals' => 'Number of decimals',
|
||||||
|
|
||||||
// administration
|
// administration
|
||||||
|
'invite_new_user_title' => 'Invite new user',
|
||||||
|
'invite_new_user_text' => 'As an administrator, you can invite users to register on your Firefly III administration. Using the direct link you can share with them, they will be able to register an account. The invited user and their invite link will appear in the table below. You are free to share the invitation link with them.',
|
||||||
|
'invited_user_mail' => 'Email address',
|
||||||
|
'invite_user' => 'Invite user',
|
||||||
|
'user_is_invited' => 'Email address ":address" was invited to Firefly III',
|
||||||
'administration' => 'Administration',
|
'administration' => 'Administration',
|
||||||
|
'code_already_used' => 'Invite code has been used',
|
||||||
'user_administration' => 'User administration',
|
'user_administration' => 'User administration',
|
||||||
'list_all_users' => 'All users',
|
'list_all_users' => 'All users',
|
||||||
'all_users' => 'All users',
|
'all_users' => 'All users',
|
||||||
@@ -2275,6 +2281,8 @@ return [
|
|||||||
'admin_notification_check_user_new_reg' => 'User gets post-registration welcome message',
|
'admin_notification_check_user_new_reg' => 'User gets post-registration welcome message',
|
||||||
'admin_notification_check_admin_new_reg' => 'Administrator(s) get new user registration notification',
|
'admin_notification_check_admin_new_reg' => 'Administrator(s) get new user registration notification',
|
||||||
'admin_notification_check_new_version' => 'A new version is available',
|
'admin_notification_check_new_version' => 'A new version is available',
|
||||||
|
'admin_notification_check_invite_created' => 'A user is invited to Firefly III',
|
||||||
|
'admin_notification_check_invite_redeemed' => 'A user invitation is redeemed',
|
||||||
'save_notification_settings' => 'Save settings',
|
'save_notification_settings' => 'Save settings',
|
||||||
'notification_settings_saved' => 'The notification settings have been saved',
|
'notification_settings_saved' => 'The notification settings have been saved',
|
||||||
|
|
||||||
|
@@ -43,6 +43,10 @@ return [
|
|||||||
'lastActivity' => 'Last activity',
|
'lastActivity' => 'Last activity',
|
||||||
'balanceDiff' => 'Balance difference',
|
'balanceDiff' => 'Balance difference',
|
||||||
'other_meta_data' => 'Other meta data',
|
'other_meta_data' => 'Other meta data',
|
||||||
|
'invited_at' => 'Invited at',
|
||||||
|
'expires' => 'Invitation expires',
|
||||||
|
'invited_by' => 'Invited by',
|
||||||
|
'invite_link' => 'Invite link',
|
||||||
'account_type' => 'Account type',
|
'account_type' => 'Account type',
|
||||||
'created_at' => 'Created at',
|
'created_at' => 'Created at',
|
||||||
'account' => 'Account',
|
'account' => 'Account',
|
||||||
|
@@ -4,6 +4,31 @@
|
|||||||
{{ Breadcrumbs.render }}
|
{{ Breadcrumbs.render }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% if allowInvites %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<form action="{{ route('admin.users.invite') }}" method="post">
|
||||||
|
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
|
||||||
|
<div class="box box-primary">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">{{ 'invite_new_user_title'|_ }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body">
|
||||||
|
<p>
|
||||||
|
{{ 'invite_new_user_text'|_ }}
|
||||||
|
</p>
|
||||||
|
{{ ExpandedForm.text('invited_user',null, {'type': 'email', 'label' : 'invited_user_mail'|_}) }}
|
||||||
|
</div>
|
||||||
|
<div class="box-footer">
|
||||||
|
<button type="submit" class="btn pull-right btn-success">
|
||||||
|
{{ ('invite_user')|_ }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<div class="box box-primary">
|
<div class="box box-primary">
|
||||||
@@ -29,8 +54,10 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="hidden-xs" data-value="{{ user.id }}">
|
<td class="hidden-xs" data-value="{{ user.id }}">
|
||||||
<div class="btn-group btn-group-xs">
|
<div class="btn-group btn-group-xs">
|
||||||
<a class="btn btn-default" href="{{ route('admin.users.edit',user.id) }}"><span class="fa fa-pencil"></span></a>
|
<a class="btn btn-default" href="{{ route('admin.users.edit',user.id) }}"><span
|
||||||
<a class="btn btn-danger" href="{{ route('admin.users.delete',user.id) }}"><span class="fa fa-trash"></span></a>
|
class="fa fa-pencil"></span></a>
|
||||||
|
<a class="btn btn-danger" href="{{ route('admin.users.delete',user.id) }}"><span
|
||||||
|
class="fa fa-trash"></span></a>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden-xs" data-value="{{ user.id }}">#{{ user.id }}</td>
|
<td class="hidden-xs" data-value="{{ user.id }}">#{{ user.id }}</td>
|
||||||
@@ -60,9 +87,11 @@
|
|||||||
</td>
|
</td>
|
||||||
<td data-value="{% if user.blocked %}1{% else %}0{% endif %}">
|
<td data-value="{% if user.blocked %}1{% else %}0{% endif %}">
|
||||||
{% if user.blocked == 1 %}
|
{% if user.blocked == 1 %}
|
||||||
<small class="text-danger"><span class="fa fa-fw fa-check" title="{{ 'yes'|_ }}"></span></small>
|
<small class="text-danger"><span class="fa fa-fw fa-check"
|
||||||
|
title="{{ 'yes'|_ }}"></span></small>
|
||||||
{% else %}
|
{% else %}
|
||||||
<small class="text-success"><span class="fa fa-fw fa-times" title="{{ 'no'|_ }}"></span></small>
|
<small class="text-success"><span class="fa fa-fw fa-times"
|
||||||
|
title="{{ 'no'|_ }}"></span></small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden-xs">
|
<td class="hidden-xs">
|
||||||
@@ -84,10 +113,71 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if invitedUsers.count > 0 %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">{{ 'all_invited_users'|_ }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body no-padding">
|
||||||
|
<table class="table table-responsive table-condensed sortable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-defaultsign="_19" class="hidden-xs" colspan="1"> </th>
|
||||||
|
<th data-defaultsign="az">{{ trans('list.email') }}</th>
|
||||||
|
<th data-defaultsign="month" class="hidden-xs">{{ trans('list.invited_at') }}</th>
|
||||||
|
<th data-defaultsign="month" class="hidden-xs">{{ trans('list.expires') }}</th>
|
||||||
|
<th class="hidden-xs">{{ trans('list.invited_by') }}</th>
|
||||||
|
<th>{{ trans('list.invite_link') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for invitee in invitedUsers %}
|
||||||
|
<tr>
|
||||||
|
<td class="hidden-xs" data-value="{{ user.id }}">
|
||||||
|
<div class="btn-group btn-group-xs">
|
||||||
|
<a class="btn btn-danger" href="{{ route('admin.users.delete-invite', invitee.id) }}"><span
|
||||||
|
class="fa fa-trash"></span></a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ invitee.email }}
|
||||||
|
</td>
|
||||||
|
<td class="hidden-xs">
|
||||||
|
{{ invitee.created_at.isoFormat(monthAndDayFormat) }}
|
||||||
|
{{ invitee.created_at.format('H:i') }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ invitee.expires.isoFormat(monthAndDayFormat) }}
|
||||||
|
{{ invitee.expires.format('H:i') }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ invitee.user.email }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if invitee.redeemed %}
|
||||||
|
<em><s>{{ 'code_already_used'|_ }}</s></em>
|
||||||
|
{% else %}
|
||||||
|
<input type="text" class="form-control" readonly value="{{ route('invite', [invitee.invite_code]) }}">
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
<link rel="stylesheet" href="v1/css/bootstrap-sortable.css?v={{ FF_VERSION }}" type="text/css" media="all" nonce="{{ JS_NONCE }}">
|
<link rel="stylesheet" href="v1/css/bootstrap-sortable.css?v={{ FF_VERSION }}" type="text/css" media="all"
|
||||||
|
nonce="{{ JS_NONCE }}">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script type="text/javascript" src="v1/js/lib/bootstrap-sortable.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script>
|
<script type="text/javascript" src="v1/js/lib/bootstrap-sortable.js?v={{ FF_VERSION }}"
|
||||||
|
nonce="{{ JS_NONCE }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
<form action="{{ route('register') }}" method="post">
|
<form action="{{ route('register') }}" method="post">
|
||||||
<input type="hidden" name="_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="_token" value="{{ csrf_token() }}">
|
||||||
|
<input type="hidden" name="invite_code" value="{{ inviteCode }}">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<input type="email" name="email" value="{{ email }}" class="form-control"
|
<input type="email" name="email" value="{{ email }}" class="form-control"
|
||||||
placeholder="{{ trans('form.email') }}"/>
|
placeholder="{{ trans('form.email') }}"/>
|
||||||
|
3
resources/views/emails/invitation-created.blade.php
Normal file
3
resources/views/emails/invitation-created.blade.php
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@component('mail::message')
|
||||||
|
{{ trans('email.invitation_created_body', ['email' => $email,'invitee' => $invitee]) }}
|
||||||
|
@endcomponent
|
@@ -58,6 +58,7 @@ Route::group(
|
|||||||
|
|
||||||
// Registration Routes...
|
// Registration Routes...
|
||||||
Route::get('register', ['uses' => 'Auth\RegisterController@showRegistrationForm', 'as' => 'register']);
|
Route::get('register', ['uses' => 'Auth\RegisterController@showRegistrationForm', 'as' => 'register']);
|
||||||
|
Route::get('invitee/{code}', ['uses' => 'Auth\RegisterController@showInviteForm', 'as' => 'invite']);
|
||||||
Route::post('register', 'Auth\RegisterController@register');
|
Route::post('register', 'Auth\RegisterController@register');
|
||||||
|
|
||||||
// Password Reset Routes...
|
// Password Reset Routes...
|
||||||
@@ -1101,6 +1102,10 @@ Route::group(
|
|||||||
Route::post('users/update/{user}', ['uses' => 'UserController@update', 'as' => 'users.update']);
|
Route::post('users/update/{user}', ['uses' => 'UserController@update', 'as' => 'users.update']);
|
||||||
Route::post('users/destroy/{user}', ['uses' => 'UserController@destroy', 'as' => 'users.destroy']);
|
Route::post('users/destroy/{user}', ['uses' => 'UserController@destroy', 'as' => 'users.destroy']);
|
||||||
|
|
||||||
|
// invitee management
|
||||||
|
Route::get('users/delete_invite/{invitedUser}', ['uses' => 'UserController@deleteInvite', 'as' => 'users.delete-invite']);
|
||||||
|
Route::post('users/invite', ['uses' => 'UserController@invite', 'as' => 'users.invite']);
|
||||||
|
|
||||||
// journal links manager
|
// journal links manager
|
||||||
Route::get('links', ['uses' => 'LinkController@index', 'as' => 'links.index']);
|
Route::get('links', ['uses' => 'LinkController@index', 'as' => 'links.index']);
|
||||||
Route::get('links/create', ['uses' => 'LinkController@create', 'as' => 'links.create']);
|
Route::get('links/create', ['uses' => 'LinkController@create', 'as' => 'links.create']);
|
||||||
|
Reference in New Issue
Block a user