mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-06-24 22:12:06 +02:00
Move locales to single json files & Replace laravel-vue-i18n with vue-i18n
This commit is contained in:
parent
01e56284ca
commit
6f419cfbcb
@ -41,7 +41,7 @@ class IconController extends Controller
|
|||||||
|
|
||||||
return $isStored
|
return $isStored
|
||||||
? response()->json(['filename' => $name], 201)
|
? response()->json(['filename' => $name], 201)
|
||||||
: response()->json(['message' => __('errors.file_upload_failed')], 500);
|
: response()->json(['message' => __('error.file_upload_failed')], 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,6 +34,6 @@ class QrCodeController extends Controller
|
|||||||
|
|
||||||
return $file instanceof \Illuminate\Http\UploadedFile
|
return $file instanceof \Illuminate\Http\UploadedFile
|
||||||
? response()->json(['data' => QrCode::decode($file)], 200)
|
? response()->json(['data' => QrCode::decode($file)], 200)
|
||||||
: response()->json(['message' => __('errors.file_upload_failed')], 500);
|
: response()->json(['message' => __('error.file_upload_failed')], 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ class SettingController extends Controller
|
|||||||
if (array_key_exists($settingName, $defaultAppSettings) && $defaultAppSettings[$settingName] !== '') {
|
if (array_key_exists($settingName, $defaultAppSettings) && $defaultAppSettings[$settingName] !== '') {
|
||||||
return response()->json(
|
return response()->json(
|
||||||
['message' => 'bad request',
|
['message' => 'bad request',
|
||||||
'reason' => [__('errors.delete_user_setting_only')],
|
'reason' => [__('error.delete_user_setting_only')],
|
||||||
], 400);
|
], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ class TwoFAccountController extends Controller
|
|||||||
|
|
||||||
return $migrationResource instanceof \Illuminate\Http\UploadedFile
|
return $migrationResource instanceof \Illuminate\Http\UploadedFile
|
||||||
? new TwoFAccountCollection(TwoFAccounts::migrate($migrationResource->get()))
|
? new TwoFAccountCollection(TwoFAccounts::migrate($migrationResource->get()))
|
||||||
: response()->json(['message' => __('errors.file_upload_failed')], 500);
|
: response()->json(['message' => __('error.file_upload_failed')], 500);
|
||||||
} else {
|
} else {
|
||||||
return new TwoFAccountCollection(TwoFAccounts::migrate($request->payload));
|
return new TwoFAccountCollection(TwoFAccounts::migrate($request->payload));
|
||||||
}
|
}
|
||||||
@ -203,7 +203,7 @@ class TwoFAccountController extends Controller
|
|||||||
if ($this->tooManyIds($validated['ids'])) {
|
if ($this->tooManyIds($validated['ids'])) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'bad request',
|
'message' => 'bad request',
|
||||||
'reason' => [__('errors.too_many_ids')],
|
'reason' => [__('error.too_many_ids')],
|
||||||
], 400);
|
], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,7 +276,7 @@ class TwoFAccountController extends Controller
|
|||||||
if ($this->tooManyIds($validated['ids'])) {
|
if ($this->tooManyIds($validated['ids'])) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'bad request',
|
'message' => 'bad request',
|
||||||
'reason' => [__('errors.too_many_ids')],
|
'reason' => [__('error.too_many_ids')],
|
||||||
], 400);
|
], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,7 +316,7 @@ class TwoFAccountController extends Controller
|
|||||||
if ($this->tooManyIds($validated['ids'])) {
|
if ($this->tooManyIds($validated['ids'])) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'bad request',
|
'message' => 'bad request',
|
||||||
'reason' => [__('errors.too_many_ids')],
|
'reason' => [__('error.too_many_ids')],
|
||||||
], 400);
|
], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ class UserManagerController extends Controller
|
|||||||
} else {
|
} else {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'bad request',
|
'message' => 'bad request',
|
||||||
'reason' => is_string($response) ? __($response) : __('errors.no_pwd_reset_for_this_user_type'),
|
'reason' => is_string($response) ? __($response) : __('error.no_pwd_reset_for_this_user_type'),
|
||||||
], 400);
|
], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +181,7 @@ class UserManagerController extends Controller
|
|||||||
// Deletion will not be done (and returns False) if the user is the only existing admin (see UserObserver clas)
|
// Deletion will not be done (and returns False) if the user is the only existing admin (see UserObserver clas)
|
||||||
return $user->delete() === false
|
return $user->delete() === false
|
||||||
? response()->json([
|
? response()->json([
|
||||||
'message' => __('errors.cannot_delete_the_only_admin'),
|
'message' => __('error.cannot_delete_the_only_admin'),
|
||||||
], 403)
|
], 403)
|
||||||
: response()->json(null, 204);
|
: response()->json(null, 204);
|
||||||
}
|
}
|
||||||
@ -203,7 +203,7 @@ class UserManagerController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => __('errors.cannot_demote_the_only_admin'),
|
'message' => __('error.cannot_demote_the_only_admin'),
|
||||||
], 403);
|
], 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ class GroupStoreRequest extends FormRequest
|
|||||||
'required',
|
'required',
|
||||||
'regex:/^[A-zÀ-ú0-9\s\-_]+$/',
|
'regex:/^[A-zÀ-ú0-9\s\-_]+$/',
|
||||||
'max:32',
|
'max:32',
|
||||||
Rule::notIn([__('commons.all')]),
|
Rule::notIn([__('message.all')]),
|
||||||
Rule::unique('groups')->where(fn ($query) => $query->where('user_id', $this->user()->id)),
|
Rule::unique('groups')->where(fn ($query) => $query->where('user_id', $this->user()->id)),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@ -44,7 +44,7 @@ class GroupStoreRequest extends FormRequest
|
|||||||
public function messages() : array
|
public function messages() : array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name.not_in' => __('errors.reserved_name_please_choose_something_else'),
|
'name.not_in' => __('error.reserved_name_please_choose_something_else'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ class FixOrphanAccounts extends Command
|
|||||||
$this->line('Trying to fix them...');
|
$this->line('Trying to fix them...');
|
||||||
|
|
||||||
foreach ($twofaccounts as $twofaccount) {
|
foreach ($twofaccounts as $twofaccount) {
|
||||||
if ($twofaccount->legacy_uri === __('errors.indecipherable')) {
|
if ($twofaccount->legacy_uri === __('error.indecipherable')) {
|
||||||
$this->error(sprintf('Account #%d cannot be deciphered', $twofaccount->id));
|
$this->error(sprintf('Account #%d cannot be deciphered', $twofaccount->id));
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
@ -72,8 +72,8 @@ class FixServiceFieldEncryption extends Command
|
|||||||
protected function encryptServiceField() : void
|
protected function encryptServiceField() : void
|
||||||
{
|
{
|
||||||
$twofaccounts = TwoFAccount::all();
|
$twofaccounts = TwoFAccount::all();
|
||||||
$fullyEncryptedTwofaccounts = $twofaccounts->whereNotIn('service', [__('errors.indecipherable')]);
|
$fullyEncryptedTwofaccounts = $twofaccounts->whereNotIn('service', [__('error.indecipherable')]);
|
||||||
$partiallyEncryptedTwofaccounts = $twofaccounts->where('service', __('errors.indecipherable'));
|
$partiallyEncryptedTwofaccounts = $twofaccounts->where('service', __('error.indecipherable'));
|
||||||
|
|
||||||
if ($fullyEncryptedTwofaccounts->count() === $twofaccounts->count()) {
|
if ($fullyEncryptedTwofaccounts->count() === $twofaccounts->count()) {
|
||||||
$this->components->info('The Service field is fully encrypted');
|
$this->components->info('The Service field is fully encrypted');
|
||||||
|
@ -76,7 +76,7 @@ class FixUnsplittedAccounts extends Command
|
|||||||
$this->line('Trying to fix them...');
|
$this->line('Trying to fix them...');
|
||||||
|
|
||||||
foreach ($twofaccounts as $twofaccount) {
|
foreach ($twofaccounts as $twofaccount) {
|
||||||
if ($twofaccount->legacy_uri === __('errors.indecipherable')) {
|
if ($twofaccount->legacy_uri === __('error.indecipherable')) {
|
||||||
$this->error(sprintf('Account #%d cannot be deciphered', $twofaccount->id));
|
$this->error(sprintf('Account #%d cannot be deciphered', $twofaccount->id));
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
@ -62,37 +62,37 @@ class Handler extends ExceptionHandler
|
|||||||
|
|
||||||
$this->renderable(function (InvalidMigrationDataException $exception, $request) {
|
$this->renderable(function (InvalidMigrationDataException $exception, $request) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => __('errors.invalid_x_migration', ['appname' => $exception->getMessage()]),
|
'message' => __('error.invalid_x_migration', ['appname' => $exception->getMessage()]),
|
||||||
], 400);
|
], 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->renderable(function (UnsupportedMigrationException $exception, $request) {
|
$this->renderable(function (UnsupportedMigrationException $exception, $request) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => __('errors.unsupported_migration'),
|
'message' => __('error.unsupported_migration'),
|
||||||
], 400);
|
], 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->renderable(function (EncryptedMigrationException $exception, $request) {
|
$this->renderable(function (EncryptedMigrationException $exception, $request) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => __('errors.encrypted_migration'),
|
'message' => __('error.encrypted_migration'),
|
||||||
], 400);
|
], 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->renderable(function (UndecipherableException $exception, $request) {
|
$this->renderable(function (UndecipherableException $exception, $request) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => __('errors.cannot_decipher_secret'),
|
'message' => __('error.cannot_decipher_secret'),
|
||||||
], 400);
|
], 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->renderable(function (UnsupportedOtpTypeException $exception, $request) {
|
$this->renderable(function (UnsupportedOtpTypeException $exception, $request) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => __('errors.unsupported_otp_type'),
|
'message' => __('error.unsupported_otp_type'),
|
||||||
], 400);
|
], 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->renderable(function (FailedIconStoreDatabaseTogglingException $exception, $request) {
|
$this->renderable(function (FailedIconStoreDatabaseTogglingException $exception, $request) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => __('errors.failed_icon_store_database_toggling'),
|
'message' => __('error.failed_icon_store_database_toggling'),
|
||||||
], 400);
|
], 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ class LoginController extends Controller
|
|||||||
$this->throttleKey($request)
|
$this->throttleKey($request)
|
||||||
);
|
);
|
||||||
|
|
||||||
return response()->json(['message' => Lang::get('auth.throttle', ['seconds' => $seconds])], Response::HTTP_TOO_MANY_REQUESTS);
|
return response()->json(['message' => Lang::get('message.auth.throttle', ['seconds' => $seconds])], Response::HTTP_TOO_MANY_REQUESTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,13 +23,13 @@ class PasswordController extends Controller
|
|||||||
if ($user->oauth_provider) {
|
if ($user->oauth_provider) {
|
||||||
Log::notice('Password update rejected: external account from a sso provider');
|
Log::notice('Password update rejected: external account from a sso provider');
|
||||||
|
|
||||||
return response()->json(['message' => __('errors.account_managed_by_external_provider')], 400);
|
return response()->json(['message' => __('error.account_managed_by_external_provider')], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! Hash::check($validated['currentPassword'], Auth::user()->password)) {
|
if (! Hash::check($validated['currentPassword'], Auth::user()->password)) {
|
||||||
Log::notice('Password update failed: wrong password provided');
|
Log::notice('Password update failed: wrong password provided');
|
||||||
|
|
||||||
return response()->json(['message' => __('errors.wrong_current_password')], 400);
|
return response()->json(['message' => __('error.wrong_current_password')], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! config('2fauth.config.isDemoApp')) {
|
if (! config('2fauth.config.isDemoApp')) {
|
||||||
@ -39,6 +39,6 @@ class PasswordController extends Controller
|
|||||||
Log::info(sprintf('Password of user ID #%s updated', $user->id));
|
Log::info(sprintf('Password of user ID #%s updated', $user->id));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(['message' => __('auth.forms.password_successfully_changed')]);
|
return response()->json(['message' => __('message.auth.forms.password_successfully_changed')]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ class PersonalAccessTokenController extends PassportPatController
|
|||||||
public function forUser(Request $request)
|
public function forUser(Request $request)
|
||||||
{
|
{
|
||||||
if (Gate::denies('manage-pat')) {
|
if (Gate::denies('manage-pat')) {
|
||||||
throw new AccessDeniedHttpException(__('errors.unsupported_with_sso_only'));
|
throw new AccessDeniedHttpException(__('error.unsupported_with_sso_only'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::forUser($request);
|
return parent::forUser($request);
|
||||||
@ -33,7 +33,7 @@ class PersonalAccessTokenController extends PassportPatController
|
|||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
if (Gate::denies('manage-pat')) {
|
if (Gate::denies('manage-pat')) {
|
||||||
throw new AccessDeniedHttpException(__('errors.unsupported_with_sso_only'));
|
throw new AccessDeniedHttpException(__('error.unsupported_with_sso_only'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::store($request);
|
return parent::store($request);
|
||||||
@ -49,7 +49,7 @@ class PersonalAccessTokenController extends PassportPatController
|
|||||||
public function destroy(Request $request, $tokenId)
|
public function destroy(Request $request, $tokenId)
|
||||||
{
|
{
|
||||||
if (Gate::denies('manage-pat')) {
|
if (Gate::denies('manage-pat')) {
|
||||||
throw new AccessDeniedHttpException(__('errors.unsupported_with_sso_only'));
|
throw new AccessDeniedHttpException(__('error.unsupported_with_sso_only'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::destroy($request, $tokenId);
|
return parent::destroy($request, $tokenId);
|
||||||
|
@ -27,13 +27,13 @@ class UserController extends Controller
|
|||||||
if (config('auth.defaults.guard') === 'reverse-proxy-guard' || $user->oauth_provider) {
|
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');
|
Log::notice('Account update rejected: reverse-proxy-guard enabled or account from external sso provider');
|
||||||
|
|
||||||
return response()->json(['message' => __('errors.account_managed_by_external_provider')], 400);
|
return response()->json(['message' => __('error.account_managed_by_external_provider')], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! Hash::check($request->password, Auth::user()->password)) {
|
if (! Hash::check($request->password, Auth::user()->password)) {
|
||||||
Log::notice('Account update failed: wrong password provided');
|
Log::notice('Account update failed: wrong password provided');
|
||||||
|
|
||||||
return response()->json(['message' => __('errors.wrong_current_password')], 400);
|
return response()->json(['message' => __('error.wrong_current_password')], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! config('2fauth.config.isDemoApp')) {
|
if (! config('2fauth.config.isDemoApp')) {
|
||||||
@ -58,14 +58,14 @@ class UserController extends Controller
|
|||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
|
|
||||||
if (! Hash::check($validated['password'], Auth::user()->password)) {
|
if (! Hash::check($validated['password'], Auth::user()->password)) {
|
||||||
return response()->json(['message' => __('errors.wrong_current_password')], 400);
|
return response()->json(['message' => __('error.wrong_current_password')], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will delete the user and all its 2FAs & Groups thanks to the onCascadeDelete constrains.
|
// 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)
|
// Deletion will not be done (and returns False) if the user is the only existing admin (see UserObserver clas)
|
||||||
return $user->delete() === false
|
return $user->delete() === false
|
||||||
? response()->json([
|
? response()->json([
|
||||||
'message' => __('errors.cannot_delete_the_only_admin'),
|
'message' => __('error.cannot_delete_the_only_admin'),
|
||||||
], 400)
|
], 400)
|
||||||
: response()->json(null, 204);
|
: response()->json(null, 204);
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,6 @@ class WebAuthnDeviceLostController extends Controller
|
|||||||
*/
|
*/
|
||||||
protected function sendRecoveryLinkResponse(Request $request, string $response)
|
protected function sendRecoveryLinkResponse(Request $request, string $response)
|
||||||
{
|
{
|
||||||
return response()->json(['message' => __('auth.webauthn.account_recovery_email_sent')]);
|
return response()->json(['message' => __('message.auth.webauthn.account_recovery_email_sent')]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@ class WebAuthnLoginController extends Controller
|
|||||||
$this->throttleKey($request)
|
$this->throttleKey($request)
|
||||||
);
|
);
|
||||||
|
|
||||||
return response()->json(['message' => Lang::get('auth.throttle', ['seconds' => $seconds])], Response::HTTP_TOO_MANY_REQUESTS);
|
return response()->json(['message' => Lang::get('message.auth.throttle', ['seconds' => $seconds])], Response::HTTP_TOO_MANY_REQUESTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,7 +19,7 @@ class WebAuthnManageController extends Controller
|
|||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
if (Gate::denies('manage-webauthn-credentials')) {
|
if (Gate::denies('manage-webauthn-credentials')) {
|
||||||
throw new AccessDeniedHttpException(__('errors.unsupported_with_sso_only'));
|
throw new AccessDeniedHttpException(__('error.unsupported_with_sso_only'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$allUserCredentials = $request->user()->webAuthnCredentials()->WhereEnabled()->get();
|
$allUserCredentials = $request->user()->webAuthnCredentials()->WhereEnabled()->get();
|
||||||
@ -54,7 +54,7 @@ class WebAuthnManageController extends Controller
|
|||||||
Log::info('Deletion of security device requested');
|
Log::info('Deletion of security device requested');
|
||||||
|
|
||||||
if (Gate::denies('manage-webauthn-credentials')) {
|
if (Gate::denies('manage-webauthn-credentials')) {
|
||||||
throw new AccessDeniedHttpException(__('errors.unsupported_with_sso_only'));
|
throw new AccessDeniedHttpException(__('error.unsupported_with_sso_only'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
@ -80,7 +80,7 @@ class WebAuthnRecoveryController extends Controller
|
|||||||
*/
|
*/
|
||||||
protected function sendRecoveryResponse(Request $request, string $response) : JsonResponse
|
protected function sendRecoveryResponse(Request $request, string $response) : JsonResponse
|
||||||
{
|
{
|
||||||
return response()->json(['message' => __('auth.webauthn.webauthn_login_disabled')]);
|
return response()->json(['message' => __('message.auth.webauthn.webauthn_login_disabled')]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,7 +93,7 @@ class WebAuthnRecoveryController extends Controller
|
|||||||
{
|
{
|
||||||
switch ($response) {
|
switch ($response) {
|
||||||
case Password::INVALID_TOKEN:
|
case Password::INVALID_TOKEN:
|
||||||
throw ValidationException::withMessages(['token' => [__('auth.webauthn.invalid_reset_token')]]);
|
throw ValidationException::withMessages(['token' => [__('message.auth.webauthn.invalid_reset_token')]]);
|
||||||
default:
|
default:
|
||||||
throw ValidationException::withMessages(['email' => [trans($response)]]);
|
throw ValidationException::withMessages(['email' => [trans($response)]]);
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ class RejectIfAuthenticated
|
|||||||
|
|
||||||
foreach ($guards as $guard) {
|
foreach ($guards as $guard) {
|
||||||
if (Auth::guard($guard)->check()) {
|
if (Auth::guard($guard)->check()) {
|
||||||
return response()->json(['message' => __('auth.already_authenticated')], 400);
|
return response()->json(['message' => __('message.auth.already_authenticated')], 400);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ class RejectIfDemoMode
|
|||||||
if (config('2fauth.config.isDemoApp')) {
|
if (config('2fauth.config.isDemoApp')) {
|
||||||
Log::info('Cannot request this action in Demo mode');
|
Log::info('Cannot request this action in Demo mode');
|
||||||
|
|
||||||
return response()->json(['message' => __('auth.forms.disabled_in_demo')], Response::HTTP_UNAUTHORIZED);
|
return response()->json(['message' => __('message.auth.forms.disabled_in_demo')], Response::HTTP_UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
|
@ -19,7 +19,7 @@ class RejectIfReverseProxy
|
|||||||
Log::info('Cannot request this action in reverse proxy mode');
|
Log::info('Cannot request this action in reverse proxy mode');
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => __('errors.unsupported_with_reverseproxy'),
|
'message' => __('error.unsupported_with_reverseproxy'),
|
||||||
], 405);
|
], 405);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ class RejectIfSsoOnlyAndNotForAdmin
|
|||||||
|
|
||||||
Log::notice(sprintf('Request to %s rejected, only Admins can request it while authentication is restricted to SSO only', $request->getPathInfo()));
|
Log::notice(sprintf('Request to %s rejected, only Admins can request it while authentication is restricted to SSO only', $request->getPathInfo()));
|
||||||
|
|
||||||
return response()->json(['message' => __('errors.unsupported_with_sso_only')], Response::HTTP_METHOD_NOT_ALLOWED);
|
return response()->json(['message' => __('error.unsupported_with_sso_only')], Response::HTTP_METHOD_NOT_ALLOWED);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
|
@ -18,7 +18,7 @@ class WebauthnAttestationRequest extends AttestationRequest
|
|||||||
*/
|
*/
|
||||||
protected function failedAuthorization()
|
protected function failedAuthorization()
|
||||||
{
|
{
|
||||||
throw new AccessDeniedHttpException(__('errors.unsupported_with_sso_only'));
|
throw new AccessDeniedHttpException(__('error.unsupported_with_sso_only'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,7 +18,7 @@ class WebauthnAttestedRequest extends AttestedRequest
|
|||||||
*/
|
*/
|
||||||
protected function failedAuthorization()
|
protected function failedAuthorization()
|
||||||
{
|
{
|
||||||
throw new AccessDeniedHttpException(__('errors.unsupported_with_sso_only'));
|
throw new AccessDeniedHttpException(__('error.unsupported_with_sso_only'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,7 +18,7 @@ class WebauthnRenameRequest extends FormRequest
|
|||||||
*/
|
*/
|
||||||
protected function failedAuthorization()
|
protected function failedAuthorization()
|
||||||
{
|
{
|
||||||
throw new AccessDeniedHttpException(__('errors.unsupported_with_sso_only'));
|
throw new AccessDeniedHttpException(__('error.unsupported_with_sso_only'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,7 +112,7 @@ class Group extends Model
|
|||||||
// resolution logic to return an instance instead of not found.
|
// resolution logic to return an instance instead of not found.
|
||||||
if ($value === '0') {
|
if ($value === '0') {
|
||||||
$group = new self([
|
$group = new self([
|
||||||
'name' => __('commons.all'),
|
'name' => __('message.all'),
|
||||||
]);
|
]);
|
||||||
$group->id = 0;
|
$group->id = 0;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ trait CanEncryptField
|
|||||||
try {
|
try {
|
||||||
return Crypt::decryptString($value);
|
return Crypt::decryptString($value);
|
||||||
} catch (\Exception $ex) {
|
} catch (\Exception $ex) {
|
||||||
return __('errors.indecipherable');
|
return __('error.indecipherable');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return $value;
|
return $value;
|
||||||
|
@ -406,7 +406,7 @@ class TwoFAccount extends Model implements Sortable
|
|||||||
Log::info(sprintf('OTP requested for TwoFAccount (%s)', $this->id ? 'id:' . $this->id : 'preview'));
|
Log::info(sprintf('OTP requested for TwoFAccount (%s)', $this->id ? 'id:' . $this->id : 'preview'));
|
||||||
|
|
||||||
// Early exit if the model has an undecipherable secret
|
// Early exit if the model has an undecipherable secret
|
||||||
if (strtolower($this->secret) === __('errors.indecipherable')) {
|
if (strtolower($this->secret) === __('error.indecipherable')) {
|
||||||
Log::error('Secret cannot be deciphered, OTP generation aborted');
|
Log::error('Secret cannot be deciphered, OTP generation aborted');
|
||||||
|
|
||||||
throw new UndecipherableException;
|
throw new UndecipherableException;
|
||||||
|
@ -45,7 +45,7 @@ class FailedLoginNotification extends Notification
|
|||||||
public function toMail(mixed $notifiable) : MailMessage
|
public function toMail(mixed $notifiable) : MailMessage
|
||||||
{
|
{
|
||||||
return (new MailMessage)
|
return (new MailMessage)
|
||||||
->subject(__('notifications.failed_login.subject'))
|
->subject(__('message.notifications.failed_login.subject'))
|
||||||
->markdown('emails.failedLogin', [
|
->markdown('emails.failedLogin', [
|
||||||
'account' => $notifiable,
|
'account' => $notifiable,
|
||||||
'time' => $this->authLog->login_at,
|
'time' => $this->authLog->login_at,
|
||||||
|
@ -42,7 +42,7 @@ class SignedInWithNewDeviceNotification extends Notification
|
|||||||
public function toMail(mixed $notifiable) : MailMessage
|
public function toMail(mixed $notifiable) : MailMessage
|
||||||
{
|
{
|
||||||
return (new MailMessage)
|
return (new MailMessage)
|
||||||
->subject(__('notifications.new_device.subject'))
|
->subject(__('message.notifications.new_device.subject'))
|
||||||
->markdown('emails.signedInWithNewDevice', [
|
->markdown('emails.signedInWithNewDevice', [
|
||||||
'account' => $notifiable,
|
'account' => $notifiable,
|
||||||
'time' => $this->authLog->login_at,
|
'time' => $this->authLog->login_at,
|
||||||
|
@ -48,13 +48,13 @@ class TestEmailSettingNotification extends Notification
|
|||||||
public function toMail($notifiable)
|
public function toMail($notifiable)
|
||||||
{
|
{
|
||||||
return (new MailMessage)
|
return (new MailMessage)
|
||||||
->subject(Lang::get('notifications.test_email_settings.subject'))
|
->subject(Lang::get('message.notifications.test_email_settings.subject'))
|
||||||
->greeting(Lang::get('notifications.hello'))
|
->greeting(Lang::get('message.notifications.hello'))
|
||||||
->line(
|
->line(
|
||||||
Lang::get('notifications.test_email_settings.reason')
|
Lang::get('message.notifications.test_email_settings.reason')
|
||||||
)
|
)
|
||||||
->line(
|
->line(
|
||||||
Lang::get('notifications.test_email_settings.success')
|
Lang::get('message.notifications.test_email_settings.success')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ class GroupService
|
|||||||
public static function prependTheAllGroup(Collection $groups, User $user)
|
public static function prependTheAllGroup(Collection $groups, User $user)
|
||||||
{
|
{
|
||||||
$theAllGroup = new Group([
|
$theAllGroup = new Group([
|
||||||
'name' => __('commons.all'),
|
'name' => __('message.all'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$theAllGroup->id = 0;
|
$theAllGroup->id = 0;
|
||||||
|
@ -101,8 +101,8 @@ class AegisMigrator extends Migrator
|
|||||||
$fakeAccount->id = TwoFAccount::FAKE_ID;
|
$fakeAccount->id = TwoFAccount::FAKE_ID;
|
||||||
$fakeAccount->otp_type = $otp_parameters['type'] ?? TwoFAccount::TOTP;
|
$fakeAccount->otp_type = $otp_parameters['type'] ?? TwoFAccount::TOTP;
|
||||||
// Only basic fields are filled to limit the risk of another exception.
|
// Only basic fields are filled to limit the risk of another exception.
|
||||||
$fakeAccount->account = $otp_parameters['name'] ?? __('twofaccounts.import.invalid_account');
|
$fakeAccount->account = $otp_parameters['name'] ?? __('message.twofaccounts.import.invalid_account');
|
||||||
$fakeAccount->service = $otp_parameters['issuer'] ?? __('twofaccounts.import.invalid_service');
|
$fakeAccount->service = $otp_parameters['issuer'] ?? __('message.twofaccounts.import.invalid_service');
|
||||||
// The secret field is used to pass the error, not very clean but will do the job for now.
|
// The secret field is used to pass the error, not very clean but will do the job for now.
|
||||||
$fakeAccount->secret = $exception->getMessage();
|
$fakeAccount->secret = $exception->getMessage();
|
||||||
|
|
||||||
|
@ -63,8 +63,8 @@ class GoogleAuthMigrator extends Migrator
|
|||||||
$fakeAccount->id = TwoFAccount::FAKE_ID;
|
$fakeAccount->id = TwoFAccount::FAKE_ID;
|
||||||
$fakeAccount->otp_type = $fakeAccount::TOTP;
|
$fakeAccount->otp_type = $fakeAccount::TOTP;
|
||||||
// Only basic fields are filled to limit the risk of another exception.
|
// Only basic fields are filled to limit the risk of another exception.
|
||||||
$fakeAccount->account = $otp_parameters->getName() ?? __('twofaccounts.import.invalid_account');
|
$fakeAccount->account = $otp_parameters->getName() ?? __('message.twofaccounts.import.invalid_account');
|
||||||
$fakeAccount->service = $otp_parameters->getIssuer() ?? __('twofaccounts.import.invalid_service');
|
$fakeAccount->service = $otp_parameters->getIssuer() ?? __('message.twofaccounts.import.invalid_service');
|
||||||
// The secret field is used to pass the error, not very clean but will do the job for now.
|
// The secret field is used to pass the error, not very clean but will do the job for now.
|
||||||
$fakeAccount->secret = $exception->getMessage();
|
$fakeAccount->secret = $exception->getMessage();
|
||||||
|
|
||||||
|
@ -41,8 +41,8 @@ class PlainTextMigrator extends Migrator
|
|||||||
$fakeAccount->id = TwoFAccount::FAKE_ID;
|
$fakeAccount->id = TwoFAccount::FAKE_ID;
|
||||||
$fakeAccount->otp_type = substr($uri, 10, 4);
|
$fakeAccount->otp_type = substr($uri, 10, 4);
|
||||||
// Only basic fields are filled to limit the risk of another exception.
|
// Only basic fields are filled to limit the risk of another exception.
|
||||||
$fakeAccount->account = __('twofaccounts.import.invalid_account');
|
$fakeAccount->account = __('message.twofaccounts.import.invalid_account');
|
||||||
$fakeAccount->service = filter_input(INPUT_GET, 'issuer', FILTER_SANITIZE_ENCODED) ?? __('twofaccounts.import.invalid_service');
|
$fakeAccount->service = filter_input(INPUT_GET, 'issuer', FILTER_SANITIZE_ENCODED) ?? __('message.twofaccounts.import.invalid_service');
|
||||||
// The secret field is used to pass the error, not very clean but will do the job for now.
|
// The secret field is used to pass the error, not very clean but will do the job for now.
|
||||||
$fakeAccount->secret = $exception->getMessage();
|
$fakeAccount->secret = $exception->getMessage();
|
||||||
|
|
||||||
|
@ -107,8 +107,8 @@ class TwoFASMigrator extends Migrator
|
|||||||
$fakeAccount->id = TwoFAccount::FAKE_ID;
|
$fakeAccount->id = TwoFAccount::FAKE_ID;
|
||||||
$fakeAccount->otp_type = $otp_parameters['otp']['tokenType'] ?? TwoFAccount::TOTP;
|
$fakeAccount->otp_type = $otp_parameters['otp']['tokenType'] ?? TwoFAccount::TOTP;
|
||||||
// Only basic fields are filled to limit the risk of another exception.
|
// Only basic fields are filled to limit the risk of another exception.
|
||||||
$fakeAccount->account = $otp_parameters['otp']['account'] ?? __('twofaccounts.import.invalid_account');
|
$fakeAccount->account = $otp_parameters['otp']['account'] ?? __('message.twofaccounts.import.invalid_account');
|
||||||
$fakeAccount->service = $otp_parameters['name'] ?? __('twofaccounts.import.invalid_service');
|
$fakeAccount->service = $otp_parameters['name'] ?? __('message.twofaccounts.import.invalid_service');
|
||||||
// The secret field is used to pass the error, not very clean but will do the job for now.
|
// The secret field is used to pass the error, not very clean but will do the job for now.
|
||||||
$fakeAccount->secret = $exception->getMessage();
|
$fakeAccount->secret = $exception->getMessage();
|
||||||
|
|
||||||
|
@ -119,8 +119,8 @@ class TwoFAuthMigrator extends Migrator
|
|||||||
$fakeAccount->id = TwoFAccount::FAKE_ID;
|
$fakeAccount->id = TwoFAccount::FAKE_ID;
|
||||||
$fakeAccount->otp_type = $otp_parameters['otp']['tokenType'] ?? TwoFAccount::TOTP;
|
$fakeAccount->otp_type = $otp_parameters['otp']['tokenType'] ?? TwoFAccount::TOTP;
|
||||||
// Only basic fields are filled to limit the risk of another exception.
|
// Only basic fields are filled to limit the risk of another exception.
|
||||||
$fakeAccount->account = $otp_parameters['otp']['account'] ?? __('twofaccounts.import.invalid_account');
|
$fakeAccount->account = $otp_parameters['otp']['account'] ?? __('message.twofaccounts.import.invalid_account');
|
||||||
$fakeAccount->service = $otp_parameters['name'] ?? __('twofaccounts.import.invalid_service');
|
$fakeAccount->service = $otp_parameters['name'] ?? __('message.twofaccounts.import.invalid_service');
|
||||||
// The secret field is used to pass the error, not very clean but will do the job for now.
|
// The secret field is used to pass the error, not very clean but will do the job for now.
|
||||||
$fakeAccount->secret = $exception->getMessage();
|
$fakeAccount->secret = $exception->getMessage();
|
||||||
|
|
||||||
|
@ -58,13 +58,13 @@ class QrCodeService
|
|||||||
if (! $text) {
|
if (! $text) {
|
||||||
switch (get_class($qrcode->getError())) {
|
switch (get_class($qrcode->getError())) {
|
||||||
case NotFoundException::class:
|
case NotFoundException::class:
|
||||||
throw new \App\Exceptions\InvalidQrCodeException(__('errors.cannot_detect_qrcode_in_image'));
|
throw new \App\Exceptions\InvalidQrCodeException(__('error.cannot_detect_qrcode_in_image'));
|
||||||
case FormatException::class:
|
case FormatException::class:
|
||||||
throw new \App\Exceptions\InvalidQrCodeException(__('errors.cannot_decode_detected_qrcode'));
|
throw new \App\Exceptions\InvalidQrCodeException(__('error.cannot_decode_detected_qrcode'));
|
||||||
case ChecksumException::class:
|
case ChecksumException::class:
|
||||||
throw new \App\Exceptions\InvalidQrCodeException(__('errors.qrcode_has_invalid_checksum'));
|
throw new \App\Exceptions\InvalidQrCodeException(__('error.qrcode_has_invalid_checksum'));
|
||||||
default:
|
default:
|
||||||
throw new \App\Exceptions\InvalidQrCodeException(__('errors.no_readable_qrcode'));
|
throw new \App\Exceptions\InvalidQrCodeException(__('error.no_readable_qrcode'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ class SettingService
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log::warning('Some data cannot be encrypted/decrypted, the useEncryption setting remain unchanged');
|
Log::warning('Some data cannot be encrypted/decrypted, the useEncryption setting remain unchanged');
|
||||||
throw new DbEncryptionException($state === true ? __('errors.error_during_encryption') : __('errors.error_during_decryption'));
|
throw new DbEncryptionException($state === true ? __('error.error_during_encryption') : __('error.error_during_decryption'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ return new class extends Migration
|
|||||||
// We don't want to encrypt the Service field with a different APP_KEY
|
// We don't want to encrypt the Service field with a different APP_KEY
|
||||||
// than the one used to encrypt the legacy_uri, account and secret fields, the
|
// than the one used to encrypt the legacy_uri, account and secret fields, the
|
||||||
// model would be inconsistent.
|
// model would be inconsistent.
|
||||||
if ($twofaccount->legacy_uri === __('errors.indecipherable')) {
|
if ($twofaccount->legacy_uri === __('error.indecipherable')) {
|
||||||
Log::warning(sprintf('Migration: Service encryption failed for twofaccount with id #%s. The current APP_KEY cannot decipher already encrypted fields, encrypting the Service field with this key would lead to inconsistent model encryption', $twofaccount->id));
|
Log::warning(sprintf('Migration: Service encryption failed for twofaccount with id #%s. The current APP_KEY cannot decipher already encrypted fields, encrypting the Service field with this key would lead to inconsistent model encryption', $twofaccount->id));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -69,7 +69,7 @@ return new class extends Migration
|
|||||||
foreach (TwoFAccount::all() as $twofaccount) {
|
foreach (TwoFAccount::all() as $twofaccount) {
|
||||||
Log::notice(sprintf('Migration rollback: Trying to decipher Service field for twofaccount with id #%s', $twofaccount->id));
|
Log::notice(sprintf('Migration rollback: Trying to decipher Service field for twofaccount with id #%s', $twofaccount->id));
|
||||||
|
|
||||||
if ($twofaccount->legacy_uri === __('errors.indecipherable')) {
|
if ($twofaccount->legacy_uri === __('error.indecipherable')) {
|
||||||
Log::warning(sprintf('Migration rollback: Service decipherement failed for twofaccount with id #%s', $twofaccount->id));
|
Log::warning(sprintf('Migration rollback: Service decipherement failed for twofaccount with id #%s', $twofaccount->id));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
881
package-lock.json
generated
881
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,7 @@
|
|||||||
"@fortawesome/free-regular-svg-icons": "^6.4.2",
|
"@fortawesome/free-regular-svg-icons": "^6.4.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||||
"@fortawesome/vue-fontawesome": "^3.0.3",
|
"@fortawesome/vue-fontawesome": "^3.0.3",
|
||||||
|
"@intlify/unplugin-vue-i18n": "^6.0.8",
|
||||||
"@kyvg/vue3-notification": "^3.0.2",
|
"@kyvg/vue3-notification": "^3.0.2",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@vueuse/components": "^10.4.1",
|
"@vueuse/components": "^10.4.1",
|
||||||
@ -24,16 +25,17 @@
|
|||||||
"eslint-plugin-vue": "^10.0.0",
|
"eslint-plugin-vue": "^10.0.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"laravel-vite-plugin": "^1.0.2",
|
"laravel-vite-plugin": "^1.0.2",
|
||||||
"laravel-vue-i18n": "^2.7.1",
|
|
||||||
"lucide-vue-next": "^0.517.0",
|
"lucide-vue-next": "^0.517.0",
|
||||||
"php-parser": "^3.1.5",
|
"php-parser": "^3.1.5",
|
||||||
"pinia": "^2.1.6",
|
"pinia": "^2.1.6",
|
||||||
"sass": "^1.67.0",
|
"sass": "^1.67.0",
|
||||||
"sortablejs": "^1.15.0",
|
"sortablejs": "^1.15.0",
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.8.2",
|
||||||
|
"unimport": "^5.0.1",
|
||||||
"unplugin-auto-import": "^19.1.2",
|
"unplugin-auto-import": "^19.1.2",
|
||||||
"vite": "^5.2.7",
|
"vite": "^5.2.7",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
|
"vue-i18n": "^11.1.6",
|
||||||
"vue-qrcode-reader": "^5.4.0",
|
"vue-qrcode-reader": "^5.4.0",
|
||||||
"vue-router": "^4.2.4"
|
"vue-router": "^4.2.4"
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { RouterView } from 'vue-router'
|
import { RouterView } from 'vue-router'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const kickUser = ref(null)
|
const kickUser = ref(null)
|
||||||
const kickUserAfter = ref(null)
|
const kickUserAfter = ref(null)
|
||||||
@ -39,6 +41,11 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.afterEach((to, from) => {
|
||||||
|
to.meta.title = t('title.' + to.name)
|
||||||
|
document.title = to.meta.title
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
18
resources/js/app.js
vendored
18
resources/js/app.js
vendored
@ -1,5 +1,6 @@
|
|||||||
import '@2fauth/styles/src/app.scss';
|
import '@2fauth/styles/src/app.scss';
|
||||||
|
|
||||||
|
import i18n from './i18n'
|
||||||
import Notifications from '@kyvg/vue3-notification'
|
import Notifications from '@kyvg/vue3-notification'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
@ -20,27 +21,20 @@ const $2fauth = {
|
|||||||
}
|
}
|
||||||
app.provide('2fauth', readonly($2fauth))
|
app.provide('2fauth', readonly($2fauth))
|
||||||
|
|
||||||
|
// Localization
|
||||||
|
app.use(i18n)
|
||||||
|
|
||||||
// Stores
|
// Stores
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
pinia.use(({ store }) => {
|
pinia.use(({ store }) => {
|
||||||
store.$2fauth = $2fauth;
|
store.$2fauth = $2fauth;
|
||||||
});
|
store.$i18n = i18n
|
||||||
|
})
|
||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
|
|
||||||
// Router
|
// Router
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
|
||||||
// Localization
|
|
||||||
app.use(i18nVue, {
|
|
||||||
lang: document.documentElement.lang,
|
|
||||||
resolve: async lang => {
|
|
||||||
const langs = import.meta.glob('../lang/*.json');
|
|
||||||
if (lang.includes('php_')) {
|
|
||||||
return await langs[`../lang/${lang}.json`]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Notifications
|
// Notifications
|
||||||
app.use(Notifications)
|
app.use(Notifications)
|
||||||
|
|
||||||
|
@ -142,23 +142,23 @@
|
|||||||
<nav v-if="props.showSearch" class="level is-mobile mb-2">
|
<nav v-if="props.showSearch" class="level is-mobile mb-2">
|
||||||
<div class="level-item has-text-centered">
|
<div class="level-item has-text-centered">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button id="btnShowOneMonth" :title="$t('admin.show_last_month_log')" v-on:click="setPeriod(periods.aMonth)" :class="{ 'has-text-grey' : period !== periods.aMonth }" type="button" class="button is-ghost p-1">
|
<button id="btnShowOneMonth" :title="$t('message.admin.show_last_month_log')" v-on:click="setPeriod(periods.aMonth)" :class="{ 'has-text-grey' : period !== periods.aMonth }" type="button" class="button is-ghost p-1">
|
||||||
{{ $t('commons.one_month') }}
|
{{ $t('message.one_month') }}
|
||||||
</button>
|
</button>
|
||||||
<button id="btnShowThreeMonths" :title="$t('admin.show_three_months_log')" v-on:click="setPeriod(periods.threeMonths)" :class="{ 'has-text-grey' : period !== periods.threeMonths }" type="button" class="button is-ghost p-1">
|
<button id="btnShowThreeMonths" :title="$t('message.admin.show_three_months_log')" v-on:click="setPeriod(periods.threeMonths)" :class="{ 'has-text-grey' : period !== periods.threeMonths }" type="button" class="button is-ghost p-1">
|
||||||
{{ $t('commons.x_month', { 'x' : '3' }) }}
|
{{ $t('message.x_month', { 'x' : '3' }) }}
|
||||||
</button>
|
</button>
|
||||||
<button id="btnShowSixMonths" :title="$t('admin.show_six_months_log')" v-on:click="setPeriod(periods.halfYear)" :class="{ 'has-text-grey' : period !== periods.halfYear }" type="button" class="button is-ghost p-1">
|
<button id="btnShowSixMonths" :title="$t('message.admin.show_six_months_log')" v-on:click="setPeriod(periods.halfYear)" :class="{ 'has-text-grey' : period !== periods.halfYear }" type="button" class="button is-ghost p-1">
|
||||||
{{ $t('commons.x_month', { 'x' : '6' }) }}
|
{{ $t('message.x_month', { 'x' : '6' }) }}
|
||||||
</button>
|
</button>
|
||||||
<button id="btnShowOneYear" :title="$t('admin.show_one_year_log')" v-on:click="setPeriod(periods.aYear)" :class="{ 'has-text-grey' : period !== periods.aYear }" type="button" class="button is-ghost p-1 mr-5">
|
<button id="btnShowOneYear" :title="$t('message.admin.show_one_year_log')" v-on:click="setPeriod(periods.aYear)" :class="{ 'has-text-grey' : period !== periods.aYear }" type="button" class="button is-ghost p-1 mr-5">
|
||||||
{{ $t('commons.one_year') }}
|
{{ $t('message.one_year') }}
|
||||||
</button>
|
</button>
|
||||||
<button id="btnSortLogDesc" v-on:click="setDesc" :title="$t('admin.sort_by_date_desc')" :class="{ 'has-text-grey' : !orderIsDesc }" type="button" class="button p-1 is-ghost">
|
<button id="btnSortLogDesc" v-on:click="setDesc" :title="$t('message.admin.sort_by_date_desc')" :class="{ 'has-text-grey' : !orderIsDesc }" type="button" class="button p-1 is-ghost">
|
||||||
<FontAwesomeIcon :icon="['fas', 'arrow-up-long']" flip="vertical" />
|
<FontAwesomeIcon :icon="['fas', 'arrow-up-long']" flip="vertical" />
|
||||||
<FontAwesomeIcon :icon="['far', 'calendar']" />
|
<FontAwesomeIcon :icon="['far', 'calendar']" />
|
||||||
</button>
|
</button>
|
||||||
<button id="btnSortLogAsc" v-on:click="setAsc" :title="$t('admin.sort_by_date_asc')" :class="{ 'has-text-grey' : orderIsDesc }" type="button" class="button p-1 is-ghost">
|
<button id="btnSortLogAsc" v-on:click="setAsc" :title="$t('message.admin.sort_by_date_asc')" :class="{ 'has-text-grey' : orderIsDesc }" type="button" class="button p-1 is-ghost">
|
||||||
<FontAwesomeIcon :icon="['fas', 'arrow-up-long']" />
|
<FontAwesomeIcon :icon="['fas', 'arrow-up-long']" />
|
||||||
<FontAwesomeIcon :icon="['far', 'calendar']" />
|
<FontAwesomeIcon :icon="['far', 'calendar']" />
|
||||||
</button>
|
</button>
|
||||||
@ -170,15 +170,31 @@
|
|||||||
<UseColorMode v-slot="{ mode }">
|
<UseColorMode v-slot="{ mode }">
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<span v-if="isFailedEntry(authentication)" v-html="$t('admin.failed_login_on', { login_at: authentication.login_at })" />
|
<i18n-t v-if="isFailedEntry(authentication)" keypath="message.admin.failed_login_on" tag="span">
|
||||||
<span v-else-if="isSuccessfulLogout(authentication)" v-html="$t('admin.successful_logout_on', { login_at: authentication.logout_at })" />
|
<template v-slot:login_at>
|
||||||
<span v-else-if="$2fauth.config.proxyAuth" v-html="$t('admin.viewed_on', { login_at: authentication.login_at })" />
|
<span class="light-or-darker">{{ authentication.login_at }}</span>
|
||||||
<span v-else v-html="$t('admin.successful_login_on', { login_at: authentication.login_at })" />
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
<i18n-t v-else-if="isSuccessfulLogout(authentication)" keypath="message.admin.successful_logout_on" tag="span">
|
||||||
|
<template v-slot:logout_at>
|
||||||
|
<span class="light-or-darker">{{ authentication.logout_at }}</span>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
<i18n-t v-else-if="$2fauth.config.proxyAuth" keypath="message.admin.viewed_on" tag="span">
|
||||||
|
<template v-slot:login_at>
|
||||||
|
<span class="light-or-darker">{{ authentication.login_at }}</span>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
<i18n-t v-else keypath="message.admin.successful_login_on" tag="span">
|
||||||
|
<template v-slot:login_at>
|
||||||
|
<span class="light-or-darker">{{ authentication.login_at }}</span>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{ $t('commons.IP') }}: <span class="light-or-darker">{{ authentication.ip_address }}</span> -
|
{{ $t('message.IP') }}: <span class="light-or-darker">{{ authentication.ip_address }}</span> -
|
||||||
{{ $t('commons.browser') }}: <span class="light-or-darker">{{ authentication.browser }}</span> -
|
{{ $t('message.browser') }}: <span class="light-or-darker">{{ authentication.browser }}</span> -
|
||||||
{{ $t('commons.operating_system_short') }}: <span class="light-or-darker">{{ authentication.platform }}</span>
|
{{ $t('message.operating_system_short') }}: <span class="light-or-darker">{{ authentication.platform }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="mode == 'dark' ? 'has-text-grey-darker' : 'has-text-grey-lighter'" class="is-align-self-center ">
|
<div :class="mode == 'dark' ? 'has-text-grey-darker' : 'has-text-grey-lighter'" class="is-align-self-center ">
|
||||||
@ -193,10 +209,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="authentications.length == 0" class="mt-4">
|
<div v-else-if="authentications.length == 0" class="mt-4">
|
||||||
{{ $t('commons.no_entry_yet') }}
|
{{ $t('message.no_entry_yet') }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="mt-5 pl-3">
|
<div v-else class="mt-5 pl-3">
|
||||||
{{ $t('commons.no_result') }}
|
{{ $t('message.no_result') }}
|
||||||
</div>
|
</div>
|
||||||
<Spinner :isVisible="isFetching" />
|
<Spinner :isVisible="isFetching" />
|
||||||
</template>
|
</template>
|
@ -33,7 +33,7 @@
|
|||||||
<!-- New item buttons -->
|
<!-- New item buttons -->
|
||||||
<p class="control" v-if="!inManagementMode">
|
<p class="control" v-if="!inManagementMode">
|
||||||
<button type="button" class="button is-link is-rounded is-focus" @click="goAddNewAccount">
|
<button type="button" class="button is-link is-rounded is-focus" @click="goAddNewAccount">
|
||||||
<span>{{ $t('commons.new') }}</span>
|
<span>{{ $t('message.new') }}</span>
|
||||||
<span class="icon is-small">
|
<span class="icon is-small">
|
||||||
<FontAwesomeIcon :icon="['fas', 'qrcode']" />
|
<FontAwesomeIcon :icon="['fas', 'qrcode']" />
|
||||||
</span>
|
</span>
|
||||||
@ -41,7 +41,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<!-- Manage button -->
|
<!-- Manage button -->
|
||||||
<p class="control" v-if="!inManagementMode">
|
<p class="control" v-if="!inManagementMode">
|
||||||
<button type="button" id="btnManage" class="button is-rounded" :class="{'is-dark' : mode == 'dark'}" @click="$emit('update:inManagementMode', true)">{{ $t('commons.manage') }}</button>
|
<button type="button" id="btnManage" class="button is-rounded" :class="{'is-dark' : mode == 'dark'}" @click="$emit('update:inManagementMode', true)">{{ $t('message.manage') }}</button>
|
||||||
</p>
|
</p>
|
||||||
<!-- move button -->
|
<!-- move button -->
|
||||||
<p class="control" v-if="inManagementMode">
|
<p class="control" v-if="inManagementMode">
|
||||||
@ -50,8 +50,8 @@
|
|||||||
:disabled='areDisabled' class="button is-rounded"
|
:disabled='areDisabled' class="button is-rounded"
|
||||||
:class="[{ 'is-outlined': mode == 'dark' || areDisabled }, areDisabled ? 'is-dark': 'is-link']"
|
:class="[{ 'is-outlined': mode == 'dark' || areDisabled }, areDisabled ? 'is-dark': 'is-link']"
|
||||||
@click="$emit('move-button-clicked')"
|
@click="$emit('move-button-clicked')"
|
||||||
:title="$t('groups.move_selected_to_group')" >
|
:title="$t('message.groups.move_selected_to_group')" >
|
||||||
{{ $t('commons.move') }}
|
{{ $t('message.move') }}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
<!-- delete button -->
|
<!-- delete button -->
|
||||||
@ -61,7 +61,7 @@
|
|||||||
:disabled='areDisabled' class="button is-rounded"
|
:disabled='areDisabled' class="button is-rounded"
|
||||||
:class="[{ 'is-outlined': mode == 'dark' || areDisabled }, areDisabled ? 'is-dark': 'is-link']"
|
:class="[{ 'is-outlined': mode == 'dark' || areDisabled }, areDisabled ? 'is-dark': 'is-link']"
|
||||||
@click="$emit('delete-button-clicked')" >
|
@click="$emit('delete-button-clicked')" >
|
||||||
{{ $t('commons.delete') }}
|
{{ $t('message.delete') }}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
<!-- export button -->
|
<!-- export button -->
|
||||||
@ -71,8 +71,8 @@
|
|||||||
:disabled='areDisabled' class="button is-rounded"
|
:disabled='areDisabled' class="button is-rounded"
|
||||||
:class="[{ 'is-outlined': mode == 'dark' || areDisabled }, areDisabled ? 'is-dark': 'is-link']"
|
:class="[{ 'is-outlined': mode == 'dark' || areDisabled }, areDisabled ? 'is-dark': 'is-link']"
|
||||||
@click="$emit('export-button-clicked')"
|
@click="$emit('export-button-clicked')"
|
||||||
:title="$t('twofaccounts.export_selected_accounts')" >
|
:title="$t('message.twofaccounts.export_selected_accounts')" >
|
||||||
{{ $t('commons.export') }}
|
{{ $t('message.export') }}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</UseColorMode>
|
</UseColorMode>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
const { copy } = useClipboard({ legacy: true })
|
const { copy } = useClipboard({ legacy: true })
|
||||||
@ -10,12 +12,12 @@
|
|||||||
|
|
||||||
function copyToClipboard() {
|
function copyToClipboard() {
|
||||||
copy(props.token)
|
copy(props.token)
|
||||||
notify.success({ text: trans('commons.copied_to_clipboard') })
|
notify.success({ text: t('message.copied_to_clipboard') })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button type="button" :aria-label="$t('commons.copy_to_clipboard')" :title="$t('commons.copy_to_clipboard')" class="button is-like-text is-pulled-right is-small is-text" @click.stop="copyToClipboard()">
|
<button type="button" :aria-label="$t('message.copy_to_clipboard')" :title="$t('message.copy_to_clipboard')" class="button is-like-text is-pulled-right is-small is-text" @click.stop="copyToClipboard()">
|
||||||
<FontAwesomeIcon :icon="['fas', 'copy']" />
|
<FontAwesomeIcon :icon="['fas', 'copy']" />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
@ -33,7 +33,7 @@
|
|||||||
<div class="container group-selector">
|
<div class="container group-selector">
|
||||||
<div class="columns is-centered is-multiline">
|
<div class="columns is-centered is-multiline">
|
||||||
<div class="column is-full has-text-centered">
|
<div class="column is-full has-text-centered">
|
||||||
{{ $t('groups.move_selected_to') }}
|
{{ $t('message.groups.move_selected_to') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-one-third-tablet is-one-quarter-desktop is-one-quarter-widescreen is-one-quarter-fullhd">
|
<div class="column is-one-third-tablet is-one-quarter-desktop is-one-quarter-widescreen is-one-quarter-fullhd">
|
||||||
<div class="columns is-multiline">
|
<div class="columns is-multiline">
|
||||||
@ -41,7 +41,7 @@
|
|||||||
<UseColorMode v-slot="{ mode }">
|
<UseColorMode v-slot="{ mode }">
|
||||||
<button type="button" class="button is-fullwidth" :class="{'is-link' : destinationGroupId === group.id, 'is-dark has-text-light is-outlined': mode == 'dark'}" @click="destinationGroupId = group.id">
|
<button type="button" class="button is-fullwidth" :class="{'is-link' : destinationGroupId === group.id, 'is-dark has-text-light is-outlined': mode == 'dark'}" @click="destinationGroupId = group.id">
|
||||||
<span v-if="group.id === 0" class="is-italic">
|
<span v-if="group.id === 0" class="is-italic">
|
||||||
{{ $t('groups.no_group') }}
|
{{ $t('message.groups.no_group') }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
{{ group.name }}
|
{{ group.name }}
|
||||||
@ -52,7 +52,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="columns is-centered">
|
<div class="columns is-centered">
|
||||||
<div class="column has-text-centered">
|
<div class="column has-text-centered">
|
||||||
<RouterLink :to="{ name: 'groups' }" >{{ $t('groups.manage_groups') }}</RouterLink>
|
<RouterLink :to="{ name: 'groups' }" >{{ $t('message.groups.manage_groups') }}</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -60,7 +60,7 @@
|
|||||||
<VueFooter :showButtons="true">
|
<VueFooter :showButtons="true">
|
||||||
<!-- Move to selected group button -->
|
<!-- Move to selected group button -->
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<button type="button" class="button is-link is-rounded" @click="moveAccounts">{{ $t('commons.move') }}</button>
|
<button type="button" class="button is-link is-rounded" @click="moveAccounts">{{ $t('message.move') }}</button>
|
||||||
</p>
|
</p>
|
||||||
<NavigationButton action="cancel" :useLinkTag="false" @canceled="$emit('update:showDestinationGroupSelector', false)" />
|
<NavigationButton action="cancel" :useLinkTag="false" @canceled="$emit('update:showDestinationGroupSelector', false)" />
|
||||||
</VueFooter>
|
</VueFooter>
|
||||||
|
@ -11,29 +11,29 @@
|
|||||||
<div class="block">
|
<div class="block">
|
||||||
<UseColorMode v-slot="{ mode }">
|
<UseColorMode v-slot="{ mode }">
|
||||||
<p class="has-text-weight-bold has-text-grey">
|
<p class="has-text-weight-bold has-text-grey">
|
||||||
{{ $t('twofaccounts.twofauth_export_format_sub') }}
|
{{ $t('message.twofaccounts.twofauth_export_format_sub') }}
|
||||||
</p>
|
</p>
|
||||||
</UseColorMode>
|
</UseColorMode>
|
||||||
<p class="is-size-7-mobile">
|
<p class="is-size-7-mobile">
|
||||||
{{ $t('twofaccounts.twofauth_export_format_desc') }}
|
{{ $t('message.twofaccounts.twofauth_export_format_desc') }}
|
||||||
{{ $t('twofaccounts.twofauth_export_format_url') }}
|
{{ $t('message.twofaccounts.twofauth_export_format_url') }}
|
||||||
<a id="lnkExportSchemaUrl" class="is-link" tabindex="0" :href="$2fauth.urls.exportSchemaUrl" target="_blank">
|
<a id="lnkExportSchemaUrl" class="is-link" tabindex="0" :href="$2fauth.urls.exportSchemaUrl" target="_blank">
|
||||||
{{ $t('twofaccounts.twofauth_export_schema') }}
|
{{ $t('message.twofaccounts.twofauth_export_schema') }}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<button type="button" id="btnExport2FAuth" class="button is-link is-rounded is-focus my-3" @click="$emit('export-twofauth-format')" :title="$t('twofaccounts.twofauth_export_format_sub')">
|
<button type="button" id="btnExport2FAuth" class="button is-link is-rounded is-focus my-3" @click="$emit('export-twofauth-format')" :title="$t('message.twofaccounts.twofauth_export_format_sub')">
|
||||||
{{ $t('twofaccounts.twofauth_export_format') }}
|
{{ $t('message.twofaccounts.twofauth_export_format') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<p class="has-text-weight-bold has-text-grey">
|
<p class="has-text-weight-bold has-text-grey">
|
||||||
{{ $t('twofaccounts.otpauth_export_format_sub') }}
|
{{ $t('message.twofaccounts.otpauth_export_format_sub') }}
|
||||||
</p>
|
</p>
|
||||||
<p class="is-size-7-mobile">
|
<p class="is-size-7-mobile">
|
||||||
{{ $t('twofaccounts.otpauth_export_format_desc') }}
|
{{ $t('message.twofaccounts.otpauth_export_format_desc') }}
|
||||||
</p>
|
</p>
|
||||||
<button type="button" id="btnExportOtpauth" class="button is-link is-rounded is-focus my-3" @click="$emit('export-otpauth-format')" :title="$t('twofaccounts.otpauth_export_format_sub')">
|
<button type="button" id="btnExportOtpauth" class="button is-link is-rounded is-focus my-3" @click="$emit('export-otpauth-format')" :title="$t('message.twofaccounts.otpauth_export_format_sub')">
|
||||||
{{ $t('twofaccounts.otpauth_export_format') }}
|
{{ $t('message.twofaccounts.otpauth_export_format') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
@ -39,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="columns is-centered">
|
<div class="columns is-centered">
|
||||||
<div class="column has-text-centered">
|
<div class="column has-text-centered">
|
||||||
<RouterLink :to="{ name: 'groups' }" >{{ $t('groups.manage_groups') }}</RouterLink>
|
<RouterLink :to="{ name: 'groups' }" >{{ $t('message.groups.manage_groups') }}</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
import Spinner from '@/components/Spinner.vue'
|
import Spinner from '@/components/Spinner.vue'
|
||||||
import TotpLooper from '@/components/TotpLooper.vue'
|
import TotpLooper from '@/components/TotpLooper.vue'
|
||||||
import Dots from '@/components/Dots.vue'
|
import Dots from '@/components/Dots.vue'
|
||||||
@ -8,6 +9,7 @@
|
|||||||
import { UseColorMode } from '@vueuse/components'
|
import { UseColorMode } from '@vueuse/components'
|
||||||
import { useDisplayablePassword } from '@/composables/helpers'
|
import { useDisplayablePassword } from '@/composables/helpers'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const user = useUserStore()
|
const user = useUserStore()
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
const $2fauth = inject('2fauth')
|
const $2fauth = inject('2fauth')
|
||||||
@ -115,10 +117,10 @@
|
|||||||
}
|
}
|
||||||
// Case 3
|
// Case 3
|
||||||
else if (! props.secret) {
|
else if (! props.secret) {
|
||||||
notify.error(new Error(trans('errors.cannot_create_otp_without_secret')))
|
notify.error(new Error(t('error.cannot_create_otp_without_secret')))
|
||||||
}
|
}
|
||||||
else if (! isTimeBased(otpauthParams.value.otp_type) && ! isHMacBased(otpauthParams.value.otp_type)) {
|
else if (! isTimeBased(otpauthParams.value.otp_type) && ! isHMacBased(otpauthParams.value.otp_type)) {
|
||||||
notify.error(new Error(trans('errors.not_a_supported_otp_type')))
|
notify.error(new Error(t('error.not_a_supported_otp_type')))
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -267,7 +269,7 @@
|
|||||||
: user.preferences.defaultGroup
|
: user.preferences.defaultGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
notify.success({ text: trans('commons.copied_to_clipboard') })
|
notify.success({ text: t('message.copied_to_clipboard') })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,7 +320,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<figure class="image is-64x64" :class="{ 'no-icon': !otpauthParams.icon }" style="display: inline-flex">
|
<figure class="image is-64x64" :class="{ 'no-icon': !otpauthParams.icon }" style="display: inline-flex">
|
||||||
<img :src="$2fauth.config.subdirectory + '/storage/icons/' + otpauthParams.icon" v-if="otpauthParams.icon" :alt="$t('twofaccounts.icon_to_illustrate_the_account')">
|
<img :src="$2fauth.config.subdirectory + '/storage/icons/' + otpauthParams.icon" v-if="otpauthParams.icon" :alt="$t('message.twofaccounts.icon_to_illustrate_the_account')">
|
||||||
</figure>
|
</figure>
|
||||||
<UseColorMode v-slot="{ mode }">
|
<UseColorMode v-slot="{ mode }">
|
||||||
<p class="is-size-4 has-ellipsis" :class="mode == 'dark' ? 'has-text-grey-light' : 'has-text-grey'">{{ otpauthParams.service }}</p>
|
<p class="is-size-4 has-ellipsis" :class="mode == 'dark' ? 'has-text-grey-light' : 'has-text-grey'">{{ otpauthParams.service }}</p>
|
||||||
@ -334,7 +336,7 @@
|
|||||||
:class="mode == 'dark' ? 'has-text-white' : 'has-text-grey-dark'"
|
:class="mode == 'dark' ? 'has-text-white' : 'has-text-grey-dark'"
|
||||||
@click="copyOTP(password, true)"
|
@click="copyOTP(password, true)"
|
||||||
@keyup.enter="copyOTP(password, true)"
|
@keyup.enter="copyOTP(password, true)"
|
||||||
:title="$t('commons.copy_to_clipboard')"
|
:title="$t('message.copy_to_clipboard')"
|
||||||
>
|
>
|
||||||
{{ useDisplayablePassword(password, user.preferences.showOtpAsDot && user.preferences.revealDottedOTP && revealPassword) }}
|
{{ useDisplayablePassword(password, user.preferences.showOtpAsDot && user.preferences.revealDottedOTP && revealPassword) }}
|
||||||
</span>
|
</span>
|
||||||
@ -345,7 +347,7 @@
|
|||||||
</UseColorMode>
|
</UseColorMode>
|
||||||
<Dots v-show="isTimeBased(otpauthParams.otp_type)" ref="dots"></Dots>
|
<Dots v-show="isTimeBased(otpauthParams.otp_type)" ref="dots"></Dots>
|
||||||
<p v-show="isHMacBased(otpauthParams.otp_type)">
|
<p v-show="isHMacBased(otpauthParams.otp_type)">
|
||||||
{{ $t('twofaccounts.forms.counter.label') }}: {{ otpauthParams.counter }}
|
{{ $t('message.twofaccounts.forms.counter.label') }}: {{ otpauthParams.counter }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="user.preferences.showNextOtp" class="mt-3 is-size-4">
|
<p v-if="user.preferences.showNextOtp" class="mt-3 is-size-4">
|
||||||
<span
|
<span
|
||||||
@ -354,7 +356,7 @@
|
|||||||
:class="opacity"
|
:class="opacity"
|
||||||
@click="copyOTP(next_password, true)"
|
@click="copyOTP(next_password, true)"
|
||||||
@keyup.enter="copyOTP(next_password, true)"
|
@keyup.enter="copyOTP(next_password, true)"
|
||||||
:title="$t('commons.copy_next_password')"
|
:title="$t('message.copy_next_password')"
|
||||||
>
|
>
|
||||||
{{ useDisplayablePassword(next_password, user.preferences.showOtpAsDot && user.preferences.revealDottedOTP && revealPassword) }}
|
{{ useDisplayablePassword(next_password, user.preferences.showOtpAsDot && user.preferences.revealDottedOTP && revealPassword) }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
const { copy } = useClipboard({ legacy: true })
|
const { copy } = useClipboard({ legacy: true })
|
||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -38,26 +40,26 @@
|
|||||||
*/
|
*/
|
||||||
function copyToClipboard(data) {
|
function copyToClipboard(data) {
|
||||||
copy(data)
|
copy(data)
|
||||||
notify.success({ text: trans('commons.copied_to_clipboard') })
|
notify.success({ text: t('message.copied_to_clipboard') })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="too-bad"></div>
|
<div class="too-bad"></div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{{ $t('errors.data_of_qrcode_is_not_valid_URI') }}
|
{{ $t('error.data_of_qrcode_is_not_valid_URI') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="block mb-6 light-or-darker">{{ qrContent ? qrContent : '[' + trans('commons.nothing') + ']' }}</div>
|
<div class="block mb-6 light-or-darker">{{ qrContent ? qrContent : '[' + $t('message.nothing') + ']' }}</div>
|
||||||
<!-- Copy to clipboard -->
|
<!-- Copy to clipboard -->
|
||||||
<div class="block has-text-link" v-if="qrContent">
|
<div class="block has-text-link" v-if="qrContent">
|
||||||
<button type="button" class="button is-link is-outlined is-rounded" @click.stop="copyToClipboard(qrContent)">
|
<button type="button" class="button is-link is-outlined is-rounded" @click.stop="copyToClipboard(qrContent)">
|
||||||
{{ $t('commons.copy_to_clipboard') }}
|
{{ $t('message.copy_to_clipboard') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Open in browser -->
|
<!-- Open in browser -->
|
||||||
<div class="block has-text-link" v-if="isUrl(qrContent)" @click="openInBrowser(qrContent)">
|
<div class="block has-text-link" v-if="isUrl(qrContent)" @click="openInBrowser(qrContent)">
|
||||||
<button type="button" class="button is-link is-outlined is-rounded">
|
<button type="button" class="button is-link is-outlined is-rounded">
|
||||||
<span>{{ $t('commons.open_in_browser') }}</span>
|
<span>{{ $t('message.open_in_browser') }}</span>
|
||||||
<span class="icon is-small">
|
<span class="icon is-small">
|
||||||
<FontAwesomeIcon :icon="['fas', 'external-link-alt']" />
|
<FontAwesomeIcon :icon="['fas', 'external-link-alt']" />
|
||||||
</span>
|
</span>
|
||||||
|
@ -64,13 +64,13 @@
|
|||||||
id="txtSearch"
|
id="txtSearch"
|
||||||
type="search"
|
type="search"
|
||||||
tabindex="1"
|
tabindex="1"
|
||||||
:aria-label="$t('commons.search')"
|
:aria-label="$t('message.search')"
|
||||||
:title="$t('commons.search')"
|
:title="$t('message.search')"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
class="input is-rounded is-search"
|
class="input is-rounded is-search"
|
||||||
:class="{ 'has-no-background': hasNoBackground }">
|
:class="{ 'has-no-background': hasNoBackground }">
|
||||||
<span class="icon is-small is-right">
|
<span class="icon is-small is-right">
|
||||||
<button type="button" v-if="keyword != ''" id="btnClearSearch" tabindex="1" :title="$t('commons.clear_search')" class="clear-selection delete" @click="clearSearch"></button>
|
<button type="button" v-if="keyword != ''" id="btnClearSearch" tabindex="1" :title="$t('message.clear_search')" class="clear-selection delete" @click="clearSearch"></button>
|
||||||
<FontAwesomeIcon v-else :icon="['fas', 'search']" />
|
<FontAwesomeIcon v-else :icon="['fas', 'search']" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'commons.generating_otp'
|
default: 'message.generating_otp'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a :id="'lnkSignWith' + props.provider" class="button is-link" :href="'socialite/redirect/' + props.provider">
|
<a :id="'lnkSignWith' + props.provider" class="button is-link" :href="'socialite/redirect/' + props.provider">
|
||||||
{{ $t('auth.sso_providers.' + props.provider) }}
|
{{ $t('message.auth.sso_providers.' + props.provider) }}
|
||||||
<FontAwesomeIcon class="ml-2" :icon="[icons[props.provider].collection, icons[props.provider].icon]" />
|
<FontAwesomeIcon class="ml-2" :icon="[icons[props.provider].collection, icons[props.provider].icon]" />
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
@ -9,19 +9,19 @@
|
|||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column has-nowrap px-0">
|
<div class="column has-nowrap px-0">
|
||||||
<!-- selected label -->
|
<!-- selected label -->
|
||||||
<span class="has-text-grey mr-1">{{ $t('commons.x_selected', { count: selectedCount }) }}</span>
|
<span class="has-text-grey mr-1">{{ $t('message.x_selected', { count: selectedCount }) }}</span>
|
||||||
<!-- deselect all -->
|
<!-- deselect all -->
|
||||||
<button type="button" id="btnUnselectAll" @click="$emit('clear-selected')" class="clear-selection delete mr-4" :style="{visibility: selectedCount > 0 ? 'visible' : 'hidden'}" :title="$t('commons.clear_selection')"></button>
|
<button type="button" id="btnUnselectAll" @click="$emit('clear-selected')" class="clear-selection delete mr-4" :style="{visibility: selectedCount > 0 ? 'visible' : 'hidden'}" :title="$t('message.clear_selection')"></button>
|
||||||
<!-- select all button -->
|
<!-- select all button -->
|
||||||
<button type="button" id="btnSelectAll" @click="$emit('select-all')" class="button mr-5 has-line-height p-1 is-ghost has-text-grey" :title="$t('commons.select_all')">
|
<button type="button" id="btnSelectAll" @click="$emit('select-all')" class="button mr-5 has-line-height p-1 is-ghost has-text-grey" :title="$t('message.select_all')">
|
||||||
<span>{{ $t('commons.check_all') }}</span>
|
<span>{{ $t('message.check_all') }}</span>
|
||||||
<FontAwesomeIcon class="ml-1" :icon="['fas', 'check-square']" />
|
<FontAwesomeIcon class="ml-1" :icon="['fas', 'check-square']" />
|
||||||
</button>
|
</button>
|
||||||
<!-- sort asc/desc buttons -->
|
<!-- sort asc/desc buttons -->
|
||||||
<button type="button" id="btnSortAscending" @click="$emit('sort-asc')" class="button has-line-height p-1 is-ghost has-text-grey" :title="$t('commons.sort_ascending')">
|
<button type="button" id="btnSortAscending" @click="$emit('sort-asc')" class="button has-line-height p-1 is-ghost has-text-grey" :title="$t('message.sort_ascending')">
|
||||||
<FontAwesomeIcon :icon="['fas', 'sort-alpha-down']" />
|
<FontAwesomeIcon :icon="['fas', 'sort-alpha-down']" />
|
||||||
</button>
|
</button>
|
||||||
<button type="button" id="btnSortDescending" @click="$emit('sort-desc')" class="button has-line-height p-1 is-ghost has-text-grey" :title="$t('commons.sort_descending')">
|
<button type="button" id="btnSortDescending" @click="$emit('sort-desc')" class="button has-line-height p-1 is-ghost has-text-grey" :title="$t('message.sort_descending')">
|
||||||
<FontAwesomeIcon :icon="['fas', 'sort-alpha-up']" />
|
<FontAwesomeIcon :icon="['fas', 'sort-alpha-up']" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,18 +28,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="columns is-mobile is-vcentered">
|
<div class="columns is-mobile is-vcentered">
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<button type="button" :class="isScanning ? 'is-loading' : ''" class="button is-link is-rounded is-small" @click="getLatestRelease">{{ $t('admin.check_now') }}</button>
|
<button type="button" :class="isScanning ? 'is-loading' : ''" class="button is-link is-rounded is-small" @click="getLatestRelease">{{ $t('message.admin.check_now') }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<UseColorMode v-slot="{ mode }">
|
<UseColorMode v-slot="{ mode }">
|
||||||
<span v-if="appSettings.latestRelease" class="mt-2" :class="mode == 'dark' ? 'has-text-warning' : 'has-text-warning-dark'">
|
<span v-if="appSettings.latestRelease" class="mt-2" :class="mode == 'dark' ? 'has-text-warning' : 'has-text-warning-dark'">
|
||||||
<span class="release-flag"></span>{{ $t('admin.x_is_available', { version: appSettings.latestRelease }) }} <a class="is-size-7" href="https://github.com/Bubka/2FAuth/releases">{{ $t('admin.view_on_github') }}</a>
|
<span class="release-flag"></span>{{ $t('message.admin.x_is_available', { version: appSettings.latestRelease }) }} <a class="is-size-7" href="https://github.com/Bubka/2FAuth/releases">{{ $t('message.admin.view_on_github') }}</a>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="isUpToDate" class="has-text-grey">
|
<span v-if="isUpToDate" class="has-text-grey">
|
||||||
<FontAwesomeIcon :icon="['fas', 'check']" class="mr-1 has-text-success" /> {{ $t('commons.you_are_up_to_date') }}
|
<FontAwesomeIcon :icon="['fas', 'check']" class="mr-1 has-text-success" /> {{ $t('message.you_are_up_to_date') }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="isUpToDate === null" class="has-text-grey">
|
<span v-else-if="isUpToDate === null" class="has-text-grey">
|
||||||
<FontAwesomeIcon :icon="['fas', 'times']" class="mr-1 has-text-danger" />{{ $t('errors.check_failed_try_later') }}
|
<FontAwesomeIcon :icon="['fas', 'times']" class="mr-1 has-text-danger" />{{ $t('error.check_failed_try_later') }}
|
||||||
</span>
|
</span>
|
||||||
</UseColorMode>
|
</UseColorMode>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import appSettingService from '@/services/appSettingService'
|
import appSettingService from '@/services/appSettingService'
|
||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a setting on the backend
|
* Saves a setting on the backend
|
||||||
@ -8,13 +9,15 @@ import { useNotifyStore } from '@/stores/notify'
|
|||||||
*/
|
*/
|
||||||
export async function useAppSettingsUpdater(setting, value, returnValidationError = false) {
|
export async function useAppSettingsUpdater(setting, value, returnValidationError = false) {
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
let data = null
|
let data = null
|
||||||
let error = null
|
let error = null
|
||||||
|
|
||||||
await appSettingService.update(setting, value, { returnError: true })
|
await appSettingService.update(setting, value, { returnError: true })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
data = value
|
data = value
|
||||||
useNotifyStore().success({ type: 'is-success', text: trans('settings.forms.setting_saved') })
|
useNotifyStore().success({ type: 'is-success', text: t('message.settings.forms.setting_saved') })
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if( returnValidationError && err.response.status === 422 ) {
|
if( returnValidationError && err.response.status === 422 ) {
|
||||||
|
6
resources/js/composables/useI18n.ts
Normal file
6
resources/js/composables/useI18n.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { I18nLocales, I18nSchema } from '../i18n';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return useI18n<[I18nSchema], I18nLocales>();
|
||||||
|
}
|
15
resources/js/i18n.ts
Normal file
15
resources/js/i18n.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { createI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import type schema from '../lang/en.json'
|
||||||
|
import messages from '@intlify/unplugin-vue-i18n/messages'
|
||||||
|
|
||||||
|
export type I18nSchema = typeof schema
|
||||||
|
export type I18nLocales = 'en' | 'fr'
|
||||||
|
|
||||||
|
export default createI18n<[I18nSchema], I18nLocales>({
|
||||||
|
legacy: false,
|
||||||
|
locale: document.documentElement.lang,
|
||||||
|
fallbackLocale: 'en',
|
||||||
|
globalInjection: true,
|
||||||
|
messages: messages as any,
|
||||||
|
})
|
@ -1,17 +1,17 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
const tabs = ref([
|
const tabs = ref([
|
||||||
{
|
{
|
||||||
'name' : 'admin.app_setup',
|
'name' : 'message.admin.app_setup',
|
||||||
'view' : 'admin.appSetup',
|
'view' : 'admin.appSetup',
|
||||||
'id' : 'lnkTabApp'
|
'id' : 'lnkTabApp'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name' : 'admin.auth',
|
'name' : 'message.admin.auth',
|
||||||
'view' : 'admin.auth',
|
'view' : 'admin.auth',
|
||||||
'id' : 'lnkTabAuth'
|
'id' : 'lnkTabAuth'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name' : 'admin.users',
|
'name' : 'message.admin.users',
|
||||||
'view' : 'admin.users',
|
'view' : 'admin.users',
|
||||||
'id' : 'lnkTabUsers'
|
'id' : 'lnkTabUsers'
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { UseColorMode } from '@vueuse/components'
|
import { UseColorMode } from '@vueuse/components'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const appSettings = useAppSettingsStore()
|
const appSettings = useAppSettingsStore()
|
||||||
const user = useUserStore()
|
const user = useUserStore()
|
||||||
const $2fauth = inject('2fauth')
|
const $2fauth = inject('2fauth')
|
||||||
@ -17,7 +19,7 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
if(confirm(trans('auth.confirm.logout'))) {
|
if(confirm(t('message.auth.confirm.logout'))) {
|
||||||
user.logout()
|
user.logout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,11 +38,11 @@
|
|||||||
<!-- sub-links -->
|
<!-- sub-links -->
|
||||||
<div v-if="internalFooterType == 'doneButton'" class="content has-text-centered">
|
<div v-if="internalFooterType == 'doneButton'" class="content has-text-centered">
|
||||||
<!-- done link -->
|
<!-- done link -->
|
||||||
<button type="button" id="lnkExitEdit" class="button is-ghost is-like-text" @click.stop="$emit('doneButtonClicked', true)">{{ $t('commons.done') }}</button>
|
<button type="button" id="lnkExitEdit" class="button is-ghost is-like-text" @click.stop="$emit('doneButtonClicked', true)">{{ $t('message.done') }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="internalFooterType == 'modal'" class="content has-text-centered">
|
<div v-else-if="internalFooterType == 'modal'" class="content has-text-centered">
|
||||||
<!-- back to home link -->
|
<!-- back to home link -->
|
||||||
<router-link v-if="$route.name != 'accounts'" id="lnkBackToHome" :to="{ name: 'accounts' }" class="has-text-grey">{{ $t('commons.back_to_home') }}</router-link>
|
<router-link v-if="$route.name != 'accounts'" id="lnkBackToHome" :to="{ name: 'accounts' }" class="has-text-grey">{{ $t('message.back_to_home') }}</router-link>
|
||||||
<span v-else> </span>
|
<span v-else> </span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="content has-text-centered">
|
<div v-else class="content has-text-centered">
|
||||||
@ -48,7 +50,7 @@
|
|||||||
<!-- about link -->
|
<!-- about link -->
|
||||||
<router-link v-if="$route.name != 'about'" id="lnkAbout" :to="{ name: 'about' }" class="has-text-grey">
|
<router-link v-if="$route.name != 'about'" id="lnkAbout" :to="{ name: 'about' }" class="has-text-grey">
|
||||||
<span v-if="user.isAuthenticated && $route.meta.watchedByKicker" class="has-text-weight-bold">2FAuth – v{{ $2fauth.version }}</span>
|
<span v-if="user.isAuthenticated && $route.meta.watchedByKicker" class="has-text-weight-bold">2FAuth – v{{ $2fauth.version }}</span>
|
||||||
<span v-else class="">{{ $t('commons.about') }}</span>
|
<span v-else class="">{{ $t('message.about') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<span v-else> </span>
|
<span v-else> </span>
|
||||||
</div>
|
</div>
|
||||||
@ -58,21 +60,21 @@
|
|||||||
<!-- settings link -->
|
<!-- settings link -->
|
||||||
<li class="column">
|
<li class="column">
|
||||||
<router-link id="lnkSettings" :to="{ name: 'settings.options' }">
|
<router-link id="lnkSettings" :to="{ name: 'settings.options' }">
|
||||||
{{ $t('settings.settings') }}
|
{{ $t('message.settings.settings') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<!-- admin link -->
|
<!-- admin link -->
|
||||||
<li v-if="user.isAdmin" class="column">
|
<li v-if="user.isAdmin" class="column">
|
||||||
<router-link id="lnkAdmin" :to="{ name: 'admin.appSetup' }" >
|
<router-link id="lnkAdmin" :to="{ name: 'admin.appSetup' }" >
|
||||||
<span v-if="appSettings.latestRelease && appSettings.checkForUpdate" class="release-flag"></span>
|
<span v-if="appSettings.latestRelease && appSettings.checkForUpdate" class="release-flag"></span>
|
||||||
{{ $t('admin.admin_panel') }}
|
{{ $t('message.admin.admin_panel') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<!-- sign-out button -->
|
<!-- sign-out button -->
|
||||||
<li v-if="!$2fauth.config.proxyAuth || ($2fauth.config.proxyAuth && $2fauth.config.proxyLogoutUrl)" class="column">
|
<li v-if="!$2fauth.config.proxyAuth || ($2fauth.config.proxyAuth && $2fauth.config.proxyLogoutUrl)" class="column">
|
||||||
<UseColorMode v-slot="{ mode }">
|
<UseColorMode v-slot="{ mode }">
|
||||||
<button type="button" id="lnkSignOut" class="button is-text is-like-text" :class="mode == 'dark' ? 'has-text-grey-lighter' : 'has-text-grey-darker'" @click="logout">
|
<button type="button" id="lnkSignOut" class="button is-text is-like-text" :class="mode == 'dark' ? 'has-text-grey-lighter' : 'has-text-grey-darker'" @click="logout">
|
||||||
{{ $t('auth.sign_out') }}
|
{{ $t('message.auth.sign_out') }}
|
||||||
</button>
|
</button>
|
||||||
</UseColorMode>
|
</UseColorMode>
|
||||||
</li>
|
</li>
|
||||||
@ -90,17 +92,17 @@
|
|||||||
<div v-else>
|
<div v-else>
|
||||||
<!-- settings link -->
|
<!-- settings link -->
|
||||||
<router-link id="lnkSettings" :to="{ name: 'settings.options' }" class="has-text-grey">
|
<router-link id="lnkSettings" :to="{ name: 'settings.options' }" class="has-text-grey">
|
||||||
{{ $t('settings.settings') }}
|
{{ $t('message.settings.settings') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
<!-- admin link -->
|
<!-- admin link -->
|
||||||
<span v-if="user.isAdmin"> -
|
<span v-if="user.isAdmin"> -
|
||||||
<router-link id="lnkAdmin" :to="{ name: 'admin.appSetup' }" class="has-text-grey">
|
<router-link id="lnkAdmin" :to="{ name: 'admin.appSetup' }" class="has-text-grey">
|
||||||
{{ $t('admin.admin') }}<span v-if="appSettings.latestRelease && appSettings.checkForUpdate" class="release-flag"></span>
|
{{ $t('message.admin.admin') }}<span v-if="appSettings.latestRelease && appSettings.checkForUpdate" class="release-flag"></span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</span>
|
</span>
|
||||||
<!-- sign-out button -->
|
<!-- sign-out button -->
|
||||||
<span v-if="!$2fauth.config.proxyAuth || ($2fauth.config.proxyAuth && $2fauth.config.proxyLogoutUrl)">
|
<span v-if="!$2fauth.config.proxyAuth || ($2fauth.config.proxyAuth && $2fauth.config.proxyLogoutUrl)">
|
||||||
- <button type="button" id="lnkSignOut" class="button is-text is-like-text has-text-grey" @click="logout">{{ $t('auth.sign_out') }}</button>
|
- <button type="button" id="lnkSignOut" class="button is-text is-like-text has-text-grey" @click="logout">{{ $t('message.auth.sign_out') }}</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
const tabs = ref([
|
const tabs = ref([
|
||||||
{
|
{
|
||||||
'name' : 'settings.options',
|
'name' : 'message.settings.options',
|
||||||
'view' : 'settings.options',
|
'view' : 'settings.options',
|
||||||
'id' : 'lnkTabOptions'
|
'id' : 'lnkTabOptions'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name' : 'settings.account',
|
'name' : 'message.settings.account',
|
||||||
'view' : 'settings.account',
|
'view' : 'settings.account',
|
||||||
'id' : 'lnkTabAccount'
|
'id' : 'lnkTabAccount'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name' : 'settings.oauth',
|
'name' : 'message.settings.oauth',
|
||||||
'view' : 'settings.oauth.tokens',
|
'view' : 'settings.oauth.tokens',
|
||||||
'id' : 'lnkTabOAuth'
|
'id' : 'lnkTabOAuth'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name' : 'settings.webauthn',
|
'name' : 'message.settings.webauthn',
|
||||||
'view' : 'settings.webauthn.devices',
|
'view' : 'settings.webauthn.devices',
|
||||||
'id' : 'lnkTabWebauthn'
|
'id' : 'lnkTabWebauthn'
|
||||||
},
|
},
|
||||||
|
5
resources/js/router/index.js
vendored
5
resources/js/router/index.js
vendored
@ -77,9 +77,4 @@ router.beforeEach((to, from, next) => {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
router.afterEach((to, from) => {
|
|
||||||
to.meta.title = trans('titles.' + to.name)
|
|
||||||
document.title = to.meta.title
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
@ -38,7 +38,7 @@ export function identifyAuthenticationError(error, options) {
|
|||||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 16)
|
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 16)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.aborted_by_user',
|
phrase: 'error.aborted_by_user',
|
||||||
type: 'is-warning'
|
type: 'is-warning'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ export function identifyAuthenticationError(error, options) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.not_allowed_operation',
|
phrase: 'error.not_allowed_operation',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ export function identifyAuthenticationError(error, options) {
|
|||||||
// https://www.w3.org/TR/webauthn-2/#sctn-discover-from-external-source (Step 5)
|
// https://www.w3.org/TR/webauthn-2/#sctn-discover-from-external-source (Step 5)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.2fauth_has_not_a_valid_domain',
|
phrase: 'error.2fauth_has_not_a_valid_domain',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ export function identifyAuthenticationError(error, options) {
|
|||||||
// // https://www.w3.org/TR/webauthn-2/#sctn-discover-from-external-source (Step 6)
|
// // https://www.w3.org/TR/webauthn-2/#sctn-discover-from-external-source (Step 6)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.security_error_check_rpid',
|
phrase: 'error.security_error_check_rpid',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,13 +83,13 @@ export function identifyAuthenticationError(error, options) {
|
|||||||
// https://www.w3.org/TR/webauthn-2/#sctn-op-get-assertion (Step 12)
|
// https://www.w3.org/TR/webauthn-2/#sctn-op-get-assertion (Step 12)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.unknown_error',
|
phrase: 'error.unknown_error',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.unknown_error',
|
phrase: 'error.unknown_error',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -38,7 +38,7 @@ export function identifyRegistrationError(error, options) {
|
|||||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 16)
|
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 16)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.aborted_by_user',
|
phrase: 'error.aborted_by_user',
|
||||||
type: 'is-warning'
|
type: 'is-warning'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@ export function identifyRegistrationError(error, options) {
|
|||||||
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 4)
|
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 4)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.authenticator_missing_discoverable_credential_support',
|
phrase: 'error.authenticator_missing_discoverable_credential_support',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ export function identifyRegistrationError(error, options) {
|
|||||||
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 5)
|
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 5)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.authenticator_missing_user_verification_support',
|
phrase: 'error.authenticator_missing_user_verification_support',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ export function identifyRegistrationError(error, options) {
|
|||||||
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 3)
|
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 3)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.security_device_already_registered',
|
phrase: 'error.security_device_already_registered',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ export function identifyRegistrationError(error, options) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.not_allowed_operation',
|
phrase: 'error.not_allowed_operation',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ export function identifyRegistrationError(error, options) {
|
|||||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 10)
|
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 10)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.no_entry_was_of_type_public_key',
|
phrase: 'error.no_entry_was_of_type_public_key',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,7 +104,7 @@ export function identifyRegistrationError(error, options) {
|
|||||||
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 2)
|
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 2)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.no_authenticator_support_specified_algorithms',
|
phrase: 'error.no_authenticator_support_specified_algorithms',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ export function identifyRegistrationError(error, options) {
|
|||||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 7)
|
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 7)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.2fauth_has_not_a_valid_domain',
|
phrase: 'error.2fauth_has_not_a_valid_domain',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ export function identifyRegistrationError(error, options) {
|
|||||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 8)
|
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 8)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.security_error_check_rpid',
|
phrase: 'error.security_error_check_rpid',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,7 +137,7 @@ export function identifyRegistrationError(error, options) {
|
|||||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 5)
|
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 5)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.user_id_not_between_1_64',
|
phrase: 'error.user_id_not_between_1_64',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,13 +148,13 @@ export function identifyRegistrationError(error, options) {
|
|||||||
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 8)
|
// https://www.w3.org/TR/webauthn-2/#sctn-op-make-cred (Step 8)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.unknown_error',
|
phrase: 'error.unknown_error',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phrase: 'errors.unknown_error',
|
phrase: 'error.unknown_error',
|
||||||
type: 'is-danger'
|
type: 'is-danger'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,13 +41,13 @@ class WebauthnService {
|
|||||||
|
|
||||||
// Check https context
|
// Check https context
|
||||||
if (!window.isSecureContext) {
|
if (!window.isSecureContext) {
|
||||||
err.message = 'errors.https_required'
|
err.message = 'error.https_required'
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check browser support
|
// Check browser support
|
||||||
if (! WebauthnService.supportsWebAuthn) {
|
if (! WebauthnService.supportsWebAuthn) {
|
||||||
err.message = 'errors.browser_does_not_support_webauthn'
|
err.message = 'error.browser_does_not_support_webauthn'
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,13 +80,13 @@ class WebauthnService {
|
|||||||
|
|
||||||
// Check https context
|
// Check https context
|
||||||
if (!window.isSecureContext) {
|
if (!window.isSecureContext) {
|
||||||
err.message = 'errors.https_required'
|
err.message = 'error.https_required'
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check browser support
|
// Check browser support
|
||||||
if (! WebauthnService.supportsWebAuthn) {
|
if (! WebauthnService.supportsWebAuthn) {
|
||||||
err.message = 'errors.browser_does_not_support_webauthn'
|
err.message = 'error.browser_does_not_support_webauthn'
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
resources/js/stores/appSettings.js
vendored
3
resources/js/stores/appSettings.js
vendored
@ -30,7 +30,8 @@ export const useAppSettingsStore = defineStore({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
useNotifyStore().alert({ text: trans('errors.failed_to_retrieve_app_settings') })
|
// TODO : move the t() call from the store
|
||||||
|
useNotifyStore().alert({ text: t('error.failed_to_retrieve_app_settings') })
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
10
resources/js/stores/groups.js
vendored
10
resources/js/stores/groups.js
vendored
@ -17,7 +17,7 @@ export const useGroups = defineStore({
|
|||||||
current(state) {
|
current(state) {
|
||||||
const group = state.items.find(item => item.id === parseInt(useUserStore().preferences.activeGroup))
|
const group = state.items.find(item => item.id === parseInt(useUserStore().preferences.activeGroup))
|
||||||
|
|
||||||
return group ? group.name : trans('commons.all')
|
return group ? group.name : t('message.all')
|
||||||
},
|
},
|
||||||
|
|
||||||
withoutTheAllGroup(state) {
|
withoutTheAllGroup(state) {
|
||||||
@ -47,11 +47,11 @@ export const useGroups = defineStore({
|
|||||||
|
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this.items[index] = group
|
this.items[index] = group
|
||||||
useNotifyStore().success({ text: trans('groups.group_name_saved') })
|
useNotifyStore().success({ text: t('message.groups.group_name_saved') })
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.items.push(group)
|
this.items.push(group)
|
||||||
useNotifyStore().success({ text: trans('groups.group_successfully_created') })
|
useNotifyStore().success({ text: t('message.groups.group_successfully_created') })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -78,10 +78,10 @@ export const useGroups = defineStore({
|
|||||||
async delete(id) {
|
async delete(id) {
|
||||||
const user = useUserStore()
|
const user = useUserStore()
|
||||||
|
|
||||||
if (confirm(trans('groups.confirm.delete'))) {
|
if (confirm(t('message.groups.confirm.delete'))) {
|
||||||
await groupService.delete(id).then(response => {
|
await groupService.delete(id).then(response => {
|
||||||
this.items = this.items.filter(a => a.id !== id)
|
this.items = this.items.filter(a => a.id !== id)
|
||||||
useNotifyStore().success({ text: trans('groups.group_successfully_deleted') })
|
useNotifyStore().success({ text: t('message.groups.group_successfully_deleted') })
|
||||||
|
|
||||||
// Reset group filter to 'All' (groupId=0) since the backend has already made
|
// Reset group filter to 'All' (groupId=0) since the backend has already made
|
||||||
// the change automatically. This prevents a new request.
|
// the change automatically. This prevents a new request.
|
||||||
|
11
resources/js/stores/notify.js
vendored
11
resources/js/stores/notify.js
vendored
@ -19,18 +19,21 @@ export const useNotifyStore = defineStore({
|
|||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
parseError(err) {
|
parseError(err) {
|
||||||
|
// TODO : use the new Notify component
|
||||||
|
// const { t } = useI18n()
|
||||||
|
|
||||||
this.$reset
|
this.$reset
|
||||||
this.err = err
|
this.err = err
|
||||||
|
|
||||||
// Hnalde axios response error
|
// Hnalde axios response error
|
||||||
if (err.response) {
|
if (err.response) {
|
||||||
if (err.response.status === 407) {
|
if (err.response.status === 407) {
|
||||||
this.message = trans('errors.auth_proxy_failed'),
|
this.message = t('error.auth_proxy_failed'),
|
||||||
this.originalMessage = trans('errors.auth_proxy_failed_legend')
|
this.originalMessage = t('error.auth_proxy_failed_legend')
|
||||||
}
|
}
|
||||||
else if (err.response.status === 403) {
|
else if (err.response.status === 403) {
|
||||||
this.message = trans('errors.unauthorized'),
|
this.message = t('error.unauthorized'),
|
||||||
this.originalMessage = trans('errors.unauthorized_legend')
|
this.originalMessage = t('error.unauthorized_legend')
|
||||||
}
|
}
|
||||||
else if(err.response.data) {
|
else if(err.response.data) {
|
||||||
this.message = err.response.data.message,
|
this.message = err.response.data.message,
|
||||||
|
4
resources/js/stores/twofaccounts.js
vendored
4
resources/js/stores/twofaccounts.js
vendored
@ -146,7 +146,7 @@ export const useTwofaccounts = defineStore({
|
|||||||
* Deletes selected accounts
|
* Deletes selected accounts
|
||||||
*/
|
*/
|
||||||
async deleteSelected() {
|
async deleteSelected() {
|
||||||
if(confirm(trans('twofaccounts.confirm.delete')) && this.selectedIds.length > 0) {
|
if(confirm(t('message.twofaccounts.confirm.delete')) && this.selectedIds.length > 0) {
|
||||||
await twofaccountService.batchDelete(this.selectedIds.join())
|
await twofaccountService.batchDelete(this.selectedIds.join())
|
||||||
.then(response => {
|
.then(response => {
|
||||||
let remainingItems = this.items
|
let remainingItems = this.items
|
||||||
@ -155,7 +155,7 @@ export const useTwofaccounts = defineStore({
|
|||||||
})
|
})
|
||||||
this.items = remainingItems
|
this.items = remainingItems
|
||||||
this.selectNone()
|
this.selectNone()
|
||||||
useNotifyStore().success({ text: trans('twofaccounts.accounts_deleted') })
|
useNotifyStore().success({ text: t('message.twofaccounts.accounts_deleted') })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
24
resources/js/stores/user.js
vendored
24
resources/js/stores/user.js
vendored
@ -76,7 +76,7 @@ export const useUserStore = defineStore({
|
|||||||
authService.logout({ returnError: true }).then(() => {
|
authService.logout({ returnError: true }).then(() => {
|
||||||
if (kicked) {
|
if (kicked) {
|
||||||
notify.clear()
|
notify.clear()
|
||||||
notify.warn({ text: trans('auth.autolock_triggered_punchline'), duration:-1 })
|
notify.warn({ text: t('message.auth.autolock_triggered_punchline'), duration:-1 })
|
||||||
}
|
}
|
||||||
this.tossOut()
|
this.tossOut()
|
||||||
})
|
})
|
||||||
@ -118,24 +118,16 @@ export const useUserStore = defineStore({
|
|||||||
*/
|
*/
|
||||||
applyLanguage() {
|
applyLanguage() {
|
||||||
const { isSupported, language } = useNavigatorLanguage()
|
const { isSupported, language } = useNavigatorLanguage()
|
||||||
let lang = 'en'
|
let lang = this.$i18n.fallbackLocale
|
||||||
|
|
||||||
if (isSupported) {
|
if (isSupported) {
|
||||||
if (this.preferences.lang == 'browser') {
|
// The language tag pushed by the browser may be composed of
|
||||||
if (this.$2fauth.langs.includes(language.value)) {
|
// multiple subtags (ex: fr-FR) so we keep only the
|
||||||
lang = language.value
|
// "language subtag" (ex: fr)
|
||||||
}
|
lang = this.preferences.lang == 'browser' ? language.value.slice(0, 2) : this.preferences.lang
|
||||||
// If the language tag pushed by the browser is composed of
|
|
||||||
// multiple subtags (ex: fr-FR) we need to retry but only with
|
|
||||||
// the "language subtag" (ex: fr)
|
|
||||||
else if (this.$2fauth.langs.includes(language.value.slice(0, 2))) {
|
|
||||||
lang = language.value.slice(0, 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else lang = this.preferences.lang
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadLanguageAsync(lang)
|
this.$i18n.global.locale = lang
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -168,7 +160,7 @@ export const useUserStore = defineStore({
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
notify.alert({ text: trans('errors.data_cannot_be_refreshed_from_server') })
|
notify.alert({ text: t('error.data_cannot_be_refreshed_from_server') })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,27 +5,29 @@
|
|||||||
const $2fauth = inject('2fauth')
|
const $2fauth = inject('2fauth')
|
||||||
const user = useUserStore()
|
const user = useUserStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const returnTo = router.options.history.state.back
|
|
||||||
|
const returnTo = router.options.routes.find((route) => route.path == router.options.history.state.back).name;
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ResponsiveWidthWrapper>
|
<ResponsiveWidthWrapper>
|
||||||
<UseColorMode v-slot="{ mode }">
|
<UseColorMode v-slot="{ mode }">
|
||||||
<h1 class="title has-text-grey-dark">{{ $t('commons.about') }}</h1>
|
<h1 class="title has-text-grey-dark">{{ $t('message.about') }}</h1>
|
||||||
<p class="block">
|
<p class="block">
|
||||||
<span :class="mode == 'dark' ? 'has-text-white':'has-text-black'">
|
<span :class="mode == 'dark' ? 'has-text-white':'has-text-black'">
|
||||||
<span class="is-size-5">2FAuth</span>
|
<span class="is-size-5">2FAuth</span>
|
||||||
<span v-if="user.isAuthenticated"> v{{ $2fauth.version }}</span>
|
<span v-if="user.isAuthenticated"> v{{ $2fauth.version }}</span>
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
{{ $t('commons.2fauth_teaser')}}
|
{{ $t('message.2fauth_teaser')}}
|
||||||
</p>
|
</p>
|
||||||
<img class="about-logo" src="logo.svg" alt="2FAuth logo" />
|
<img class="about-logo" src="logo.svg" alt="2FAuth logo" />
|
||||||
<p class="block">
|
<p class="block">
|
||||||
©Bubka <a class="is-size-7" href="https://github.com/Bubka/2FAuth/blob/master/LICENSE">AGPL-3.0 license</a>
|
©Bubka <a class="is-size-7" href="https://github.com/Bubka/2FAuth/blob/master/LICENSE">AGPL-3.0 license</a>
|
||||||
</p>
|
</p>
|
||||||
<h2 class="title is-5 has-text-grey-light">
|
<h2 class="title is-5 has-text-grey-light">
|
||||||
{{ $t('commons.resources') }}
|
{{ $t('message.resources') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<a class="button" :class="{'is-dark' : mode == 'dark'}" href="https://github.com/Bubka/2FAuth" target="_blank">
|
<a class="button" :class="{'is-dark' : mode == 'dark'}" href="https://github.com/Bubka/2FAuth" target="_blank">
|
||||||
@ -54,12 +56,12 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="title is-5 has-text-grey-light">
|
<h2 class="title is-5 has-text-grey-light">
|
||||||
{{ $t('commons.credits') }}
|
{{ $t('message.credits') }}
|
||||||
</h2>
|
</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>{{ $t('commons.made_with') }} <a href="https://docs.2fauth.app/credits/">Laravel, Bulma CSS, Vue.js and more</a></li>
|
<li>{{ $t('message.made_with') }} <a href="https://docs.2fauth.app/credits/">Laravel, Bulma CSS, Vue.js and more</a></li>
|
||||||
<li>{{ $t('commons.ui_icons_by') }} <a href="https://fontawesome.com/">Font Awesome</a> <a class="is-size-7" href="https://fontawesome.com/license/free">(CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)</a></li>
|
<li>{{ $t('message.ui_icons_by') }} <a href="https://fontawesome.com/">Font Awesome</a> <a class="is-size-7" href="https://fontawesome.com/license/free">(CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)</a></li>
|
||||||
<li>{{ $t('commons.logos_by') }} <a href="https://2fa.directory/">2FA Directory</a> <a class="is-size-7" href="https://github.com/2factorauth/twofactorauth/blob/master/LICENSE.md">(MIT License)</a></li>
|
<li>{{ $t('message.logos_by') }} <a href="https://2fa.directory/">2FA Directory</a> <a class="is-size-7" href="https://github.com/2factorauth/twofactorauth/blob/master/LICENSE.md">(MIT License)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<!-- footer -->
|
<!-- footer -->
|
||||||
<VueFooter :showButtons="true">
|
<VueFooter :showButtons="true">
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const errorHandler = useNotifyStore()
|
const errorHandler = useNotifyStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@ -23,7 +25,7 @@
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (route.query.err) {
|
if (route.query.err) {
|
||||||
errorHandler.message = trans('errors.' + route.query.err)
|
errorHandler.message = t('error.' + route.query.err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -43,11 +45,11 @@
|
|||||||
<modal v-model="showModal" :closable="props.closable">
|
<modal v-model="showModal" :closable="props.closable">
|
||||||
<div class="error-message" v-if="$route.name == '404' || $route.name == 'notFound'">
|
<div class="error-message" v-if="$route.name == '404' || $route.name == 'notFound'">
|
||||||
<p class="error-404"></p>
|
<p class="error-404"></p>
|
||||||
<p>{{ $t('errors.resource_not_found') }}</p>
|
<p>{{ $t('error.resource_not_found') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="error-message" >
|
<div v-else class="error-message" >
|
||||||
<p class="error-generic"></p>
|
<p class="error-generic"></p>
|
||||||
<p>{{ $t('errors.error_occured') }} </p>
|
<p>{{ $t('error.error_occured') }} </p>
|
||||||
<p v-if="errorHandler.message" class="has-text-grey-lighter">{{ errorHandler.message }}</p>
|
<p v-if="errorHandler.message" class="has-text-grey-lighter">{{ errorHandler.message }}</p>
|
||||||
<p v-if="errorHandler.originalMessage" class="has-text-grey-lighter">{{ errorHandler.originalMessage }}</p>
|
<p v-if="errorHandler.originalMessage" class="has-text-grey-lighter">{{ errorHandler.originalMessage }}</p>
|
||||||
<p v-if="showDebug && errorHandler.debug" class="is-size-7 is-family-code"><br>{{ errorHandler.debug }}</p>
|
<p v-if="showDebug && errorHandler.debug" class="is-size-7 is-family-code"><br>{{ errorHandler.debug }}</p>
|
||||||
|
@ -64,8 +64,8 @@
|
|||||||
<div class="columns quick-uploader">
|
<div class="columns quick-uploader">
|
||||||
<!-- trailer phrase that invite to add an account -->
|
<!-- trailer phrase that invite to add an account -->
|
||||||
<div class="column is-full quick-uploader-header" :class="{ 'is-invisible' : twofaccounts.count !== 0 }">
|
<div class="column is-full quick-uploader-header" :class="{ 'is-invisible' : twofaccounts.count !== 0 }">
|
||||||
{{ $t('twofaccounts.no_account_here') }}<br>
|
{{ $t('message.twofaccounts.no_account_here') }}<br>
|
||||||
{{ $t('twofaccounts.add_first_account') }}
|
{{ $t('message.twofaccounts.add_first_account') }}
|
||||||
</div>
|
</div>
|
||||||
<!-- Livescan button -->
|
<!-- Livescan button -->
|
||||||
<div class="column is-full quick-uploader-button" >
|
<div class="column is-full quick-uploader-button" >
|
||||||
@ -73,35 +73,35 @@
|
|||||||
<!-- upload a qr code (with basic file field and backend decoding) -->
|
<!-- upload a qr code (with basic file field and backend decoding) -->
|
||||||
<label role="button" tabindex="0" v-if="user.preferences.useBasicQrcodeReader" class="button is-link is-medium is-rounded is-main" ref="qrcodeInputLabel" @keyup.enter="qrcodeInputLabel.click()">
|
<label role="button" tabindex="0" v-if="user.preferences.useBasicQrcodeReader" class="button is-link is-medium is-rounded is-main" ref="qrcodeInputLabel" @keyup.enter="qrcodeInputLabel.click()">
|
||||||
<input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="submitQrCode" ref="qrcodeInput">
|
<input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="submitQrCode" ref="qrcodeInput">
|
||||||
{{ $t('twofaccounts.forms.upload_qrcode') }}
|
{{ $t('message.twofaccounts.forms.upload_qrcode') }}
|
||||||
</label>
|
</label>
|
||||||
<!-- scan button that launch camera stream -->
|
<!-- scan button that launch camera stream -->
|
||||||
<button v-else type="button" class="button is-link is-medium is-rounded is-main" @click="capture()">
|
<button v-else type="button" class="button is-link is-medium is-rounded is-main" @click="capture()">
|
||||||
{{ $t('twofaccounts.forms.scan_qrcode') }}
|
{{ $t('message.twofaccounts.forms.scan_qrcode') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<FormFieldError v-if="form.errors.hasAny('qrcode')" :error="form.errors.get('qrcode')" :field="'qrcode'" />
|
<FormFieldError v-if="form.errors.hasAny('qrcode')" :error="form.errors.get('qrcode')" :field="'qrcode'" />
|
||||||
</div>
|
</div>
|
||||||
<!-- alternative methods -->
|
<!-- alternative methods -->
|
||||||
<div class="column is-full">
|
<div class="column is-full">
|
||||||
<div class="block light-or-darker">{{ $t('twofaccounts.forms.alternative_methods') }}</div>
|
<div class="block light-or-darker">{{ $t('message.twofaccounts.forms.alternative_methods') }}</div>
|
||||||
<!-- upload a qr code -->
|
<!-- upload a qr code -->
|
||||||
<div class="block has-text-link" v-if="!user.preferences.useBasicQrcodeReader">
|
<div class="block has-text-link" v-if="!user.preferences.useBasicQrcodeReader">
|
||||||
<label role="button" tabindex="0" class="button is-link is-outlined is-rounded" ref="qrcodeInputLabel" @keyup.enter="qrcodeInputLabel.click()">
|
<label role="button" tabindex="0" class="button is-link is-outlined is-rounded" ref="qrcodeInputLabel" @keyup.enter="qrcodeInputLabel.click()">
|
||||||
<input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="submitQrCode" ref="qrcodeInput">
|
<input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="submitQrCode" ref="qrcodeInput">
|
||||||
{{ $t('twofaccounts.forms.upload_qrcode') }}
|
{{ $t('message.twofaccounts.forms.upload_qrcode') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<!-- link to advanced form -->
|
<!-- link to advanced form -->
|
||||||
<div class="block has-text-link">
|
<div class="block has-text-link">
|
||||||
<RouterLink class="button is-link is-outlined is-rounded" :to="{ name: 'createAccount' }" >
|
<RouterLink class="button is-link is-outlined is-rounded" :to="{ name: 'createAccount' }" >
|
||||||
{{ $t('twofaccounts.forms.use_advanced_form') }}
|
{{ $t('message.twofaccounts.forms.use_advanced_form') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<!-- link to import view -->
|
<!-- link to import view -->
|
||||||
<div class="block has-text-link">
|
<div class="block has-text-link">
|
||||||
<RouterLink id="btnImport" class="button is-link is-outlined is-rounded" :to="{ name: 'importAccounts' }" >
|
<RouterLink id="btnImport" class="button is-link is-outlined is-rounded" :to="{ name: 'importAccounts' }" >
|
||||||
{{ $t('twofaccounts.import.import') }}
|
{{ $t('message.twofaccounts.import.import') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,7 +7,9 @@
|
|||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import VersionChecker from '@/components/VersionChecker.vue'
|
import VersionChecker from '@/components/VersionChecker.vue'
|
||||||
import CopyButton from '@/components/CopyButton.vue'
|
import CopyButton from '@/components/CopyButton.vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const $2fauth = inject('2fauth')
|
const $2fauth = inject('2fauth')
|
||||||
const user = useUserStore()
|
const user = useUserStore()
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
@ -40,7 +42,7 @@
|
|||||||
isClearingCache.value = true;
|
isClearingCache.value = true;
|
||||||
|
|
||||||
systemService.clearCache().then(response => {
|
systemService.clearCache().then(response => {
|
||||||
useNotifyStore().success({ type: 'is-success', text: trans('admin.cache_cleared') })
|
useNotifyStore().success({ type: 'is-success', text: t('message.admin.cache_cleared') })
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isClearingCache.value = false;
|
isClearingCache.value = false;
|
||||||
@ -72,15 +74,21 @@
|
|||||||
<div class="options-tabs">
|
<div class="options-tabs">
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<form>
|
<form>
|
||||||
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('settings.general') }}</h4>
|
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('message.settings.general') }}</h4>
|
||||||
<!-- Check for update -->
|
<!-- Check for update -->
|
||||||
<FormCheckbox v-model="appSettings.checkForUpdate" @update:model-value="val => useAppSettingsUpdater('checkForUpdate', val)" fieldName="checkForUpdate" label="commons.check_for_update" help="commons.check_for_update_help" />
|
<FormCheckbox v-model="appSettings.checkForUpdate" @update:model-value="val => useAppSettingsUpdater('checkForUpdate', val)" fieldName="checkForUpdate" label="message.check_for_update" help="message.check_for_update_help" />
|
||||||
<VersionChecker />
|
<VersionChecker />
|
||||||
<!-- email config test -->
|
<!-- email config test -->
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="btnTestEmail" v-html="$t('admin.forms.test_email.label')" />
|
<label class="label" for="btnTestEmail" v-html="$t('message.admin.forms.test_email.label')" />
|
||||||
<p class="help" v-html="$t('admin.forms.test_email.help')" />
|
<p class="help" v-html="$t('message.admin.forms.test_email.help')" />
|
||||||
<p class="help" v-html="$t('admin.forms.test_email.email_will_be_send_to_x', { email: user.email })" />
|
<p class="help">
|
||||||
|
<i18n-t keypath="message.admin.forms.test_email.email_will_be_send_to_x" tag="span">
|
||||||
|
<template v-slot:email>
|
||||||
|
<span class="is-family-code has-text-info">{{ user.email }}</span>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="columns is-mobile is-vcentered">
|
<div class="columns is-mobile is-vcentered">
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
@ -88,43 +96,43 @@
|
|||||||
<span class="icon is-small">
|
<span class="icon is-small">
|
||||||
<FontAwesomeIcon :icon="['far', 'paper-plane']" />
|
<FontAwesomeIcon :icon="['far', 'paper-plane']" />
|
||||||
</span>
|
</span>
|
||||||
<span>{{ $t('commons.send') }}</span>
|
<span>{{ $t('message.send') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- healthcheck -->
|
<!-- healthcheck -->
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="lnkHealthCheck" v-html="$t('admin.forms.health_endpoint.label')" />
|
<label class="label" for="lnkHealthCheck" v-html="$t('message.admin.forms.health_endpoint.label')" />
|
||||||
<p class="help" v-html="$t('admin.forms.health_endpoint.help')" />
|
<p class="help" v-html="$t('message.admin.forms.health_endpoint.help')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="field mb-5">
|
<div class="field mb-5">
|
||||||
<a id="lnkHealthCheck" target="_blank" :href="healthEndPoint">{{ healthEndPointFullPath }}</a>
|
<a id="lnkHealthCheck" target="_blank" :href="healthEndPoint">{{ healthEndPointFullPath }}</a>
|
||||||
</div>
|
</div>
|
||||||
<h4 class="title is-4 pt-5 has-text-grey-light">{{ $t('admin.storage') }}</h4>
|
<h4 class="title is-4 pt-5 has-text-grey-light">{{ $t('message.admin.storage') }}</h4>
|
||||||
<!-- store icons in database -->
|
<!-- store icons in database -->
|
||||||
<FormCheckbox v-model="appSettings.storeIconsInDatabase" @update:model-value="val => useAppSettingsUpdater('storeIconsInDatabase', val)" fieldName="storeIconsInDatabase" label="admin.forms.store_icon_to_database.label" help="admin.forms.store_icon_to_database.help" />
|
<FormCheckbox v-model="appSettings.storeIconsInDatabase" @update:model-value="val => useAppSettingsUpdater('storeIconsInDatabase', val)" fieldName="storeIconsInDatabase" label="message.admin.forms.store_icon_to_database.label" help="message.admin.forms.store_icon_to_database.help" />
|
||||||
<h4 class="title is-4 pt-5 has-text-grey-light">{{ $t('settings.security') }}</h4>
|
<h4 class="title is-4 pt-5 has-text-grey-light">{{ $t('message.settings.security') }}</h4>
|
||||||
<!-- protect db -->
|
<!-- protect db -->
|
||||||
<FormCheckbox v-model="appSettings.useEncryption" @update:model-value="val => useAppSettingsUpdater('useEncryption', val)" fieldName="useEncryption" label="admin.forms.use_encryption.label" help="admin.forms.use_encryption.help" />
|
<FormCheckbox v-model="appSettings.useEncryption" @update:model-value="val => useAppSettingsUpdater('useEncryption', val)" fieldName="useEncryption" label="message.admin.forms.use_encryption.label" help="message.admin.forms.use_encryption.help" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<h4 class="title is-4 pt-5 has-text-grey-light">{{ $t('commons.environment') }}</h4>
|
<h4 class="title is-4 pt-5 has-text-grey-light">{{ $t('message.environment') }}</h4>
|
||||||
<!-- cache management -->
|
<!-- cache management -->
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<!-- <h5 class="title is-5">{{ $t('settings.security') }}</h5> -->
|
<!-- <h5 class="title is-5">{{ $t('message.settings.security') }}</h5> -->
|
||||||
<label for="btnClearCache" class="label" v-html="$t('admin.forms.cache_management.label')" />
|
<label for="btnClearCache" class="label" v-html="$t('message.admin.forms.cache_management.label')" />
|
||||||
<p class="help" v-html="$t('admin.forms.cache_management.help')" />
|
<p class="help" v-html="$t('message.admin.forms.cache_management.help')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="field mb-5 is-grouped">
|
<div class="field mb-5 is-grouped">
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<button id="btnClearCache" type="button" :class="isClearingCache ? 'is-loading' : ''" class="button is-link is-rounded is-small" @click="clearCache">
|
<button id="btnClearCache" type="button" :class="isClearingCache ? 'is-loading' : ''" class="button is-link is-rounded is-small" @click="clearCache">
|
||||||
{{ $t('commons.clear') }}
|
{{ $t('message.clear') }}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- env vars -->
|
<!-- env vars -->
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="btnCopyEnvVars" class="label" v-html="$t('admin.variables')" />
|
<label for="btnCopyEnvVars" class="label" v-html="$t('message.admin.variables')" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="infos" class="about-debug box is-family-monospace is-size-7">
|
<div v-if="infos" class="about-debug box is-family-monospace is-size-7">
|
||||||
<CopyButton id="btnCopyEnvVars" :token="listInfos?.innerText" />
|
<CopyButton id="btnCopyEnvVars" :token="listInfos?.innerText" />
|
||||||
@ -135,7 +143,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="infos === null" class="about-debug box is-family-monospace is-size-7 has-text-warning-dark">
|
<div v-else-if="infos === null" class="about-debug box is-family-monospace is-size-7 has-text-warning-dark">
|
||||||
{{ $t('errors.error_during_data_fetching') }}
|
{{ $t('error.error_during_data_fetching') }}
|
||||||
</div>
|
</div>
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
import { useAppSettingsUpdater } from '@/composables/appSettingsUpdater'
|
import { useAppSettingsUpdater } from '@/composables/appSettingsUpdater'
|
||||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const $2fauth = inject('2fauth')
|
const $2fauth = inject('2fauth')
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
const appSettings = useAppSettingsStore()
|
const appSettings = useAppSettingsStore()
|
||||||
@ -40,7 +42,7 @@
|
|||||||
if (value == '') {
|
if (value == '') {
|
||||||
appSettingService.delete(setting, { returnError: true }).then(response => {
|
appSettingService.delete(setting, { returnError: true }).then(response => {
|
||||||
appSettings[setting] = ''
|
appSettings[setting] = ''
|
||||||
notify.success({ type: 'is-success', text: trans('settings.forms.setting_saved') })
|
notify.success({ type: 'is-success', text: t('message.settings.forms.setting_saved') })
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if( error.response.status !== 404 ) {
|
if( error.response.status !== 404 ) {
|
||||||
@ -75,24 +77,24 @@
|
|||||||
<div class="options-tabs">
|
<div class="options-tabs">
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<form>
|
<form>
|
||||||
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('admin.single_sign_on') }}</h4>
|
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('message.admin.single_sign_on') }}</h4>
|
||||||
<!-- enable SSO -->
|
<!-- enable SSO -->
|
||||||
<FormCheckbox v-model="appSettings.enableSso" @update:model-value="val => useAppSettingsUpdater('enableSso', val)" fieldName="enableSso" label="admin.forms.enable_sso.label" help="admin.forms.enable_sso.help" />
|
<FormCheckbox v-model="appSettings.enableSso" @update:model-value="val => useAppSettingsUpdater('enableSso', val)" fieldName="enableSso" label="message.admin.forms.enable_sso.label" help="message.admin.forms.enable_sso.help" />
|
||||||
<!-- use SSO only -->
|
<!-- use SSO only -->
|
||||||
<FormCheckbox v-model="appSettings.useSsoOnly" @update:model-value="val => useAppSettingsUpdater('useSsoOnly', val)" fieldName="useSsoOnly" label="admin.forms.use_sso_only.label" help="admin.forms.use_sso_only.help" :isDisabled="!appSettings.enableSso" :isIndented="true" />
|
<FormCheckbox v-model="appSettings.useSsoOnly" @update:model-value="val => useAppSettingsUpdater('useSsoOnly', val)" fieldName="useSsoOnly" label="message.admin.forms.use_sso_only.label" help="message.admin.forms.use_sso_only.help" :isDisabled="!appSettings.enableSso" :isIndented="true" />
|
||||||
<!-- Allow Pat In SSO Only -->
|
<!-- Allow Pat In SSO Only -->
|
||||||
<FormCheckbox v-model="appSettings.allowPatWhileSsoOnly" @update:model-value="val => useAppSettingsUpdater('allowPatWhileSsoOnly', val)" fieldName="allowPatWhileSsoOnly" label="admin.forms.allow_pat_in_sso_only.label" help="admin.forms.allow_pat_in_sso_only.help" :isDisabled="!appSettings.useSsoOnly" :isIndented="true" />
|
<FormCheckbox v-model="appSettings.allowPatWhileSsoOnly" @update:model-value="val => useAppSettingsUpdater('allowPatWhileSsoOnly', val)" fieldName="allowPatWhileSsoOnly" label="message.admin.forms.allow_pat_in_sso_only.label" help="message.admin.forms.allow_pat_in_sso_only.help" :isDisabled="!appSettings.useSsoOnly" :isIndented="true" />
|
||||||
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('admin.registrations') }}</h4>
|
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('message.admin.registrations') }}</h4>
|
||||||
<!-- restrict registration -->
|
<!-- restrict registration -->
|
||||||
<FormCheckbox v-model="appSettings.restrictRegistration" @update:model-value="val => useAppSettingsUpdater('restrictRegistration', val)" fieldName="restrictRegistration" :isDisabled="appSettings.disableRegistration" label="admin.forms.restrict_registration.label" help="admin.forms.restrict_registration.help" />
|
<FormCheckbox v-model="appSettings.restrictRegistration" @update:model-value="val => useAppSettingsUpdater('restrictRegistration', val)" fieldName="restrictRegistration" :isDisabled="appSettings.disableRegistration" label="message.admin.forms.restrict_registration.label" help="message.admin.forms.restrict_registration.help" />
|
||||||
<!-- restrict list -->
|
<!-- restrict list -->
|
||||||
<FormField v-model="appSettings.restrictList" @change:model-value="val => saveOrDeleteSetting('restrictList', val)" :errorMessage="fieldErrors.restrictList" fieldName="restrictList" :isDisabled="!appSettings.restrictRegistration || appSettings.disableRegistration" label="admin.forms.restrict_list.label" help="admin.forms.restrict_list.help" :isIndented="true" />
|
<FormField v-model="appSettings.restrictList" @change:model-value="val => saveOrDeleteSetting('restrictList', val)" :errorMessage="fieldErrors.restrictList" fieldName="restrictList" :isDisabled="!appSettings.restrictRegistration || appSettings.disableRegistration" label="message.admin.forms.restrict_list.label" help="message.admin.forms.restrict_list.help" :isIndented="true" />
|
||||||
<!-- restrict rule -->
|
<!-- restrict rule -->
|
||||||
<FormField v-model="appSettings.restrictRule" @change:model-value="val => saveOrDeleteSetting('restrictRule', val)" :errorMessage="fieldErrors.restrictRule" fieldName="restrictRule" :isDisabled="!appSettings.restrictRegistration || appSettings.disableRegistration" label="admin.forms.restrict_rule.label" help="admin.forms.restrict_rule.help" :isIndented="true" leftIcon="Slash" rightIcon="Slash" />
|
<FormField v-model="appSettings.restrictRule" @change:model-value="val => saveOrDeleteSetting('restrictRule', val)" :errorMessage="fieldErrors.restrictRule" fieldName="restrictRule" :isDisabled="!appSettings.restrictRegistration || appSettings.disableRegistration" label="message.admin.forms.restrict_rule.label" help="message.admin.forms.restrict_rule.help" :isIndented="true" leftIcon="Slash" rightIcon="Slash" />
|
||||||
<!-- disable registration -->
|
<!-- disable registration -->
|
||||||
<FormCheckbox v-model="appSettings.disableRegistration" @update:model-value="val => useAppSettingsUpdater('disableRegistration', val)" fieldName="disableRegistration" label="admin.forms.disable_registration.label" help="admin.forms.disable_registration.help" />
|
<FormCheckbox v-model="appSettings.disableRegistration" @update:model-value="val => useAppSettingsUpdater('disableRegistration', val)" fieldName="disableRegistration" label="message.admin.forms.disable_registration.label" help="message.admin.forms.disable_registration.help" />
|
||||||
<!-- keep sso registration -->
|
<!-- keep sso registration -->
|
||||||
<FormCheckbox v-model="appSettings.keepSsoRegistrationEnabled" @update:model-value="val => useAppSettingsUpdater('keepSsoRegistrationEnabled', val)" fieldName="keepSsoRegistrationEnabled" :isDisabled="!appSettings.enableSso || !appSettings.disableRegistration" label="admin.forms.keep_sso_registration_enabled.label" help="admin.forms.keep_sso_registration_enabled.help" :isIndented="true" />
|
<FormCheckbox v-model="appSettings.keepSsoRegistrationEnabled" @update:model-value="val => useAppSettingsUpdater('keepSsoRegistrationEnabled', val)" fieldName="keepSsoRegistrationEnabled" :isDisabled="!appSettings.enableSso || !appSettings.disableRegistration" label="message.admin.forms.keep_sso_registration_enabled.label" help="message.admin.forms.keep_sso_registration_enabled.help" :isIndented="true" />
|
||||||
</form>
|
</form>
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
</div>
|
</div>
|
||||||
|
@ -108,25 +108,25 @@
|
|||||||
<AdminTabs activeTab="admin.users" />
|
<AdminTabs activeTab="admin.users" />
|
||||||
<div class="options-tabs">
|
<div class="options-tabs">
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<h4 class="title is-4 has-text-grey-light">{{ $t('admin.users') }}</h4>
|
<h4 class="title is-4 has-text-grey-light">{{ $t('message.admin.users') }}</h4>
|
||||||
<div class="is-size-7-mobile">
|
<div class="is-size-7-mobile">
|
||||||
{{ $t('admin.users_legend')}}
|
{{ $t('message.admin.users_legend')}}
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-6 mt-3">
|
<div class="mb-6 mt-3">
|
||||||
<RouterLink class="is-link mt-5" :to="{ name: 'admin.createUser' }">
|
<RouterLink class="is-link mt-5" :to="{ name: 'admin.createUser' }">
|
||||||
<FontAwesomeIcon :icon="['fas', 'plus-circle']" /> {{ $t('admin.create_new_user') }}
|
<FontAwesomeIcon :icon="['fas', 'plus-circle']" /> {{ $t('message.admin.create_new_user') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<!-- search -->
|
<!-- search -->
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column pb-0">
|
<div class="column pb-0">
|
||||||
<SearchBox v-model:keyword="searched" :hasNoBackground="true" :placeholder="$t('admin.search_user_placeholder')" />
|
<SearchBox v-model:keyword="searched" :hasNoBackground="true" :placeholder="$t('message.admin.search_user_placeholder')" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="level is-mobile mb-0">
|
<div class="level is-mobile mb-0">
|
||||||
<div class="level-item has-text-centered is-justify-content-end">
|
<div class="level-item has-text-centered is-justify-content-end">
|
||||||
<p class="subtitle is-7">
|
<p class="subtitle is-7">
|
||||||
{{ $t('admin.quick_filters_colons') }}
|
{{ $t('message.admin.quick_filters_colons') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="level-item has-text-centered is-justify-content-start">
|
<div class="level-item has-text-centered is-justify-content-start">
|
||||||
@ -154,19 +154,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<!-- manage link -->
|
<!-- manage link -->
|
||||||
<RouterLink :to="{ name: 'admin.manageUser', params: { userId: user.id }}" class="button is-small has-normal-radius" :class="{'is-dark' : mode == 'dark'}" :title="$t('commons.manage')">
|
<RouterLink :to="{ name: 'admin.manageUser', params: { userId: user.id }}" class="button is-small has-normal-radius" :class="{'is-dark' : mode == 'dark'}" :title="$t('message.manage')">
|
||||||
{{ $t('commons.manage') }}
|
{{ $t('message.manage') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</UseColorMode>
|
</UseColorMode>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="mt-2 is-size-7 is-pulled-right">
|
<!-- <div class="mt-2 is-size-7 is-pulled-right">
|
||||||
{{ $t('settings.revoking_a_token_is_permanent')}}
|
{{ $t('message.settings.revoking_a_token_is_permanent')}}
|
||||||
</div> -->
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="mt-4 pl-3">
|
<div v-else class="mt-4 pl-3">
|
||||||
{{ $t('commons.no_result') }}
|
{{ $t('message.no_result') }}
|
||||||
</div>
|
</div>
|
||||||
<Spinner :isVisible="isFetching && users.length === 0" />
|
<Spinner :isVisible="isFetching && users.length === 0" />
|
||||||
<!-- footer -->
|
<!-- footer -->
|
||||||
|
@ -32,10 +32,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<ResponsiveWidthWrapper>
|
<ResponsiveWidthWrapper>
|
||||||
<h1 class="title has-text-grey-dark">
|
<h1 class="title has-text-grey-dark">
|
||||||
{{ $t('titles.admin.logs.access') }}
|
{{ $t('title.admin.logs.access') }}
|
||||||
</h1>
|
</h1>
|
||||||
<div class="block is-size-7-mobile">
|
<div class="block is-size-7-mobile">
|
||||||
{{ $t('admin.access_log_legend_for_user', { username: username }) }} (#{{ props.userId }})
|
{{ $t('message.admin.access_log_legend_for_user', { username: username }) }} (#{{ props.userId }})
|
||||||
</div>
|
</div>
|
||||||
<AccessLogViewer :userId="props.userId" :lastOnly="false" :showSearch="true" :period="1" />
|
<AccessLogViewer :userId="props.userId" :lastOnly="false" :showSearch="true" :period="1" />
|
||||||
<!-- footer -->
|
<!-- footer -->
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Form from '@/components/formElements/Form'
|
import Form from '@/components/formElements/Form'
|
||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@ -21,7 +23,7 @@
|
|||||||
|
|
||||||
registerForm.post('/api/v1/users').then(response => {
|
registerForm.post('/api/v1/users').then(response => {
|
||||||
const user = response.data
|
const user = response.data
|
||||||
notify.success({ text: trans('admin.user_created') })
|
notify.success({ text: t('message.admin.user_created') })
|
||||||
router.push({ name: 'admin.manageUser', params: { userId: user.info.id } })
|
router.push({ name: 'admin.manageUser', params: { userId: user.info.id } })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -29,13 +31,13 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<FormWrapper title="admin.new_user">
|
<FormWrapper title="message.admin.new_user">
|
||||||
<form @submit.prevent="createUser" @keydown="registerForm.onKeydown($event)">
|
<form @submit.prevent="createUser" @keydown="registerForm.onKeydown($event)">
|
||||||
<FormField v-model="registerForm.name" fieldName="name" :errorMessage="registerForm.errors.get('name')" inputType="text" label="auth.forms.name" autocomplete="username" :maxLength="255" autofocus />
|
<FormField v-model="registerForm.name" fieldName="name" :errorMessage="registerForm.errors.get('name')" inputType="text" label="message.auth.forms.name" autocomplete="username" :maxLength="255" autofocus />
|
||||||
<FormField v-model="registerForm.email" fieldName="email" :errorMessage="registerForm.errors.get('email')" inputType="email" label="auth.forms.email" autocomplete="email" :maxLength="255" />
|
<FormField v-model="registerForm.email" fieldName="email" :errorMessage="registerForm.errors.get('email')" inputType="email" label="message.auth.forms.email" autocomplete="email" :maxLength="255" />
|
||||||
<FormPasswordField v-model="registerForm.password" fieldName="password" :errorMessage="registerForm.errors.get('password')" :showRules="true" label="auth.forms.password" autocomplete="new-password" />
|
<FormPasswordField v-model="registerForm.password" fieldName="password" :errorMessage="registerForm.errors.get('password')" :showRules="true" label="message.auth.forms.password" autocomplete="new-password" />
|
||||||
<FormCheckbox v-model="registerForm.is_admin" fieldName="is_admin" label="admin.forms.is_admin.label" help="admin.forms.is_admin.help" />
|
<FormCheckbox v-model="registerForm.is_admin" fieldName="is_admin" label="message.admin.forms.is_admin.label" help="message.admin.forms.is_admin.help" />
|
||||||
<FormButtons :isBusy="registerForm.isBusy" :isDisabled="registerForm.isDisabled" :showCancelButton="true" @cancel="router.push({ name: 'admin.users' })" submitLabel="commons.create" submitId="btnCreateUser" />
|
<FormButtons :isBusy="registerForm.isBusy" :isDisabled="registerForm.isDisabled" :showCancelButton="true" @cancel="router.push({ name: 'admin.users' })" submitLabel="message.create" submitId="btnCreateUser" />
|
||||||
</form>
|
</form>
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
<!-- footer -->
|
<!-- footer -->
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
import { UseColorMode } from '@vueuse/components'
|
import { UseColorMode } from '@vueuse/components'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { useBusStore } from '@/stores/bus'
|
import { useBusStore } from '@/stores/bus'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const user = useUserStore()
|
const user = useUserStore()
|
||||||
@ -53,7 +55,7 @@
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (confirm(trans('admin.confirm.purge_password_reset_request')) === true) {
|
if (confirm(t('message.admin.confirm.purge_password_reset_request')) === true) {
|
||||||
await userService.resendPasswordEmail(managedUser.value.info.id)
|
await userService.resendPasswordEmail(managedUser.value.info.id)
|
||||||
managedUser.value.password_reset = null
|
managedUser.value.password_reset = null
|
||||||
}
|
}
|
||||||
@ -67,11 +69,11 @@
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (confirm(trans('admin.confirm.request_password_reset')) === true) {
|
if (confirm(t('message.admin.confirm.request_password_reset')) === true) {
|
||||||
userService.resetPassword(managedUser.value.info.id, { returnError: true })
|
userService.resetPassword(managedUser.value.info.id, { returnError: true })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
managedUser.value = response.data
|
managedUser.value = response.data
|
||||||
notify.success({ text: trans('admin.password_successfully_reset') })
|
notify.success({ text: t('message.admin.password_successfully_reset') })
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if(error.response.status === 400) {
|
if(error.response.status === 400) {
|
||||||
@ -89,7 +91,7 @@
|
|||||||
* @param {boolean} isAdmin
|
* @param {boolean} isAdmin
|
||||||
*/
|
*/
|
||||||
function saveAdminRole(isAdmin) {
|
function saveAdminRole(isAdmin) {
|
||||||
if (! confirm(trans('admin.confirm.change_admin_role'))) {
|
if (! confirm(t('message.admin.confirm.change_admin_role'))) {
|
||||||
nextTick().then(() => {
|
nextTick().then(() => {
|
||||||
managedUser.value.info.is_admin = ! isAdmin
|
managedUser.value.info.is_admin = ! isAdmin
|
||||||
})
|
})
|
||||||
@ -97,7 +99,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(isAdmin === false && managedUser.value.info.id === user.id) {
|
if(isAdmin === false && managedUser.value.info.id === user.id) {
|
||||||
if (! confirm(trans('admin.confirm.demote_own_account'))) {
|
if (! confirm(t('message.admin.confirm.demote_own_account'))) {
|
||||||
nextTick().then(() => {
|
nextTick().then(() => {
|
||||||
managedUser.value.info.is_admin = true
|
managedUser.value.info.is_admin = true
|
||||||
})
|
})
|
||||||
@ -107,7 +109,7 @@
|
|||||||
|
|
||||||
userService.promote(managedUser.value.info.id, { 'is_admin': isAdmin }, { returnError: true }).then(response => {
|
userService.promote(managedUser.value.info.id, { 'is_admin': isAdmin }, { returnError: true }).then(response => {
|
||||||
managedUser.value.info.is_admin = response.data.info.is_admin
|
managedUser.value.info.is_admin = response.data.info.is_admin
|
||||||
notify.success({ text: trans('admin.user_role_updated') })
|
notify.success({ text: t('message.admin.user_role_updated') })
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if( error.response.status === 403 ) {
|
if( error.response.status === 403 ) {
|
||||||
@ -128,9 +130,9 @@
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if(confirm(trans('admin.confirm.delete_account'))) {
|
if(confirm(t('message.admin.confirm.delete_account'))) {
|
||||||
userService.delete(managedUser.value.info.id, { returnError: true }).then(response => {
|
userService.delete(managedUser.value.info.id, { returnError: true }).then(response => {
|
||||||
notify.success({ text: trans('auth.forms.user_account_successfully_deleted') })
|
notify.success({ text: t('message.auth.forms.user_account_successfully_deleted') })
|
||||||
router.push({ name: 'admin.users' });
|
router.push({ name: 'admin.users' });
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@ -154,7 +156,7 @@
|
|||||||
|
|
||||||
userService.revokePATs(managedUser.value.info.id).then(response => {
|
userService.revokePATs(managedUser.value.info.id).then(response => {
|
||||||
managedUser.value.valid_personal_access_tokens = 0
|
managedUser.value.valid_personal_access_tokens = 0
|
||||||
notify.success({ text: trans('admin.pats_succesfully_revoked') })
|
notify.success({ text: t('message.admin.pats_succesfully_revoked') })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +170,7 @@
|
|||||||
|
|
||||||
userService.revokeWebauthnCredentials(managedUser.value.info.id).then(response => {
|
userService.revokeWebauthnCredentials(managedUser.value.info.id).then(response => {
|
||||||
managedUser.value.valid_personal_access_tokens = 0
|
managedUser.value.valid_personal_access_tokens = 0
|
||||||
notify.success({ text: trans('admin.security_devices_succesfully_revoked') })
|
notify.success({ text: t('message.admin.security_devices_succesfully_revoked') })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +179,7 @@
|
|||||||
*/
|
*/
|
||||||
function confirmForYourself() {
|
function confirmForYourself() {
|
||||||
if(managedUser.value.info.id === user.id) {
|
if(managedUser.value.info.id === user.id) {
|
||||||
if (! confirm(trans('admin.confirm.edit_own_account'))) {
|
if (! confirm(t('message.admin.confirm.edit_own_account'))) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,7 +193,7 @@
|
|||||||
<UseColorMode v-slot="{ mode }">
|
<UseColorMode v-slot="{ mode }">
|
||||||
<ResponsiveWidthWrapper>
|
<ResponsiveWidthWrapper>
|
||||||
<h1 class="title has-text-grey-dark mb-6">
|
<h1 class="title has-text-grey-dark mb-6">
|
||||||
{{ $t('admin.user_management') }}
|
{{ $t('message.admin.user_management') }}
|
||||||
</h1>
|
</h1>
|
||||||
<!-- loader -->
|
<!-- loader -->
|
||||||
<div v-if="isFetching || ! managedUser" class="has-text-centered">
|
<div v-if="isFetching || ! managedUser" class="has-text-centered">
|
||||||
@ -207,57 +209,57 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- oauth banner -->
|
<!-- oauth banner -->
|
||||||
<div v-if="managedUser.info.oauth_provider" class="notification is-dark is-size-7-mobile has-text-centered">
|
<div v-if="managedUser.info.oauth_provider" class="notification is-dark is-size-7-mobile has-text-centered">
|
||||||
{{ $t('admin.account_bound_to_x_via_oauth', { provider: managedUser.info.oauth_provider }) }}
|
{{ $t('message.admin.account_bound_to_x_via_oauth', { provider: managedUser.info.oauth_provider }) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="block is-size-6 is-size-7-mobile has-text-grey">
|
<div class="block is-size-6 is-size-7-mobile has-text-grey">
|
||||||
{{ $t('admin.registered_on_date', { date: managedUser.info.created_at }) }} - {{ $t('admin.last_seen_on_date', { date: managedUser.info.last_seen_at }) }}
|
{{ $t('message.admin.registered_on_date', { date: managedUser.info.created_at }) }} - {{ $t('message.admin.last_seen_on_date', { date: managedUser.info.last_seen_at }) }}
|
||||||
</div>
|
</div>
|
||||||
<!-- isAdmin option -->
|
<!-- isAdmin option -->
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<FormCheckbox v-model="managedUser.info.is_admin" @update:model-value="val => saveAdminRole(val === true)" fieldName="is_admin" label="admin.forms.is_admin.label" help="admin.forms.is_admin.help" />
|
<FormCheckbox v-model="managedUser.info.is_admin" @update:model-value="val => saveAdminRole(val === true)" fieldName="is_admin" label="message.admin.forms.is_admin.label" help="message.admin.forms.is_admin.help" />
|
||||||
</div>
|
</div>
|
||||||
<h2 v-if="!$2fauth.config.proxyAuth" class="title is-4 has-text-grey-light">{{ $t('admin.access') }}</h2>
|
<h2 v-if="!$2fauth.config.proxyAuth" class="title is-4 has-text-grey-light">{{ $t('message.admin.access') }}</h2>
|
||||||
<!-- access -->
|
<!-- access -->
|
||||||
<div v-if="!$2fauth.config.proxyAuth" class="block">
|
<div v-if="!$2fauth.config.proxyAuth" class="block">
|
||||||
<!-- reset password -->
|
<!-- reset password -->
|
||||||
<div class="list-item is-size-6 is-size-6-mobile has-text-grey">
|
<div class="list-item is-size-6 is-size-6-mobile has-text-grey">
|
||||||
<div class="mb-3 is-flex is-justify-content-space-between">
|
<div class="mb-3 is-flex is-justify-content-space-between">
|
||||||
<div>
|
<div>
|
||||||
<span class="has-text-weight-bold">{{ $t('auth.forms.password') }}</span>
|
<span class="has-text-weight-bold">{{ $t('message.auth.forms.password') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="tags ml-3 is-right">
|
<div class="tags ml-3 is-right">
|
||||||
<!-- resend email button -->
|
<!-- resend email button -->
|
||||||
<button type="button" v-if="managedUser.password_reset" class="button tag is-pulled-right" :class="mode == 'dark' ? 'is-dark has-background-link' : 'is-white'" @click="resendPasswordEmail" :title="$t('admin.resend_email_title')">
|
<button type="button" v-if="managedUser.password_reset" class="button tag is-pulled-right" :class="mode == 'dark' ? 'is-dark has-background-link' : 'is-white'" @click="resendPasswordEmail" :title="$t('message.admin.resend_email_title')">
|
||||||
{{ $t('admin.resend_email') }}
|
{{ $t('message.admin.resend_email') }}
|
||||||
</button>
|
</button>
|
||||||
<!-- reset password button -->
|
<!-- reset password button -->
|
||||||
<button type="button" class="button tag is-pulled-right " :class="mode == 'dark' ? 'is-dark has-background-link' : 'is-white'" @click="resetPassword" :title="$t('admin.reset_password_title')">
|
<button type="button" class="button tag is-pulled-right " :class="mode == 'dark' ? 'is-dark has-background-link' : 'is-white'" @click="resetPassword" :title="$t('message.admin.reset_password_title')">
|
||||||
{{ $t('admin.reset_password') }}
|
{{ $t('message.admin.reset_password') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="is-size-7 is-size-7-mobile has-text-grey-dark">
|
<div class="is-size-7 is-size-7-mobile has-text-grey-dark">
|
||||||
<span v-if="managedUser.password_reset === 0" v-html="$t('admin.password_request_expired')" class="is-block block"></span>
|
<span v-if="managedUser.password_reset === 0" v-html="$t('message.admin.password_request_expired')" class="is-block block"></span>
|
||||||
<span v-else-if="managedUser.password_reset" v-html="$t('admin.password_requested_on_t', { datetime: managedUser.password_reset })" class="is-block block"></span>
|
<span v-else-if="managedUser.password_reset" v-html="$t('message.admin.password_requested_on_t', { datetime: managedUser.password_reset })" class="is-block block"></span>
|
||||||
<span v-if="managedUser.password_reset" v-html="$t('admin.resend_email_help')" class="is-block block"></span>
|
<span v-if="managedUser.password_reset" v-html="$t('message.admin.resend_email_help')" class="is-block block"></span>
|
||||||
<span v-html="$t('admin.reset_password_help')" class="is-block block"></span>
|
<span v-html="$t('message.admin.reset_password_help')" class="is-block block"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- personal access tokens -->
|
<!-- personal access tokens -->
|
||||||
<div class="list-item is-size-6 is-size-6-mobile has-text-grey is-flex is-justify-content-space-between">
|
<div class="list-item is-size-6 is-size-6-mobile has-text-grey is-flex is-justify-content-space-between">
|
||||||
<div>
|
<div>
|
||||||
<span class="has-text-weight-bold">{{ $t('settings.personal_access_tokens') }}</span>
|
<span class="has-text-weight-bold">{{ $t('message.settings.personal_access_tokens') }}</span>
|
||||||
<span class="is-block is-family-primary has-text-grey-dark">
|
<span class="is-block is-family-primary has-text-grey-dark">
|
||||||
{{ $t('admin.user_has_x_active_pat', { count: managedUser.valid_personal_access_tokens }) }}
|
{{ $t('message.admin.user_has_x_active_pat', { count: managedUser.valid_personal_access_tokens }) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="managedUser.valid_personal_access_tokens > 0">
|
<div v-if="managedUser.valid_personal_access_tokens > 0">
|
||||||
<div class="tags ml-3 is-right">
|
<div class="tags ml-3 is-right">
|
||||||
<!-- manage link -->
|
<!-- manage link -->
|
||||||
<button type="button" class="button tag is-pulled-right" :class="mode == 'dark' ? 'is-dark has-background-link' : 'is-white'" @click="revokePATs" :title="$t('admin.revoke_all_pat_for_user')">
|
<button type="button" class="button tag is-pulled-right" :class="mode == 'dark' ? 'is-dark has-background-link' : 'is-white'" @click="revokePATs" :title="$t('message.admin.revoke_all_pat_for_user')">
|
||||||
{{ $t('settings.revoke') }}
|
{{ $t('message.settings.revoke') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -265,16 +267,16 @@
|
|||||||
<!-- webauthn devices -->
|
<!-- webauthn devices -->
|
||||||
<div class="list-item is-size-6 is-size-6-mobile has-text-grey is-flex is-justify-content-space-between">
|
<div class="list-item is-size-6 is-size-6-mobile has-text-grey is-flex is-justify-content-space-between">
|
||||||
<div>
|
<div>
|
||||||
<span class="has-text-weight-bold">{{ $t('auth.webauthn.security_devices') }}</span>
|
<span class="has-text-weight-bold">{{ $t('message.auth.webauthn.security_devices') }}</span>
|
||||||
<span class="is-block has-text-grey-dark">
|
<span class="is-block has-text-grey-dark">
|
||||||
{{ $t('admin.user_has_x_security_devices', { count: managedUser.webauthn_credentials }) }}
|
{{ $t('message.admin.user_has_x_security_devices', { count: managedUser.webauthn_credentials }) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="managedUser.webauthn_credentials > 0">
|
<div v-if="managedUser.webauthn_credentials > 0">
|
||||||
<div class="tags ml-3 is-right">
|
<div class="tags ml-3 is-right">
|
||||||
<!-- manage link -->
|
<!-- manage link -->
|
||||||
<button type="button" class="button tag is-pulled-right" :class="mode == 'dark' ? 'is-dark has-background-link' : 'is-white'" :title="$t('admin.revoke_all_devices_for_user')">
|
<button type="button" class="button tag is-pulled-right" :class="mode == 'dark' ? 'is-dark has-background-link' : 'is-white'" :title="$t('message.admin.revoke_all_devices_for_user')">
|
||||||
{{ $t('settings.revoke') }}
|
{{ $t('message.settings.revoke') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -282,16 +284,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- last access -->
|
<!-- last access -->
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h3 class="title is-5 has-text-grey-light mb-2">{{ $t('admin.last_accesses') }}</h3>
|
<h3 class="title is-5 has-text-grey-light mb-2">{{ $t('message.admin.last_accesses') }}</h3>
|
||||||
<AccessLogViewer :userId="props.userId" :lastOnly="true" @has-more-entries="showFullLogLink = true"/>
|
<AccessLogViewer :userId="props.userId" :lastOnly="true" @has-more-entries="showFullLogLink = true"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showFullLogLink" class="block is-size-6 is-size-7-mobile has-text-grey">
|
<div v-if="showFullLogLink" class="block is-size-6 is-size-7-mobile has-text-grey">
|
||||||
{{ $t('admin.access_log_has_more_entries') }} <router-link id="lnkFullLogs" :to="{ name: 'admin.logs.access', params: { userId: props.userId }}" >
|
{{ $t('message.admin.access_log_has_more_entries') }} <router-link id="lnkFullLogs" :to="{ name: 'admin.logs.access', params: { userId: props.userId }}" >
|
||||||
{{ $t('admin.see_full_log') }}.
|
{{ $t('message.admin.see_full_log') }}.
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<!-- preferences -->
|
<!-- preferences -->
|
||||||
<h2 class="title is-4 has-text-grey-light">{{ $t('settings.preferences') }}</h2>
|
<h2 class="title is-4 has-text-grey-light">{{ $t('message.settings.preferences') }}</h2>
|
||||||
<div class="about-debug box is-family-monospace is-size-7">
|
<div class="about-debug box is-family-monospace is-size-7">
|
||||||
<CopyButton id="btnCopyEnvVars" :token="listUserPreferences?.innerText" />
|
<CopyButton id="btnCopyEnvVars" :token="listUserPreferences?.innerText" />
|
||||||
<ul ref="listUserPreferences" id="listUserPreferences">
|
<ul ref="listUserPreferences" id="listUserPreferences">
|
||||||
@ -301,16 +303,16 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- danger zone -->
|
<!-- danger zone -->
|
||||||
<h2 class="title is-4 has-text-danger">{{ $t('admin.danger_zone') }}</h2>
|
<h2 class="title is-4 has-text-danger">{{ $t('message.admin.danger_zone') }}</h2>
|
||||||
<div class="is-left-bordered-danger">
|
<div class="is-left-bordered-danger">
|
||||||
<div class="block is-size-6 is-size-7-mobile">
|
<div class="block is-size-6 is-size-7-mobile">
|
||||||
{{ $t('admin.delete_this_user_legend') }}
|
{{ $t('message.admin.delete_this_user_legend') }}
|
||||||
<span class="is-block has-text-grey has-text-weight-bold">
|
<span class="is-block has-text-grey has-text-weight-bold">
|
||||||
{{ $t('admin.this_is_not_soft_delete') }}
|
{{ $t('message.admin.this_is_not_soft_delete') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="button is-danger" @click="deleteUser" title="delete">
|
<button type="button" class="button is-danger" @click="deleteUser" title="delete">
|
||||||
{{ $t('admin.delete_this_user') }}
|
{{ $t('message.admin.delete_this_user') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||||
import { webauthnService } from '@/services/webauthn/webauthnService'
|
import { webauthnService } from '@/services/webauthn/webauthnService'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const $2fauth = inject('2fauth')
|
const $2fauth = inject('2fauth')
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const user = useUserStore()
|
const user = useUserStore()
|
||||||
@ -62,7 +64,7 @@
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if( error.response.status === 401 ) {
|
if( error.response.status === 401 ) {
|
||||||
notify.alert({text: trans('auth.forms.authentication_failed'), duration: 10000 })
|
notify.alert({text: t('message.auth.forms.authentication_failed'), duration: 10000 })
|
||||||
}
|
}
|
||||||
else if( error.response.status !== 422 ) {
|
else if( error.response.status !== 422 ) {
|
||||||
notify.error(error)
|
notify.error(error)
|
||||||
@ -97,12 +99,12 @@
|
|||||||
.catch(error => {
|
.catch(error => {
|
||||||
if ('webauthn' in error) {
|
if ('webauthn' in error) {
|
||||||
if (error.name == 'is-warning') {
|
if (error.name == 'is-warning') {
|
||||||
notify.warn({ text: trans(error.message) })
|
notify.warn({ text: t(error.message) })
|
||||||
}
|
}
|
||||||
else notify.alert({ text: trans(error.message) })
|
else notify.alert({ text: t(error.message) })
|
||||||
}
|
}
|
||||||
else if( error.response.status === 401 ) {
|
else if( error.response.status === 401 ) {
|
||||||
notify.alert({text: trans('auth.forms.authentication_failed'), duration: 10000 })
|
notify.alert({text: t('message.auth.forms.authentication_failed'), duration: 10000 })
|
||||||
}
|
}
|
||||||
else if( error.response.status == 422 ) {
|
else if( error.response.status == 422 ) {
|
||||||
form.errors.set(form.extractErrors(error.response))
|
form.errors.set(form.extractErrors(error.response))
|
||||||
@ -120,36 +122,36 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- webauthn authentication -->
|
<!-- webauthn authentication -->
|
||||||
<FormWrapper v-if="activeForm == 'webauthn'" title="auth.forms.webauthn_login" punchline="auth.welcome_to_2fauth">
|
<FormWrapper v-if="activeForm == 'webauthn'" title="message.auth.forms.webauthn_login" punchline="message.auth.welcome_to_2fauth">
|
||||||
<div v-if="appSettings.enableSso == true && appSettings.useSsoOnly == true" class="notification is-warning has-text-centered" v-html="$t('auth.forms.sso_only_form_restricted_to_admin')" />
|
<div v-if="appSettings.enableSso == true && appSettings.useSsoOnly == true" class="notification is-warning has-text-centered" v-html="$t('message.auth.forms.sso_only_form_restricted_to_admin')" />
|
||||||
<div class="field">
|
<div class="field">
|
||||||
{{ $t('auth.webauthn.use_security_device_to_sign_in') }}
|
{{ $t('message.auth.webauthn.use_security_device_to_sign_in') }}
|
||||||
</div>
|
</div>
|
||||||
<form id="frmWebauthnLogin" @submit.prevent="webauthnLogin" @keydown="form.onKeydown($event)">
|
<form id="frmWebauthnLogin" @submit.prevent="webauthnLogin" @keydown="form.onKeydown($event)">
|
||||||
<FormField v-model="form.email" fieldName="email" :errorMessage="form.errors.get('email')" inputType="email" label="auth.forms.email" autofocus />
|
<FormField v-model="form.email" fieldName="email" :errorMessage="form.errors.get('email')" inputType="email" label="message.auth.forms.email" autofocus />
|
||||||
<FormButtons :isBusy="isBusy" submitLabel="commons.continue" submitId="btnContinue"/>
|
<FormButtons :isBusy="isBusy" submitLabel="message.continue" submitId="btnContinue"/>
|
||||||
</form>
|
</form>
|
||||||
<div class="nav-links">
|
<div class="nav-links">
|
||||||
<p>
|
<p>
|
||||||
{{ $t('auth.webauthn.lost_your_device') }}
|
{{ $t('message.auth.webauthn.lost_your_device') }}
|
||||||
<RouterLink id="lnkRecoverAccount" :to="{ name: 'webauthn.lost' }" class="is-link">
|
<RouterLink id="lnkRecoverAccount" :to="{ name: 'webauthn.lost' }" class="is-link">
|
||||||
{{ $t('auth.webauthn.recover_your_account') }}
|
{{ $t('message.auth.webauthn.recover_your_account') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</p>
|
</p>
|
||||||
<p>{{ $t('auth.sign_in_using') }}
|
<p>{{ $t('message.auth.sign_in_using') }}
|
||||||
<a id="lnkSignWithLegacy" role="button" class="is-link" @keyup.enter="switchToForm('legacy')" @click="switchToForm('legacy')" tabindex="0">
|
<a id="lnkSignWithLegacy" role="button" class="is-link" @keyup.enter="switchToForm('legacy')" @click="switchToForm('legacy')" tabindex="0">
|
||||||
{{ $t('auth.login_and_password') }}
|
{{ $t('message.auth.login_and_password') }}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p v-if="appSettings.disableRegistration == false && appSettings.useSsoOnly == false" class="mt-4">
|
<p v-if="appSettings.disableRegistration == false && appSettings.useSsoOnly == false" class="mt-4">
|
||||||
{{ $t('auth.forms.dont_have_account_yet') }}
|
{{ $t('message.auth.forms.dont_have_account_yet') }}
|
||||||
<RouterLink id="lnkRegister" :to="{ name: 'register' }" class="is-link">
|
<RouterLink id="lnkRegister" :to="{ name: 'register' }" class="is-link">
|
||||||
{{ $t('auth.register') }}
|
{{ $t('message.auth.register') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</p>
|
</p>
|
||||||
<div v-if="appSettings.enableSso == true && Object.values($2fauth.config.sso).includes(true)" class="columns mt-4 is-variable is-1">
|
<div v-if="appSettings.enableSso == true && Object.values($2fauth.config.sso).includes(true)" class="columns mt-4 is-variable is-1">
|
||||||
<div class="column is-narrow py-1">
|
<div class="column is-narrow py-1">
|
||||||
{{ $t('auth.or_continue_with') }}
|
{{ $t('message.auth.or_continue_with') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="column py-1">
|
<div class="column py-1">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
@ -162,63 +164,104 @@
|
|||||||
</div>
|
</div>
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
<!-- SSO only links -->
|
<!-- SSO only links -->
|
||||||
<FormWrapper v-else-if="activeForm == 'sso'" title="auth.forms.sso_login" punchline="auth.welcome_to_2fauth">
|
<!-- TODO: compléter la punchline avec les lignes manquantes de auth.webauthn.webauthn_uses_trusted_devices -->
|
||||||
<div v-if="$2fauth.isDemoApp" class="notification is-info has-text-centered is-radiusless" v-html="$t('auth.forms.welcome_to_demo_app_use_those_credentials')" />
|
<FormWrapper v-else-if="activeForm == 'sso'" title="message.auth.forms.sso_login" punchline="message.auth.welcome_to_2fauth">
|
||||||
<div v-if="$2fauth.isTestingApp" class="notification is-warning has-text-centered is-radiusless" v-html="$t('auth.forms.welcome_to_testing_app_use_those_credentials')" />
|
<div v-if="$2fauth.isDemoApp" class="notification is-info has-text-centered is-radiusless">
|
||||||
|
{{ $t('message.auth.forms.welcome_to_demo_app') }}<br />
|
||||||
|
<i18n-t keypath="message.auth.forms.sign_in_using_email_password" tag="span">
|
||||||
|
<template v-slot:email>
|
||||||
|
<strong>demo@2fauth.app</strong>
|
||||||
|
</template>
|
||||||
|
<template v-slot:password>
|
||||||
|
<strong>demo</strong>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
<div v-if="$2fauth.isTestingApp" class="notification is-warning has-text-centered is-radiusless">
|
||||||
|
{{ $t('message.auth.forms.welcome_to_testing_app') }}
|
||||||
|
<i18n-t keypath="message.auth.forms.use_those_credentials" tag="span">
|
||||||
|
<template v-slot:email>
|
||||||
|
<strong>testing@2fauth.app</strong>
|
||||||
|
</template>
|
||||||
|
<template v-slot:password>
|
||||||
|
<strong>password</strong>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
<div class="nav-links">
|
<div class="nav-links">
|
||||||
<p class="">{{ $t('auth.password_login_and_webauthn_are_disabled') }}</p>
|
<p class="">{{ $t('message.auth.password_login_and_webauthn_are_disabled') }}</p>
|
||||||
<p class="">{{ $t('auth.sign_in_using_sso') }}</p>
|
<p class="">{{ $t('message.auth.sign_in_using_sso') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="Object.values($2fauth.config.sso).includes(true)" class="buttons mt-4">
|
<div v-if="Object.values($2fauth.config.sso).includes(true)" class="buttons mt-4">
|
||||||
<template v-for="(isEnabled, provider) in $2fauth.config.sso">
|
<template v-for="(isEnabled, provider) in $2fauth.config.sso">
|
||||||
<SsoConnectLink v-if="isEnabled" :provider="provider" />
|
<SsoConnectLink v-if="isEnabled" :provider="provider" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<p v-else class="is-italic">- {{ $t('auth.no_provider') }} -</p>
|
<p v-else class="is-italic">- {{ $t('message.auth.no_provider') }} -</p>
|
||||||
<div class="nav-links">
|
<div class="nav-links">
|
||||||
<p>
|
<p>
|
||||||
{{ $t('auth.no_sso_provider_or_provider_is_missing') }}
|
{{ $t('message.auth.no_sso_provider_or_provider_is_missing') }}
|
||||||
<a id="lnkSsoDocs" class="is-link" tabindex="0" :href="$2fauth.urls.ssoDocUrl" target="_blank">
|
<a id="lnkSsoDocs" class="is-link" tabindex="0" :href="$2fauth.urls.ssoDocUrl" target="_blank">
|
||||||
{{ $t('auth.see_how_to_enable_sso') }}
|
{{ $t('message.auth.see_how_to_enable_sso') }}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p >{{ $t('auth.if_administrator') }}
|
<p >{{ $t('message.auth.if_administrator') }}
|
||||||
<a id="lnkSignWithLegacy" role="button" class="is-link" @keyup.enter="switchToForm('legacy')" @click="switchToForm('legacy')" tabindex="0">
|
<a id="lnkSignWithLegacy" role="button" class="is-link" @keyup.enter="switchToForm('legacy')" @click="switchToForm('legacy')" tabindex="0">
|
||||||
{{ $t('auth.sign_in_here') }}
|
{{ $t('message.auth.sign_in_here') }}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
<!-- login/password legacy form -->
|
<!-- login/password legacy form -->
|
||||||
<FormWrapper v-else-if="activeForm == 'legacy'" title="auth.forms.login" punchline="auth.welcome_to_2fauth">
|
<FormWrapper v-else-if="activeForm == 'legacy'" title="message.auth.forms.login" punchline="message.auth.welcome_to_2fauth">
|
||||||
<div v-if="$2fauth.isDemoApp" class="notification is-info has-text-centered is-radiusless" v-html="$t('auth.forms.welcome_to_demo_app_use_those_credentials')" />
|
<div v-if="$2fauth.isDemoApp" class="notification is-info has-text-centered is-radiusless">
|
||||||
<div v-if="$2fauth.isTestingApp" class="notification is-warning has-text-centered is-radiusless" v-html="$t('auth.forms.welcome_to_testing_app_use_those_credentials')" />
|
{{ $t('message.auth.forms.welcome_to_demo_app') }}<br />
|
||||||
<div v-if="appSettings.enableSso == true && appSettings.useSsoOnly == true" class="notification is-warning has-text-centered" v-html="$t('auth.forms.sso_only_form_restricted_to_admin')" />
|
<i18n-t keypath="message.auth.forms.sign_in_using_email_password" tag="span">
|
||||||
|
<template v-slot:email>
|
||||||
|
<strong>demo@2fauth.app</strong>
|
||||||
|
</template>
|
||||||
|
<template v-slot:password>
|
||||||
|
<strong>demo</strong>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
<div v-if="$2fauth.isTestingApp" class="notification is-warning has-text-centered is-radiusless">
|
||||||
|
{{ $t('message.auth.forms.welcome_to_testing_app') }}
|
||||||
|
<i18n-t keypath="message.auth.forms.use_those_credentials" tag="span">
|
||||||
|
<template v-slot:email>
|
||||||
|
<strong>testing@2fauth.app</strong>
|
||||||
|
</template>
|
||||||
|
<template v-slot:password>
|
||||||
|
<strong>password</strong>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
<div v-if="appSettings.enableSso == true && appSettings.useSsoOnly == true" class="notification is-warning has-text-centered" v-html="$t('message.auth.forms.sso_only_form_restricted_to_admin')" />
|
||||||
<form id="frmLegacyLogin" @submit.prevent="LegacysignIn" @keydown="form.onKeydown($event)">
|
<form id="frmLegacyLogin" @submit.prevent="LegacysignIn" @keydown="form.onKeydown($event)">
|
||||||
<FormField v-model="form.email" fieldName="email" :errorMessage="form.errors.get('email')" inputType="email" label="auth.forms.email" autocomplete="username" autofocus />
|
<FormField v-model="form.email" fieldName="email" :errorMessage="form.errors.get('email')" inputType="email" label="message.auth.forms.email" autocomplete="username" autofocus />
|
||||||
<FormPasswordField v-model="form.password" fieldName="password" :errorMessage="form.errors.get('password')" label="auth.forms.password" autocomplete="current-password" />
|
<FormPasswordField v-model="form.password" fieldName="password" :errorMessage="form.errors.get('password')" label="message.auth.forms.password" autocomplete="current-password" />
|
||||||
<FormButtons :isBusy="isBusy" submitLabel="auth.sign_in" submitId="btnSignIn"/>
|
<FormButtons :isBusy="isBusy" submitLabel="message.auth.sign_in" submitId="btnSignIn"/>
|
||||||
</form>
|
</form>
|
||||||
<div class="nav-links">
|
<div class="nav-links">
|
||||||
<p>{{ $t('auth.forms.forgot_your_password') }}
|
<p>{{ $t('message.auth.forms.forgot_your_password') }}
|
||||||
<RouterLink id="lnkResetPwd" :to="{ name: 'password.request' }" class="is-link" :aria-label="$t('auth.forms.reset_your_password')">
|
<RouterLink id="lnkResetPwd" :to="{ name: 'password.request' }" class="is-link" :aria-label="$t('message.auth.forms.reset_your_password')">
|
||||||
{{ $t('auth.forms.request_password_reset') }}
|
{{ $t('message.auth.forms.request_password_reset') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</p>
|
</p>
|
||||||
<p >{{ $t('auth.sign_in_using') }}
|
<p >{{ $t('message.auth.sign_in_using') }}
|
||||||
<a id="lnkSignWithWebauthn" role="button" class="is-link" @keyup.enter="switchToForm('webauthn')" @click="switchToForm('webauthn')" tabindex="0" :aria-label="$t('auth.sign_in_using_security_device')">
|
<a id="lnkSignWithWebauthn" role="button" class="is-link" @keyup.enter="switchToForm('webauthn')" @click="switchToForm('webauthn')" tabindex="0" :aria-label="$t('message.auth.sign_in_using_security_device')">
|
||||||
{{ $t('auth.webauthn.security_device') }}
|
{{ $t('message.auth.webauthn.security_device') }}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p v-if="appSettings.disableRegistration == false && appSettings.useSsoOnly == false" class="mt-4">
|
<p v-if="appSettings.disableRegistration == false && appSettings.useSsoOnly == false" class="mt-4">
|
||||||
{{ $t('auth.forms.dont_have_account_yet') }}
|
{{ $t('message.auth.forms.dont_have_account_yet') }}
|
||||||
<RouterLink id="lnkRegister" :to="{ name: 'register' }" class="is-link">
|
<RouterLink id="lnkRegister" :to="{ name: 'register' }" class="is-link">
|
||||||
{{ $t('auth.register') }}
|
{{ $t('message.auth.register') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</p>
|
</p>
|
||||||
<div v-if="appSettings.enableSso && Object.values($2fauth.config.sso).includes(true)" class="columns mt-4 is-variable is-1">
|
<div v-if="appSettings.enableSso && Object.values($2fauth.config.sso).includes(true)" class="columns mt-4 is-variable is-1">
|
||||||
<div class="column is-narrow py-1">
|
<div class="column is-narrow py-1">
|
||||||
{{ $t('auth.or_continue_with') }}
|
{{ $t('message.auth.or_continue_with') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="column py-1">
|
<div class="column py-1">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { webauthnService } from '@/services/webauthn/webauthnService'
|
import { webauthnService } from '@/services/webauthn/webauthnService'
|
||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const user = useUserStore()
|
const user = useUserStore()
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -66,7 +68,7 @@
|
|||||||
function RenameDevice(e) {
|
function RenameDevice(e) {
|
||||||
renameDeviceForm.patch('/webauthn/credentials/' + deviceId.value + '/name')
|
renameDeviceForm.patch('/webauthn/credentials/' + deviceId.value + '/name')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
notify.success({ text: trans('auth.webauthn.device_successfully_registered') })
|
notify.success({ text: t('message.auth.webauthn.device_successfully_registered') })
|
||||||
router.push({ name: 'accounts' })
|
router.push({ name: 'accounts' })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -79,35 +81,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- webauthn registration -->
|
<!-- webauthn registration -->
|
||||||
<FormWrapper v-if="showWebauthnRegistration" title="auth.authentication" punchline="auth.webauthn.enhance_security_using_webauthn">
|
<FormWrapper v-if="showWebauthnRegistration" title="message.auth.authentication" punchline="message.auth.webauthn.enhance_security_using_webauthn">
|
||||||
<div v-if="deviceId" class="field">
|
<div v-if="deviceId" class="field">
|
||||||
<label id="lblDeviceRegistrationSuccess" class="label mb-5">{{ $t('auth.webauthn.device_successfully_registered') }} <font-awesome-icon :icon="['fas', 'check']" /></label>
|
<label id="lblDeviceRegistrationSuccess" class="label mb-5">{{ $t('message.auth.webauthn.device_successfully_registered') }} <font-awesome-icon :icon="['fas', 'check']" /></label>
|
||||||
<form @submit.prevent="RenameDevice" @keydown="renameDeviceForm.onKeydown($event)">
|
<form @submit.prevent="RenameDevice" @keydown="renameDeviceForm.onKeydown($event)">
|
||||||
<FormField v-model="renameDeviceForm.name" fieldName="name" :errorMessage="renameDeviceForm.errors.get('name')" inputType="text" placeholder="iPhone 12, TouchID, Yubikey 5C" label="auth.forms.name_this_device" />
|
<FormField v-model="renameDeviceForm.name" fieldName="name" :errorMessage="renameDeviceForm.errors.get('name')" inputType="text" placeholder="iPhone 12, TouchID, Yubikey 5C" label="message.auth.forms.name_this_device" />
|
||||||
<FormButtons :isBusy="renameDeviceForm.isBusy" :isDisabled="renameDeviceForm.isDisabled" submitLabel="commons.continue" />
|
<FormButtons :isBusy="renameDeviceForm.isBusy" :isDisabled="renameDeviceForm.isDisabled" submitLabel="message.continue" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="field is-grouped">
|
<div v-else class="field is-grouped">
|
||||||
<!-- register button -->
|
<!-- register button -->
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button type="button" id="btnRegisterNewDevice" @click="registerWebauthnDevice()" class="button is-link">{{ $t('auth.webauthn.register_a_device') }}</button>
|
<button type="button" id="btnRegisterNewDevice" @click="registerWebauthnDevice()" class="button is-link">{{ $t('message.auth.webauthn.register_a_device') }}</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- dismiss button -->
|
<!-- dismiss button -->
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<RouterLink id="btnMaybeLater" :to="{ name: 'accounts' }" class="button is-text">{{ $t('auth.maybe_later') }}</RouterLink>
|
<RouterLink id="btnMaybeLater" :to="{ name: 'accounts' }" class="button is-text">{{ $t('message.auth.maybe_later') }}</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
<!-- User registration form -->
|
<!-- User registration form -->
|
||||||
<FormWrapper v-else title="auth.register" punchline="auth.forms.register_punchline">
|
<!-- TODO: completer la punchline avec la ligne manquante auth.forms.you_need_an_account_to_go_further -->
|
||||||
|
<FormWrapper v-else title="message.auth.register" punchline="message.auth.forms.welcome_to_2fauth">
|
||||||
<form @submit.prevent="register" @keydown="registerForm.onKeydown($event)">
|
<form @submit.prevent="register" @keydown="registerForm.onKeydown($event)">
|
||||||
<FormField v-model="registerForm.name" fieldName="name" :errorMessage="registerForm.errors.get('name')" inputType="text" label="auth.forms.name" autocomplete="username" :maxLength="255" autofocus />
|
<FormField v-model="registerForm.name" fieldName="name" :errorMessage="registerForm.errors.get('name')" inputType="text" label="message.auth.forms.name" autocomplete="username" :maxLength="255" autofocus />
|
||||||
<FormField v-model="registerForm.email" fieldName="email" :errorMessage="registerForm.errors.get('email')" inputType="email" label="auth.forms.email" autocomplete="email" :maxLength="255" />
|
<FormField v-model="registerForm.email" fieldName="email" :errorMessage="registerForm.errors.get('email')" inputType="email" label="message.auth.forms.email" autocomplete="email" :maxLength="255" />
|
||||||
<FormPasswordField v-model="registerForm.password" fieldName="password" :errorMessage="registerForm.errors.get('password')" :showRules="true" autocomplete="new-password" label="auth.forms.password" />
|
<FormPasswordField v-model="registerForm.password" fieldName="password" :errorMessage="registerForm.errors.get('password')" :showRules="true" autocomplete="new-password" label="message.auth.forms.password" />
|
||||||
<FormButtons :isBusy="registerForm.isBusy" :isDisabled="registerForm.isDisabled" submitLabel="auth.register" submitId="btnRegister" />
|
<FormButtons :isBusy="registerForm.isBusy" :isDisabled="registerForm.isDisabled" submitLabel="message.auth.register" submitId="btnRegister" />
|
||||||
</form>
|
</form>
|
||||||
<div class="nav-links">
|
<div class="nav-links">
|
||||||
<p>{{ $t('auth.forms.already_register') }} <RouterLink id="lnkSignIn" :to="{ name: 'login' }" class="is-link">{{ $t('auth.sign_in') }}</RouterLink></p>
|
<p>{{ $t('message.auth.forms.already_register') }} <RouterLink id="lnkSignIn" :to="{ name: 'login' }" class="is-link">{{ $t('message.auth.sign_in') }}</RouterLink></p>
|
||||||
</div>
|
</div>
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
<!-- footer -->
|
<!-- footer -->
|
||||||
|
@ -36,13 +36,14 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormWrapper :title="$t(isWebauthnReset ? 'auth.webauthn.account_recovery' : 'auth.forms.reset_password')" :punchline="$t(isWebauthnReset ? 'auth.webauthn.recovery_punchline' : 'auth.forms.reset_punchline')">
|
<!-- TODO: compléter la punchline avec la ligne manquante de auth.webauthn.ensure_you_open_mail_in_trusted_device -->
|
||||||
|
<FormWrapper :title="isWebauthnReset ? 'message.auth.webauthn.account_recovery' : 'message.auth.forms.reset_password'" :punchline="isWebauthnReset ? 'message.auth.webauthn.recovery_punchline' : 'message.auth.forms.reset_punchline'">
|
||||||
<form @submit.prevent="requestPasswordReset" @keydown="form.onKeydown($event)">
|
<form @submit.prevent="requestPasswordReset" @keydown="form.onKeydown($event)">
|
||||||
<FormField v-model="form.email" fieldName="email" :errorMessage="form.errors.get('email')" label="auth.forms.email" autofocus />
|
<FormField v-model="form.email" fieldName="email" :errorMessage="form.errors.get('email')" label="message.auth.forms.email" autofocus />
|
||||||
<FormButtons
|
<FormButtons
|
||||||
:submitId="'btnSendResetPwd'"
|
:submitId="'btnSendResetPwd'"
|
||||||
:isBusy="form.isBusy"
|
:isBusy="form.isBusy"
|
||||||
:submitLabel="isWebauthnReset ? 'auth.webauthn.send_recovery_link' : 'auth.forms.send_password_reset_link'"
|
:submitLabel="isWebauthnReset ? 'message.auth.webauthn.send_recovery_link' : 'message.auth.forms.send_password_reset_link'"
|
||||||
:showCancelButton="true"
|
:showCancelButton="true"
|
||||||
@cancel="router.push({ name: 'login' })" />
|
@cancel="router.push({ name: 'login' })" />
|
||||||
</form>
|
</form>
|
||||||
|
@ -43,19 +43,19 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormWrapper :title="$t('auth.forms.new_password')">
|
<FormWrapper title="message.auth.forms.new_password">
|
||||||
<form @submit.prevent="resetPassword" @keydown="form.onKeydown($event)">
|
<form @submit.prevent="resetPassword" @keydown="form.onKeydown($event)">
|
||||||
<FormField v-model="form.email" :isDisabled="true" fieldName="email" :errorMessage="form.errors.get('email')" label="auth.forms.email" autofocus />
|
<FormField v-model="form.email" :isDisabled="true" fieldName="email" :errorMessage="form.errors.get('email')" label="message.auth.forms.email" autofocus />
|
||||||
<FormPasswordField v-model="form.password" fieldName="password" :errorMessage="form.errors.get('password')" autocomplete="new-password" :showRules="true" label="auth.forms.new_password" />
|
<FormPasswordField v-model="form.password" fieldName="password" :errorMessage="form.errors.get('password')" autocomplete="new-password" :showRules="true" label="message.auth.forms.new_password" />
|
||||||
<FormFieldError v-if="form.errors.get('token') != undefined" :error="form.errors.get('token')" :field="form.token" />
|
<FormFieldError v-if="form.errors.get('token') != undefined" :error="form.errors.get('token')" :field="form.token" />
|
||||||
<FormButtons
|
<FormButtons
|
||||||
v-if="isPending"
|
v-if="isPending"
|
||||||
:submitId="'btnResetPwd'"
|
:submitId="'btnResetPwd'"
|
||||||
:isBusy="form.isBusy"
|
:isBusy="form.isBusy"
|
||||||
submitLabel="auth.forms.change_password"
|
submitLabel="message.auth.forms.change_password"
|
||||||
:showCancelButton="true"
|
:showCancelButton="true"
|
||||||
@cancel="router.push({ name: 'login' })" />
|
@cancel="router.push({ name: 'login' })" />
|
||||||
<RouterLink v-if="!isPending" id="btnContinue" :to="{ name: 'accounts' }" class="button is-link">{{ $t('commons.continue') }}</RouterLink>
|
<RouterLink v-if="!isPending" id="btnContinue" :to="{ name: 'accounts' }" class="button is-link">{{ $t('message.continue') }}</RouterLink>
|
||||||
</form>
|
</form>
|
||||||
<VueFooter />
|
<VueFooter />
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Form from '@/components/formElements/Form'
|
import Form from '@/components/formElements/Form'
|
||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const $2fauth = inject('2fauth')
|
const $2fauth = inject('2fauth')
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -27,7 +29,7 @@
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if ( error.response.status === 401 ) {
|
if ( error.response.status === 401 ) {
|
||||||
notify.alert({ text: trans('auth.forms.authentication_failed'), duration:-1 })
|
notify.alert({ text: t('message.auth.forms.authentication_failed'), duration:-1 })
|
||||||
}
|
}
|
||||||
else if (error.response.status === 422) {
|
else if (error.response.status === 422) {
|
||||||
notify.alert({ text: error.response.data.message, duration:-1 })
|
notify.alert({ text: error.response.data.message, duration:-1 })
|
||||||
@ -44,16 +46,16 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormWrapper :title="$t('auth.webauthn.account_recovery')" :punchline="$t('auth.webauthn.recover_account_instructions')" >
|
<FormWrapper title="message.auth.webauthn.account_recovery" punchline="message.auth.webauthn.recover_account_instructions" >
|
||||||
<div>
|
<div>
|
||||||
<form @submit.prevent="recover" @keydown="form.onKeydown($event)">
|
<form @submit.prevent="recover" @keydown="form.onKeydown($event)">
|
||||||
<FormCheckbox v-model="form.revokeAll" fieldName="revokeAll" label="auth.webauthn.disable_all_security_devices" help="auth.webauthn.disable_all_security_devices_help" />
|
<FormCheckbox v-model="form.revokeAll" fieldName="revokeAll" label="message.auth.webauthn.disable_all_security_devices" help="message.auth.webauthn.disable_all_security_devices_help" />
|
||||||
<FormPasswordField v-model="form.password" fieldName="password" :errorMessage="form.errors.get('password')" autocomplete="current-password" :showRules="false" label="auth.forms.current_password.label" help="auth.forms.current_password.help" />
|
<FormPasswordField v-model="form.password" fieldName="password" :errorMessage="form.errors.get('password')" autocomplete="current-password" :showRules="false" label="message.auth.forms.current_password.label" help="message.auth.forms.current_password.help" />
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<p>
|
<p>
|
||||||
{{ $t('auth.forms.forgot_your_password') }}
|
{{ $t('message.auth.forms.forgot_your_password') }}
|
||||||
<RouterLink id="lnkResetPwd" :to="{ name: 'password.request' }" class="is-link" :aria-label="$t('auth.forms.reset_your_password')">
|
<RouterLink id="lnkResetPwd" :to="{ name: 'password.request' }" class="is-link" :aria-label="$t('message.auth.forms.reset_your_password')">
|
||||||
{{ $t('auth.forms.request_password_reset') }}
|
{{ $t('message.auth.forms.request_password_reset') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -61,7 +63,7 @@
|
|||||||
:submitId="'btnRecover'"
|
:submitId="'btnRecover'"
|
||||||
:isBusy="form.isBusy"
|
:isBusy="form.isBusy"
|
||||||
:isDisabled="form.isDisabled"
|
:isDisabled="form.isDisabled"
|
||||||
submitLabel="commons.continue"
|
submitLabel="message.continue"
|
||||||
:showCancelButton="true"
|
:showCancelButton="true"
|
||||||
@cancel="router.push({ name: 'login' })" />
|
@cancel="router.push({ name: 'login' })" />
|
||||||
</form>
|
</form>
|
||||||
|
@ -66,13 +66,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormWrapper :title="isEditMode ? $t('groups.forms.rename_group') : $t('groups.forms.new_group')">
|
<FormWrapper :title="isEditMode ? 'message.groups.forms.rename_group' : 'message.groups.forms.new_group'">
|
||||||
<form @submit.prevent="handleSubmit" @keydown="form.onKeydown($event)">
|
<form @submit.prevent="handleSubmit" @keydown="form.onKeydown($event)">
|
||||||
<FormField v-model="form.name" fieldName="name" :errorMessage="form.errors.get('name')" label="commons.name" autofocus />
|
<FormField v-model="form.name" fieldName="name" :errorMessage="form.errors.get('name')" label="message.name" autofocus />
|
||||||
<FormButtons
|
<FormButtons
|
||||||
:submitId="isEditMode ? 'btnEditGroup' : 'btnCreateGroup'"
|
:submitId="isEditMode ? 'btnEditGroup' : 'btnCreateGroup'"
|
||||||
:isBusy="form.isBusy"
|
:isBusy="form.isBusy"
|
||||||
:submitLabel="isEditMode ? 'commons.save' : 'commons.create'"
|
:submitLabel="isEditMode ? 'message.save' : 'message.create'"
|
||||||
:showCancelButton="true"
|
:showCancelButton="true"
|
||||||
@cancel="router.push({ name: 'groups' })" />
|
@cancel="router.push({ name: 'groups' })" />
|
||||||
</form>
|
</form>
|
||||||
|
@ -34,14 +34,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<ResponsiveWidthWrapper>
|
<ResponsiveWidthWrapper>
|
||||||
<h1 class="title has-text-grey-dark">
|
<h1 class="title has-text-grey-dark">
|
||||||
{{ $t('groups.groups') }}
|
{{ $t('message.groups.groups') }}
|
||||||
</h1>
|
</h1>
|
||||||
<div class="is-size-7-mobile">
|
<div class="is-size-7-mobile">
|
||||||
{{ $t('groups.manage_groups_legend')}}
|
{{ $t('message.groups.manage_groups_legend')}}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 mb-6">
|
<div class="mt-3 mb-6">
|
||||||
<RouterLink class="is-link mt-5" :to="{ name: 'createGroup' }">
|
<RouterLink class="is-link mt-5" :to="{ name: 'createGroup' }">
|
||||||
<FontAwesomeIcon :icon="['fas', 'plus-circle']" /> {{ $t('groups.create_group') }}
|
<FontAwesomeIcon :icon="['fas', 'plus-circle']" /> {{ $t('message.groups.create_group') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!groups.isEmpty">
|
<div v-if="!groups.isEmpty">
|
||||||
@ -49,18 +49,18 @@
|
|||||||
{{ group.name }}
|
{{ group.name }}
|
||||||
<!-- delete icon -->
|
<!-- delete icon -->
|
||||||
<UseColorMode v-slot="{ mode }">
|
<UseColorMode v-slot="{ mode }">
|
||||||
<button type="button" class="button tag is-pulled-right" :class="mode == 'dark' ? 'is-dark' : 'is-white'" @click="groups.delete(group.id)" :title="$t('commons.delete')">
|
<button type="button" class="button tag is-pulled-right" :class="mode == 'dark' ? 'is-dark' : 'is-white'" @click="groups.delete(group.id)" :title="$t('message.delete')">
|
||||||
{{ $t('commons.delete') }}
|
{{ $t('message.delete') }}
|
||||||
</button>
|
</button>
|
||||||
</UseColorMode>
|
</UseColorMode>
|
||||||
<!-- edit link -->
|
<!-- edit link -->
|
||||||
<RouterLink :to="{ name: 'editGroup', params: { groupId: group.id }}" class="has-text-grey px-1" :title="$t('commons.rename')">
|
<RouterLink :to="{ name: 'editGroup', params: { groupId: group.id }}" class="has-text-grey px-1" :title="$t('message.rename')">
|
||||||
<FontAwesomeIcon :icon="['fas', 'pen-square']" />
|
<FontAwesomeIcon :icon="['fas', 'pen-square']" />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<span class="is-family-primary is-size-6 is-size-7-mobile has-text-grey">{{ $t('groups.x_accounts', { count: group.twofaccounts_count }) }}</span>
|
<span class="is-family-primary is-size-6 is-size-7-mobile has-text-grey">{{ $t('message.groups.x_accounts', { count: group.twofaccounts_count }) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 is-size-7 is-pulled-right">
|
<div class="mt-2 is-size-7 is-pulled-right">
|
||||||
{{ $t('groups.deleting_group_does_not_delete_accounts')}}
|
{{ $t('message.groups.deleting_group_does_not_delete_accounts')}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isFetching && groups.isEmpty" class="has-text-centered">
|
<div v-if="isFetching && groups.isEmpty" class="has-text-centered">
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
import SettingTabs from '@/layouts/SettingTabs.vue'
|
import SettingTabs from '@/layouts/SettingTabs.vue'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const $2fauth = inject('2fauth')
|
const $2fauth = inject('2fauth')
|
||||||
const user = useUserStore()
|
const user = useUserStore()
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
@ -36,7 +38,7 @@
|
|||||||
email: response.data.email,
|
email: response.data.email,
|
||||||
isAdmin: response.data.is_admin,
|
isAdmin: response.data.is_admin,
|
||||||
})
|
})
|
||||||
notify.success({ text: trans('auth.forms.profile_saved') })
|
notify.success({ text: t('message.auth.forms.profile_saved') })
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if( error.response.status === 400 ) {
|
if( error.response.status === 400 ) {
|
||||||
@ -75,10 +77,10 @@
|
|||||||
*/
|
*/
|
||||||
function submitDelete(e) {
|
function submitDelete(e) {
|
||||||
|
|
||||||
if(confirm(trans('auth.confirm.delete_account'))) {
|
if(confirm(t('message.auth.confirm.delete_account'))) {
|
||||||
formDelete.delete('/user', {returnError: true})
|
formDelete.delete('/user', {returnError: true})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
notify.success({ text: trans('auth.forms.user_account_successfully_deleted') })
|
notify.success({ text: t('message.auth.forms.user_account_successfully_deleted') })
|
||||||
router.push({ name: 'register' });
|
router.push({ name: 'register' });
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@ -105,44 +107,46 @@
|
|||||||
<div class="options-tabs">
|
<div class="options-tabs">
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<div v-if="user.isAdmin" class="notification is-warning">
|
<div v-if="user.isAdmin" class="notification is-warning">
|
||||||
{{ $t('settings.you_are_administrator') }}
|
{{ $t('message.settings.you_are_administrator') }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="user.oauth_provider" class="notification is-info has-text-centered">
|
<div v-if="user.oauth_provider" class="notification is-info has-text-centered">
|
||||||
{{ $t('settings.account_linked_to_sso_x_provider', { provider: user.oauth_provider }) }}
|
{{ $t('message.settings.account_linked_to_sso_x_provider', { provider: user.oauth_provider }) }}
|
||||||
</div>
|
</div>
|
||||||
<form @submit.prevent="submitProfile" @keydown="formProfile.onKeydown($event)">
|
<form @submit.prevent="submitProfile" @keydown="formProfile.onKeydown($event)">
|
||||||
<div v-if="$2fauth.config.proxyAuth" class="notification is-warning has-text-centered" v-html="$t('auth.user_account_controlled_by_proxy')" />
|
<div v-if="$2fauth.config.proxyAuth" class="notification is-warning has-text-centered">
|
||||||
<h4 class="title is-4 has-text-grey-light">{{ $t('settings.profile') }}</h4>
|
{{ $t('message.auth.user_account_controlled_by_proxy') + '<br />' + $t('message.auth.manage_account_at_proxy_level') }}
|
||||||
|
</div>
|
||||||
|
<h4 class="title is-4 has-text-grey-light">{{ $t('message.settings.profile') }}</h4>
|
||||||
<fieldset :disabled="$2fauth.config.proxyAuth || user.oauth_provider">
|
<fieldset :disabled="$2fauth.config.proxyAuth || user.oauth_provider">
|
||||||
<FormField v-model="formProfile.name" fieldName="name" :errorMessage="formProfile.errors.get('name')" label="auth.forms.name" :maxLength="255" autocomplete="username" autofocus />
|
<FormField v-model="formProfile.name" fieldName="name" :errorMessage="formProfile.errors.get('name')" label="message.auth.forms.name" :maxLength="255" autocomplete="username" autofocus />
|
||||||
<FormField v-model="formProfile.email" fieldName="email" :errorMessage="formProfile.errors.get('email')" inputType="email" label="auth.forms.email" autocomplete="email" :maxLength="255" autofocus />
|
<FormField v-model="formProfile.email" fieldName="email" :errorMessage="formProfile.errors.get('email')" inputType="email" label="message.auth.forms.email" autocomplete="email" :maxLength="255" autofocus />
|
||||||
<FormField v-model="formProfile.password" fieldName="password" :errorMessage="formProfile.errors.get('password')" inputType="password" label="auth.forms.current_password.label" autocomplete="current-password" help="auth.forms.current_password.help" />
|
<FormField v-model="formProfile.password" fieldName="password" :errorMessage="formProfile.errors.get('password')" inputType="password" label="message.auth.forms.current_password.label" autocomplete="current-password" help="message.auth.forms.current_password.help" />
|
||||||
<FormButtons :isBusy="formProfile.isBusy" submitLabel="commons.update" />
|
<FormButtons :isBusy="formProfile.isBusy" submitLabel="message.update" />
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
<form @submit.prevent="submitPassword" @keydown="formPassword.onKeydown($event)">
|
<form @submit.prevent="submitPassword" @keydown="formPassword.onKeydown($event)">
|
||||||
<input hidden type="text" name="name" :value="formProfile.name" autocomplete="username" />
|
<input hidden type="text" name="name" :value="formProfile.name" autocomplete="username" />
|
||||||
<input hidden type="text" name="email" :value="formProfile.email" autocomplete="email" />
|
<input hidden type="text" name="email" :value="formProfile.email" autocomplete="email" />
|
||||||
<h4 class="title is-4 pt-6 has-text-grey-light">{{ $t('settings.change_password') }}</h4>
|
<h4 class="title is-4 pt-6 has-text-grey-light">{{ $t('message.settings.change_password') }}</h4>
|
||||||
<fieldset :disabled="$2fauth.config.proxyAuth || user.oauth_provider">
|
<fieldset :disabled="$2fauth.config.proxyAuth || user.oauth_provider">
|
||||||
<FormPasswordField v-model="formPassword.password" fieldName="password" :errorMessage="formPassword.errors.get('password')" idSuffix="ForUpdate" autocomplete="new-password" :showRules="true" label="auth.forms.new_password" />
|
<FormPasswordField v-model="formPassword.password" fieldName="password" :errorMessage="formPassword.errors.get('password')" idSuffix="ForUpdate" autocomplete="new-password" :showRules="true" label="message.auth.forms.new_password" />
|
||||||
<FormPasswordField v-model="formPassword.password_confirmation" :showRules="false" fieldName="password_confirmation" :errorMessage="formPassword.errors.get('password_confirmation')" inputType="password" autocomplete="new-password" label="auth.forms.confirm_new_password" />
|
<FormPasswordField v-model="formPassword.password_confirmation" :showRules="false" fieldName="password_confirmation" :errorMessage="formPassword.errors.get('password_confirmation')" inputType="password" autocomplete="new-password" label="message.auth.forms.confirm_new_password" />
|
||||||
<FormField v-model="formPassword.currentPassword" fieldName="currentPassword" :errorMessage="formPassword.errors.get('currentPassword')" inputType="password" label="auth.forms.current_password.label" autocomplete="current-password" help="auth.forms.current_password.help" />
|
<FormField v-model="formPassword.currentPassword" fieldName="currentPassword" :errorMessage="formPassword.errors.get('currentPassword')" inputType="password" label="message.auth.forms.current_password.label" autocomplete="current-password" help="message.auth.forms.current_password.help" />
|
||||||
<FormButtons :isBusy="formPassword.isBusy" submitId="btnSubmitChangePwd" submitLabel="auth.forms.change_password" />
|
<FormButtons :isBusy="formPassword.isBusy" submitId="btnSubmitChangePwd" submitLabel="message.auth.forms.change_password" />
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
<form id="frmDeleteAccount" @submit.prevent="submitDelete" @keydown="formDelete.onKeydown($event)">
|
<form id="frmDeleteAccount" @submit.prevent="submitDelete" @keydown="formDelete.onKeydown($event)">
|
||||||
<input hidden type="text" name="name" :value="formProfile.name" autocomplete="username" />
|
<input hidden type="text" name="name" :value="formProfile.name" autocomplete="username" />
|
||||||
<input hidden type="text" name="email" :value="formProfile.email" autocomplete="email" />
|
<input hidden type="text" name="email" :value="formProfile.email" autocomplete="email" />
|
||||||
<h4 class="title is-4 pt-6 has-text-danger">{{ $t('auth.forms.delete_account') }}</h4>
|
<h4 class="title is-4 pt-6 has-text-danger">{{ $t('message.auth.forms.delete_account') }}</h4>
|
||||||
<div class="field is-size-7-mobile">
|
<div class="field is-size-7-mobile">
|
||||||
<p class="block">{{ $t('auth.forms.delete_your_account_and_reset_all_data')}}</p>
|
<p class="block">{{ $t('message.auth.forms.delete_your_account_and_reset_all_data')}}</p>
|
||||||
<p>{{ $t('auth.forms.reset_your_password_to_delete_your_account') }}</p>
|
<p>{{ $t('message.auth.forms.reset_your_password_to_delete_your_account') }}</p>
|
||||||
<p>{{ $t('auth.forms.deleting_2fauth_account_does_not_impact_provider') }}</p>
|
<p>{{ $t('message.auth.forms.deleting_2fauth_account_does_not_impact_provider') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<fieldset :disabled="$2fauth.config.proxyAuth">
|
<fieldset :disabled="$2fauth.config.proxyAuth">
|
||||||
<FormField v-model="formDelete.password" fieldName="password" :errorMessage="formDelete.errors.get('password')" inputType="password" idSuffix="ForDelete" autocomplete="new-password" label="auth.forms.current_password.label" help="auth.forms.current_password.help" />
|
<FormField v-model="formDelete.password" fieldName="password" :errorMessage="formDelete.errors.get('password')" inputType="password" idSuffix="ForDelete" autocomplete="new-password" label="message.auth.forms.current_password.label" help="message.auth.forms.current_password.help" />
|
||||||
<FormButtons :isBusy="formDelete.isBusy" submitLabel="auth.forms.delete_your_account" submitId="btnDeleteAccount" color="is-danger" />
|
<FormButtons :isBusy="formDelete.isBusy" submitLabel="message.auth.forms.delete_your_account" submitId="btnDeleteAccount" color="is-danger" />
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Form from '@/components/formElements/Form'
|
import Form from '@/components/formElements/Form'
|
||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
const form = reactive(new Form({
|
const form = reactive(new Form({
|
||||||
name: trans('auth.webauthn.my_device')
|
name: t('message.auth.webauthn.my_device')
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -18,7 +20,7 @@
|
|||||||
function updateCredential() {
|
function updateCredential() {
|
||||||
form.patch('/webauthn/credentials/' + props.credentialId + '/name')
|
form.patch('/webauthn/credentials/' + props.credentialId + '/name')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
notify.success({ text: trans('auth.webauthn.device_successfully_registered') })
|
notify.success({ text: t('message.auth.webauthn.device_successfully_registered') })
|
||||||
router.push({ name: 'settings.webauthn.devices' })
|
router.push({ name: 'settings.webauthn.devices' })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -26,13 +28,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormWrapper title="auth.webauthn.rename_device">
|
<FormWrapper title="message.auth.webauthn.rename_device">
|
||||||
<form @submit.prevent="updateCredential" @keydown="form.onKeydown($event)">
|
<form @submit.prevent="updateCredential" @keydown="form.onKeydown($event)">
|
||||||
<FormField v-model="form.name" fieldName="name" :errorMessage="form.errors.get('name')" inputType="text" label="commons.new_name" autofocus />
|
<FormField v-model="form.name" fieldName="name" :errorMessage="form.errors.get('name')" inputType="text" label="message.new_name" autofocus />
|
||||||
<FormButtons
|
<FormButtons
|
||||||
:submitId="'btnEditCredential'"
|
:submitId="'btnEditCredential'"
|
||||||
:isBusy="form.isBusy"
|
:isBusy="form.isBusy"
|
||||||
submitLabel="commons.save"
|
submitLabel="message.save"
|
||||||
:showCancelButton="true"
|
:showCancelButton="true"
|
||||||
@cancel="router.push({ name: 'settings.webauthn.devices' })"
|
@cancel="router.push({ name: 'settings.webauthn.devices' })"
|
||||||
/>
|
/>
|
||||||
|
@ -7,7 +7,9 @@
|
|||||||
import { UseColorMode } from '@vueuse/components'
|
import { UseColorMode } from '@vueuse/components'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import Spinner from '@/components/Spinner.vue'
|
import Spinner from '@/components/Spinner.vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const appSettings = useAppSettingsStore()
|
const appSettings = useAppSettingsStore()
|
||||||
const $2fauth = inject('2fauth')
|
const $2fauth = inject('2fauth')
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
@ -77,7 +79,7 @@
|
|||||||
clearTokenValues()
|
clearTokenValues()
|
||||||
|
|
||||||
if (isDisabled.value) {
|
if (isDisabled.value) {
|
||||||
notify.warn({ text: trans('errors.unsupported_with_reverseproxy') })
|
notify.warn({ text: t('error.unsupported_with_reverseproxy') })
|
||||||
}
|
}
|
||||||
else createPATModalIsVisible.value = true
|
else createPATModalIsVisible.value = true
|
||||||
}
|
}
|
||||||
@ -100,12 +102,12 @@
|
|||||||
* revoke a token (after confirmation)
|
* revoke a token (after confirmation)
|
||||||
*/
|
*/
|
||||||
function revokeToken(tokenId) {
|
function revokeToken(tokenId) {
|
||||||
if(confirm(trans('settings.confirm.revoke'))) {
|
if(confirm(t('message.settings.confirm.revoke'))) {
|
||||||
userService.deletePersonalAccessToken(tokenId)
|
userService.deletePersonalAccessToken(tokenId)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
// Remove the revoked token from the collection
|
// Remove the revoked token from the collection
|
||||||
tokens.value = tokens.value.filter(a => a.id !== tokenId)
|
tokens.value = tokens.value.filter(a => a.id !== tokenId)
|
||||||
notify.success({ text: trans('settings.token_revoked') })
|
notify.success({ text: t('message.settings.token_revoked') })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,7 +127,7 @@
|
|||||||
*/
|
*/
|
||||||
function copyToClipboard(data) {
|
function copyToClipboard(data) {
|
||||||
copy(data)
|
copy(data)
|
||||||
notify.success({ text: trans('commons.copied_to_clipboard') })
|
notify.success({ text: t('message.copied_to_clipboard') })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,16 +151,18 @@
|
|||||||
<div class="options-tabs">
|
<div class="options-tabs">
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<div v-if="isDisabled && user.oauth_provider" class="notification is-warning has-text-centered">
|
<div v-if="isDisabled && user.oauth_provider" class="notification is-warning has-text-centered">
|
||||||
{{ $t('auth.sso_only_x_settings_are_disabled', { auth_method: 'OAuth' }) }}
|
{{ $t('message.auth.sso_only_x_settings_are_disabled', { auth_method: 'OAuth' }) }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isDisabled && user.authenticated_by_proxy" class="notification is-warning has-text-centered" v-html="$t('auth.auth_handled_by_proxy')" />
|
<div v-if="isDisabled && user.authenticated_by_proxy" class="notification is-warning has-text-centered">
|
||||||
<h4 class="title is-4 has-text-grey-light">{{ $t('settings.personal_access_tokens') }}</h4>
|
{{ $t('message.auth.auth_handled_by_proxy') + '<br />' + $t('message.auth.manage_auth_at_proxy_level') }}
|
||||||
|
</div>
|
||||||
|
<h4 class="title is-4 has-text-grey-light">{{ $t('message.settings.personal_access_tokens') }}</h4>
|
||||||
<div class="is-size-7-mobile">
|
<div class="is-size-7-mobile">
|
||||||
{{ $t('settings.token_legend')}}
|
{{ $t('message.settings.token_legend')}}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<a tabindex="0" class="is-link" @click="showPATcreationForm" @keyup.enter="showPATcreationForm">
|
<a tabindex="0" class="is-link" @click="showPATcreationForm" @keyup.enter="showPATcreationForm">
|
||||||
<FontAwesomeIcon :icon="['fas', 'plus-circle']" /> {{ $t('settings.generate_new_token')}}
|
<FontAwesomeIcon :icon="['fas', 'plus-circle']" /> {{ $t('message.settings.generate_new_token')}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="tokens.length > 0">
|
<div v-if="tokens.length > 0">
|
||||||
@ -168,16 +172,16 @@
|
|||||||
<div class="tags is-pulled-right">
|
<div class="tags is-pulled-right">
|
||||||
<UseColorMode v-slot="{ mode }">
|
<UseColorMode v-slot="{ mode }">
|
||||||
<button type="button" v-if="token.value" class="button tag" :class="{'is-link': mode != 'dark'}" @click.stop="copyToClipboard(token.value)">
|
<button type="button" v-if="token.value" class="button tag" :class="{'is-link': mode != 'dark'}" @click.stop="copyToClipboard(token.value)">
|
||||||
{{ $t('commons.copy') }}
|
{{ $t('message.copy') }}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="button tag" :class="mode === 'dark' ? 'is-dark':'is-white'" @click="revokeToken(token.id)" :title="$t('settings.revoke')">
|
<button type="button" class="button tag" :class="mode === 'dark' ? 'is-dark':'is-white'" @click="revokeToken(token.id)" :title="$t('message.settings.revoke')">
|
||||||
{{ $t('settings.revoke') }}
|
{{ $t('message.settings.revoke') }}
|
||||||
</button>
|
</button>
|
||||||
</UseColorMode>
|
</UseColorMode>
|
||||||
</div>
|
</div>
|
||||||
<!-- warning msg -->
|
<!-- warning msg -->
|
||||||
<span v-if="token.value" class="is-size-7-mobile is-size-6 my-3">
|
<span v-if="token.value" class="is-size-7-mobile is-size-6 my-3">
|
||||||
{{ $t('settings.make_sure_copy_token') }}
|
{{ $t('message.settings.make_sure_copy_token') }}
|
||||||
</span>
|
</span>
|
||||||
<!-- token value -->
|
<!-- token value -->
|
||||||
<span v-if="token.value" class="pat is-family-monospace is-size-6 is-size-7-mobile has-text-success">
|
<span v-if="token.value" class="pat is-family-monospace is-size-6 is-size-7-mobile has-text-success">
|
||||||
@ -185,7 +189,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 is-size-7 is-pulled-right">
|
<div class="mt-2 is-size-7 is-pulled-right">
|
||||||
{{ $t('settings.revoking_a_token_is_permanent')}}
|
{{ $t('message.settings.revoking_a_token_is_permanent')}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Spinner :isVisible="isFetching && tokens.length === 0" />
|
<Spinner :isVisible="isFetching && tokens.length === 0" />
|
||||||
@ -197,18 +201,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="createPATModalIsVisible" class="is-overlay modal-otp modal-background">
|
<div v-if="createPATModalIsVisible" class="is-overlay modal-otp modal-background">
|
||||||
<main class="main-section">
|
<main class="main-section">
|
||||||
<FormWrapper title="settings.forms.new_token">
|
<FormWrapper title="message.settings.forms.new_token">
|
||||||
<form @submit.prevent="generatePAToken" @keydown="form.onKeydown($event)">
|
<form @submit.prevent="generatePAToken" @keydown="form.onKeydown($event)">
|
||||||
<FormField v-model="form.name" fieldName="name" :errorMessage="form.errors.get('name')" inputType="text" label="commons.name" autofocus />
|
<FormField v-model="form.name" fieldName="name" :errorMessage="form.errors.get('name')" inputType="text" label="message.name" autofocus />
|
||||||
<div class="field is-grouped">
|
<div class="field is-grouped">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<VueButton nativeType="submit" :id="'btnGenerateToken'" :isLoading="form.isBusy" >
|
<VueButton nativeType="submit" :id="'btnGenerateToken'" :isLoading="form.isBusy" >
|
||||||
{{ $t('commons.generate') }}
|
{{ $t('message.generate') }}
|
||||||
</VueButton>
|
</VueButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<VueButton @click="cancelPATcreation" nativeType="button" id="btnCancel" :color="'is-text'">
|
<VueButton @click="cancelPATcreation" nativeType="button" id="btnCancel" :color="'is-text'">
|
||||||
{{ $t('commons.cancel') }}
|
{{ $t('message.cancel') }}
|
||||||
</VueButton>
|
</VueButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||||
import { timezones } from './timezones'
|
import { timezones } from './timezones'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const $2fauth = inject('2fauth')
|
const $2fauth = inject('2fauth')
|
||||||
const user = useUserStore()
|
const user = useUserStore()
|
||||||
const groups = useGroups()
|
const groups = useGroups()
|
||||||
@ -15,13 +17,13 @@
|
|||||||
const returnTo = useStorage($2fauth.prefix + 'returnTo', 'accounts')
|
const returnTo = useStorage($2fauth.prefix + 'returnTo', 'accounts')
|
||||||
|
|
||||||
const layouts = [
|
const layouts = [
|
||||||
{ text: 'settings.forms.grid', value: 'grid', icon: 'Grid3X3' },
|
{ text: 'message.settings.forms.grid', value: 'grid', icon: 'Grid3X3' },
|
||||||
{ text: 'settings.forms.list', value: 'list', icon: 'List' },
|
{ text: 'message.settings.forms.list', value: 'list', icon: 'List' },
|
||||||
]
|
]
|
||||||
const themes = [
|
const themes = [
|
||||||
{ text: 'settings.forms.light', value: 'light', icon: 'Sun' },
|
{ text: 'message.settings.forms.light', value: 'light', icon: 'Sun' },
|
||||||
{ text: 'settings.forms.dark', value: 'dark', icon: 'Moon' },
|
{ text: 'message.settings.forms.dark', value: 'dark', icon: 'Moon' },
|
||||||
{ text: 'settings.forms.automatic', value: 'system', icon: 'MonitorCheck' },
|
{ text: 'message.settings.forms.automatic', value: 'system', icon: 'MonitorCheck' },
|
||||||
]
|
]
|
||||||
const iconCollections = [
|
const iconCollections = [
|
||||||
{ text: 'selfh.st', value: 'selfh', url: 'https://selfh.st/icons/', defaultVariant: 'regular' },
|
{ text: 'selfh.st', value: 'selfh', url: 'https://selfh.st/icons/', defaultVariant: 'regular' },
|
||||||
@ -30,64 +32,64 @@
|
|||||||
]
|
]
|
||||||
const iconCollectionVariants = {
|
const iconCollectionVariants = {
|
||||||
selfh: [
|
selfh: [
|
||||||
{ text: 'commons.regular', value: 'regular' },
|
{ text: 'message.regular', value: 'regular' },
|
||||||
{ text: 'settings.forms.light', value: 'light' },
|
{ text: 'message.settings.forms.light', value: 'light' },
|
||||||
{ text: 'settings.forms.dark', value: 'dark' },
|
{ text: 'message.settings.forms.dark', value: 'dark' },
|
||||||
],
|
],
|
||||||
dashboardicons: [
|
dashboardicons: [
|
||||||
{ text: 'commons.regular', value: 'regular' },
|
{ text: 'message.regular', value: 'regular' },
|
||||||
{ text: 'settings.forms.light', value: 'light' },
|
{ text: 'message.settings.forms.light', value: 'light' },
|
||||||
{ text: 'settings.forms.dark', value: 'dark' },
|
{ text: 'message.settings.forms.dark', value: 'dark' },
|
||||||
],
|
],
|
||||||
tfa: [
|
tfa: [
|
||||||
{ text: 'commons.regular', value: 'regular' },
|
{ text: 'message.regular', value: 'regular' },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
const passwordFormats = [
|
const passwordFormats = [
|
||||||
{ text: '12 34 56', value: 2, legend: 'settings.forms.pair', title: 'settings.forms.pair_legend' },
|
{ text: '12 34 56', value: 2, legend: 'message.settings.forms.pair', title: 'message.settings.forms.pair_legend' },
|
||||||
{ text: '123 456', value: 3, legend: 'settings.forms.trio', title: 'settings.forms.trio_legend' },
|
{ text: '123 456', value: 3, legend: 'message.settings.forms.trio', title: 'message.settings.forms.trio_legend' },
|
||||||
{ text: '1234 5678', value: 0.5, legend: 'settings.forms.half', title: 'settings.forms.half_legend' },
|
{ text: '1234 5678', value: 0.5, legend: 'message.settings.forms.half', title: 'message.settings.forms.half_legend' },
|
||||||
]
|
]
|
||||||
const kickUserAfters = [
|
const kickUserAfters = [
|
||||||
{ text: 'settings.forms.never', value: 0 },
|
{ text: 'message.settings.forms.never', value: 0 },
|
||||||
{ text: 'settings.forms.on_otp_copy', value: -1 },
|
{ text: 'message.settings.forms.on_otp_copy', value: -1 },
|
||||||
{ text: 'settings.forms.1_minutes', value: 1 },
|
{ text: 'message.settings.forms.1_minutes', value: 1 },
|
||||||
{ text: 'settings.forms.5_minutes', value: 5 },
|
{ text: 'message.settings.forms.5_minutes', value: 5 },
|
||||||
{ text: 'settings.forms.10_minutes', value: 10 },
|
{ text: 'message.settings.forms.10_minutes', value: 10 },
|
||||||
{ text: 'settings.forms.15_minutes', value: 15 },
|
{ text: 'message.settings.forms.15_minutes', value: 15 },
|
||||||
{ text: 'settings.forms.30_minutes', value: 30 },
|
{ text: 'message.settings.forms.30_minutes', value: 30 },
|
||||||
{ text: 'settings.forms.1_hour', value: 60 },
|
{ text: 'message.settings.forms.1_hour', value: 60 },
|
||||||
{ text: 'settings.forms.1_day', value: 1440 },
|
{ text: 'message.settings.forms.1_day', value: 1440 },
|
||||||
]
|
]
|
||||||
const autoCloseTimeout = [
|
const autoCloseTimeout = [
|
||||||
{ text: 'settings.forms.never', value: 0 },
|
{ text: 'message.settings.forms.never', value: 0 },
|
||||||
{ text: 'settings.forms.1_minutes', value: 1 },
|
{ text: 'message.settings.forms.1_minutes', value: 1 },
|
||||||
{ text: 'settings.forms.2_minutes', value: 2 },
|
{ text: 'message.settings.forms.2_minutes', value: 2 },
|
||||||
{ text: 'settings.forms.5_minutes', value: 5 },
|
{ text: 'message.settings.forms.5_minutes', value: 5 },
|
||||||
]
|
]
|
||||||
const groupsList = ref([
|
const groupsList = ref([
|
||||||
{ text: 'groups.no_group', value: 0 },
|
{ text: 'message.groups.no_group', value: 0 },
|
||||||
{ text: 'groups.active_group', value: -1 },
|
{ text: 'message.groups.active_group', value: -1 },
|
||||||
])
|
])
|
||||||
const captureModes = [
|
const captureModes = [
|
||||||
{ text: 'settings.forms.livescan', value: 'livescan' },
|
{ text: 'message.settings.forms.livescan', value: 'livescan' },
|
||||||
{ text: 'settings.forms.upload', value: 'upload' },
|
{ text: 'message.settings.forms.upload', value: 'upload' },
|
||||||
{ text: 'settings.forms.advanced_form', value: 'advancedForm' },
|
{ text: 'message.settings.forms.advanced_form', value: 'advancedForm' },
|
||||||
]
|
]
|
||||||
const getOtpTriggers = [
|
const getOtpTriggers = [
|
||||||
{ text: 'settings.forms.otp_generation_on_request', value: true, legend: 'settings.forms.otp_generation_on_request_legend', title: 'settings.forms.otp_generation_on_request_title' },
|
{ text: 'message.settings.forms.otp_generation_on_request', value: true, legend: 'message.settings.forms.otp_generation_on_request_legend', title: 'message.settings.forms.otp_generation_on_request_title' },
|
||||||
{ text: 'settings.forms.otp_generation_on_home', value: false, legend: 'settings.forms.otp_generation_on_home_legend', title: 'settings.forms.otp_generation_on_home_title' },
|
{ text: 'message.settings.forms.otp_generation_on_home', value: false, legend: 'message.settings.forms.otp_generation_on_home_legend', title: 'message.settings.forms.otp_generation_on_home_title' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const langs = computed(() => {
|
const langs = computed(() => {
|
||||||
let locales = [{
|
let locales = [{
|
||||||
text: 'languages.browser_preference',
|
text: 'message.browser_preference',
|
||||||
value: 'browser'
|
value: 'browser'
|
||||||
}];
|
}];
|
||||||
|
|
||||||
for (const locale of $2fauth.langs) {
|
for (const locale of $2fauth.langs) {
|
||||||
locales.push({
|
locales.push({
|
||||||
text: 'languages.' + locale,
|
text: 'lang.' + locale,
|
||||||
value: locale
|
value: locale
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -122,9 +124,9 @@
|
|||||||
*/
|
*/
|
||||||
function savePreference(preference, value) {
|
function savePreference(preference, value) {
|
||||||
userService.updatePreference(preference, value).then(response => {
|
userService.updatePreference(preference, value).then(response => {
|
||||||
useNotifyStore().success({ type: 'is-success', text: trans('settings.forms.setting_saved') })
|
useNotifyStore().success({ type: 'is-success', text: t('message.settings.forms.setting_saved') })
|
||||||
|
|
||||||
if(preference === 'lang' && getActiveLanguage() !== value) {
|
if(preference === 'lang') {
|
||||||
user.applyLanguage()
|
user.applyLanguage()
|
||||||
}
|
}
|
||||||
else if(preference === 'theme') {
|
else if(preference === 'theme') {
|
||||||
@ -172,90 +174,90 @@
|
|||||||
<!-- <input type="hidden" name="isReady" id="isReady" :value="isReady" /> -->
|
<!-- <input type="hidden" name="isReady" id="isReady" :value="isReady" /> -->
|
||||||
<!-- user preferences -->
|
<!-- user preferences -->
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h4 class="title is-4 has-text-grey-light">{{ $t('settings.general') }}</h4>
|
<h4 class="title is-4 has-text-grey-light">{{ $t('message.settings.general') }}</h4>
|
||||||
<div v-if="appSettings.lockedPreferences.length > 0" class="notification is-warning">
|
<div v-if="appSettings.lockedPreferences.length > 0" class="notification is-warning">
|
||||||
{{ $t('settings.settings_managed_by_administrator') }}
|
{{ $t('message.settings.settings_managed_by_administrator') }}
|
||||||
</div>
|
</div>
|
||||||
<!-- Language -->
|
<!-- Language -->
|
||||||
<FormSelect v-model="user.preferences.lang" @update:model-value="val => savePreference('lang', val)" :options="langs" fieldName="lang" :isLocked="appSettings.lockedPreferences.includes('lang')" label="settings.forms.language.label" help="settings.forms.language.help" />
|
<FormSelect v-model="user.preferences.lang" @update:model-value="val => savePreference('lang', val)" :options="langs" fieldName="lang" :isLocked="appSettings.lockedPreferences.includes('lang')" label="message.settings.forms.language.label" help="message.settings.forms.language.help" />
|
||||||
<div class="field help">
|
<div class="field help">
|
||||||
{{ $t('settings.forms.some_translation_are_missing') }}
|
{{ $t('message.settings.forms.some_translation_are_missing') }}
|
||||||
<a class="ml-2" href="https://crowdin.com/project/2fauth">
|
<a class="ml-2" href="https://crowdin.com/project/2fauth">
|
||||||
{{ $t('settings.forms.help_translate_2fauth') }}
|
{{ $t('message.settings.forms.help_translate_2fauth') }}
|
||||||
<FontAwesomeIcon :icon="['fas', 'external-link-alt']" />
|
<FontAwesomeIcon :icon="['fas', 'external-link-alt']" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- timezone -->
|
<!-- timezone -->
|
||||||
<FormSelect v-model="user.preferences.timezone" @update:model-value="val => savePreference('timezone', val)" :options="timezones" fieldName="timezone" :isLocked="appSettings.lockedPreferences.includes('timezone')" label="settings.forms.timezone.label" help="settings.forms.timezone.help" />
|
<FormSelect v-model="user.preferences.timezone" @update:model-value="val => savePreference('timezone', val)" :options="timezones" fieldName="timezone" :isLocked="appSettings.lockedPreferences.includes('timezone')" label="message.settings.forms.timezone.label" help="message.settings.forms.timezone.help" />
|
||||||
<!-- display mode -->
|
<!-- display mode -->
|
||||||
<FormToggle v-model="user.preferences.displayMode" @update:model-value="val => savePreference('displayMode', val)" :choices="layouts" fieldName="displayMode" :isLocked="appSettings.lockedPreferences.includes('displayMode')" label="settings.forms.display_mode.label" help="settings.forms.display_mode.help" />
|
<FormToggle v-model="user.preferences.displayMode" @update:model-value="val => savePreference('displayMode', val)" :choices="layouts" fieldName="displayMode" :isLocked="appSettings.lockedPreferences.includes('displayMode')" label="message.settings.forms.display_mode.label" help="message.settings.forms.display_mode.help" />
|
||||||
<!-- theme -->
|
<!-- theme -->
|
||||||
<FormToggle v-model="user.preferences.theme" @update:model-value="val => savePreference('theme', val)" :choices="themes" fieldName="theme" :isLocked="appSettings.lockedPreferences.includes('theme')" label="settings.forms.theme.label" help="settings.forms.theme.help" />
|
<FormToggle v-model="user.preferences.theme" @update:model-value="val => savePreference('theme', val)" :choices="themes" fieldName="theme" :isLocked="appSettings.lockedPreferences.includes('theme')" label="message.settings.forms.theme.label" help="message.settings.forms.theme.help" />
|
||||||
<!-- show icon -->
|
<!-- show icon -->
|
||||||
<FormCheckbox v-model="user.preferences.showAccountsIcons" @update:model-value="val => savePreference('showAccountsIcons', val)" fieldName="showAccountsIcons" :isLocked="appSettings.lockedPreferences.includes('showAccountsIcons')" label="settings.forms.show_accounts_icons.label" help="settings.forms.show_accounts_icons.help" />
|
<FormCheckbox v-model="user.preferences.showAccountsIcons" @update:model-value="val => savePreference('showAccountsIcons', val)" fieldName="showAccountsIcons" :isLocked="appSettings.lockedPreferences.includes('showAccountsIcons')" label="message.settings.forms.show_accounts_icons.label" help="message.settings.forms.show_accounts_icons.help" />
|
||||||
<!-- Official icons -->
|
<!-- Official icons -->
|
||||||
<FormCheckbox v-model="user.preferences.getOfficialIcons" @update:model-value="val => savePreference('getOfficialIcons', val)" fieldName="getOfficialIcons" :isLocked="appSettings.lockedPreferences.includes('getOfficialIcons')" label="settings.forms.get_official_icons.label" help="settings.forms.get_official_icons.help" />
|
<FormCheckbox v-model="user.preferences.getOfficialIcons" @update:model-value="val => savePreference('getOfficialIcons', val)" fieldName="getOfficialIcons" :isLocked="appSettings.lockedPreferences.includes('getOfficialIcons')" label="message.settings.forms.get_official_icons.label" help="message.settings.forms.get_official_icons.help" />
|
||||||
<!-- icon collections -->
|
<!-- icon collections -->
|
||||||
<FormSelect v-model="user.preferences.iconCollection" @update:model-value="val => saveIconCollection(val)" :options="iconCollections" fieldName="iconCollection" :isLocked="appSettings.lockedPreferences.includes('iconCollection')" :isDisabled="!user.preferences.getOfficialIcons" label="settings.forms.icon_collection.label" help="settings.forms.icon_collection.help" :isIndented="true">
|
<FormSelect v-model="user.preferences.iconCollection" @update:model-value="val => saveIconCollection(val)" :options="iconCollections" fieldName="iconCollection" :isLocked="appSettings.lockedPreferences.includes('iconCollection')" :isDisabled="!user.preferences.getOfficialIcons" label="message.settings.forms.icon_collection.label" help="message.settings.forms.icon_collection.help" :isIndented="true">
|
||||||
<a class="button is-ghost" :href="iconCollectionUrl" target="_blank" :title="$t('commons.visit_x', { website: iconCollectionDomain})">
|
<a class="button is-ghost" :href="iconCollectionUrl" target="_blank" :title="$t('message.visit_x', { website: iconCollectionDomain})">
|
||||||
<FontAwesomeIcon :icon="['fas', 'external-link-alt']" />
|
<FontAwesomeIcon :icon="['fas', 'external-link-alt']" />
|
||||||
</a>
|
</a>
|
||||||
</FormSelect>
|
</FormSelect>
|
||||||
<!-- icon variant -->
|
<!-- icon variant -->
|
||||||
<FormSelect v-model="user.preferences.iconVariant" @update:model-value="val => savePreference('iconVariant', val)" :options="iconCollectionVariants[user.preferences.iconCollection]" fieldName="iconVariant" :isLocked="appSettings.lockedPreferences.includes('iconVariant')" :isDisabled="!user.preferences.getOfficialIcons" label="settings.forms.icon_variant.label" help="settings.forms.icon_variant.help" :isIndented="true" />
|
<FormSelect v-model="user.preferences.iconVariant" @update:model-value="val => savePreference('iconVariant', val)" :options="iconCollectionVariants[user.preferences.iconCollection]" fieldName="iconVariant" :isLocked="appSettings.lockedPreferences.includes('iconVariant')" :isDisabled="!user.preferences.getOfficialIcons" label="message.settings.forms.icon_variant.label" help="message.settings.forms.icon_variant.help" :isIndented="true" />
|
||||||
<!-- icon variant strict fetch -->
|
<!-- icon variant strict fetch -->
|
||||||
<FormCheckbox v-model="user.preferences.iconVariantStrictFetch" @update:model-value="val => savePreference('iconVariantStrictFetch', val)" fieldName="iconVariantStrictFetch" :isLocked="appSettings.lockedPreferences.includes('iconVariantStrictFetch')" :isDisabled="user.preferences.iconVariant == 'regular'" label="settings.forms.icon_variant_strict_fetch.label" help="settings.forms.icon_variant_strict_fetch.help" :isIndented="true" />
|
<FormCheckbox v-model="user.preferences.iconVariantStrictFetch" @update:model-value="val => savePreference('iconVariantStrictFetch', val)" fieldName="iconVariantStrictFetch" :isLocked="appSettings.lockedPreferences.includes('iconVariantStrictFetch')" :isDisabled="user.preferences.iconVariant == 'regular'" label="message.settings.forms.icon_variant_strict_fetch.label" help="message.settings.forms.icon_variant_strict_fetch.help" :isIndented="true" />
|
||||||
<!-- password format -->
|
<!-- password format -->
|
||||||
<FormCheckbox v-model="user.preferences.formatPassword" @update:model-value="val => savePreference('formatPassword', val)" fieldName="formatPassword" :isLocked="appSettings.lockedPreferences.includes('formatPassword')" label="settings.forms.password_format.label" help="settings.forms.password_format.help" />
|
<FormCheckbox v-model="user.preferences.formatPassword" @update:model-value="val => savePreference('formatPassword', val)" fieldName="formatPassword" :isLocked="appSettings.lockedPreferences.includes('formatPassword')" label="message.settings.forms.password_format.label" help="message.settings.forms.password_format.help" />
|
||||||
<FormToggle v-model="user.preferences.formatPasswordBy" @update:model-value="val => savePreference('formatPasswordBy', val)" :choices="passwordFormats" fieldName="formatPasswordBy" :isLocked="appSettings.lockedPreferences.includes('formatPasswordBy')" :isDisabled="!user.preferences.formatPassword" />
|
<FormToggle v-model="user.preferences.formatPasswordBy" @update:model-value="val => savePreference('formatPasswordBy', val)" :choices="passwordFormats" fieldName="formatPasswordBy" :isLocked="appSettings.lockedPreferences.includes('formatPasswordBy')" :isDisabled="!user.preferences.formatPassword" />
|
||||||
<!-- clear search on copy -->
|
<!-- clear search on copy -->
|
||||||
<FormCheckbox v-model="user.preferences.clearSearchOnCopy" @update:model-value="val => savePreference('clearSearchOnCopy', val)" fieldName="clearSearchOnCopy" :isLocked="appSettings.lockedPreferences.includes('clearSearchOnCopy')" label="settings.forms.clear_search_on_copy.label" help="settings.forms.clear_search_on_copy.help" />
|
<FormCheckbox v-model="user.preferences.clearSearchOnCopy" @update:model-value="val => savePreference('clearSearchOnCopy', val)" fieldName="clearSearchOnCopy" :isLocked="appSettings.lockedPreferences.includes('clearSearchOnCopy')" label="message.settings.forms.clear_search_on_copy.label" help="message.settings.forms.clear_search_on_copy.help" />
|
||||||
<!-- sort case sensitive -->
|
<!-- sort case sensitive -->
|
||||||
<FormCheckbox v-model="user.preferences.sortCaseSensitive" @update:model-value="val => savePreference('sortCaseSensitive', val)" fieldName="sortCaseSensitive" :isLocked="appSettings.lockedPreferences.includes('sortCaseSensitive')" label="settings.forms.sort_case_sensitive.label" help="settings.forms.sort_case_sensitive.help" />
|
<FormCheckbox v-model="user.preferences.sortCaseSensitive" @update:model-value="val => savePreference('sortCaseSensitive', val)" fieldName="sortCaseSensitive" :isLocked="appSettings.lockedPreferences.includes('sortCaseSensitive')" label="message.settings.forms.sort_case_sensitive.label" help="message.settings.forms.sort_case_sensitive.help" />
|
||||||
<!-- show email in footer -->
|
<!-- show email in footer -->
|
||||||
<FormCheckbox v-model="user.preferences.showEmailInFooter" @update:model-value="val => savePreference('showEmailInFooter', val)" fieldName="showEmailInFooter" :isLocked="appSettings.lockedPreferences.includes('showEmailInFooter')" label="settings.forms.show_email_in_footer.label" help="settings.forms.show_email_in_footer.help" />
|
<FormCheckbox v-model="user.preferences.showEmailInFooter" @update:model-value="val => savePreference('showEmailInFooter', val)" fieldName="showEmailInFooter" :isLocked="appSettings.lockedPreferences.includes('showEmailInFooter')" label="message.settings.forms.show_email_in_footer.label" help="message.settings.forms.show_email_in_footer.help" />
|
||||||
|
|
||||||
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('groups.groups') }}</h4>
|
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('message.groups.groups') }}</h4>
|
||||||
<!-- default group -->
|
<!-- default group -->
|
||||||
<FormSelect v-model="user.preferences.defaultGroup" @update:model-value="val => savePreference('defaultGroup', val)" :options="groupsList" fieldName="defaultGroup" label="settings.forms.default_group.label" help="settings.forms.default_group.help" />
|
<FormSelect v-model="user.preferences.defaultGroup" @update:model-value="val => savePreference('defaultGroup', val)" :options="groupsList" fieldName="defaultGroup" label="message.settings.forms.default_group.label" help="message.settings.forms.default_group.help" />
|
||||||
<!-- retain active group -->
|
<!-- retain active group -->
|
||||||
<FormCheckbox v-model="user.preferences.rememberActiveGroup" @update:model-value="val => savePreference('rememberActiveGroup', val)" fieldName="rememberActiveGroup" :isLocked="appSettings.lockedPreferences.includes('rememberActiveGroup')" label="settings.forms.remember_active_group.label" help="settings.forms.remember_active_group.help" />
|
<FormCheckbox v-model="user.preferences.rememberActiveGroup" @update:model-value="val => savePreference('rememberActiveGroup', val)" fieldName="rememberActiveGroup" :isLocked="appSettings.lockedPreferences.includes('rememberActiveGroup')" label="message.settings.forms.remember_active_group.label" help="message.settings.forms.remember_active_group.help" />
|
||||||
<!-- always return to default group after copying -->
|
<!-- always return to default group after copying -->
|
||||||
<FormCheckbox v-model="user.preferences.viewDefaultGroupOnCopy" @update:model-value="val => savePreference('viewDefaultGroupOnCopy', val)" fieldName="viewDefaultGroupOnCopy" :isLocked="appSettings.lockedPreferences.includes('viewDefaultGroupOnCopy')" label="settings.forms.view_default_group_on_copy.label" help="settings.forms.view_default_group_on_copy.help" />
|
<FormCheckbox v-model="user.preferences.viewDefaultGroupOnCopy" @update:model-value="val => savePreference('viewDefaultGroupOnCopy', val)" fieldName="viewDefaultGroupOnCopy" :isLocked="appSettings.lockedPreferences.includes('viewDefaultGroupOnCopy')" label="message.settings.forms.view_default_group_on_copy.label" help="message.settings.forms.view_default_group_on_copy.help" />
|
||||||
|
|
||||||
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('settings.security') }}</h4>
|
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('message.settings.security') }}</h4>
|
||||||
<!-- auto lock -->
|
<!-- auto lock -->
|
||||||
<FormSelect v-model="user.preferences.kickUserAfter" @update:model-value="val => savePreference('kickUserAfter', val)" :options="kickUserAfters" fieldName="kickUserAfter" :isLocked="appSettings.lockedPreferences.includes('kickUserAfter')" label="settings.forms.auto_lock.label" help="settings.forms.auto_lock.help" />
|
<FormSelect v-model="user.preferences.kickUserAfter" @update:model-value="val => savePreference('kickUserAfter', val)" :options="kickUserAfters" fieldName="kickUserAfter" :isLocked="appSettings.lockedPreferences.includes('kickUserAfter')" label="message.settings.forms.auto_lock.label" help="message.settings.forms.auto_lock.help" />
|
||||||
<!-- get OTP on request -->
|
<!-- get OTP on request -->
|
||||||
<FormToggle v-model="user.preferences.getOtpOnRequest" @update:model-value="val => savePreference('getOtpOnRequest', val)" :choices="getOtpTriggers" fieldName="getOtpOnRequest" :isLocked="appSettings.lockedPreferences.includes('getOtpOnRequest')" label="settings.forms.otp_generation.label" help="settings.forms.otp_generation.help"/>
|
<FormToggle v-model="user.preferences.getOtpOnRequest" @update:model-value="val => savePreference('getOtpOnRequest', val)" :choices="getOtpTriggers" fieldName="getOtpOnRequest" :isLocked="appSettings.lockedPreferences.includes('getOtpOnRequest')" label="message.settings.forms.otp_generation.label" help="message.settings.forms.otp_generation.help"/>
|
||||||
<!-- close otp on copy -->
|
<!-- close otp on copy -->
|
||||||
<FormCheckbox v-model="user.preferences.closeOtpOnCopy" @update:model-value="val => savePreference('closeOtpOnCopy', val)" fieldName="closeOtpOnCopy" :isLocked="appSettings.lockedPreferences.includes('closeOtpOnCopy')" :isDisabled="!user.preferences.getOtpOnRequest" label="settings.forms.close_otp_on_copy.label" help="settings.forms.close_otp_on_copy.help" :isIndented="true" />
|
<FormCheckbox v-model="user.preferences.closeOtpOnCopy" @update:model-value="val => savePreference('closeOtpOnCopy', val)" fieldName="closeOtpOnCopy" :isLocked="appSettings.lockedPreferences.includes('closeOtpOnCopy')" :isDisabled="!user.preferences.getOtpOnRequest" label="message.settings.forms.close_otp_on_copy.label" help="message.settings.forms.close_otp_on_copy.help" :isIndented="true" />
|
||||||
<!-- auto-close timeout -->
|
<!-- auto-close timeout -->
|
||||||
<FormSelect v-model="user.preferences.autoCloseTimeout" @update:model-value="val => savePreference('autoCloseTimeout', val)" :options="autoCloseTimeout" fieldName="autoCloseTimeout" :isLocked="appSettings.lockedPreferences.includes('autoCloseTimeout')" :isDisabled="!user.preferences.getOtpOnRequest" label="settings.forms.auto_close_timeout.label" help="settings.forms.auto_close_timeout.help" :isIndented="true" />
|
<FormSelect v-model="user.preferences.autoCloseTimeout" @update:model-value="val => savePreference('autoCloseTimeout', val)" :options="autoCloseTimeout" fieldName="autoCloseTimeout" :isLocked="appSettings.lockedPreferences.includes('autoCloseTimeout')" :isDisabled="!user.preferences.getOtpOnRequest" label="message.settings.forms.auto_close_timeout.label" help="message.settings.forms.auto_close_timeout.help" :isIndented="true" />
|
||||||
<!-- clear search on copy -->
|
<!-- clear search on copy -->
|
||||||
<FormCheckbox v-model="user.preferences.copyOtpOnDisplay" @update:model-value="val => savePreference('copyOtpOnDisplay', val)" fieldName="copyOtpOnDisplay" :isLocked="appSettings.lockedPreferences.includes('copyOtpOnDisplay')" :isDisabled="!user.preferences.getOtpOnRequest" label="settings.forms.copy_otp_on_display.label" help="settings.forms.copy_otp_on_display.help" :isIndented="true" />
|
<FormCheckbox v-model="user.preferences.copyOtpOnDisplay" @update:model-value="val => savePreference('copyOtpOnDisplay', val)" fieldName="copyOtpOnDisplay" :isLocked="appSettings.lockedPreferences.includes('copyOtpOnDisplay')" :isDisabled="!user.preferences.getOtpOnRequest" label="message.settings.forms.copy_otp_on_display.label" help="message.settings.forms.copy_otp_on_display.help" :isIndented="true" />
|
||||||
<!-- otp as dot -->
|
<!-- otp as dot -->
|
||||||
<FormCheckbox v-model="user.preferences.showOtpAsDot" @update:model-value="val => savePreference('showOtpAsDot', val)" fieldName="showOtpAsDot" :isLocked="appSettings.lockedPreferences.includes('showOtpAsDot')" label="settings.forms.show_otp_as_dot.label" help="settings.forms.show_otp_as_dot.help" />
|
<FormCheckbox v-model="user.preferences.showOtpAsDot" @update:model-value="val => savePreference('showOtpAsDot', val)" fieldName="showOtpAsDot" :isLocked="appSettings.lockedPreferences.includes('showOtpAsDot')" label="message.settings.forms.show_otp_as_dot.label" help="message.settings.forms.show_otp_as_dot.help" />
|
||||||
<!-- reveal dotted OTPs -->
|
<!-- reveal dotted OTPs -->
|
||||||
<FormCheckbox v-model="user.preferences.revealDottedOTP" @update:model-value="val => savePreference('revealDottedOTP', val)" fieldName="revealDottedOTP" :isLocked="appSettings.lockedPreferences.includes('revealDottedOTP')" :isDisabled="!user.preferences.showOtpAsDot" label="settings.forms.reveal_dotted_otp.label" help="settings.forms.reveal_dotted_otp.help" :isIndented="true" />
|
<FormCheckbox v-model="user.preferences.revealDottedOTP" @update:model-value="val => savePreference('revealDottedOTP', val)" fieldName="revealDottedOTP" :isLocked="appSettings.lockedPreferences.includes('revealDottedOTP')" :isDisabled="!user.preferences.showOtpAsDot" label="message.settings.forms.reveal_dotted_otp.label" help="message.settings.forms.reveal_dotted_otp.help" :isIndented="true" />
|
||||||
<!-- show next OTP -->
|
<!-- show next OTP -->
|
||||||
<FormCheckbox v-model="user.preferences.showNextOtp" @update:model-value="val => savePreference('showNextOtp', val)" fieldName="showNextOtp" :isLocked="appSettings.lockedPreferences.includes('showNextOtp')" label="settings.forms.show_next_otp.label" help="settings.forms.show_next_otp.help" />
|
<FormCheckbox v-model="user.preferences.showNextOtp" @update:model-value="val => savePreference('showNextOtp', val)" fieldName="showNextOtp" :isLocked="appSettings.lockedPreferences.includes('showNextOtp')" label="message.settings.forms.show_next_otp.label" help="message.settings.forms.show_next_otp.help" />
|
||||||
|
|
||||||
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('settings.notifications') }}</h4>
|
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('message.settings.notifications') }}</h4>
|
||||||
<!-- on new device -->
|
<!-- on new device -->
|
||||||
<FormCheckbox v-model="user.preferences.notifyOnNewAuthDevice" @update:model-value="val => savePreference('notifyOnNewAuthDevice', val)" fieldName="notifyOnNewAuthDevice" :isLocked="appSettings.lockedPreferences.includes('notifyOnNewAuthDevice')" label="settings.forms.notify_on_new_auth_device.label" help="settings.forms.notify_on_new_auth_device.help" />
|
<FormCheckbox v-model="user.preferences.notifyOnNewAuthDevice" @update:model-value="val => savePreference('notifyOnNewAuthDevice', val)" fieldName="notifyOnNewAuthDevice" :isLocked="appSettings.lockedPreferences.includes('notifyOnNewAuthDevice')" label="message.settings.forms.notify_on_new_auth_device.label" help="message.settings.forms.notify_on_new_auth_device.help" />
|
||||||
<!-- on failed login -->
|
<!-- on failed login -->
|
||||||
<FormCheckbox v-model="user.preferences.notifyOnFailedLogin" @update:model-value="val => savePreference('notifyOnFailedLogin', val)" fieldName="notifyOnFailedLogin" :isLocked="appSettings.lockedPreferences.includes('notifyOnFailedLogin')" label="settings.forms.notify_on_failed_login.label" help="settings.forms.notify_on_failed_login.help" />
|
<FormCheckbox v-model="user.preferences.notifyOnFailedLogin" @update:model-value="val => savePreference('notifyOnFailedLogin', val)" fieldName="notifyOnFailedLogin" :isLocked="appSettings.lockedPreferences.includes('notifyOnFailedLogin')" label="message.settings.forms.notify_on_failed_login.label" help="message.settings.forms.notify_on_failed_login.help" />
|
||||||
|
|
||||||
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('settings.data_input') }}</h4>
|
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('message.settings.data_input') }}</h4>
|
||||||
<!-- auto-save QrCoded account -->
|
<!-- auto-save QrCoded account -->
|
||||||
<FormCheckbox v-model="user.preferences.AutoSaveQrcodedAccount" @update:model-value="val => savePreference('AutoSaveQrcodedAccount', val)" fieldName="AutoSaveQrcodedAccount" :isLocked="appSettings.lockedPreferences.includes('AutoSaveQrcodedAccount')" label="settings.forms.auto_save_qrcoded_account.label" help="settings.forms.auto_save_qrcoded_account.help" />
|
<FormCheckbox v-model="user.preferences.AutoSaveQrcodedAccount" @update:model-value="val => savePreference('AutoSaveQrcodedAccount', val)" fieldName="AutoSaveQrcodedAccount" :isLocked="appSettings.lockedPreferences.includes('AutoSaveQrcodedAccount')" label="message.settings.forms.auto_save_qrcoded_account.label" help="message.settings.forms.auto_save_qrcoded_account.help" />
|
||||||
<!-- basic qrcode -->
|
<!-- basic qrcode -->
|
||||||
<FormCheckbox v-model="user.preferences.useBasicQrcodeReader" @update:model-value="val => savePreference('useBasicQrcodeReader', val)" fieldName="useBasicQrcodeReader" :isLocked="appSettings.lockedPreferences.includes('useBasicQrcodeReader')" label="settings.forms.use_basic_qrcode_reader.label" help="settings.forms.use_basic_qrcode_reader.help" />
|
<FormCheckbox v-model="user.preferences.useBasicQrcodeReader" @update:model-value="val => savePreference('useBasicQrcodeReader', val)" fieldName="useBasicQrcodeReader" :isLocked="appSettings.lockedPreferences.includes('useBasicQrcodeReader')" label="message.settings.forms.use_basic_qrcode_reader.label" help="message.settings.forms.use_basic_qrcode_reader.help" />
|
||||||
<!-- direct capture -->
|
<!-- direct capture -->
|
||||||
<FormCheckbox v-model="user.preferences.useDirectCapture" @update:model-value="val => savePreference('useDirectCapture', val)" fieldName="useDirectCapture" :isLocked="appSettings.lockedPreferences.includes('useDirectCapture')" label="settings.forms.useDirectCapture.label" help="settings.forms.useDirectCapture.help" />
|
<FormCheckbox v-model="user.preferences.useDirectCapture" @update:model-value="val => savePreference('useDirectCapture', val)" fieldName="useDirectCapture" :isLocked="appSettings.lockedPreferences.includes('useDirectCapture')" label="message.settings.forms.useDirectCapture.label" help="message.settings.forms.useDirectCapture.help" />
|
||||||
<!-- default capture mode -->
|
<!-- default capture mode -->
|
||||||
<FormSelect v-model="user.preferences.defaultCaptureMode" @update:model-value="val => savePreference('defaultCaptureMode', val)" :options="captureModes" fieldName="defaultCaptureMode" :isLocked="appSettings.lockedPreferences.includes('defaultCaptureMode')" :isDisabled="!user.preferences.useDirectCapture" label="settings.forms.defaultCaptureMode.label" help="settings.forms.defaultCaptureMode.help" :isIndented="true" />
|
<FormSelect v-model="user.preferences.defaultCaptureMode" @update:model-value="val => savePreference('defaultCaptureMode', val)" :options="captureModes" fieldName="defaultCaptureMode" :isLocked="appSettings.lockedPreferences.includes('defaultCaptureMode')" :isDisabled="!user.preferences.useDirectCapture" label="message.settings.forms.defaultCaptureMode.label" help="message.settings.forms.defaultCaptureMode.help" :isIndented="true" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
|
@ -7,7 +7,9 @@
|
|||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
import { UseColorMode } from '@vueuse/components'
|
import { UseColorMode } from '@vueuse/components'
|
||||||
import Spinner from '@/components/Spinner.vue'
|
import Spinner from '@/components/Spinner.vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const $2fauth = inject('2fauth')
|
const $2fauth = inject('2fauth')
|
||||||
const user = useUserStore()
|
const user = useUserStore()
|
||||||
const appSettings = useAppSettingsStore()
|
const appSettings = useAppSettingsStore()
|
||||||
@ -28,7 +30,7 @@
|
|||||||
|
|
||||||
watch(() => user.preferences.useWebauthnOnly, () => {
|
watch(() => user.preferences.useWebauthnOnly, () => {
|
||||||
userService.updatePreference('useWebauthnOnly', user.preferences.useWebauthnOnly).then(response => {
|
userService.updatePreference('useWebauthnOnly', user.preferences.useWebauthnOnly).then(response => {
|
||||||
notify.success({ text: trans('settings.forms.setting_saved') })
|
notify.success({ text: t('message.settings.forms.setting_saved') })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -37,7 +39,7 @@
|
|||||||
*/
|
*/
|
||||||
function register() {
|
function register() {
|
||||||
if (isDisabled.value == true) {
|
if (isDisabled.value == true) {
|
||||||
notify.warn({text: trans('errors.unsupported_with_reverseproxy') })
|
notify.warn({text: t('error.unsupported_with_reverseproxy') })
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,9 +49,9 @@
|
|||||||
.catch(error => {
|
.catch(error => {
|
||||||
if ('webauthn' in error) {
|
if ('webauthn' in error) {
|
||||||
if (error.name == 'is-warning') {
|
if (error.name == 'is-warning') {
|
||||||
notify.warn({ text: trans(error.message) })
|
notify.warn({ text: t(error.message) })
|
||||||
}
|
}
|
||||||
else notify.alert({ text: trans(error.message) })
|
else notify.alert({ text: t(error.message) })
|
||||||
}
|
}
|
||||||
else if( error.response?.status === 422 ) {
|
else if( error.response?.status === 422 ) {
|
||||||
notify.alert({ text: error.response.data.message })
|
notify.alert({ text: error.response.data.message })
|
||||||
@ -64,7 +66,7 @@
|
|||||||
* revoke a credential
|
* revoke a credential
|
||||||
*/
|
*/
|
||||||
function revokeCredential(credentialId) {
|
function revokeCredential(credentialId) {
|
||||||
if(confirm(trans('auth.confirm.revoke_device'))) {
|
if(confirm(t('message.auth.confirm.revoke_device'))) {
|
||||||
userService.revokeWebauthnDevice(credentialId).then(response => {
|
userService.revokeWebauthnDevice(credentialId).then(response => {
|
||||||
// Remove the revoked credential from the collection
|
// Remove the revoked credential from the collection
|
||||||
credentials.value = credentials.value.filter(a => a.id !== credentialId)
|
credentials.value = credentials.value.filter(a => a.id !== credentialId)
|
||||||
@ -75,7 +77,7 @@
|
|||||||
user.preferences.useWebauthnOnly = false
|
user.preferences.useWebauthnOnly = false
|
||||||
}
|
}
|
||||||
|
|
||||||
notify.success({ text: trans('auth.webauthn.device_revoked') })
|
notify.success({ text: t('message.auth.webauthn.device_revoked') })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,7 +86,7 @@
|
|||||||
* Always display a printable name
|
* Always display a printable name
|
||||||
*/
|
*/
|
||||||
function displayName(credential) {
|
function displayName(credential) {
|
||||||
return credential.alias ? credential.alias : trans('auth.webauthn.my_device') + ' (#' + credential.id.substring(0, 10) + ')'
|
return credential.alias ? credential.alias : t('message.auth.webauthn.my_device') + ' (#' + credential.id.substring(0, 10) + ')'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,16 +127,18 @@
|
|||||||
<div class="options-tabs">
|
<div class="options-tabs">
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<div v-if="isDisabled && user.oauth_provider" class="notification is-warning has-text-centered">
|
<div v-if="isDisabled && user.oauth_provider" class="notification is-warning has-text-centered">
|
||||||
{{ $t('auth.sso_only_x_settings_are_disabled', { auth_method: 'WebAuthn' }) }}
|
{{ $t('message.auth.sso_only_x_settings_are_disabled', { auth_method: 'WebAuthn' }) }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isDisabled && user.authenticated_by_proxy" class="notification is-warning has-text-centered" v-html="$t('auth.auth_handled_by_proxy')" />
|
<div v-if="isDisabled && user.authenticated_by_proxy" class="notification is-warning has-text-centered">
|
||||||
<h4 class="title is-4 has-text-grey-light">{{ $t('auth.webauthn.security_devices') }}</h4>
|
{{ $t('message.auth.auth_handled_by_proxy') + '<br />' + $t('message.auth.manage_auth_at_proxy_level') }}
|
||||||
|
</div>
|
||||||
|
<h4 class="title is-4 has-text-grey-light">{{ $t('message.auth.webauthn.security_devices') }}</h4>
|
||||||
<div class="is-size-7-mobile">
|
<div class="is-size-7-mobile">
|
||||||
{{ $t('auth.webauthn.security_devices_legend')}}
|
{{ $t('message.auth.webauthn.security_devices_legend')}}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<a tabindex="0" @click="register" @keyup.enter="register">
|
<a tabindex="0" @click="register" @keyup.enter="register">
|
||||||
<FontAwesomeIcon :icon="['fas', 'plus-circle']" /> {{ $t('auth.webauthn.register_a_new_device')}}
|
<FontAwesomeIcon :icon="['fas', 'plus-circle']" /> {{ $t('message.auth.webauthn.register_a_new_device')}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- credentials list -->
|
<!-- credentials list -->
|
||||||
@ -143,31 +147,31 @@
|
|||||||
{{ displayName(credential) }}
|
{{ displayName(credential) }}
|
||||||
<!-- revoke link -->
|
<!-- revoke link -->
|
||||||
<UseColorMode v-slot="{ mode }">
|
<UseColorMode v-slot="{ mode }">
|
||||||
<button type="button" class="button tag is-pulled-right" :class="mode === 'dark' ? 'is-dark':'is-white'" @click="revokeCredential(credential.id)" :title="$t('settings.revoke')">
|
<button type="button" class="button tag is-pulled-right" :class="mode === 'dark' ? 'is-dark':'is-white'" @click="revokeCredential(credential.id)" :title="$t('message.settings.revoke')">
|
||||||
{{ $t('settings.revoke') }}
|
{{ $t('message.settings.revoke') }}
|
||||||
</button>
|
</button>
|
||||||
</UseColorMode>
|
</UseColorMode>
|
||||||
<!-- edit link -->
|
<!-- edit link -->
|
||||||
<!-- <RouterLink :to="{ name: '' }" class="has-text-grey pl-1" :title="$t('commons.rename')">
|
<!-- <RouterLink :to="{ name: '' }" class="has-text-grey pl-1" :title="$t('message.rename')">
|
||||||
<FontAwesomeIcon :icon="['fas', 'pen-square']" />
|
<FontAwesomeIcon :icon="['fas', 'pen-square']" />
|
||||||
</RouterLink> -->
|
</RouterLink> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 is-size-7 is-pulled-right">
|
<div class="mt-2 is-size-7 is-pulled-right">
|
||||||
{{ $t('auth.webauthn.revoking_a_device_is_permanent')}}
|
{{ $t('message.auth.webauthn.revoking_a_device_is_permanent')}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Spinner :isVisible="isFetching && credentials.length === 0" />
|
<Spinner :isVisible="isFetching && credentials.length === 0" />
|
||||||
<h4 class="title is-4 pt-6 has-text-grey-light">{{ $t('auth.webauthn.options') }}</h4>
|
<h4 class="title is-4 pt-6 has-text-grey-light">{{ $t('message.auth.webauthn.options') }}</h4>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
{{ $t('auth.webauthn.need_a_security_device_to_enable_options')}}
|
{{ $t('message.auth.webauthn.need_a_security_device_to_enable_options')}}
|
||||||
</div>
|
</div>
|
||||||
<form>
|
<form>
|
||||||
<!-- use webauthn only -->
|
<!-- use webauthn only -->
|
||||||
<FormCheckbox
|
<FormCheckbox
|
||||||
v-model="user.preferences.useWebauthnOnly"
|
v-model="user.preferences.useWebauthnOnly"
|
||||||
fieldName="useWebauthnOnly"
|
fieldName="useWebauthnOnly"
|
||||||
label="auth.webauthn.use_webauthn_only.label"
|
label="message.auth.webauthn.use_webauthn_only.label"
|
||||||
help="auth.webauthn.use_webauthn_only.help"
|
help="message.auth.webauthn.use_webauthn_only.help"
|
||||||
:isDisabled="isDisabled || credentials.length === 0"
|
:isDisabled="isDisabled || credentials.length === 0"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
@ -17,7 +17,9 @@
|
|||||||
import { useGroups } from '@/stores/groups'
|
import { useGroups } from '@/stores/groups'
|
||||||
import { useDisplayablePassword } from '@/composables/helpers'
|
import { useDisplayablePassword } from '@/composables/helpers'
|
||||||
import { useSortable, moveArrayElement } from '@vueuse/integrations/useSortable'
|
import { useSortable, moveArrayElement } from '@vueuse/integrations/useSortable'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const $2fauth = inject('2fauth')
|
const $2fauth = inject('2fauth')
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
@ -92,7 +94,7 @@
|
|||||||
else {
|
else {
|
||||||
twofaccounts.fetch().then(() => {
|
twofaccounts.fetch().then(() => {
|
||||||
if (twofaccounts.backendWasNewer) {
|
if (twofaccounts.backendWasNewer) {
|
||||||
notify.info({ text: trans('commons.data_refreshed_to_reflect_server_changes'), duration: 10000 })
|
notify.info({ text: t('message.data_refreshed_to_reflect_server_changes'), duration: 10000 })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -126,7 +128,7 @@
|
|||||||
twofaccounts.fetch()
|
twofaccounts.fetch()
|
||||||
twofaccounts.selectNone()
|
twofaccounts.selectNone()
|
||||||
showDestinationGroupSelector.value = false
|
showDestinationGroupSelector.value = false
|
||||||
notify.success({ text: trans('twofaccounts.accounts_moved') })
|
notify.success({ text: t('message.twofaccounts.accounts_moved') })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -183,7 +185,7 @@
|
|||||||
: user.preferences.defaultGroup
|
: user.preferences.defaultGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
notify.success({ text: trans('commons.copied_to_clipboard') })
|
notify.success({ text: t('message.copied_to_clipboard') })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,12 +362,12 @@
|
|||||||
<div v-else class="has-text-centered">
|
<div v-else class="has-text-centered">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column" v-if="showGroupSwitch">
|
<div class="column" v-if="showGroupSwitch">
|
||||||
<button type="button" id="btnHideGroupSwitch" :title="$t('groups.hide_group_selector')" tabindex="1" class="button is-text is-like-text" :class="{'has-text-grey' : mode != 'dark'}" @click.stop="showGroupSwitch = !showGroupSwitch">
|
<button type="button" id="btnHideGroupSwitch" :title="$t('message.groups.hide_group_selector')" tabindex="1" class="button is-text is-like-text" :class="{'has-text-grey' : mode != 'dark'}" @click.stop="showGroupSwitch = !showGroupSwitch">
|
||||||
{{ $t('groups.select_accounts_to_show') }}
|
{{ $t('message.groups.select_accounts_to_show') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="column" v-else>
|
<div class="column" v-else>
|
||||||
<button type="button" id="btnShowGroupSwitch" :title="$t('groups.show_group_selector')" tabindex="1" class="button is-text is-like-text" :class="{'has-text-grey' : mode != 'dark'}" @click.stop="showGroupSwitch = !showGroupSwitch">
|
<button type="button" id="btnShowGroupSwitch" :title="$t('message.groups.show_group_selector')" tabindex="1" class="button is-text is-like-text" :class="{'has-text-grey' : mode != 'dark'}" @click.stop="showGroupSwitch = !showGroupSwitch">
|
||||||
{{ groups.current }} ({{ twofaccounts.filteredCount }})
|
{{ groups.current }} ({{ twofaccounts.filteredCount }})
|
||||||
<FontAwesomeIcon :icon="['fas', 'caret-down']" />
|
<FontAwesomeIcon :icon="['fas', 'caret-down']" />
|
||||||
</button>
|
</button>
|
||||||
@ -424,18 +426,18 @@
|
|||||||
<div class="tfa-text has-ellipsis">
|
<div class="tfa-text has-ellipsis">
|
||||||
<img v-if="account.icon && user.preferences.showAccountsIcons" role="presentation" class="tfa-icon" :src="$2fauth.config.subdirectory + '/storage/icons/' + account.icon" alt="">
|
<img v-if="account.icon && user.preferences.showAccountsIcons" role="presentation" class="tfa-icon" :src="$2fauth.config.subdirectory + '/storage/icons/' + account.icon" alt="">
|
||||||
<img v-else-if="account.icon == null && user.preferences.showAccountsIcons" role="presentation" class="tfa-icon" :src="$2fauth.config.subdirectory + '/storage/noicon.svg'" alt="">
|
<img v-else-if="account.icon == null && user.preferences.showAccountsIcons" role="presentation" class="tfa-icon" :src="$2fauth.config.subdirectory + '/storage/noicon.svg'" alt="">
|
||||||
{{ account.service ? account.service : $t('twofaccounts.no_service') }}<FontAwesomeIcon class="has-text-danger is-size-5 ml-2" v-if="account.account === $t('errors.indecipherable')" :icon="['fas', 'exclamation-circle']" />
|
{{ account.service ? account.service : $t('message.twofaccounts.no_service') }}<FontAwesomeIcon class="has-text-danger is-size-5 ml-2" v-if="account.account === $t('error.indecipherable')" :icon="['fas', 'exclamation-circle']" />
|
||||||
<span class="is-block has-ellipsis is-family-primary is-size-6 is-size-7-mobile has-text-grey ">{{ account.account }}</span>
|
<span class="is-block has-ellipsis is-family-primary is-size-6 is-size-7-mobile has-text-grey ">{{ account.account }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<transition name="popLater">
|
<transition name="popLater">
|
||||||
<div v-show="user.preferences.getOtpOnRequest == false && !bus.inManagementMode" class="has-text-right">
|
<div v-show="user.preferences.getOtpOnRequest == false && !bus.inManagementMode" class="has-text-right">
|
||||||
<div v-if="account.otp != undefined">
|
<div v-if="account.otp != undefined">
|
||||||
<div class="always-on-otp is-clickable has-nowrap has-text-grey is-size-5 ml-4" @click="copyToClipboard(account.otp.password)" @keyup.enter="copyToClipboard(account.otp.password)" :title="$t('commons.copy_to_clipboard')">
|
<div class="always-on-otp is-clickable has-nowrap has-text-grey is-size-5 ml-4" @click="copyToClipboard(account.otp.password)" @keyup.enter="copyToClipboard(account.otp.password)" :title="$t('message.copy_to_clipboard')">
|
||||||
{{ useDisplayablePassword(account.otp.password, user.preferences.showOtpAsDot && user.preferences.revealDottedOTP && revealPassword == account.id) }}
|
{{ useDisplayablePassword(account.otp.password, user.preferences.showOtpAsDot && user.preferences.revealDottedOTP && revealPassword == account.id) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="has-nowrap" style="line-height: 0.9;">
|
<div class="has-nowrap" style="line-height: 0.9;">
|
||||||
<span v-if="user.preferences.showNextOtp" class="always-on-otp is-clickable has-nowrap has-text-grey is-size-7 mr-2" :class="opacities[account.period]" @click="copyToClipboard(account.otp.next_password)" @keyup.enter="copyToClipboard(account.otp.next_password)" :title="$t('commons.copy_next_password')">
|
<span v-if="user.preferences.showNextOtp" class="always-on-otp is-clickable has-nowrap has-text-grey is-size-7 mr-2" :class="opacities[account.period]" @click="copyToClipboard(account.otp.next_password)" @keyup.enter="copyToClipboard(account.otp.next_password)" :title="$t('message.copy_next_password')">
|
||||||
{{ useDisplayablePassword(account.otp.next_password, user.preferences.showOtpAsDot && user.preferences.revealDottedOTP && revealPassword == account.id) }}
|
{{ useDisplayablePassword(account.otp.next_password, user.preferences.showOtpAsDot && user.preferences.revealDottedOTP && revealPassword == account.id) }}
|
||||||
</span>
|
</span>
|
||||||
<Dots
|
<Dots
|
||||||
@ -447,8 +449,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<!-- get hotp button -->
|
<!-- get hotp button -->
|
||||||
<button type="button" class="button tag" :class="mode == 'dark' ? 'is-dark' : 'is-white'" @click="showOTP(account)" :title="$t('twofaccounts.import.import_this_account')">
|
<button type="button" class="button tag" :class="mode == 'dark' ? 'is-dark' : 'is-white'" @click="showOTP(account)" :title="$t('message.twofaccounts.import.import_this_account')">
|
||||||
{{ $t('commons.generate') }}
|
{{ $t('message.generate') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -466,9 +468,9 @@
|
|||||||
<transition name="fadeInOut">
|
<transition name="fadeInOut">
|
||||||
<div class="tfa-cell tfa-edit has-text-grey" v-if="bus.inManagementMode">
|
<div class="tfa-cell tfa-edit has-text-grey" v-if="bus.inManagementMode">
|
||||||
<RouterLink :to="{ name: 'editAccount', params: { twofaccountId: account.id }}" class="tag is-rounded mr-1" :class="mode == 'dark' ? 'is-dark' : 'is-white'">
|
<RouterLink :to="{ name: 'editAccount', params: { twofaccountId: account.id }}" class="tag is-rounded mr-1" :class="mode == 'dark' ? 'is-dark' : 'is-white'">
|
||||||
{{ $t('commons.edit') }}
|
{{ $t('message.edit') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<RouterLink :to="{ name: 'showQRcode', params: { twofaccountId: account.id }}" class="tag is-rounded" :class="mode == 'dark' ? 'is-dark' : 'is-white'" :title="$t('twofaccounts.show_qrcode')">
|
<RouterLink :to="{ name: 'showQRcode', params: { twofaccountId: account.id }}" class="tag is-rounded" :class="mode == 'dark' ? 'is-dark' : 'is-white'" :title="$t('message.twofaccounts.show_qrcode')">
|
||||||
<FontAwesomeIcon :icon="['fas', 'qrcode']" />
|
<FontAwesomeIcon :icon="['fas', 'qrcode']" />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
import { UseColorMode } from '@vueuse/components'
|
import { UseColorMode } from '@vueuse/components'
|
||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
import { QrcodeStream } from 'vue-qrcode-reader'
|
import { QrcodeStream } from 'vue-qrcode-reader'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const bus = useBusStore()
|
const bus = useBusStore()
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
@ -69,7 +71,7 @@
|
|||||||
form.uri = firstCode.rawValue
|
form.uri = firstCode.rawValue
|
||||||
|
|
||||||
if (! form.uri) {
|
if (! form.uri) {
|
||||||
notify.warn({ text: trans('errors.qrcode_cannot_be_read') })
|
notify.warn({ text: t('error.qrcode_cannot_be_read') })
|
||||||
}
|
}
|
||||||
else if (form.uri.slice(0, 33).toLowerCase() == "otpauth-migration://offline?data=") {
|
else if (form.uri.slice(0, 33).toLowerCase() == "otpauth-migration://offline?data=") {
|
||||||
bus.migrationUri = form.uri
|
bus.migrationUri = form.uri
|
||||||
@ -77,7 +79,7 @@
|
|||||||
}
|
}
|
||||||
else if (form.uri.slice(0, 15).toLowerCase() !== "otpauth://totp/" && form.uri.slice(0, 15).toLowerCase() !== "otpauth://hotp/") {
|
else if (form.uri.slice(0, 15).toLowerCase() !== "otpauth://totp/" && form.uri.slice(0, 15).toLowerCase() !== "otpauth://hotp/") {
|
||||||
showQrContent.value = true
|
showQrContent.value = true
|
||||||
notify.warn({ text: trans('errors.no_valid_otp') })
|
notify.warn({ text: t('error.no_valid_otp') })
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
bus.decodedUri = form.uri
|
bus.decodedUri = form.uri
|
||||||
@ -142,11 +144,11 @@
|
|||||||
<div class="modal-slot has-text-centered is-shadowless">
|
<div class="modal-slot has-text-centered is-shadowless">
|
||||||
<UseColorMode v-slot="{ mode }">
|
<UseColorMode v-slot="{ mode }">
|
||||||
<div v-if="errorPhrase">
|
<div v-if="errorPhrase">
|
||||||
<p class="block is-size-5">{{ $t('twofaccounts.stream.live_scan_cant_start') }}</p>
|
<p class="block is-size-5">{{ $t('message.twofaccounts.stream.live_scan_cant_start') }}</p>
|
||||||
<p class="block" :class="{'has-text-light': mode == 'dark'}">{{ $t('twofaccounts.stream.' + errorPhrase + '.reason') }}</p>
|
<p class="block" :class="{'has-text-light': mode == 'dark'}">{{ $t('message.twofaccounts.stream.' + errorPhrase + '.reason') }}</p>
|
||||||
<div v-if="errorPhrase == 'need_grant_permission'" >
|
<div v-if="errorPhrase == 'need_grant_permission'" >
|
||||||
<p class="is-size-7 mb-3">{{ $t('twofaccounts.stream.need_grant_permission.solution') }}</p>
|
<p class="is-size-7 mb-3">{{ $t('message.twofaccounts.stream.need_grant_permission.solution') }}</p>
|
||||||
<p class="is-size-7 mb-3">{{ $t('twofaccounts.stream.need_grant_permission.click_camera_icon') }}</p>
|
<p class="is-size-7 mb-3">{{ $t('message.twofaccounts.stream.need_grant_permission.click_camera_icon') }}</p>
|
||||||
|
|
||||||
<div class="addressbar columns is-mobile is-gapless">
|
<div class="addressbar columns is-mobile is-gapless">
|
||||||
<div class="column is-narrow has-text-left circled">
|
<div class="column is-narrow has-text-left circled">
|
||||||
@ -161,7 +163,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
<a @click.stop="reloadLocation">{{ $t('commons.refresh') }}</a>
|
<a @click.stop="reloadLocation">{{ $t('message.refresh') }}</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- <div class="addressbar">
|
<!-- <div class="addressbar">
|
||||||
@ -170,7 +172,7 @@
|
|||||||
<FontAwesomeIcon :icon="['far', 'star']" class="mr-3" size="xs" />
|
<FontAwesomeIcon :icon="['far', 'star']" class="mr-3" size="xs" />
|
||||||
</div> -->
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<p v-else class="is-size-7">{{ $t('twofaccounts.stream.' + errorPhrase + '.solution') }}</p>
|
<p v-else class="is-size-7">{{ $t('message.twofaccounts.stream.' + errorPhrase + '.solution') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<span v-else class="is-size-4" :class="mode == 'dark' ? 'has-text-light':'has-text-grey-dark'">
|
<span v-else class="is-size-4" :class="mode == 'dark' ? 'has-text-light':'has-text-grey-dark'">
|
||||||
<Spinner :isVisible="true" :type="'raw'" class="is-size-1" />
|
<Spinner :isVisible="true" :type="'raw'" class="is-size-1" />
|
||||||
@ -196,7 +198,7 @@
|
|||||||
<span class="select">
|
<span class="select">
|
||||||
<select v-model="selectedCamera">
|
<select v-model="selectedCamera">
|
||||||
<option v-for="camera in cameras" :key="camera.label" :value="camera">
|
<option v-for="camera in cameras" :key="camera.label" :value="camera">
|
||||||
{{ camera.label ? camera.label : $t('commons.default') }}
|
{{ camera.label ? camera.label : $t('message.default') }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</span>
|
</span>
|
||||||
|
@ -10,7 +10,9 @@
|
|||||||
import { useBusStore } from '@/stores/bus'
|
import { useBusStore } from '@/stores/bus'
|
||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
import { UseColorMode } from '@vueuse/components'
|
import { UseColorMode } from '@vueuse/components'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const $2fauth = inject('2fauth')
|
const $2fauth = inject('2fauth')
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@ -44,14 +46,14 @@
|
|||||||
]
|
]
|
||||||
const iconCollectionVariants = {
|
const iconCollectionVariants = {
|
||||||
selfh: [
|
selfh: [
|
||||||
{ text: 'commons.regular', value: 'regular' },
|
{ text: 'message.regular', value: 'regular' },
|
||||||
{ text: 'settings.forms.light', value: 'light' },
|
{ text: 'message.settings.forms.light', value: 'light' },
|
||||||
{ text: 'settings.forms.dark', value: 'dark' },
|
{ text: 'message.settings.forms.dark', value: 'dark' },
|
||||||
],
|
],
|
||||||
dashboardicons: [
|
dashboardicons: [
|
||||||
{ text: 'commons.regular', value: 'regular' },
|
{ text: 'message.regular', value: 'regular' },
|
||||||
{ text: 'settings.forms.light', value: 'light' },
|
{ text: 'message.settings.forms.light', value: 'light' },
|
||||||
{ text: 'settings.forms.dark', value: 'dark' },
|
{ text: 'message.settings.forms.dark', value: 'dark' },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
const otpDisplayProps = ref({
|
const otpDisplayProps = ref({
|
||||||
@ -109,7 +111,7 @@
|
|||||||
|
|
||||||
const groups = computed(() => {
|
const groups = computed(() => {
|
||||||
return useGroups().items.map((item) => {
|
return useGroups().items.map((item) => {
|
||||||
return { text: item.id > 0 ? item.name : '- ' + trans('groups.no_group') + ' -', value: item.id }
|
return { text: item.id > 0 ? item.name : '- ' + t('message.groups.no_group') + ' -', value: item.id }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -226,7 +228,7 @@
|
|||||||
|
|
||||||
if (form.errors.any() === false) {
|
if (form.errors.any() === false) {
|
||||||
twofaccounts.items.push(data)
|
twofaccounts.items.push(data)
|
||||||
notify.success({ text: trans('twofaccounts.account_created') })
|
notify.success({ text: t('message.twofaccounts.account_created') })
|
||||||
router.push({ name: 'accounts' });
|
router.push({ name: 'accounts' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,7 +252,7 @@
|
|||||||
const index = twofaccounts.items.findIndex(acc => acc.id === data.id)
|
const index = twofaccounts.items.findIndex(acc => acc.id === data.id)
|
||||||
twofaccounts.items.splice(index, 1, data)
|
twofaccounts.items.splice(index, 1, data)
|
||||||
|
|
||||||
notify.success({ text: trans('twofaccounts.account_updated') })
|
notify.success({ text: t('message.twofaccounts.account_updated') })
|
||||||
router.push({ name: 'accounts' })
|
router.push({ name: 'accounts' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,7 +289,7 @@
|
|||||||
*/
|
*/
|
||||||
function cancelCreation() {
|
function cancelCreation() {
|
||||||
if (form.hasChanged() || tempIcon.value != form.icon) {
|
if (form.hasChanged() || tempIcon.value != form.icon) {
|
||||||
if (confirm(trans('twofaccounts.confirm.cancel')) === true) {
|
if (confirm(t('message.twofaccounts.confirm.cancel')) === true) {
|
||||||
if (!isEditMode.value || tempIcon.value != form.icon) {
|
if (!isEditMode.value || tempIcon.value != form.icon) {
|
||||||
deleteTempIcon()
|
deleteTempIcon()
|
||||||
}
|
}
|
||||||
@ -385,7 +387,7 @@
|
|||||||
if( error.response.data.errors.uri ) {
|
if( error.response.data.errors.uri ) {
|
||||||
showAlternatives.value = true
|
showAlternatives.value = true
|
||||||
}
|
}
|
||||||
else notify.alert({ text: trans(error.response.data.message) })
|
else notify.alert({ text: t(error.response.data.message) })
|
||||||
} else {
|
} else {
|
||||||
notify.error(error)
|
notify.error(error)
|
||||||
}
|
}
|
||||||
@ -412,10 +414,10 @@
|
|||||||
deleteTempIcon()
|
deleteTempIcon()
|
||||||
tempIcon.value = response.data.filename;
|
tempIcon.value = response.data.filename;
|
||||||
}
|
}
|
||||||
else notify.warn( {text: trans('errors.no_icon_for_this_variant') })
|
else notify.warn( {text: t('error.no_icon_for_this_variant') })
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
notify.warn({ text: trans('errors.no_icon_for_this_variant') })
|
notify.warn({ text: t('error.no_icon_for_this_variant') })
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
fetchingLogo.value = false
|
fetchingLogo.value = false
|
||||||
@ -478,7 +480,7 @@
|
|||||||
<div class="column quickform-footer">
|
<div class="column quickform-footer">
|
||||||
<div class="field is-grouped is-grouped-centered">
|
<div class="field is-grouped is-grouped-centered">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<VueButton nativeType="submit" :isLoading="form.isBusy" >{{ $t('commons.save') }}</VueButton>
|
<VueButton nativeType="submit" :isLoading="form.isBusy" >{{ $t('message.save') }}</VueButton>
|
||||||
</div>
|
</div>
|
||||||
<NavigationButton action="cancel" :isText="true" :isRounded="false" :useLinkTag="false" @canceled="cancelCreation" />
|
<NavigationButton action="cancel" :isText="true" :isRounded="false" :useLinkTag="false" @canceled="cancelCreation" />
|
||||||
</div>
|
</div>
|
||||||
@ -487,19 +489,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<!-- Full form -->
|
<!-- Full form -->
|
||||||
<FormWrapper :title="$t(isEditMode ? 'twofaccounts.forms.edit_account' : 'twofaccounts.forms.new_account')" v-if="showAdvancedForm">
|
<FormWrapper :title="isEditMode ? 'message.twofaccounts.forms.edit_account' : 'message.twofaccounts.forms.new_account'" v-if="showAdvancedForm">
|
||||||
<form @submit.prevent="handleSubmit" @keydown="form.onKeydown($event)">
|
<form @submit.prevent="handleSubmit" @keydown="form.onKeydown($event)">
|
||||||
<!-- qcode fileupload -->
|
<!-- qcode fileupload -->
|
||||||
<div v-if="!isEditMode" class="field is-grouped">
|
<div v-if="!isEditMode" class="field is-grouped">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<div role="button" tabindex="0" class="file is-small" :class="{ 'is-black': mode == 'dark' }" @keyup.enter="qrcodeInputLabel.click()">
|
<div role="button" tabindex="0" class="file is-small" :class="{ 'is-black': mode == 'dark' }" @keyup.enter="qrcodeInputLabel.click()">
|
||||||
<label class="file-label" :title="$t('twofaccounts.forms.use_qrcode.title')" ref="qrcodeInputLabel">
|
<label class="file-label" :title="$t('message.twofaccounts.forms.use_qrcode.title')" ref="qrcodeInputLabel">
|
||||||
<input inert tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="uploadQrcode" ref="qrcodeInput">
|
<input inert tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="uploadQrcode" ref="qrcodeInput">
|
||||||
<span class="file-cta">
|
<span class="file-cta">
|
||||||
<span class="file-icon">
|
<span class="file-icon">
|
||||||
<FontAwesomeIcon :icon="['fas', 'qrcode']" size="lg" />
|
<FontAwesomeIcon :icon="['fas', 'qrcode']" size="lg" />
|
||||||
</span>
|
</span>
|
||||||
<span class="file-label">{{ $t('twofaccounts.forms.prefill_using_qrcode') }}</span>
|
<span class="file-label">{{ $t('message.twofaccounts.forms.prefill_using_qrcode') }}</span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -507,11 +509,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<FormFieldError v-if="qrcodeForm.errors.hasAny('qrcode')" :error="qrcodeForm.errors.get('qrcode')" :field="'qrcode'" class="help-for-file" />
|
<FormFieldError v-if="qrcodeForm.errors.hasAny('qrcode')" :error="qrcodeForm.errors.get('qrcode')" :field="'qrcode'" class="help-for-file" />
|
||||||
<!-- service -->
|
<!-- service -->
|
||||||
<FormField v-model="form.service" fieldName="service" :errorMessage="form.errors.get('email')" :isDisabled="form.otp_type === 'steamtotp'" label="twofaccounts.service" :placeholder="$t('twofaccounts.forms.service.placeholder')" autofocus />
|
<FormField v-model="form.service" fieldName="service" :errorMessage="form.errors.get('email')" :isDisabled="form.otp_type === 'steamtotp'" label="message.twofaccounts.service" :placeholder="$t('message.twofaccounts.forms.service.placeholder')" autofocus />
|
||||||
<!-- account -->
|
<!-- account -->
|
||||||
<FormField v-model="form.account" fieldName="account" :errorMessage="form.errors.get('account')" label="twofaccounts.account" :placeholder="$t('twofaccounts.forms.account.placeholder')" />
|
<FormField v-model="form.account" fieldName="account" :errorMessage="form.errors.get('account')" label="message.twofaccounts.account" :placeholder="$t('message.twofaccounts.forms.account.placeholder')" />
|
||||||
<!-- icon upload -->
|
<!-- icon upload -->
|
||||||
<label for="filUploadIcon" class="label">{{ $t('twofaccounts.icon') }}</label>
|
<label for="filUploadIcon" class="label">{{ $t('message.twofaccounts.icon') }}</label>
|
||||||
<!-- try my luck -->
|
<!-- try my luck -->
|
||||||
<!-- <fieldset v-if="user.preferences.getOfficialIcons" :disabled="!form.service"> -->
|
<!-- <fieldset v-if="user.preferences.getOfficialIcons" :disabled="!form.service"> -->
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
@ -542,7 +544,7 @@
|
|||||||
<span class="icon is-small">
|
<span class="icon is-small">
|
||||||
<FontAwesomeIcon :icon="['fas', 'globe']" />
|
<FontAwesomeIcon :icon="['fas', 'globe']" />
|
||||||
</span>
|
</span>
|
||||||
<span>{{ $t('twofaccounts.forms.i_m_lucky') }}</span>
|
<span>{{ $t('message.twofaccounts.forms.i_m_lucky') }}</span>
|
||||||
</VueButton>
|
</VueButton>
|
||||||
</div>
|
</div>
|
||||||
<!-- upload icon button -->
|
<!-- upload icon button -->
|
||||||
@ -554,49 +556,49 @@
|
|||||||
<span class="file-icon">
|
<span class="file-icon">
|
||||||
<FontAwesomeIcon :icon="['fas', 'upload']" />
|
<FontAwesomeIcon :icon="['fas', 'upload']" />
|
||||||
</span>
|
</span>
|
||||||
<span class="file-label">{{ $t('twofaccounts.forms.choose_image') }}</span>
|
<span class="file-label">{{ $t('message.twofaccounts.forms.choose_image') }}</span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<span class="tag is-large" :class="mode =='dark' ? 'is-dark' : 'is-white'" v-if="tempIcon">
|
<span class="tag is-large" :class="mode =='dark' ? 'is-dark' : 'is-white'" v-if="tempIcon">
|
||||||
<img class="icon-preview" :src="$2fauth.config.subdirectory + '/storage/icons/' + tempIcon" :alt="$t('twofaccounts.icon_to_illustrate_the_account')">
|
<img class="icon-preview" :src="$2fauth.config.subdirectory + '/storage/icons/' + tempIcon" :alt="$t('message.twofaccounts.icon_to_illustrate_the_account')">
|
||||||
<button type="button" class="clear-selection delete is-small" @click.prevent="deleteTempIcon" :aria-label="$t('twofaccounts.remove_icon')"></button>
|
<button type="button" class="clear-selection delete is-small" @click.prevent="deleteTempIcon" :aria-label="$t('message.twofaccounts.remove_icon')"></button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<FormFieldError v-if="iconForm.errors.hasAny('icon')" :error="iconForm.errors.get('icon')" :field="'icon'" class="help-for-file" />
|
<FormFieldError v-if="iconForm.errors.hasAny('icon')" :error="iconForm.errors.get('icon')" :field="'icon'" class="help-for-file" />
|
||||||
<p id="lgdTryMyLuck" v-if="user.preferences.getOfficialIcons" class="help" v-html="$t('twofaccounts.forms.i_m_lucky_legend')"></p>
|
<p id="lgdTryMyLuck" v-if="user.preferences.getOfficialIcons" class="help" v-html="$t('message.twofaccounts.forms.i_m_lucky_legend')"></p>
|
||||||
</div>
|
</div>
|
||||||
<!-- group -->
|
<!-- group -->
|
||||||
<FormSelect v-if="groups.length > 0" v-model="form.group_id" :options="groups" fieldName="group_id" label="twofaccounts.forms.group.label" help="twofaccounts.forms.group.help" />
|
<FormSelect v-if="groups.length > 0" v-model="form.group_id" :options="groups" fieldName="group_id" label="message.twofaccounts.forms.group.label" help="message.twofaccounts.forms.group.help" />
|
||||||
<!-- otp type -->
|
<!-- otp type -->
|
||||||
<FormToggle v-model="form.otp_type" :isDisabled="isEditMode" :choices="otp_types" fieldName="otp_type" :errorMessage="form.errors.get('otp_type')" label="twofaccounts.forms.otp_type.label" help="twofaccounts.forms.otp_type.help" :hasOffset="true" />
|
<FormToggle v-model="form.otp_type" :isDisabled="isEditMode" :choices="otp_types" fieldName="otp_type" :errorMessage="form.errors.get('otp_type')" label="message.twofaccounts.forms.otp_type.label" help="message.twofaccounts.forms.otp_type.help" :hasOffset="true" />
|
||||||
<div v-if="form.otp_type != ''">
|
<div v-if="form.otp_type != ''">
|
||||||
<!-- secret -->
|
<!-- secret -->
|
||||||
<FormProtectedField :enableProtection="isEditMode" v-model.trimAll="form.secret" fieldName="secret" :errorMessage="form.errors.get('secret')" label="twofaccounts.forms.secret.label" help="twofaccounts.forms.secret.help" />
|
<FormProtectedField :enableProtection="isEditMode" v-model.trimAll="form.secret" fieldName="secret" :errorMessage="form.errors.get('secret')" label="message.twofaccounts.forms.secret.label" help="message.twofaccounts.forms.secret.help" />
|
||||||
<!-- Options -->
|
<!-- Options -->
|
||||||
<div v-if="form.otp_type !== 'steamtotp'">
|
<div v-if="form.otp_type !== 'steamtotp'">
|
||||||
<h2 class="title is-4 mt-5 mb-2">{{ $t('commons.options') }}</h2>
|
<h2 class="title is-4 mt-5 mb-2">{{ $t('message.options') }}</h2>
|
||||||
<p class="help mb-4">
|
<p class="help mb-4">
|
||||||
{{ $t('twofaccounts.forms.options_help') }}
|
{{ $t('message.twofaccounts.forms.options_help') }}
|
||||||
</p>
|
</p>
|
||||||
<!-- digits -->
|
<!-- digits -->
|
||||||
<FormToggle v-model="form.digits" :choices="digitsChoices" fieldName="digits" :errorMessage="form.errors.get('digits')" label="twofaccounts.forms.digits.label" help="twofaccounts.forms.digits.help" />
|
<FormToggle v-model="form.digits" :choices="digitsChoices" fieldName="digits" :errorMessage="form.errors.get('digits')" label="message.twofaccounts.forms.digits.label" help="message.twofaccounts.forms.digits.help" />
|
||||||
<!-- algorithm -->
|
<!-- algorithm -->
|
||||||
<FormToggle v-model="form.algorithm" :choices="algorithms" fieldName="algorithm" :errorMessage="form.errors.get('algorithm')" label="twofaccounts.forms.algorithm.label" help="twofaccounts.forms.algorithm.help" />
|
<FormToggle v-model="form.algorithm" :choices="algorithms" fieldName="algorithm" :errorMessage="form.errors.get('algorithm')" label="message.twofaccounts.forms.algorithm.label" help="message.twofaccounts.forms.algorithm.help" />
|
||||||
<!-- TOTP period -->
|
<!-- TOTP period -->
|
||||||
<FormField v-if="form.otp_type === 'totp'" pattern="[0-9]{1,4}" :class="'is-third-width-field'" v-model="form.period" fieldName="period" :errorMessage="form.errors.get('period')" label="twofaccounts.forms.period.label" help="twofaccounts.forms.period.help" :placeholder="$t('twofaccounts.forms.period.placeholder')" />
|
<FormField v-if="form.otp_type === 'totp'" pattern="[0-9]{1,4}" :class="'is-half-width-field'" v-model="form.period" fieldName="period" :errorMessage="form.errors.get('period')" label="message.twofaccounts.forms.period.label" help="message.twofaccounts.forms.period.help" :placeholder="$t('message.twofaccounts.forms.period.placeholder')" />
|
||||||
<!-- HOTP counter -->
|
<!-- HOTP counter -->
|
||||||
<FormProtectedField v-if="form.otp_type === 'hotp'" pattern="[0-9]{1,4}" :enableProtection="isEditMode" :isExpanded="false" v-model="form.counter" fieldName="counter" :errorMessage="form.errors.get('counter')" label="twofaccounts.forms.counter.label" :placeholder="$t('twofaccounts.forms.counter.placeholder')" :help="isEditMode ? 'twofaccounts.forms.counter.help_lock' : 'twofaccounts.forms.counter.help'" />
|
<FormProtectedField v-if="form.otp_type === 'hotp'" pattern="[0-9]{1,4}" :enableProtection="isEditMode" :isExpanded="false" v-model="form.counter" fieldName="counter" :errorMessage="form.errors.get('counter')" label="message.twofaccounts.forms.counter.label" :placeholder="$t('message.twofaccounts.forms.counter.placeholder')" :help="isEditMode ? 'message.twofaccounts.forms.counter.help_lock' : 'message.twofaccounts.forms.counter.help'" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<VueFooter :showButtons="true">
|
<VueFooter :showButtons="true">
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<VueButton nativeType="submit" :id="isEditMode ? 'btnUpdate' : 'btnCreate'" :isLoading="form.isBusy" class="is-rounded" >{{ isEditMode ? $t('commons.save') : $t('commons.create') }}</VueButton>
|
<VueButton nativeType="submit" :id="isEditMode ? 'btnUpdate' : 'btnCreate'" :isLoading="form.isBusy" class="is-rounded" >{{ isEditMode ? $t('message.save') : $t('message.create') }}</VueButton>
|
||||||
</p>
|
</p>
|
||||||
<p class="control" v-if="form.otp_type && form.secret">
|
<p class="control" v-if="form.otp_type && form.secret">
|
||||||
<button id="btnPreview" type="button" class="button is-success is-rounded" @click="previewOTP">{{ $t('twofaccounts.forms.test') }}</button>
|
<button id="btnPreview" type="button" class="button is-success is-rounded" @click="previewOTP">{{ $t('message.twofaccounts.forms.test') }}</button>
|
||||||
</p>
|
</p>
|
||||||
<NavigationButton action="cancel" :useLinkTag="false" @canceled="cancelCreation" />
|
<NavigationButton action="cancel" :useLinkTag="false" @canceled="cancelCreation" />
|
||||||
</VueFooter>
|
</VueFooter>
|
||||||
|
@ -9,7 +9,9 @@
|
|||||||
import { useBusStore } from '@/stores/bus'
|
import { useBusStore } from '@/stores/bus'
|
||||||
import { useTwofaccounts } from '@/stores/twofaccounts'
|
import { useTwofaccounts } from '@/stores/twofaccounts'
|
||||||
import { UseColorMode } from '@vueuse/components'
|
import { UseColorMode } from '@vueuse/components'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
const $2fauth = inject('2fauth')
|
const $2fauth = inject('2fauth')
|
||||||
const notify = useNotifyStore()
|
const notify = useNotifyStore()
|
||||||
const user = useUserStore()
|
const user = useUserStore()
|
||||||
@ -42,9 +44,9 @@
|
|||||||
const showTwofaccountInModal = ref(false)
|
const showTwofaccountInModal = ref(false)
|
||||||
const supportedSources = [
|
const supportedSources = [
|
||||||
{app: '2FAuth', format: 'JSON'},
|
{app: '2FAuth', format: 'JSON'},
|
||||||
{app: 'Google Auth', format: trans('twofaccounts.import.qr_code')},
|
{app: 'Google Auth', format: t('message.twofaccounts.import.qr_code')},
|
||||||
{app: 'Aegis Auth', format: 'JSON'},
|
{app: 'Aegis Auth', format: 'JSON'},
|
||||||
{app: 'Aegis Auth', format: trans('twofaccounts.import.plain_text')},
|
{app: 'Aegis Auth', format: t('message.twofaccounts.import.plain_text')},
|
||||||
{app: '2FAS auth', format: 'JSON'},
|
{app: '2FAS auth', format: 'JSON'},
|
||||||
]
|
]
|
||||||
const exportedAccounts = ref([])
|
const exportedAccounts = ref([])
|
||||||
@ -92,7 +94,7 @@
|
|||||||
directInput.value = directInputError.value = null
|
directInput.value = directInputError.value = null
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
notify.alert({ text: trans(error.response.data.message) })
|
notify.alert({ text: t(error.response.data.message) })
|
||||||
});
|
});
|
||||||
|
|
||||||
isFetching.value = false
|
isFetching.value = false
|
||||||
@ -102,7 +104,7 @@
|
|||||||
* Removes all duplicates from the accounts list
|
* Removes all duplicates from the accounts list
|
||||||
*/
|
*/
|
||||||
function discardDuplicates() {
|
function discardDuplicates() {
|
||||||
if(confirm(trans('twofaccounts.confirm.discard_duplicates'))) {
|
if(confirm(t('message.twofaccounts.confirm.discard_duplicates'))) {
|
||||||
notify.clear()
|
notify.clear()
|
||||||
otpDisplay.value?.clearOTP()
|
otpDisplay.value?.clearOTP()
|
||||||
exportedAccounts.value = exportedAccounts.value.filter(account => account.id !== -1)
|
exportedAccounts.value = exportedAccounts.value.filter(account => account.id !== -1)
|
||||||
@ -113,7 +115,7 @@
|
|||||||
* Clears the accounts list
|
* Clears the accounts list
|
||||||
*/
|
*/
|
||||||
function discardAccounts() {
|
function discardAccounts() {
|
||||||
if(confirm(trans('twofaccounts.confirm.discard_all'))) {
|
if(confirm(t('message.twofaccounts.confirm.discard_all'))) {
|
||||||
notify.clear()
|
notify.clear()
|
||||||
otpDisplay.value?.clearOTP()
|
otpDisplay.value?.clearOTP()
|
||||||
exportedAccounts.value = []
|
exportedAccounts.value = []
|
||||||
@ -124,7 +126,7 @@
|
|||||||
* Removes one duplicate from the accounts list
|
* Removes one duplicate from the accounts list
|
||||||
*/
|
*/
|
||||||
function discardAccount(accountIndex) {
|
function discardAccount(accountIndex) {
|
||||||
if(confirm(trans('twofaccounts.confirm.discard'))) {
|
if(confirm(t('message.twofaccounts.confirm.discard'))) {
|
||||||
exportedAccounts.value.splice(accountIndex, 1)
|
exportedAccounts.value.splice(accountIndex, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,7 +192,7 @@
|
|||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error.response.status === 422) {
|
if (error.response.status === 422) {
|
||||||
if (error.response.data.errors.file == undefined) {
|
if (error.response.data.errors.file == undefined) {
|
||||||
notify.alert({ text: trans('errors.invalid_2fa_data') })
|
notify.alert({ text: t('error.invalid_2fa_data') })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else notify.alert({ text: error.response.data.message})
|
else notify.alert({ text: error.response.data.message})
|
||||||
@ -213,7 +215,7 @@
|
|||||||
.catch(error => {
|
.catch(error => {
|
||||||
if( error.response.status === 422 ) {
|
if( error.response.status === 422 ) {
|
||||||
if (error.response.data.errors.qrcode == undefined) {
|
if (error.response.data.errors.qrcode == undefined) {
|
||||||
notify.alert({ text: trans('errors.invalid_2fa_data') })
|
notify.alert({ text: t('error.invalid_2fa_data') })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else notify.alert({ text: error.response.data.message})
|
else notify.alert({ text: error.response.data.message})
|
||||||
@ -226,7 +228,7 @@
|
|||||||
* Notifies that valid account(s) have been found for import
|
* Notifies that valid account(s) have been found for import
|
||||||
*/
|
*/
|
||||||
function notifyValidAccountFound() {
|
function notifyValidAccountFound() {
|
||||||
notify.success({ text: trans('twofaccounts.import.x_valid_accounts_found', { count: importableCount.value }) })
|
notify.success({ text: t('message.twofaccounts.import.x_valid_accounts_found', { count: importableCount.value }) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -236,7 +238,7 @@
|
|||||||
directInputError.value = null
|
directInputError.value = null
|
||||||
|
|
||||||
if (! directInput.value) {
|
if (! directInput.value) {
|
||||||
directInputError.value = trans('validation.required', { attribute: 'Direct input' })
|
directInputError.value = t('validation.required', { attribute: 'Direct input' })
|
||||||
}
|
}
|
||||||
else migrate(directInput.value)
|
else migrate(directInput.value)
|
||||||
}
|
}
|
||||||
@ -248,12 +250,12 @@
|
|||||||
<div>
|
<div>
|
||||||
<ResponsiveWidthWrapper>
|
<ResponsiveWidthWrapper>
|
||||||
<h1 class="title has-text-grey-dark">
|
<h1 class="title has-text-grey-dark">
|
||||||
{{ $t('twofaccounts.import.import') }}
|
{{ $t('message.twofaccounts.import.import') }}
|
||||||
</h1>
|
</h1>
|
||||||
<div v-if="!isFetching && exportedAccounts.length == 0">
|
<div v-if="!isFetching && exportedAccounts.length == 0">
|
||||||
<div class="block is-size-7-mobile">
|
<div class="block is-size-7-mobile">
|
||||||
<p class="mb-2">{{ $t('twofaccounts.import.import_legend') }}</p>
|
<p class="mb-2">{{ $t('message.twofaccounts.import.import_legend') }}</p>
|
||||||
<p>{{ $t('twofaccounts.import.import_legend_afterpart') }}</p>
|
<p>{{ $t('message.twofaccounts.import.import_legend_afterpart') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
@ -267,19 +269,19 @@
|
|||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
<div class="media-content">
|
<div class="media-content">
|
||||||
<p class="title is-5 has-text-grey" v-html="$t('twofaccounts.import.qr_code')" />
|
<p class="title is-5 has-text-grey" v-html="$t('message.twofaccounts.import.qr_code')" />
|
||||||
<p class="subtitle is-6 is-size-7-mobile">{{ $t('twofaccounts.import.supported_formats_for_qrcode_upload') }}</p>
|
<p class="subtitle is-6 is-size-7-mobile">{{ $t('message.twofaccounts.import.supported_formats_for_qrcode_upload') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<FormFieldError v-if="qrcodeForm.errors.hasAny('qrcode')" :error="qrcodeForm.errors.get('qrcode')" :field="'qrcode'" />
|
<FormFieldError v-if="qrcodeForm.errors.hasAny('qrcode')" :error="qrcodeForm.errors.get('qrcode')" :field="'qrcode'" />
|
||||||
</div>
|
</div>
|
||||||
<footer class="card-footer">
|
<footer class="card-footer">
|
||||||
<RouterLink id="btnCapture" :to="{ name: 'capture' }" class="card-footer-item">
|
<RouterLink id="btnCapture" :to="{ name: 'capture' }" class="card-footer-item">
|
||||||
{{ $t('twofaccounts.import.scan') }}
|
{{ $t('message.twofaccounts.import.scan') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<a role="button" tabindex="0" class="card-footer-item is-relative" @click="qrcodeInput.click()" @keyup.enter="qrcodeInput.click()">
|
<a role="button" tabindex="0" class="card-footer-item is-relative" @click="qrcodeInput.click()" @keyup.enter="qrcodeInput.click()">
|
||||||
<input inert tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="submitQrCode" ref="qrcodeInput">
|
<input inert tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="submitQrCode" ref="qrcodeInput">
|
||||||
{{ $t('twofaccounts.import.upload') }}
|
{{ $t('message.twofaccounts.import.upload') }}
|
||||||
</a>
|
</a>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
@ -294,8 +296,8 @@
|
|||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
<div class="media-content">
|
<div class="media-content">
|
||||||
<p class="title is-5 has-text-grey">{{ $t('twofaccounts.import.text_file') }}</p>
|
<p class="title is-5 has-text-grey">{{ $t('message.twofaccounts.import.text_file') }}</p>
|
||||||
<p class="subtitle is-6 is-size-7-mobile">{{ $t('twofaccounts.import.supported_formats_for_file_upload') }}</p>
|
<p class="subtitle is-6 is-size-7-mobile">{{ $t('message.twofaccounts.import.supported_formats_for_file_upload') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<FormFieldError v-if="fileForm.errors.hasAny('file')" :error="fileForm.errors.get('file')" :field="'file'" />
|
<FormFieldError v-if="fileForm.errors.hasAny('file')" :error="fileForm.errors.get('file')" :field="'file'" />
|
||||||
@ -303,7 +305,7 @@
|
|||||||
<footer class="card-footer">
|
<footer class="card-footer">
|
||||||
<a role="button" tabindex="0" class="card-footer-item is-relative" @click="fileInput.click()" @keyup.enter="fileInput.click()">
|
<a role="button" tabindex="0" class="card-footer-item is-relative" @click="fileInput.click()" @keyup.enter="fileInput.click()">
|
||||||
<input inert tabindex="-1" class="file-input" type="file" accept="text/plain,application/json,text/csv,.2fas" v-on:change="submitFile" ref="fileInput">
|
<input inert tabindex="-1" class="file-input" type="file" accept="text/plain,application/json,text/csv,.2fas" v-on:change="submitFile" ref="fileInput">
|
||||||
{{ $t('twofaccounts.import.upload') }}
|
{{ $t('message.twofaccounts.import.upload') }}
|
||||||
</a>
|
</a>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
@ -318,8 +320,8 @@
|
|||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
<div class="media-content">
|
<div class="media-content">
|
||||||
<p class="title is-5 has-text-grey" v-html="$t('twofaccounts.import.direct_input')" />
|
<p class="title is-5 has-text-grey" v-html="$t('message.twofaccounts.import.direct_input')" />
|
||||||
<p class="subtitle is-6 is-size-7-mobile">{{ $t('twofaccounts.import.expected_format_for_direct_input') }}</p>
|
<p class="subtitle is-6 is-size-7-mobile">{{ $t('message.twofaccounts.import.expected_format_for_direct_input') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@ -328,7 +330,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<footer class="card-footer">
|
<footer class="card-footer">
|
||||||
<a role="button" tabindex="0" class="card-footer-item is-relative" @click.stop="submitDirectInput">
|
<a role="button" tabindex="0" class="card-footer-item is-relative" @click.stop="submitDirectInput">
|
||||||
{{ $t('commons.submit') }}
|
{{ $t('message.submit') }}
|
||||||
</a>
|
</a>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
@ -336,10 +338,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Supported migration resources -->
|
<!-- Supported migration resources -->
|
||||||
<h2 class="title is-5 has-text-grey-dark">{{ $t('twofaccounts.import.supported_migration_formats') }}</h2>
|
<h2 class="title is-5 has-text-grey-dark">{{ $t('message.twofaccounts.import.supported_migration_formats') }}</h2>
|
||||||
<div class="block is-size-7-mobile">
|
<div class="block is-size-7-mobile">
|
||||||
<FontAwesomeIcon :icon="['fas', 'fa-triangle-exclamation']" class="has-text-warning-dark" />
|
<FontAwesomeIcon :icon="['fas', 'fa-triangle-exclamation']" class="has-text-warning-dark" />
|
||||||
{{ $t('twofaccounts.import.do_not_set_password_or_encryption') }}
|
{{ $t('message.twofaccounts.import.do_not_set_password_or_encryption') }}
|
||||||
</div>
|
</div>
|
||||||
<table class="table is-size-7-mobile is-fullwidth">
|
<table class="table is-size-7-mobile is-fullwidth">
|
||||||
<thead>
|
<thead>
|
||||||
@ -385,17 +387,17 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="isFetching && exportedAccounts.length === 0">
|
<div v-else-if="isFetching && exportedAccounts.length === 0">
|
||||||
<Spinner :type="'fullscreen-overlay'" :isVisible="true" :message="'twofaccounts.import.parsing_data'" />
|
<Spinner :type="'fullscreen-overlay'" :isVisible="true" :message="'message.twofaccounts.import.parsing_data'" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div class="block is-size-7-mobile">
|
<div class="block is-size-7-mobile">
|
||||||
<p class="mb-2">{{ $t('twofaccounts.import.submitted_data_parsed_now_accounts_are_awaiting_import') }}</p>
|
<p class="mb-2">{{ $t('message.twofaccounts.import.submitted_data_parsed_now_accounts_are_awaiting_import') }}</p>
|
||||||
<p>{{ $t('twofaccounts.import.use_buttons_to_save_or_discard') }}</p>
|
<p>{{ $t('message.twofaccounts.import.use_buttons_to_save_or_discard') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="(account, index) in exportedAccounts" :key="account.name" class="group-item is-size-5 is-size-6-mobile">
|
<div v-for="(account, index) in exportedAccounts" :key="account.name" class="group-item is-size-5 is-size-6-mobile">
|
||||||
<div class="is-flex is-justify-content-space-between">
|
<div class="is-flex is-justify-content-space-between">
|
||||||
<!-- Account name -->
|
<!-- Account name -->
|
||||||
<div v-if="account.id > -2 && account.imported !== 0" class="is-flex-grow-1 has-ellipsis is-clickable" @click="previewAccount(index)" :title="$t('twofaccounts.import.generate_a_test_password')">
|
<div v-if="account.id > -2 && account.imported !== 0" class="is-flex-grow-1 has-ellipsis is-clickable" @click="previewAccount(index)" :title="$t('message.twofaccounts.import.generate_a_test_password')">
|
||||||
<img role="presentation" v-if="account.icon && user.preferences.showAccountsIcons" class="import-icon" :src="$2fauth.config.subdirectory + '/storage/icons/' + account.icon" alt="">
|
<img role="presentation" v-if="account.icon && user.preferences.showAccountsIcons" class="import-icon" :src="$2fauth.config.subdirectory + '/storage/icons/' + account.icon" alt="">
|
||||||
{{ account.account }}
|
{{ account.account }}
|
||||||
</div>
|
</div>
|
||||||
@ -403,34 +405,34 @@
|
|||||||
<!-- buttons -->
|
<!-- buttons -->
|
||||||
<div v-if="account.imported === -1" class="tags is-flex-wrap-nowrap">
|
<div v-if="account.imported === -1" class="tags is-flex-wrap-nowrap">
|
||||||
<!-- discard button -->
|
<!-- discard button -->
|
||||||
<button type="button" class="button tag" :class="{'is-dark has-text-grey-light' : mode == 'dark'}" @click="discardAccount(index)" :title="$t('twofaccounts.import.discard_this_account')">
|
<button type="button" class="button tag" :class="{'is-dark has-text-grey-light' : mode == 'dark'}" @click="discardAccount(index)" :title="$t('message.twofaccounts.import.discard_this_account')">
|
||||||
<FontAwesomeIcon :icon="['fas', 'trash']" />
|
<FontAwesomeIcon :icon="['fas', 'trash']" />
|
||||||
</button>
|
</button>
|
||||||
<!-- import button -->
|
<!-- import button -->
|
||||||
<button v-if="account.id > -2" type="button" class="button tag is-link" @click="createAccount(index)" :title="$t('twofaccounts.import.import_this_account')">
|
<button v-if="account.id > -2" type="button" class="button tag is-link" @click="createAccount(index)" :title="$t('message.twofaccounts.import.import_this_account')">
|
||||||
{{ $t('twofaccounts.import.to_import') }}
|
{{ $t('message.twofaccounts.import.to_import') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- result label -->
|
<!-- result label -->
|
||||||
<div v-else class="has-nowrap">
|
<div v-else class="has-nowrap">
|
||||||
<span v-if="account.imported === 1" class="has-text-success">
|
<span v-if="account.imported === 1" class="has-text-success">
|
||||||
{{ $t('twofaccounts.import.imported') }} <FontAwesomeIcon :icon="['fas', 'check']" />
|
{{ $t('message.twofaccounts.import.imported') }} <FontAwesomeIcon :icon="['fas', 'check']" />
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="has-text-danger">
|
<span v-else class="has-text-danger">
|
||||||
{{ $t('twofaccounts.import.failure') }} <FontAwesomeIcon :icon="['fas', 'times']" />
|
{{ $t('message.twofaccounts.import.failure') }} <FontAwesomeIcon :icon="['fas', 'times']" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="is-size-6 is-size-7-mobile">
|
<div class="is-size-6 is-size-7-mobile">
|
||||||
<!-- service name -->
|
<!-- service name -->
|
||||||
<div class="is-family-primary has-text-grey">{{ $t('twofaccounts.import.issuer') }}: {{ account.service }}</div>
|
<div class="is-family-primary has-text-grey">{{ $t('message.twofaccounts.import.issuer') }}: {{ account.service }}</div>
|
||||||
<!-- reasons to invalid G-Auth data -->
|
<!-- reasons to invalid G-Auth data -->
|
||||||
<div v-if="account.id === -2" class="has-text-danger">
|
<div v-if="account.id === -2" class="has-text-danger">
|
||||||
<FontAwesomeIcon class="mr-1" :icon="['fas', 'times-circle']" />{{ account.secret }}
|
<FontAwesomeIcon class="mr-1" :icon="['fas', 'times-circle']" />{{ account.secret }}
|
||||||
</div>
|
</div>
|
||||||
<!-- possible duplicates -->
|
<!-- possible duplicates -->
|
||||||
<div v-if="account.id === -1 && account.imported !== 1 && !account.errors" class="has-text-warning">
|
<div v-if="account.id === -1 && account.imported !== 1 && !account.errors" class="has-text-warning">
|
||||||
<FontAwesomeIcon class="mr-1" :icon="['fas', 'exclamation-circle']" />{{ $t('twofaccounts.import.possible_duplicate') }}
|
<FontAwesomeIcon class="mr-1" :icon="['fas', 'exclamation-circle']" />{{ $t('message.twofaccounts.import.possible_duplicate') }}
|
||||||
</div>
|
</div>
|
||||||
<!-- errors during account creation -->
|
<!-- errors during account creation -->
|
||||||
<ul v-if="account.errors">
|
<ul v-if="account.errors">
|
||||||
@ -440,11 +442,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- discard links -->
|
<!-- discard links -->
|
||||||
<div v-if="importableCount > 0" class="mt-2 is-size-7 is-pulled-right">
|
<div v-if="importableCount > 0" class="mt-2 is-size-7 is-pulled-right">
|
||||||
<button v-if="duplicateCount" @click="discardDuplicates()" type="button" class="has-text-grey button is-small is-ghost">{{ $t('twofaccounts.import.discard_duplicates') }} ({{duplicateCount}})</button>
|
<button v-if="duplicateCount" @click="discardDuplicates()" type="button" class="has-text-grey button is-small is-ghost">{{ $t('message.twofaccounts.import.discard_duplicates') }} ({{duplicateCount}})</button>
|
||||||
<button @click="discardAccounts()" type="button" class="has-text-grey button is-small is-ghost">{{ $t('twofaccounts.import.discard_all') }}</button>
|
<button @click="discardAccounts()" type="button" class="has-text-grey button is-small is-ghost">{{ $t('message.twofaccounts.import.discard_all') }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="importedCount == exportedAccounts.length" class="mt-2 is-size-7 is-pulled-right">
|
<div v-if="importedCount == exportedAccounts.length" class="mt-2 is-size-7 is-pulled-right">
|
||||||
<button @click="exportedAccounts = []" type="button" class="has-text-grey button is-small is-ghost">{{ $t('commons.clear') }}</button>
|
<button @click="exportedAccounts = []" type="button" class="has-text-grey button is-small is-ghost">{{ $t('message.clear') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- footer -->
|
<!-- footer -->
|
||||||
@ -452,7 +454,7 @@
|
|||||||
<!-- Import all button -->
|
<!-- Import all button -->
|
||||||
<p class="control" v-if="importableCount > 0">
|
<p class="control" v-if="importableCount > 0">
|
||||||
<button type="button" class="button is-link is-rounded is-focus" @click="createAccounts">
|
<button type="button" class="button is-link is-rounded is-focus" @click="createAccounts">
|
||||||
<span>{{ $t('twofaccounts.import.import_all') }} ({{ importableCount }})</span>
|
<span>{{ $t('message.twofaccounts.import.import_all') }} ({{ importableCount }})</span>
|
||||||
<!-- <span class="icon is-small">
|
<!-- <span class="icon is-small">
|
||||||
<FontAwesomeIcon :icon="['fas', 'qrcode']" />
|
<FontAwesomeIcon :icon="['fas', 'qrcode']" />
|
||||||
</span> -->
|
</span> -->
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<div class="modal-background"></div>
|
<div class="modal-background"></div>
|
||||||
<div class="modal-content modal-with-footer">
|
<div class="modal-content modal-with-footer">
|
||||||
<p class="has-text-centered m-5">
|
<p class="has-text-centered m-5">
|
||||||
<img v-if="qrcode" :src="qrcode" class="qrcode has-background-light" :alt="$t('commons.image_of_qrcode_to_scan')">
|
<img v-if="qrcode" :src="qrcode" class="qrcode has-background-light" :alt="$t('message.image_of_qrcode_to_scan')">
|
||||||
<Spinner :isVisible="!qrcode" :type="'raw'" class="is-size-1" />
|
<Spinner :isVisible="!qrcode" :type="'raw'" class="is-size-1" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,148 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Admin Language Lines
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The following language lines are used during authentication for various
|
|
||||||
| messages that we need to display to the user. You are free to modify
|
|
||||||
| these language lines according to your application's requirements.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'admin' => 'مدير',
|
|
||||||
'admin_panel' => 'لوحة الإدارة',
|
|
||||||
'app_setup' => 'إعداد التطبيق',
|
|
||||||
'auth' => 'المصادقة',
|
|
||||||
'registrations' => 'التسجيلات',
|
|
||||||
'users' => 'المستخدمون',
|
|
||||||
'users_legend' => 'Manage users registered on your instance or create new ones.',
|
|
||||||
'admin_settings' => 'إعدادات المدير',
|
|
||||||
'create_new_user' => 'أنشئ مستخدمًا',
|
|
||||||
'new_user' => 'مستخدم جديد',
|
|
||||||
'search_user_placeholder' => 'اسم المستخدم، البريد الإلكتروني...',
|
|
||||||
'quick_filters_colons' => 'فلاتر السريعة:',
|
|
||||||
'user_created' => 'تم إنشاء المستخدم بنجاح',
|
|
||||||
'confirm' => [
|
|
||||||
'delete_user' => 'هل أنت متيقِّن من أنك تريد حذف هذا المستخدم؟ لا يوجد أي رجعة.',
|
|
||||||
'request_password_reset' => 'هل أنت متيقِّن من أنك تريد إعادة تعيين كلمة مرور هذا المستخدم؟',
|
|
||||||
'purge_password_reset_request' => 'هل أنت متيقِّن من أنك تريد إلغاء الطلب السابق؟',
|
|
||||||
'delete_account' => 'هل أنت متيقِّن من أنك تريد حذف هذا المستخدم ؟',
|
|
||||||
'edit_own_account' => 'هذا هو حسابك الخاص. هل أنت متيقِّن؟',
|
|
||||||
'change_admin_role' => 'سيكون لهذا تأثيرات خطيرة على أذونات هذا المستخدم. هل أنت متيقِّن؟',
|
|
||||||
'demote_own_account' => 'لن تكون بعد الآن مديرا. هل أنت متيقِّن؟'
|
|
||||||
],
|
|
||||||
'logs' => 'السجلات',
|
|
||||||
'administration_legend' => 'الإعدادات التالية هي شاملة وتنطبق على جميع المستخدمين.',
|
|
||||||
'user_management' => 'إدارة المستخدم',
|
|
||||||
'oauth_provider' => 'مُزود OAuth',
|
|
||||||
'account_bound_to_x_via_oauth' => 'هذا الحساب مرتبط بحساب :provider عبر OAuth',
|
|
||||||
'last_seen_on_date' => 'اخر ظهور :date',
|
|
||||||
'registered_on_date' => 'تم تسجيله :date',
|
|
||||||
'updated_on_date' => 'تم التحديث :date',
|
|
||||||
'access' => 'الدخول',
|
|
||||||
'password_requested_on_t' => 'A password reset request exists for this user (request sent at :datetime), which means that the user has not yet changed their password but the link they received is still valid. This may be a request from the user themselves or from an administrator.',
|
|
||||||
'password_request_expired' => 'يوجد طلب إعادة تعيين كلمة المرور لهذا المستخدم ولكن انتهت صلاحيته، بمعنى أن المستخدم لم يغير كلمة المرور الخاصة به في الوقت المناسب. قد يكون هذا طلباً من المستخدم نفسه أو من أحد المسؤولين.',
|
|
||||||
'resend_email' => 'إعادة إرسال البريد الإلكتروني',
|
|
||||||
'resend_email_title' => 'إعادة إرسال بريد إلكتروني لإعادة تعيين كلمة المرور للمستخدم',
|
|
||||||
'resend_email_help' => 'استخدم <b>إعادة إرسال البريد الإلكتروني</b> لإرسال بريد إلكتروني جديد لإعادة تعيين كلمة المرور للمستخدم حتى يتمكن من تعيين كلمة مرور جديدة. هذا سيترك كلمة المرور الحالية كما هو وسيتم إلغاء أي طلب سابق.',
|
|
||||||
'reset_password' => 'إعادة تعيين كلمة المرور',
|
|
||||||
'reset_password_help' => 'Use <b>Reset password</b> to force a password reset (this will set a temporary password) before sending a password reset email to the user so they can set a new password. Any previous request will be revoked.',
|
|
||||||
'reset_password_title' => 'Reset the user\'s password',
|
|
||||||
'password_successfully_reset' => 'Password successfully reset',
|
|
||||||
'user_has_x_active_pat' => ':count active token(s)',
|
|
||||||
'user_has_x_security_devices' => ':count security device(s) (passkeys)',
|
|
||||||
'revoke_all_pat_for_user' => 'Revoke all user\'s tokens',
|
|
||||||
'revoke_all_devices_for_user' => 'Revoke all user\'s security devices',
|
|
||||||
'danger_zone' => 'Danger Zone',
|
|
||||||
'delete_this_user_legend' => 'The user account will be deleted as well as all its 2FA data.',
|
|
||||||
'this_is_not_soft_delete' => 'This is not a soft delete, there is no going back.',
|
|
||||||
'delete_this_user' => 'Delete this user',
|
|
||||||
'user_role_updated' => 'User role updated',
|
|
||||||
'pats_succesfully_revoked' => 'User\'s PATs successfully revoked',
|
|
||||||
'security_devices_succesfully_revoked' => 'User\'s security devices successfully revoked',
|
|
||||||
'variables' => 'Variables',
|
|
||||||
'cache_cleared' => 'Cache cleared',
|
|
||||||
'cache_optimized' => 'Cache optimized',
|
|
||||||
'check_now' => 'Check now',
|
|
||||||
'view_on_github' => 'View on Github',
|
|
||||||
'x_is_available' => ':version is available',
|
|
||||||
'successful_login_on' => 'Successful login on <span class="light-or-darker">:login_at</span>',
|
|
||||||
'successful_logout_on' => 'Successful logout on <span class="light-or-darker">:login_at</span>',
|
|
||||||
'failed_login_on' => 'Failed login on <span class="light-or-darker">:login_at</span>',
|
|
||||||
'viewed_on' => 'Viewed on <span class="light-or-darker">:login_at</span>',
|
|
||||||
'last_accesses' => 'Last accesses',
|
|
||||||
'see_full_log' => 'See full log',
|
|
||||||
'browser_on_platform' => ':browser on :platform',
|
|
||||||
'access_log_has_more_entries' => 'The access log contains more entries.',
|
|
||||||
'access_log_legend_for_user' => 'Full access log for user :username',
|
|
||||||
'show_last_month_log' => 'Show entries from the last month',
|
|
||||||
'show_three_months_log' => 'Show entries from the last 3 months',
|
|
||||||
'show_six_months_log' => 'Show entries from the last 6 months',
|
|
||||||
'show_one_year_log' => 'Show entries from the last year',
|
|
||||||
'sort_by_date_asc' => 'Show least recent first',
|
|
||||||
'sort_by_date_desc' => 'Show most recent first',
|
|
||||||
'single_sign_on' => 'Single Sign-On (SSO)',
|
|
||||||
'database' => 'Database',
|
|
||||||
'file_system' => 'File system',
|
|
||||||
'storage' => 'Storage',
|
|
||||||
'forms' => [
|
|
||||||
'use_encryption' => [
|
|
||||||
'label' => 'Protect sensitive data',
|
|
||||||
'help' => 'Sensitive data, the 2FA secrets and emails, are stored encrypted in database. Be sure to backup the APP_KEY value of your .env file (or the whole file) as it serves as key encryption. There is no way to decypher encrypted data without this key.',
|
|
||||||
],
|
|
||||||
'restrict_registration' => [
|
|
||||||
'label' => 'Restrict registration',
|
|
||||||
'help' => 'Make registration only available to a limited range of email addresses. Both rules can be used simultaneously. This has no effect on registration via SSO.',
|
|
||||||
],
|
|
||||||
'restrict_list' => [
|
|
||||||
'label' => 'Filtering list',
|
|
||||||
'help' => 'Emails in this list will be allowed to register. Separate addresses with a pipe ("|")',
|
|
||||||
],
|
|
||||||
'restrict_rule' => [
|
|
||||||
'label' => 'Filtering rule',
|
|
||||||
'help' => 'Emails matching this regular expression will be allowed to register',
|
|
||||||
],
|
|
||||||
'disable_registration' => [
|
|
||||||
'label' => 'Disable registration',
|
|
||||||
'help' => 'Prevent new user registration. Unless overridden (see below), this affects SSO as well, so new users won\'t be able to sign in via SSO',
|
|
||||||
],
|
|
||||||
'enable_sso' => [
|
|
||||||
'label' => 'Enable SSO',
|
|
||||||
'help' => 'Allow visitors to authenticate using an external ID via the Single Sign-On scheme',
|
|
||||||
],
|
|
||||||
'use_sso_only' => [
|
|
||||||
'label' => 'Use SSO only',
|
|
||||||
'help' => 'Make SSO the only available method to log in to 2FAuth. Password login and Webauthn are then disabled for regular users. Administrators are not affected by this restriction.',
|
|
||||||
],
|
|
||||||
'keep_sso_registration_enabled' => [
|
|
||||||
'label' => 'Keep SSO registration enabled',
|
|
||||||
'help' => 'Allow new users to sign in for the first time via SSO whereas registration is disabled',
|
|
||||||
],
|
|
||||||
'is_admin' => [
|
|
||||||
'label' => 'Is administrator',
|
|
||||||
'help' => 'Give administrator rights to the user. Administrators have permissions to manage the whole app, i.e. settings and other users, but cannot generate password for a 2FA they don\'t own.'
|
|
||||||
],
|
|
||||||
'test_email' => [
|
|
||||||
'label' => 'Email configuration test',
|
|
||||||
'help' => 'Send a test email to control your instance\'s email configuration. It is important to have a working configuration, otherwise users will not be able to request a reset password.',
|
|
||||||
'email_will_be_send_to_x' => 'The email will be send to <span class="is-family-code has-text-info">:email</span>',
|
|
||||||
],
|
|
||||||
'health_endpoint' => [
|
|
||||||
'label' => 'Health endpoint',
|
|
||||||
'help' => 'URL you can visit to check the health of this 2FAuth instance. This URL can be used to set up a Docker HEALTHCHECK or a Kubernetes HTTPS Liveness probe.',
|
|
||||||
],
|
|
||||||
'cache_management' => [
|
|
||||||
'label' => 'Cache management',
|
|
||||||
'help' => 'Sometimes cache needs to be cleared, for instance after a change to environment variables or an update. You can do it from here.',
|
|
||||||
],
|
|
||||||
'store_icon_to_database' => [
|
|
||||||
'label' => 'Store icons to database',
|
|
||||||
'help' => 'Uploaded icons are registered in the database in addition to the file system storage, which is then used only as a cache. This makes creating a 2FAuth backup much easier, as only the database has to be backed up.<br /><br />But beware, this may has some drawbacks: The database size may increase significantly if the instance hosts many large icons. It may also affect the application performance because the file system is hit more often to ensure it is synchronised with the database.',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
@ -1,142 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Authentication Language Lines
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The following language lines are used during authentication for various
|
|
||||||
| messages that we need to display to the user. You are free to modify
|
|
||||||
| these language lines according to your application's requirements.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Laravel
|
|
||||||
'failed' => 'These credentials do not match our records.',
|
|
||||||
'password' => 'The provided password is incorrect.',
|
|
||||||
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
|
|
||||||
|
|
||||||
// 2FAuth
|
|
||||||
'sign_out' => 'Sign out',
|
|
||||||
'sign_in' => 'Sign in',
|
|
||||||
'sign_in_using' => 'Sign in using',
|
|
||||||
'if_administrator' => 'Administrator?',
|
|
||||||
'sign_in_here' => 'You can sign without SSO',
|
|
||||||
'or_continue_with' => 'You can also continue with:',
|
|
||||||
'password_login_and_webauthn_are_disabled' => 'Password login and WebAuthn are disabled.',
|
|
||||||
'sign_in_using_sso' => 'Pick an SSO provider to sign in with:',
|
|
||||||
'no_provider' => 'no provider',
|
|
||||||
'no_sso_provider_or_provider_is_missing' => 'Provider is missing?',
|
|
||||||
'see_how_to_enable_sso' => 'See how to enable a provider',
|
|
||||||
'sign_in_using_security_device' => 'Sign in using a security device',
|
|
||||||
'login_and_password' => 'login & password',
|
|
||||||
'register' => 'Register',
|
|
||||||
'welcome_to_2fauth' => 'Welcome to 2FAuth',
|
|
||||||
'autolock_triggered' => 'Auto lock triggered',
|
|
||||||
'autolock_triggered_punchline' => 'Auto-lock triggered, you\'ve been logged out',
|
|
||||||
'already_authenticated' => 'Already authenticated, please log out first',
|
|
||||||
'authentication' => 'Authentication',
|
|
||||||
'maybe_later' => 'Maybe later',
|
|
||||||
'user_account_controlled_by_proxy' => 'User account made available by an authentication proxy.<br />Manage the account at proxy level.',
|
|
||||||
'auth_handled_by_proxy' => 'Authentication handled by a reverse proxy, below settings are disabled.<br />Manage authentication at proxy level.',
|
|
||||||
'sso_only_x_settings_are_disabled' => 'Authentication is restricted to SSO only, :auth_method is disabled',
|
|
||||||
'confirm' => [
|
|
||||||
'logout' => 'Are you sure you want to log out?',
|
|
||||||
'revoke_device' => 'Are you sure you want to revoke this device?',
|
|
||||||
'delete_account' => 'Are you sure you want to delete your account?',
|
|
||||||
],
|
|
||||||
'webauthn' => [
|
|
||||||
'security_device' => 'a security device',
|
|
||||||
'security_devices' => 'Security devices',
|
|
||||||
'security_devices_legend' => 'Authentication devices you can use to sign in 2FAuth, like security keys (i.e Yubikey) or smartphones with biometric capabilities (i.e. Apple FaceId/TouchId)',
|
|
||||||
'enhance_security_using_webauthn' => 'You can enhance the security of your 2FAuth account by enabling WebAuthn authentication.<br /><br />
|
|
||||||
WebAuthn allows you to use trusted devices (like Yubikeys or smartphones with biometric capabilities) to sign in quickly and more securely.',
|
|
||||||
'use_security_device_to_sign_in' => 'Get ready to authenticate using (one of) your security devices. Plug your key in, remove face mask or gloves, etc.',
|
|
||||||
'lost_your_device' => 'Lost your device?',
|
|
||||||
'recover_your_account' => 'Recover your account',
|
|
||||||
'account_recovery' => 'Account recovery',
|
|
||||||
'recovery_punchline' => '2FAuth will send you a recovery link to this email address. Click the link in the received email and follow the instructions.<br /><br />Ensure you open the email on a device you fully own.',
|
|
||||||
'send_recovery_link' => 'Send recovery link',
|
|
||||||
'account_recovery_email_sent' => 'Account recovery email sent!',
|
|
||||||
'disable_all_security_devices' => 'Disable all security devices',
|
|
||||||
'disable_all_security_devices_help' => 'All your security devices will be revoked. Use this option if you have lost one or its security has been compromised.',
|
|
||||||
'register_a_new_device' => 'Register a new device',
|
|
||||||
'register_a_device' => 'Register a device',
|
|
||||||
'device_successfully_registered' => 'Device successfully registered',
|
|
||||||
'device_revoked' => 'Device successfully revoked',
|
|
||||||
'revoking_a_device_is_permanent' => 'Revoking a device is permanent',
|
|
||||||
'recover_account_instructions' => 'To recover your account, 2FAuth resets some Webauthn settings so you will be able to sign in using your email and password.',
|
|
||||||
'invalid_recovery_token' => 'Invalid recovery token',
|
|
||||||
'webauthn_login_disabled' => 'Webauthn login disabled',
|
|
||||||
'invalid_reset_token' => 'This reset token is invalid.',
|
|
||||||
'rename_device' => 'Rename device',
|
|
||||||
'my_device' => 'My device',
|
|
||||||
'unknown_device' => 'Unknown device',
|
|
||||||
'use_webauthn_only' => [
|
|
||||||
'label' => 'Use WebAuthn only',
|
|
||||||
'help' => 'Make WebAuthn the only authorized method to log into your 2FAuth account. This is the recommended setup to take advantage of the WebAuthn enhanced security.<br /><br />
|
|
||||||
In case of device lost, you will be able to recover your account by resetting this option and signing in using your email and password.<br /><br />
|
|
||||||
Attention! The Email & Password form remains available despite this option being enabled, but it will always return an \'Authentication failed\' response.'
|
|
||||||
],
|
|
||||||
'need_a_security_device_to_enable_options' => 'Set at least one device to enable the following options',
|
|
||||||
'options' => 'Options',
|
|
||||||
],
|
|
||||||
'forms' => [
|
|
||||||
'name' => 'Name',
|
|
||||||
'login' => 'Login',
|
|
||||||
'webauthn_login' => 'WebAuthn login',
|
|
||||||
'sso_login' => 'SSO login',
|
|
||||||
'email' => 'Email',
|
|
||||||
'password' => 'Password',
|
|
||||||
'reveal_password' => 'Reveal password',
|
|
||||||
'hide_password' => 'Hide password',
|
|
||||||
'confirm_password' => 'Confirm password',
|
|
||||||
'new_password' => 'New password',
|
|
||||||
'confirm_new_password' => 'Confirm new password',
|
|
||||||
'dont_have_account_yet' => 'Don\'t have your account yet?',
|
|
||||||
'already_register' => 'Already registered?',
|
|
||||||
'authentication_failed' => 'Authentication failed',
|
|
||||||
'forgot_your_password' => 'Forgot your password?',
|
|
||||||
'request_password_reset' => 'Reset it',
|
|
||||||
'reset_your_password' => 'Reset your password',
|
|
||||||
'reset_password' => 'Reset password',
|
|
||||||
'disabled_in_demo' => 'Feature disabled in Demo mode',
|
|
||||||
'sso_only_form_restricted_to_admin' => 'Regular users must sign in with SSO. Other methods are for administrators only.',
|
|
||||||
'new_password' => 'New password',
|
|
||||||
'current_password' => [
|
|
||||||
'label' => 'Current password',
|
|
||||||
'help' => 'Fill in your current password to confirm that it\'s you'
|
|
||||||
],
|
|
||||||
'change_password' => 'Change password',
|
|
||||||
'send_password_reset_link' => 'Send password reset link',
|
|
||||||
'password_successfully_reset' => 'Password successfully reset',
|
|
||||||
'edit_account' => 'Edit account',
|
|
||||||
'profile_saved' => 'Profile successfully updated!',
|
|
||||||
'welcome_to_demo_app_use_those_credentials' => 'Welcome to the 2FAuth demo.<br><br>You can connect using the email address <strong>demo@2fauth.app</strong> and the password <strong>demo</strong>',
|
|
||||||
'welcome_to_testing_app_use_those_credentials' => 'Welcome to the 2FAuth testing instance.<br><br>Use email address <strong>testing@2fauth.app</strong> and password <strong>password</strong>',
|
|
||||||
'register_punchline' => 'Welcome to <b>2FAuth</b>.<br/>You need an account to go further, please register yourself.',
|
|
||||||
'reset_punchline' => '2FAuth will send you a password reset link to this address. Click the link in the received email to set a new password.',
|
|
||||||
'name_this_device' => 'Name this device',
|
|
||||||
'delete_account' => 'Delete account',
|
|
||||||
'delete_your_account' => 'Delete your account',
|
|
||||||
'delete_your_account_and_reset_all_data' => 'Your user account will be deleted as well as all your 2FA data. There is no going back.',
|
|
||||||
'reset_your_password_to_delete_your_account' => 'If you always used SSO to sign in, sign out then use the reset password feature to get a password so you can fill this form.',
|
|
||||||
'deleting_2fauth_account_does_not_impact_provider' => 'Deleting your 2FAuth account has no impact on your external SSO account.',
|
|
||||||
'user_account_successfully_deleted' => 'User account successfully deleted',
|
|
||||||
'has_lower_case' => 'Has lower case',
|
|
||||||
'has_upper_case' => 'Has upper case',
|
|
||||||
'has_special_char' => 'Has special char',
|
|
||||||
'has_number' => 'Has number',
|
|
||||||
'is_long_enough' => '8 characters min.',
|
|
||||||
'mandatory_rules' => 'Mandatory',
|
|
||||||
'optional_rules_you_should_follow' => 'Recommanded (highly)',
|
|
||||||
'caps_lock_is_on' => 'Caps lock is On',
|
|
||||||
],
|
|
||||||
'sso_providers' => [
|
|
||||||
'unknown' => 'unknown',
|
|
||||||
'github' => 'Github',
|
|
||||||
'openid' => 'OpenID'
|
|
||||||
]
|
|
||||||
];
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user