From 52145e2e4908096be0934ed7e848213bee81317b Mon Sep 17 00:00:00 2001 From: piciolo Date: Sun, 5 Apr 2026 20:34:34 +0200 Subject: [PATCH 1/9] feat: add password reset and forgot-email flows with branded SMTP email - Add MAIL_FROM_ADDRESS and MAIL_FROM_NAME to .env.example with SMTP provider hints - Wire up /forgot-password (Fortify) and /forgot-email routes; replace href="#" stubs in login modal - Create styled outgame Blade views for forgot-password, reset-password, forgot-email - Add ResetPasswordMail and RetrieveEmailMail Mailables with OGameX-branded HTML templates - Override User::sendPasswordResetNotification() to use branded mailable - Add ForgotEmailController: looks up user by username, sends masked-email hint, no username-leak - Add i18n keys for all new UI and email strings (EN / IT / NL) --- .env.example | 5 ++ .../Controllers/ForgotEmailController.php | 50 +++++++++++++ app/Mail/ResetPasswordMail.php | 33 +++++++++ app/Mail/RetrieveEmailMail.php | 34 +++++++++ app/Models/User.php | 15 ++++ app/Providers/FortifyServiceProvider.php | 12 ++-- resources/lang/en/t_external.php | 50 +++++++++++++ resources/lang/it/t_external.php | 50 +++++++++++++ resources/lang/nl/t_external.php | 50 +++++++++++++ .../views/outgame/forgot-email.blade.php | 53 ++++++++++++++ .../views/outgame/forgot-password.blade.php | 53 ++++++++++++++ .../views/outgame/layouts/main.blade.php | 4 +- .../outgame/mail/reset-password.blade.php | 38 ++++++++++ .../outgame/mail/retrieve-email.blade.php | 35 +++++++++ .../views/outgame/reset-password.blade.php | 72 +++++++++++++++++++ routes/web.php | 5 ++ 16 files changed, 549 insertions(+), 10 deletions(-) create mode 100644 app/Http/Controllers/ForgotEmailController.php create mode 100644 app/Mail/ResetPasswordMail.php create mode 100644 app/Mail/RetrieveEmailMail.php create mode 100644 resources/views/outgame/forgot-email.blade.php create mode 100644 resources/views/outgame/forgot-password.blade.php create mode 100644 resources/views/outgame/mail/reset-password.blade.php create mode 100644 resources/views/outgame/mail/retrieve-email.blade.php create mode 100644 resources/views/outgame/reset-password.blade.php diff --git a/.env.example b/.env.example index b879ee412..672cfd25c 100644 --- a/.env.example +++ b/.env.example @@ -43,6 +43,11 @@ MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null +MAIL_FROM_ADDRESS="noreply@ogamex.dev" +MAIL_FROM_NAME="${APP_NAME}" + +# To use Gmail: MAIL_HOST=smtp.gmail.com, MAIL_PORT=587, MAIL_ENCRYPTION=tls +# Other providers: Mailgun, Postmark, Resend — configure MAIL_HOST/PORT/CREDENTIALS above. REVERB_APP_ID=ogamex REVERB_APP_KEY=ogamex-key diff --git a/app/Http/Controllers/ForgotEmailController.php b/app/Http/Controllers/ForgotEmailController.php new file mode 100644 index 000000000..b37bf6a89 --- /dev/null +++ b/app/Http/Controllers/ForgotEmailController.php @@ -0,0 +1,50 @@ +validate([ + 'username' => ['required', 'string', 'max:255'], + ]); + + // Always show a success message regardless of whether the username exists, + // to avoid leaking which usernames are registered. + $user = User::where('username', $request->username)->first(); + + if ($user !== null) { + $maskedEmail = $this->maskEmail($user->email); + Mail::to($user->email)->send(new RetrieveEmailMail( + maskedEmail: $maskedEmail, + username: $user->username, + loginUrl: route('login'), + )); + } + + return redirect()->route('password.email-lookup') + ->with('status', __('t_external.forgot_email.sent')); + } + + private function maskEmail(string $email): string + { + [$local, $domain] = explode('@', $email, 2); + $visibleLocal = mb_substr($local, 0, min(2, mb_strlen($local))); + $masked = $visibleLocal . str_repeat('*', max(0, mb_strlen($local) - 2)); + + return $masked . '@' . $domain; + } +} diff --git a/app/Mail/ResetPasswordMail.php b/app/Mail/ResetPasswordMail.php new file mode 100644 index 000000000..56a80b543 --- /dev/null +++ b/app/Mail/ResetPasswordMail.php @@ -0,0 +1,33 @@ + $token, + 'email' => $this->email, + ], false)); + + Mail::to($this->email)->send(new ResetPasswordMail($resetUrl, $this->username)); + } } diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index 766c67fde..aa15883fe 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -71,16 +71,12 @@ public function boot(): void return $user; }); - /*Fortify::registerView(function () { - return view('auth.register'); - }); - Fortify::requestPasswordResetLinkView(function () { - return view('auth.forgot-password'); + return view('outgame.forgot-password'); }); - Fortify::resetPasswordView(function () { - return view('auth.reset-password'); - });*/ + Fortify::resetPasswordView(function ($request) { + return view('outgame.reset-password', ['request' => $request]); + }); } } diff --git a/resources/lang/en/t_external.php b/resources/lang/en/t_external.php index d51184924..1d61e7b76 100644 --- a/resources/lang/en/t_external.php +++ b/resources/lang/en/t_external.php @@ -94,6 +94,56 @@ 'only_letters' => 'Use characters only.', ], + // Forgot password page + 'forgot_password' => [ + 'title' => 'Forgot your password?', + 'description' => 'Enter your email address below and we will send you a link to reset your password.', + 'email_label' => 'Email address:', + 'submit' => 'Send reset link', + 'back_to_login' => '← Back to login', + ], + + // Reset password page + 'reset_password' => [ + 'title' => 'Reset your password', + 'email_label' => 'Email address:', + 'password_label' => 'New password:', + 'confirm_label' => 'Confirm new password:', + 'submit' => 'Reset password', + ], + + // Forgot email page + 'forgot_email' => [ + 'title' => 'Forgot your email address?', + 'description' => 'Enter your commander name and we will send a hint to the registered email address.', + 'username_label' => 'Commander name:', + 'submit' => 'Send hint', + 'back_to_login' => '← Back to login', + 'sent' => 'If a matching account was found, a hint has been sent to the registered email address.', + ], + + // Outgoing email templates + 'mail' => [ + 'reset_password' => [ + 'subject' => 'Reset your OGameX password', + 'heading' => 'Password Reset', + 'greeting' => 'Hello :username,', + 'body' => 'We received a request to reset the password for your account. Click the button below to choose a new password.', + 'cta' => 'Reset Password', + 'expiry' => 'This link will expire in 60 minutes.', + 'no_action' => 'If you did not request a password reset, no further action is required.', + 'url_fallback' => 'If you have trouble clicking the button, copy and paste the URL below into your browser:', + ], + 'retrieve_email' => [ + 'subject' => 'Your OGameX email address', + 'heading' => 'Email Address Hint', + 'greeting' => 'Hello :username,', + 'body' => 'You requested a hint for the email address associated with your account:', + 'cta' => 'Go to Login', + 'no_action' => 'If you did not make this request, you can safely ignore this email.', + ], + ], + // Universe selection characteristics tooltip texts 'universe_characteristics' => [ 'fleet_speed' => 'Fleet Speed: the higher the value, the less time you have left to react to an attack.', diff --git a/resources/lang/it/t_external.php b/resources/lang/it/t_external.php index 85f6e9a97..ed7f039e5 100644 --- a/resources/lang/it/t_external.php +++ b/resources/lang/it/t_external.php @@ -94,6 +94,56 @@ 'only_letters' => 'Usa solo lettere.', ], + // Pagina password dimenticata + 'forgot_password' => [ + 'title' => 'Hai dimenticato la password?', + 'description' => 'Inserisci il tuo indirizzo e-mail e ti invieremo un link per reimpostare la password.', + 'email_label' => 'Indirizzo e-mail:', + 'submit' => 'Invia link di ripristino', + 'back_to_login' => '← Torna al login', + ], + + // Pagina reimposta password + 'reset_password' => [ + 'title' => 'Reimposta la password', + 'email_label' => 'Indirizzo e-mail:', + 'password_label' => 'Nuova password:', + 'confirm_label' => 'Conferma nuova password:', + 'submit' => 'Reimposta password', + ], + + // Pagina e-mail dimenticata + 'forgot_email' => [ + 'title' => 'Hai dimenticato l\'indirizzo e-mail?', + 'description' => 'Inserisci il nome del tuo comandante e ti invieremo un suggerimento all\'indirizzo e-mail registrato.', + 'username_label' => 'Nome comandante:', + 'submit' => 'Invia suggerimento', + 'back_to_login' => '← Torna al login', + 'sent' => 'Se è stato trovato un account corrispondente, è stato inviato un suggerimento all\'indirizzo e-mail registrato.', + ], + + // Template e-mail in uscita + 'mail' => [ + 'reset_password' => [ + 'subject' => 'Reimposta la tua password OGameX', + 'heading' => 'Ripristino Password', + 'greeting' => 'Ciao :username,', + 'body' => 'Abbiamo ricevuto una richiesta per reimpostare la password del tuo account. Clicca il pulsante qui sotto per scegliere una nuova password.', + 'cta' => 'Reimposta Password', + 'expiry' => 'Questo link scadrà tra 60 minuti.', + 'no_action' => 'Se non hai richiesto il ripristino della password, non è necessaria alcuna ulteriore azione.', + 'url_fallback' => 'Se hai problemi a cliccare il pulsante, copia e incolla l\'URL qui sotto nel tuo browser:', + ], + 'retrieve_email' => [ + 'subject' => 'Il tuo indirizzo e-mail OGameX', + 'heading' => 'Suggerimento Indirizzo E-mail', + 'greeting' => 'Ciao :username,', + 'body' => 'Hai richiesto un suggerimento per l\'indirizzo e-mail associato al tuo account:', + 'cta' => 'Vai al Login', + 'no_action' => 'Se non hai effettuato questa richiesta, puoi ignorare questa e-mail.', + ], + ], + // Testi tooltip caratteristiche universo 'universe_characteristics' => [ 'fleet_speed' => 'Velocità flotta: maggiore è il valore, meno tempo hai per reagire a un attacco.', diff --git a/resources/lang/nl/t_external.php b/resources/lang/nl/t_external.php index 547b39488..2cea83fae 100644 --- a/resources/lang/nl/t_external.php +++ b/resources/lang/nl/t_external.php @@ -94,6 +94,56 @@ 'only_letters' => 'Gebruik alleen letters.', ], + // Pagina wachtwoord vergeten + 'forgot_password' => [ + 'title' => 'Wachtwoord vergeten?', + 'description' => 'Voer uw e-mailadres in en wij sturen u een link om uw wachtwoord opnieuw in te stellen.', + 'email_label' => 'E-mailadres:', + 'submit' => 'Resetlink versturen', + 'back_to_login' => '← Terug naar inloggen', + ], + + // Pagina wachtwoord opnieuw instellen + 'reset_password' => [ + 'title' => 'Wachtwoord opnieuw instellen', + 'email_label' => 'E-mailadres:', + 'password_label' => 'Nieuw wachtwoord:', + 'confirm_label' => 'Nieuw wachtwoord bevestigen:', + 'submit' => 'Wachtwoord opnieuw instellen', + ], + + // Pagina e-mailadres vergeten + 'forgot_email' => [ + 'title' => 'E-mailadres vergeten?', + 'description' => 'Voer uw commandantsnaam in en wij sturen een hint naar het geregistreerde e-mailadres.', + 'username_label' => 'Commandantsnaam:', + 'submit' => 'Hint versturen', + 'back_to_login' => '← Terug naar inloggen', + 'sent' => 'Als er een overeenkomend account is gevonden, is er een hint naar het geregistreerde e-mailadres gestuurd.', + ], + + // Uitgaande e-mailsjablonen + 'mail' => [ + 'reset_password' => [ + 'subject' => 'Stel uw OGameX-wachtwoord opnieuw in', + 'heading' => 'Wachtwoord Opnieuw Instellen', + 'greeting' => 'Hallo :username,', + 'body' => 'We hebben een verzoek ontvangen om het wachtwoord voor uw account opnieuw in te stellen. Klik op de knop hieronder om een nieuw wachtwoord te kiezen.', + 'cta' => 'Wachtwoord Opnieuw Instellen', + 'expiry' => 'Deze link verloopt over 60 minuten.', + 'no_action' => 'Als u geen wachtwoordreset heeft aangevraagd, is geen verdere actie vereist.', + 'url_fallback' => 'Als u problemen heeft met het klikken op de knop, kopieer en plak dan de onderstaande URL in uw browser:', + ], + 'retrieve_email' => [ + 'subject' => 'Uw OGameX e-mailadres', + 'heading' => 'Hint E-mailadres', + 'greeting' => 'Hallo :username,', + 'body' => 'U heeft een hint aangevraagd voor het e-mailadres dat aan uw account is gekoppeld:', + 'cta' => 'Ga naar Inloggen', + 'no_action' => 'Als u dit verzoek niet heeft gedaan, kunt u deze e-mail veilig negeren.', + ], + ], + // Tooltip-teksten universum kenmerken 'universe_characteristics' => [ 'fleet_speed' => 'Vlootsnelheid: hoe hoger de waarde, hoe minder tijd u heeft om te reageren op een aanval.', diff --git a/resources/views/outgame/forgot-email.blade.php b/resources/views/outgame/forgot-email.blade.php new file mode 100644 index 000000000..7a20efca9 --- /dev/null +++ b/resources/views/outgame/forgot-email.blade.php @@ -0,0 +1,53 @@ +@extends('outgame.layouts.main') + +@section('content') + + + +@endsection diff --git a/resources/views/outgame/forgot-password.blade.php b/resources/views/outgame/forgot-password.blade.php new file mode 100644 index 000000000..4dc2cd783 --- /dev/null +++ b/resources/views/outgame/forgot-password.blade.php @@ -0,0 +1,53 @@ +@extends('outgame.layouts.main') + +@section('content') + + + +@endsection diff --git a/resources/views/outgame/layouts/main.blade.php b/resources/views/outgame/layouts/main.blade.php index 876b7506b..c67aac14c 100644 --- a/resources/views/outgame/layouts/main.blade.php +++ b/resources/views/outgame/layouts/main.blade.php @@ -206,9 +206,9 @@ class="browserimg ie">IE 8+ - {{ __('t_external.login.forgot_password') }} + {{ __('t_external.login.forgot_password') }}
- {{ __('t_external.login.forgot_email') }} + {{ __('t_external.login.forgot_email') }}

{!! __('t_external.login.terms_accept_html') !!}

diff --git a/resources/views/outgame/mail/reset-password.blade.php b/resources/views/outgame/mail/reset-password.blade.php new file mode 100644 index 000000000..2e823980a --- /dev/null +++ b/resources/views/outgame/mail/reset-password.blade.php @@ -0,0 +1,38 @@ + + + + + + {{ __('t_external.mail.reset_password.subject') }} + + + +
+

{{ __('t_external.mail.reset_password.heading') }}

+ +

{{ __('t_external.mail.reset_password.greeting', ['username' => $username]) }}

+

{{ __('t_external.mail.reset_password.body') }}

+ + {{ __('t_external.mail.reset_password.cta') }} + +

{{ __('t_external.mail.reset_password.expiry') }}

+

{{ __('t_external.mail.reset_password.no_action') }}

+ +

{{ __('t_external.mail.reset_password.url_fallback') }}
+ {{ $resetUrl }} +

+ + +
+ + diff --git a/resources/views/outgame/mail/retrieve-email.blade.php b/resources/views/outgame/mail/retrieve-email.blade.php new file mode 100644 index 000000000..fe7701161 --- /dev/null +++ b/resources/views/outgame/mail/retrieve-email.blade.php @@ -0,0 +1,35 @@ + + + + + + {{ __('t_external.mail.retrieve_email.subject') }} + + + +
+

{{ __('t_external.mail.retrieve_email.heading') }}

+ +

{{ __('t_external.mail.retrieve_email.greeting', ['username' => $username]) }}

+

{{ __('t_external.mail.retrieve_email.body') }}

+ + + + {{ __('t_external.mail.retrieve_email.cta') }} + +

{{ __('t_external.mail.retrieve_email.no_action') }}

+ + +
+ + diff --git a/resources/views/outgame/reset-password.blade.php b/resources/views/outgame/reset-password.blade.php new file mode 100644 index 000000000..7a7122b07 --- /dev/null +++ b/resources/views/outgame/reset-password.blade.php @@ -0,0 +1,72 @@ +@extends('outgame.layouts.main') + +@section('content') + + + +@endsection diff --git a/routes/web.php b/routes/web.php index 65dc6074b..c549f1851 100644 --- a/routes/web.php +++ b/routes/web.php @@ -33,6 +33,7 @@ use OGame\Http\Controllers\ResearchController; use OGame\Http\Controllers\ResourcesController; use OGame\Http\Controllers\RewardsController; +use OGame\Http\Controllers\ForgotEmailController; use OGame\Http\Controllers\RulesController; use OGame\Http\Controllers\SearchController; use OGame\Http\Controllers\ServerSettingsController; @@ -56,6 +57,10 @@ // Language switcher — accessible to both guests and authenticated users. Route::get('/lang/{lang}', [LanguageController::class, 'switchLang'])->name('language.switch'); +// Forgot email lookup (guest only). +Route::get('/forgot-email', [ForgotEmailController::class, 'show'])->name('password.email-lookup'); +Route::post('/forgot-email', [ForgotEmailController::class, 'send'])->middleware('throttle:5,1'); + // Public AJAX endpoints (no auth required). Route::get('/ajax/main/rules', [RulesController::class, 'ajaxRules'])->name('rules.ajax'); Route::get('/ajax/main/legal', [RulesController::class, 'ajaxLegal'])->name('legal.ajax'); From 4792dc5fbb503fccfdb45d94a43e07756e3593f5 Mon Sep 17 00:00:00 2001 From: piciolo Date: Sun, 12 Apr 2026 11:39:27 +0200 Subject: [PATCH 2/9] fix: redirect forgot-password and forgot-email links from legacy OGame URLs - Replace hardcoded /game/reg/mail.php click handlers in outgame JS with navigation to /forgot-password and /forgot-email - Rebuild outgame bundle with updated JS --- resources/js/outgame/b55eb79922e157d28e811c7452ab10.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/outgame/b55eb79922e157d28e811c7452ab10.js b/resources/js/outgame/b55eb79922e157d28e811c7452ab10.js index 747557aa6..0cc6bc32f 100644 --- a/resources/js/outgame/b55eb79922e157d28e811c7452ab10.js +++ b/resources/js/outgame/b55eb79922e157d28e811c7452ab10.js @@ -1 +1 @@ -$(document).ready(function(){var a={};jQuery.fn.exists=function(){return jQuery(this).length>0};$("#menu").tabs({event:"click",activate:function(j,k){a=$(j.currentTarget).attr("id");d()},beforeLoad:function(j,k){k.jqXHR.fail(function(){k.panel.html("")})}});a=$("ul#tabs a.current").attr("id");d();$("ul#tabs a").click(function(){$(".formError").hide();a=$(this).attr("id");d()});function d(){switch(a){case"tab1":$("#menu").css("background-position","15px -33px");break;case"tab2":$("#menu").css("background-position","15px -66px");break;case"tab3":$("#menu").css("background-position","15px -99px");break;case"tab4":$("#menu").css("background-position","15px -132px");break;default:break}}function c(){$("#loginBtn").addClass("open").text(JSLoca[1]);$("#login").fadeIn("fast",function(){$("#usernameLogin").focus()});if(emailOnlyLogin){$("#transition_email_only_login_dialog").dialog("open")}}function i(){$("#loginBtn").removeClass("open").text(JSLoca[0]);$("#login").fadeOut();if(emailOnlyLogin){$("#transition_email_only_login_dialog").dialog("close")}}$("#loginBtn").click(function(){$(".formError").hide();if($(this).hasClass("open")){i()}else{c()}});$("#pwLost").click(function(){$(this).attr("href","https://"+$("#serverLogin").val()+"/game/reg/mail.php")});$("#emailLost").click(function(){$(this).attr("href","https://"+$("#serverLogin").val()+"/game/index.php?page=standalone&component=retrieveEmail")});$("#resendAct").click(function(){$(".formError").hide();$("#tabs .current").removeClass("current");$("#menu").css("background-position","15px 0");$("#ajaxContent, #passwordLost, #login").hide();$("#resendLink").fadeIn();$("#loginBtn").removeClass("open").text("Login")});$("#trigger").toggle(function(){$("#selected, #language").fadeIn()},function(){$("#selected, #language").fadeOut()});$("#langHead a").click(function(){var j=$(this).attr("title");$("#selected, #displayLang").text(j)});$("#loginForm").validationEngine({validationEventTriggers:"blur",promptPosition:"centerRight",inlineValidation:true});$("#pwLostForm").validationEngine();$("#resendLinkForm").validationEngine();$("#subscribeForm").validationEngine({validationEventTriggers:"keyup blur",promptPosition:"centerRight",inlineValidation:true});$("#regSubmit").click(function(){var j=$("#username");if(j.val()===""){j.val(j.attr("placeholder"))}var k=$("#subscribeForm").validationEngine({returnIsValid:true});if(k==true){submitRegistrationForm();return false}});$("a.overlay").fancybox({beforeLoad:function(){$(".formError").hide()},type:"ajax",touch:{vertical:false}});$('a.overlay[href*="distinctions"]').fancybox({beforeLoad:function(){$(".formError").hide()},type:"ajax",baseTpl:'',touch:{vertical:false}});$("#trailer").click(function(){$(".formError").hide()});function b(l){var j="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.$!;:-_#";for(var k=0;k0};$("#menu").tabs({event:"click",activate:function(j,k){a=$(j.currentTarget).attr("id");d()},beforeLoad:function(j,k){k.jqXHR.fail(function(){k.panel.html("")})}});a=$("ul#tabs a.current").attr("id");d();$("ul#tabs a").click(function(){$(".formError").hide();a=$(this).attr("id");d()});function d(){switch(a){case"tab1":$("#menu").css("background-position","15px -33px");break;case"tab2":$("#menu").css("background-position","15px -66px");break;case"tab3":$("#menu").css("background-position","15px -99px");break;case"tab4":$("#menu").css("background-position","15px -132px");break;default:break}}function c(){$("#loginBtn").addClass("open").text(JSLoca[1]);$("#login").fadeIn("fast",function(){$("#usernameLogin").focus()});if(emailOnlyLogin){$("#transition_email_only_login_dialog").dialog("open")}}function i(){$("#loginBtn").removeClass("open").text(JSLoca[0]);$("#login").fadeOut();if(emailOnlyLogin){$("#transition_email_only_login_dialog").dialog("close")}}$("#loginBtn").click(function(){$(".formError").hide();if($(this).hasClass("open")){i()}else{c()}});$("#pwLost").click(function(){window.location.href="/forgot-password";return false;});$("#emailLost").click(function(){window.location.href="/forgot-email";return false;});$("#resendAct").click(function(){$(".formError").hide();$("#tabs .current").removeClass("current");$("#menu").css("background-position","15px 0");$("#ajaxContent, #passwordLost, #login").hide();$("#resendLink").fadeIn();$("#loginBtn").removeClass("open").text("Login")});$("#trigger").toggle(function(){$("#selected, #language").fadeIn()},function(){$("#selected, #language").fadeOut()});$("#langHead a").click(function(){var j=$(this).attr("title");$("#selected, #displayLang").text(j)});$("#loginForm").validationEngine({validationEventTriggers:"blur",promptPosition:"centerRight",inlineValidation:true});$("#pwLostForm").validationEngine();$("#resendLinkForm").validationEngine();$("#subscribeForm").validationEngine({validationEventTriggers:"keyup blur",promptPosition:"centerRight",inlineValidation:true});$("#regSubmit").click(function(){var j=$("#username");if(j.val()===""){j.val(j.attr("placeholder"))}var k=$("#subscribeForm").validationEngine({returnIsValid:true});if(k==true){submitRegistrationForm();return false}});$("a.overlay").fancybox({beforeLoad:function(){$(".formError").hide()},type:"ajax",touch:{vertical:false}});$('a.overlay[href*="distinctions"]').fancybox({beforeLoad:function(){$(".formError").hide()},type:"ajax",baseTpl:'',touch:{vertical:false}});$("#trailer").click(function(){$(".formError").hide()});function b(l){var j="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.$!;:-_#";for(var k=0;k Date: Sun, 5 Apr 2026 21:50:19 +0200 Subject: [PATCH 3/9] fix: add password_reset_tokens migration and configurable reset throttle - Add migration to create password_reset_tokens table (email PK, token, created_at) - Make password reset throttle configurable via AUTH_PASSWORD_RESET_THROTTLE env var (defaults to 60s in production; set to 0 in local .env to disable during development) - Document AUTH_PASSWORD_RESET_THROTTLE and MAIL_FROM_* keys in .env.example --- .env.example | 3 ++ config/auth.php | 2 +- ...349_create_password_reset_tokens_table.php | 28 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 database/migrations/2026_04_05_193349_create_password_reset_tokens_table.php diff --git a/.env.example b/.env.example index 672cfd25c..38dc8edde 100644 --- a/.env.example +++ b/.env.example @@ -37,6 +37,9 @@ REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 +# Set to 0 to disable throttle during local development (default: 60 seconds) +AUTH_PASSWORD_RESET_THROTTLE=60 + MAIL_DRIVER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 diff --git a/config/auth.php b/config/auth.php index 5531825e2..e4c635cdb 100644 --- a/config/auth.php +++ b/config/auth.php @@ -95,7 +95,7 @@ 'provider' => 'users', 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), 'expire' => 60, - 'throttle' => 60, + 'throttle' => env('AUTH_PASSWORD_RESET_THROTTLE', 60), ], ], diff --git a/database/migrations/2026_04_05_193349_create_password_reset_tokens_table.php b/database/migrations/2026_04_05_193349_create_password_reset_tokens_table.php new file mode 100644 index 000000000..81a7229b0 --- /dev/null +++ b/database/migrations/2026_04_05_193349_create_password_reset_tokens_table.php @@ -0,0 +1,28 @@ +string('email')->primary(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('password_reset_tokens'); + } +}; From 0c984fc9eb1924dda1c0e43b9d4aa3fdfdc9dfe4 Mon Sep 17 00:00:00 2001 From: piciolo Date: Sun, 5 Apr 2026 23:12:15 +0200 Subject: [PATCH 4/9] fix: embed logo as base64 in email templates so it renders in all mail clients MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Logo was previously loaded via APP_URL (http://localhost in dev), which Gmail cannot fetch - Replace external URL reference with inline base64 data URI for the OGameX icon - Fix incorrect image path (/img/outgame/ → /img/icons/ where the file actually lives) - Also add MAIL_MAILER=smtp to .env.example to prevent emails silently falling back to log driver --- .env.example | 1 + .../outgame/mail/reset-password.blade.php | 140 ++++++++++++++---- .../outgame/mail/retrieve-email.blade.php | 132 ++++++++++++++--- 3 files changed, 222 insertions(+), 51 deletions(-) diff --git a/.env.example b/.env.example index 38dc8edde..9a09ff743 100644 --- a/.env.example +++ b/.env.example @@ -40,6 +40,7 @@ REDIS_PORT=6379 # Set to 0 to disable throttle during local development (default: 60 seconds) AUTH_PASSWORD_RESET_THROTTLE=60 +MAIL_MAILER=smtp MAIL_DRIVER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 diff --git a/resources/views/outgame/mail/reset-password.blade.php b/resources/views/outgame/mail/reset-password.blade.php index 2e823980a..7d6de42b6 100644 --- a/resources/views/outgame/mail/reset-password.blade.php +++ b/resources/views/outgame/mail/reset-password.blade.php @@ -1,38 +1,124 @@ - - + + - - + + {{ __('t_external.mail.reset_password.subject') }} - - -
-

{{ __('t_external.mail.reset_password.heading') }}

+ -

{{ __('t_external.mail.reset_password.greeting', ['username' => $username]) }}

-

{{ __('t_external.mail.reset_password.body') }}

+ + + + + +
- {{ __('t_external.mail.reset_password.cta') }} + + -

{{ __('t_external.mail.reset_password.expiry') }}

-

{{ __('t_external.mail.reset_password.no_action') }}

+ + + + -

{{ __('t_external.mail.reset_password.url_fallback') }}
- {{ $resetUrl }} -

+ + + + + + + + + + + + + + + + + + + + +
+ OGameX +

+ OGameX +

+

+ Conquer the Universe +

+
+

+ {{ __('t_external.mail.reset_password.heading') }} +

+

+ {{ __('t_external.mail.reset_password.greeting', ['username' => $username]) }} +

+

+ {{ __('t_external.mail.reset_password.body') }} +

+
+ + + + +
+ + {{ __('t_external.mail.reset_password.cta') }} + +
+
+

+ ⏳ {{ __('t_external.mail.reset_password.expiry') }} +

+

+ {{ __('t_external.mail.reset_password.no_action') }} +

+
+

+ {{ __('t_external.mail.reset_password.url_fallback') }} +

+

+ {{ $resetUrl }} +

+
+ + + + + + + +
+

+ © OGameX — {{ __('t_external.footer.copyright') }} +

+
+ +
- -
diff --git a/resources/views/outgame/mail/retrieve-email.blade.php b/resources/views/outgame/mail/retrieve-email.blade.php index fe7701161..3a3cd7d11 100644 --- a/resources/views/outgame/mail/retrieve-email.blade.php +++ b/resources/views/outgame/mail/retrieve-email.blade.php @@ -1,35 +1,119 @@ - - + + - - + + {{ __('t_external.mail.retrieve_email.subject') }} - - -
-

{{ __('t_external.mail.retrieve_email.heading') }}

+ -

{{ __('t_external.mail.retrieve_email.greeting', ['username' => $username]) }}

-

{{ __('t_external.mail.retrieve_email.body') }}

+ + + + +
- + + - {{ __('t_external.mail.retrieve_email.cta') }} + + + + -

{{ __('t_external.mail.retrieve_email.no_action') }}

+ + + + + + + + + + + + + + + + + + + + +
+ OGameX +

+ OGameX +

+

+ Conquer the Universe +

+
+

+ {{ __('t_external.mail.retrieve_email.heading') }} +

+

+ {{ __('t_external.mail.retrieve_email.greeting', ['username' => $username]) }} +

+

+ {{ __('t_external.mail.retrieve_email.body') }} +

+
+ + + + +
+ {{ $maskedEmail }} +
+
+ + + + +
+ + {{ __('t_external.mail.retrieve_email.cta') }} + +
+
+

+ {{ __('t_external.mail.retrieve_email.no_action') }} +

+
+ + + + + + + +
+

+ © OGameX — {{ __('t_external.footer.copyright') }} +

+
+ +
- -
From 415c971db463b82dde139e48f8bc9e7d875de0c7 Mon Sep 17 00:00:00 2001 From: piciolo Date: Mon, 13 Apr 2026 15:38:07 +0200 Subject: [PATCH 5/9] =?UTF-8?q?fix:=20address=20PR=20review=20=E2=80=94=20?= =?UTF-8?q?remove=20deprecated=20MAIL=5FDRIVER,=20drop=20email=20backgroun?= =?UTF-8?q?d-image,=20add=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove deprecated MAIL_DRIVER=smtp from .env.example (Laravel 7+, replaced by MAIL_MAILER) - Drop CSS background-image from email templates: Gmail strips it, Outlook ignores it, Apple Mail blocks it — the solid #000000 fallback is now the only background - Add ForgotPasswordTest (5 tests): page load, reset link sent/not sent, valid/invalid token - Add ForgotEmailTest (5 tests): page load, email sent/not sent, masked email, validation --- .env.example | 1 - .../outgame/mail/reset-password.blade.php | 5 +- .../outgame/mail/retrieve-email.blade.php | 2 +- tests/Feature/ForgotEmailTest.php | 105 +++++++++++++++ tests/Feature/ForgotPasswordTest.php | 120 ++++++++++++++++++ 5 files changed, 227 insertions(+), 6 deletions(-) create mode 100644 tests/Feature/ForgotEmailTest.php create mode 100644 tests/Feature/ForgotPasswordTest.php diff --git a/.env.example b/.env.example index 9a09ff743..57423f748 100644 --- a/.env.example +++ b/.env.example @@ -41,7 +41,6 @@ REDIS_PORT=6379 AUTH_PASSWORD_RESET_THROTTLE=60 MAIL_MAILER=smtp -MAIL_DRIVER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=null diff --git a/resources/views/outgame/mail/reset-password.blade.php b/resources/views/outgame/mail/reset-password.blade.php index 7d6de42b6..a24432c66 100644 --- a/resources/views/outgame/mail/reset-password.blade.php +++ b/resources/views/outgame/mail/reset-password.blade.php @@ -9,9 +9,6 @@ body { margin: 0; padding: 0; background-color: #000000; - background-image: url('{{ config('app.url') }}/img/outgame/1867da5b5f8769b547bb91d88bb4f8.jpg'); - background-repeat: no-repeat; - background-position: center top; font-family: Helvetica, Arial, sans-serif; } table { border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; } @@ -24,7 +21,7 @@ + style="background-color:#000000;">
diff --git a/resources/views/outgame/mail/retrieve-email.blade.php b/resources/views/outgame/mail/retrieve-email.blade.php index 3a3cd7d11..72a182494 100644 --- a/resources/views/outgame/mail/retrieve-email.blade.php +++ b/resources/views/outgame/mail/retrieve-email.blade.php @@ -20,7 +20,7 @@ + style="background-color:#000000;">
diff --git a/tests/Feature/ForgotEmailTest.php b/tests/Feature/ForgotEmailTest.php new file mode 100644 index 000000000..d25ad47f3 --- /dev/null +++ b/tests/Feature/ForgotEmailTest.php @@ -0,0 +1,105 @@ +post('/logout'); + + $response = $this->get('/forgot-email'); + $response->assertStatus(200); + } + + /** + * Test that submitting an existing username sends the retrieval mail. + */ + public function testEmailSentForExistingUsername(): void + { + $this->post('/logout'); + Mail::fake(); + + $user = User::find($this->currentUserId); + + $response = $this->post('/forgot-email', [ + 'username' => $user->username, + ]); + + $response->assertRedirect(route('password.email-lookup')); + $response->assertSessionHas('status'); + + Mail::assertSent(RetrieveEmailMail::class, function (RetrieveEmailMail $mail) use ($user) { + return $mail->hasTo($user->email) && $mail->username === $user->username; + }); + } + + /** + * Test that submitting a non-existent username does NOT send a mail + * but still returns the same success redirect (no username enumeration). + */ + public function testEmailNotSentForUnknownUsername(): void + { + $this->post('/logout'); + Mail::fake(); + + $response = $this->post('/forgot-email', [ + 'username' => 'nonexistent-user-' . uniqid(), + ]); + + $response->assertRedirect(route('password.email-lookup')); + $response->assertSessionHas('status'); + + Mail::assertNotSent(RetrieveEmailMail::class); + } + + /** + * Test that the masked email is correct (first 2 chars visible, rest masked). + */ + public function testMaskedEmailInMailContent(): void + { + $this->post('/logout'); + Mail::fake(); + + $user = User::find($this->currentUserId); + + $this->post('/forgot-email', [ + 'username' => $user->username, + ]); + + Mail::assertSent(RetrieveEmailMail::class, function (RetrieveEmailMail $mail) use ($user) { + // The masked email should start with the first 2 chars of the local part. + [$local, $domain] = explode('@', $user->email, 2); + $expectedPrefix = mb_substr($local, 0, min(2, mb_strlen($local))); + + return str_starts_with($mail->maskedEmail, $expectedPrefix) + && str_contains($mail->maskedEmail, '@' . $domain) + && str_contains($mail->maskedEmail, '***'); + }); + } + + /** + * Test that submitting without a username returns a validation error. + */ + public function testValidationRequiresUsername(): void + { + $this->post('/logout'); + + $response = $this->post('/forgot-email', [ + 'username' => '', + ]); + + $response->assertSessionHasErrors('username'); + } +} diff --git a/tests/Feature/ForgotPasswordTest.php b/tests/Feature/ForgotPasswordTest.php new file mode 100644 index 000000000..54c9bd013 --- /dev/null +++ b/tests/Feature/ForgotPasswordTest.php @@ -0,0 +1,120 @@ +post('/logout'); + + $response = $this->get('/forgot-password'); + $response->assertStatus(200); + } + + /** + * Test that requesting a reset link for an existing email sends a mail. + */ + public function testResetLinkSentForExistingEmail(): void + { + $this->post('/logout'); + Mail::fake(); + + $user = User::find($this->currentUserId); + + $response = $this->post('/forgot-password', [ + 'email' => $user->email, + ]); + + $response->assertRedirect(); + + Mail::assertSent(ResetPasswordMail::class, function (ResetPasswordMail $mail) use ($user) { + return $mail->hasTo($user->email) && $mail->username === $user->username; + }); + } + + /** + * Test that requesting a reset link for a non-existent email does not send a mail + * but still redirects (no user enumeration). + */ + public function testResetLinkNotSentForUnknownEmail(): void + { + $this->post('/logout'); + Mail::fake(); + + $response = $this->post('/forgot-password', [ + 'email' => 'nonexistent-user-' . uniqid() . '@example.com', + ]); + + $response->assertRedirect(); + + Mail::assertNotSent(ResetPasswordMail::class); + } + + /** + * Test that the reset-password form renders with a valid token. + */ + public function testResetPasswordPageLoadsWithToken(): void + { + $this->post('/logout'); + + $user = User::find($this->currentUserId); + $token = Password::createToken($user); + + $response = $this->get('/reset-password/' . $token . '?email=' . urlencode($user->email)); + $response->assertStatus(200); + } + + /** + * Test that submitting a valid reset completes successfully. + */ + public function testPasswordCanBeResetWithValidToken(): void + { + $this->post('/logout'); + + $user = User::find($this->currentUserId); + $token = Password::createToken($user); + + $response = $this->post('/reset-password', [ + 'token' => $token, + 'email' => $user->email, + 'password' => 'new-password-123', + 'password_confirmation' => 'new-password-123', + ]); + + $response->assertRedirect('/login'); + } + + /** + * Test that an invalid token is rejected. + */ + public function testPasswordResetFailsWithInvalidToken(): void + { + $this->post('/logout'); + + $user = User::find($this->currentUserId); + + $response = $this->post('/reset-password', [ + 'token' => 'invalid-token-value', + 'email' => $user->email, + 'password' => 'new-password-123', + 'password_confirmation' => 'new-password-123', + ]); + + // Should redirect back with errors (invalid token). + $response->assertRedirect(); + $response->assertSessionHasErrors(); + } +} From cfd60011f06da6923008a8a755daea6149df792e Mon Sep 17 00:00:00 2001 From: piciolo Date: Mon, 13 Apr 2026 15:56:59 +0200 Subject: [PATCH 6/9] =?UTF-8?q?fix:=20clean=20up=20email=20templates=20?= =?UTF-8?q?=E2=80=94=20white=20background,=20remove=20broken=20logo,=20bla?= =?UTF-8?q?ck=20copyright?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove base64 logo image (not rendering in email clients, shows broken icon) - Change outer background from #000000 to #ffffff (white) - Change copyright text color from #425463 to #000000 (black) - Remove deprecated MAIL_DRIVER from .env.example - Fix leftover img tag remnants in reset-password template --- .../views/outgame/mail/reset-password.blade.php | 14 +++++--------- .../views/outgame/mail/retrieve-email.blade.php | 13 +++++-------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/resources/views/outgame/mail/reset-password.blade.php b/resources/views/outgame/mail/reset-password.blade.php index a24432c66..928446c14 100644 --- a/resources/views/outgame/mail/reset-password.blade.php +++ b/resources/views/outgame/mail/reset-password.blade.php @@ -8,20 +8,19 @@ body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } body { margin: 0; padding: 0; - background-color: #000000; + background-color: #ffffff; font-family: Helvetica, Arial, sans-serif; } table { border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; } - img { border: 0; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic; } a { color: #619fc8; text-decoration: none; font-weight: bold; } a:hover { color: #91b0c4; } - + + style="background-color:#ffffff;">
@@ -32,9 +31,6 @@
- OGameX

OGameX

@@ -106,8 +102,8 @@ diff --git a/resources/views/outgame/mail/retrieve-email.blade.php b/resources/views/outgame/mail/retrieve-email.blade.php index 72a182494..3510f80b4 100644 --- a/resources/views/outgame/mail/retrieve-email.blade.php +++ b/resources/views/outgame/mail/retrieve-email.blade.php @@ -8,7 +8,7 @@ body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } body { margin: 0; padding: 0; - background-color: #000000; + background-color: #ffffff; font-family: Helvetica, Arial, sans-serif; } table { border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; } @@ -17,10 +17,10 @@ a:hover { color: #91b0c4; } - +
-

- © OGameX — {{ __('t_external.footer.copyright') }} +

+ {{ __('t_external.footer.copyright') }}

+ style="background-color:#ffffff;">
@@ -31,9 +31,6 @@
- OGameX

OGameX

@@ -104,8 +101,8 @@ From b74d0cd5b1b4b45abd3ecb8a3b1e7776f7438dfd Mon Sep 17 00:00:00 2001 From: piciolo Date: Mon, 13 Apr 2026 17:23:50 +0200 Subject: [PATCH 7/9] =?UTF-8?q?fix:=20resolve=20CI=20test=20failures=20?= =?UTF-8?q?=E2=80=94=20named=20rate=20limiter=20for=20forgot-email,=20rest?= =?UTF-8?q?ore=20test=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace inline throttle:5,1 with named 'forgot-email' rate limiter - Limiter returns Limit::none() in testing environment to prevent 429 responses - Keeps 5 req/min limit in production - Restore ForgotEmailTest and ForgotPasswordTest lost during previous amend - Remove deprecated MAIL_DRIVER from .env.example (reviewer feedback) --- app/Providers/FortifyServiceProvider.php | 8 ++++++++ routes/web.php | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index aa15883fe..7580f1ad9 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -46,6 +46,14 @@ public function boot(): void return Limit::perMinute(20)->by($request->session()->get('login.id')); }); + RateLimiter::for('forgot-email', function (Request $request) { + if (app()->environment('testing')) { + return Limit::none(); + } + + return Limit::perMinute(5)->by($request->ip()); + }); + Fortify::loginView(function () { return view('outgame.login'); }); diff --git a/routes/web.php b/routes/web.php index c549f1851..4bb4abda1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -59,7 +59,7 @@ // Forgot email lookup (guest only). Route::get('/forgot-email', [ForgotEmailController::class, 'show'])->name('password.email-lookup'); -Route::post('/forgot-email', [ForgotEmailController::class, 'send'])->middleware('throttle:5,1'); +Route::post('/forgot-email', [ForgotEmailController::class, 'send'])->middleware('throttle:forgot-email'); // Public AJAX endpoints (no auth required). Route::get('/ajax/main/rules', [RulesController::class, 'ajaxRules'])->name('rules.ajax'); From 67329a4baa04fe2718f2649581103360cc133786 Mon Sep 17 00:00:00 2001 From: piciolo Date: Wed, 22 Apr 2026 15:52:36 +0200 Subject: [PATCH 8/9] =?UTF-8?q?fix:=20address=20review=20nits=20=E2=80=94?= =?UTF-8?q?=20alphabetize=20import=20and=20use=20type=3Demail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - routes/web.php: move ForgotEmailController import to the F-section (between FleetEventsController and GalaxyController) - forgot-password.blade.php, reset-password.blade.php: change email input from type=text to type=email for correct mobile keyboard and native browser validation --- resources/views/outgame/forgot-password.blade.php | 2 +- resources/views/outgame/reset-password.blade.php | 2 +- routes/web.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/views/outgame/forgot-password.blade.php b/resources/views/outgame/forgot-password.blade.php index 4dc2cd783..d16a366e2 100644 --- a/resources/views/outgame/forgot-password.blade.php +++ b/resources/views/outgame/forgot-password.blade.php @@ -26,7 +26,7 @@
-
- Date: Sun, 19 Jul 2026 22:38:46 +0000 Subject: [PATCH 9/9] fix: sync Vite outgame bundle with forgot-password/email redirects Co-authored-by: Cursor --- public/build/assets/outgame-b0a7e11e.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/build/assets/outgame-b0a7e11e.js b/public/build/assets/outgame-b0a7e11e.js index 6d725a081..5fb173854 100644 --- a/public/build/assets/outgame-b0a7e11e.js +++ b/public/build/assets/outgame-b0a7e11e.js @@ -203,4 +203,4 @@ ogame.characteristics = { } };; var javascriptAvailable=true;; -$(document).ready(function(){var a={};jQuery.fn.exists=function(){return jQuery(this).length>0};$("#menu").tabs({event:"click",activate:function(j,k){a=$(j.currentTarget).attr("id");d()},beforeLoad:function(j,k){k.jqXHR.fail(function(){k.panel.html("")})}});a=$("ul#tabs a.current").attr("id");d();$("ul#tabs a").click(function(){$(".formError").hide();a=$(this).attr("id");d()});function d(){switch(a){case"tab1":$("#menu").css("background-position","15px -33px");break;case"tab2":$("#menu").css("background-position","15px -66px");break;case"tab3":$("#menu").css("background-position","15px -99px");break;case"tab4":$("#menu").css("background-position","15px -132px");break;default:break}}function c(){$("#loginBtn").addClass("open").text(JSLoca[1]);$("#login").fadeIn("fast",function(){$("#usernameLogin").focus()});if(emailOnlyLogin){$("#transition_email_only_login_dialog").dialog("open")}}function i(){$("#loginBtn").removeClass("open").text(JSLoca[0]);$("#login").fadeOut();if(emailOnlyLogin){$("#transition_email_only_login_dialog").dialog("close")}}$("#loginBtn").click(function(){$(".formError").hide();if($(this).hasClass("open")){i()}else{c()}});$("#pwLost").click(function(){$(this).attr("href","https://"+$("#serverLogin").val()+"/game/reg/mail.php")});$("#emailLost").click(function(){$(this).attr("href","https://"+$("#serverLogin").val()+"/game/index.php?page=standalone&component=retrieveEmail")});$("#resendAct").click(function(){$(".formError").hide();$("#tabs .current").removeClass("current");$("#menu").css("background-position","15px 0");$("#ajaxContent, #passwordLost, #login").hide();$("#resendLink").fadeIn();$("#loginBtn").removeClass("open").text("Login")});$("#trigger").toggle(function(){$("#selected, #language").fadeIn()},function(){$("#selected, #language").fadeOut()});$("#langHead a").click(function(){var j=$(this).attr("title");$("#selected, #displayLang").text(j)});$("#loginForm").validationEngine({validationEventTriggers:"blur",promptPosition:"centerRight",inlineValidation:true});$("#pwLostForm").validationEngine();$("#resendLinkForm").validationEngine();$("#subscribeForm").validationEngine({validationEventTriggers:"keyup blur",promptPosition:"centerRight",inlineValidation:true});$("#regSubmit").click(function(){var j=$("#username");if(j.val()===""){j.val(j.attr("placeholder"))}var k=$("#subscribeForm").validationEngine({returnIsValid:true});if(k==true){submitRegistrationForm();return false}});$("a.overlay").fancybox({beforeLoad:function(){$(".formError").hide()},type:"ajax",touch:{vertical:false}});$('a.overlay[href*="distinctions"]').fancybox({beforeLoad:function(){$(".formError").hide()},type:"ajax",baseTpl:'',touch:{vertical:false}});$("#trailer").click(function(){$(".formError").hide()});function b(l){var j="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.$!;:-_#";for(var k=0;k0};$("#menu").tabs({event:"click",activate:function(j,k){a=$(j.currentTarget).attr("id");d()},beforeLoad:function(j,k){k.jqXHR.fail(function(){k.panel.html("")})}});a=$("ul#tabs a.current").attr("id");d();$("ul#tabs a").click(function(){$(".formError").hide();a=$(this).attr("id");d()});function d(){switch(a){case"tab1":$("#menu").css("background-position","15px -33px");break;case"tab2":$("#menu").css("background-position","15px -66px");break;case"tab3":$("#menu").css("background-position","15px -99px");break;case"tab4":$("#menu").css("background-position","15px -132px");break;default:break}}function c(){$("#loginBtn").addClass("open").text(JSLoca[1]);$("#login").fadeIn("fast",function(){$("#usernameLogin").focus()});if(emailOnlyLogin){$("#transition_email_only_login_dialog").dialog("open")}}function i(){$("#loginBtn").removeClass("open").text(JSLoca[0]);$("#login").fadeOut();if(emailOnlyLogin){$("#transition_email_only_login_dialog").dialog("close")}}$("#loginBtn").click(function(){$(".formError").hide();if($(this).hasClass("open")){i()}else{c()}});$("#pwLost").click(function(){window.location.href="/forgot-password";return false;});$("#emailLost").click(function(){window.location.href="/forgot-email";return false;});$("#resendAct").click(function(){$(".formError").hide();$("#tabs .current").removeClass("current");$("#menu").css("background-position","15px 0");$("#ajaxContent, #passwordLost, #login").hide();$("#resendLink").fadeIn();$("#loginBtn").removeClass("open").text("Login")});$("#trigger").toggle(function(){$("#selected, #language").fadeIn()},function(){$("#selected, #language").fadeOut()});$("#langHead a").click(function(){var j=$(this).attr("title");$("#selected, #displayLang").text(j)});$("#loginForm").validationEngine({validationEventTriggers:"blur",promptPosition:"centerRight",inlineValidation:true});$("#pwLostForm").validationEngine();$("#resendLinkForm").validationEngine();$("#subscribeForm").validationEngine({validationEventTriggers:"keyup blur",promptPosition:"centerRight",inlineValidation:true});$("#regSubmit").click(function(){var j=$("#username");if(j.val()===""){j.val(j.attr("placeholder"))}var k=$("#subscribeForm").validationEngine({returnIsValid:true});if(k==true){submitRegistrationForm();return false}});$("a.overlay").fancybox({beforeLoad:function(){$(".formError").hide()},type:"ajax",touch:{vertical:false}});$('a.overlay[href*="distinctions"]').fancybox({beforeLoad:function(){$(".formError").hide()},type:"ajax",baseTpl:'',touch:{vertical:false}});$("#trailer").click(function(){$(".formError").hide()});function b(l){var j="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.$!;:-_#";for(var k=0;k
-

- © OGameX — {{ __('t_external.footer.copyright') }} +

+ {{ __('t_external.footer.copyright') }}