diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index b7f567d7..f7af111c 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -17,6 +17,7 @@ use Carbon\Carbon; use Illuminate\Http\Request; use Mail; use Redis; +use PragmaRX\Google2FA\Google2FA; class AccountController extends Controller { @@ -301,4 +302,28 @@ class AccountController extends Controller ->withErrors(['password' => __('auth.failed')]); } } + + public function twoFactorCheckpoint(Request $request) + { + return view('auth.checkpoint'); + } + + public function twoFactorVerify(Request $request) + { + $this->validate($request, [ + 'code' => 'required|string|max:32' + ]); + $user = Auth::user(); + $code = $request->input('code'); + $google2fa = new Google2FA(); + $verify = $google2fa->verifyKey($user->{'2fa_secret'}, $code); + if($verify) { + $request->session()->push('2fa.session.active', true); + return redirect('/'); + } else { + return redirect()->back()->withErrors([ + 'code' => 'Invalid code' + ]); + } + } } diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index 97cc1ff5..8f04879e 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -19,7 +19,8 @@ class AdminController extends Controller public function __construct() { - return $this->middleware('admin'); + $this->middleware('admin'); + $this->middleware('twofactor'); } public function home() diff --git a/app/Http/Controllers/Settings/HomeSettings.php b/app/Http/Controllers/Settings/HomeSettings.php new file mode 100644 index 00000000..591d3e45 --- /dev/null +++ b/app/Http/Controllers/Settings/HomeSettings.php @@ -0,0 +1,153 @@ +profile->id; + $storage = []; + $used = Media::whereProfileId($id)->sum('size'); + $storage['limit'] = config('pixelfed.max_account_size') * 1024; + $storage['used'] = $used; + $storage['percentUsed'] = ceil($storage['used'] / $storage['limit'] * 100); + $storage['limitPretty'] = PrettyNumber::size($storage['limit']); + $storage['usedPretty'] = PrettyNumber::size($storage['used']); + + return view('settings.home', compact('storage')); + } + + public function homeUpdate(Request $request) + { + $this->validate($request, [ + 'name' => 'required|string|max:'.config('pixelfed.max_name_length'), + 'bio' => 'nullable|string|max:'.config('pixelfed.max_bio_length'), + 'website' => 'nullable|url', + 'email' => 'nullable|email', + ]); + + $changes = false; + $name = $request->input('name'); + $bio = $request->input('bio'); + $website = $request->input('website'); + $email = $request->input('email'); + $user = Auth::user(); + $profile = $user->profile; + + $validate = config('pixelfed.enforce_email_verification'); + + if ($user->email != $email) { + $changes = true; + $user->email = $email; + + if ($validate) { + $user->email_verified_at = null; + // Prevent old verifications from working + EmailVerification::whereUserId($user->id)->delete(); + } + + $log = new AccountLog(); + $log->user_id = $user->id; + $log->item_id = $user->id; + $log->item_type = 'App\User'; + $log->action = 'account.edit.email'; + $log->message = 'Email changed'; + $log->link = null; + $log->ip_address = $request->ip(); + $log->user_agent = $request->userAgent(); + $log->save(); + } + + // Only allow email to be updated if not yet verified + if (!$validate || !$changes && $user->email_verified_at) { + if ($profile->name != $name) { + $changes = true; + $user->name = $name; + $profile->name = $name; + } + + if (!$profile->website || $profile->website != $website) { + $changes = true; + $profile->website = $website; + } + + if (!$profile->bio || !$profile->bio != $bio) { + $changes = true; + $profile->bio = $bio; + } + } + + if ($changes === true) { + $user->save(); + $profile->save(); + + return redirect('/settings/home')->with('status', 'Profile successfully updated!'); + } + + return redirect('/settings/home'); + } + + public function password() + { + return view('settings.password'); + } + + public function passwordUpdate(Request $request) + { + $this->validate($request, [ + 'current' => 'required|string', + 'password' => 'required|string', + 'password_confirmation' => 'required|string', + ]); + + $current = $request->input('current'); + $new = $request->input('password'); + $confirm = $request->input('password_confirmation'); + + $user = Auth::user(); + + if (password_verify($current, $user->password) && $new === $confirm) { + $user->password = bcrypt($new); + $user->save(); + + $log = new AccountLog(); + $log->user_id = $user->id; + $log->item_id = $user->id; + $log->item_type = 'App\User'; + $log->action = 'account.edit.password'; + $log->message = 'Password changed'; + $log->link = null; + $log->ip_address = $request->ip(); + $log->user_agent = $request->userAgent(); + $log->save(); + + return redirect('/settings/home')->with('status', 'Password successfully updated!'); + } + + return redirect('/settings/home')->with('error', 'There was an error with your request!'); + } + + public function email() + { + return view('settings.email'); + } + + public function avatar() + { + return view('settings.avatar'); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/Settings/PrivacySettings.php b/app/Http/Controllers/Settings/PrivacySettings.php new file mode 100644 index 00000000..dd01feb6 --- /dev/null +++ b/app/Http/Controllers/Settings/PrivacySettings.php @@ -0,0 +1,127 @@ +settings; + $is_private = Auth::user()->profile->is_private; + $settings['is_private'] = (bool) $is_private; + + return view('settings.privacy', compact('settings')); + } + + public function privacyStore(Request $request) + { + $settings = Auth::user()->settings; + $profile = Auth::user()->profile; + $fields = [ + 'is_private', + 'crawlable', + 'show_profile_follower_count', + 'show_profile_following_count', + ]; + foreach ($fields as $field) { + $form = $request->input($field); + if ($field == 'is_private') { + if ($form == 'on') { + $profile->{$field} = true; + $settings->show_guests = false; + $settings->show_discover = false; + $profile->save(); + } else { + $profile->{$field} = false; + $profile->save(); + } + } elseif ($field == 'crawlable') { + if ($form == 'on') { + $settings->{$field} = false; + } else { + $settings->{$field} = true; + } + } else { + if ($form == 'on') { + $settings->{$field} = true; + } else { + $settings->{$field} = false; + } + } + $settings->save(); + } + + return redirect(route('settings.privacy'))->with('status', 'Settings successfully updated!'); + } + + public function mutedUsers() + { + $pid = Auth::user()->profile->id; + $ids = (new UserFilter())->mutedUserIds($pid); + $users = Profile::whereIn('id', $ids)->simplePaginate(15); + return view('settings.privacy.muted', compact('users')); + } + + public function mutedUsersUpdate(Request $request) + { + $this->validate($request, [ + 'profile_id' => 'required|integer|min:1' + ]); + $fid = $request->input('profile_id'); + $pid = Auth::user()->profile->id; + DB::transaction(function () use ($fid, $pid) { + $filter = UserFilter::whereUserId($pid) + ->whereFilterableId($fid) + ->whereFilterableType('App\Profile') + ->whereFilterType('mute') + ->firstOrFail(); + $filter->delete(); + }); + return redirect()->back(); + } + + public function blockedUsers() + { + $pid = Auth::user()->profile->id; + $ids = (new UserFilter())->blockedUserIds($pid); + $users = Profile::whereIn('id', $ids)->simplePaginate(15); + return view('settings.privacy.blocked', compact('users')); + } + + + public function blockedUsersUpdate(Request $request) + { + $this->validate($request, [ + 'profile_id' => 'required|integer|min:1' + ]); + $fid = $request->input('profile_id'); + $pid = Auth::user()->profile->id; + DB::transaction(function () use ($fid, $pid) { + $filter = UserFilter::whereUserId($pid) + ->whereFilterableId($fid) + ->whereFilterableType('App\Profile') + ->whereFilterType('block') + ->firstOrFail(); + $filter->delete(); + }); + return redirect()->back(); + } + + public function blockedInstances() + { + $settings = Auth::user()->settings; + return view('settings.privacy.blocked-instances'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Settings/SecuritySettings.php b/app/Http/Controllers/Settings/SecuritySettings.php new file mode 100644 index 00000000..99547b73 --- /dev/null +++ b/app/Http/Controllers/Settings/SecuritySettings.php @@ -0,0 +1,139 @@ +whereUserId(Auth::id()) + ->limit(20) + ->get(); + + $activity = AccountLog::whereUserId(Auth::id()) + ->orderBy('created_at', 'desc') + ->limit(20) + ->get(); + + $user = Auth::user(); + + return view('settings.security', compact('sessions', 'activity', 'user')); + } + + public function securityTwoFactorSetup(Request $request) + { + $user = Auth::user(); + if($user->{'2fa_enabled'} && $user->{'2fa_secret'}) { + return redirect(route('account.security')); + } + $backups = $this->generateBackupCodes(); + $google2fa = new Google2FA(); + $key = $google2fa->generateSecretKey(32); + $qrcode = $google2fa->getQRCodeInline( + config('pixelfed.domain.app'), + $user->email, + $key, + 500 + ); + $user->{'2fa_secret'} = $key; + $user->{'2fa_backup_codes'} = json_encode($backups); + $user->save(); + return view('settings.security.2fa.setup', compact('user', 'qrcode', 'backups')); + } + + protected function generateBackupCodes() + { + $keys = []; + for ($i=0; $i < 11; $i++) { + $key = str_random(24); + $keys[] = $key; + } + return $keys; + } + + public function securityTwoFactorSetupStore(Request $request) + { + $user = Auth::user(); + if($user->{'2fa_enabled'} && $user->{'2fa_secret'}) { + abort(403, 'Two factor auth is already setup.'); + } + $this->validate($request, [ + 'code' => 'required|integer' + ]); + $code = $request->input('code'); + $google2fa = new Google2FA(); + $verify = $google2fa->verifyKey($user->{'2fa_secret'}, $code); + if($verify) { + $user->{'2fa_enabled'} = true; + $user->{'2fa_setup_at'} = Carbon::now(); + $user->save(); + return response()->json(['msg'=>'success']); + } else { + return response()->json(['msg'=>'fail'], 403); + } + } + + public function securityTwoFactorEdit(Request $request) + { + $user = Auth::user(); + + if(!$user->{'2fa_enabled'} || !$user->{'2fa_secret'}) { + abort(403); + } + + return view('settings.security.2fa.edit', compact('user')); + } + + public function securityTwoFactorRecoveryCodes(Request $request) + { + $user = Auth::user(); + + if(!$user->{'2fa_enabled'} || !$user->{'2fa_secret'} || !$user->{'2fa_backup_codes'}) { + abort(403); + } + $codes = json_decode($user->{'2fa_backup_codes'}, true); + return view('settings.security.2fa.recovery-codes', compact('user', 'codes')); + } + + public function securityTwoFactorUpdate(Request $request) + { + $user = Auth::user(); + + if(!$user->{'2fa_enabled'} || !$user->{'2fa_secret'} || !$user->{'2fa_backup_codes'}) { + abort(403); + } + + $this->validate($request, [ + 'action' => 'required|string|max:12' + ]); + + if($request->action !== 'remove') { + abort(403); + } + + $user->{'2fa_enabled'} = false; + $user->{'2fa_secret'} = null; + $user->{'2fa_backup_codes'} = null; + $user->{'2fa_setup_at'} = null; + $user->save(); + + return response()->json([ + 'msg' => 'Successfully removed 2fa device' + ], 200); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 670bf3b2..9b6c85f7 100644 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -3,135 +3,27 @@ namespace App\Http\Controllers; use App\AccountLog; -use App\EmailVerification; -use App\Media; -use App\Profile; -use App\User; -use App\UserFilter; -use App\Util\Lexer\PrettyNumber; + use Auth; use DB; use Illuminate\Http\Request; +use App\Http\Controllers\Settings\{ + HomeSettings, + PrivacySettings, + SecuritySettings +}; class SettingsController extends Controller { + use HomeSettings, + PrivacySettings, + SecuritySettings; + public function __construct() { $this->middleware('auth'); } - public function home() - { - $id = Auth::user()->profile->id; - $storage = []; - $used = Media::whereProfileId($id)->sum('size'); - $storage['limit'] = config('pixelfed.max_account_size') * 1024; - $storage['used'] = $used; - $storage['percentUsed'] = ceil($storage['used'] / $storage['limit'] * 100); - $storage['limitPretty'] = PrettyNumber::size($storage['limit']); - $storage['usedPretty'] = PrettyNumber::size($storage['used']); - - return view('settings.home', compact('storage')); - } - - public function homeUpdate(Request $request) - { - $this->validate($request, [ - 'name' => 'required|string|max:'.config('pixelfed.max_name_length'), - 'bio' => 'nullable|string|max:'.config('pixelfed.max_bio_length'), - 'website' => 'nullable|url', - 'email' => 'nullable|email', - ]); - - $changes = false; - $name = $request->input('name'); - $bio = $request->input('bio'); - $website = $request->input('website'); - $email = $request->input('email'); - $user = Auth::user(); - $profile = $user->profile; - - $validate = config('pixelfed.enforce_email_verification'); - - if ($user->email != $email) { - $changes = true; - $user->email = $email; - - if ($validate) { - $user->email_verified_at = null; - // Prevent old verifications from working - EmailVerification::whereUserId($user->id)->delete(); - } - } - - // Only allow email to be updated if not yet verified - if (!$validate || !$changes && $user->email_verified_at) { - if ($profile->name != $name) { - $changes = true; - $user->name = $name; - $profile->name = $name; - } - - if (!$profile->website || $profile->website != $website) { - $changes = true; - $profile->website = $website; - } - - if (!$profile->bio || !$profile->bio != $bio) { - $changes = true; - $profile->bio = $bio; - } - } - - if ($changes === true) { - $user->save(); - $profile->save(); - - return redirect('/settings/home')->with('status', 'Profile successfully updated!'); - } - - return redirect('/settings/home'); - } - - public function password() - { - return view('settings.password'); - } - - public function passwordUpdate(Request $request) - { - $this->validate($request, [ - 'current' => 'required|string', - 'password' => 'required|string', - 'password_confirmation' => 'required|string', - ]); - - $current = $request->input('current'); - $new = $request->input('password'); - $confirm = $request->input('password_confirmation'); - - $user = Auth::user(); - - if (password_verify($current, $user->password) && $new === $confirm) { - $user->password = bcrypt($new); - $user->save(); - - return redirect('/settings/home')->with('status', 'Password successfully updated!'); - } - - return redirect('/settings/home')->with('error', 'There was an error with your request!'); - } - - public function email() - { - return view('settings.email'); - } - - public function avatar() - { - return view('settings.avatar'); - } - public function accessibility() { $settings = Auth::user()->settings; @@ -167,70 +59,6 @@ class SettingsController extends Controller return view('settings.notifications'); } - public function privacy() - { - $settings = Auth::user()->settings; - $is_private = Auth::user()->profile->is_private; - $settings['is_private'] = (bool) $is_private; - - return view('settings.privacy', compact('settings')); - } - - public function privacyStore(Request $request) - { - $settings = Auth::user()->settings; - $profile = Auth::user()->profile; - $fields = [ - 'is_private', - 'crawlable', - 'show_profile_follower_count', - 'show_profile_following_count', - ]; - foreach ($fields as $field) { - $form = $request->input($field); - if ($field == 'is_private') { - if ($form == 'on') { - $profile->{$field} = true; - $settings->show_guests = false; - $settings->show_discover = false; - $profile->save(); - } else { - $profile->{$field} = false; - $profile->save(); - } - } elseif ($field == 'crawlable') { - if ($form == 'on') { - $settings->{$field} = false; - } else { - $settings->{$field} = true; - } - } else { - if ($form == 'on') { - $settings->{$field} = true; - } else { - $settings->{$field} = false; - } - } - $settings->save(); - } - - return redirect(route('settings.privacy'))->with('status', 'Settings successfully updated!'); - } - - public function security() - { - $sessions = DB::table('sessions') - ->whereUserId(Auth::id()) - ->limit(20) - ->get(); - $activity = AccountLog::whereUserId(Auth::id()) - ->orderBy('created_at', 'desc') - ->limit(50) - ->get(); - - return view('settings.security', compact('sessions', 'activity')); - } - public function applications() { return view('settings.applications'); @@ -255,64 +83,5 @@ class SettingsController extends Controller { return view('settings.developers'); } - - public function mutedUsers() - { - $pid = Auth::user()->profile->id; - $ids = (new UserFilter())->mutedUserIds($pid); - $users = Profile::whereIn('id', $ids)->simplePaginate(15); - return view('settings.privacy.muted', compact('users')); - } - - public function mutedUsersUpdate(Request $request) - { - $this->validate($request, [ - 'profile_id' => 'required|integer|min:1' - ]); - $fid = $request->input('profile_id'); - $pid = Auth::user()->profile->id; - DB::transaction(function () use ($fid, $pid) { - $filter = UserFilter::whereUserId($pid) - ->whereFilterableId($fid) - ->whereFilterableType('App\Profile') - ->whereFilterType('mute') - ->firstOrFail(); - $filter->delete(); - }); - return redirect()->back(); - } - - public function blockedUsers() - { - $pid = Auth::user()->profile->id; - $ids = (new UserFilter())->blockedUserIds($pid); - $users = Profile::whereIn('id', $ids)->simplePaginate(15); - return view('settings.privacy.blocked', compact('users')); - } - - - public function blockedUsersUpdate(Request $request) - { - $this->validate($request, [ - 'profile_id' => 'required|integer|min:1' - ]); - $fid = $request->input('profile_id'); - $pid = Auth::user()->profile->id; - DB::transaction(function () use ($fid, $pid) { - $filter = UserFilter::whereUserId($pid) - ->whereFilterableId($fid) - ->whereFilterableType('App\Profile') - ->whereFilterType('block') - ->firstOrFail(); - $filter->delete(); - }); - return redirect()->back(); - } - - public function blockedInstances() - { - $settings = Auth::user()->settings; - return view('settings.privacy.blocked-instances'); - } } diff --git a/app/Http/Controllers/TimelineController.php b/app/Http/Controllers/TimelineController.php index 1ce714b9..58665fb0 100644 --- a/app/Http/Controllers/TimelineController.php +++ b/app/Http/Controllers/TimelineController.php @@ -14,6 +14,7 @@ class TimelineController extends Controller public function __construct() { $this->middleware('auth'); + $this->middleware('twofactor'); } public function personal() diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index b90d197a..eb8a2a4f 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -61,6 +61,7 @@ class Kernel extends HttpKernel 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'twofactor' => \App\Http\Middleware\TwoFactorAuth::class, 'validemail' => \App\Http\Middleware\EmailVerificationCheck::class, ]; } diff --git a/app/Http/Middleware/TwoFactorAuth.php b/app/Http/Middleware/TwoFactorAuth.php new file mode 100644 index 00000000..9eb742e6 --- /dev/null +++ b/app/Http/Middleware/TwoFactorAuth.php @@ -0,0 +1,32 @@ +user()) { + $user = $request->user(); + $enabled = (bool) $user->{'2fa_enabled'}; + if($enabled != false) { + $checkpoint = 'i/auth/checkpoint'; + if($request->session()->has('2fa.session.active') !== true && !$request->is($checkpoint)) + { + return redirect('/i/auth/checkpoint'); + } + } + } + return $next($request); + } +} diff --git a/app/ImportJob.php b/app/ImportJob.php index 52b1187d..76a440d4 100644 --- a/app/ImportJob.php +++ b/app/ImportJob.php @@ -6,6 +6,11 @@ use Illuminate\Database\Eloquent\Model; class ImportJob extends Model { + public function profile() + { + return $this->belongsTo(Profile::class, 'profile_id'); + } + public function url() { return url("/i/import/job/{$this->uuid}/{$this->stage}"); diff --git a/app/User.php b/app/User.php index 4e014f04..7fba2c17 100644 --- a/app/User.php +++ b/app/User.php @@ -16,7 +16,7 @@ class User extends Authenticatable * * @var array */ - protected $dates = ['deleted_at', 'email_verified_at']; + protected $dates = ['deleted_at', 'email_verified_at', '2fa_setup_at']; /** * The attributes that are mass assignable. diff --git a/app/Util/Lexer/RestrictedNames.php b/app/Util/Lexer/RestrictedNames.php index 1bd67e81..0bd2648b 100644 --- a/app/Util/Lexer/RestrictedNames.php +++ b/app/Util/Lexer/RestrictedNames.php @@ -113,6 +113,7 @@ class RestrictedNames public static $reserved = [ // Reserved for instance admin 'admin', + 'administrator', // Static Assets 'assets', @@ -126,6 +127,7 @@ class RestrictedNames 'api', 'auth', 'css', + 'checkpoint', 'c', 'i', 'dashboard', diff --git a/resources/lang/cs/auth.php b/resources/lang/cs/auth.php new file mode 100644 index 00000000..a6568920 --- /dev/null +++ b/resources/lang/cs/auth.php @@ -0,0 +1,19 @@ + 'Tyto přihlašovací údaje se neshodují s našemi záznamy.', + 'throttle' => 'Příliš mnoho pokusů o přihlášení. Prosím zkuste to znovu za :seconds sekund.', + +]; diff --git a/resources/lang/cs/navmenu.php b/resources/lang/cs/navmenu.php new file mode 100644 index 00000000..7ca3c49b --- /dev/null +++ b/resources/lang/cs/navmenu.php @@ -0,0 +1,14 @@ + 'Zobrazit můj profil', + 'myTimeline' => 'Moje časová osa', + 'publicTimeline' => 'Veřejná časová osa', + 'remoteFollow' => 'Vzdálené sledování', + 'settings' => 'Nastavení', + 'admin' => 'Administrace', + 'logout' => 'Odhlásit', + 'directMessages' => 'Přímé zprávy', + +]; diff --git a/resources/lang/cs/notification.php b/resources/lang/cs/notification.php new file mode 100644 index 00000000..d93d21e6 --- /dev/null +++ b/resources/lang/cs/notification.php @@ -0,0 +1,10 @@ + 'si oblíbil/a vaši fotku.', + 'startedFollowingYou' => 'vás začal/a sledovat.', + 'commented' => 'okomentoval/a vaši fotku.', + 'mentionedYou' => 'vás zmínil/a.', + +]; diff --git a/resources/lang/cs/pagination.php b/resources/lang/cs/pagination.php new file mode 100644 index 00000000..865f9f0c --- /dev/null +++ b/resources/lang/cs/pagination.php @@ -0,0 +1,19 @@ + '« Předchozí', + 'next' => 'Další »', + +]; diff --git a/resources/lang/cs/passwords.php b/resources/lang/cs/passwords.php new file mode 100644 index 00000000..869237f2 --- /dev/null +++ b/resources/lang/cs/passwords.php @@ -0,0 +1,22 @@ + 'Hesla musí být alespoň šest znaků dlouhá a shodovat se s potvrzením.', + 'reset' => 'Vaše heslo bylo obnoveno!', + 'sent' => 'Poslali jsme vám e-mailem odkaz pro obnovu hesla!', + 'token' => 'Tento token pro obnovu hesla je neplatný.', + 'user' => "Nemůžeme najít uživatele s touto e-mailovou adresou.", + +]; diff --git a/resources/lang/cs/profile.php b/resources/lang/cs/profile.php new file mode 100644 index 00000000..f6fa1fee --- /dev/null +++ b/resources/lang/cs/profile.php @@ -0,0 +1,12 @@ + 'Tento uživatel ještě nemá žádné příspěvky!', + 'emptyFollowers' => 'Tento uživatel ještě nemá žádné sledovatele!', + 'emptyFollowing' => 'Tento uživatel ještě nikoho nesleduje!', + 'emptySaved' => 'Ještě jste neuložil/a žádné příspěvky!', + 'savedWarning' => 'Pouze vy můžete vidět, co máte uložené', + 'privateProfileWarning' => 'Tento účet je soukromý', + 'alreadyFollow' => 'Již uživatele :username sledujete?', + 'loginToSeeProfile' => 'pro zobrazení jeho/jejích fotek a videí.', +]; diff --git a/resources/lang/cs/timeline.php b/resources/lang/cs/timeline.php new file mode 100644 index 00000000..9516c030 --- /dev/null +++ b/resources/lang/cs/timeline.php @@ -0,0 +1,7 @@ + 'Vaše časová osa je prázdná.', + +]; diff --git a/resources/lang/cs/validation.php b/resources/lang/cs/validation.php new file mode 100644 index 00000000..fc23045b --- /dev/null +++ b/resources/lang/cs/validation.php @@ -0,0 +1,122 @@ + ':attribute musí být akceptován.', + 'active_url' => ':attribute není platná URL adresa.', + 'after' => ':attribute musí být datum po :date.', + 'after_or_equal' => ':attribute musí být datum po nebo rovný datu :date.', + 'alpha' => ':attribute musí obsahovat pouze písmena.', + 'alpha_dash' => ':attribute musí obsahovat pouze písmena, číslice a podtržítka.', + 'alpha_num' => ':attribute musí obsahovat pouze písmena a číslice.', + 'array' => ':attribute musí být pole.', + 'before' => ':attribute musí být datum před :date.', + 'before_or_equal' => ':attribute musí být datum před nebo rovný datu :date.', + 'between' => [ + 'numeric' => ':attribute musí být mezi :min a :max.', + 'file' => ':attribute musí být mezi :min a :max kilobyty.', + 'string' => ':attribute musí být mezi :min a :max znaky.', + 'array' => ':attribute musí mít mezi :min a :max položkami.', + ], + 'boolean' => 'Pole :attribute musí být true nebo false.', + 'confirmed' => 'Potvrzení :attribute se neshoduje.', + 'date' => ':attribute není platné datum.', + 'date_format' => ':attribute se neshoduje s formátem :format.', + 'different' => ':attribute a :other musí být jiné.', + 'digits' => ':attribute musí mít :digits číslic.', + 'digits_between' => ':attribute musí mít mezi :min a :max číslicemi.', + 'dimensions' => ':attribute má neplatné rozměry obrázku.', + 'distinct' => 'Pole :attribute má duplicitní hodnotu.', + 'email' => ':attribute musí být platná e-mailová adresa.', + 'exists' => 'Zvolený :attribute je neplatný.', + 'file' => ':attribute musí být soubor.', + 'filled' => 'Pole :attribute musí mít hodnotu.', + 'image' => ':attribute musí být obrázek.', + 'in' => 'Zvolený :attribute je neplatný.', + 'in_array' => 'Pole :attribute neexistuje v :other.', + 'integer' => ':attribute musí být celé číslo.', + 'ip' => ':attribute musí být platná IP adresa.', + 'ipv4' => ':attribute musí být platná IPv4 adresa.', + 'ipv6' => ':attribute musí být platná IPv6 adresa.', + 'json' => ':attribute musí být platný řetězec JSON.', + 'max' => [ + 'numeric' => ':attribute nesmí být větší než :max.', + 'file' => ':attribute nesmí být větší než :max kilobytů.', + 'string' => ':attribute nesmí být větší než :max znaků.', + 'array' => ':attribute nesmí mít více než :max položek.', + ], + 'mimes' => ':attribute musí být soubor typu: :values.', + 'mimetypes' => ':attribute musí být soubor typu: :values.', + 'min' => [ + 'numeric' => ':attribute musí být alespoň :min.', + 'file' => ':attribute musí být alespoň :min kilobytů.', + 'string' => ':attribute musí být alespoň :min znaků.', + 'array' => ':attribute musí mít alespoň :min položek.', + ], + 'not_in' => 'Zvolený :attribute je neplatný.', + 'not_regex' => 'Formát :attribute je neplatný.', + 'numeric' => ':attribute musí být číslo.', + 'present' => 'Pole :attribute musí být přítomné.', + 'regex' => 'Formát :attribute je neplatný.', + 'required' => 'Pole :attribute je vyžadováno.', + 'required_if' => 'Pole :attribute je vyžadováno, pokud je :other :value.', + 'required_unless' => 'Pole :attribute je vyžadováno, pokud není :other v :values.', + 'required_with' => 'Pole :attribute je vyžadováno, pokud je přítomno :values.', + 'required_with_all' => 'Pole :attribute je vyžadováno, pokud je přítomno :values.', + 'required_without' => 'Pole :attribute je vyžadováno, pokud není přítomno :values.', + 'required_without_all' => 'Pole :attribute je vyžadováno, pokud není přítomno žádné z :values.', + 'same' => ':attribute a :other se musí shodovat.', + 'size' => [ + 'numeric' => ':attribute musí být :size.', + 'file' => ':attribute musí být :size kilobytů.', + 'string' => ':attribute musí být :size znaků.', + 'array' => ':attribute musí obsahovat :size položek.', + ], + 'string' => ':attribute musí být řetězec.', + 'timezone' => ':attribute musí být platná zóna.', + 'unique' => ':attribute je již zabráno.', + 'uploaded' => 'Nahrávání :attribute selhalo.', + 'url' => 'Formát :attribute je neplatný.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap attribute place-holders + | with something more reader friendly such as E-Mail Address instead + | of "email". This simply helps us make messages a little cleaner. + | + */ + + 'attributes' => [], + +]; diff --git a/resources/views/auth/checkpoint.blade.php b/resources/views/auth/checkpoint.blade.php new file mode 100644 index 00000000..24c4ae77 --- /dev/null +++ b/resources/views/auth/checkpoint.blade.php @@ -0,0 +1,49 @@ +@extends('layouts.blank') + +@section('content') +
Verify 2FA Code to continue
++ To register a new device, you have to remove any active devices. +
+ ++ Added {{$user->{'2fa_setup_at'}->diffForHumans()}} +
++ +
++ Two factor authentication is not enabled yet. +
++ Two-factor authentication adds an additional layer of security to your account by requiring more than just a password to log in. Learn more. +
+ +Two-factor authentication adds an additional layer of security to your account by requiring more than just a password to log in. Learn more.
+No activity logs found!
+ @endif + @foreach($activity as $log) ++ IP Address: + + {{$log->ip_address}} + +
++ User Agent: + + {{$log->user_agent}} + +
++ Each code can only be used once. +
+ + +{{$code}}
You will need to install a compatible mobile app, we recommend the following apps:
+Please scan the QR code and then enter the 6 digit code in the form below. Keep in mind the code changes every 30 seconds, and is only good for 1 minute.
+Please store the following codes in a safe place, each backup code can be used only once if you do not have access to your 2FA mobile app.
+ +
+ @foreach($backups as $code)
+ {{$code}}
+ @endforeach
+
+