mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-06-24 14:02:09 +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
|
||||
? 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
|
||||
? 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] !== '') {
|
||||
return response()->json(
|
||||
['message' => 'bad request',
|
||||
'reason' => [__('errors.delete_user_setting_only')],
|
||||
'reason' => [__('error.delete_user_setting_only')],
|
||||
], 400);
|
||||
}
|
||||
|
||||
|
@ -151,7 +151,7 @@ class TwoFAccountController extends Controller
|
||||
|
||||
return $migrationResource instanceof \Illuminate\Http\UploadedFile
|
||||
? new TwoFAccountCollection(TwoFAccounts::migrate($migrationResource->get()))
|
||||
: response()->json(['message' => __('errors.file_upload_failed')], 500);
|
||||
: response()->json(['message' => __('error.file_upload_failed')], 500);
|
||||
} else {
|
||||
return new TwoFAccountCollection(TwoFAccounts::migrate($request->payload));
|
||||
}
|
||||
@ -203,7 +203,7 @@ class TwoFAccountController extends Controller
|
||||
if ($this->tooManyIds($validated['ids'])) {
|
||||
return response()->json([
|
||||
'message' => 'bad request',
|
||||
'reason' => [__('errors.too_many_ids')],
|
||||
'reason' => [__('error.too_many_ids')],
|
||||
], 400);
|
||||
}
|
||||
|
||||
@ -276,7 +276,7 @@ class TwoFAccountController extends Controller
|
||||
if ($this->tooManyIds($validated['ids'])) {
|
||||
return response()->json([
|
||||
'message' => 'bad request',
|
||||
'reason' => [__('errors.too_many_ids')],
|
||||
'reason' => [__('error.too_many_ids')],
|
||||
], 400);
|
||||
}
|
||||
|
||||
@ -316,7 +316,7 @@ class TwoFAccountController extends Controller
|
||||
if ($this->tooManyIds($validated['ids'])) {
|
||||
return response()->json([
|
||||
'message' => 'bad request',
|
||||
'reason' => [__('errors.too_many_ids')],
|
||||
'reason' => [__('error.too_many_ids')],
|
||||
], 400);
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ class UserManagerController extends Controller
|
||||
} else {
|
||||
return response()->json([
|
||||
'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);
|
||||
}
|
||||
|
||||
@ -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)
|
||||
return $user->delete() === false
|
||||
? response()->json([
|
||||
'message' => __('errors.cannot_delete_the_only_admin'),
|
||||
'message' => __('error.cannot_delete_the_only_admin'),
|
||||
], 403)
|
||||
: response()->json(null, 204);
|
||||
}
|
||||
@ -203,7 +203,7 @@ class UserManagerController extends Controller
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => __('errors.cannot_demote_the_only_admin'),
|
||||
'message' => __('error.cannot_demote_the_only_admin'),
|
||||
], 403);
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ class GroupStoreRequest extends FormRequest
|
||||
'required',
|
||||
'regex:/^[A-zÀ-ú0-9\s\-_]+$/',
|
||||
'max:32',
|
||||
Rule::notIn([__('commons.all')]),
|
||||
Rule::notIn([__('message.all')]),
|
||||
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
|
||||
{
|
||||
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...');
|
||||
|
||||
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));
|
||||
} else {
|
||||
try {
|
||||
|
@ -72,8 +72,8 @@ class FixServiceFieldEncryption extends Command
|
||||
protected function encryptServiceField() : void
|
||||
{
|
||||
$twofaccounts = TwoFAccount::all();
|
||||
$fullyEncryptedTwofaccounts = $twofaccounts->whereNotIn('service', [__('errors.indecipherable')]);
|
||||
$partiallyEncryptedTwofaccounts = $twofaccounts->where('service', __('errors.indecipherable'));
|
||||
$fullyEncryptedTwofaccounts = $twofaccounts->whereNotIn('service', [__('error.indecipherable')]);
|
||||
$partiallyEncryptedTwofaccounts = $twofaccounts->where('service', __('error.indecipherable'));
|
||||
|
||||
if ($fullyEncryptedTwofaccounts->count() === $twofaccounts->count()) {
|
||||
$this->components->info('The Service field is fully encrypted');
|
||||
|
@ -76,7 +76,7 @@ class FixUnsplittedAccounts extends Command
|
||||
$this->line('Trying to fix them...');
|
||||
|
||||
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));
|
||||
} else {
|
||||
try {
|
||||
|
@ -62,37 +62,37 @@ class Handler extends ExceptionHandler
|
||||
|
||||
$this->renderable(function (InvalidMigrationDataException $exception, $request) {
|
||||
return response()->json([
|
||||
'message' => __('errors.invalid_x_migration', ['appname' => $exception->getMessage()]),
|
||||
'message' => __('error.invalid_x_migration', ['appname' => $exception->getMessage()]),
|
||||
], 400);
|
||||
});
|
||||
|
||||
$this->renderable(function (UnsupportedMigrationException $exception, $request) {
|
||||
return response()->json([
|
||||
'message' => __('errors.unsupported_migration'),
|
||||
'message' => __('error.unsupported_migration'),
|
||||
], 400);
|
||||
});
|
||||
|
||||
$this->renderable(function (EncryptedMigrationException $exception, $request) {
|
||||
return response()->json([
|
||||
'message' => __('errors.encrypted_migration'),
|
||||
'message' => __('error.encrypted_migration'),
|
||||
], 400);
|
||||
});
|
||||
|
||||
$this->renderable(function (UndecipherableException $exception, $request) {
|
||||
return response()->json([
|
||||
'message' => __('errors.cannot_decipher_secret'),
|
||||
'message' => __('error.cannot_decipher_secret'),
|
||||
], 400);
|
||||
});
|
||||
|
||||
$this->renderable(function (UnsupportedOtpTypeException $exception, $request) {
|
||||
return response()->json([
|
||||
'message' => __('errors.unsupported_otp_type'),
|
||||
'message' => __('error.unsupported_otp_type'),
|
||||
], 400);
|
||||
});
|
||||
|
||||
$this->renderable(function (FailedIconStoreDatabaseTogglingException $exception, $request) {
|
||||
return response()->json([
|
||||
'message' => __('errors.failed_icon_store_database_toggling'),
|
||||
'message' => __('error.failed_icon_store_database_toggling'),
|
||||
], 400);
|
||||
});
|
||||
|
||||
|
@ -148,7 +148,7 @@ class LoginController extends Controller
|
||||
$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) {
|
||||
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)) {
|
||||
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')) {
|
||||
@ -39,6 +39,6 @@ class PasswordController extends Controller
|
||||
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)
|
||||
{
|
||||
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);
|
||||
@ -33,7 +33,7 @@ class PersonalAccessTokenController extends PassportPatController
|
||||
public function store(Request $request)
|
||||
{
|
||||
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);
|
||||
@ -49,7 +49,7 @@ class PersonalAccessTokenController extends PassportPatController
|
||||
public function destroy(Request $request, $tokenId)
|
||||
{
|
||||
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);
|
||||
|
@ -27,13 +27,13 @@ class UserController extends Controller
|
||||
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');
|
||||
|
||||
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)) {
|
||||
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')) {
|
||||
@ -58,14 +58,14 @@ class UserController extends Controller
|
||||
$user = Auth::user();
|
||||
|
||||
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.
|
||||
// Deletion will not be done (and returns False) if the user is the only existing admin (see UserObserver clas)
|
||||
return $user->delete() === false
|
||||
? response()->json([
|
||||
'message' => __('errors.cannot_delete_the_only_admin'),
|
||||
'message' => __('error.cannot_delete_the_only_admin'),
|
||||
], 400)
|
||||
: response()->json(null, 204);
|
||||
}
|
||||
|
@ -51,6 +51,6 @@ class WebAuthnDeviceLostController extends Controller
|
||||
*/
|
||||
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)
|
||||
);
|
||||
|
||||
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)
|
||||
{
|
||||
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();
|
||||
@ -54,7 +54,7 @@ class WebAuthnManageController extends Controller
|
||||
Log::info('Deletion of security device requested');
|
||||
|
||||
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();
|
||||
|
@ -80,7 +80,7 @@ class WebAuthnRecoveryController extends Controller
|
||||
*/
|
||||
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) {
|
||||
case Password::INVALID_TOKEN:
|
||||
throw ValidationException::withMessages(['token' => [__('auth.webauthn.invalid_reset_token')]]);
|
||||
throw ValidationException::withMessages(['token' => [__('message.auth.webauthn.invalid_reset_token')]]);
|
||||
default:
|
||||
throw ValidationException::withMessages(['email' => [trans($response)]]);
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ class RejectIfAuthenticated
|
||||
|
||||
foreach ($guards as $guard) {
|
||||
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')) {
|
||||
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);
|
||||
|
@ -19,7 +19,7 @@ class RejectIfReverseProxy
|
||||
Log::info('Cannot request this action in reverse proxy mode');
|
||||
|
||||
return response()->json([
|
||||
'message' => __('errors.unsupported_with_reverseproxy'),
|
||||
'message' => __('error.unsupported_with_reverseproxy'),
|
||||
], 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()));
|
||||
|
||||
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);
|
||||
|
@ -18,7 +18,7 @@ class WebauthnAttestationRequest extends AttestationRequest
|
||||
*/
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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.
|
||||
if ($value === '0') {
|
||||
$group = new self([
|
||||
'name' => __('commons.all'),
|
||||
'name' => __('message.all'),
|
||||
]);
|
||||
$group->id = 0;
|
||||
|
||||
|
@ -17,7 +17,7 @@ trait CanEncryptField
|
||||
try {
|
||||
return Crypt::decryptString($value);
|
||||
} catch (\Exception $ex) {
|
||||
return __('errors.indecipherable');
|
||||
return __('error.indecipherable');
|
||||
}
|
||||
} else {
|
||||
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'));
|
||||
|
||||
// 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');
|
||||
|
||||
throw new UndecipherableException;
|
||||
|
@ -45,7 +45,7 @@ class FailedLoginNotification extends Notification
|
||||
public function toMail(mixed $notifiable) : MailMessage
|
||||
{
|
||||
return (new MailMessage)
|
||||
->subject(__('notifications.failed_login.subject'))
|
||||
->subject(__('message.notifications.failed_login.subject'))
|
||||
->markdown('emails.failedLogin', [
|
||||
'account' => $notifiable,
|
||||
'time' => $this->authLog->login_at,
|
||||
|
@ -42,7 +42,7 @@ class SignedInWithNewDeviceNotification extends Notification
|
||||
public function toMail(mixed $notifiable) : MailMessage
|
||||
{
|
||||
return (new MailMessage)
|
||||
->subject(__('notifications.new_device.subject'))
|
||||
->subject(__('message.notifications.new_device.subject'))
|
||||
->markdown('emails.signedInWithNewDevice', [
|
||||
'account' => $notifiable,
|
||||
'time' => $this->authLog->login_at,
|
||||
|
@ -48,13 +48,13 @@ class TestEmailSettingNotification extends Notification
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
return (new MailMessage)
|
||||
->subject(Lang::get('notifications.test_email_settings.subject'))
|
||||
->greeting(Lang::get('notifications.hello'))
|
||||
->subject(Lang::get('message.notifications.test_email_settings.subject'))
|
||||
->greeting(Lang::get('message.notifications.hello'))
|
||||
->line(
|
||||
Lang::get('notifications.test_email_settings.reason')
|
||||
Lang::get('message.notifications.test_email_settings.reason')
|
||||
)
|
||||
->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)
|
||||
{
|
||||
$theAllGroup = new Group([
|
||||
'name' => __('commons.all'),
|
||||
'name' => __('message.all'),
|
||||
]);
|
||||
|
||||
$theAllGroup->id = 0;
|
||||
|
@ -101,8 +101,8 @@ class AegisMigrator extends Migrator
|
||||
$fakeAccount->id = TwoFAccount::FAKE_ID;
|
||||
$fakeAccount->otp_type = $otp_parameters['type'] ?? TwoFAccount::TOTP;
|
||||
// Only basic fields are filled to limit the risk of another exception.
|
||||
$fakeAccount->account = $otp_parameters['name'] ?? __('twofaccounts.import.invalid_account');
|
||||
$fakeAccount->service = $otp_parameters['issuer'] ?? __('twofaccounts.import.invalid_service');
|
||||
$fakeAccount->account = $otp_parameters['name'] ?? __('message.twofaccounts.import.invalid_account');
|
||||
$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.
|
||||
$fakeAccount->secret = $exception->getMessage();
|
||||
|
||||
|
@ -63,8 +63,8 @@ class GoogleAuthMigrator extends Migrator
|
||||
$fakeAccount->id = TwoFAccount::FAKE_ID;
|
||||
$fakeAccount->otp_type = $fakeAccount::TOTP;
|
||||
// Only basic fields are filled to limit the risk of another exception.
|
||||
$fakeAccount->account = $otp_parameters->getName() ?? __('twofaccounts.import.invalid_account');
|
||||
$fakeAccount->service = $otp_parameters->getIssuer() ?? __('twofaccounts.import.invalid_service');
|
||||
$fakeAccount->account = $otp_parameters->getName() ?? __('message.twofaccounts.import.invalid_account');
|
||||
$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.
|
||||
$fakeAccount->secret = $exception->getMessage();
|
||||
|
||||
|
@ -41,8 +41,8 @@ class PlainTextMigrator extends Migrator
|
||||
$fakeAccount->id = TwoFAccount::FAKE_ID;
|
||||
$fakeAccount->otp_type = substr($uri, 10, 4);
|
||||
// Only basic fields are filled to limit the risk of another exception.
|
||||
$fakeAccount->account = __('twofaccounts.import.invalid_account');
|
||||
$fakeAccount->service = filter_input(INPUT_GET, 'issuer', FILTER_SANITIZE_ENCODED) ?? __('twofaccounts.import.invalid_service');
|
||||
$fakeAccount->account = __('message.twofaccounts.import.invalid_account');
|
||||
$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.
|
||||
$fakeAccount->secret = $exception->getMessage();
|
||||
|
||||
|
@ -107,8 +107,8 @@ class TwoFASMigrator extends Migrator
|
||||
$fakeAccount->id = TwoFAccount::FAKE_ID;
|
||||
$fakeAccount->otp_type = $otp_parameters['otp']['tokenType'] ?? TwoFAccount::TOTP;
|
||||
// Only basic fields are filled to limit the risk of another exception.
|
||||
$fakeAccount->account = $otp_parameters['otp']['account'] ?? __('twofaccounts.import.invalid_account');
|
||||
$fakeAccount->service = $otp_parameters['name'] ?? __('twofaccounts.import.invalid_service');
|
||||
$fakeAccount->account = $otp_parameters['otp']['account'] ?? __('message.twofaccounts.import.invalid_account');
|
||||
$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.
|
||||
$fakeAccount->secret = $exception->getMessage();
|
||||
|
||||
|
@ -119,8 +119,8 @@ class TwoFAuthMigrator extends Migrator
|
||||
$fakeAccount->id = TwoFAccount::FAKE_ID;
|
||||
$fakeAccount->otp_type = $otp_parameters['otp']['tokenType'] ?? TwoFAccount::TOTP;
|
||||
// Only basic fields are filled to limit the risk of another exception.
|
||||
$fakeAccount->account = $otp_parameters['otp']['account'] ?? __('twofaccounts.import.invalid_account');
|
||||
$fakeAccount->service = $otp_parameters['name'] ?? __('twofaccounts.import.invalid_service');
|
||||
$fakeAccount->account = $otp_parameters['otp']['account'] ?? __('message.twofaccounts.import.invalid_account');
|
||||
$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.
|
||||
$fakeAccount->secret = $exception->getMessage();
|
||||
|
||||
|
@ -58,13 +58,13 @@ class QrCodeService
|
||||
if (! $text) {
|
||||
switch (get_class($qrcode->getError())) {
|
||||
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:
|
||||
throw new \App\Exceptions\InvalidQrCodeException(__('errors.cannot_decode_detected_qrcode'));
|
||||
throw new \App\Exceptions\InvalidQrCodeException(__('error.cannot_decode_detected_qrcode'));
|
||||
case ChecksumException::class:
|
||||
throw new \App\Exceptions\InvalidQrCodeException(__('errors.qrcode_has_invalid_checksum'));
|
||||
throw new \App\Exceptions\InvalidQrCodeException(__('error.qrcode_has_invalid_checksum'));
|
||||
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 {
|
||||
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
|
||||
// than the one used to encrypt the legacy_uri, account and secret fields, the
|
||||
// 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));
|
||||
}
|
||||
else {
|
||||
@ -69,7 +69,7 @@ return new class extends Migration
|
||||
foreach (TwoFAccount::all() as $twofaccount) {
|
||||
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));
|
||||
}
|
||||
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-solid-svg-icons": "^6.4.2",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.3",
|
||||
"@intlify/unplugin-vue-i18n": "^6.0.8",
|
||||
"@kyvg/vue3-notification": "^3.0.2",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vueuse/components": "^10.4.1",
|
||||
@ -24,16 +25,17 @@
|
||||
"eslint-plugin-vue": "^10.0.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"laravel-vite-plugin": "^1.0.2",
|
||||
"laravel-vue-i18n": "^2.7.1",
|
||||
"lucide-vue-next": "^0.517.0",
|
||||
"php-parser": "^3.1.5",
|
||||
"pinia": "^2.1.6",
|
||||
"sass": "^1.67.0",
|
||||
"sortablejs": "^1.15.0",
|
||||
"typescript": "^5.8.2",
|
||||
"unimport": "^5.0.1",
|
||||
"unplugin-auto-import": "^19.1.2",
|
||||
"vite": "^5.2.7",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^11.1.6",
|
||||
"vue-qrcode-reader": "^5.4.0",
|
||||
"vue-router": "^4.2.4"
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
<script setup>
|
||||
import { RouterView } from 'vue-router'
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const kickUser = 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>
|
||||
|
||||
<template>
|
||||
|
18
resources/js/app.js
vendored
18
resources/js/app.js
vendored
@ -1,5 +1,6 @@
|
||||
import '@2fauth/styles/src/app.scss';
|
||||
|
||||
import i18n from './i18n'
|
||||
import Notifications from '@kyvg/vue3-notification'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
@ -20,27 +21,20 @@ const $2fauth = {
|
||||
}
|
||||
app.provide('2fauth', readonly($2fauth))
|
||||
|
||||
// Localization
|
||||
app.use(i18n)
|
||||
|
||||
// Stores
|
||||
const pinia = createPinia()
|
||||
pinia.use(({ store }) => {
|
||||
store.$2fauth = $2fauth;
|
||||
});
|
||||
store.$i18n = i18n
|
||||
})
|
||||
app.use(pinia)
|
||||
|
||||
// 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
|
||||
app.use(Notifications)
|
||||
|
||||
|
@ -142,23 +142,23 @@
|
||||
<nav v-if="props.showSearch" class="level is-mobile mb-2">
|
||||
<div class="level-item has-text-centered">
|
||||
<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">
|
||||
{{ $t('commons.one_month') }}
|
||||
<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('message.one_month') }}
|
||||
</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">
|
||||
{{ $t('commons.x_month', { 'x' : '3' }) }}
|
||||
<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('message.x_month', { 'x' : '3' }) }}
|
||||
</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">
|
||||
{{ $t('commons.x_month', { 'x' : '6' }) }}
|
||||
<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('message.x_month', { 'x' : '6' }) }}
|
||||
</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">
|
||||
{{ $t('commons.one_year') }}
|
||||
<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('message.one_year') }}
|
||||
</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="['far', 'calendar']" />
|
||||
</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="['far', 'calendar']" />
|
||||
</button>
|
||||
@ -170,15 +170,31 @@
|
||||
<UseColorMode v-slot="{ mode }">
|
||||
<div>
|
||||
<div>
|
||||
<span v-if="isFailedEntry(authentication)" v-html="$t('admin.failed_login_on', { login_at: authentication.login_at })" />
|
||||
<span v-else-if="isSuccessfulLogout(authentication)" v-html="$t('admin.successful_logout_on', { login_at: authentication.logout_at })" />
|
||||
<span v-else-if="$2fauth.config.proxyAuth" v-html="$t('admin.viewed_on', { login_at: authentication.login_at })" />
|
||||
<span v-else v-html="$t('admin.successful_login_on', { login_at: authentication.login_at })" />
|
||||
<i18n-t v-if="isFailedEntry(authentication)" keypath="message.admin.failed_login_on" tag="span">
|
||||
<template v-slot:login_at>
|
||||
<span class="light-or-darker">{{ authentication.login_at }}</span>
|
||||
</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>
|
||||
{{ $t('commons.IP') }}: <span class="light-or-darker">{{ authentication.ip_address }}</span> -
|
||||
{{ $t('commons.browser') }}: <span class="light-or-darker">{{ authentication.browser }}</span> -
|
||||
{{ $t('commons.operating_system_short') }}: <span class="light-or-darker">{{ authentication.platform }}</span>
|
||||
{{ $t('message.IP') }}: <span class="light-or-darker">{{ authentication.ip_address }}</span> -
|
||||
{{ $t('message.browser') }}: <span class="light-or-darker">{{ authentication.browser }}</span> -
|
||||
{{ $t('message.operating_system_short') }}: <span class="light-or-darker">{{ authentication.platform }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="mode == 'dark' ? 'has-text-grey-darker' : 'has-text-grey-lighter'" class="is-align-self-center ">
|
||||
@ -193,10 +209,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="authentications.length == 0" class="mt-4">
|
||||
{{ $t('commons.no_entry_yet') }}
|
||||
{{ $t('message.no_entry_yet') }}
|
||||
</div>
|
||||
<div v-else class="mt-5 pl-3">
|
||||
{{ $t('commons.no_result') }}
|
||||
{{ $t('message.no_result') }}
|
||||
</div>
|
||||
<Spinner :isVisible="isFetching" />
|
||||
</template>
|
@ -33,7 +33,7 @@
|
||||
<!-- New item buttons -->
|
||||
<p class="control" v-if="!inManagementMode">
|
||||
<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">
|
||||
<FontAwesomeIcon :icon="['fas', 'qrcode']" />
|
||||
</span>
|
||||
@ -41,7 +41,7 @@
|
||||
</p>
|
||||
<!-- Manage button -->
|
||||
<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>
|
||||
<!-- move button -->
|
||||
<p class="control" v-if="inManagementMode">
|
||||
@ -50,8 +50,8 @@
|
||||
:disabled='areDisabled' class="button is-rounded"
|
||||
:class="[{ 'is-outlined': mode == 'dark' || areDisabled }, areDisabled ? 'is-dark': 'is-link']"
|
||||
@click="$emit('move-button-clicked')"
|
||||
:title="$t('groups.move_selected_to_group')" >
|
||||
{{ $t('commons.move') }}
|
||||
:title="$t('message.groups.move_selected_to_group')" >
|
||||
{{ $t('message.move') }}
|
||||
</button>
|
||||
</p>
|
||||
<!-- delete button -->
|
||||
@ -61,7 +61,7 @@
|
||||
:disabled='areDisabled' class="button is-rounded"
|
||||
:class="[{ 'is-outlined': mode == 'dark' || areDisabled }, areDisabled ? 'is-dark': 'is-link']"
|
||||
@click="$emit('delete-button-clicked')" >
|
||||
{{ $t('commons.delete') }}
|
||||
{{ $t('message.delete') }}
|
||||
</button>
|
||||
</p>
|
||||
<!-- export button -->
|
||||
@ -71,8 +71,8 @@
|
||||
:disabled='areDisabled' class="button is-rounded"
|
||||
:class="[{ 'is-outlined': mode == 'dark' || areDisabled }, areDisabled ? 'is-dark': 'is-link']"
|
||||
@click="$emit('export-button-clicked')"
|
||||
:title="$t('twofaccounts.export_selected_accounts')" >
|
||||
{{ $t('commons.export') }}
|
||||
:title="$t('message.twofaccounts.export_selected_accounts')" >
|
||||
{{ $t('message.export') }}
|
||||
</button>
|
||||
</p>
|
||||
</UseColorMode>
|
||||
|
@ -1,5 +1,7 @@
|
||||
<script setup>
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
|
||||
const notify = useNotifyStore()
|
||||
const { copy } = useClipboard({ legacy: true })
|
||||
@ -10,12 +12,12 @@
|
||||
|
||||
function copyToClipboard() {
|
||||
copy(props.token)
|
||||
notify.success({ text: trans('commons.copied_to_clipboard') })
|
||||
notify.success({ text: t('message.copied_to_clipboard') })
|
||||
}
|
||||
</script>
|
||||
|
||||
<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']" />
|
||||
</button>
|
||||
</template>
|
@ -33,7 +33,7 @@
|
||||
<div class="container group-selector">
|
||||
<div class="columns is-centered is-multiline">
|
||||
<div class="column is-full has-text-centered">
|
||||
{{ $t('groups.move_selected_to') }}
|
||||
{{ $t('message.groups.move_selected_to') }}
|
||||
</div>
|
||||
<div class="column is-one-third-tablet is-one-quarter-desktop is-one-quarter-widescreen is-one-quarter-fullhd">
|
||||
<div class="columns is-multiline">
|
||||
@ -41,7 +41,7 @@
|
||||
<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">
|
||||
<span v-if="group.id === 0" class="is-italic">
|
||||
{{ $t('groups.no_group') }}
|
||||
{{ $t('message.groups.no_group') }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ group.name }}
|
||||
@ -52,7 +52,7 @@
|
||||
</div>
|
||||
<div class="columns is-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>
|
||||
@ -60,7 +60,7 @@
|
||||
<VueFooter :showButtons="true">
|
||||
<!-- Move to selected group button -->
|
||||
<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>
|
||||
<NavigationButton action="cancel" :useLinkTag="false" @canceled="$emit('update:showDestinationGroupSelector', false)" />
|
||||
</VueFooter>
|
||||
|
@ -11,29 +11,29 @@
|
||||
<div class="block">
|
||||
<UseColorMode v-slot="{ mode }">
|
||||
<p class="has-text-weight-bold has-text-grey">
|
||||
{{ $t('twofaccounts.twofauth_export_format_sub') }}
|
||||
{{ $t('message.twofaccounts.twofauth_export_format_sub') }}
|
||||
</p>
|
||||
</UseColorMode>
|
||||
<p class="is-size-7-mobile">
|
||||
{{ $t('twofaccounts.twofauth_export_format_desc') }}
|
||||
{{ $t('twofaccounts.twofauth_export_format_url') }}
|
||||
{{ $t('message.twofaccounts.twofauth_export_format_desc') }}
|
||||
{{ $t('message.twofaccounts.twofauth_export_format_url') }}
|
||||
<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>
|
||||
</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')">
|
||||
{{ $t('twofaccounts.twofauth_export_format') }}
|
||||
<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('message.twofaccounts.twofauth_export_format') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="block">
|
||||
<p class="has-text-weight-bold has-text-grey">
|
||||
{{ $t('twofaccounts.otpauth_export_format_sub') }}
|
||||
{{ $t('message.twofaccounts.otpauth_export_format_sub') }}
|
||||
</p>
|
||||
<p class="is-size-7-mobile">
|
||||
{{ $t('twofaccounts.otpauth_export_format_desc') }}
|
||||
{{ $t('message.twofaccounts.otpauth_export_format_desc') }}
|
||||
</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')">
|
||||
{{ $t('twofaccounts.otpauth_export_format') }}
|
||||
<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('message.twofaccounts.otpauth_export_format') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
@ -39,7 +39,7 @@
|
||||
</div>
|
||||
<div class="columns is-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>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Spinner from '@/components/Spinner.vue'
|
||||
import TotpLooper from '@/components/TotpLooper.vue'
|
||||
import Dots from '@/components/Dots.vue'
|
||||
@ -8,6 +9,7 @@
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import { useDisplayablePassword } from '@/composables/helpers'
|
||||
|
||||
const { t } = useI18n()
|
||||
const user = useUserStore()
|
||||
const notify = useNotifyStore()
|
||||
const $2fauth = inject('2fauth')
|
||||
@ -115,10 +117,10 @@
|
||||
}
|
||||
// Case 3
|
||||
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)) {
|
||||
notify.error(new Error(trans('errors.not_a_supported_otp_type')))
|
||||
notify.error(new Error(t('error.not_a_supported_otp_type')))
|
||||
}
|
||||
|
||||
try {
|
||||
@ -267,7 +269,7 @@
|
||||
: user.preferences.defaultGroup
|
||||
}
|
||||
|
||||
notify.success({ text: trans('commons.copied_to_clipboard') })
|
||||
notify.success({ text: t('message.copied_to_clipboard') })
|
||||
}
|
||||
}
|
||||
|
||||
@ -318,7 +320,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<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>
|
||||
<UseColorMode v-slot="{ mode }">
|
||||
<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'"
|
||||
@click="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) }}
|
||||
</span>
|
||||
@ -345,7 +347,7 @@
|
||||
</UseColorMode>
|
||||
<Dots v-show="isTimeBased(otpauthParams.otp_type)" ref="dots"></Dots>
|
||||
<p v-show="isHMacBased(otpauthParams.otp_type)">
|
||||
{{ $t('twofaccounts.forms.counter.label') }}: {{ otpauthParams.counter }}
|
||||
{{ $t('message.twofaccounts.forms.counter.label') }}: {{ otpauthParams.counter }}
|
||||
</p>
|
||||
<p v-if="user.preferences.showNextOtp" class="mt-3 is-size-4">
|
||||
<span
|
||||
@ -354,7 +356,7 @@
|
||||
:class="opacity"
|
||||
@click="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) }}
|
||||
</span>
|
||||
|
@ -1,7 +1,9 @@
|
||||
<script setup>
|
||||
const { copy } = useClipboard({ legacy: true })
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const notify = useNotifyStore()
|
||||
|
||||
const props = defineProps({
|
||||
@ -38,26 +40,26 @@
|
||||
*/
|
||||
function copyToClipboard(data) {
|
||||
copy(data)
|
||||
notify.success({ text: trans('commons.copied_to_clipboard') })
|
||||
notify.success({ text: t('message.copied_to_clipboard') })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="too-bad"></div>
|
||||
<div class="block">
|
||||
{{ $t('errors.data_of_qrcode_is_not_valid_URI') }}
|
||||
{{ $t('error.data_of_qrcode_is_not_valid_URI') }}
|
||||
</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 -->
|
||||
<div class="block has-text-link" v-if="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>
|
||||
</div>
|
||||
<!-- Open in browser -->
|
||||
<div class="block has-text-link" v-if="isUrl(qrContent)" @click="openInBrowser(qrContent)">
|
||||
<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">
|
||||
<FontAwesomeIcon :icon="['fas', 'external-link-alt']" />
|
||||
</span>
|
||||
|
@ -64,13 +64,13 @@
|
||||
id="txtSearch"
|
||||
type="search"
|
||||
tabindex="1"
|
||||
:aria-label="$t('commons.search')"
|
||||
:title="$t('commons.search')"
|
||||
:aria-label="$t('message.search')"
|
||||
:title="$t('message.search')"
|
||||
:placeholder="placeholder"
|
||||
class="input is-rounded is-search"
|
||||
:class="{ 'has-no-background': hasNoBackground }">
|
||||
<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']" />
|
||||
</span>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: 'commons.generating_otp'
|
||||
default: 'message.generating_otp'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
<template>
|
||||
<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]" />
|
||||
</a>
|
||||
</template>
|
||||
|
@ -9,19 +9,19 @@
|
||||
<div class="columns">
|
||||
<div class="column has-nowrap px-0">
|
||||
<!-- 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 -->
|
||||
<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 -->
|
||||
<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')">
|
||||
<span>{{ $t('commons.check_all') }}</span>
|
||||
<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('message.check_all') }}</span>
|
||||
<FontAwesomeIcon class="ml-1" :icon="['fas', 'check-square']" />
|
||||
</button>
|
||||
<!-- 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']" />
|
||||
</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']" />
|
||||
</button>
|
||||
</div>
|
||||
|
@ -28,18 +28,18 @@
|
||||
<template>
|
||||
<div class="columns is-mobile is-vcentered">
|
||||
<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 class="column">
|
||||
<UseColorMode v-slot="{ mode }">
|
||||
<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 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 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>
|
||||
</UseColorMode>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import appSettingService from '@/services/appSettingService'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
/**
|
||||
* Saves a setting on the backend
|
||||
@ -8,13 +9,15 @@ import { useNotifyStore } from '@/stores/notify'
|
||||
*/
|
||||
export async function useAppSettingsUpdater(setting, value, returnValidationError = false) {
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
let data = null
|
||||
let error = null
|
||||
|
||||
await appSettingService.update(setting, value, { returnError: true })
|
||||
.then(response => {
|
||||
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 => {
|
||||
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>
|
||||
const tabs = ref([
|
||||
{
|
||||
'name' : 'admin.app_setup',
|
||||
'name' : 'message.admin.app_setup',
|
||||
'view' : 'admin.appSetup',
|
||||
'id' : 'lnkTabApp'
|
||||
},
|
||||
{
|
||||
'name' : 'admin.auth',
|
||||
'name' : 'message.admin.auth',
|
||||
'view' : 'admin.auth',
|
||||
'id' : 'lnkTabAuth'
|
||||
},
|
||||
{
|
||||
'name' : 'admin.users',
|
||||
'name' : 'message.admin.users',
|
||||
'view' : 'admin.users',
|
||||
'id' : 'lnkTabUsers'
|
||||
},
|
||||
|
@ -2,7 +2,9 @@
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appSettings = useAppSettingsStore()
|
||||
const user = useUserStore()
|
||||
const $2fauth = inject('2fauth')
|
||||
@ -17,7 +19,7 @@
|
||||
})
|
||||
|
||||
function logout() {
|
||||
if(confirm(trans('auth.confirm.logout'))) {
|
||||
if(confirm(t('message.auth.confirm.logout'))) {
|
||||
user.logout()
|
||||
}
|
||||
}
|
||||
@ -36,11 +38,11 @@
|
||||
<!-- sub-links -->
|
||||
<div v-if="internalFooterType == 'doneButton'" class="content has-text-centered">
|
||||
<!-- 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 v-else-if="internalFooterType == 'modal'" class="content has-text-centered">
|
||||
<!-- 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>
|
||||
</div>
|
||||
<div v-else class="content has-text-centered">
|
||||
@ -48,7 +50,7 @@
|
||||
<!-- about link -->
|
||||
<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-else class="">{{ $t('commons.about') }}</span>
|
||||
<span v-else class="">{{ $t('message.about') }}</span>
|
||||
</router-link>
|
||||
<span v-else> </span>
|
||||
</div>
|
||||
@ -58,21 +60,21 @@
|
||||
<!-- settings link -->
|
||||
<li class="column">
|
||||
<router-link id="lnkSettings" :to="{ name: 'settings.options' }">
|
||||
{{ $t('settings.settings') }}
|
||||
{{ $t('message.settings.settings') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<!-- admin link -->
|
||||
<li v-if="user.isAdmin" class="column">
|
||||
<router-link id="lnkAdmin" :to="{ name: 'admin.appSetup' }" >
|
||||
<span v-if="appSettings.latestRelease && appSettings.checkForUpdate" class="release-flag"></span>
|
||||
{{ $t('admin.admin_panel') }}
|
||||
{{ $t('message.admin.admin_panel') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<!-- sign-out button -->
|
||||
<li v-if="!$2fauth.config.proxyAuth || ($2fauth.config.proxyAuth && $2fauth.config.proxyLogoutUrl)" class="column">
|
||||
<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">
|
||||
{{ $t('auth.sign_out') }}
|
||||
{{ $t('message.auth.sign_out') }}
|
||||
</button>
|
||||
</UseColorMode>
|
||||
</li>
|
||||
@ -90,17 +92,17 @@
|
||||
<div v-else>
|
||||
<!-- settings link -->
|
||||
<router-link id="lnkSettings" :to="{ name: 'settings.options' }" class="has-text-grey">
|
||||
{{ $t('settings.settings') }}
|
||||
{{ $t('message.settings.settings') }}
|
||||
</router-link>
|
||||
<!-- admin link -->
|
||||
<span v-if="user.isAdmin"> -
|
||||
<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>
|
||||
</span>
|
||||
<!-- sign-out button -->
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,22 +1,22 @@
|
||||
<script setup>
|
||||
const tabs = ref([
|
||||
{
|
||||
'name' : 'settings.options',
|
||||
'name' : 'message.settings.options',
|
||||
'view' : 'settings.options',
|
||||
'id' : 'lnkTabOptions'
|
||||
},
|
||||
{
|
||||
'name' : 'settings.account',
|
||||
'name' : 'message.settings.account',
|
||||
'view' : 'settings.account',
|
||||
'id' : 'lnkTabAccount'
|
||||
},
|
||||
{
|
||||
'name' : 'settings.oauth',
|
||||
'name' : 'message.settings.oauth',
|
||||
'view' : 'settings.oauth.tokens',
|
||||
'id' : 'lnkTabOAuth'
|
||||
},
|
||||
{
|
||||
'name' : 'settings.webauthn',
|
||||
'name' : 'message.settings.webauthn',
|
||||
'view' : 'settings.webauthn.devices',
|
||||
'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
|
||||
|
@ -38,7 +38,7 @@ export function identifyAuthenticationError(error, options) {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 16)
|
||||
|
||||
return {
|
||||
phrase: 'errors.aborted_by_user',
|
||||
phrase: 'error.aborted_by_user',
|
||||
type: 'is-warning'
|
||||
}
|
||||
}
|
||||
@ -50,7 +50,7 @@ export function identifyAuthenticationError(error, options) {
|
||||
*/
|
||||
|
||||
return {
|
||||
phrase: 'errors.not_allowed_operation',
|
||||
phrase: 'error.not_allowed_operation',
|
||||
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)
|
||||
|
||||
return {
|
||||
phrase: 'errors.2fauth_has_not_a_valid_domain',
|
||||
phrase: 'error.2fauth_has_not_a_valid_domain',
|
||||
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)
|
||||
|
||||
return {
|
||||
phrase: 'errors.security_error_check_rpid',
|
||||
phrase: 'error.security_error_check_rpid',
|
||||
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)
|
||||
|
||||
return {
|
||||
phrase: 'errors.unknown_error',
|
||||
phrase: 'error.unknown_error',
|
||||
type: 'is-danger'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
phrase: 'errors.unknown_error',
|
||||
phrase: 'error.unknown_error',
|
||||
type: 'is-danger'
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ export function identifyRegistrationError(error, options) {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 16)
|
||||
|
||||
return {
|
||||
phrase: 'errors.aborted_by_user',
|
||||
phrase: 'error.aborted_by_user',
|
||||
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)
|
||||
|
||||
return {
|
||||
phrase: 'errors.authenticator_missing_discoverable_credential_support',
|
||||
phrase: 'error.authenticator_missing_discoverable_credential_support',
|
||||
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)
|
||||
|
||||
return {
|
||||
phrase: 'errors.authenticator_missing_user_verification_support',
|
||||
phrase: 'error.authenticator_missing_user_verification_support',
|
||||
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)
|
||||
|
||||
return {
|
||||
phrase: 'errors.security_device_already_registered',
|
||||
phrase: 'error.security_device_already_registered',
|
||||
type: 'is-danger'
|
||||
}
|
||||
|
||||
@ -80,7 +80,7 @@ export function identifyRegistrationError(error, options) {
|
||||
*/
|
||||
|
||||
return {
|
||||
phrase: 'errors.not_allowed_operation',
|
||||
phrase: 'error.not_allowed_operation',
|
||||
type: 'is-danger'
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ export function identifyRegistrationError(error, options) {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 10)
|
||||
|
||||
return {
|
||||
phrase: 'errors.no_entry_was_of_type_public_key',
|
||||
phrase: 'error.no_entry_was_of_type_public_key',
|
||||
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)
|
||||
|
||||
return {
|
||||
phrase: 'errors.no_authenticator_support_specified_algorithms',
|
||||
phrase: 'error.no_authenticator_support_specified_algorithms',
|
||||
type: 'is-danger'
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ export function identifyRegistrationError(error, options) {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 7)
|
||||
|
||||
return {
|
||||
phrase: 'errors.2fauth_has_not_a_valid_domain',
|
||||
phrase: 'error.2fauth_has_not_a_valid_domain',
|
||||
type: 'is-danger'
|
||||
}
|
||||
|
||||
@ -126,7 +126,7 @@ export function identifyRegistrationError(error, options) {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 8)
|
||||
|
||||
return {
|
||||
phrase: 'errors.security_error_check_rpid',
|
||||
phrase: 'error.security_error_check_rpid',
|
||||
type: 'is-danger'
|
||||
}
|
||||
}
|
||||
@ -137,7 +137,7 @@ export function identifyRegistrationError(error, options) {
|
||||
// https://www.w3.org/TR/webauthn-2/#sctn-createCredential (Step 5)
|
||||
|
||||
return {
|
||||
phrase: 'errors.user_id_not_between_1_64',
|
||||
phrase: 'error.user_id_not_between_1_64',
|
||||
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)
|
||||
|
||||
return {
|
||||
phrase: 'errors.unknown_error',
|
||||
phrase: 'error.unknown_error',
|
||||
type: 'is-danger'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
phrase: 'errors.unknown_error',
|
||||
phrase: 'error.unknown_error',
|
||||
type: 'is-danger'
|
||||
}
|
||||
}
|
||||
|
@ -41,13 +41,13 @@ class WebauthnService {
|
||||
|
||||
// Check https context
|
||||
if (!window.isSecureContext) {
|
||||
err.message = 'errors.https_required'
|
||||
err.message = 'error.https_required'
|
||||
return Promise.reject(err)
|
||||
}
|
||||
|
||||
// Check browser support
|
||||
if (! WebauthnService.supportsWebAuthn) {
|
||||
err.message = 'errors.browser_does_not_support_webauthn'
|
||||
err.message = 'error.browser_does_not_support_webauthn'
|
||||
return Promise.reject(err)
|
||||
}
|
||||
|
||||
@ -80,13 +80,13 @@ class WebauthnService {
|
||||
|
||||
// Check https context
|
||||
if (!window.isSecureContext) {
|
||||
err.message = 'errors.https_required'
|
||||
err.message = 'error.https_required'
|
||||
return Promise.reject(err)
|
||||
}
|
||||
|
||||
// Check browser support
|
||||
if (! WebauthnService.supportsWebAuthn) {
|
||||
err.message = 'errors.browser_does_not_support_webauthn'
|
||||
err.message = 'error.browser_does_not_support_webauthn'
|
||||
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 => {
|
||||
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) {
|
||||
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) {
|
||||
@ -47,11 +47,11 @@ export const useGroups = defineStore({
|
||||
|
||||
if (index > -1) {
|
||||
this.items[index] = group
|
||||
useNotifyStore().success({ text: trans('groups.group_name_saved') })
|
||||
useNotifyStore().success({ text: t('message.groups.group_name_saved') })
|
||||
}
|
||||
else {
|
||||
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) {
|
||||
const user = useUserStore()
|
||||
|
||||
if (confirm(trans('groups.confirm.delete'))) {
|
||||
if (confirm(t('message.groups.confirm.delete'))) {
|
||||
await groupService.delete(id).then(response => {
|
||||
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
|
||||
// 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: {
|
||||
parseError(err) {
|
||||
// TODO : use the new Notify component
|
||||
// const { t } = useI18n()
|
||||
|
||||
this.$reset
|
||||
this.err = err
|
||||
|
||||
// Hnalde axios response error
|
||||
if (err.response) {
|
||||
if (err.response.status === 407) {
|
||||
this.message = trans('errors.auth_proxy_failed'),
|
||||
this.originalMessage = trans('errors.auth_proxy_failed_legend')
|
||||
this.message = t('error.auth_proxy_failed'),
|
||||
this.originalMessage = t('error.auth_proxy_failed_legend')
|
||||
}
|
||||
else if (err.response.status === 403) {
|
||||
this.message = trans('errors.unauthorized'),
|
||||
this.originalMessage = trans('errors.unauthorized_legend')
|
||||
this.message = t('error.unauthorized'),
|
||||
this.originalMessage = t('error.unauthorized_legend')
|
||||
}
|
||||
else if(err.response.data) {
|
||||
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
|
||||
*/
|
||||
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())
|
||||
.then(response => {
|
||||
let remainingItems = this.items
|
||||
@ -155,7 +155,7 @@ export const useTwofaccounts = defineStore({
|
||||
})
|
||||
this.items = remainingItems
|
||||
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(() => {
|
||||
if (kicked) {
|
||||
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()
|
||||
})
|
||||
@ -118,24 +118,16 @@ export const useUserStore = defineStore({
|
||||
*/
|
||||
applyLanguage() {
|
||||
const { isSupported, language } = useNavigatorLanguage()
|
||||
let lang = 'en'
|
||||
let lang = this.$i18n.fallbackLocale
|
||||
|
||||
if (isSupported) {
|
||||
if (this.preferences.lang == 'browser') {
|
||||
if (this.$2fauth.langs.includes(language.value)) {
|
||||
lang = language.value
|
||||
}
|
||||
// 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
|
||||
// The language tag pushed by the browser may be composed of
|
||||
// multiple subtags (ex: fr-FR) so we keep only the
|
||||
// "language subtag" (ex: fr)
|
||||
lang = this.preferences.lang == 'browser' ? language.value.slice(0, 2) : this.preferences.lang
|
||||
}
|
||||
|
||||
loadLanguageAsync(lang)
|
||||
this.$i18n.global.locale = lang
|
||||
},
|
||||
|
||||
/**
|
||||
@ -168,7 +160,7 @@ export const useUserStore = defineStore({
|
||||
})
|
||||
.catch(error => {
|
||||
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 user = useUserStore()
|
||||
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>
|
||||
|
||||
<template>
|
||||
<ResponsiveWidthWrapper>
|
||||
<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">
|
||||
<span :class="mode == 'dark' ? 'has-text-white':'has-text-black'">
|
||||
<span class="is-size-5">2FAuth</span>
|
||||
<span v-if="user.isAuthenticated"> v{{ $2fauth.version }}</span>
|
||||
</span>
|
||||
<br />
|
||||
{{ $t('commons.2fauth_teaser')}}
|
||||
{{ $t('message.2fauth_teaser')}}
|
||||
</p>
|
||||
<img class="about-logo" src="logo.svg" alt="2FAuth logo" />
|
||||
<p class="block">
|
||||
©Bubka <a class="is-size-7" href="https://github.com/Bubka/2FAuth/blob/master/LICENSE">AGPL-3.0 license</a>
|
||||
</p>
|
||||
<h2 class="title is-5 has-text-grey-light">
|
||||
{{ $t('commons.resources') }}
|
||||
{{ $t('message.resources') }}
|
||||
</h2>
|
||||
<div class="buttons">
|
||||
<a class="button" :class="{'is-dark' : mode == 'dark'}" href="https://github.com/Bubka/2FAuth" target="_blank">
|
||||
@ -54,12 +56,12 @@
|
||||
</a>
|
||||
</div>
|
||||
<h2 class="title is-5 has-text-grey-light">
|
||||
{{ $t('commons.credits') }}
|
||||
{{ $t('message.credits') }}
|
||||
</h2>
|
||||
<ul>
|
||||
<li>{{ $t('commons.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('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.made_with') }} <a href="https://docs.2fauth.app/credits/">Laravel, Bulma CSS, Vue.js and more</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('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>
|
||||
<!-- footer -->
|
||||
<VueFooter :showButtons="true">
|
||||
|
@ -1,6 +1,8 @@
|
||||
<script setup>
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const errorHandler = useNotifyStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@ -23,7 +25,7 @@
|
||||
|
||||
onMounted(() => {
|
||||
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">
|
||||
<div class="error-message" v-if="$route.name == '404' || $route.name == 'notFound'">
|
||||
<p class="error-404"></p>
|
||||
<p>{{ $t('errors.resource_not_found') }}</p>
|
||||
<p>{{ $t('error.resource_not_found') }}</p>
|
||||
</div>
|
||||
<div v-else class="error-message" >
|
||||
<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.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>
|
||||
|
@ -64,8 +64,8 @@
|
||||
<div class="columns quick-uploader">
|
||||
<!-- trailer phrase that invite to add an account -->
|
||||
<div class="column is-full quick-uploader-header" :class="{ 'is-invisible' : twofaccounts.count !== 0 }">
|
||||
{{ $t('twofaccounts.no_account_here') }}<br>
|
||||
{{ $t('twofaccounts.add_first_account') }}
|
||||
{{ $t('message.twofaccounts.no_account_here') }}<br>
|
||||
{{ $t('message.twofaccounts.add_first_account') }}
|
||||
</div>
|
||||
<!-- Livescan button -->
|
||||
<div class="column is-full quick-uploader-button" >
|
||||
@ -73,35 +73,35 @@
|
||||
<!-- 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()">
|
||||
<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>
|
||||
<!-- scan button that launch camera stream -->
|
||||
<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>
|
||||
</div>
|
||||
<FormFieldError v-if="form.errors.hasAny('qrcode')" :error="form.errors.get('qrcode')" :field="'qrcode'" />
|
||||
</div>
|
||||
<!-- alternative methods -->
|
||||
<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 -->
|
||||
<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()">
|
||||
<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>
|
||||
</div>
|
||||
<!-- link to advanced form -->
|
||||
<div class="block has-text-link">
|
||||
<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>
|
||||
</div>
|
||||
<!-- link to import view -->
|
||||
<div class="block has-text-link">
|
||||
<RouterLink id="btnImport" class="button is-link is-outlined is-rounded" :to="{ name: 'importAccounts' }" >
|
||||
{{ $t('twofaccounts.import.import') }}
|
||||
{{ $t('message.twofaccounts.import.import') }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,9 @@
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import VersionChecker from '@/components/VersionChecker.vue'
|
||||
import CopyButton from '@/components/CopyButton.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const user = useUserStore()
|
||||
const notify = useNotifyStore()
|
||||
@ -40,7 +42,7 @@
|
||||
isClearingCache.value = true;
|
||||
|
||||
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(() => {
|
||||
isClearingCache.value = false;
|
||||
@ -72,15 +74,21 @@
|
||||
<div class="options-tabs">
|
||||
<FormWrapper>
|
||||
<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 -->
|
||||
<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 />
|
||||
<!-- email config test -->
|
||||
<div class="field">
|
||||
<label class="label" for="btnTestEmail" v-html="$t('admin.forms.test_email.label')" />
|
||||
<p class="help" v-html="$t('admin.forms.test_email.help')" />
|
||||
<p class="help" v-html="$t('admin.forms.test_email.email_will_be_send_to_x', { email: user.email })" />
|
||||
<label class="label" for="btnTestEmail" v-html="$t('message.admin.forms.test_email.label')" />
|
||||
<p class="help" v-html="$t('message.admin.forms.test_email.help')" />
|
||||
<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 class="columns is-mobile is-vcentered">
|
||||
<div class="column is-narrow">
|
||||
@ -88,43 +96,43 @@
|
||||
<span class="icon is-small">
|
||||
<FontAwesomeIcon :icon="['far', 'paper-plane']" />
|
||||
</span>
|
||||
<span>{{ $t('commons.send') }}</span>
|
||||
<span>{{ $t('message.send') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- healthcheck -->
|
||||
<div class="field">
|
||||
<label class="label" for="lnkHealthCheck" v-html="$t('admin.forms.health_endpoint.label')" />
|
||||
<p class="help" v-html="$t('admin.forms.health_endpoint.help')" />
|
||||
<label class="label" for="lnkHealthCheck" v-html="$t('message.admin.forms.health_endpoint.label')" />
|
||||
<p class="help" v-html="$t('message.admin.forms.health_endpoint.help')" />
|
||||
</div>
|
||||
<div class="field mb-5">
|
||||
<a id="lnkHealthCheck" target="_blank" :href="healthEndPoint">{{ healthEndPointFullPath }}</a>
|
||||
</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 -->
|
||||
<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" />
|
||||
<h4 class="title is-4 pt-5 has-text-grey-light">{{ $t('settings.security') }}</h4>
|
||||
<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('message.settings.security') }}</h4>
|
||||
<!-- 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>
|
||||
|
||||
<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 -->
|
||||
<div class="field">
|
||||
<!-- <h5 class="title is-5">{{ $t('settings.security') }}</h5> -->
|
||||
<label for="btnClearCache" class="label" v-html="$t('admin.forms.cache_management.label')" />
|
||||
<p class="help" v-html="$t('admin.forms.cache_management.help')" />
|
||||
<!-- <h5 class="title is-5">{{ $t('message.settings.security') }}</h5> -->
|
||||
<label for="btnClearCache" class="label" v-html="$t('message.admin.forms.cache_management.label')" />
|
||||
<p class="help" v-html="$t('message.admin.forms.cache_management.help')" />
|
||||
</div>
|
||||
<div class="field mb-5 is-grouped">
|
||||
<p class="control">
|
||||
<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>
|
||||
</p>
|
||||
</div>
|
||||
<!-- env vars -->
|
||||
<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 v-if="infos" class="about-debug box is-family-monospace is-size-7">
|
||||
<CopyButton id="btnCopyEnvVars" :token="listInfos?.innerText" />
|
||||
@ -135,7 +143,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
<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>
|
||||
</FormWrapper>
|
||||
</div>
|
||||
|
@ -4,7 +4,9 @@
|
||||
import { useAppSettingsUpdater } from '@/composables/appSettingsUpdater'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const notify = useNotifyStore()
|
||||
const appSettings = useAppSettingsStore()
|
||||
@ -40,7 +42,7 @@
|
||||
if (value == '') {
|
||||
appSettingService.delete(setting, { returnError: true }).then(response => {
|
||||
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 => {
|
||||
if( error.response.status !== 404 ) {
|
||||
@ -75,24 +77,24 @@
|
||||
<div class="options-tabs">
|
||||
<FormWrapper>
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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" />
|
||||
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('admin.registrations') }}</h4>
|
||||
<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('message.admin.registrations') }}</h4>
|
||||
<!-- 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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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>
|
||||
</FormWrapper>
|
||||
</div>
|
||||
|
@ -108,25 +108,25 @@
|
||||
<AdminTabs activeTab="admin.users" />
|
||||
<div class="options-tabs">
|
||||
<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">
|
||||
{{ $t('admin.users_legend')}}
|
||||
{{ $t('message.admin.users_legend')}}
|
||||
</div>
|
||||
<div class="mb-6 mt-3">
|
||||
<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>
|
||||
</div>
|
||||
<!-- search -->
|
||||
<div class="columns">
|
||||
<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 class="level is-mobile mb-0">
|
||||
<div class="level-item has-text-centered is-justify-content-end">
|
||||
<p class="subtitle is-7">
|
||||
{{ $t('admin.quick_filters_colons') }}
|
||||
{{ $t('message.admin.quick_filters_colons') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="level-item has-text-centered is-justify-content-start">
|
||||
@ -154,19 +154,19 @@
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<!-- 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')">
|
||||
{{ $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('message.manage') }}
|
||||
</RouterLink>
|
||||
|
||||
</div>
|
||||
</UseColorMode>
|
||||
</div>
|
||||
<!-- <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 v-else class="mt-4 pl-3">
|
||||
{{ $t('commons.no_result') }}
|
||||
{{ $t('message.no_result') }}
|
||||
</div>
|
||||
<Spinner :isVisible="isFetching && users.length === 0" />
|
||||
<!-- footer -->
|
||||
|
@ -32,10 +32,10 @@
|
||||
<template>
|
||||
<ResponsiveWidthWrapper>
|
||||
<h1 class="title has-text-grey-dark">
|
||||
{{ $t('titles.admin.logs.access') }}
|
||||
{{ $t('title.admin.logs.access') }}
|
||||
</h1>
|
||||
<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>
|
||||
<AccessLogViewer :userId="props.userId" :lastOnly="false" :showSearch="true" :period="1" />
|
||||
<!-- footer -->
|
||||
|
@ -1,7 +1,9 @@
|
||||
<script setup>
|
||||
import Form from '@/components/formElements/Form'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const notify = useNotifyStore()
|
||||
const router = useRouter()
|
||||
|
||||
@ -21,7 +23,7 @@
|
||||
|
||||
registerForm.post('/api/v1/users').then(response => {
|
||||
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 } })
|
||||
})
|
||||
}
|
||||
@ -29,13 +31,13 @@
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<FormWrapper title="admin.new_user">
|
||||
<FormWrapper title="message.admin.new_user">
|
||||
<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.email" fieldName="email" :errorMessage="registerForm.errors.get('email')" inputType="email" label="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" />
|
||||
<FormCheckbox v-model="registerForm.is_admin" fieldName="is_admin" label="admin.forms.is_admin.label" help="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" />
|
||||
<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="message.auth.forms.email" autocomplete="email" :maxLength="255" />
|
||||
<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="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="message.create" submitId="btnCreateUser" />
|
||||
</form>
|
||||
</FormWrapper>
|
||||
<!-- footer -->
|
||||
|
@ -6,7 +6,9 @@
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useBusStore } from '@/stores/bus'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const notify = useNotifyStore()
|
||||
const router = useRouter()
|
||||
const user = useUserStore()
|
||||
@ -53,7 +55,7 @@
|
||||
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)
|
||||
managedUser.value.password_reset = null
|
||||
}
|
||||
@ -67,11 +69,11 @@
|
||||
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 })
|
||||
.then(response => {
|
||||
managedUser.value = response.data
|
||||
notify.success({ text: trans('admin.password_successfully_reset') })
|
||||
notify.success({ text: t('message.admin.password_successfully_reset') })
|
||||
})
|
||||
.catch(error => {
|
||||
if(error.response.status === 400) {
|
||||
@ -89,7 +91,7 @@
|
||||
* @param {boolean} isAdmin
|
||||
*/
|
||||
function saveAdminRole(isAdmin) {
|
||||
if (! confirm(trans('admin.confirm.change_admin_role'))) {
|
||||
if (! confirm(t('message.admin.confirm.change_admin_role'))) {
|
||||
nextTick().then(() => {
|
||||
managedUser.value.info.is_admin = ! isAdmin
|
||||
})
|
||||
@ -97,7 +99,7 @@
|
||||
}
|
||||
|
||||
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(() => {
|
||||
managedUser.value.info.is_admin = true
|
||||
})
|
||||
@ -107,7 +109,7 @@
|
||||
|
||||
userService.promote(managedUser.value.info.id, { 'is_admin': isAdmin }, { returnError: true }).then(response => {
|
||||
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 => {
|
||||
if( error.response.status === 403 ) {
|
||||
@ -128,9 +130,9 @@
|
||||
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 => {
|
||||
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' });
|
||||
})
|
||||
.catch(error => {
|
||||
@ -154,7 +156,7 @@
|
||||
|
||||
userService.revokePATs(managedUser.value.info.id).then(response => {
|
||||
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 => {
|
||||
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() {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -191,7 +193,7 @@
|
||||
<UseColorMode v-slot="{ mode }">
|
||||
<ResponsiveWidthWrapper>
|
||||
<h1 class="title has-text-grey-dark mb-6">
|
||||
{{ $t('admin.user_management') }}
|
||||
{{ $t('message.admin.user_management') }}
|
||||
</h1>
|
||||
<!-- loader -->
|
||||
<div v-if="isFetching || ! managedUser" class="has-text-centered">
|
||||
@ -207,57 +209,57 @@
|
||||
</div>
|
||||
<!-- oauth banner -->
|
||||
<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 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>
|
||||
<!-- isAdmin option -->
|
||||
<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>
|
||||
<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 -->
|
||||
<div v-if="!$2fauth.config.proxyAuth" class="block">
|
||||
<!-- reset password -->
|
||||
<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>
|
||||
<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 class="tags ml-3 is-right">
|
||||
<!-- 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')">
|
||||
{{ $t('admin.resend_email') }}
|
||||
<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('message.admin.resend_email') }}
|
||||
</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')">
|
||||
{{ $t('admin.reset_password') }}
|
||||
<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('message.admin.reset_password') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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-else-if="managedUser.password_reset" v-html="$t('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-html="$t('admin.reset_password_help')" 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('message.admin.password_requested_on_t', { datetime: managedUser.password_reset })" 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('message.admin.reset_password_help')" class="is-block block"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 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>
|
||||
<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">
|
||||
{{ $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>
|
||||
</div>
|
||||
<div v-if="managedUser.valid_personal_access_tokens > 0">
|
||||
<div class="tags ml-3 is-right">
|
||||
<!-- 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')">
|
||||
{{ $t('settings.revoke') }}
|
||||
<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('message.settings.revoke') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -265,16 +267,16 @@
|
||||
<!-- webauthn devices -->
|
||||
<div class="list-item is-size-6 is-size-6-mobile has-text-grey is-flex is-justify-content-space-between">
|
||||
<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">
|
||||
{{ $t('admin.user_has_x_security_devices', { count: managedUser.webauthn_credentials }) }}
|
||||
{{ $t('message.admin.user_has_x_security_devices', { count: managedUser.webauthn_credentials }) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="managedUser.webauthn_credentials > 0">
|
||||
<div class="tags ml-3 is-right">
|
||||
<!-- 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')">
|
||||
{{ $t('settings.revoke') }}
|
||||
<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('message.settings.revoke') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -282,16 +284,16 @@
|
||||
</div>
|
||||
<!-- last access -->
|
||||
<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"/>
|
||||
</div>
|
||||
<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('admin.see_full_log') }}.
|
||||
{{ $t('message.admin.access_log_has_more_entries') }} <router-link id="lnkFullLogs" :to="{ name: 'admin.logs.access', params: { userId: props.userId }}" >
|
||||
{{ $t('message.admin.see_full_log') }}.
|
||||
</router-link>
|
||||
</div>
|
||||
<!-- 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">
|
||||
<CopyButton id="btnCopyEnvVars" :token="listUserPreferences?.innerText" />
|
||||
<ul ref="listUserPreferences" id="listUserPreferences">
|
||||
@ -301,16 +303,16 @@
|
||||
</ul>
|
||||
</div>
|
||||
<!-- 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="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">
|
||||
{{ $t('admin.this_is_not_soft_delete') }}
|
||||
{{ $t('message.admin.this_is_not_soft_delete') }}
|
||||
</span>
|
||||
</div>
|
||||
<button type="button" class="button is-danger" @click="deleteUser" title="delete">
|
||||
{{ $t('admin.delete_this_user') }}
|
||||
{{ $t('message.admin.delete_this_user') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,7 +5,9 @@
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { webauthnService } from '@/services/webauthn/webauthnService'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const router = useRouter()
|
||||
const user = useUserStore()
|
||||
@ -62,7 +64,7 @@
|
||||
})
|
||||
.catch(error => {
|
||||
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 ) {
|
||||
notify.error(error)
|
||||
@ -97,12 +99,12 @@
|
||||
.catch(error => {
|
||||
if ('webauthn' in error) {
|
||||
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 ) {
|
||||
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 ) {
|
||||
form.errors.set(form.extractErrors(error.response))
|
||||
@ -120,36 +122,36 @@
|
||||
|
||||
<template>
|
||||
<!-- webauthn authentication -->
|
||||
<FormWrapper v-if="activeForm == 'webauthn'" title="auth.forms.webauthn_login" punchline="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')" />
|
||||
<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('message.auth.forms.sso_only_form_restricted_to_admin')" />
|
||||
<div class="field">
|
||||
{{ $t('auth.webauthn.use_security_device_to_sign_in') }}
|
||||
{{ $t('message.auth.webauthn.use_security_device_to_sign_in') }}
|
||||
</div>
|
||||
<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 />
|
||||
<FormButtons :isBusy="isBusy" submitLabel="commons.continue" submitId="btnContinue"/>
|
||||
<FormField v-model="form.email" fieldName="email" :errorMessage="form.errors.get('email')" inputType="email" label="message.auth.forms.email" autofocus />
|
||||
<FormButtons :isBusy="isBusy" submitLabel="message.continue" submitId="btnContinue"/>
|
||||
</form>
|
||||
<div class="nav-links">
|
||||
<p>
|
||||
{{ $t('auth.webauthn.lost_your_device') }}
|
||||
{{ $t('message.auth.webauthn.lost_your_device') }}
|
||||
<RouterLink id="lnkRecoverAccount" :to="{ name: 'webauthn.lost' }" class="is-link">
|
||||
{{ $t('auth.webauthn.recover_your_account') }}
|
||||
{{ $t('message.auth.webauthn.recover_your_account') }}
|
||||
</RouterLink>
|
||||
</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">
|
||||
{{ $t('auth.login_and_password') }}
|
||||
{{ $t('message.auth.login_and_password') }}
|
||||
</a>
|
||||
</p>
|
||||
<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">
|
||||
{{ $t('auth.register') }}
|
||||
{{ $t('message.auth.register') }}
|
||||
</RouterLink>
|
||||
</p>
|
||||
<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">
|
||||
{{ $t('auth.or_continue_with') }}
|
||||
{{ $t('message.auth.or_continue_with') }}
|
||||
</div>
|
||||
<div class="column py-1">
|
||||
<div class="buttons">
|
||||
@ -162,63 +164,104 @@
|
||||
</div>
|
||||
</FormWrapper>
|
||||
<!-- SSO only links -->
|
||||
<FormWrapper v-else-if="activeForm == 'sso'" title="auth.forms.sso_login" punchline="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.isTestingApp" class="notification is-warning has-text-centered is-radiusless" v-html="$t('auth.forms.welcome_to_testing_app_use_those_credentials')" />
|
||||
<!-- TODO: compléter la punchline avec les lignes manquantes de auth.webauthn.webauthn_uses_trusted_devices -->
|
||||
<FormWrapper v-else-if="activeForm == 'sso'" title="message.auth.forms.sso_login" punchline="message.auth.welcome_to_2fauth">
|
||||
<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">
|
||||
<p class="">{{ $t('auth.password_login_and_webauthn_are_disabled') }}</p>
|
||||
<p class="">{{ $t('auth.sign_in_using_sso') }}</p>
|
||||
<p class="">{{ $t('message.auth.password_login_and_webauthn_are_disabled') }}</p>
|
||||
<p class="">{{ $t('message.auth.sign_in_using_sso') }}</p>
|
||||
</div>
|
||||
<div v-if="Object.values($2fauth.config.sso).includes(true)" class="buttons mt-4">
|
||||
<template v-for="(isEnabled, provider) in $2fauth.config.sso">
|
||||
<SsoConnectLink v-if="isEnabled" :provider="provider" />
|
||||
</template>
|
||||
</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">
|
||||
<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">
|
||||
{{ $t('auth.see_how_to_enable_sso') }}
|
||||
{{ $t('message.auth.see_how_to_enable_sso') }}
|
||||
</a>
|
||||
</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">
|
||||
{{ $t('auth.sign_in_here') }}
|
||||
{{ $t('message.auth.sign_in_here') }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</FormWrapper>
|
||||
<!-- login/password legacy form -->
|
||||
<FormWrapper v-else-if="activeForm == 'legacy'" title="auth.forms.login" punchline="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.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="appSettings.enableSso == true && appSettings.useSsoOnly == true" class="notification is-warning has-text-centered" v-html="$t('auth.forms.sso_only_form_restricted_to_admin')" />
|
||||
<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">
|
||||
{{ $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 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)">
|
||||
<FormField v-model="form.email" fieldName="email" :errorMessage="form.errors.get('email')" inputType="email" label="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" />
|
||||
<FormButtons :isBusy="isBusy" submitLabel="auth.sign_in" submitId="btnSignIn"/>
|
||||
<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="message.auth.forms.password" autocomplete="current-password" />
|
||||
<FormButtons :isBusy="isBusy" submitLabel="message.auth.sign_in" submitId="btnSignIn"/>
|
||||
</form>
|
||||
<div class="nav-links">
|
||||
<p>{{ $t('auth.forms.forgot_your_password') }}
|
||||
<RouterLink id="lnkResetPwd" :to="{ name: 'password.request' }" class="is-link" :aria-label="$t('auth.forms.reset_your_password')">
|
||||
{{ $t('auth.forms.request_password_reset') }}
|
||||
<p>{{ $t('message.auth.forms.forgot_your_password') }}
|
||||
<RouterLink id="lnkResetPwd" :to="{ name: 'password.request' }" class="is-link" :aria-label="$t('message.auth.forms.reset_your_password')">
|
||||
{{ $t('message.auth.forms.request_password_reset') }}
|
||||
</RouterLink>
|
||||
</p>
|
||||
<p >{{ $t('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')">
|
||||
{{ $t('auth.webauthn.security_device') }}
|
||||
<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('message.auth.sign_in_using_security_device')">
|
||||
{{ $t('message.auth.webauthn.security_device') }}
|
||||
</a>
|
||||
</p>
|
||||
<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">
|
||||
{{ $t('auth.register') }}
|
||||
{{ $t('message.auth.register') }}
|
||||
</RouterLink>
|
||||
</p>
|
||||
<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">
|
||||
{{ $t('auth.or_continue_with') }}
|
||||
{{ $t('message.auth.or_continue_with') }}
|
||||
</div>
|
||||
<div class="column py-1">
|
||||
<div class="buttons">
|
||||
|
@ -3,7 +3,9 @@
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { webauthnService } from '@/services/webauthn/webauthnService'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const user = useUserStore()
|
||||
const notify = useNotifyStore()
|
||||
const router = useRouter()
|
||||
@ -66,7 +68,7 @@
|
||||
function RenameDevice(e) {
|
||||
renameDeviceForm.patch('/webauthn/credentials/' + deviceId.value + '/name')
|
||||
.then(() => {
|
||||
notify.success({ text: trans('auth.webauthn.device_successfully_registered') })
|
||||
notify.success({ text: t('message.auth.webauthn.device_successfully_registered') })
|
||||
router.push({ name: 'accounts' })
|
||||
})
|
||||
}
|
||||
@ -79,35 +81,36 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 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">
|
||||
<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)">
|
||||
<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" />
|
||||
<FormButtons :isBusy="renameDeviceForm.isBusy" :isDisabled="renameDeviceForm.isDisabled" submitLabel="commons.continue" />
|
||||
<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="message.continue" />
|
||||
</form>
|
||||
</div>
|
||||
<div v-else class="field is-grouped">
|
||||
<!-- register button -->
|
||||
<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>
|
||||
<!-- dismiss button -->
|
||||
<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>
|
||||
</FormWrapper>
|
||||
<!-- 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)">
|
||||
<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.email" fieldName="email" :errorMessage="registerForm.errors.get('email')" inputType="email" label="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" />
|
||||
<FormButtons :isBusy="registerForm.isBusy" :isDisabled="registerForm.isDisabled" submitLabel="auth.register" submitId="btnRegister" />
|
||||
<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="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="message.auth.forms.password" />
|
||||
<FormButtons :isBusy="registerForm.isBusy" :isDisabled="registerForm.isDisabled" submitLabel="message.auth.register" submitId="btnRegister" />
|
||||
</form>
|
||||
<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>
|
||||
</FormWrapper>
|
||||
<!-- footer -->
|
||||
|
@ -36,13 +36,14 @@
|
||||
</script>
|
||||
|
||||
<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)">
|
||||
<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
|
||||
:submitId="'btnSendResetPwd'"
|
||||
: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"
|
||||
@cancel="router.push({ name: 'login' })" />
|
||||
</form>
|
||||
|
@ -43,19 +43,19 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormWrapper :title="$t('auth.forms.new_password')">
|
||||
<FormWrapper title="message.auth.forms.new_password">
|
||||
<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 />
|
||||
<FormPasswordField v-model="form.password" fieldName="password" :errorMessage="form.errors.get('password')" autocomplete="new-password" :showRules="true" label="auth.forms.new_password" />
|
||||
<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="message.auth.forms.new_password" />
|
||||
<FormFieldError v-if="form.errors.get('token') != undefined" :error="form.errors.get('token')" :field="form.token" />
|
||||
<FormButtons
|
||||
v-if="isPending"
|
||||
:submitId="'btnResetPwd'"
|
||||
:isBusy="form.isBusy"
|
||||
submitLabel="auth.forms.change_password"
|
||||
submitLabel="message.auth.forms.change_password"
|
||||
:showCancelButton="true"
|
||||
@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>
|
||||
<VueFooter />
|
||||
</FormWrapper>
|
||||
|
@ -1,7 +1,9 @@
|
||||
<script setup>
|
||||
import Form from '@/components/formElements/Form'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const notify = useNotifyStore()
|
||||
const router = useRouter()
|
||||
@ -27,7 +29,7 @@
|
||||
})
|
||||
.catch(error => {
|
||||
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) {
|
||||
notify.alert({ text: error.response.data.message, duration:-1 })
|
||||
@ -44,16 +46,16 @@
|
||||
</script>
|
||||
|
||||
<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>
|
||||
<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" />
|
||||
<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" />
|
||||
<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="message.auth.forms.current_password.label" help="message.auth.forms.current_password.help" />
|
||||
<div class="field">
|
||||
<p>
|
||||
{{ $t('auth.forms.forgot_your_password') }}
|
||||
<RouterLink id="lnkResetPwd" :to="{ name: 'password.request' }" class="is-link" :aria-label="$t('auth.forms.reset_your_password')">
|
||||
{{ $t('auth.forms.request_password_reset') }}
|
||||
{{ $t('message.auth.forms.forgot_your_password') }}
|
||||
<RouterLink id="lnkResetPwd" :to="{ name: 'password.request' }" class="is-link" :aria-label="$t('message.auth.forms.reset_your_password')">
|
||||
{{ $t('message.auth.forms.request_password_reset') }}
|
||||
</RouterLink>
|
||||
</p>
|
||||
</div>
|
||||
@ -61,7 +63,7 @@
|
||||
:submitId="'btnRecover'"
|
||||
:isBusy="form.isBusy"
|
||||
:isDisabled="form.isDisabled"
|
||||
submitLabel="commons.continue"
|
||||
submitLabel="message.continue"
|
||||
:showCancelButton="true"
|
||||
@cancel="router.push({ name: 'login' })" />
|
||||
</form>
|
||||
|
@ -66,13 +66,13 @@
|
||||
</script>
|
||||
|
||||
<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)">
|
||||
<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
|
||||
:submitId="isEditMode ? 'btnEditGroup' : 'btnCreateGroup'"
|
||||
:isBusy="form.isBusy"
|
||||
:submitLabel="isEditMode ? 'commons.save' : 'commons.create'"
|
||||
:submitLabel="isEditMode ? 'message.save' : 'message.create'"
|
||||
:showCancelButton="true"
|
||||
@cancel="router.push({ name: 'groups' })" />
|
||||
</form>
|
||||
|
@ -34,14 +34,14 @@
|
||||
<template>
|
||||
<ResponsiveWidthWrapper>
|
||||
<h1 class="title has-text-grey-dark">
|
||||
{{ $t('groups.groups') }}
|
||||
{{ $t('message.groups.groups') }}
|
||||
</h1>
|
||||
<div class="is-size-7-mobile">
|
||||
{{ $t('groups.manage_groups_legend')}}
|
||||
{{ $t('message.groups.manage_groups_legend')}}
|
||||
</div>
|
||||
<div class="mt-3 mb-6">
|
||||
<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>
|
||||
</div>
|
||||
<div v-if="!groups.isEmpty">
|
||||
@ -49,18 +49,18 @@
|
||||
{{ group.name }}
|
||||
<!-- delete icon -->
|
||||
<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')">
|
||||
{{ $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('message.delete') }}
|
||||
</button>
|
||||
</UseColorMode>
|
||||
<!-- 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']" />
|
||||
</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 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 v-if="isFetching && groups.isEmpty" class="has-text-centered">
|
||||
|
@ -3,7 +3,9 @@
|
||||
import SettingTabs from '@/layouts/SettingTabs.vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const user = useUserStore()
|
||||
const notify = useNotifyStore()
|
||||
@ -36,7 +38,7 @@
|
||||
email: response.data.email,
|
||||
isAdmin: response.data.is_admin,
|
||||
})
|
||||
notify.success({ text: trans('auth.forms.profile_saved') })
|
||||
notify.success({ text: t('message.auth.forms.profile_saved') })
|
||||
})
|
||||
.catch(error => {
|
||||
if( error.response.status === 400 ) {
|
||||
@ -75,10 +77,10 @@
|
||||
*/
|
||||
function submitDelete(e) {
|
||||
|
||||
if(confirm(trans('auth.confirm.delete_account'))) {
|
||||
if(confirm(t('message.auth.confirm.delete_account'))) {
|
||||
formDelete.delete('/user', {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: 'register' });
|
||||
})
|
||||
.catch(error => {
|
||||
@ -105,44 +107,46 @@
|
||||
<div class="options-tabs">
|
||||
<FormWrapper>
|
||||
<div v-if="user.isAdmin" class="notification is-warning">
|
||||
{{ $t('settings.you_are_administrator') }}
|
||||
{{ $t('message.settings.you_are_administrator') }}
|
||||
</div>
|
||||
<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>
|
||||
<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')" />
|
||||
<h4 class="title is-4 has-text-grey-light">{{ $t('settings.profile') }}</h4>
|
||||
<div v-if="$2fauth.config.proxyAuth" class="notification is-warning has-text-centered">
|
||||
{{ $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">
|
||||
<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.email" fieldName="email" :errorMessage="formProfile.errors.get('email')" inputType="email" label="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" />
|
||||
<FormButtons :isBusy="formProfile.isBusy" submitLabel="commons.update" />
|
||||
<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="message.auth.forms.email" autocomplete="email" :maxLength="255" autofocus />
|
||||
<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="message.update" />
|
||||
</fieldset>
|
||||
</form>
|
||||
<form @submit.prevent="submitPassword" @keydown="formPassword.onKeydown($event)">
|
||||
<input hidden type="text" name="name" :value="formProfile.name" autocomplete="username" />
|
||||
<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">
|
||||
<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_confirmation" :showRules="false" fieldName="password_confirmation" :errorMessage="formPassword.errors.get('password_confirmation')" inputType="password" autocomplete="new-password" label="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" />
|
||||
<FormButtons :isBusy="formPassword.isBusy" submitId="btnSubmitChangePwd" submitLabel="auth.forms.change_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="message.auth.forms.confirm_new_password" />
|
||||
<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="message.auth.forms.change_password" />
|
||||
</fieldset>
|
||||
</form>
|
||||
<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="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">
|
||||
<p class="block">{{ $t('auth.forms.delete_your_account_and_reset_all_data')}}</p>
|
||||
<p>{{ $t('auth.forms.reset_your_password_to_delete_your_account') }}</p>
|
||||
<p>{{ $t('auth.forms.deleting_2fauth_account_does_not_impact_provider') }}</p>
|
||||
<p class="block">{{ $t('message.auth.forms.delete_your_account_and_reset_all_data')}}</p>
|
||||
<p>{{ $t('message.auth.forms.reset_your_password_to_delete_your_account') }}</p>
|
||||
<p>{{ $t('message.auth.forms.deleting_2fauth_account_does_not_impact_provider') }}</p>
|
||||
</div>
|
||||
<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" />
|
||||
<FormButtons :isBusy="formDelete.isBusy" submitLabel="auth.forms.delete_your_account" submitId="btnDeleteAccount" color="is-danger" />
|
||||
<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="message.auth.forms.delete_your_account" submitId="btnDeleteAccount" color="is-danger" />
|
||||
</fieldset>
|
||||
</form>
|
||||
</FormWrapper>
|
||||
|
@ -1,11 +1,13 @@
|
||||
<script setup>
|
||||
import Form from '@/components/formElements/Form'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const notify = useNotifyStore()
|
||||
const form = reactive(new Form({
|
||||
name: trans('auth.webauthn.my_device')
|
||||
name: t('message.auth.webauthn.my_device')
|
||||
}))
|
||||
|
||||
const props = defineProps({
|
||||
@ -18,7 +20,7 @@
|
||||
function updateCredential() {
|
||||
form.patch('/webauthn/credentials/' + props.credentialId + '/name')
|
||||
.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' })
|
||||
})
|
||||
}
|
||||
@ -26,13 +28,13 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormWrapper title="auth.webauthn.rename_device">
|
||||
<FormWrapper title="message.auth.webauthn.rename_device">
|
||||
<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
|
||||
:submitId="'btnEditCredential'"
|
||||
:isBusy="form.isBusy"
|
||||
submitLabel="commons.save"
|
||||
submitLabel="message.save"
|
||||
:showCancelButton="true"
|
||||
@cancel="router.push({ name: 'settings.webauthn.devices' })"
|
||||
/>
|
||||
|
@ -7,7 +7,9 @@
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import Spinner from '@/components/Spinner.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appSettings = useAppSettingsStore()
|
||||
const $2fauth = inject('2fauth')
|
||||
const notify = useNotifyStore()
|
||||
@ -77,7 +79,7 @@
|
||||
clearTokenValues()
|
||||
|
||||
if (isDisabled.value) {
|
||||
notify.warn({ text: trans('errors.unsupported_with_reverseproxy') })
|
||||
notify.warn({ text: t('error.unsupported_with_reverseproxy') })
|
||||
}
|
||||
else createPATModalIsVisible.value = true
|
||||
}
|
||||
@ -100,12 +102,12 @@
|
||||
* revoke a token (after confirmation)
|
||||
*/
|
||||
function revokeToken(tokenId) {
|
||||
if(confirm(trans('settings.confirm.revoke'))) {
|
||||
if(confirm(t('message.settings.confirm.revoke'))) {
|
||||
userService.deletePersonalAccessToken(tokenId)
|
||||
.then(response => {
|
||||
// Remove the revoked token from the collection
|
||||
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) {
|
||||
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">
|
||||
<FormWrapper>
|
||||
<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 v-if="isDisabled && user.authenticated_by_proxy" class="notification is-warning has-text-centered" v-html="$t('auth.auth_handled_by_proxy')" />
|
||||
<h4 class="title is-4 has-text-grey-light">{{ $t('settings.personal_access_tokens') }}</h4>
|
||||
<div v-if="isDisabled && user.authenticated_by_proxy" class="notification is-warning has-text-centered">
|
||||
{{ $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">
|
||||
{{ $t('settings.token_legend')}}
|
||||
{{ $t('message.settings.token_legend')}}
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<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>
|
||||
</div>
|
||||
<div v-if="tokens.length > 0">
|
||||
@ -168,16 +172,16 @@
|
||||
<div class="tags is-pulled-right">
|
||||
<UseColorMode v-slot="{ mode }">
|
||||
<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 type="button" class="button tag" :class="mode === 'dark' ? 'is-dark':'is-white'" @click="revokeToken(token.id)" :title="$t('settings.revoke')">
|
||||
{{ $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('message.settings.revoke') }}
|
||||
</button>
|
||||
</UseColorMode>
|
||||
</div>
|
||||
<!-- warning msg -->
|
||||
<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>
|
||||
<!-- token value -->
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
<Spinner :isVisible="isFetching && tokens.length === 0" />
|
||||
@ -197,18 +201,18 @@
|
||||
</div>
|
||||
<div v-if="createPATModalIsVisible" class="is-overlay modal-otp modal-background">
|
||||
<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)">
|
||||
<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="control">
|
||||
<VueButton nativeType="submit" :id="'btnGenerateToken'" :isLoading="form.isBusy" >
|
||||
{{ $t('commons.generate') }}
|
||||
{{ $t('message.generate') }}
|
||||
</VueButton>
|
||||
</div>
|
||||
<div class="control">
|
||||
<VueButton @click="cancelPATcreation" nativeType="button" id="btnCancel" :color="'is-text'">
|
||||
{{ $t('commons.cancel') }}
|
||||
{{ $t('message.cancel') }}
|
||||
</VueButton>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6,7 +6,9 @@
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { timezones } from './timezones'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const user = useUserStore()
|
||||
const groups = useGroups()
|
||||
@ -15,13 +17,13 @@
|
||||
const returnTo = useStorage($2fauth.prefix + 'returnTo', 'accounts')
|
||||
|
||||
const layouts = [
|
||||
{ text: 'settings.forms.grid', value: 'grid', icon: 'Grid3X3' },
|
||||
{ text: 'settings.forms.list', value: 'list', icon: 'List' },
|
||||
{ text: 'message.settings.forms.grid', value: 'grid', icon: 'Grid3X3' },
|
||||
{ text: 'message.settings.forms.list', value: 'list', icon: 'List' },
|
||||
]
|
||||
const themes = [
|
||||
{ text: 'settings.forms.light', value: 'light', icon: 'Sun' },
|
||||
{ text: 'settings.forms.dark', value: 'dark', icon: 'Moon' },
|
||||
{ text: 'settings.forms.automatic', value: 'system', icon: 'MonitorCheck' },
|
||||
{ text: 'message.settings.forms.light', value: 'light', icon: 'Sun' },
|
||||
{ text: 'message.settings.forms.dark', value: 'dark', icon: 'Moon' },
|
||||
{ text: 'message.settings.forms.automatic', value: 'system', icon: 'MonitorCheck' },
|
||||
]
|
||||
const iconCollections = [
|
||||
{ text: 'selfh.st', value: 'selfh', url: 'https://selfh.st/icons/', defaultVariant: 'regular' },
|
||||
@ -30,64 +32,64 @@
|
||||
]
|
||||
const iconCollectionVariants = {
|
||||
selfh: [
|
||||
{ text: 'commons.regular', value: 'regular' },
|
||||
{ text: 'settings.forms.light', value: 'light' },
|
||||
{ text: 'settings.forms.dark', value: 'dark' },
|
||||
{ text: 'message.regular', value: 'regular' },
|
||||
{ text: 'message.settings.forms.light', value: 'light' },
|
||||
{ text: 'message.settings.forms.dark', value: 'dark' },
|
||||
],
|
||||
dashboardicons: [
|
||||
{ text: 'commons.regular', value: 'regular' },
|
||||
{ text: 'settings.forms.light', value: 'light' },
|
||||
{ text: 'settings.forms.dark', value: 'dark' },
|
||||
{ text: 'message.regular', value: 'regular' },
|
||||
{ text: 'message.settings.forms.light', value: 'light' },
|
||||
{ text: 'message.settings.forms.dark', value: 'dark' },
|
||||
],
|
||||
tfa: [
|
||||
{ text: 'commons.regular', value: 'regular' },
|
||||
{ text: 'message.regular', value: 'regular' },
|
||||
],
|
||||
}
|
||||
const passwordFormats = [
|
||||
{ text: '12 34 56', value: 2, legend: 'settings.forms.pair', title: 'settings.forms.pair_legend' },
|
||||
{ text: '123 456', value: 3, legend: 'settings.forms.trio', title: 'settings.forms.trio_legend' },
|
||||
{ text: '1234 5678', value: 0.5, legend: 'settings.forms.half', title: 'settings.forms.half_legend' },
|
||||
{ text: '12 34 56', value: 2, legend: 'message.settings.forms.pair', title: 'message.settings.forms.pair_legend' },
|
||||
{ text: '123 456', value: 3, legend: 'message.settings.forms.trio', title: 'message.settings.forms.trio_legend' },
|
||||
{ text: '1234 5678', value: 0.5, legend: 'message.settings.forms.half', title: 'message.settings.forms.half_legend' },
|
||||
]
|
||||
const kickUserAfters = [
|
||||
{ text: 'settings.forms.never', value: 0 },
|
||||
{ text: 'settings.forms.on_otp_copy', value: -1 },
|
||||
{ text: 'settings.forms.1_minutes', value: 1 },
|
||||
{ text: 'settings.forms.5_minutes', value: 5 },
|
||||
{ text: 'settings.forms.10_minutes', value: 10 },
|
||||
{ text: 'settings.forms.15_minutes', value: 15 },
|
||||
{ text: 'settings.forms.30_minutes', value: 30 },
|
||||
{ text: 'settings.forms.1_hour', value: 60 },
|
||||
{ text: 'settings.forms.1_day', value: 1440 },
|
||||
{ text: 'message.settings.forms.never', value: 0 },
|
||||
{ text: 'message.settings.forms.on_otp_copy', value: -1 },
|
||||
{ text: 'message.settings.forms.1_minutes', value: 1 },
|
||||
{ text: 'message.settings.forms.5_minutes', value: 5 },
|
||||
{ text: 'message.settings.forms.10_minutes', value: 10 },
|
||||
{ text: 'message.settings.forms.15_minutes', value: 15 },
|
||||
{ text: 'message.settings.forms.30_minutes', value: 30 },
|
||||
{ text: 'message.settings.forms.1_hour', value: 60 },
|
||||
{ text: 'message.settings.forms.1_day', value: 1440 },
|
||||
]
|
||||
const autoCloseTimeout = [
|
||||
{ text: 'settings.forms.never', value: 0 },
|
||||
{ text: 'settings.forms.1_minutes', value: 1 },
|
||||
{ text: 'settings.forms.2_minutes', value: 2 },
|
||||
{ text: 'settings.forms.5_minutes', value: 5 },
|
||||
{ text: 'message.settings.forms.never', value: 0 },
|
||||
{ text: 'message.settings.forms.1_minutes', value: 1 },
|
||||
{ text: 'message.settings.forms.2_minutes', value: 2 },
|
||||
{ text: 'message.settings.forms.5_minutes', value: 5 },
|
||||
]
|
||||
const groupsList = ref([
|
||||
{ text: 'groups.no_group', value: 0 },
|
||||
{ text: 'groups.active_group', value: -1 },
|
||||
{ text: 'message.groups.no_group', value: 0 },
|
||||
{ text: 'message.groups.active_group', value: -1 },
|
||||
])
|
||||
const captureModes = [
|
||||
{ text: 'settings.forms.livescan', value: 'livescan' },
|
||||
{ text: 'settings.forms.upload', value: 'upload' },
|
||||
{ text: 'settings.forms.advanced_form', value: 'advancedForm' },
|
||||
{ text: 'message.settings.forms.livescan', value: 'livescan' },
|
||||
{ text: 'message.settings.forms.upload', value: 'upload' },
|
||||
{ text: 'message.settings.forms.advanced_form', value: 'advancedForm' },
|
||||
]
|
||||
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: '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_request', value: true, legend: 'message.settings.forms.otp_generation_on_request_legend', title: 'message.settings.forms.otp_generation_on_request_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(() => {
|
||||
let locales = [{
|
||||
text: 'languages.browser_preference',
|
||||
text: 'message.browser_preference',
|
||||
value: 'browser'
|
||||
}];
|
||||
|
||||
for (const locale of $2fauth.langs) {
|
||||
locales.push({
|
||||
text: 'languages.' + locale,
|
||||
text: 'lang.' + locale,
|
||||
value: locale
|
||||
})
|
||||
}
|
||||
@ -122,9 +124,9 @@
|
||||
*/
|
||||
function savePreference(preference, value) {
|
||||
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()
|
||||
}
|
||||
else if(preference === 'theme') {
|
||||
@ -172,90 +174,90 @@
|
||||
<!-- <input type="hidden" name="isReady" id="isReady" :value="isReady" /> -->
|
||||
<!-- user preferences -->
|
||||
<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">
|
||||
{{ $t('settings.settings_managed_by_administrator') }}
|
||||
{{ $t('message.settings.settings_managed_by_administrator') }}
|
||||
</div>
|
||||
<!-- 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">
|
||||
{{ $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">
|
||||
{{ $t('settings.forms.help_translate_2fauth') }}
|
||||
{{ $t('message.settings.forms.help_translate_2fauth') }}
|
||||
<FontAwesomeIcon :icon="['fas', 'external-link-alt']" />
|
||||
</a>
|
||||
</div>
|
||||
<!-- 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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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">
|
||||
<a class="button is-ghost" :href="iconCollectionUrl" target="_blank" :title="$t('commons.visit_x', { website: iconCollectionDomain})">
|
||||
<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('message.visit_x', { website: iconCollectionDomain})">
|
||||
<FontAwesomeIcon :icon="['fas', 'external-link-alt']" />
|
||||
</a>
|
||||
</FormSelect>
|
||||
<!-- 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 -->
|
||||
<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 -->
|
||||
<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" />
|
||||
<!-- 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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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>
|
||||
</form>
|
||||
</FormWrapper>
|
||||
|
@ -7,7 +7,9 @@
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import Spinner from '@/components/Spinner.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const user = useUserStore()
|
||||
const appSettings = useAppSettingsStore()
|
||||
@ -28,7 +30,7 @@
|
||||
|
||||
watch(() => user.preferences.useWebauthnOnly, () => {
|
||||
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() {
|
||||
if (isDisabled.value == true) {
|
||||
notify.warn({text: trans('errors.unsupported_with_reverseproxy') })
|
||||
notify.warn({text: t('error.unsupported_with_reverseproxy') })
|
||||
return false
|
||||
}
|
||||
|
||||
@ -47,9 +49,9 @@
|
||||
.catch(error => {
|
||||
if ('webauthn' in error) {
|
||||
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 ) {
|
||||
notify.alert({ text: error.response.data.message })
|
||||
@ -64,7 +66,7 @@
|
||||
* revoke a credential
|
||||
*/
|
||||
function revokeCredential(credentialId) {
|
||||
if(confirm(trans('auth.confirm.revoke_device'))) {
|
||||
if(confirm(t('message.auth.confirm.revoke_device'))) {
|
||||
userService.revokeWebauthnDevice(credentialId).then(response => {
|
||||
// Remove the revoked credential from the collection
|
||||
credentials.value = credentials.value.filter(a => a.id !== credentialId)
|
||||
@ -75,7 +77,7 @@
|
||||
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
|
||||
*/
|
||||
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">
|
||||
<FormWrapper>
|
||||
<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 v-if="isDisabled && user.authenticated_by_proxy" class="notification is-warning has-text-centered" v-html="$t('auth.auth_handled_by_proxy')" />
|
||||
<h4 class="title is-4 has-text-grey-light">{{ $t('auth.webauthn.security_devices') }}</h4>
|
||||
<div v-if="isDisabled && user.authenticated_by_proxy" class="notification is-warning has-text-centered">
|
||||
{{ $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">
|
||||
{{ $t('auth.webauthn.security_devices_legend')}}
|
||||
{{ $t('message.auth.webauthn.security_devices_legend')}}
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<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>
|
||||
</div>
|
||||
<!-- credentials list -->
|
||||
@ -143,31 +147,31 @@
|
||||
{{ displayName(credential) }}
|
||||
<!-- revoke link -->
|
||||
<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')">
|
||||
{{ $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('message.settings.revoke') }}
|
||||
</button>
|
||||
</UseColorMode>
|
||||
<!-- 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']" />
|
||||
</RouterLink> -->
|
||||
</div>
|
||||
<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>
|
||||
<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">
|
||||
{{ $t('auth.webauthn.need_a_security_device_to_enable_options')}}
|
||||
{{ $t('message.auth.webauthn.need_a_security_device_to_enable_options')}}
|
||||
</div>
|
||||
<form>
|
||||
<!-- use webauthn only -->
|
||||
<FormCheckbox
|
||||
v-model="user.preferences.useWebauthnOnly"
|
||||
fieldName="useWebauthnOnly"
|
||||
label="auth.webauthn.use_webauthn_only.label"
|
||||
help="auth.webauthn.use_webauthn_only.help"
|
||||
label="message.auth.webauthn.use_webauthn_only.label"
|
||||
help="message.auth.webauthn.use_webauthn_only.help"
|
||||
:isDisabled="isDisabled || credentials.length === 0"
|
||||
/>
|
||||
</form>
|
||||
|
@ -17,7 +17,9 @@
|
||||
import { useGroups } from '@/stores/groups'
|
||||
import { useDisplayablePassword } from '@/composables/helpers'
|
||||
import { useSortable, moveArrayElement } from '@vueuse/integrations/useSortable'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const router = useRouter()
|
||||
const notify = useNotifyStore()
|
||||
@ -92,7 +94,7 @@
|
||||
else {
|
||||
twofaccounts.fetch().then(() => {
|
||||
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.selectNone()
|
||||
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
|
||||
}
|
||||
|
||||
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 class="columns">
|
||||
<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">
|
||||
{{ $t('groups.select_accounts_to_show') }}
|
||||
<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('message.groups.select_accounts_to_show') }}
|
||||
</button>
|
||||
</div>
|
||||
<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 }})
|
||||
<FontAwesomeIcon :icon="['fas', 'caret-down']" />
|
||||
</button>
|
||||
@ -424,18 +426,18 @@
|
||||
<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-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>
|
||||
</div>
|
||||
</div>
|
||||
<transition name="popLater">
|
||||
<div v-show="user.preferences.getOtpOnRequest == false && !bus.inManagementMode" class="has-text-right">
|
||||
<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) }}
|
||||
</div>
|
||||
<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) }}
|
||||
</span>
|
||||
<Dots
|
||||
@ -447,8 +449,8 @@
|
||||
</div>
|
||||
<div v-else>
|
||||
<!-- 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')">
|
||||
{{ $t('commons.generate') }}
|
||||
<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('message.generate') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -466,9 +468,9 @@
|
||||
<transition name="fadeInOut">
|
||||
<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'">
|
||||
{{ $t('commons.edit') }}
|
||||
{{ $t('message.edit') }}
|
||||
</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']" />
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
@ -6,7 +6,9 @@
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { QrcodeStream } from 'vue-qrcode-reader'
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const bus = useBusStore()
|
||||
const notify = useNotifyStore()
|
||||
@ -69,7 +71,7 @@
|
||||
form.uri = firstCode.rawValue
|
||||
|
||||
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=") {
|
||||
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/") {
|
||||
showQrContent.value = true
|
||||
notify.warn({ text: trans('errors.no_valid_otp') })
|
||||
notify.warn({ text: t('error.no_valid_otp') })
|
||||
}
|
||||
else {
|
||||
bus.decodedUri = form.uri
|
||||
@ -142,11 +144,11 @@
|
||||
<div class="modal-slot has-text-centered is-shadowless">
|
||||
<UseColorMode v-slot="{ mode }">
|
||||
<div v-if="errorPhrase">
|
||||
<p class="block is-size-5">{{ $t('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 is-size-5">{{ $t('message.twofaccounts.stream.live_scan_cant_start') }}</p>
|
||||
<p class="block" :class="{'has-text-light': mode == 'dark'}">{{ $t('message.twofaccounts.stream.' + errorPhrase + '.reason') }}</p>
|
||||
<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('twofaccounts.stream.need_grant_permission.click_camera_icon') }}</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('message.twofaccounts.stream.need_grant_permission.click_camera_icon') }}</p>
|
||||
|
||||
<div class="addressbar columns is-mobile is-gapless">
|
||||
<div class="column is-narrow has-text-left circled">
|
||||
@ -161,7 +163,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
<a @click.stop="reloadLocation">{{ $t('commons.refresh') }}</a>
|
||||
<a @click.stop="reloadLocation">{{ $t('message.refresh') }}</a>
|
||||
</p>
|
||||
|
||||
<!-- <div class="addressbar">
|
||||
@ -170,7 +172,7 @@
|
||||
<FontAwesomeIcon :icon="['far', 'star']" class="mr-3" size="xs" />
|
||||
</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>
|
||||
<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" />
|
||||
@ -196,7 +198,7 @@
|
||||
<span class="select">
|
||||
<select v-model="selectedCamera">
|
||||
<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>
|
||||
</select>
|
||||
</span>
|
||||
|
@ -10,7 +10,9 @@
|
||||
import { useBusStore } from '@/stores/bus'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@ -44,14 +46,14 @@
|
||||
]
|
||||
const iconCollectionVariants = {
|
||||
selfh: [
|
||||
{ text: 'commons.regular', value: 'regular' },
|
||||
{ text: 'settings.forms.light', value: 'light' },
|
||||
{ text: 'settings.forms.dark', value: 'dark' },
|
||||
{ text: 'message.regular', value: 'regular' },
|
||||
{ text: 'message.settings.forms.light', value: 'light' },
|
||||
{ text: 'message.settings.forms.dark', value: 'dark' },
|
||||
],
|
||||
dashboardicons: [
|
||||
{ text: 'commons.regular', value: 'regular' },
|
||||
{ text: 'settings.forms.light', value: 'light' },
|
||||
{ text: 'settings.forms.dark', value: 'dark' },
|
||||
{ text: 'message.regular', value: 'regular' },
|
||||
{ text: 'message.settings.forms.light', value: 'light' },
|
||||
{ text: 'message.settings.forms.dark', value: 'dark' },
|
||||
]
|
||||
}
|
||||
const otpDisplayProps = ref({
|
||||
@ -109,7 +111,7 @@
|
||||
|
||||
const groups = computed(() => {
|
||||
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) {
|
||||
twofaccounts.items.push(data)
|
||||
notify.success({ text: trans('twofaccounts.account_created') })
|
||||
notify.success({ text: t('message.twofaccounts.account_created') })
|
||||
router.push({ name: 'accounts' });
|
||||
}
|
||||
}
|
||||
@ -250,7 +252,7 @@
|
||||
const index = twofaccounts.items.findIndex(acc => acc.id === data.id)
|
||||
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' })
|
||||
}
|
||||
}
|
||||
@ -287,7 +289,7 @@
|
||||
*/
|
||||
function cancelCreation() {
|
||||
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) {
|
||||
deleteTempIcon()
|
||||
}
|
||||
@ -385,7 +387,7 @@
|
||||
if( error.response.data.errors.uri ) {
|
||||
showAlternatives.value = true
|
||||
}
|
||||
else notify.alert({ text: trans(error.response.data.message) })
|
||||
else notify.alert({ text: t(error.response.data.message) })
|
||||
} else {
|
||||
notify.error(error)
|
||||
}
|
||||
@ -412,10 +414,10 @@
|
||||
deleteTempIcon()
|
||||
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(() => {
|
||||
notify.warn({ text: trans('errors.no_icon_for_this_variant') })
|
||||
notify.warn({ text: t('error.no_icon_for_this_variant') })
|
||||
})
|
||||
.finally(() => {
|
||||
fetchingLogo.value = false
|
||||
@ -478,7 +480,7 @@
|
||||
<div class="column quickform-footer">
|
||||
<div class="field is-grouped is-grouped-centered">
|
||||
<div class="control">
|
||||
<VueButton nativeType="submit" :isLoading="form.isBusy" >{{ $t('commons.save') }}</VueButton>
|
||||
<VueButton nativeType="submit" :isLoading="form.isBusy" >{{ $t('message.save') }}</VueButton>
|
||||
</div>
|
||||
<NavigationButton action="cancel" :isText="true" :isRounded="false" :useLinkTag="false" @canceled="cancelCreation" />
|
||||
</div>
|
||||
@ -487,19 +489,19 @@
|
||||
</div>
|
||||
</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)">
|
||||
<!-- qcode fileupload -->
|
||||
<div v-if="!isEditMode" class="field is-grouped">
|
||||
<div class="control">
|
||||
<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">
|
||||
<span class="file-cta">
|
||||
<span class="file-icon">
|
||||
<FontAwesomeIcon :icon="['fas', 'qrcode']" size="lg" />
|
||||
</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>
|
||||
</label>
|
||||
</div>
|
||||
@ -507,11 +509,11 @@
|
||||
</div>
|
||||
<FormFieldError v-if="qrcodeForm.errors.hasAny('qrcode')" :error="qrcodeForm.errors.get('qrcode')" :field="'qrcode'" class="help-for-file" />
|
||||
<!-- 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 -->
|
||||
<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 -->
|
||||
<label for="filUploadIcon" class="label">{{ $t('twofaccounts.icon') }}</label>
|
||||
<label for="filUploadIcon" class="label">{{ $t('message.twofaccounts.icon') }}</label>
|
||||
<!-- try my luck -->
|
||||
<!-- <fieldset v-if="user.preferences.getOfficialIcons" :disabled="!form.service"> -->
|
||||
<div class="field has-addons">
|
||||
@ -542,7 +544,7 @@
|
||||
<span class="icon is-small">
|
||||
<FontAwesomeIcon :icon="['fas', 'globe']" />
|
||||
</span>
|
||||
<span>{{ $t('twofaccounts.forms.i_m_lucky') }}</span>
|
||||
<span>{{ $t('message.twofaccounts.forms.i_m_lucky') }}</span>
|
||||
</VueButton>
|
||||
</div>
|
||||
<!-- upload icon button -->
|
||||
@ -554,49 +556,49 @@
|
||||
<span class="file-icon">
|
||||
<FontAwesomeIcon :icon="['fas', 'upload']" />
|
||||
</span>
|
||||
<span class="file-label">{{ $t('twofaccounts.forms.choose_image') }}</span>
|
||||
<span class="file-label">{{ $t('message.twofaccounts.forms.choose_image') }}</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<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')">
|
||||
<button type="button" class="clear-selection delete is-small" @click.prevent="deleteTempIcon" :aria-label="$t('twofaccounts.remove_icon')"></button>
|
||||
<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('message.twofaccounts.remove_icon')"></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<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>
|
||||
<!-- 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 -->
|
||||
<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 != ''">
|
||||
<!-- 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 -->
|
||||
<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">
|
||||
{{ $t('twofaccounts.forms.options_help') }}
|
||||
{{ $t('message.twofaccounts.forms.options_help') }}
|
||||
</p>
|
||||
<!-- 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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<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>
|
||||
<VueFooter :showButtons="true">
|
||||
<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 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>
|
||||
<NavigationButton action="cancel" :useLinkTag="false" @canceled="cancelCreation" />
|
||||
</VueFooter>
|
||||
|
@ -9,7 +9,9 @@
|
||||
import { useBusStore } from '@/stores/bus'
|
||||
import { useTwofaccounts } from '@/stores/twofaccounts'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const $2fauth = inject('2fauth')
|
||||
const notify = useNotifyStore()
|
||||
const user = useUserStore()
|
||||
@ -42,9 +44,9 @@
|
||||
const showTwofaccountInModal = ref(false)
|
||||
const supportedSources = [
|
||||
{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: trans('twofaccounts.import.plain_text')},
|
||||
{app: 'Aegis Auth', format: t('message.twofaccounts.import.plain_text')},
|
||||
{app: '2FAS auth', format: 'JSON'},
|
||||
]
|
||||
const exportedAccounts = ref([])
|
||||
@ -92,7 +94,7 @@
|
||||
directInput.value = directInputError.value = null
|
||||
})
|
||||
.catch(error => {
|
||||
notify.alert({ text: trans(error.response.data.message) })
|
||||
notify.alert({ text: t(error.response.data.message) })
|
||||
});
|
||||
|
||||
isFetching.value = false
|
||||
@ -102,7 +104,7 @@
|
||||
* Removes all duplicates from the accounts list
|
||||
*/
|
||||
function discardDuplicates() {
|
||||
if(confirm(trans('twofaccounts.confirm.discard_duplicates'))) {
|
||||
if(confirm(t('message.twofaccounts.confirm.discard_duplicates'))) {
|
||||
notify.clear()
|
||||
otpDisplay.value?.clearOTP()
|
||||
exportedAccounts.value = exportedAccounts.value.filter(account => account.id !== -1)
|
||||
@ -113,7 +115,7 @@
|
||||
* Clears the accounts list
|
||||
*/
|
||||
function discardAccounts() {
|
||||
if(confirm(trans('twofaccounts.confirm.discard_all'))) {
|
||||
if(confirm(t('message.twofaccounts.confirm.discard_all'))) {
|
||||
notify.clear()
|
||||
otpDisplay.value?.clearOTP()
|
||||
exportedAccounts.value = []
|
||||
@ -124,7 +126,7 @@
|
||||
* Removes one duplicate from the accounts list
|
||||
*/
|
||||
function discardAccount(accountIndex) {
|
||||
if(confirm(trans('twofaccounts.confirm.discard'))) {
|
||||
if(confirm(t('message.twofaccounts.confirm.discard'))) {
|
||||
exportedAccounts.value.splice(accountIndex, 1)
|
||||
}
|
||||
}
|
||||
@ -190,7 +192,7 @@
|
||||
.catch(error => {
|
||||
if (error.response.status === 422) {
|
||||
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})
|
||||
@ -213,7 +215,7 @@
|
||||
.catch(error => {
|
||||
if( error.response.status === 422 ) {
|
||||
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})
|
||||
@ -226,7 +228,7 @@
|
||||
* Notifies that valid account(s) have been found for import
|
||||
*/
|
||||
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
|
||||
|
||||
if (! directInput.value) {
|
||||
directInputError.value = trans('validation.required', { attribute: 'Direct input' })
|
||||
directInputError.value = t('validation.required', { attribute: 'Direct input' })
|
||||
}
|
||||
else migrate(directInput.value)
|
||||
}
|
||||
@ -248,12 +250,12 @@
|
||||
<div>
|
||||
<ResponsiveWidthWrapper>
|
||||
<h1 class="title has-text-grey-dark">
|
||||
{{ $t('twofaccounts.import.import') }}
|
||||
{{ $t('message.twofaccounts.import.import') }}
|
||||
</h1>
|
||||
<div v-if="!isFetching && exportedAccounts.length == 0">
|
||||
<div class="block is-size-7-mobile">
|
||||
<p class="mb-2">{{ $t('twofaccounts.import.import_legend') }}</p>
|
||||
<p>{{ $t('twofaccounts.import.import_legend_afterpart') }}</p>
|
||||
<p class="mb-2">{{ $t('message.twofaccounts.import.import_legend') }}</p>
|
||||
<p>{{ $t('message.twofaccounts.import.import_legend_afterpart') }}</p>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
@ -267,19 +269,19 @@
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-5 has-text-grey" v-html="$t('twofaccounts.import.qr_code')" />
|
||||
<p class="subtitle is-6 is-size-7-mobile">{{ $t('twofaccounts.import.supported_formats_for_qrcode_upload') }}</p>
|
||||
<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('message.twofaccounts.import.supported_formats_for_qrcode_upload') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<FormFieldError v-if="qrcodeForm.errors.hasAny('qrcode')" :error="qrcodeForm.errors.get('qrcode')" :field="'qrcode'" />
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<RouterLink id="btnCapture" :to="{ name: 'capture' }" class="card-footer-item">
|
||||
{{ $t('twofaccounts.import.scan') }}
|
||||
{{ $t('message.twofaccounts.import.scan') }}
|
||||
</RouterLink>
|
||||
<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">
|
||||
{{ $t('twofaccounts.import.upload') }}
|
||||
{{ $t('message.twofaccounts.import.upload') }}
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
@ -294,8 +296,8 @@
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-5 has-text-grey">{{ $t('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="title is-5 has-text-grey">{{ $t('message.twofaccounts.import.text_file') }}</p>
|
||||
<p class="subtitle is-6 is-size-7-mobile">{{ $t('message.twofaccounts.import.supported_formats_for_file_upload') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<FormFieldError v-if="fileForm.errors.hasAny('file')" :error="fileForm.errors.get('file')" :field="'file'" />
|
||||
@ -303,7 +305,7 @@
|
||||
<footer class="card-footer">
|
||||
<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">
|
||||
{{ $t('twofaccounts.import.upload') }}
|
||||
{{ $t('message.twofaccounts.import.upload') }}
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
@ -318,8 +320,8 @@
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-5 has-text-grey" v-html="$t('twofaccounts.import.direct_input')" />
|
||||
<p class="subtitle is-6 is-size-7-mobile">{{ $t('twofaccounts.import.expected_format_for_direct_input') }}</p>
|
||||
<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('message.twofaccounts.import.expected_format_for_direct_input') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
@ -328,7 +330,7 @@
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<a role="button" tabindex="0" class="card-footer-item is-relative" @click.stop="submitDirectInput">
|
||||
{{ $t('commons.submit') }}
|
||||
{{ $t('message.submit') }}
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
@ -336,10 +338,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- 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">
|
||||
<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>
|
||||
<table class="table is-size-7-mobile is-fullwidth">
|
||||
<thead>
|
||||
@ -385,17 +387,17 @@
|
||||
</table>
|
||||
</div>
|
||||
<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 v-else>
|
||||
<div class="block is-size-7-mobile">
|
||||
<p class="mb-2">{{ $t('twofaccounts.import.submitted_data_parsed_now_accounts_are_awaiting_import') }}</p>
|
||||
<p>{{ $t('twofaccounts.import.use_buttons_to_save_or_discard') }}</p>
|
||||
<p class="mb-2">{{ $t('message.twofaccounts.import.submitted_data_parsed_now_accounts_are_awaiting_import') }}</p>
|
||||
<p>{{ $t('message.twofaccounts.import.use_buttons_to_save_or_discard') }}</p>
|
||||
</div>
|
||||
<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">
|
||||
<!-- 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="">
|
||||
{{ account.account }}
|
||||
</div>
|
||||
@ -403,34 +405,34 @@
|
||||
<!-- buttons -->
|
||||
<div v-if="account.imported === -1" class="tags is-flex-wrap-nowrap">
|
||||
<!-- 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']" />
|
||||
</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')">
|
||||
{{ $t('twofaccounts.import.to_import') }}
|
||||
<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('message.twofaccounts.import.to_import') }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- result label -->
|
||||
<div v-else class="has-nowrap">
|
||||
<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 v-else class="has-text-danger">
|
||||
{{ $t('twofaccounts.import.failure') }} <FontAwesomeIcon :icon="['fas', 'times']" />
|
||||
{{ $t('message.twofaccounts.import.failure') }} <FontAwesomeIcon :icon="['fas', 'times']" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="is-size-6 is-size-7-mobile">
|
||||
<!-- 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 -->
|
||||
<div v-if="account.id === -2" class="has-text-danger">
|
||||
<FontAwesomeIcon class="mr-1" :icon="['fas', 'times-circle']" />{{ account.secret }}
|
||||
</div>
|
||||
<!-- possible duplicates -->
|
||||
<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>
|
||||
<!-- errors during account creation -->
|
||||
<ul v-if="account.errors">
|
||||
@ -440,11 +442,11 @@
|
||||
</div>
|
||||
<!-- discard links -->
|
||||
<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 @click="discardAccounts()" type="button" class="has-text-grey button is-small is-ghost">{{ $t('twofaccounts.import.discard_all') }}</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('message.twofaccounts.import.discard_all') }}</button>
|
||||
</div>
|
||||
<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>
|
||||
<!-- footer -->
|
||||
@ -452,7 +454,7 @@
|
||||
<!-- Import all button -->
|
||||
<p class="control" v-if="importableCount > 0">
|
||||
<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">
|
||||
<FontAwesomeIcon :icon="['fas', 'qrcode']" />
|
||||
</span> -->
|
||||
|
@ -24,7 +24,7 @@
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-content modal-with-footer">
|
||||
<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" />
|
||||
</p>
|
||||
</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