Compare commits

...

92 Commits

Author SHA1 Message Date
Bubka 2e8a9f75b5 Fix translation sources 2024-03-28 09:25:25 +01:00
Bubka dd7d3d95df Fix DB_DATABASE path not respected by entrypoint script - Fixes #192 2024-03-23 18:54:19 +01:00
Bubka 2e296ba3c1 Fix translation sources 2024-03-23 18:43:20 +01:00
Bubka 2db5adfe3b Enhance QR code reading & return more relevant error msg - Fixes #244 2024-03-22 18:04:44 +01:00
Bubka 9c8d349e1c Fix version in change log 2024-03-21 18:47:01 +01:00
Bubka 2bfd4850ff Rebuild assets 2024-03-21 18:41:26 +01:00
Bubka 4516e8d33a Complete change log & Bump version number 2024-03-21 18:29:52 +01:00
Bubka 80d43911dc Update Languages translations 2024-03-21 18:28:42 +01:00
Bubka 790cbdcf28 Fix ineffective keepSsoRegistrationEnabled setting - Closes #327 2024-03-19 18:21:12 +01:00
Bubka f2c9f8aaa8 Fix missing admin permissions on WebAuthn login - Closes #326 2024-03-19 18:13:35 +01:00
Bubka f4624e2793 Rebuild assets 2024-03-16 11:45:12 +01:00
Bubka 1ef4370002 Update Languages translations 2024-03-16 11:41:31 +01:00
Bubka c0d4f58680 Quick fix: Increase PHP memory limit to let Docker tests pass 2024-03-16 11:28:20 +01:00
Bubka 1ac95d8439 Rebuild assets 2024-03-15 15:37:37 +01:00
Bubka 2002e29641 Complete change log 2024-03-15 15:37:06 +01:00
Bubka 999fc1bf5e Update Languages translations 2024-03-15 15:12:57 +01:00
Bubka aaf06d8cf1 Complete change log & Bump version number 2024-03-15 10:44:06 +01:00
Bubka 704166cfd6 Fix php doc 2024-03-15 08:02:36 +01:00
Bubka b56ee8b04f Fix call on null object 2024-03-14 15:09:34 +01:00
Bubka 86e7601328 Replace PUT by PATCH to promote admin permissions 2024-03-14 15:09:05 +01:00
Bubka e956959f69 Fix ownership verification - Closes #305, closes #320 2024-03-14 14:49:49 +01:00
Bubka 480268a606 Fix disabled labels appearance 2024-03-08 15:07:48 +01:00
Bubka e6d201d882 Add a keepSsoRegistrationEnabled admin setting - Closes #317 2024-03-08 15:07:44 +01:00
Bubka 1a26c75325 Add spaces removal on the Secret field - Closes #311 2024-03-08 11:11:36 +01:00
Bubka edd810cafd Add a Clear cache feature - Closes #316 2024-03-07 14:13:02 +01:00
Bubka fb029f77f6 Fix hard coded strings & illegible text - Closes #315 2024-03-07 14:07:25 +01:00
Bubka 9dddf1c14c Add viewDefaultGroupOnCopy user preference - Completes #300 2024-03-06 10:04:41 +01:00
Bubka 70f884270e Fix missing notification when refreshing preferences 2024-03-06 08:41:28 +01:00
Bubka 9519d5838c Fix inactivity detection followed by logout - Fixes #267 2024-03-06 08:40:29 +01:00
Bubka 214c1c2349 Rebuild assets 2024-03-03 22:50:28 +01:00
Bubka 1c51541d4d Improve cache management in Docker entrypoint 2024-03-03 22:46:29 +01:00
Bubka b280636296 Remove useless language string 2024-03-01 23:21:24 +01:00
Bubka 4491852568 Add plain text field to the Import view - Closes #288 2024-03-01 21:30:26 +01:00
Bubka 4ef3efd6ce Add clearSearchOnCopy user preference - Resolves #300 2024-03-01 14:46:15 +01:00
Bubka 3eed7c8f5b Add an email registration policy feature - Closes #250 2024-02-29 14:03:30 +01:00
Bubka fd5520c1cf Add Indentation prop & Left/Right icons props 2024-02-29 13:28:48 +01:00
Bubka 88d37394d3 Add a test email feature to the admin panel - Closes #307 2024-02-26 15:06:26 +01:00
Bubka 04078b09aa Add a listener to automatically log notification sends 2024-02-26 15:05:00 +01:00
Bubka 1e42008be7 Update legend for the is_admin field 2024-02-26 09:01:40 +01:00
Bubka a6646b2e9d Fix missing titles for new admin views 2024-02-26 08:38:02 +01:00
Bubka bfa1cc99c3 Add a title parameter to the CopyButton component 2024-02-26 08:37:30 +01:00
Bubka 21fa77f348 Move debug information to the admin section - Closes #303 2024-02-23 16:42:42 +01:00
Bubka ee4b21eab2 Change links separator in footer 2024-02-23 16:42:42 +01:00
Bubka a5b722c560 Change user logs format & Remove Updated On information 2024-02-23 16:42:42 +01:00
Bubka 5d01685ae1 Fix issues on Light theme 2024-02-23 16:42:42 +01:00
Bubka f591910719 Add a label to searchbox filters in user management view 2024-02-23 16:42:42 +01:00
Bubka 699a2a1f9e Add a placeholder to the SearchBox component 2024-02-23 16:42:42 +01:00
Bubka 444b110c05 Add headings to App Setup view 2024-02-23 16:42:42 +01:00
Bubka f398768f4e Add a confirmation when setting admin permissions 2024-02-23 16:42:42 +01:00
Bubka 6aeb7dcbc9 Add User management features to front-end 2024-02-23 16:42:42 +01:00
Bubka 91242860f0 Update & Complete tests 2024-02-23 16:39:32 +01:00
Bubka 96f883d19a Add User management features to back-end 2024-02-23 16:39:32 +01:00
Bubka 37e4711071 Refactor user deletion logic in a User observer 2024-02-23 16:39:32 +01:00
Bubka fab67097bc Update logged message 2024-02-23 16:39:32 +01:00
Bubka db3a732b15 Add a User policy to control authorization on User model 2024-02-23 16:39:32 +01:00
Bubka 3b156df8a2 Fix the useWebauthnOnly preference not being saved at webauthn reset 2024-02-23 16:39:32 +01:00
Bubka 6fe00585e5 Target db tables using config helper rather than hard coded strings 2024-02-23 16:39:32 +01:00
Bubka 8b397750e8 Control & Promote administrator status via a method rather than a prop 2024-02-23 16:39:32 +01:00
Bubka d96c943927 Add a no-background variation to the SearchBox component 2024-02-23 16:39:32 +01:00
Bubka 5249b2ab97 Move the copy button to a standalone component 2024-02-23 16:39:31 +01:00
Bubka 342448b352 Rebuild assets 2024-02-23 15:31:08 +01:00
Bubka 784b5206b9 Complete change log & Bump version number 2024-02-23 15:28:43 +01:00
Bubka 12bef04dd9 Update Languages translations 2024-02-23 14:55:03 +01:00
Bubka cee032897a Update translations & Add Japanese 2024-02-23 14:25:07 +01:00
Bubka 903509b0c6 Fix inconsistent translation sources 2024-02-21 15:16:47 +01:00
Bubka 406f0095ea Default ASSET_URL to APP_URL - Fixes #284 2024-02-19 13:38:38 +01:00
Bubka 1e3cb32bc2 Fix error at docker run when env is production - Fixes #296 2024-02-19 11:00:38 +01:00
Bubka 119eca6c7e Fix OAUTH redirect urls not respecting APP_URL - Fixes #299 2024-02-19 09:45:21 +01:00
Bubka 300d3c3dc8 Refresh user preferences from back-end when entering the Options view 2024-02-18 14:58:21 +01:00
Bubka f2d4c43239 Fix reset emails not being rendered - Fixes #298 2024-02-17 22:10:02 +01:00
Bubka 35f2f1df84 Rebuild assets 2024-01-19 15:49:01 +01:00
Bubka 5ed8414d2c Update translations 2024-01-19 15:47:47 +01:00
Bubka 91ca9f61c0 Complete change log 2024-01-19 15:42:25 +01:00
Bubka df33bcdbe0 Rebuild assets 2024-01-19 15:18:35 +01:00
Bubka 8c1df18204 Complete change log & Bump version number 2024-01-19 15:16:18 +01:00
Bubka b4e3f97313 Add no camera detection to 45d2ca5 - Completes #276 2024-01-17 14:40:26 +01:00
Bubka 45d2ca55ec Check secured context before trying to list cameras - Fixes #276 2024-01-17 14:27:45 +01:00
Bubka b9abcb3d18 Replace change event by input event in input fields components - Fixes #273 2024-01-17 12:34:41 +01:00
Bubka 98f711800d Add ASSET_URL to env vars to prevent mixed content/blank page
Fixes #256, Resolves #275
2024-01-17 10:38:58 +01:00
Bubka d2427364a5 Update available log channels in .env comments - Resolves #279 2024-01-17 08:56:44 +01:00
Bubka f4edbcd044 Set missing parameters to null to prevent error at import - Fixes #277 2024-01-17 08:33:03 +01:00
Bubka 1a9f011809 Rebuild assets 2023-12-29 17:55:28 +01:00
Bubka 7593fc91a8 Complete change log & Bump version number 2023-12-29 17:54:24 +01:00
Bubka 9fa4142a12 Fix Always On TOTP update when a HOTP exists - Closes #265 2023-12-29 17:53:51 +01:00
Bubka 78a1eebdd2 Complete change log & Bump version number 2023-12-29 00:57:57 +01:00
Bubka ddedcef61a Rebuild assets 2023-12-29 00:32:51 +01:00
Bubka 308bf3c436 Restore error message when auth proxy header is missing 2023-12-29 00:31:30 +01:00
Bubka 5fd7f43968 Fix missing subdirectory support - Fixes #262 2023-12-29 00:21:52 +01:00
Bubka e3ded669ca Clean obsolete Mix config & build assets 2023-12-22 11:02:07 +01:00
Bubka 6ffa07211e Add API change log 2023-12-22 10:52:52 +01:00
Bubka 5710ccc9d3 Update composer dependencies 2023-12-22 10:28:05 +01:00
Bubka 26d245aa79 Complete change log 2023-12-22 09:52:11 +01:00
357 changed files with 10676 additions and 2480 deletions

View File

@ -35,6 +35,13 @@ APP_KEY=
APP_URL=http://localhost
# If you want to serve js assets from a CDN (like https://cdn.example.com),
# uncomment the following line and set this var with the CDN url.
# Otherwise, let this line commented.
# ASSET_URL=http://localhost
# The domain subdirectory from which you want to serve 2FAuth.
# This must reflect the path targeted by APP_URL.
#
@ -54,8 +61,8 @@ IS_DEMO_APP=false
# The log channel defines where your log entries go to.
# 'daily' is the default logging mode giving you 7 daily rotated log files in /storage/logs/.
# Several other options exist. You can use 'single' for one big fat error log (not recommended).
# Also available are 'syslog', 'errorlog' and 'stdout' which will log to the system itself.
# Also available are 'errorlog', 'syslog', 'stderr', 'papertrail', 'slack' and a 'stack' channel
# to combine multiple channels into a single one.
LOG_CHANNEL=daily

12
Dockerfile vendored
View File

@ -31,6 +31,7 @@ COPY . .
RUN mv .env.testing .env
RUN composer install
RUN php artisan key:generate
COPY docker/php-test.ini /usr/local/etc/php/php.ini
ENTRYPOINT [ "/srv/vendor/bin/phpunit" ]
FROM alpine:${ALPINE_VERSION}
@ -124,13 +125,18 @@ ENV \
# This variable must match your installation's external address.
# Webauthn won't work otherwise.
APP_URL=http://localhost \
# If you want to serve js assets from a CDN (like https://cdn.example.com),
# uncomment the following line and set this var with the CDN url.
# Otherwise, let this line commented.
# ASSET_URL=http://localhost \
#
# Turn this to true if you want your app to react like a demo.
# The Demo mode reset the app content every hours and set a generic demo user.
IS_DEMO_APP=false \
# The log channel defines where your log entries go to.
# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/.
# Several other options exist. You can use 'single' for one big fat error log (not recommended).
# Also available are 'syslog', 'errorlog' and 'stdout' which will log to the system itself.
# 'daily' is the default logging mode giving you 7 daily rotated log files in /storage/logs/.
# Also available are 'errorlog', 'syslog', 'stderr', 'papertrail', 'slack' and a 'stack' channel
# to combine multiple channels into a single one.
LOG_CHANNEL=daily \
# Log level. You can set this from least severe to most severe:
# debug, info, notice, warning, error, critical, alert, emergency

View File

@ -0,0 +1,200 @@
<?php
namespace App\Api\v1\Controllers;
use App\Api\v1\Requests\UserManagerStoreRequest;
use App\Api\v1\Requests\UserManagerPromoteRequest;
use App\Api\v1\Resources\UserManagerResource;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Password;
use Laravel\Passport\TokenRepository;
class UserManagerController extends Controller
{
/**
* Display all users.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function index(Request $request)
{
return UserManagerResource::collection(User::all());
}
/**
* Get a user
*
* @return \App\Api\v1\Resources\UserManagerResource
*/
public function show(User $user)
{
return new UserManagerResource($user);
}
/**
* Reset user's password
*
* @return \Illuminate\Http\JsonResponse
*/
public function resetPassword(Request $request, User $user)
{
Log::info(sprintf('Password reset for User ID #%s requested by User ID #%s', $user->id, $request->user()->id));
$credentials = [
'token' => $this->broker()->createToken($user),
'email' => $user->email,
'password' => $user->password,
];
$response = $this->broker()->reset(
$credentials, function ($user) {
$user->resetPassword();
$user->save();
}
);
if ($response == Password::PASSWORD_RESET) {
Log::info(sprintf('Temporary password set for User ID #%s', $user->id));
$response = $this->broker()->sendResetLink(
['email' => $credentials['email']]
);
}
else {
return response()->json([
'message' => 'bad request',
'reason' => is_string($response) ? __($response) : __('errors.no_pwd_reset_for_this_user_type')
], 400);
}
return $response == Password::RESET_LINK_SENT
? new UserManagerResource($user)
: response()->json([
'message' => 'bad request',
'reason' => __($response)
], 400);
}
/**
* Store a newly created user in storage.
*
* @return \Illuminate\Http\JsonResponse
*/
public function store(UserManagerStoreRequest $request)
{
$validated = $request->validated();
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
Log::info(sprintf('User ID #%s created by user ID #%s', $user->id, $request->user()->id));
if ($validated['is_admin']) {
$user->promoteToAdministrator();
$user->save();
Log::notice(sprintf('User ID #%s set as administrator at creation by user ID #%s', $user->id, $request->user()->id));
}
$user->refresh();
return (new UserManagerResource($user))
->response()
->setStatusCode(201);
}
/**
* Purge user's PATs.
*
* @return \Illuminate\Http\JsonResponse
*/
public function revokePATs(Request $request, User $user, TokenRepository $tokenRepository)
{
Log::info(sprintf('Deletion of all personal access tokens for User ID #%s requested by User ID #%s', $user->id, $request->user()->id));
$tokens = $tokenRepository->forUser($user->getAuthIdentifier());
$tokens->load('client')->filter(function ($token) {
return $token->client->personal_access_client && ! $token->revoked;
})->each(function ($token) {
$token->revoke();
});
Log::info(sprintf('All personal access tokens for User ID #%s have been revoked', $user->id));
return response()->json(null, 204);
}
/**
* Purge user's webauthn credentials.
*
* @return \Illuminate\Http\JsonResponse
*/
public function revokeWebauthnCredentials(Request $request, User $user)
{
Log::info(sprintf('Deletion of all security devices for User ID #%s requested by User ID #%s', $user->id, $request->user()->id));
$user->flushCredentials();
// WebauthnOnly user options need to be reset to prevent impossible login when
// no more registered device exists.
// See #110
if (blank($user->webAuthnCredentials()->WhereEnabled()->get())) {
$user['preferences->useWebauthnOnly'] = false;
$user->save();
Log::notice(sprintf('No more Webauthn credential for user ID #%s, useWebauthnOnly user preference reset to false', $user->id));
}
Log::info(sprintf('All security devices for User ID #%s have been revoked', $user->id));
return response()->json(null, 204);
}
/**
* Remove the specified user from storage.
*
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, User $user)
{
// This will delete the user and all its 2FAs & Groups thanks to the onCascadeDelete constrains.
// Deletion will not be done (and returns False) if the user is the only existing admin (see UserObserver clas)
return $user->delete() === false
? response()->json([
'message' => __('errors.cannot_delete_the_only_admin'),
], 403)
: response()->json(null, 204);
}
/**
* Promote (or demote) a user
*
* @return \App\Api\v1\Resources\UserManagerResource
*/
public function promote(UserManagerPromoteRequest $request, User $user)
{
$user->promoteToAdministrator($request->validated('is_admin'));
$user->save();
Log::info(sprintf('User ID #%s set is_admin=%s for User ID #%s', $request->user()->id, $user->isAdministrator(), $user->id));
return new UserManagerResource($user);
}
/**
* Get the broker to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\PasswordBroker|\Illuminate\Auth\Passwords\PasswordBroker
*/
protected function broker()
{
return Password::broker();
}
}

View File

@ -2,6 +2,7 @@
namespace App\Api\v1\Requests;
use App\Rules\IsValideEmailList;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
@ -24,8 +25,16 @@ class SettingUpdateRequest extends FormRequest
*/
public function rules()
{
return [
'value' => 'required',
$rule = [
'value' => [
'required',
]
];
if ($this->route()?->parameter('settingName') == 'restrictList') {
$rule['value'][] = new IsValideEmailList;
}
return $rule;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Api\v1\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class UserManagerPromoteRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Auth::user()->isAdministrator();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'is_admin' => 'required|boolean',
];
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Api\v1\Requests;
use App\Http\Requests\UserStoreRequest;
use Illuminate\Support\Facades\Auth;
class UserManagerStoreRequest extends UserStoreRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Auth::user()->isAdministrator();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return array_merge(
parent::rules(),
[
'is_admin' => 'required|boolean',
],
);
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace App\Api\v1\Resources;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Laravel\Passport\TokenRepository;
/**
* @property mixed $id
* @property string $name
* @property string $email
* @property string $oauth_provider
* @property \Illuminate\Support\Collection<array-key, mixed> $preferences
* @property string $is_admin
* @property string $last_seen_at
* @property string $created_at
* @property string $updated_at
* @property int|null $twofaccounts_count
*/
class UserManagerResource extends UserResource
{
/**
* The "data" wrapper that should be applied.
*
* @var string|null
*/
public static $wrap = 'info';
/**
* Create a new resource instance.
*
* @param mixed $resource
* @return void
*/
public function __construct($resource)
{
$this->resource = $resource;
$password_reset = null;
// Password reset token
$resetToken = DB::table(config('auth.passwords.users.table'))->where(
'email', $this->resource->getEmailForPasswordReset()
)->first();
if ($resetToken) {
$password_reset = $this->tokenExpired($resetToken->created_at)
? 0
: $resetToken->created_at;
}
// Personal Access Tokens (PATs)
$tokenRepository = App::make(TokenRepository::class);
$tokens = $tokenRepository->forUser($this->resource->getAuthIdentifier());
$PATs_count = $tokens->load('client')->filter(function ($token) {
return $token->client->personal_access_client && ! $token->revoked;
})->count();
$this->with = [
'password_reset' => $password_reset,
'valid_personal_access_tokens' => $PATs_count,
'webauthn_credentials' => $this->resource->webAuthnCredentials()->count()
];
}
/**
* Determine if the token has expired.
*
* @param string $createdAt
* @return bool
*/
protected function tokenExpired($createdAt)
{
// See Illuminate\Auth\Passwords\DatabaseTokenRepository
return Carbon::parse($createdAt)->addSeconds(config('auth.passwords.users.expires', 60)*60)->isPast();
}
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return array_merge(
parent::toArray($request),
[
'twofaccounts_count' => is_null($this->twofaccounts_count) ? 0 : $this->twofaccounts_count,
'last_seen_at' => Carbon::parse($this->last_seen_at)->locale(App::getLocale())->diffForHumans(),
'created_at' => Carbon::parse($this->created_at)->locale(App::getLocale())->diffForHumans(),
]
);
}
}

View File

@ -60,12 +60,12 @@ trait ResetTrait
protected function flushDB() : void
{
// Reset the db
DB::table('password_resets')->delete();
DB::table(config('auth.passwords.users.table'))->delete();
DB::table('oauth_access_tokens')->delete();
DB::table('oauth_personal_access_clients')->delete();
DB::table('oauth_refresh_tokens')->delete();
DB::table('webauthn_credentials')->delete();
DB::table('webauthn_recoveries')->delete();
DB::table(config('auth.passwords.webauthn.table'))->delete();
DB::table('twofaccounts')->delete();
DB::table('groups')->delete();
DB::table('users')->delete();

View File

@ -45,7 +45,8 @@ class Handler extends ExceptionHandler
$this->renderable(function (InvalidQrCodeException $exception, $request) {
return response()->json([
'message' => 'not a valid QR code', ], 400);
'message' => $exception->getMessage(),
], 400);
});
$this->renderable(function (InvalidSecretException $exception, $request) {

View File

@ -63,7 +63,7 @@ class RemoteUserProvider implements UserProvider
Log::info(sprintf('Remote user %s created with email address %s', var_export($user->name, true), var_export($user->email, true)));
if (User::count() === 1) {
$user->is_admin = true;
$user->promoteToAdministrator();
$user->save();
}
} else {

View File

@ -107,16 +107,21 @@ class LoginController extends Controller
{
$this->clearLoginAttempts($request);
$name = $this->guard()->user()?->name;
/**
* @var \App\Models\User|null
*/
$user = $this->guard()->user();
$name = $user?->name;
$this->authenticated($request, $this->guard()->user());
return response()->json([
'message' => 'authenticated',
'id' => $user->id,
'name' => $name,
'email' => $this->guard()->user()->email,
'preferences' => $this->guard()->user()->preferences,
'is_admin' => $this->guard()->user()->is_admin,
'email' => $user->email,
'preferences' => $user->preferences,
'is_admin' => $user->isAdministrator(),
], Response::HTTP_OK);
}

View File

@ -43,13 +43,17 @@ class RegisterController extends Controller
event(new Registered($user = $this->create($validated)));
$this->guard()->login($user);
/**
* @var \App\Models\User|null
*/
$user = $this->guard()->user();
return response()->json([
'message' => 'account created',
'name' => $user->name,
'email' => $user->email,
'preferences' => $this->guard()->user()->preferences,
'is_admin' => $this->guard()->user()->is_admin,
'preferences' => $user->preferences,
'is_admin' => $user->isAdministrator(),
], 201);
}
@ -69,7 +73,7 @@ class RegisterController extends Controller
Log::info(sprintf('User ID #%s created', $user->id));
if (User::count() == 1) {
$user->is_admin = true;
$user->promoteToAdministrator();
$user->save();
Log::notice(sprintf('User ID #%s set as administrator', $user->id));
}

View File

@ -56,8 +56,8 @@ class SocialiteController extends Controller
if (User::where('email', $socialiteEmail)->exists()) {
return redirect('/error?err=sso_email_already_used');
} elseif (User::count() === 0) {
$user->is_admin = true;
} elseif (Settings::get('disableRegistration')) {
$user->promoteToAdministrator();
} elseif (Settings::get('disableRegistration') && ! Settings::get('keepSsoRegistrationEnabled')) {
return redirect('/error?err=sso_no_register');
}
$user->password = bcrypt(Str::random());

View File

@ -24,6 +24,8 @@ class UserController extends Controller
$user = $request->user();
$validated = $request->validated();
$this->authorize('update', $user);
if (config('auth.defaults.guard') === 'reverse-proxy-guard' || $user->oauth_provider) {
Log::notice('Account update rejected: reverse-proxy-guard enabled or account from external sso provider');
@ -57,37 +59,16 @@ class UserController extends Controller
$validated = $request->validated();
$user = Auth::user();
Log::info(sprintf('Deletion of user ID #%s requested', $user->id));
if ($user->is_admin && User::admins()->count() == 1) {
return response()->json(['message' => __('errors.cannot_delete_the_only_admin')], 400);
}
if (! Hash::check($validated['password'], Auth::user()->password)) {
return response()->json(['message' => __('errors.wrong_current_password')], 400);
}
try {
DB::transaction(function () use ($user) {
DB::table('twofaccounts')->where('user_id', $user->id)->delete();
DB::table('groups')->where('user_id', $user->id)->delete();
DB::table('webauthn_credentials')->where('authenticatable_id', $user->id)->delete();
DB::table('webauthn_recoveries')->where('email', $user->email)->delete();
DB::table('oauth_access_tokens')->where('user_id', $user->id)->delete();
DB::table('password_resets')->where('email', $user->email)->delete();
DB::table('users')->where('id', $user->id)->delete();
});
}
// @codeCoverageIgnoreStart
catch (\Throwable $e) {
Log::error(sprintf('Deletion of user ID #%s failed, transaction has been rolled-back', $user->id));
return response()->json(['message' => __('errors.user_deletion_failed')], 400);
}
// @codeCoverageIgnoreEnd
Log::info(sprintf('User ID #%s deleted', $user->id));
return response()->json(null, 204);
// This will delete the user and all its 2FAs & Groups thanks to the onCascadeDelete constrains.
// Deletion will not be done (and returns False) if the user is the only existing admin (see UserObserver clas)
return $user->delete() === false
? response()->json([
'message' => __('errors.cannot_delete_the_only_admin'),
], 400)
: response()->json(null, 204);
}
}

View File

@ -147,8 +147,11 @@ class WebAuthnLoginController extends Controller
return response()->json([
'message' => 'authenticated',
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'preferences' => $user->preferences,
'is_admin' => $user->isAdministrator(),
], Response::HTTP_OK);
}

View File

@ -56,7 +56,7 @@ class WebAuthnManageController extends Controller
if (blank($user->webAuthnCredentials()->WhereEnabled()->get())) {
$request->user()->preferences['useWebauthnOnly'] = false;
$request->user()->save();
Log::notice(sprintf('No more Webauthn credential for user ID #%s, user Webauthn options reset to default', $user->id));
Log::notice(sprintf('No more Webauthn credential for user ID #%s, useWebauthnOnly user preference reset to false', $user->id));
}
Log::info(sprintf('User ID #%s revoked a security device', $user->id));

View File

@ -50,7 +50,7 @@ class WebAuthnRecoveryController extends Controller
if ($this->shouldRevokeAllCredentials($request)) {
$user->flushCredentials();
}
$user->preferences['useWebauthnOnly'] = false;
$user['preferences->useWebauthnOnly'] = false;
$user->save();
Log::notice(sprintf('Legacy login restored for user ID #%s', $user->id));
} else {

View File

@ -3,10 +3,13 @@
namespace App\Http\Controllers;
use App\Facades\Settings;
use App\Notifications\TestEmailSettingNotification;
use App\Services\ReleaseRadarService;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class SystemController extends Controller
{
@ -34,27 +37,15 @@ class SystemController extends Controller
$infos['common']['Operating system'] = PHP_OS;
$infos['common']['interface'] = PHP_SAPI;
// Auth & Security infos
if (! is_null($request->user())) {
$infos['common']['Auth guard'] = config('auth.defaults.guard');
if ($infos['common']['Auth guard'] === 'reverse-proxy-guard') {
$infos['common']['Auth proxy logout url'] = config('2fauth.config.proxyLogoutUrl');
$infos['common']['Auth proxy header for user'] = config('auth.auth_proxy_headers.user');
$infos['common']['Auth proxy header for email'] = config('auth.auth_proxy_headers.email');
}
$infos['common']['webauthn user verification'] = config('webauthn.user_verification');
$infos['common']['Trusted proxies'] = config('2fauth.config.trustedProxies') ?: 'none';
// Admin settings
if ($request->user()->is_admin == true) {
$infos['admin_settings']['useEncryption'] = Settings::get('useEncryption');
$infos['admin_settings']['lastRadarScan'] = Carbon::parse(Settings::get('lastRadarScan'))->format('Y-m-d H:i:s');
$infos['admin_settings']['checkForUpdate'] = Settings::get('checkForUpdate');
}
}
// User info
if ($request->user()) {
$infos['user_preferences'] = $request->user()->preferences->toArray();
$infos['common']['Auth guard'] = config('auth.defaults.guard');
if ($infos['common']['Auth guard'] === 'reverse-proxy-guard') {
$infos['common']['Auth proxy logout url'] = config('2fauth.config.proxyLogoutUrl');
$infos['common']['Auth proxy header for user'] = config('auth.auth_proxy_headers.user');
$infos['common']['Auth proxy header for email'] = config('auth.auth_proxy_headers.email');
}
$infos['common']['webauthn user verification'] = config('webauthn.user_verification');
$infos['common']['Trusted proxies'] = config('2fauth.config.trustedProxies') ?: 'none';
$infos['common']['lastRadarScan'] = Carbon::parse(Settings::get('lastRadarScan'))->format('Y-m-d H:i:s');
return response()->json($infos);
}
@ -70,4 +61,54 @@ class SystemController extends Controller
return response()->json(['newRelease' => $release]);
}
/**
* Send a test email
*
* @return \Illuminate\Http\JsonResponse
*/
public function testEmail(Request $request)
{
try {
$request->user()->notify(new TestEmailSettingNotification());
} catch (\Throwable $th) {
Log::error($th->getMessage());
}
return response()->json(['message' => 'Ok']);
}
/**
* Clears all app caches and rebuild them
*
* @return \Illuminate\Http\JsonResponse
*/
public function optimize(Request $request)
{
$configCode = Artisan::call('config:cache');
$routeCode = Artisan::call('route:cache');
$eventCode = Artisan::call('event:cache');
$viewCode = Artisan::call('view:cache');
return response()->json([
'config-exit-code' => $configCode,
'route-exit-code' => $routeCode,
'event-exit-code' => $eventCode,
'view-exit-code' => $viewCode,
], 200);
}
/**
* Clears application cache
*
* @return \Illuminate\Http\JsonResponse
*/
public function clear(Request $request)
{
$exitCode = Artisan::call('optimize:clear');
return response()->json(['exit-code' => $exitCode], 200);
}
}

View File

@ -48,8 +48,8 @@ class Kernel extends HttpKernel
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\Authenticate::class,
\App\Http\Middleware\LogUserLastSeen::class,
\App\Http\Middleware\KickOutInactiveUser::class,
\App\Http\Middleware\LogUserLastSeen::class,
\App\Http\Middleware\SetLanguage::class,
\App\Http\Middleware\CustomCreateFreshApiToken::class,
],

View File

@ -16,7 +16,7 @@ class AdminOnly
*/
public function handle($request, Closure $next)
{
if (! Auth::user()->is_admin) {
if (! Auth::user()->isAdministrator()) {
throw new AuthorizationException;
}

View File

@ -38,11 +38,8 @@ class KickOutInactiveUser
if ($kickUserAfterXSecond > 0 && $inactiveFor > $kickUserAfterXSecond) {
$user->last_seen_at = $now->format('Y-m-d H:i:s');
$user->save();
Log::info(sprintf('User ID #%s detected as inactive, authentication rejected', $user->id));
if (method_exists('Illuminate\Support\Facades\Auth', 'logout')) {
Auth::logout();
}
Auth::guard('web-guard')->logout();
return response()->json(['message' => 'inactivity detected'], Response::HTTP_I_AM_A_TEAPOT);
}

View File

@ -2,6 +2,7 @@
namespace App\Http\Requests;
use App\Rules\ComplyWithEmailRestrictionPolicy;
use Illuminate\Foundation\Http\FormRequest;
class UserStoreRequest extends FormRequest
@ -24,8 +25,15 @@ class UserStoreRequest extends FormRequest
public function rules()
{
return [
'name' => 'unique:App\Models\User,name|required|string|max:191',
'email' => 'unique:App\Models\User,email|required|string|email|max:191',
'name' => 'unique:App\Models\User,name|required|string|max:191',
'email' => [
'unique:App\Models\User,email',
'required',
'string',
'email',
'max:191',
new ComplyWithEmailRestrictionPolicy,
],
'password' => 'required|string|min:8|confirmed',
];
}

View File

@ -2,6 +2,7 @@
namespace App\Http\Requests;
use App\Rules\ComplyWithEmailRestrictionPolicy;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
@ -37,6 +38,7 @@ class UserUpdateRequest extends FormRequest
'email',
'max:191',
Rule::unique('users')->ignore($this->user()->id),
new ComplyWithEmailRestrictionPolicy,
],
'password' => 'required',
];

View File

@ -0,0 +1,35 @@
<?php
namespace App\Listeners;
use Illuminate\Notifications\Events\NotificationSent;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class LogNotification
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @return void
*/
public function handle(NotificationSent $event)
{
// $event->channel
// $event->notifiable
// $event->notification
// $event->response
Log::info(sprintf('Notification of type %s sent via channel %s to user ID #%s', get_class($event->notification), $event->channel, $event->notifiable->id));
}
}

View File

@ -52,6 +52,7 @@ class Group extends Model
*/
protected $casts = [
'twofaccounts_count' => 'integer',
'user_id' => 'integer',
];
/**

View File

@ -145,7 +145,9 @@ class TwoFAccount extends Model implements Sortable
*
* @var array<string, string>
*/
protected $casts = [];
protected $casts = [
'user_id' => 'integer',
];
/**
* The event map for the model.

View File

@ -3,11 +3,14 @@
namespace App\Models;
use App\Models\Traits\WebAuthnManageCredentials;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Laragear\WebAuthn\WebAuthnAuthentication;
use Laravel\Passport\HasApiTokens;
@ -86,6 +89,39 @@ class User extends Authenticatable implements WebAuthnAuthenticatable
return $query->where('is_admin', true);
}
/**
* Determine if the user is an administrator.
*
* @return boolean
*/
public function isAdministrator()
{
return $this->is_admin;
}
/**
* Grant administrator permissions to the user.
*
* @param bool $promote
* @return void
*/
public function promoteToAdministrator(bool $promote = true)
{
$this->is_admin = $promote;
}
/**
* Reset user password with a 12 chars random string.
*
* @return void
*/
public function resetPassword()
{
$this->password = Hash::make(Str::password(12));
event(new PasswordReset($this));
}
/**
* Send the password reset notification.
*
@ -95,8 +131,6 @@ class User extends Authenticatable implements WebAuthnAuthenticatable
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPassword($token));
Log::info(sprintf('Password reset token sent to user id "%s', $this->id));
}
/**

View File

@ -0,0 +1,64 @@
<?php
namespace App\Notifications;
use Closure;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Lang;
class TestEmailSettingNotification extends Notification
{
// /**
// * The callback that should be used to create the reset password URL.
// *
// * @var \Closure|null
// */
// protected static ?Closure $createUrlCallback;
// /**
// * The callback that should be used to build the mail message.
// *
// * @var \Closure|null
// */
// protected static ?Closure $toMailCallback;
/**
* TestEmailSettingNotification constructor.
*/
public function __construct()
{
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->subject(Lang::get('notifications.test_email_settings.subject'))
->greeting(Lang::get('notifications.hello'))
->line(
Lang::get('notifications.test_email_settings.reason')
)
->line(
Lang::get('notifications.test_email_settings.success')
);
}
}

View File

@ -0,0 +1,97 @@
<?php
namespace App\Observers;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\Storage;
class UserObserver
{
/**
* Handle the User "created" event.
*/
public function created(User $user): void
{
//
}
/**
* Handle the User "updated" event.
*/
public function updated(User $user): void
{
//
}
/**
* Handle the User "deleting" event.
*/
public function deleting(User $user): bool
{
Log::info(sprintf('Deletion of User ID #%s requested by User ID #%s', $user->id, Auth::user()->id ?? 'unknown'));
// Prevent deletion of the only administrator
$isLastAdmin = $user->isAdministrator() && User::admins()->count() == 1;
if ($isLastAdmin) {
Log::notice(sprintf('Deletion of user ID #%s refused, cannot delete the only administrator', $user->id));
return false;
}
// Deleting user's twofaccounts icon
$iconPathes = $user->twofaccounts->filter(function ($twofaccount, $key) {
return $twofaccount->icon;
})->map(function ($twofaccount, $key) {
return $twofaccount->icon;
});
Storage::disk('icons')->delete($iconPathes->toArray());
return true;
}
/**
* Handle the User "deleted" event.
*/
public function deleted(User $user): void
{
// DB has cascade delete enabled to flush 2FA and Groups but,
// for an unknown reason, SQLite refuses to delete these related.
// (despite DB_FOREIGN_KEYS=true which is supposed to enable it)
// So it ends with a direct db delete for SQLite...
if (DB::getDriverName() === 'sqlite') {
DB::table('twofaccounts')->where('user_id', $user->id)->delete();
DB::table('groups')->where('user_id', $user->id)->delete();
}
// We flush webauthn credentials & tokens
Password::broker('webauthn')->deleteToken($user);
$user->flushCredentials();
// And Passport tokens
Password::broker()->deleteToken($user);
DB::table('oauth_access_tokens')->where('user_id', $user->id)->delete();
Log::info(sprintf('User ID #%s and all user traces deleted', $user->id));
}
/**
* Handle the User "restored" event.
*/
public function restored(User $user): void
{
//
}
/**
* Handle the User "force deleted" event.
*/
public function forceDeleted(User $user): void
{
//
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Policies;
use App\Models\User;
trait SelfTrait
{
/**
* Ownership of single item condition
*
* @return bool
*/
protected function isHimself(User $user, mixed $item)
{
return $user->id === $item->id;
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace App\Policies;
use App\Models\User;
use Illuminate\Support\Facades\Log;
class UserPolicy
{
use SelfTrait;
/**
* Perform pre-authorization checks.
*/
public function before(User $user, string $ability): bool|null
{
if ($user->isAdministrator()) {
return true;
}
return null;
}
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return false;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, User $model): bool
{
$can = $this->isHimself($user, $model);
if (! $can) {
Log::notice(sprintf('User ID #%s cannot view users other than himself)', $user->id));
}
return $can;
}
/**
* Determine whether the user can create models.
*/
public function create(?User $user): bool
{
return true;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, User $model): bool
{
$can = $this->isHimself($user, $model);
if (! $can) {
Log::notice(sprintf('User ID #%s cannot update users other than himself)', $user->id));
}
return $can;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, User $model): bool
{
$can = $this->isHimself($user, $model);
if (! $can) {
Log::notice(sprintf('User ID #%s cannot delete users other than himself)', $user->id));
}
return $can;
}
}

View File

@ -29,8 +29,6 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot()
{
Blade::withoutComponentTags();
// Limited to 191 to prevent index length issue with MyISAM and utf8mb4_unicode_ci
// when using WAMP (WAMP uses MyISAM as default engine in place of INNOdb)
Schema::defaultStringLength(191);

View File

@ -6,8 +6,10 @@ use App\Extensions\RemoteUserProvider;
use App\Extensions\WebauthnCredentialBroker;
use App\Models\Group;
use App\Models\TwoFAccount;
use App\Models\User;
use App\Policies\GroupPolicy;
use App\Policies\TwoFAccountPolicy;
use App\Policies\UserPolicy;
use App\Services\Auth\ReverseProxyGuard;
use Illuminate\Auth\Passwords\DatabaseTokenRepository;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
@ -25,6 +27,7 @@ class AuthServiceProvider extends ServiceProvider
protected $policies = [
TwoFAccount::class => TwoFAccountPolicy::class,
Group::class => GroupPolicy::class,
User::class => UserPolicy::class,
];
/**

View File

@ -8,12 +8,16 @@ use App\Events\ScanForNewReleaseCalled;
use App\Events\TwoFAccountDeleted;
use App\Listeners\CleanIconStorage;
use App\Listeners\DissociateTwofaccountFromGroup;
use App\Listeners\LogNotification;
use App\Listeners\RegisterOpenId;
use App\Listeners\ReleaseRadar;
use App\Listeners\ResetUsersPreference;
use App\Models\User;
use App\Observers\UserObserver;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Notifications\Events\NotificationSent;
use SocialiteProviders\Manager\SocialiteWasCalled;
class EventServiceProvider extends ServiceProvider
@ -42,6 +46,18 @@ class EventServiceProvider extends ServiceProvider
SocialiteWasCalled::class => [
RegisterOpenId::class,
],
NotificationSent::class => [
LogNotification::class,
],
];
/**
* The model observers for your application.
*
* @var array<string, string|object|array<int, string|object>>
*/
protected $observers = [
User::class => [UserObserver::class],
];
/**

View File

@ -0,0 +1,42 @@
<?php
namespace App\Rules;
use App\Facades\Settings;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class ComplyWithEmailRestrictionPolicy implements ValidationRule
{
/**
* Run the validation rule.
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$list = Settings::get('restrictList');
$regex = Settings::get('restrictRule');
$validatesFilter = true;
$validatesRegex = true;
if (Settings::get('restrictRegistration') == true) {
if ($list && ! in_array($value, explode('|', $list))) {
$validatesFilter = false;
}
if ($regex && ! preg_match('/' . $regex . '/', $value)) {
$validatesRegex = false;
}
if ($list && $regex) {
if (! $validatesFilter && ! $validatesRegex) {
$fail('validation.custom.email.ComplyWithEmailRestrictionPolicy')->translate();
}
}
else {
if (! $validatesFilter || ! $validatesRegex) {
$fail('validation.custom.email.ComplyWithEmailRestrictionPolicy')->translate();
}
}
}
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Validator;
class IsValideEmailList implements ValidationRule
{
/**
* Run the validation rule.
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$emails = explode('|', $value);
$pass = Validator::make(
$emails,
[
'*' => 'email',
]
)->passes();
if (! $pass) {
$fail('validation.custom.email.IsValidEmailList')->translate();
}
}
}

View File

@ -52,8 +52,8 @@ class AegisMigrator extends Migrator
$parameters['service'] = $otp_parameters['issuer'];
$parameters['account'] = $otp_parameters['name'] ?? $parameters['service'];
$parameters['secret'] = $this->padToValidBase32Secret($otp_parameters['info']['secret']);
$parameters['algorithm'] = $otp_parameters['info']['algo'];
$parameters['digits'] = $otp_parameters['info']['digits'];
$parameters['algorithm'] = $otp_parameters['info']['algo'] ?? null;
$parameters['digits'] = $otp_parameters['info']['digits'] ?? null;
$parameters['counter'] = $otp_parameters['info']['counter'] ?? null;
$parameters['period'] = $otp_parameters['info']['period'] ?? null;

View File

@ -86,12 +86,12 @@ class TwoFASMigrator extends Migrator
$parameters['service'] = $otp_parameters['name'];
$parameters['account'] = $otp_parameters['otp']['account'] ?? $parameters['service'];
$parameters['secret'] = $this->padToValidBase32Secret($otp_parameters['secret']);
$parameters['algorithm'] = $otp_parameters['otp']['algorithm'];
$parameters['digits'] = $otp_parameters['otp']['digits'];
$parameters['counter'] = strtolower($parameters['otp_type']) === 'hotp' && $otp_parameters['otp']['counter'] > 0
$parameters['algorithm'] = $otp_parameters['otp']['algorithm'] ?? null;
$parameters['digits'] = $otp_parameters['otp']['digits'] ?? null;
$parameters['counter'] = strtolower($parameters['otp_type']) === 'hotp' && Arr::has($otp_parameters['otp'], 'counter')
? $otp_parameters['otp']['counter']
: null;
$parameters['period'] = strtolower($parameters['otp_type']) === 'totp' && $otp_parameters['otp']['period'] > 0
$parameters['period'] = strtolower($parameters['otp_type']) === 'totp' && Arr::has($otp_parameters['otp'], 'period')
? $otp_parameters['otp']['period']
: null;

View File

@ -55,8 +55,8 @@ class TwoFAuthMigrator extends Migrator
$parameters['service'] = $otp_parameters['service'];
$parameters['account'] = $otp_parameters['account'];
$parameters['secret'] = $this->padToValidBase32Secret($otp_parameters['secret']);
$parameters['algorithm'] = $otp_parameters['algorithm'];
$parameters['digits'] = $otp_parameters['digits'];
$parameters['algorithm'] = $otp_parameters['algorithm'] ?? null;
$parameters['digits'] = $otp_parameters['digits'] ?? null;
$parameters['legacy_uri'] = $otp_parameters['legacy_uri'];
$parameters['counter'] = strtolower($parameters['otp_type']) === 'hotp' && $otp_parameters['counter'] > 0
? $otp_parameters['counter']

View File

@ -5,6 +5,9 @@ namespace App\Services;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use Illuminate\Support\Facades\Log;
use Zxing\ChecksumException;
use Zxing\FormatException;
use Zxing\NotFoundException;
use Zxing\QrReader;
class QrCodeService
@ -37,12 +40,39 @@ class QrCodeService
public static function decode(\Illuminate\Http\UploadedFile $file)
{
$qrcode = new QrReader($file->get(), QrReader::SOURCE_TYPE_BLOB);
$data = urldecode($qrcode->text());
$text = $qrcode->text();
if (! $data) {
throw new \App\Exceptions\InvalidQrCodeException;
if (! $text) {
$text = $qrcode->text([
'TRY_HARDER' => true,
'NR_ALLOW_SKIP_ROWS' => 0,
]);
}
// At this point, if we do not have a text, QR code cannot be detected or decoded
// so we check the error to provide the user a relevant error message
if (! $text) {
switch (get_class($qrcode->getError())) {
case NotFoundException::class:
throw new \App\Exceptions\InvalidQrCodeException(__('errors.cannot_detect_qrcode_in_image'));
break;
case FormatException::class:
throw new \App\Exceptions\InvalidQrCodeException(__('errors.cannot_decode_detected_qrcode'));
break;
case ChecksumException::class:
throw new \App\Exceptions\InvalidQrCodeException(__('errors.qrcode_has_invalid_checksum'));
break;
default:
throw new \App\Exceptions\InvalidQrCodeException(__('errors.no_readable_qrcode'));
break;
}
}
$data = urldecode($qrcode->text());
Log::info('QR code decoded');
return $data;

View File

@ -1,5 +1,90 @@
# Change log
## [5.1.1] - 2024-03-21
### Fixed
- [issue #326](https://github.com/Bubka/2FAuth/issues/326) Admin panel not working when using security device
- [issue #327](https://github.com/Bubka/2FAuth/issues/327) "Keep SSO registration enabled" is not saved
## [5.1.0] - 2024-03-08
Hey Administrators, this release is for you, a brand new Admin Panel has arrived.
With this dedicated space, you will be able to manage admin settings previously located in the User Options view (like encryption, version check, registration). Some new settings are available to better control registration (email restrictions and self-ruling SSO) and two new features are coming: Email Configuration Testing and Cache Clearing.
But the real newness is the user management. All registered accounts are now searchable, the administrator role can be granted to any user, user access (password, personal token, security key/passphrase) can be revoked and you may also delete existing users or even create new ones.
Note that the 2FAuth API has been updated with the new paths related to user management.
### Added
- A user preference to clear search results after copying a code ([#300](https://github.com/Bubka/2FAuth/issues/300)).
- A user preference to return to default group after copying a code ([#300](https://github.com/Bubka/2FAuth/issues/300)).
- The ability to submit a migration text directly in the Import view besides TXT files & QR codes loading ([#288](https://github.com/Bubka/2FAuth/issues/288)).
- An administrator setting to restrict registration to a limited range of email addresses ([#250](https://github.com/Bubka/2FAuth/issues/250)).
- An administrator setting to keep user registration via SSO enabled ([#317](https://github.com/Bubka/2FAuth/issues/317)).
- A test email feature to ensure email sending works as expected ([#307](https://github.com/Bubka/2FAuth/issues/307)).
- A Clear cache feature to... clear the cache, but from the browser ([#316](https://github.com/Bubka/2FAuth/issues/316)).
- Hindi translation, thanks to [@saxenas](https://crowdin.com/profile/saxenas)
### Changed
- User preferences & Environment variables have been moved from the About view to the new Administration panel ([#303](https://github.com/Bubka/2FAuth/issues/303)).
- Spaces are now removed from the Secret when filling out the Advanced form ([#311](https://github.com/Bubka/2FAuth/issues/311)).
### Fixed
- [issue #303](https://github.com/Bubka/2FAuth/issues/303) "Already authenticated" error message
- [issue #305](https://github.com/Bubka/2FAuth/issues/305) 403 Forbidden {message: "unauthorized"}
- [issue #315](https://github.com/Bubka/2FAuth/issues/315) "Check now" button is untranslatable
- [issue #320](https://github.com/Bubka/2FAuth/issues/320) app/Policies/OwnershipTrait contains a bug, i think
### API [1.3.0]
- `/api/v1/users` paths added to manage registered users
- `oauth_provider` property to the response body of `/api/v1/user` GET path
## [5.0.4] - 2024-02-23
### Added
- Japanese translation, thanks to [@yheuhtozr](https://crowdin.com/profile/yheuhtozr)
### Fixed
- [issue #284](https://github.com/Bubka/2FAuth/issues/284) Blank screen with version 5.0.3
- [issue #296](https://github.com/Bubka/2FAuth/issues/296) WARN Command cancelled (env=production breaks docker entrypoint)
- [issue #298](https://github.com/Bubka/2FAuth/issues/298) WebAuthn account recovery and password recovery doesn't work. Email template broken
- [issue #299](https://github.com/Bubka/2FAuth/issues/299) OID redirect behind reverse proxy
## [5.0.3] - 2024-01-19
⚠️ For everyone experiencing a blank screen after updating to v5.*, please set the `ASSET_URL` env variable to the same value as `APP_URL`.
### Added
- The `ASSET_URL` now appears in the .env.example variables next to `APP_URL`
### Fixed
- [issue #273](https://github.com/Bubka/2FAuth/issues/273) Unable to automatically paste email and password in login page
- [issue #276](https://github.com/Bubka/2FAuth/issues/276) Camera does not work
- [issue #277](https://github.com/Bubka/2FAuth/issues/277) Import 2FAS
- [issue #279](https://github.com/Bubka/2FAuth/issues/279) Cannot use stdout LOG_CHANNEL anymore
## [5.0.2] - 2023-12-29
### Fixed
- [issue #265](https://github.com/Bubka/2FAuth/issues/265) Version 5.0.1 doesn't display colored countdown segments
## [5.0.1] - 2023-12-29
### Fixed
- [issue #262](https://github.com/Bubka/2FAuth/issues/262) Missing custom base url support
## [5.0.0] - 2023-12-15
### 2FAuth v5, the not-so-major release
@ -13,7 +98,7 @@ Yes, SSO.
Not so bad, right ?
The feature, bootstrapped by [@indyKoning](https://github.com/indykoning) with an OpenID provider, has been completed and now provides a Github provider as well. If you need help, the [doc site](https://docs.2fauth.app/security/authenticattion/sso/) has been updated to guide you through the setup process.
The feature, bootstrapped by [@indyKoning](https://github.com/indykoning) with an OpenID provider, has been completed and now provides a Github provider as well. I plan to add more providers, tell me in the discussion which ones you would like to see. If you need help, the [docs site](https://docs.2fauth.app/security/authentication/sso/) has been updated to guide you through the setup process.
v5 also comes with the following.
@ -38,6 +123,11 @@ v5 also comes with the following.
- [issue #253](https://github.com/Bubka/2FAuth/issues/253) 2FAs exports cannot be imported
### API [1.2.0]
- `/api/v1/user` GET path added
- `ids` and `withOtp` query parameters added to the `/api/v1/twofaccounts` GET path
---
**Full Changelog**: [v4.2.4...v5.0.0](https://github.com/Bubka/2FAuth/compare/v4.2.4...v5.0.0)
@ -81,7 +171,7 @@ v5 also comes with the following.
### Changed
- Navigation with the __Back__ and __Close__ buttons is now fully consistent with their labeling, even when browsing back through successive views using those buttons.
- Navigation with the **Back** and **Close** buttons is now fully consistent with their labeling, even when browsing back through successive views using those buttons.
- The length of the email submitted during registration is now limited to 191 characters ([#214](https://github.com/Bubka/2FAuth/issues/214)).
- Upgrade to Laravel 10

View File

@ -41,10 +41,10 @@
"require-dev": {
"barryvdh/laravel-ide-helper": "^2.13",
"fakerphp/faker": "^1.21",
"larastan/larastan": "^2.0",
"laravel/pint": "^1.6",
"mockery/mockery": "^1.5",
"nunomaduro/collision": "^7.0",
"nunomaduro/larastan": "^2.5",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.1",
"spatie/laravel-ignition": "^2.0"

298
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "8ab04001cb3cb872bf0848236af3fc20",
"content-hash": "15022c60c2ef59e0821ae0064f477e50",
"packages": [
{
"name": "brick/math",
@ -2005,16 +2005,16 @@
},
{
"name": "laravel/framework",
"version": "v10.37.3",
"version": "v10.38.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "996375dd61f8c6e4ac262b57ed485655d71fcbdc"
"reference": "ced4689f495213e9d23995b36098f12a802cc15b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/996375dd61f8c6e4ac262b57ed485655d71fcbdc",
"reference": "996375dd61f8c6e4ac262b57ed485655d71fcbdc",
"url": "https://api.github.com/repos/laravel/framework/zipball/ced4689f495213e9d23995b36098f12a802cc15b",
"reference": "ced4689f495213e9d23995b36098f12a802cc15b",
"shasum": ""
},
"require": {
@ -2060,6 +2060,8 @@
"voku/portable-ascii": "^2.0"
},
"conflict": {
"carbonphp/carbon-doctrine-types": ">=3.0",
"doctrine/dbal": ">=4.0",
"tightenco/collect": "<5.5.33"
},
"provide": {
@ -2171,6 +2173,7 @@
"files": [
"src/Illuminate/Collections/helpers.php",
"src/Illuminate/Events/functions.php",
"src/Illuminate/Filesystem/functions.php",
"src/Illuminate/Foundation/helpers.php",
"src/Illuminate/Support/helpers.php"
],
@ -2203,7 +2206,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2023-12-13T20:10:58+00:00"
"time": "2023-12-20T14:52:12+00:00"
},
{
"name": "laravel/passport",
@ -2541,16 +2544,16 @@
},
{
"name": "laravel/ui",
"version": "v4.2.3",
"version": "v4.3.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/ui.git",
"reference": "eb532ea096ca1c0298c87c19233daf011fda743a"
"reference": "d166e09cdcb2e23836f694774cba77a32edb6007"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/ui/zipball/eb532ea096ca1c0298c87c19233daf011fda743a",
"reference": "eb532ea096ca1c0298c87c19233daf011fda743a",
"url": "https://api.github.com/repos/laravel/ui/zipball/d166e09cdcb2e23836f694774cba77a32edb6007",
"reference": "d166e09cdcb2e23836f694774cba77a32edb6007",
"shasum": ""
},
"require": {
@ -2597,9 +2600,9 @@
"ui"
],
"support": {
"source": "https://github.com/laravel/ui/tree/v4.2.3"
"source": "https://github.com/laravel/ui/tree/v4.3.0"
},
"time": "2023-11-23T14:44:22+00:00"
"time": "2023-12-19T14:46:09+00:00"
},
{
"name": "lcobucci/clock",
@ -8553,6 +8556,107 @@
},
"time": "2020-07-09T08:09:16+00:00"
},
{
"name": "larastan/larastan",
"version": "v2.7.0",
"source": {
"type": "git",
"url": "https://github.com/larastan/larastan.git",
"reference": "a2610d46b9999cf558d9900ccb641962d1442f55"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/larastan/larastan/zipball/a2610d46b9999cf558d9900ccb641962d1442f55",
"reference": "a2610d46b9999cf558d9900ccb641962d1442f55",
"shasum": ""
},
"require": {
"ext-json": "*",
"illuminate/console": "^9.52.16 || ^10.28.0",
"illuminate/container": "^9.52.16 || ^10.28.0",
"illuminate/contracts": "^9.52.16 || ^10.28.0",
"illuminate/database": "^9.52.16 || ^10.28.0",
"illuminate/http": "^9.52.16 || ^10.28.0",
"illuminate/pipeline": "^9.52.16 || ^10.28.0",
"illuminate/support": "^9.52.16 || ^10.28.0",
"php": "^8.0.2",
"phpmyadmin/sql-parser": "^5.8.2",
"phpstan/phpstan": "^1.10.41"
},
"require-dev": {
"nikic/php-parser": "^4.17.1",
"orchestra/canvas": "^7.11.1 || ^8.11.0",
"orchestra/testbench": "^7.33.0 || ^8.13.0",
"phpunit/phpunit": "^9.6.13"
},
"suggest": {
"orchestra/testbench": "Using Larastan for analysing a package needs Testbench"
},
"type": "phpstan-extension",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
},
"phpstan": {
"includes": [
"extension.neon"
]
}
},
"autoload": {
"psr-4": {
"Larastan\\Larastan\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Can Vural",
"email": "can9119@gmail.com"
},
{
"name": "Nuno Maduro",
"email": "enunomaduro@gmail.com"
}
],
"description": "Larastan - Discover bugs in your code without running it. A phpstan/phpstan wrapper for Laravel",
"keywords": [
"PHPStan",
"code analyse",
"code analysis",
"larastan",
"laravel",
"package",
"php",
"static analysis"
],
"support": {
"issues": "https://github.com/larastan/larastan/issues",
"source": "https://github.com/larastan/larastan/tree/v2.7.0"
},
"funding": [
{
"url": "https://www.paypal.com/paypalme/enunomaduro",
"type": "custom"
},
{
"url": "https://github.com/canvural",
"type": "github"
},
{
"url": "https://github.com/nunomaduro",
"type": "github"
},
{
"url": "https://www.patreon.com/nunomaduro",
"type": "patreon"
}
],
"time": "2023-12-04T19:21:38+00:00"
},
{
"name": "laravel/pint",
"version": "v1.13.7",
@ -8857,108 +8961,6 @@
],
"time": "2023-10-11T15:45:01+00:00"
},
{
"name": "nunomaduro/larastan",
"version": "v2.7.0",
"source": {
"type": "git",
"url": "https://github.com/larastan/larastan.git",
"reference": "a2610d46b9999cf558d9900ccb641962d1442f55"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/larastan/larastan/zipball/a2610d46b9999cf558d9900ccb641962d1442f55",
"reference": "a2610d46b9999cf558d9900ccb641962d1442f55",
"shasum": ""
},
"require": {
"ext-json": "*",
"illuminate/console": "^9.52.16 || ^10.28.0",
"illuminate/container": "^9.52.16 || ^10.28.0",
"illuminate/contracts": "^9.52.16 || ^10.28.0",
"illuminate/database": "^9.52.16 || ^10.28.0",
"illuminate/http": "^9.52.16 || ^10.28.0",
"illuminate/pipeline": "^9.52.16 || ^10.28.0",
"illuminate/support": "^9.52.16 || ^10.28.0",
"php": "^8.0.2",
"phpmyadmin/sql-parser": "^5.8.2",
"phpstan/phpstan": "^1.10.41"
},
"require-dev": {
"nikic/php-parser": "^4.17.1",
"orchestra/canvas": "^7.11.1 || ^8.11.0",
"orchestra/testbench": "^7.33.0 || ^8.13.0",
"phpunit/phpunit": "^9.6.13"
},
"suggest": {
"orchestra/testbench": "Using Larastan for analysing a package needs Testbench"
},
"type": "phpstan-extension",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
},
"phpstan": {
"includes": [
"extension.neon"
]
}
},
"autoload": {
"psr-4": {
"Larastan\\Larastan\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Can Vural",
"email": "can9119@gmail.com"
},
{
"name": "Nuno Maduro",
"email": "enunomaduro@gmail.com"
}
],
"description": "Larastan - Discover bugs in your code without running it. A phpstan/phpstan wrapper for Laravel",
"keywords": [
"PHPStan",
"code analyse",
"code analysis",
"larastan",
"laravel",
"package",
"php",
"static analysis"
],
"support": {
"issues": "https://github.com/larastan/larastan/issues",
"source": "https://github.com/larastan/larastan/tree/v2.7.0"
},
"funding": [
{
"url": "https://www.paypal.com/paypalme/enunomaduro",
"type": "custom"
},
{
"url": "https://github.com/canvural",
"type": "github"
},
{
"url": "https://github.com/nunomaduro",
"type": "github"
},
{
"url": "https://www.patreon.com/nunomaduro",
"type": "patreon"
}
],
"abandoned": "larastan/larastan",
"time": "2023-12-04T19:21:38+00:00"
},
{
"name": "phar-io/manifest",
"version": "2.0.3",
@ -9270,16 +9272,16 @@
},
{
"name": "phpstan/phpdoc-parser",
"version": "1.24.4",
"version": "1.24.5",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496"
"reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6bd0c26f3786cd9b7c359675cb789e35a8e07496",
"reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fedf211ff14ec8381c9bf5714e33a7a552dd1acc",
"reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc",
"shasum": ""
},
"require": {
@ -9311,9 +9313,9 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.4"
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.5"
},
"time": "2023-11-26T18:29:22+00:00"
"time": "2023-12-16T09:33:33+00:00"
},
{
"name": "phpstan/phpstan",
@ -9379,23 +9381,23 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "10.1.10",
"version": "10.1.11",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "599109c8ca6bae97b23482d557d2874c25a65e59"
"reference": "78c3b7625965c2513ee96569a4dbb62601784145"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/599109c8ca6bae97b23482d557d2874c25a65e59",
"reference": "599109c8ca6bae97b23482d557d2874c25a65e59",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/78c3b7625965c2513ee96569a4dbb62601784145",
"reference": "78c3b7625965c2513ee96569a4dbb62601784145",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^4.15",
"nikic/php-parser": "^4.18 || ^5.0",
"php": ">=8.1",
"phpunit/php-file-iterator": "^4.0",
"phpunit/php-text-template": "^3.0",
@ -9445,7 +9447,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.10"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.11"
},
"funding": [
{
@ -9453,7 +9455,7 @@
"type": "github"
}
],
"time": "2023-12-11T06:28:43+00:00"
"time": "2023-12-21T15:38:30+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -10045,20 +10047,20 @@
},
{
"name": "sebastian/complexity",
"version": "3.1.0",
"version": "3.2.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/complexity.git",
"reference": "68cfb347a44871f01e33ab0ef8215966432f6957"
"reference": "68ff824baeae169ec9f2137158ee529584553799"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68cfb347a44871f01e33ab0ef8215966432f6957",
"reference": "68cfb347a44871f01e33ab0ef8215966432f6957",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799",
"reference": "68ff824baeae169ec9f2137158ee529584553799",
"shasum": ""
},
"require": {
"nikic/php-parser": "^4.10",
"nikic/php-parser": "^4.18 || ^5.0",
"php": ">=8.1"
},
"require-dev": {
@ -10067,7 +10069,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.1-dev"
"dev-main": "3.2-dev"
}
},
"autoload": {
@ -10091,7 +10093,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/complexity/issues",
"security": "https://github.com/sebastianbergmann/complexity/security/policy",
"source": "https://github.com/sebastianbergmann/complexity/tree/3.1.0"
"source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0"
},
"funding": [
{
@ -10099,7 +10101,7 @@
"type": "github"
}
],
"time": "2023-09-28T11:50:59+00:00"
"time": "2023-12-21T08:37:17+00:00"
},
{
"name": "sebastian/diff",
@ -10374,20 +10376,20 @@
},
{
"name": "sebastian/lines-of-code",
"version": "2.0.1",
"version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/lines-of-code.git",
"reference": "649e40d279e243d985aa8fb6e74dd5bb28dc185d"
"reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/649e40d279e243d985aa8fb6e74dd5bb28dc185d",
"reference": "649e40d279e243d985aa8fb6e74dd5bb28dc185d",
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0",
"reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0",
"shasum": ""
},
"require": {
"nikic/php-parser": "^4.10",
"nikic/php-parser": "^4.18 || ^5.0",
"php": ">=8.1"
},
"require-dev": {
@ -10420,7 +10422,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
"security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.1"
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2"
},
"funding": [
{
@ -10428,7 +10430,7 @@
"type": "github"
}
],
"time": "2023-08-31T09:25:50+00:00"
"time": "2023-12-21T08:38:20+00:00"
},
{
"name": "sebastian/object-enumerator",
@ -10931,16 +10933,16 @@
},
{
"name": "spatie/laravel-ignition",
"version": "2.3.1",
"version": "2.3.3",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-ignition.git",
"reference": "bf21cd15aa47fa4ec5d73bbc932005c70261efc8"
"reference": "66499cd3c858642ded56dafb8fa0352057ca20dd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/bf21cd15aa47fa4ec5d73bbc932005c70261efc8",
"reference": "bf21cd15aa47fa4ec5d73bbc932005c70261efc8",
"url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/66499cd3c858642ded56dafb8fa0352057ca20dd",
"reference": "66499cd3c858642ded56dafb8fa0352057ca20dd",
"shasum": ""
},
"require": {
@ -11019,7 +11021,7 @@
"type": "github"
}
],
"time": "2023-10-09T12:55:26+00:00"
"time": "2023-12-21T09:43:05+00:00"
},
{
"name": "theseer/tokenizer",
@ -11092,5 +11094,5 @@
"ext-xml": "*"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.6.0"
}

View File

@ -9,7 +9,7 @@ return [
|
*/
'version' => '5.0.0',
'version' => '5.1.1',
'repository' => 'https://github.com/Bubka/2FAuth',
'latestReleaseUrl' => 'https://api.github.com/repos/Bubka/2FAuth/releases/latest',
'installDocUrl' => 'https://docs.2fauth.app/getting-started/installation/self-hosted-server/',
@ -56,6 +56,8 @@ return [
'es',
'bg',
'ru',
'ja',
'hi',
],
/*
@ -73,6 +75,8 @@ return [
'latestRelease' => false,
'disableRegistration' => false,
'enableSso' => true,
'restrictRegistration' => false,
'keepSsoRegistrationEnabled' => false,
],
/*
@ -88,12 +92,14 @@ return [
'revealDottedOTP' => false,
'closeOtpOnCopy' => false,
'copyOtpOnDisplay' => false,
'clearSearchOnCopy' => false,
'useBasicQrcodeReader' => false,
'displayMode' => 'list',
'showAccountsIcons' => true,
'kickUserAfter' => 15,
'activeGroup' => 0,
'rememberActiveGroup' => true,
'viewDefaultGroupOnCopy' => false,
'defaultGroup' => 0,
'defaultCaptureMode' => 'livescan',
'useDirectCapture' => false,

View File

@ -3,6 +3,8 @@
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\ServiceProvider;
$appUrl = env('APP_URL', env('HEROKU_APP_NAME') ? 'https://' . env('HEROKU_APP_NAME') . '.herokuapp.com' : 'http://localhost');
return [
/*
@ -55,9 +57,9 @@ return [
|
*/
'url' => env('APP_URL', env('HEROKU_APP_NAME') ? 'https://' . env('HEROKU_APP_NAME') . '.herokuapp.com' : 'http://localhost'),
'url' => $appUrl,
'asset_url' => env('ASSET_URL', null),
'asset_url' => env('ASSET_URL', $appUrl),
/*
|--------------------------------------------------------------------------

View File

@ -27,19 +27,19 @@ return [
'userinfo_url' => env('OPENID_USERINFO_URL'),
'client_id' => env('OPENID_CLIENT_ID'),
'client_secret' => env('OPENID_CLIENT_SECRET'),
'redirect' => '/socialite/callback/openid',
'redirect' => env('APP_URL') . '/socialite/callback/openid',
],
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => '/socialite/callback/github',
'redirect' => env('APP_URL') . '/socialite/callback/github',
],
// 'google' => [
// 'client_id' => env('GOOGLE_CLIENT_ID'),
// 'client_secret' => env('GOOGLE_CLIENT_SECRET'),
// 'redirect' => '/socialite/callback/google ',
// 'redirect' => env('APP_URL') . '/socialite/callback/google ',
// ],
'postmark' => [

View File

@ -24,13 +24,18 @@ services:
# This variable must match your installation's external address.
# Webauthn won't work otherwise.
- APP_URL=http://localhost
# If you want to serve js assets from a CDN (like https://cdn.example.com),
# uncomment the following line and set this var with the CDN url.
# Otherwise, let this line commented.
# - ASSET_URL=http://localhost
#
# Turn this to true if you want your app to react like a demo.
# The Demo mode reset the app content every hours and set a generic demo user.
- IS_DEMO_APP=false
# The log channel defines where your log entries go to.
# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/.
# Several other options exist. You can use 'single' for one big fat error log (not recommended).
# Also available are 'syslog', 'errorlog' and 'stdout' which will log to the system itself.
# 'daily' is the default logging mode giving you 7 daily rotated log files in /storage/logs/.
# Also available are 'errorlog', 'syslog', 'stderr', 'papertrail', 'slack' and a 'stack' channel
# to combine multiple channels into a single one.
- LOG_CHANNEL=daily
# Log level. You can set this from least severe to most severe:
# debug, info, notice, warning, error, critical, alert, emergency

35
docker/entrypoint.sh vendored
View File

@ -9,12 +9,31 @@ echo "supervisord version: $(supervisord version)"
php-fpm81 -v | head -n 1
nginx -v
# Database creation
if [ "${DB_CONNECTION}" = "sqlite" ]; then
if [ ! -f /2fauth/database.sqlite ]; then
touch /2fauth/database.sqlite
# DB_DATABASE is trimmed if necessary
if [[ $DB_DATABASE == \"* ]] && [[ $DB_DATABASE == *\" ]] ; then
dbpath=${DB_DATABASE:1:${#DB_DATABASE}-2}
else
dbpath=${DB_DATABASE}
fi
if [ $dbpath != "/srv/database/database.sqlite" ]; then
echo "DB_DATABASE sets with custom path: ${dbpath}"
if [ ! -f ${dbpath} ]; then
echo "${dbpath} does not exist, we create it"
touch ${dbpath}
fi
else
echo "DB_DATABASE sets with default path, we will use a symlink"
echo "Actual db file will be /2fauth/database.sqlite"
if [ ! -f /2fauth/database.sqlite ]; then
echo "/2fauth/database.sqlite does not exist, we create it"
touch /2fauth/database.sqlite
fi
rm -f /srv/database/database.sqlite
ln -s /2fauth/database.sqlite /srv/database/database.sqlite
echo "/srv/database/database.sqlite is now a symlink to /2fauth/database.sqlite"
fi
rm -f /srv/database/database.sqlite
ln -s /2fauth/database.sqlite /srv/database/database.sqlite
fi
# Inject storage in /2fauth and use it with a symlink
@ -30,16 +49,20 @@ if [ -f /2fauth/installed ]; then
INSTALLED_COMMIT="$(cat /2fauth/installed)"
if [ "${INSTALLED_COMMIT}" != "${COMMIT}" ]; then
echo "Installed commit ${INSTALLED_COMMIT} is different from program commit ${COMMIT}, we are migrating..."
php artisan migrate
php artisan cache:clear
php artisan config:clear
php artisan migrate --force
fi
else
php artisan migrate:refresh
php artisan migrate:refresh --force
php artisan passport:install
fi
echo "${COMMIT}" > /2fauth/installed
php artisan storage:link --quiet
php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
supervisord

1
docker/php-test.ini Normal file
View File

@ -0,0 +1 @@
memory_limit=2048M

1
public/build/assets/About-51085f4f.js vendored Normal file
View File

@ -0,0 +1 @@
import{Q as b,u as k,e as i,f as g,k as C,p as r,h as s,t,m as o,n as c,j as e,i as h,U as u}from"./app-2d89b28f.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const B={class:"title has-text-grey-dark"},F={class:"block"},A=s("span",{class:"is-size-5"},"2FAuth",-1),w=s("br",null,null,-1),y=s("img",{class:"about-logo",src:"logo.svg",alt:"2FAuth logo"},null,-1),v=s("p",{class:"block"},[e(" ©Bubka "),s("a",{class:"is-size-7",href:"https://github.com/Bubka/2FAuth/blob/master/LICENSE"},"AGPL-3.0 license")],-1),I={class:"title is-5 has-text-grey-light"},L={class:"buttons"},$={class:"icon is-small"},S=s("span",null,"Github",-1),T={class:"icon is-small"},V=s("span",null,"Docs",-1),z={class:"icon is-small"},N=s("span",null,"Demo",-1),D={class:"icon is-small"},E=s("span",null,"API",-1),W={class:"title is-5 has-text-grey-light"},j={class:"block"},M=s("a",{href:"https://docs.2fauth.app/credits/"},"Laravel, Bulma CSS, Vue.js and more",-1),R=s("a",{href:"https://fontawesome.com/"},"Font Awesome",-1),x=s("a",{class:"is-size-7",href:"https://fontawesome.com/license/free"},"(CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)",-1),G=s("a",{href:"https://2fa.directory/"},"2FA Directory",-1),P=s("a",{class:"is-size-7",href:"https://github.com/2factorauth/twofactorauth/blob/master/LICENSE.md"},"(MIT License)",-1),q={__name:"About",setup(U){const _=b("2fauth"),d=k().options.history.state.back;return(a,Q)=>{const l=i("FontAwesomeIcon"),p=i("ButtonBackCloseCancel"),m=i("VueFooter"),f=i("ResponsiveWidthWrapper");return g(),C(f,null,{default:r(()=>[s("h1",B,t(a.$t("commons.about")),1),s("p",F,[o(h(u),null,{default:r(({mode:n})=>[s("span",{class:c(n=="dark"?"has-text-white":"has-text-black")},[A,e(" v"+t(h(_).version),1)],2)]),_:1}),w,e(" "+t(a.$t("commons.2fauth_teaser")),1)]),y,v,s("h2",I,t(a.$t("commons.resources")),1),s("div",L,[o(h(u),null,{default:r(({mode:n})=>[s("a",{class:c(["button",{"is-dark":n=="dark"}]),href:"https://github.com/Bubka/2FAuth",target:"_blank"},[s("span",$,[o(l,{icon:["fab","github-alt"]})]),S],2),s("a",{class:c(["button",{"is-dark":n=="dark"}]),href:"https://docs.2fauth.app/",target:"_blank"},[s("span",T,[o(l,{icon:["fas","book"]})]),V],2),s("a",{class:c(["button",{"is-dark":n=="dark"}]),href:"https://demo.2fauth.app/",target:"_blank"},[s("span",z,[o(l,{icon:["fas","flask"]})]),N],2),s("a",{class:c(["button",{"is-dark":n=="dark"}]),href:"https://docs.2fauth.app/resources/rapidoc.html",target:"_blank"},[s("span",D,[o(l,{icon:["fas","code"]})]),E],2)]),_:1})]),s("h2",W,t(a.$t("commons.credits")),1),s("p",j,[s("ul",null,[s("li",null,[e(t(a.$t("commons.made_with"))+" ",1),M]),s("li",null,[e(t(a.$t("commons.ui_icons_by"))+" ",1),R,e(" "),x]),s("li",null,[e(t(a.$t("commons.logos_by"))+" ",1),G,e(" "),P])])]),o(m,{showButtons:!0},{default:r(()=>[o(p,{returnTo:{path:h(d)},action:"back"},null,8,["returnTo"])]),_:1})]),_:1})}}};export{q as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
import{r as u,e as t,f as s,g as a,m as n,p as i,h as o,F as _,G as v,i as f,n as h,j as k,t as w}from"./app-2d89b28f.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const T={class:"options-header"},g={class:"tabs is-centered is-fullwidth"},b={__name:"AdminTabs",props:{activeTab:{type:String,default:""}},setup(r){const p=r,d=u([{name:"admin.app_setup",view:"admin.appSetup",id:"lnkTabApp"},{name:"admin.users",view:"admin.users",id:"lnkTabUsers"}]);return(c,R)=>{const l=t("RouterLink"),m=t("ResponsiveWidthWrapper");return s(),a("div",T,[n(m,null,{default:i(()=>[o("div",g,[o("ul",null,[(s(!0),a(_,null,v(f(d),e=>(s(),a("li",{key:e.view,class:h({"is-active":e.view===p.activeTab})},[n(l,{id:e.id,to:{name:e.view}},{default:i(()=>[k(w(c.$t(e.name)),1)]),_:2},1032,["id","to"])],2))),128))])])]),_:1})])}}};export{b as _};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
import{b as r,S as p,e as l,f as m,g as _,m as u,D as b,I as d}from"./app-2d89b28f.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const y=["aria-label","title"],C={__name:"CopyButton",props:{token:String},setup(e){const s=e,n=r(),{copy:c}=p({legacy:!0});function a(){c(s.token),n.success({text:d("commons.copied_to_clipboard")})}return(o,t)=>{const i=l("FontAwesomeIcon");return m(),_("button",{"aria-label":o.$t("commons.copy_to_clipboard"),title:o.$t("commons.copy_to_clipboard"),class:"button is-like-text is-pulled-right is-small is-text",onClick:t[0]||(t[0]=b(f=>a(),["stop"]))},[u(i,{icon:["fas","copy"]})],8,y)}}};export{C as _};

View File

@ -0,0 +1 @@
import{b as _,u as F,d as V,e as n,f as b,g,m as r,p as y,h as x,i as o,D as B,I as h}from"./app-2d89b28f.js";import{F as C}from"./Form-5283f7b6.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const E=["onSubmit"],k={__name:"Create",setup(N){const m=_(),l=F(),e=V(new C({name:"",email:"",password:"",password_confirmation:"",is_admin:!1}));async function i(d){e.password_confirmation=e.password,e.post("/api/v1/users").then(s=>{const t=s.data;m.success({text:h("admin.user_created")}),l.push({name:"admin.manageUser",params:{userId:t.info.id}})})}return(d,s)=>{const t=n("FormField"),u=n("FormPasswordField"),p=n("FormCheckbox"),f=n("FormButtons"),c=n("FormWrapper"),w=n("VueFooter");return b(),g("div",null,[r(c,{title:"admin.new_user"},{default:y(()=>[x("form",{onSubmit:B(i,["prevent"]),onKeydown:s[4]||(s[4]=a=>o(e).onKeydown(a))},[r(t,{modelValue:o(e).name,"onUpdate:modelValue":s[0]||(s[0]=a=>o(e).name=a),fieldName:"name",fieldError:o(e).errors.get("name"),inputType:"text",label:"auth.forms.name",maxLength:255,autofocus:""},null,8,["modelValue","fieldError"]),r(t,{modelValue:o(e).email,"onUpdate:modelValue":s[1]||(s[1]=a=>o(e).email=a),fieldName:"email",fieldError:o(e).errors.get("email"),inputType:"email",label:"auth.forms.email",maxLength:255},null,8,["modelValue","fieldError"]),r(u,{modelValue:o(e).password,"onUpdate:modelValue":s[2]||(s[2]=a=>o(e).password=a),fieldName:"password",fieldError:o(e).errors.get("password"),showRules:!0,label:"auth.forms.password",autocomplete:"new-password"},null,8,["modelValue","fieldError"]),r(p,{modelValue:o(e).is_admin,"onUpdate:modelValue":s[3]||(s[3]=a=>o(e).is_admin=a),fieldName:"is_admin",label:"admin.forms.is_admin.label",help:"admin.forms.is_admin.help"},null,8,["modelValue"]),r(f,{isBusy:o(e).isBusy,isDisabled:o(e).isDisabled,showCancelButton:!0,cancelLandingView:"admin.users",caption:"commons.create",submitId:"btnCreateUser"},null,8,["isBusy","isDisabled","cancelLandingView"])],40,E)]),_:1}),r(w)])}}};export{k as default};

View File

@ -1 +1 @@
import{S as v,u as G,Z as h,v as w,d as y,_ as N,K as S,e as m,f as E,k as I,p as V,i as t,h as C,m as i,D as $}from"./app-1b332c21.js";import{F as k}from"./Form-940b5f6c.js";import{u as K}from"./bus-84126a4e.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const M=["onSubmit"],W={__name:"CreateUpdate",props:{groupId:[Number,String]},setup(c){const s=c,p=v(),d=G(),l=h(),a=K(),r=w(()=>s.groupId!=null),o=y(new k({name:""}));N(()=>{l.name=="editGroup"&&(a.editedGroupName?(o.name=a.editedGroupName,a.editedGroupName=void 0):S.get(s.groupId).then(e=>{o.name=e.data.name}))});function f(){r.value?B():g()}async function g(){o.post("/api/v1/groups").then(e=>{p.addOrEdit(e.data),d.push({name:"groups"})})}async function B(){o.put("/api/v1/groups/"+s.groupId).then(e=>{p.addOrEdit(e.data),d.push({name:"groups"})})}return(e,n)=>{const b=m("FormField"),F=m("FormButtons"),_=m("FormWrapper");return E(),I(_,{title:t(r)?e.$t("groups.forms.rename_group"):e.$t("groups.forms.new_group")},{default:V(()=>[C("form",{onSubmit:$(f,["prevent"]),onKeydown:n[1]||(n[1]=u=>t(o).onKeydown(u))},[i(b,{modelValue:t(o).name,"onUpdate:modelValue":n[0]||(n[0]=u=>t(o).name=u),fieldName:"name",fieldError:t(o).errors.get("name"),label:"commons.name",autofocus:""},null,8,["modelValue","fieldError"]),i(F,{submitId:t(r)?"btnEditGroup":"btnCreateGroup",isBusy:t(o).isBusy,caption:t(r)?e.$t("commons.save"):e.$t("commons.create"),showCancelButton:!0,cancelLandingView:"groups"},null,8,["submitId","isBusy","caption"])],40,M)]),_:1},8,["title"])}}};export{W as default};
import{T as v,u as G,_ as h,v as w,d as y,$ as N,L as E,e as m,f as I,k as S,p as V,i as t,h as C,m as i,D as $}from"./app-2d89b28f.js";import{F as k}from"./Form-5283f7b6.js";import{u as M}from"./bus-7802a020.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const K=["onSubmit"],U={__name:"CreateUpdate",props:{groupId:[Number,String]},setup(c){const s=c,p=v(),d=G(),l=h(),a=M(),r=w(()=>s.groupId!=null),o=y(new k({name:""}));N(()=>{l.name=="editGroup"&&(a.editedGroupName?(o.name=a.editedGroupName,a.editedGroupName=void 0):E.get(s.groupId).then(e=>{o.name=e.data.name}))});function f(){r.value?B():g()}async function g(){o.post("/api/v1/groups").then(e=>{p.addOrEdit(e.data),d.push({name:"groups"})})}async function B(){o.put("/api/v1/groups/"+s.groupId).then(e=>{p.addOrEdit(e.data),d.push({name:"groups"})})}return(e,n)=>{const b=m("FormField"),F=m("FormButtons"),_=m("FormWrapper");return I(),S(_,{title:t(r)?e.$t("groups.forms.rename_group"):e.$t("groups.forms.new_group")},{default:V(()=>[C("form",{onSubmit:$(f,["prevent"]),onKeydown:n[1]||(n[1]=u=>t(o).onKeydown(u))},[i(b,{modelValue:t(o).name,"onUpdate:modelValue":n[0]||(n[0]=u=>t(o).name=u),fieldName:"name",fieldError:t(o).errors.get("name"),label:"commons.name",autofocus:""},null,8,["modelValue","fieldError"]),i(F,{submitId:t(r)?"btnEditGroup":"btnCreateGroup",isBusy:t(o).isBusy,caption:t(r)?e.$t("commons.save"):e.$t("commons.create"),showCancelButton:!0,cancelLandingView:"groups"},null,8,["submitId","isBusy","caption"])],40,K)]),_:1},8,["title"])}}};export{U as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{u as h,b as w,d as b,I as a,e as s,f as y,k as F,p as v,h as B,m as r,i as t,D as g}from"./app-1b332c21.js";import{F as V}from"./Form-940b5f6c.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const C=["onSubmit"],I={__name:"Edit",props:{credentialId:{type:String,default:""}},setup(i){const u=i,m=h(),d=w(),e=b(new V({name:a("auth.webauthn.my_device")}));function c(){e.patch("/webauthn/credentials/"+u.credentialId+"/name").then(()=>{d.success({text:a("auth.webauthn.device_successfully_registered")}),m.push({name:"settings.webauthn.devices"})})}return(l,n)=>{const p=s("FormField"),f=s("FormButtons"),_=s("FormWrapper");return y(),F(_,{title:"auth.webauthn.rename_device"},{default:v(()=>[B("form",{onSubmit:g(c,["prevent"]),onKeydown:n[1]||(n[1]=o=>t(e).onKeydown(o))},[r(p,{modelValue:t(e).name,"onUpdate:modelValue":n[0]||(n[0]=o=>t(e).name=o),fieldName:"name",fieldError:t(e).errors.get("name"),inputType:"text",label:"commons.new_name",autofocus:""},null,8,["modelValue","fieldError"]),r(f,{submitId:"btnEditCredential",isBusy:t(e).isBusy,caption:l.$t("commons.save"),showCancelButton:!0,cancelLandingView:"settings.webauthn.devices"},null,8,["isBusy","caption"])],40,C)]),_:1})}}};export{I as default};
import{u as h,b as w,d as b,I as a,e as s,f as y,k as F,p as v,h as B,m as r,i as t,D as g}from"./app-2d89b28f.js";import{F as V}from"./Form-5283f7b6.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const C=["onSubmit"],I={__name:"Edit",props:{credentialId:{type:String,default:""}},setup(i){const u=i,m=h(),d=w(),e=b(new V({name:a("auth.webauthn.my_device")}));function c(){e.patch("/webauthn/credentials/"+u.credentialId+"/name").then(()=>{d.success({text:a("auth.webauthn.device_successfully_registered")}),m.push({name:"settings.webauthn.devices"})})}return(l,n)=>{const p=s("FormField"),f=s("FormButtons"),_=s("FormWrapper");return y(),F(_,{title:"auth.webauthn.rename_device"},{default:v(()=>[B("form",{onSubmit:g(c,["prevent"]),onKeydown:n[1]||(n[1]=o=>t(e).onKeydown(o))},[r(p,{modelValue:t(e).name,"onUpdate:modelValue":n[0]||(n[0]=o=>t(e).name=o),fieldName:"name",fieldError:t(e).errors.get("name"),inputType:"text",label:"commons.new_name",autofocus:""},null,8,["modelValue","fieldError"]),r(f,{submitId:"btnEditCredential",isBusy:t(e).isBusy,caption:l.$t("commons.save"),showCancelButton:!0,cancelLandingView:"settings.webauthn.devices"},null,8,["isBusy","caption"])],40,C)]),_:1})}}};export{I as default};

View File

@ -1 +1 @@
import{b as y,u as b,Z as k,r as v,v as V,x as w,o as N,I as x,e as B,f as r,g as t,m as M,p as $,i as e,E as q,h as l,t as n,l as c,j as C}from"./app-1b332c21.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const E={key:0,class:"error-message"},R=l("p",{class:"error-404"},null,-1),D={key:1,class:"error-message"},F=l("p",{class:"error-generic"},null,-1),S={key:0,class:"has-text-grey-lighter"},j={key:1,class:"has-text-grey-lighter"},z={key:2,class:"is-size-7 is-family-code"},H=l("br",null,null,-1),T={__name:"Error",props:{closable:{type:Boolean,default:!0}},setup(m){const p=m,s=y(),d=b(),a=k(),u=v(!0),_=V(()=>!1);w(u,o=>{o==!1&&g()}),N(()=>{a.query.err&&(s.message=x("errors."+a.query.err))});function g(){window.history.length>1&&a.name!=="404"&&a.name!=="notFound"&&!a.query.err?d.go(-1):d.push({name:"accounts"})}return(o,i)=>{const h=B("modal");return r(),t("div",null,[M(h,{modelValue:e(u),"onUpdate:modelValue":i[0]||(i[0]=f=>q(u)?u.value=f:null),closable:p.closable},{default:$(()=>[o.$route.name=="404"||o.$route.name=="notFound"?(r(),t("div",E,[R,l("p",null,n(o.$t("errors.resource_not_found")),1)])):(r(),t("div",D,[F,l("p",null,n(o.$t("errors.error_occured")),1),e(s).message?(r(),t("p",S,n(e(s).message),1)):c("",!0),e(s).originalMessage?(r(),t("p",j,n(e(s).originalMessage),1)):c("",!0),e(_)&&e(s).debug?(r(),t("p",z,[H,C(n(e(s).debug),1)])):c("",!0)]))]),_:1},8,["modelValue","closable"])])}}};export{T as default};
import{b as y,u as b,_ as k,r as v,v as V,x as w,o as N,I as x,e as B,f as r,g as t,m as M,p as $,i as e,E as q,h as l,t as n,l as c,j as C}from"./app-2d89b28f.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const E={key:0,class:"error-message"},R=l("p",{class:"error-404"},null,-1),D={key:1,class:"error-message"},F=l("p",{class:"error-generic"},null,-1),S={key:0,class:"has-text-grey-lighter"},j={key:1,class:"has-text-grey-lighter"},z={key:2,class:"is-size-7 is-family-code"},H=l("br",null,null,-1),T={__name:"Error",props:{closable:{type:Boolean,default:!0}},setup(m){const p=m,s=y(),d=b(),a=k(),u=v(!0),_=V(()=>!1);w(u,o=>{o==!1&&g()}),N(()=>{a.query.err&&(s.message=x("errors."+a.query.err))});function g(){window.history.length>1&&a.name!=="404"&&a.name!=="notFound"&&!a.query.err?d.go(-1):d.push({name:"accounts"})}return(o,i)=>{const h=B("modal");return r(),t("div",null,[M(h,{modelValue:e(u),"onUpdate:modelValue":i[0]||(i[0]=f=>q(u)?u.value=f:null),closable:p.closable},{default:$(()=>[o.$route.name=="404"||o.$route.name=="notFound"?(r(),t("div",E,[R,l("p",null,n(o.$t("errors.resource_not_found")),1)])):(r(),t("div",D,[F,l("p",null,n(o.$t("errors.error_occured")),1),e(s).message?(r(),t("p",S,n(e(s).message),1)):c("",!0),e(s).originalMessage?(r(),t("p",j,n(e(s).originalMessage),1)):c("",!0),e(_)&&e(s).debug?(r(),t("p",z,[H,C(n(e(s).debug),1)])):c("",!0)]))]),_:1},8,["modelValue","closable"])])}}};export{T as default};

View File

@ -1 +1 @@
import{a1 as l}from"./app-1b332c21.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */class f{constructor(){this.errors={}}set(t,s){typeof t=="object"?this.errors=t:this.set({...this.errors,[t]:c(s)})}all(){return this.errors}has(t){return this.errors.hasOwnProperty(t)}hasAny(...t){return t.some(s=>this.has(s))}any(){return Object.keys(this.errors).length>0}get(t){if(this.has(t))return this.getAll(t)[0]}getAll(t){return c(this.errors[t]||[])}only(...t){const s=[];return t.forEach(r=>{const e=this.get(r);e&&s.push(e)}),s}flatten(){return Object.values(this.errors).reduce((t,s)=>t.concat(s),[])}clear(t){const s={};t&&Object.keys(this.errors).forEach(r=>{r!==t&&(s[r]=this.errors[r])}),this.set(s)}}function c(o){return Array.isArray(o)?o:[o]}class i{constructor(t={}){this.axios=l("web"),this.isBusy=!1,this.isDisabled=!1,this.errors=new f,this.originalData=this.deepCopy(t),Object.assign(this,t)}fill(t){this.keys().forEach(s=>{this[s]=t[s]})}setOriginal(){Object.keys(this).filter(t=>!i.ignore.includes(t)).forEach(t=>{this.originalData[t]=this.deepCopy(this[t])})}hasChanged(){return this.keys().some(t=>this[t]!==this.originalData[t])}fillWithKeyValueObject(t){this.keys().forEach(s=>{const r=t.find(e=>e.key===s.toString());r!=null&&(this[s]=r.value)})}data(){return this.keys().reduce((t,s)=>({...t,[s]:this[s]}),{})}keys(){return Object.keys(this).filter(t=>!i.ignore.includes(t))}startProcessing(){this.errors.clear(),this.isBusy=!0}finishProcessing(){this.isBusy=!1}clear(){this.errors.clear()}reset(){Object.keys(this).filter(t=>!i.ignore.includes(t)).forEach(t=>{this[t]=this.deepCopy(this.originalData[t])})}get(t,s={}){return this.submit("get",t,s)}post(t,s={}){return this.submit("post",t,s)}patch(t,s={}){return this.submit("patch",t,s)}put(t,s={}){return this.submit("put",t,s)}delete(t,s={}){return this.submit("delete",t,s)}submit(t,s,r={}){this.startProcessing();const e=t==="get"?{params:this.data()}:this.data();return new Promise((a,u)=>{this.axios.request({url:this.route(s),method:t,data:e,...r}).then(h=>{this.finishProcessing(),a(h)}).catch(h=>{var n;this.isBusy=!1,h.response&&this.errors.set(this.extractErrors(h.response)),((n=h.response)==null?void 0:n.status)!=422&&u(h)})})}upload(t,s={}){return this.startProcessing(),new Promise((r,e)=>{this.axios.post(this.route(t),this.data(),{headers:{"Content-Type":"multipart/form-data"},...s}).then(a=>{this.finishProcessing(),r(a)}).catch(a=>{this.isBusy=!1,a.response&&this.errors.set(this.extractErrors(a.response)),e(a)})})}extractErrors(t){return!t.data||typeof t.data!="object"?{error:i.errorMessage}:t.data.errors?{...t.data.errors}:t.data.message?{error:t.data.message}:{...t.data}}route(t,s={}){let r=t;return i.routes.hasOwnProperty(t)&&(r=decodeURI(i.routes[t])),typeof s!="object"&&(s={id:s}),Object.keys(s).forEach(e=>{r=r.replace(`{${e}}`,s[e])}),r}onKeydown(t){t.target.name&&this.errors.clear(t.target.name)}deepCopy(t){if(t===null||typeof t!="object")return t;const s=Array.isArray(t)?[]:{};return Object.keys(t).forEach(r=>{s[r]=this.deepCopy(t[r])}),s}}i.routes={};i.errorMessage="Something went wrong. Please try again.";i.ignore=["isBusy","isDisabled","errors","originalData","axios"];export{i as F};
import{a4 as l}from"./app-2d89b28f.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */class f{constructor(){this.errors={}}set(t,s){typeof t=="object"?this.errors=t:this.set({...this.errors,[t]:c(s)})}all(){return this.errors}has(t){return this.errors.hasOwnProperty(t)}hasAny(...t){return t.some(s=>this.has(s))}any(){return Object.keys(this.errors).length>0}get(t){if(this.has(t))return this.getAll(t)[0]}getAll(t){return c(this.errors[t]||[])}only(...t){const s=[];return t.forEach(r=>{const e=this.get(r);e&&s.push(e)}),s}flatten(){return Object.values(this.errors).reduce((t,s)=>t.concat(s),[])}clear(t){const s={};t&&Object.keys(this.errors).forEach(r=>{r!==t&&(s[r]=this.errors[r])}),this.set(s)}}function c(o){return Array.isArray(o)?o:[o]}class i{constructor(t={}){this.axios=l("web"),this.isBusy=!1,this.isDisabled=!1,this.errors=new f,this.originalData=this.deepCopy(t),Object.assign(this,t)}fill(t){this.keys().forEach(s=>{this[s]=t[s]})}setOriginal(){Object.keys(this).filter(t=>!i.ignore.includes(t)).forEach(t=>{this.originalData[t]=this.deepCopy(this[t])})}hasChanged(){return this.keys().some(t=>this[t]!==this.originalData[t])}fillWithKeyValueObject(t){this.keys().forEach(s=>{const r=t.find(e=>e.key===s.toString());r!=null&&(this[s]=r.value)})}data(){return this.keys().reduce((t,s)=>({...t,[s]:this[s]}),{})}keys(){return Object.keys(this).filter(t=>!i.ignore.includes(t))}startProcessing(){this.errors.clear(),this.isBusy=!0}finishProcessing(){this.isBusy=!1}clear(){this.errors.clear()}reset(){Object.keys(this).filter(t=>!i.ignore.includes(t)).forEach(t=>{this[t]=this.deepCopy(this.originalData[t])})}get(t,s={}){return this.submit("get",t,s)}post(t,s={}){return this.submit("post",t,s)}patch(t,s={}){return this.submit("patch",t,s)}put(t,s={}){return this.submit("put",t,s)}delete(t,s={}){return this.submit("delete",t,s)}submit(t,s,r={}){this.startProcessing();const e=t==="get"?{params:this.data()}:this.data();return new Promise((a,u)=>{this.axios.request({url:this.route(s),method:t,data:e,...r}).then(h=>{this.finishProcessing(),a(h)}).catch(h=>{var n;this.isBusy=!1,h.response&&this.errors.set(this.extractErrors(h.response)),((n=h.response)==null?void 0:n.status)!=422&&u(h)})})}upload(t,s={}){return this.startProcessing(),new Promise((r,e)=>{this.axios.post(this.route(t),this.data(),{headers:{"Content-Type":"multipart/form-data"},...s}).then(a=>{this.finishProcessing(),r(a)}).catch(a=>{this.isBusy=!1,a.response&&this.errors.set(this.extractErrors(a.response)),e(a)})})}extractErrors(t){return!t.data||typeof t.data!="object"?{error:i.errorMessage}:t.data.errors?{...t.data.errors}:t.data.message?{error:t.data.message}:{...t.data}}route(t,s={}){let r=t;return i.routes.hasOwnProperty(t)&&(r=decodeURI(i.routes[t])),typeof s!="object"&&(s={id:s}),Object.keys(s).forEach(e=>{r=r.replace(`{${e}}`,s[e])}),r}onKeydown(t){t.target.name&&this.errors.clear(t.target.name)}deepCopy(t){if(t===null||typeof t!="object")return t;const s=Array.isArray(t)?[]:{};return Object.keys(t).forEach(r=>{s[r]=this.deepCopy(t[r])}),s}}i.routes={};i.errorMessage="Something went wrong. Please try again.";i.ignore=["isBusy","isDisabled","errors","originalData","axios"];export{i as F};

View File

@ -0,0 +1 @@
import{u as C,T as $,r as w,o as z,a0 as G,e as c,f as l,k as b,p as u,h as o,t as a,m as s,j as _,i,g as m,F,G as R,n as V,U as N,l as h}from"./app-2d89b28f.js";import{u as E}from"./bus-7802a020.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const I={class:"title has-text-grey-dark"},L={class:"is-size-7-mobile"},T={class:"mt-3 mb-6"},W={key:0},A=["onClick","title"],x={class:"is-family-primary is-size-6 is-size-7-mobile has-text-grey"},M={class:"mt-2 is-size-7 is-pulled-right"},S={key:1,class:"has-text-centered"},U={class:"is-size-4"},J={__name:"Groups",setup(j){C();const t=$(),f=E(),p=w(!1);return z(async()=>{p.value=t.isEmpty,await t.fetch().finally(()=>{p.value=!1})}),G((e,g)=>{var n;e.name=="editGroup"&&(f.editedGroupName=(n=t.items.find(d=>d.id==e.params.groupId))==null?void 0:n.name)}),(e,g)=>{const n=c("FontAwesomeIcon"),d=c("RouterLink"),k=c("ButtonBackCloseCancel"),y=c("VueFooter"),v=c("ResponsiveWidthWrapper");return l(),b(v,null,{default:u(()=>[o("h1",I,a(e.$t("groups.groups")),1),o("div",L,a(e.$t("groups.manage_groups_legend")),1),o("div",T,[s(d,{class:"is-link mt-5",to:{name:"createGroup"}},{default:u(()=>[s(n,{icon:["fas","plus-circle"]}),_(" "+a(e.$t("groups.create_group")),1)]),_:1})]),i(t).isEmpty?h("",!0):(l(),m("div",W,[(l(!0),m(F,null,R(i(t).withoutTheAllGroup,r=>(l(),m("div",{key:r.id,class:"group-item is-size-5 is-size-6-mobile"},[_(a(r.name)+" ",1),s(i(N),null,{default:u(({mode:B})=>[o("button",{class:V(["button tag is-pulled-right",B=="dark"?"is-dark":"is-white"]),onClick:q=>i(t).delete(r.id),title:e.$t("commons.delete")},a(e.$t("commons.delete")),11,A)]),_:2},1024),s(d,{to:{name:"editGroup",params:{groupId:r.id}},class:"has-text-grey px-1",title:e.$t("commons.rename")},{default:u(()=>[s(n,{icon:["fas","pen-square"]})]),_:2},1032,["to","title"]),o("span",x,a(e.$t("groups.x_accounts",{count:r.twofaccounts_count})),1)]))),128)),o("div",M,a(e.$t("groups.deleting_group_does_not_delete_accounts")),1)])),i(p)&&i(t).isEmpty?(l(),m("div",S,[o("span",U,[s(n,{icon:["fas","spinner"],spin:""})])])):h("",!0),s(y,{showButtons:!0},{default:u(()=>[s(k,{returnTo:{name:"accounts"},action:"close"})]),_:1})]),_:1})}}};export{J as default};

View File

@ -1 +0,0 @@
import{u as C,S as $,r as w,o as z,$ as G,e as c,f as l,k as b,p as u,h as a,t as s,m as t,j as _,i,g as m,F,G as R,n as V,U as N,l as h}from"./app-1b332c21.js";import{u as E}from"./bus-84126a4e.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const I={class:"title has-text-grey-dark"},L={class:"is-size-7-mobile"},W={class:"mt-3 mb-6"},A={key:0},S=["onClick","title"],T={class:"is-family-primary is-size-6 is-size-7-mobile has-text-grey"},M={class:"mt-2 is-size-7 is-pulled-right"},U={key:1,class:"has-text-centered"},j={class:"is-size-4"},J={__name:"Groups",setup(q){C();const o=$(),f=E(),p=w(!1);return z(async()=>{p.value=o.isEmpty,await o.fetch().finally(()=>{p.value=!1})}),G((e,g)=>{var n;e.name=="editGroup"&&(f.editedGroupName=(n=o.items.find(d=>d.id==e.params.groupId))==null?void 0:n.name)}),(e,g)=>{const n=c("FontAwesomeIcon"),d=c("RouterLink"),k=c("ButtonBackCloseCancel"),y=c("VueFooter"),v=c("ResponsiveWidthWrapper");return l(),b(v,null,{default:u(()=>[a("h1",I,s(e.$t("groups.groups")),1),a("div",L,s(e.$t("groups.manage_groups_legend")),1),a("div",W,[t(d,{class:"is-link mt-5",to:{name:"createGroup"}},{default:u(()=>[t(n,{icon:["fas","plus-circle"]}),_(" "+s(e.$t("groups.create_group")),1)]),_:1})]),i(o).isEmpty?h("",!0):(l(),m("div",A,[(l(!0),m(F,null,R(i(o).withoutTheAllGroup,r=>(l(),m("div",{key:r.id,class:"group-item is-size-5 is-size-6-mobile"},[_(s(r.name)+" ",1),t(i(N),null,{default:u(({mode:B})=>[a("button",{class:V(["button tag is-pulled-right",B=="dark"?"is-dark":"is-white"]),onClick:x=>i(o).delete(r.id),title:e.$t("commons.delete")},s(e.$t("commons.delete")),11,S)]),_:2},1024),t(d,{to:{name:"editGroup",params:{groupId:r.id}},class:"has-text-grey px-1",title:e.$t("commons.rename")},{default:u(()=>[t(n,{icon:["fas","pen-square"]})]),_:2},1032,["to","title"]),a("span",T,s(r.twofaccounts_count)+" "+s(e.$t("twofaccounts.accounts")),1)]))),128)),a("div",M,s(e.$t("groups.deleting_group_does_not_delete_accounts")),1)])),i(p)&&i(o).isEmpty?(l(),m("div",U,[a("span",j,[t(n,{icon:["fas","spinner"],spin:""})])])):h("",!0),t(y,{showButtons:!0},{default:u(()=>[t(k,{returnTo:{name:"accounts"},action:"close"})]),_:1})]),_:1})}}};export{J as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/build/assets/Login-69c0645b.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
import{P as D,b as G,a2 as H,R as O,r as m,o as q,d as J,$ as Q,e as _,f as l,g as c,m as n,h as t,p as f,i as o,l as d,t as a,w as X,j as g,F as Y,G as Z,k as ee,U as se,n as z,D as A,I as y}from"./app-1b332c21.js";import{F as te}from"./Form-940b5f6c.js";import{u as S}from"./userService-5f2b5050.js";import{_ as oe}from"./SettingTabs-52d14fa3.js";import{S as ne}from"./Spinner-b3cbad3a.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const ae={class:"options-tabs"},ie=["innerHTML"],le={class:"title is-4 has-text-grey-light"},re={class:"is-size-7-mobile"},ce={class:"mt-3"},ue=["onKeyup"],de={key:1},me={class:"tags is-pulled-right"},_e=["onClick"],fe=["onClick","title"],pe={key:1,class:"is-size-7-mobile is-size-6 my-3"},ve={key:2,class:"pat is-family-monospace is-size-6 is-size-7-mobile has-text-success"},he={class:"mt-2 is-size-7 is-pulled-right"},ke={key:0,class:"is-overlay modal-otp modal-background"},ge={class:"main-section"},ye=["onSubmit"],be={class:"field is-grouped"},Te={class:"control"},Ce={class:"control"},Ae={__name:"OAuth",setup(we){const P=D("2fauth"),p=G(),x=H(P.prefix+"returnTo","accounts"),{copy:E}=O({legacy:!0}),r=m([]),b=m(!1),T=m(!1),v=m(!1),h=m(null),C=m(null);q(()=>{w()});const u=J(new te({name:""}));function w(){b.value=!0,S.getPersonalAccessTokens({returnError:!0}).then(e=>{r.value=[],e.data.forEach(i=>{i.id===C.value?(i.value=h.value,r.value.unshift(i)):r.value.push(i)})}).catch(e=>{e.response.status===405?T.value=!0:p.error(e)}).finally(()=>{b.value=!1,C.value=null,h.value=null})}function F(){N(),T.value?p.warn({text:y("errors.unsupported_with_reverseproxy")}):v.value=!0}function L(){u.post("/oauth/personal-access-tokens").then(e=>{h.value=e.data.accessToken,C.value=e.data.token.id,w(),v.value=!1,u.reset()})}function M(e){confirm(y("settings.confirm.revoke"))&&S.deletePersonalAccessToken(e).then(i=>{r.value=r.value.filter(k=>k.id!==e),p.success({text:y("settings.token_revoked")})})}function N(){r.value.forEach(e=>{e.value=null}),h.value=null}function K(e){E(e),p.success({text:y("commons.copied_to_clipboard")})}function I(){v.value=!1,u.reset()}return Q(e=>{e.name.startsWith("settings.")||p.clear()}),(e,i)=>{const k=_("FontAwesomeIcon"),U=_("ButtonBackCloseCancel"),R=_("VueFooter"),$=_("FormWrapper"),W=_("FormField"),V=_("VueButton");return l(),c("div",null,[n(oe,{activeTab:"settings.oauth.tokens"},null,8,["activeTab"]),t("div",ae,[n($,null,{default:f(()=>[o(T)?(l(),c("div",{key:0,class:"notification is-warning has-text-centered",innerHTML:e.$t("auth.auth_handled_by_proxy")},null,8,ie)):d("",!0),t("h4",le,a(e.$t("settings.personal_access_tokens")),1),t("div",re,a(e.$t("settings.token_legend")),1),t("div",ce,[t("a",{tabindex:"0",class:"is-link",onClick:F,onKeyup:X(F,["enter"])},[n(k,{icon:["fas","plus-circle"]}),g(" "+a(e.$t("settings.generate_new_token")),1)],40,ue)]),o(r).length>0?(l(),c("div",de,[(l(!0),c(Y,null,Z(o(r),s=>(l(),c("div",{key:s.id,class:"group-item is-size-5 is-size-6-mobile"},[s.value?(l(),ee(k,{key:0,class:"has-text-success",icon:["fas","check"]})):d("",!0),g(" "+a(s.name)+" ",1),t("div",me,[n(o(se),null,{default:f(({mode:B})=>[s.value?(l(),c("button",{key:0,class:z(["button tag",{"is-link":B!="dark"}]),onClick:A(j=>K(s.value),["stop"])},a(e.$t("commons.copy")),11,_e)):d("",!0),t("button",{class:z(["button tag",B==="dark"?"is-dark":"is-white"]),onClick:j=>M(s.id),title:e.$t("settings.revoke")},a(e.$t("settings.revoke")),11,fe)]),_:2},1024)]),s.value?(l(),c("span",pe,a(e.$t("settings.make_sure_copy_token")),1)):d("",!0),s.value?(l(),c("span",ve,a(s.value),1)):d("",!0)]))),128)),t("div",he,a(e.$t("settings.revoking_a_token_is_permanent")),1)])):d("",!0),n(ne,{isVisible:o(b)&&o(r).length===0},null,8,["isVisible"]),n(R,{showButtons:!0},{default:f(()=>[n(U,{returnTo:{name:o(x)},action:"close"},null,8,["returnTo"])]),_:1})]),_:1})]),o(v)?(l(),c("div",ke,[t("main",ge,[n($,{title:"settings.forms.new_token"},{default:f(()=>[t("form",{onSubmit:A(L,["prevent"]),onKeydown:i[1]||(i[1]=s=>o(u).onKeydown(s))},[n(W,{modelValue:o(u).name,"onUpdate:modelValue":i[0]||(i[0]=s=>o(u).name=s),fieldName:"name",fieldError:o(u).errors.get("name"),inputType:"text",label:"commons.name",autofocus:""},null,8,["modelValue","fieldError"]),t("div",be,[t("div",Te,[n(V,{id:"btnGenerateToken",isLoading:o(u).isBusy},{default:f(()=>[g(a(e.$t("commons.generate")),1)]),_:1},8,["isLoading"])]),t("div",Ce,[n(V,{onClick:I,nativeType:"button",id:"btnCancel",color:"is-text"},{default:f(()=>[g(a(e.$t("commons.cancel")),1)]),_:1})])])],40,ye)]),_:1})])])):d("",!0)])}}};export{Ae as default};

1
public/build/assets/OAuth-c85ae7fb.js vendored Normal file
View File

@ -0,0 +1 @@
import{Q as G,b as H,a2 as R,S as J,r as m,o as O,d as Q,J as z,a0 as q,e as _,f as l,g as c,m as n,h as t,p as f,i as o,l as d,t as a,w as X,j as g,F as Y,G as Z,k as ee,U as se,n as A,D as S,I as y}from"./app-2d89b28f.js";import{F as te}from"./Form-5283f7b6.js";import{_ as oe}from"./SettingTabs-708dbaa6.js";import{S as ne}from"./Spinner-aea2b665.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const ae={class:"options-tabs"},ie=["innerHTML"],le={class:"title is-4 has-text-grey-light"},re={class:"is-size-7-mobile"},ce={class:"mt-3"},ue=["onKeyup"],de={key:1},me={class:"tags is-pulled-right"},_e=["onClick"],fe=["onClick","title"],pe={key:1,class:"is-size-7-mobile is-size-6 my-3"},ve={key:2,class:"pat is-family-monospace is-size-6 is-size-7-mobile has-text-success"},he={class:"mt-2 is-size-7 is-pulled-right"},ke={key:0,class:"is-overlay modal-otp modal-background"},ge={class:"main-section"},ye=["onSubmit"],be={class:"field is-grouped"},Te={class:"control"},Ce={class:"control"},ze={__name:"OAuth",setup(we){const x=G("2fauth"),p=H(),E=R(x.prefix+"returnTo","accounts"),{copy:L}=J({legacy:!0}),r=m([]),b=m(!1),T=m(!1),v=m(!1),h=m(null),C=m(null);O(()=>{w()});const u=Q(new te({name:""}));function w(){b.value=!0,z.getPersonalAccessTokens({returnError:!0}).then(e=>{r.value=[],e.data.forEach(i=>{i.id===C.value?(i.value=h.value,r.value.unshift(i)):r.value.push(i)})}).catch(e=>{e.response.status===405?T.value=!0:p.error(e)}).finally(()=>{b.value=!1,C.value=null,h.value=null})}function F(){P(),T.value?p.warn({text:y("errors.unsupported_with_reverseproxy")}):v.value=!0}function M(){u.post("/oauth/personal-access-tokens").then(e=>{h.value=e.data.accessToken,C.value=e.data.token.id,w(),v.value=!1,u.reset()})}function N(e){confirm(y("settings.confirm.revoke"))&&z.deletePersonalAccessToken(e).then(i=>{r.value=r.value.filter(k=>k.id!==e),p.success({text:y("settings.token_revoked")})})}function P(){r.value.forEach(e=>{e.value=null}),h.value=null}function K(e){L(e),p.success({text:y("commons.copied_to_clipboard")})}function I(){v.value=!1,u.reset()}return q(e=>{e.name.startsWith("settings.")||p.clear()}),(e,i)=>{const k=_("FontAwesomeIcon"),U=_("ButtonBackCloseCancel"),W=_("VueFooter"),V=_("FormWrapper"),j=_("FormField"),$=_("VueButton");return l(),c("div",null,[n(oe,{activeTab:"settings.oauth.tokens"},null,8,["activeTab"]),t("div",ae,[n(V,null,{default:f(()=>[o(T)?(l(),c("div",{key:0,class:"notification is-warning has-text-centered",innerHTML:e.$t("auth.auth_handled_by_proxy")},null,8,ie)):d("",!0),t("h4",le,a(e.$t("settings.personal_access_tokens")),1),t("div",re,a(e.$t("settings.token_legend")),1),t("div",ce,[t("a",{tabindex:"0",class:"is-link",onClick:F,onKeyup:X(F,["enter"])},[n(k,{icon:["fas","plus-circle"]}),g(" "+a(e.$t("settings.generate_new_token")),1)],40,ue)]),o(r).length>0?(l(),c("div",de,[(l(!0),c(Y,null,Z(o(r),s=>(l(),c("div",{key:s.id,class:"group-item is-size-5 is-size-6-mobile"},[s.value?(l(),ee(k,{key:0,class:"has-text-success",icon:["fas","check"]})):d("",!0),g(" "+a(s.name)+" ",1),t("div",me,[n(o(se),null,{default:f(({mode:B})=>[s.value?(l(),c("button",{key:0,class:A(["button tag",{"is-link":B!="dark"}]),onClick:S(D=>K(s.value),["stop"])},a(e.$t("commons.copy")),11,_e)):d("",!0),t("button",{class:A(["button tag",B==="dark"?"is-dark":"is-white"]),onClick:D=>N(s.id),title:e.$t("settings.revoke")},a(e.$t("settings.revoke")),11,fe)]),_:2},1024)]),s.value?(l(),c("span",pe,a(e.$t("settings.make_sure_copy_token")),1)):d("",!0),s.value?(l(),c("span",ve,a(s.value),1)):d("",!0)]))),128)),t("div",he,a(e.$t("settings.revoking_a_token_is_permanent")),1)])):d("",!0),n(ne,{isVisible:o(b)&&o(r).length===0},null,8,["isVisible"]),n(W,{showButtons:!0},{default:f(()=>[n(U,{returnTo:{name:o(E)},action:"close"},null,8,["returnTo"])]),_:1})]),_:1})]),o(v)?(l(),c("div",ke,[t("main",ge,[n(V,{title:"settings.forms.new_token"},{default:f(()=>[t("form",{onSubmit:S(M,["prevent"]),onKeydown:i[1]||(i[1]=s=>o(u).onKeydown(s))},[n(j,{modelValue:o(u).name,"onUpdate:modelValue":i[0]||(i[0]=s=>o(u).name=s),fieldName:"name",fieldError:o(u).errors.get("name"),inputType:"text",label:"commons.name",autofocus:""},null,8,["modelValue","fieldError"]),t("div",be,[t("div",Te,[n($,{id:"btnGenerateToken",isLoading:o(u).isBusy},{default:f(()=>[g(a(e.$t("commons.generate")),1)]),_:1},8,["isLoading"])]),t("div",Ce,[n($,{onClick:I,nativeType:"button",id:"btnCancel",color:"is-text"},{default:f(()=>[g(a(e.$t("commons.cancel")),1)]),_:1})])])],40,ye)]),_:1})])])):d("",!0)])}}};export{ze as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{u as _,Z as m,r as p,_ as f,J as h,e as c,f as n,g as r,h as a,i as t,l as B,m as s,p as g}from"./app-1b332c21.js";import{S as k}from"./Spinner-b3cbad3a.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const v={class:"modal modal-otp is-active"},C=a("div",{class:"modal-background"},null,-1),V={class:"modal-content"},w={class:"has-text-centered m-5"},b=["src","alt"],F={__name:"QRcode",setup(y){_();const l=m(),e=p();f(()=>{i()});async function i(){const{data:o}=await h.getQrcode(l.params.twofaccountId);e.value=o.qrcode}return(o,R)=>{const u=c("ButtonBackCloseCancel"),d=c("VueFooter");return n(),r("div",v,[C,a("div",V,[a("p",w,[t(e)?(n(),r("img",{key:0,src:t(e),class:"has-background-light",alt:o.$t("commons.image_of_qrcode_to_scan")},null,8,b)):B("",!0),s(k,{isVisible:!t(e),type:"raw",class:"is-size-1"},null,8,["isVisible"])])]),s(d,{showButtons:!0,internalFooterType:"modal"},{default:g(()=>[s(u,{returnTo:{name:"accounts"},action:"close"})]),_:1})])}}};export{F as default};
import{u as _,_ as m,r as p,$ as f,K as h,e as c,f as n,g as r,h as a,i as t,l as B,m as s,p as g}from"./app-2d89b28f.js";import{S as k}from"./Spinner-aea2b665.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const v={class:"modal modal-otp is-active"},C=a("div",{class:"modal-background"},null,-1),V={class:"modal-content"},w={class:"has-text-centered m-5"},b=["src","alt"],F={__name:"QRcode",setup(y){_();const l=m(),e=p();f(()=>{i()});async function i(){const{data:o}=await h.getQrcode(l.params.twofaccountId);e.value=o.qrcode}return(o,R)=>{const u=c("ButtonBackCloseCancel"),d=c("VueFooter");return n(),r("div",v,[C,a("div",V,[a("p",w,[t(e)?(n(),r("img",{key:0,src:t(e),class:"has-background-light",alt:o.$t("commons.image_of_qrcode_to_scan")},null,8,b)):B("",!0),s(k,{isVisible:!t(e),type:"raw",class:"is-size-1"},null,8,["isVisible"])])]),s(d,{showButtons:!0,internalFooterType:"modal"},{default:g(()=>[s(u,{returnTo:{name:"accounts"},action:"close"})]),_:1})])}}};export{F as default};

View File

@ -1 +1 @@
import{R as h,b as v,e as w,f as r,g as l,h as n,t as s,m as c,p as _,n as y,i as d,I as u,U as g,D as q,l as m,F as $}from"./app-1b332c21.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const x=n("div",{class:"too-bad"},null,-1),E={class:"block"},I={key:0,class:"block has-text-link"},B={class:"button is-link is-outlined is-rounded"},F={class:"icon is-small"},R={__name:"QrContentDisplay",props:{qrContent:String},setup(o){const{copy:b}=h({legacy:!0}),k=v();function p(t){var e=/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/,a=new RegExp(e);return a.test(t)}function C(t){const e=document.createElement("a");e.setAttribute("href",t),e.dispatchEvent(new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0}))}function f(t){b(t),k.success({text:u("commons.copied_to_clipboard")})}return(t,e)=>{const a=w("FontAwesomeIcon");return r(),l($,null,[x,n("div",E,s(t.$t("errors.data_of_qrcode_is_not_valid_URI")),1),c(d(g),null,{default:_(({mode:i})=>[n("div",{class:y(["block mb-6",i=="dark"?"has-text-light":"has-text-grey-dark"])},s(o.qrContent?o.qrContent:"["+("trans"in t?t.trans:d(u))("commons.nothing")+"]"),3)]),_:1}),o.qrContent?(r(),l("div",I,[n("button",{class:"button is-link is-outlined is-rounded",onClick:e[0]||(e[0]=q(i=>f(o.qrContent),["stop"]))},s(t.$t("commons.copy_to_clipboard")),1)])):m("",!0),p(o.qrContent)?(r(),l("div",{key:1,class:"block has-text-link",onClick:e[1]||(e[1]=i=>C(o.qrContent))},[n("button",B,[n("span",null,s(t.$t("commons.open_in_browser")),1),n("span",F,[c(a,{icon:["fas","external-link-alt"]})])])])):m("",!0)],64)}}};export{R as _};
import{S as h,b as v,e as w,f as r,g as l,h as n,t as s,m as c,p as _,n as y,i as d,I as u,U as g,D as q,l as m,F as $}from"./app-2d89b28f.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const x=n("div",{class:"too-bad"},null,-1),E={class:"block"},I={key:0,class:"block has-text-link"},B={class:"button is-link is-outlined is-rounded"},F={class:"icon is-small"},S={__name:"QrContentDisplay",props:{qrContent:String},setup(o){const{copy:b}=h({legacy:!0}),k=v();function p(t){var e=/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/,a=new RegExp(e);return a.test(t)}function C(t){const e=document.createElement("a");e.setAttribute("href",t),e.dispatchEvent(new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0}))}function f(t){b(t),k.success({text:u("commons.copied_to_clipboard")})}return(t,e)=>{const a=w("FontAwesomeIcon");return r(),l($,null,[x,n("div",E,s(t.$t("errors.data_of_qrcode_is_not_valid_URI")),1),c(d(g),null,{default:_(({mode:i})=>[n("div",{class:y(["block mb-6",i=="dark"?"has-text-light":"has-text-grey-dark"])},s(o.qrContent?o.qrContent:"["+("trans"in t?t.trans:d(u))("commons.nothing")+"]"),3)]),_:1}),o.qrContent?(r(),l("div",I,[n("button",{class:"button is-link is-outlined is-rounded",onClick:e[0]||(e[0]=q(i=>f(o.qrContent),["stop"]))},s(t.$t("commons.copy_to_clipboard")),1)])):m("",!0),p(o.qrContent)?(r(),l("div",{key:1,class:"block has-text-link",onClick:e[1]||(e[1]=i=>C(o.qrContent))},[n("button",B,[n("span",null,s(t.$t("commons.open_in_browser")),1),n("span",F,[c(a,{icon:["fas","external-link-alt"]})])])])):m("",!0)],64)}}};export{S as _};

View File

@ -0,0 +1 @@
import{Q as V,b as B,u as R,_ as g,a2 as $,d as N,a0 as C,e as a,f as D,k as S,p as d,h as u,D as q,i as s,m as l,j as c,t as m,I as A}from"./app-2d89b28f.js";import{F as L}from"./Form-5283f7b6.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const W=["onSubmit"],x={class:"field"},I={__name:"Recover",setup(E){const p=V("2fauth"),r=B(),f=R(),i=g(),h=$(p.prefix+"showWebauthnForm",!1),o=N(new L({email:i.query.email,password:"",token:i.query.token,revokeAll:!1}));function _(t){r.clear(),o.post("/webauthn/recover",{returnError:!0}).then(e=>{h.value=!1,f.push({name:"login"})}).catch(e=>{e.response.status===401?r.alert({text:A("auth.forms.authentication_failed"),duration:-1}):e.response.status===422?r.alert({text:e.response.data.message,duration:-1}):r.error(e)})}return C(()=>{r.clear()}),(t,e)=>{const w=a("FormCheckbox"),b=a("FormPasswordField"),v=a("RouterLink"),F=a("FormButtons"),k=a("VueFooter"),y=a("FormWrapper");return D(),S(y,{title:t.$t("auth.webauthn.account_recovery"),punchline:t.$t("auth.webauthn.recover_account_instructions")},{default:d(()=>[u("div",null,[u("form",{onSubmit:q(_,["prevent"]),onKeydown:e[2]||(e[2]=n=>s(o).onKeydown(n))},[l(w,{modelValue:s(o).revokeAll,"onUpdate:modelValue":e[0]||(e[0]=n=>s(o).revokeAll=n),fieldName:"revokeAll",label:"auth.webauthn.disable_all_security_devices",help:"auth.webauthn.disable_all_security_devices_help"},null,8,["modelValue"]),l(b,{modelValue:s(o).password,"onUpdate:modelValue":e[1]||(e[1]=n=>s(o).password=n),fieldName:"password",fieldError:s(o).errors.get("password"),autocomplete:"current-password",showRules:!1,label:"auth.forms.current_password.label",help:"auth.forms.current_password.help"},null,8,["modelValue","fieldError"]),u("div",x,[u("p",null,[c(m(t.$t("auth.forms.forgot_your_password"))+"  ",1),l(v,{id:"lnkResetPwd",to:{name:"password.request"},class:"is-link","aria-label":t.$t("auth.forms.reset_your_password")},{default:d(()=>[c(m(t.$t("auth.forms.request_password_reset")),1)]),_:1},8,["to","aria-label"])])]),l(F,{submitId:"btnRecover",isBusy:s(o).isBusy,isDisabled:s(o).isDisabled,caption:t.$t("commons.continue"),showCancelButton:!0,cancelLandingView:"login"},null,8,["isBusy","isDisabled","caption"])],40,W)]),l(k)]),_:1},8,["title","punchline"])}}};export{I as default};

View File

@ -1 +0,0 @@
import{P as V,b as B,u as R,Z as g,a2 as $,d as N,$ as C,e as a,f as D,k as S,p as d,h as u,D as q,i as s,m as l,j as c,t as m,I as A}from"./app-1b332c21.js";import{F as L}from"./Form-940b5f6c.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const P=["onSubmit"],W={class:"field"},I={__name:"Recover",setup(x){const p=V("2fauth"),r=B(),f=R(),i=g(),h=$(p.prefix+"showWebauthnForm",!1),o=N(new L({email:i.query.email,password:"",token:i.query.token,revokeAll:!1}));function _(t){r.clear(),o.post("/webauthn/recover",{returnError:!0}).then(e=>{h.value=!1,f.push({name:"login"})}).catch(e=>{e.response.status===401?r.alert({text:A("auth.forms.authentication_failed"),duration:-1}):e.response.status===422?r.alert({text:e.response.data.message,duration:-1}):r.error(e)})}return C(()=>{r.clear()}),(t,e)=>{const w=a("FormCheckbox"),b=a("FormPasswordField"),v=a("RouterLink"),F=a("FormButtons"),k=a("VueFooter"),y=a("FormWrapper");return D(),S(y,{title:t.$t("auth.webauthn.account_recovery"),punchline:t.$t("auth.webauthn.recover_account_instructions")},{default:d(()=>[u("div",null,[u("form",{onSubmit:q(_,["prevent"]),onKeydown:e[2]||(e[2]=n=>s(o).onKeydown(n))},[l(w,{modelValue:s(o).revokeAll,"onUpdate:modelValue":e[0]||(e[0]=n=>s(o).revokeAll=n),fieldName:"revokeAll",label:"auth.webauthn.disable_all_security_devices",help:"auth.webauthn.disable_all_security_devices_help"},null,8,["modelValue"]),l(b,{modelValue:s(o).password,"onUpdate:modelValue":e[1]||(e[1]=n=>s(o).password=n),fieldName:"password",fieldError:s(o).errors.get("password"),autocomplete:"current-password",showRules:!1,label:"auth.forms.current_password.label",help:"auth.forms.current_password.help"},null,8,["modelValue","fieldError"]),u("div",W,[u("p",null,[c(m(t.$t("auth.forms.forgot_your_password"))+"  ",1),l(v,{id:"lnkResetPwd",to:{name:"password.request"},class:"is-link","aria-label":t.$t("auth.forms.reset_your_password")},{default:d(()=>[c(m(t.$t("auth.forms.request_password_reset")),1)]),_:1},8,["to","aria-label"])])]),l(F,{submitId:"btnRecover",isBusy:s(o).isBusy,isDisabled:s(o).isDisabled,caption:t.$t("commons.continue"),showCancelButton:!0,cancelLandingView:"login"},null,8,["isBusy","isDisabled","caption"])],40,P)]),l(k)]),_:1},8,["title","punchline"])}}};export{I as default};

View File

@ -1 +0,0 @@
import{a as T,b as C,u as I,r as F,d as k,$ as K,e as l,f as u,g as _,i as t,k as V,p as c,m as n,h as i,j as p,t as d,D,I as U}from"./app-1b332c21.js";import{F as B}from"./Form-940b5f6c.js";import{w as W}from"./webauthnService-839b1903.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const P={key:0,class:"field"},M={id:"lblDeviceRegistrationSuccess",class:"label mb-5"},j=["onSubmit"],A={key:1,class:"field is-grouped"},J={class:"control"},O={class:"control"},Y=["onSubmit"],q={class:"nav-links"},X={__name:"Register",setup(z){const b=T(),m=C(),R=I(),w=F(!1),f=F(null),s=k(new B({name:"",email:"",password:"",password_confirmation:""})),r=k(new B({name:""}));async function S(o){s.password_confirmation=s.password,s.post("/user").then(e=>{b.$patch({name:e.data.name,email:e.data.email,preferences:e.data.preferences,isAdmin:e.data.is_admin??!1}),b.applyTheme(),w.value=!0})}function N(){W.register().then(o=>{const e=JSON.parse(o.config.data);f.value=e.id}).catch(o=>{o.response.status===422?m.alert({text:o.response.data.message}):m.error(o)})}function E(o){r.patch("/webauthn/credentials/"+f.value+"/name").then(()=>{m.success({text:U("auth.webauthn.device_successfully_registered")}),R.push({name:"accounts"})})}return K(()=>{m.clear()}),(o,e)=>{const $=l("font-awesome-icon"),h=l("FormField"),g=l("FormButtons"),y=l("RouterLink"),v=l("FormWrapper"),x=l("FormPasswordField"),L=l("VueFooter");return u(),_("div",null,[t(w)?(u(),V(v,{key:0,title:"auth.authentication",punchline:"auth.webauthn.enhance_security_using_webauthn"},{default:c(()=>[t(f)?(u(),_("div",P,[i("label",M,[p(d(o.$t("auth.webauthn.device_successfully_registered"))+" ",1),n($,{icon:["fas","check"]})]),i("form",{onSubmit:D(E,["prevent"]),onKeydown:e[1]||(e[1]=a=>t(r).onKeydown(a))},[n(h,{modelValue:t(r).name,"onUpdate:modelValue":e[0]||(e[0]=a=>t(r).name=a),fieldName:"name",fieldError:t(r).errors.get("name"),inputType:"text",placeholder:"iPhone 12, TouchID, Yubikey 5C",label:"auth.forms.name_this_device"},null,8,["modelValue","fieldError"]),n(g,{isBusy:t(r).isBusy,isDisabled:t(r).isDisabled,caption:"commons.continue"},null,8,["isBusy","isDisabled"])],40,j)])):(u(),_("div",A,[i("div",J,[i("button",{type:"button",id:"btnRegisterNewDevice",onClick:e[2]||(e[2]=a=>N()),class:"button is-link"},d(o.$t("auth.webauthn.register_a_device")),1)]),i("div",O,[n(y,{id:"btnMaybeLater",to:{name:"accounts"},class:"button is-text"},{default:c(()=>[p(d(o.$t("auth.maybe_later")),1)]),_:1})])]))]),_:1})):(u(),V(v,{key:1,title:"auth.register",punchline:"auth.forms.register_punchline"},{default:c(()=>[i("form",{onSubmit:D(S,["prevent"]),onKeydown:e[6]||(e[6]=a=>t(s).onKeydown(a))},[n(h,{modelValue:t(s).name,"onUpdate:modelValue":e[3]||(e[3]=a=>t(s).name=a),fieldName:"name",fieldError:t(s).errors.get("name"),inputType:"text",label:"auth.forms.name",maxLength:255,autofocus:""},null,8,["modelValue","fieldError"]),n(h,{modelValue:t(s).email,"onUpdate:modelValue":e[4]||(e[4]=a=>t(s).email=a),fieldName:"email",fieldError:t(s).errors.get("email"),inputType:"email",label:"auth.forms.email",maxLength:255},null,8,["modelValue","fieldError"]),n(x,{modelValue:t(s).password,"onUpdate:modelValue":e[5]||(e[5]=a=>t(s).password=a),fieldName:"password",fieldError:t(s).errors.get("password"),showRules:!0,label:"auth.forms.password"},null,8,["modelValue","fieldError"]),n(g,{isBusy:t(s).isBusy,isDisabled:t(s).isDisabled,caption:"auth.register",submitId:"btnRegister"},null,8,["isBusy","isDisabled"])],40,Y),i("div",q,[i("p",null,[p(d(o.$t("auth.forms.already_register"))+" ",1),n(y,{id:"lnkSignIn",to:{name:"login"},class:"is-link"},{default:c(()=>[p(d(o.$t("auth.sign_in")),1)]),_:1})])])]),_:1})),n(L)])}}};export{X as default};

View File

@ -0,0 +1 @@
import{a as T,b as C,u as I,r as F,d as k,a0 as K,e as l,f as u,g as _,i as t,k as V,p as c,m as n,h as i,j as p,t as d,D,I as U}from"./app-2d89b28f.js";import{F as B}from"./Form-5283f7b6.js";import{w as W}from"./webauthnService-43ef310f.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const P={key:0,class:"field"},M={id:"lblDeviceRegistrationSuccess",class:"label mb-5"},j=["onSubmit"],A={key:1,class:"field is-grouped"},J={class:"control"},O={class:"control"},Y=["onSubmit"],q={class:"nav-links"},X={__name:"Register",setup(z){const b=T(),m=C(),R=I(),w=F(!1),f=F(null),s=k(new B({name:"",email:"",password:"",password_confirmation:""})),r=k(new B({name:""}));async function S(o){s.password_confirmation=s.password,s.post("/user").then(e=>{b.$patch({name:e.data.name,email:e.data.email,preferences:e.data.preferences,isAdmin:e.data.is_admin??!1}),b.applyTheme(),w.value=!0})}function N(){W.register().then(o=>{const e=JSON.parse(o.config.data);f.value=e.id}).catch(o=>{o.response.status===422?m.alert({text:o.response.data.message}):m.error(o)})}function E(o){r.patch("/webauthn/credentials/"+f.value+"/name").then(()=>{m.success({text:U("auth.webauthn.device_successfully_registered")}),R.push({name:"accounts"})})}return K(()=>{m.clear()}),(o,e)=>{const $=l("font-awesome-icon"),h=l("FormField"),g=l("FormButtons"),y=l("RouterLink"),v=l("FormWrapper"),x=l("FormPasswordField"),L=l("VueFooter");return u(),_("div",null,[t(w)?(u(),V(v,{key:0,title:"auth.authentication",punchline:"auth.webauthn.enhance_security_using_webauthn"},{default:c(()=>[t(f)?(u(),_("div",P,[i("label",M,[p(d(o.$t("auth.webauthn.device_successfully_registered"))+" ",1),n($,{icon:["fas","check"]})]),i("form",{onSubmit:D(E,["prevent"]),onKeydown:e[1]||(e[1]=a=>t(r).onKeydown(a))},[n(h,{modelValue:t(r).name,"onUpdate:modelValue":e[0]||(e[0]=a=>t(r).name=a),fieldName:"name",fieldError:t(r).errors.get("name"),inputType:"text",placeholder:"iPhone 12, TouchID, Yubikey 5C",label:"auth.forms.name_this_device"},null,8,["modelValue","fieldError"]),n(g,{isBusy:t(r).isBusy,isDisabled:t(r).isDisabled,caption:"commons.continue"},null,8,["isBusy","isDisabled"])],40,j)])):(u(),_("div",A,[i("div",J,[i("button",{type:"button",id:"btnRegisterNewDevice",onClick:e[2]||(e[2]=a=>N()),class:"button is-link"},d(o.$t("auth.webauthn.register_a_device")),1)]),i("div",O,[n(y,{id:"btnMaybeLater",to:{name:"accounts"},class:"button is-text"},{default:c(()=>[p(d(o.$t("auth.maybe_later")),1)]),_:1})])]))]),_:1})):(u(),V(v,{key:1,title:"auth.register",punchline:"auth.forms.register_punchline"},{default:c(()=>[i("form",{onSubmit:D(S,["prevent"]),onKeydown:e[6]||(e[6]=a=>t(s).onKeydown(a))},[n(h,{modelValue:t(s).name,"onUpdate:modelValue":e[3]||(e[3]=a=>t(s).name=a),fieldName:"name",fieldError:t(s).errors.get("name"),inputType:"text",label:"auth.forms.name",maxLength:255,autofocus:""},null,8,["modelValue","fieldError"]),n(h,{modelValue:t(s).email,"onUpdate:modelValue":e[4]||(e[4]=a=>t(s).email=a),fieldName:"email",fieldError:t(s).errors.get("email"),inputType:"email",label:"auth.forms.email",maxLength:255},null,8,["modelValue","fieldError"]),n(x,{modelValue:t(s).password,"onUpdate:modelValue":e[5]||(e[5]=a=>t(s).password=a),fieldName:"password",fieldError:t(s).errors.get("password"),showRules:!0,label:"auth.forms.password"},null,8,["modelValue","fieldError"]),n(g,{isBusy:t(s).isBusy,isDisabled:t(s).isDisabled,caption:"auth.register",submitId:"btnRegister"},null,8,["isBusy","isDisabled"])],40,Y),i("div",q,[i("p",null,[p(d(o.$t("auth.forms.already_register"))+" ",1),n(y,{id:"lnkSignIn",to:{name:"login"},class:"is-link"},{default:c(()=>[p(d(o.$t("auth.sign_in")),1)]),_:1})])])]),_:1})),n(L)])}}};export{X as default};

View File

@ -1 +0,0 @@
import{b as f,Z as h,d as _,$ as w,e as r,f as F,k as b,p as y,h as B,m as i,i as s,D as V}from"./app-1b332c21.js";import{F as v}from"./Form-940b5f6c.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const R=["onSubmit"],S={__name:"RequestReset",setup(k){const o=f(),n=h().name=="webauthn.lost",t=_(new v({email:""}));function l(a){o.clear(),t.post(n?"/webauthn/lost":"/user/password/lost",{returnError:!0}).then(e=>{o.success({text:e.data.message,duration:-1})}).catch(e=>{e.response.data.requestFailed?o.alert({text:e.response.data.requestFailed,duration:-1}):e.response.status!==422&&o.error(e)})}return w(()=>{o.clear()}),(a,e)=>{const m=r("FormField"),c=r("FormButtons"),d=r("VueFooter"),p=r("FormWrapper");return F(),b(p,{title:a.$t(n?"auth.webauthn.account_recovery":"auth.forms.reset_password"),punchline:a.$t(n?"auth.webauthn.recovery_punchline":"auth.forms.reset_punchline")},{default:y(()=>[B("form",{onSubmit:V(l,["prevent"]),onKeydown:e[1]||(e[1]=u=>s(t).onKeydown(u))},[i(m,{modelValue:s(t).email,"onUpdate:modelValue":e[0]||(e[0]=u=>s(t).email=u),fieldName:"email",fieldError:s(t).errors.get("email"),label:"auth.forms.email",autofocus:""},null,8,["modelValue","fieldError"]),i(c,{submitId:"btnSendResetPwd",isBusy:s(t).isBusy,caption:a.$t(n?"auth.webauthn.send_recovery_link":"auth.forms.send_password_reset_link"),showCancelButton:!0,cancelLandingView:"login"},null,8,["isBusy","caption"])],40,R),i(d)]),_:1},8,["title","punchline"])}}};export{S as default};

View File

@ -0,0 +1 @@
import{b as f,_,d as h,a0 as w,e as r,f as F,k as b,p as y,h as B,m as i,i as s,D as V}from"./app-2d89b28f.js";import{F as v}from"./Form-5283f7b6.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const R=["onSubmit"],S={__name:"RequestReset",setup(k){const o=f(),n=_().name=="webauthn.lost",t=h(new v({email:""}));function l(a){o.clear(),t.post(n?"/webauthn/lost":"/user/password/lost",{returnError:!0}).then(e=>{o.success({text:e.data.message,duration:-1})}).catch(e=>{e.response.data.requestFailed?o.alert({text:e.response.data.requestFailed,duration:-1}):e.response.status!==422&&o.error(e)})}return w(()=>{o.clear()}),(a,e)=>{const m=r("FormField"),c=r("FormButtons"),d=r("VueFooter"),p=r("FormWrapper");return F(),b(p,{title:a.$t(n?"auth.webauthn.account_recovery":"auth.forms.reset_password"),punchline:a.$t(n?"auth.webauthn.recovery_punchline":"auth.forms.reset_punchline")},{default:y(()=>[B("form",{onSubmit:V(l,["prevent"]),onKeydown:e[1]||(e[1]=u=>s(t).onKeydown(u))},[i(m,{modelValue:s(t).email,"onUpdate:modelValue":e[0]||(e[0]=u=>s(t).email=u),fieldName:"email",fieldError:s(t).errors.get("email"),label:"auth.forms.email",autofocus:""},null,8,["modelValue","fieldError"]),i(c,{submitId:"btnSendResetPwd",isBusy:s(t).isBusy,caption:a.$t(n?"auth.webauthn.send_recovery_link":"auth.forms.send_password_reset_link"),showCancelButton:!0,cancelLandingView:"login"},null,8,["isBusy","caption"])],40,R),i(d)]),_:1},8,["title","punchline"])}}};export{S as default};

View File

@ -1 +0,0 @@
import{b as g,u as B,Z as h,r as R,d as E,$ as N,e as s,f as i,k as l,p as c,h as v,m as d,i as t,l as m,j as C,t as P,D as L}from"./app-1b332c21.js";import{F as S}from"./Form-940b5f6c.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const $=["onSubmit"],K={__name:"Reset",setup(x){const n=g();B();const p=h(),u=R(!0),e=E(new S({email:p.query.email,password:"",password_confirmation:"",token:p.query.token}));function f(a){e.password_confirmation=e.password,e.post("/user/password/reset",{returnError:!0}).then(o=>{e.password="",e.password_confirmation="",u.value=!1,n.success({text:o.data.message,duration:-1})}).catch(o=>{o.response.data.resetFailed?n.alert({text:o.response.data.resetFailed,duration:-1}):o.response.status!==422&&n.error(o)})}return N(()=>{n.clear()}),(a,o)=>{const w=s("FormField"),_=s("FormPasswordField"),F=s("FieldError"),k=s("FormButtons"),y=s("RouterLink"),V=s("VueFooter"),b=s("FormWrapper");return i(),l(b,{title:a.$t("auth.forms.new_password")},{default:c(()=>[v("form",{onSubmit:L(f,["prevent"]),onKeydown:o[2]||(o[2]=r=>t(e).onKeydown(r))},[d(w,{modelValue:t(e).email,"onUpdate:modelValue":o[0]||(o[0]=r=>t(e).email=r),isDisabled:!0,fieldName:"email",fieldError:t(e).errors.get("email"),label:"auth.forms.email",autofocus:""},null,8,["modelValue","fieldError"]),d(_,{modelValue:t(e).password,"onUpdate:modelValue":o[1]||(o[1]=r=>t(e).password=r),fieldName:"password",fieldError:t(e).errors.get("password"),autocomplete:"new-password",showRules:!0,label:"auth.forms.new_password"},null,8,["modelValue","fieldError"]),t(e).errors.get("token")!=null?(i(),l(F,{key:0,error:t(e).errors.get("token"),field:t(e).token},null,8,["error","field"])):m("",!0),t(u)?(i(),l(k,{key:1,submitId:"btnResetPwd",isBusy:t(e).isBusy,caption:a.$t("auth.forms.change_password"),showCancelButton:!0,cancelLandingView:"login"},null,8,["isBusy","caption"])):m("",!0),t(u)?m("",!0):(i(),l(y,{key:2,id:"btnContinue",to:{name:"accounts"},class:"button is-link"},{default:c(()=>[C(P(a.$t("commons.continue")),1)]),_:1}))],40,$),d(V)]),_:1},8,["title"])}}};export{K as default};

1
public/build/assets/Reset-78a79c23.js vendored Normal file
View File

@ -0,0 +1 @@
import{b as g,u as B,_ as h,r as R,d as E,a0 as N,e as s,f as i,k as l,p as c,h as v,m as d,i as t,l as m,j as C,t as P,D as L}from"./app-2d89b28f.js";import{F as S}from"./Form-5283f7b6.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const x=["onSubmit"],K={__name:"Reset",setup(D){const a=g();B();const p=h(),u=R(!0),e=E(new S({email:p.query.email,password:"",password_confirmation:"",token:p.query.token}));function f(n){e.password_confirmation=e.password,e.post("/user/password/reset",{returnError:!0}).then(o=>{e.password="",e.password_confirmation="",u.value=!1,a.success({text:o.data.message,duration:-1})}).catch(o=>{o.response.data.resetFailed?a.alert({text:o.response.data.resetFailed,duration:-1}):o.response.status!==422&&a.error(o)})}return N(()=>{a.clear()}),(n,o)=>{const w=s("FormField"),_=s("FormPasswordField"),F=s("FieldError"),k=s("FormButtons"),y=s("RouterLink"),V=s("VueFooter"),b=s("FormWrapper");return i(),l(b,{title:n.$t("auth.forms.new_password")},{default:c(()=>[v("form",{onSubmit:L(f,["prevent"]),onKeydown:o[2]||(o[2]=r=>t(e).onKeydown(r))},[d(w,{modelValue:t(e).email,"onUpdate:modelValue":o[0]||(o[0]=r=>t(e).email=r),isDisabled:!0,fieldName:"email",fieldError:t(e).errors.get("email"),label:"auth.forms.email",autofocus:""},null,8,["modelValue","fieldError"]),d(_,{modelValue:t(e).password,"onUpdate:modelValue":o[1]||(o[1]=r=>t(e).password=r),fieldName:"password",fieldError:t(e).errors.get("password"),autocomplete:"new-password",showRules:!0,label:"auth.forms.new_password"},null,8,["modelValue","fieldError"]),t(e).errors.get("token")!=null?(i(),l(F,{key:0,error:t(e).errors.get("token"),field:t(e).token},null,8,["error","field"])):m("",!0),t(u)?(i(),l(k,{key:1,submitId:"btnResetPwd",isBusy:t(e).isBusy,caption:n.$t("auth.forms.change_password"),showCancelButton:!0,cancelLandingView:"login"},null,8,["isBusy","caption"])):m("",!0),t(u)?m("",!0):(i(),l(y,{key:2,id:"btnContinue",to:{name:"accounts"},class:"button is-link"},{default:c(()=>[C(P(n.$t("commons.continue")),1)]),_:1}))],40,x),d(V)]),_:1},8,["title"])}}};export{K as default};

View File

@ -0,0 +1 @@
import{r as d,o as u,s as h,e as m,f as n,g as c,h as a,n as k,k as p}from"./app-2d89b28f.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const y={role:"search",class:"field"},f={class:"control has-icons-right"},v=["aria-label","title","placeholder","value"],g={class:"icon is-small is-right"},w=["title"],b={__name:"SearchBox",props:{keyword:String,hasNoBackground:{type:Boolean,default:!1},placeholder:String},setup(t){const s=d(null);u(()=>{document.addEventListener("keydown",r)}),h(()=>{document.removeEventListener("keydown",r)});function r(e){var o;e.key==="f"&&(e.ctrlKey||e.metaKey)&&(e.preventDefault(),(o=s.value)==null||o.focus())}return(e,o)=>{const i=m("FontAwesomeIcon");return n(),c("div",y,[a("div",f,[a("input",{ref_key:"searchInput",ref:s,id:"txtSearch",type:"search",tabindex:"1","aria-label":e.$t("commons.search"),title:e.$t("commons.search"),placeholder:t.placeholder,class:k(["input is-rounded is-search",{"has-no-background":t.hasNoBackground}]),value:t.keyword,onKeyup:o[0]||(o[0]=l=>e.$emit("update:keyword",l.target.value))},null,42,v),a("span",g,[t.keyword!=""?(n(),c("button",{key:0,id:"btnClearSearch",tabindex:"1",title:e.$t("commons.clear_search"),class:"clear-selection delete",onClick:o[1]||(o[1]=l=>e.$emit("update:keyword",""))},null,8,w)):(n(),p(i,{key:1,icon:["fas","search"]}))])])])}}};export{b as _};

View File

@ -1 +1 @@
import{r as m,e as n,f as t,g as s,m as a,p as i,h as o,F as v,G as _,i as h,n as g,j as w,t as k}from"./app-1b332c21.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const f={class:"options-header"},b={class:"tabs is-centered is-fullwidth"},R={__name:"SettingTabs",props:{activeTab:{type:String,default:""}},setup(r){const c=r,l=m([{name:"settings.options",view:"settings.options",id:"lnkTabOptions"},{name:"settings.account",view:"settings.account",id:"lnkTabAccount"},{name:"settings.oauth",view:"settings.oauth.tokens",id:"lnkTabOAuth"},{name:"settings.webauthn",view:"settings.webauthn.devices",id:"lnkTabWebauthn"}]);return(u,T)=>{const d=n("RouterLink"),p=n("ResponsiveWidthWrapper");return t(),s("div",f,[a(p,null,{default:i(()=>[o("div",b,[o("ul",null,[(t(!0),s(v,null,_(h(l),e=>(t(),s("li",{key:e.view,class:g({"is-active":e.view===c.activeTab})},[a(d,{id:e.id,to:{name:e.view}},{default:i(()=>[w(k(u.$t(e.name)),1)]),_:2},1032,["id","to"])],2))),128))])])]),_:1})])}}};export{R as _};
import{r as m,e as n,f as t,g as s,m as a,p as i,h as o,F as v,G as _,i as h,n as g,j as w,t as k}from"./app-2d89b28f.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const f={class:"options-header"},b={class:"tabs is-centered is-fullwidth"},R={__name:"SettingTabs",props:{activeTab:{type:String,default:""}},setup(r){const c=r,l=m([{name:"settings.options",view:"settings.options",id:"lnkTabOptions"},{name:"settings.account",view:"settings.account",id:"lnkTabAccount"},{name:"settings.oauth",view:"settings.oauth.tokens",id:"lnkTabOAuth"},{name:"settings.webauthn",view:"settings.webauthn.devices",id:"lnkTabWebauthn"}]);return(u,T)=>{const d=n("RouterLink"),p=n("ResponsiveWidthWrapper");return t(),s("div",f,[a(p,null,{default:i(()=>[o("div",b,[o("ul",null,[(t(!0),s(v,null,_(h(l),e=>(t(),s("li",{key:e.view,class:g({"is-active":e.view===c.activeTab})},[a(d,{id:e.id,to:{name:e.view}},{default:i(()=>[w(k(u.$t(e.name)),1)]),_:2},1032,["id","to"])],2))),128))])])]),_:1})])}}};export{R as _};

View File

@ -1 +0,0 @@
.spinner-container[data-v-56b21d53],.spinner-overlay-container[data-v-56b21d53]{text-align:center;z-index:100000;position:absolute;top:0;left:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center}.spinner-container[data-v-56b21d53],.spinner-overlay-container[data-v-56b21d53]{top:25%;height:50%}.spinner[data-v-56b21d53]{display:block}

View File

@ -0,0 +1 @@
.spinner-container[data-v-247a4fa7],.spinner-overlay-container[data-v-247a4fa7]{text-align:center;z-index:100000;position:absolute;top:0;left:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center}.spinner-container[data-v-247a4fa7],.spinner-overlay-container[data-v-247a4fa7]{top:25%;height:50%}.spinner[data-v-247a4fa7]{display:block}

View File

@ -1 +1 @@
import{a4 as p,e as l,f as n,g as t,h as e,m as o,t as c,l as r,k as d}from"./app-1b332c21.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const _={key:0},m={key:0,class:"spinner-container"},y={class:"spinner-wrapper"},u={id:"icnSpinnerFull",class:"is-size-1 spinner"},f={key:1,class:"spinner-overlay-container"},h={class:"spinner-wrapper"},v={id:"icnSpinnerFull",class:"is-size-1 spinner"},g={key:3,class:"has-text-centered mt-6"},S={id:"icnSpinner",class:"is-size-4"},k={__name:"Spinner",props:{isVisible:Boolean,type:{type:String,default:"inline"},message:{type:String,default:"commons.generating_otp"}},setup(s){return(a,w)=>{const i=l("FontAwesomeIcon");return s.isVisible?(n(),t("div",_,[s.type=="fullscreen"?(n(),t("div",m,[e("div",y,[e("span",u,[o(i,{icon:["fas","spinner"],spin:""})]),e("span",null,c(a.$t(s.message)),1)])])):r("",!0),s.type=="fullscreen-overlay"?(n(),t("div",f,[e("div",h,[e("span",v,[o(i,{icon:["fas","spinner"],spin:""})]),e("span",null,c(a.$t(s.message)),1)])])):s.type=="raw"?(n(),d(i,{key:2,icon:["fas","spinner"],spin:""})):(n(),t("div",g,[e("span",S,[o(i,{icon:["fas","spinner"],spin:""})])]))])):r("",!0)}}},V=p(k,[["__scopeId","data-v-56b21d53"]]);export{V as S};
import{a5 as p,e as l,f as n,g as t,h as e,m as a,t as c,l as r,k as d}from"./app-2d89b28f.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const _={key:0},m={key:0,class:"spinner-container"},y={class:"spinner-wrapper"},u={id:"icnSpinnerFull",class:"is-size-1 spinner"},f={key:1,class:"spinner-overlay-container"},h={class:"spinner-wrapper"},v={id:"icnSpinnerFull",class:"is-size-1 spinner"},g={key:3,class:"has-text-centered mt-6"},S={id:"icnSpinner",class:"is-size-4"},k={__name:"Spinner",props:{isVisible:Boolean,type:{type:String,default:"inline"},message:{type:String,default:"commons.generating_otp"}},setup(s){return(o,w)=>{const i=l("FontAwesomeIcon");return s.isVisible?(n(),t("div",_,[s.type=="fullscreen"?(n(),t("div",m,[e("div",y,[e("span",u,[a(i,{icon:["fas","spinner"],spin:""})]),e("span",null,c(o.$t(s.message)),1)])])):r("",!0),s.type=="fullscreen-overlay"?(n(),t("div",f,[e("div",h,[e("span",v,[a(i,{icon:["fas","spinner"],spin:""})]),e("span",null,c(o.$t(s.message)),1)])])):s.type=="raw"?(n(),d(i,{key:2,icon:["fas","spinner"],spin:""})):(n(),t("div",g,[e("span",S,[a(i,{icon:["fas","spinner"],spin:""})])]))])):r("",!0)}}},V=p(k,[["__scopeId","data-v-247a4fa7"]]);export{V as S};

View File

@ -1 +1 @@
import{u as U,a as V,b as A,c as E,r as w,d as N,o as R,e as d,f as a,g as f,h as t,n as C,i as o,j as r,t as s,w as g,k as B,l as b,m as p,p as m,U as S}from"./app-1b332c21.js";import{F as K}from"./Form-940b5f6c.js";import{u as M}from"./bus-84126a4e.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const Q={class:"container has-text-centered"},T={class:"columns quick-uploader"},D=t("br",null,null,-1),j={class:"column is-full quick-uploader-button"},z={class:"quick-uploader-centerer"},G={class:"column is-full"},H={key:0,class:"block has-text-link"},J={class:"block has-text-link"},O={class:"block has-text-link"},Z={__name:"Start",setup(P){const k=U(),i=V(),h=M(),$=A(),v=E(),_=w(null),c=w(null),u=N(new K({qrcode:null,inputFormat:"fileUpload"}));function y(){u.clear(),u.qrcode=_.value.files[0],u.upload("/api/v1/qrcode/decode",{returnError:!0}).then(e=>{e.data.data.slice(0,33).toLowerCase()==="otpauth-migration://offline?data="?(h.migrationUri=e.data.data,k.push({name:"importAccounts"})):(h.decodedUri=e.data.data,k.push({name:"createAccount"}))}).catch(e=>{e.response.status!==422&&$.alert({text:e.response.data.message})})}function x(){k.push({name:"capture"})}return R(()=>{i.preferences.useDirectCapture&&i.preferences.defaultCaptureMode==="upload"&&c.value.click()}),(e,n)=>{const F=d("FieldError"),q=d("RouterLink"),I=d("ButtonBackCloseCancel"),L=d("VueFooter");return a(),f("div",Q,[t("div",T,[t("div",{class:C(["column is-full quick-uploader-header",{"is-invisible":o(v).count!==0}])},[r(s(e.$t("twofaccounts.no_account_here")),1),D,r(" "+s(e.$t("twofaccounts.add_first_account")),1)],2),t("div",j,[t("div",z,[o(i).preferences.useBasicQrcodeReader?(a(),f("label",{key:0,role:"button",tabindex:"0",class:"button is-link is-medium is-rounded is-main",ref_key:"qrcodeInputLabel",ref:c,onKeyup:n[0]||(n[0]=g(l=>o(c).click(),["enter"]))},[t("input",{"aria-hidden":"true",tabindex:"-1",class:"file-input",type:"file",accept:"image/*",onChange:y,ref_key:"qrcodeInput",ref:_},null,544),r(" "+s(e.$t("twofaccounts.forms.upload_qrcode")),1)],544)):(a(),f("button",{key:1,class:"button is-link is-medium is-rounded is-main",onClick:n[1]||(n[1]=l=>x())},s(e.$t("twofaccounts.forms.scan_qrcode")),1))]),o(u).errors.hasAny("qrcode")?(a(),B(F,{key:0,error:o(u).errors.get("qrcode"),field:"qrcode"},null,8,["error"])):b("",!0)]),t("div",G,[p(o(S),null,{default:m(({mode:l})=>[t("div",{class:C(["block",l=="dark"?"has-text-light":"has-text-grey-dark"])},s(e.$t("twofaccounts.forms.alternative_methods")),3)]),_:1}),o(i).preferences.useBasicQrcodeReader?b("",!0):(a(),f("div",H,[t("label",{role:"button",tabindex:"0",class:"button is-link is-outlined is-rounded",ref_key:"qrcodeInputLabel",ref:c,onKeyup:n[2]||(n[2]=g(l=>o(c).click(),["enter"]))},[t("input",{"aria-hidden":"true",tabindex:"-1",class:"file-input",type:"file",accept:"image/*",onChange:y,ref_key:"qrcodeInput",ref:_},null,544),r(" "+s(e.$t("twofaccounts.forms.upload_qrcode")),1)],544)])),t("div",J,[p(q,{class:"button is-link is-outlined is-rounded",to:{name:"createAccount"}},{default:m(()=>[r(s(e.$t("twofaccounts.forms.use_advanced_form")),1)]),_:1})]),t("div",O,[p(q,{id:"btnImport",class:"button is-link is-outlined is-rounded",to:{name:"importAccounts"}},{default:m(()=>[r(s(e.$t("twofaccounts.import.import")),1)]),_:1})])])]),p(L,{showButtons:!0},{default:m(()=>[o(v).isEmpty?b("",!0):(a(),B(I,{key:0,returnTo:{name:"accounts"},action:"back"}))]),_:1})])}}};export{Z as default};
import{u as U,a as V,b as A,c as E,r as w,d as N,o as R,e as d,f as a,g as f,h as t,n as C,i as o,j as r,t as s,w as g,k as B,l as b,m as p,p as m,U as S}from"./app-2d89b28f.js";import{F as K}from"./Form-5283f7b6.js";import{u as M}from"./bus-7802a020.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const Q={class:"container has-text-centered"},T={class:"columns quick-uploader"},D=t("br",null,null,-1),j={class:"column is-full quick-uploader-button"},z={class:"quick-uploader-centerer"},G={class:"column is-full"},H={key:0,class:"block has-text-link"},J={class:"block has-text-link"},O={class:"block has-text-link"},Z={__name:"Start",setup(P){const k=U(),i=V(),h=M(),$=A(),v=E(),_=w(null),c=w(null),u=N(new K({qrcode:null,inputFormat:"fileUpload"}));function y(){u.clear(),u.qrcode=_.value.files[0],u.upload("/api/v1/qrcode/decode",{returnError:!0}).then(e=>{e.data.data.slice(0,33).toLowerCase()==="otpauth-migration://offline?data="?(h.migrationUri=e.data.data,k.push({name:"importAccounts"})):(h.decodedUri=e.data.data,k.push({name:"createAccount"}))}).catch(e=>{e.response.status!==422&&$.alert({text:e.response.data.message})})}function x(){k.push({name:"capture"})}return R(()=>{i.preferences.useDirectCapture&&i.preferences.defaultCaptureMode==="upload"&&c.value.click()}),(e,n)=>{const F=d("FieldError"),q=d("RouterLink"),I=d("ButtonBackCloseCancel"),L=d("VueFooter");return a(),f("div",Q,[t("div",T,[t("div",{class:C(["column is-full quick-uploader-header",{"is-invisible":o(v).count!==0}])},[r(s(e.$t("twofaccounts.no_account_here")),1),D,r(" "+s(e.$t("twofaccounts.add_first_account")),1)],2),t("div",j,[t("div",z,[o(i).preferences.useBasicQrcodeReader?(a(),f("label",{key:0,role:"button",tabindex:"0",class:"button is-link is-medium is-rounded is-main",ref_key:"qrcodeInputLabel",ref:c,onKeyup:n[0]||(n[0]=g(l=>o(c).click(),["enter"]))},[t("input",{"aria-hidden":"true",tabindex:"-1",class:"file-input",type:"file",accept:"image/*",onChange:y,ref_key:"qrcodeInput",ref:_},null,544),r(" "+s(e.$t("twofaccounts.forms.upload_qrcode")),1)],544)):(a(),f("button",{key:1,class:"button is-link is-medium is-rounded is-main",onClick:n[1]||(n[1]=l=>x())},s(e.$t("twofaccounts.forms.scan_qrcode")),1))]),o(u).errors.hasAny("qrcode")?(a(),B(F,{key:0,error:o(u).errors.get("qrcode"),field:"qrcode"},null,8,["error"])):b("",!0)]),t("div",G,[p(o(S),null,{default:m(({mode:l})=>[t("div",{class:C(["block",l=="dark"?"has-text-light":"has-text-grey-dark"])},s(e.$t("twofaccounts.forms.alternative_methods")),3)]),_:1}),o(i).preferences.useBasicQrcodeReader?b("",!0):(a(),f("div",H,[t("label",{role:"button",tabindex:"0",class:"button is-link is-outlined is-rounded",ref_key:"qrcodeInputLabel",ref:c,onKeyup:n[2]||(n[2]=g(l=>o(c).click(),["enter"]))},[t("input",{"aria-hidden":"true",tabindex:"-1",class:"file-input",type:"file",accept:"image/*",onChange:y,ref_key:"qrcodeInput",ref:_},null,544),r(" "+s(e.$t("twofaccounts.forms.upload_qrcode")),1)],544)])),t("div",J,[p(q,{class:"button is-link is-outlined is-rounded",to:{name:"createAccount"}},{default:m(()=>[r(s(e.$t("twofaccounts.forms.use_advanced_form")),1)]),_:1})]),t("div",O,[p(q,{id:"btnImport",class:"button is-link is-outlined is-rounded",to:{name:"importAccounts"}},{default:m(()=>[r(s(e.$t("twofaccounts.import.import")),1)]),_:1})])])]),p(L,{showButtons:!0},{default:m(()=>[o(v).isEmpty?b("",!0):(a(),B(I,{key:0,returnTo:{name:"accounts"},action:"back"}))]),_:1})])}}};export{Z as default};

1
public/build/assets/Users-422cc043.js vendored Normal file
View File

@ -0,0 +1 @@
import{_ as j}from"./AdminTabs-7af3858b.js";import{Q as N,b as S,a2 as T,r as k,v as $,o as I,J as E,a0 as R,e as p,f as u,g as m,m as n,h as t,p as h,t as i,j as B,i as r,E as O,F as W,G as M,U as F,n as y,l as A}from"./app-2d89b28f.js";import{S as Z}from"./Spinner-aea2b665.js";import{_ as q}from"./SearchBox-cb8625ff.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const D={class:"options-tabs"},G={class:"title is-4 has-text-grey-light"},J={class:"is-size-7-mobile"},Q={class:"mb-6 mt-3"},H={class:"columns"},K={class:"column pb-0"},P={class:"level is-mobile mb-0"},X={class:"level-item has-text-centered is-justify-content-end"},Y={class:"subtitle is-7"},ee={class:"level-item has-text-centered is-justify-content-start"},se={class:"buttons"},te={key:0},ae={class:"has-ellipsis"},oe={class:"is-block has-ellipsis is-family-primary is-size-6 is-size-7-mobile has-text-grey"},ne={class:"tags mt-2"},ie={class:"ml-3"},le={key:1,class:"mt-4 pl-3"},he={__name:"Users",setup(re){const x=N("2fauth"),w=S(),z=T(x.prefix+"returnTo","accounts"),f=k([]),a=k(""),g=k(!1),C=$(()=>f.value.filter(e=>{let s=e.name.toLowerCase().includes(d.value.keywords)||e.email.toLowerCase().includes(d.value.keywords);return d.value.admin!=null&&(s=s&&e.is_admin==d.value.admin),d.value.oauth!=null&&(s=s&&e.oauth_provider==d.value.oauth),s})),d=$(()=>{const e={admin:void 0,oauth:void 0,keywords:a.value.toLowerCase()},s=a.value.toLowerCase().split(" "),c=/admin:([01])/,_=/oauth:([a-zA-Z0-9])/;return s.forEach(l=>{l.match(c)&&(e.admin=parseInt(l.replace(c,"$1")),e.keywords=e.keywords.replace(l,"").trim()),l.match(_)&&(e.oauth=l.replace(_,"$1"),e.keywords=e.keywords.replace(l,"").trim())}),e});I(()=>{L()});function b(e){const s=/admin:([01])/,c=/oauth:([a-zA-Z0-9]*)/;a.value.match(s)&&e.match(s)?a.value=a.value.replace(s,e):d.value.oauth!=null&&e.match(c)?a.value=a.value.replace(c,e):a.value=a.value?a.value+" "+e:e}function L(){g.value=!0,E.getAll({returnError:!0}).then(e=>{f.value=e.data}).catch(e=>{w.error(e)}).finally(()=>{g.value=!1})}return R(e=>{e.name.startsWith("admin.")||w.clear()}),(e,s)=>{const c=p("FontAwesomeIcon"),_=p("RouterLink"),l=p("ButtonBackCloseCancel"),U=p("VueFooter"),V=p("FormWrapper");return u(),m("div",null,[n(j,{activeTab:"admin.users"}),t("div",D,[n(V,null,{default:h(()=>[t("h4",G,i(e.$t("admin.users")),1),t("div",J,i(e.$t("admin.users_legend")),1),t("div",Q,[n(_,{class:"is-link mt-5",to:{name:"admin.createUser"}},{default:h(()=>[n(c,{icon:["fas","plus-circle"]}),B(" "+i(e.$t("admin.create_new_user")),1)]),_:1},8,["to"])]),t("div",H,[t("div",K,[n(q,{keyword:r(a),"onUpdate:keyword":s[0]||(s[0]=o=>O(a)?a.value=o:null),hasNoBackground:!0,placeholder:e.$t("admin.search_user_placeholder")},null,8,["keyword","placeholder"])])]),t("div",P,[t("div",X,[t("p",Y,i(e.$t("admin.quick_filters_colons")),1)]),t("div",ee,[t("div",se,[t("button",{class:"button is-small is-ghost p-0",onClick:s[1]||(s[1]=o=>b("admin:1"))},"admin"),t("button",{class:"button is-small is-ghost p-0",onClick:s[2]||(s[2]=o=>b("oauth:github"))},"github"),t("button",{class:"button is-small is-ghost p-0",onClick:s[3]||(s[3]=o=>b("oauth:openid"))},"openId")])])]),r(C).length>0?(u(),m("div",te,[(u(!0),m(W,null,M(r(C),o=>(u(),m("div",{key:o.id,class:"list-item is-size-5 is-size-6-mobile is-flex is-justify-content-space-between"},[t("div",ae,[t("span",null,i(o.name),1),t("span",oe,i(o.email),1),n(r(F),null,{default:h(({mode:v})=>[t("div",ne,[o.is_admin?(u(),m("span",{key:0,class:y(["tag is-rounded has-text-warning-dark",v=="dark"?"has-background-black-bis":"has-background-grey-lighter"])},"admin",2)):A("",!0),o.oauth_provider?(u(),m("span",{key:1,class:y(["tag is-rounded has-text-grey",v=="dark"?"has-background-black-bis":"has-background-grey-lighter"])},"oauth: "+i(o.oauth_provider),3)):A("",!0)])]),_:2},1024)]),t("div",ie,[n(r(F),null,{default:h(({mode:v})=>[n(_,{to:{name:"admin.manageUser",params:{userId:o.id}},class:y(["button is-small has-normal-radius is-pulled-right",{"is-dark":v=="dark"}]),title:e.$t("commons.manage")},{default:h(()=>[B(i(e.$t("commons.manage")),1)]),_:2},1032,["to","class","title"])]),_:2},1024)])]))),128))])):(u(),m("div",le,i(e.$t("commons.no_result")),1)),n(Z,{isVisible:r(g)&&r(f).length===0},null,8,["isVisible"]),n(U,{showButtons:!0},{default:h(()=>[n(l,{returnTo:{name:r(z)},action:"close"},null,8,["returnTo"])]),_:1})]),_:1})])])}}};export{he as default};

View File

@ -1 +0,0 @@
import{P as z,a as U,b as L,u as M,a2 as A,r as b,o as D,x as I,$ as K,e as h,f as d,g as _,m as o,h as s,p as v,i as t,l as y,t as i,w as R,j as k,F as j,G as E,U as H,n as P,I as r}from"./app-1b332c21.js";import{_ as G}from"./SettingTabs-52d14fa3.js";import{u as g}from"./userService-5f2b5050.js";import{w as J}from"./webauthnService-839b1903.js";import{S as q}from"./Spinner-b3cbad3a.js";/*! 2FAuth version 5.0.0 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const Q={class:"options-tabs"},X=["innerHTML"],Y={class:"title is-4 has-text-grey-light"},Z={class:"is-size-7-mobile"},ee={class:"mt-3"},te=["onKeyup"],se={key:1,class:"field"},ne=["onClick","title"],ae={class:"mt-2 is-size-7 is-pulled-right"},oe={class:"title is-4 pt-6 has-text-grey-light"},ie={class:"field"},pe={__name:"WebAuthn",setup(re){const C=z("2fauth"),l=U(),n=L(),W=M(),$=A(C.prefix+"returnTo","accounts"),a=b([]),f=b(!1),p=b(!1);D(()=>{V()}),I(()=>l.preferences.useWebauthnOnly,()=>{g.updatePreference("useWebauthnOnly",l.preferences.useWebauthnOnly).then(e=>{n.success({text:r("settings.forms.setting_saved")})})});function w(){if(p==!0)return n.warn({text:r("errors.unsupported_with_reverseproxy")}),!1;J.register().then(e=>{W.push({name:"settings.webauthn.editCredential",params:{credentialId:JSON.parse(e.config.data).id}})}).catch(e=>{var u;"webauthn"in e?e.name=="is-warning"?n.warn({text:r(e.message)}):n.alert({text:r(e.message)}):((u=e.response)==null?void 0:u.status)===422?n.alert({text:e.response.data.message}):n.error(e)})}function x(e){confirm(r("auth.confirm.revoke_device"))&&g.revokeWebauthnDevice(e).then(u=>{a.value=a.value.filter(m=>m.id!==e),a.value.length==0&&(l.preferences.useWebauthnOnly=!1),n.success({text:r("auth.webauthn.device_revoked")})})}function F(e){return e.alias?e.alias:r("auth.webauthn.my_device")+" (#"+e.id.substring(0,10)+")"}function V(){f.value=!0,g.getWebauthnDevices({returnError:!0}).then(e=>{a.value=e.data}).catch(e=>{e.response.status===405?p.value=!0:n.error(e)}).finally(()=>{f.value=!1})}return K(e=>{e.name.startsWith("settings.")||n.clear()}),(e,u)=>{const m=h("FontAwesomeIcon"),B=h("FormCheckbox"),S=h("ButtonBackCloseCancel"),T=h("VueFooter"),N=h("FormWrapper");return d(),_("div",null,[o(G,{activeTab:"settings.webauthn.devices"},null,8,["activeTab"]),s("div",Q,[o(N,null,{default:v(()=>[t(p)?(d(),_("div",{key:0,class:"notification is-warning has-text-centered",innerHTML:e.$t("auth.auth_handled_by_proxy")},null,8,X)):y("",!0),s("h4",Y,i(e.$t("auth.webauthn.security_devices")),1),s("div",Z,i(e.$t("auth.webauthn.security_devices_legend")),1),s("div",ee,[s("a",{tabindex:"0",onClick:w,onKeyup:R(w,["enter"])},[o(m,{icon:["fas","plus-circle"]}),k(" "+i(e.$t("auth.webauthn.register_a_new_device")),1)],40,te)]),t(a).length>0?(d(),_("div",se,[(d(!0),_(j,null,E(t(a),c=>(d(),_("div",{key:c.id,class:"group-item is-size-5 is-size-6-mobile"},[k(i(F(c))+" ",1),o(t(H),null,{default:v(({mode:O})=>[s("button",{class:P(["button tag is-pulled-right",O==="dark"?"is-dark":"is-white"]),onClick:ue=>x(c.id),title:e.$t("settings.revoke")},i(e.$t("settings.revoke")),11,ne)]),_:2},1024)]))),128)),s("div",ae,i(e.$t("auth.webauthn.revoking_a_device_is_permanent")),1)])):y("",!0),o(q,{isVisible:t(f)&&t(a).length===0},null,8,["isVisible"]),s("h4",oe,i(e.$t("settings.options")),1),s("div",ie,i(e.$t("auth.webauthn.need_a_security_device_to_enable_options")),1),s("form",null,[o(B,{modelValue:t(l).preferences.useWebauthnOnly,"onUpdate:modelValue":u[0]||(u[0]=c=>t(l).preferences.useWebauthnOnly=c),fieldName:"useWebauthnOnly",label:"auth.webauthn.use_webauthn_only.label",help:"auth.webauthn.use_webauthn_only.help",disabled:t(p)||t(a).length===0},null,8,["modelValue","disabled"])]),o(T,{showButtons:!0},{default:v(()=>[o(S,{returnTo:{name:t($)},action:"close"},null,8,["returnTo"])]),_:1})]),_:1})])])}}};export{pe as default};

View File

@ -0,0 +1 @@
import{Q as z,a as U,b as L,u as M,a2 as A,r as b,o as D,x as I,J as v,a0 as K,e as h,f as d,g as _,m as o,h as s,p as g,i as t,l as y,t as i,w as R,j as k,F as j,G as E,U as H,n as J,I as r}from"./app-2d89b28f.js";import{_ as G}from"./SettingTabs-708dbaa6.js";import{w as P}from"./webauthnService-43ef310f.js";import{S as Q}from"./Spinner-aea2b665.js";/*! 2FAuth version 5.1.1 - Copyright (c) 2023 Bubka - https://github.com/Bubka/2FAuth */const q={class:"options-tabs"},X=["innerHTML"],Y={class:"title is-4 has-text-grey-light"},Z={class:"is-size-7-mobile"},ee={class:"mt-3"},te=["onKeyup"],se={key:1,class:"field"},ne=["onClick","title"],ae={class:"mt-2 is-size-7 is-pulled-right"},oe={class:"title is-4 pt-6 has-text-grey-light"},ie={class:"field"},_e={__name:"WebAuthn",setup(re){const C=z("2fauth"),l=U(),n=L(),W=M(),$=A(C.prefix+"returnTo","accounts"),a=b([]),f=b(!1),p=b(!1);D(()=>{V()}),I(()=>l.preferences.useWebauthnOnly,()=>{v.updatePreference("useWebauthnOnly",l.preferences.useWebauthnOnly).then(e=>{n.success({text:r("settings.forms.setting_saved")})})});function w(){if(p==!0)return n.warn({text:r("errors.unsupported_with_reverseproxy")}),!1;P.register().then(e=>{W.push({name:"settings.webauthn.editCredential",params:{credentialId:JSON.parse(e.config.data).id}})}).catch(e=>{var u;"webauthn"in e?e.name=="is-warning"?n.warn({text:r(e.message)}):n.alert({text:r(e.message)}):((u=e.response)==null?void 0:u.status)===422?n.alert({text:e.response.data.message}):n.error(e)})}function x(e){confirm(r("auth.confirm.revoke_device"))&&v.revokeWebauthnDevice(e).then(u=>{a.value=a.value.filter(m=>m.id!==e),a.value.length==0&&(l.preferences.useWebauthnOnly=!1),n.success({text:r("auth.webauthn.device_revoked")})})}function F(e){return e.alias?e.alias:r("auth.webauthn.my_device")+" (#"+e.id.substring(0,10)+")"}function V(){f.value=!0,v.getWebauthnDevices({returnError:!0}).then(e=>{a.value=e.data}).catch(e=>{e.response.status===405?p.value=!0:n.error(e)}).finally(()=>{f.value=!1})}return K(e=>{e.name.startsWith("settings.")||n.clear()}),(e,u)=>{const m=h("FontAwesomeIcon"),B=h("FormCheckbox"),S=h("ButtonBackCloseCancel"),T=h("VueFooter"),N=h("FormWrapper");return d(),_("div",null,[o(G,{activeTab:"settings.webauthn.devices"},null,8,["activeTab"]),s("div",q,[o(N,null,{default:g(()=>[t(p)?(d(),_("div",{key:0,class:"notification is-warning has-text-centered",innerHTML:e.$t("auth.auth_handled_by_proxy")},null,8,X)):y("",!0),s("h4",Y,i(e.$t("auth.webauthn.security_devices")),1),s("div",Z,i(e.$t("auth.webauthn.security_devices_legend")),1),s("div",ee,[s("a",{tabindex:"0",onClick:w,onKeyup:R(w,["enter"])},[o(m,{icon:["fas","plus-circle"]}),k(" "+i(e.$t("auth.webauthn.register_a_new_device")),1)],40,te)]),t(a).length>0?(d(),_("div",se,[(d(!0),_(j,null,E(t(a),c=>(d(),_("div",{key:c.id,class:"group-item is-size-5 is-size-6-mobile"},[k(i(F(c))+" ",1),o(t(H),null,{default:g(({mode:O})=>[s("button",{class:J(["button tag is-pulled-right",O==="dark"?"is-dark":"is-white"]),onClick:ue=>x(c.id),title:e.$t("settings.revoke")},i(e.$t("settings.revoke")),11,ne)]),_:2},1024)]))),128)),s("div",ae,i(e.$t("auth.webauthn.revoking_a_device_is_permanent")),1)])):y("",!0),o(Q,{isVisible:t(f)&&t(a).length===0},null,8,["isVisible"]),s("h4",oe,i(e.$t("auth.webauthn.options")),1),s("div",ie,i(e.$t("auth.webauthn.need_a_security_device_to_enable_options")),1),s("form",null,[o(B,{modelValue:t(l).preferences.useWebauthnOnly,"onUpdate:modelValue":u[0]||(u[0]=c=>t(l).preferences.useWebauthnOnly=c),fieldName:"useWebauthnOnly",label:"auth.webauthn.use_webauthn_only.label",help:"auth.webauthn.use_webauthn_only.help",disabled:t(p)||t(a).length===0},null,8,["modelValue","disabled"])]),o(T,{showButtons:!0},{default:g(()=>[o(S,{returnTo:{name:t($)},action:"close"},null,8,["returnTo"])]),_:1})]),_:1})])])}}};export{_e as default};

File diff suppressed because one or more lines are too long

771
public/build/assets/app-2d89b28f.js vendored Normal file

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More