Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,20 @@ REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_DRIVER=smtp
# Set to 0 to disable throttle during local development (default: 60 seconds)
AUTH_PASSWORD_RESET_THROTTLE=60

MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
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
Expand Down
50 changes: 50 additions & 0 deletions app/Http/Controllers/ForgotEmailController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace OGame\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use Illuminate\View\View;
use OGame\Mail\RetrieveEmailMail;
use OGame\Models\User;

class ForgotEmailController extends Controller
{
public function show(): View
{
return view('outgame.forgot-email');
}

public function send(Request $request): RedirectResponse
{
$request->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;
}
}
33 changes: 33 additions & 0 deletions app/Mail/ResetPasswordMail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace OGame\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class ResetPasswordMail extends Mailable
{
use Queueable, SerializesModels;

public function __construct(
public readonly string $resetUrl,
public readonly string $username,
) {}

public function envelope(): Envelope
{
return new Envelope(
subject: __('t_external.mail.reset_password.subject'),
);
}

public function content(): Content
{
return new Content(
view: 'outgame.mail.reset-password',
);
}
}
34 changes: 34 additions & 0 deletions app/Mail/RetrieveEmailMail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace OGame\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class RetrieveEmailMail extends Mailable
{
use Queueable, SerializesModels;

public function __construct(
public readonly string $maskedEmail,
public readonly string $username,
public readonly string $loginUrl,
) {}

public function envelope(): Envelope
{
return new Envelope(
subject: __('t_external.mail.retrieve_email.subject'),
);
}

public function content(): Content
{
return new Content(
view: 'outgame.mail.retrieve-email',
);
}
}
15 changes: 15 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Facades\Mail;
use OGame\Mail\ResetPasswordMail;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Notifications\DatabaseNotificationCollection;
use Illuminate\Notifications\Notifiable;
Expand Down Expand Up @@ -315,4 +317,17 @@ public function canBeImpersonated(): bool
{
return true;
}

/**
* Send the password reset notification using OGameX branded email.
*/
public function sendPasswordResetNotification($token): void
{
$resetUrl = url(route('password.reset', [
'token' => $token,
'email' => $this->email,
], false));

Mail::to($this->email)->send(new ResetPasswordMail($resetUrl, $this->username));
}
}
20 changes: 12 additions & 8 deletions app/Providers/FortifyServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
Expand All @@ -71,16 +79,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]);
});
}
}
2 changes: 1 addition & 1 deletion config/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -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),
],
],

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('password_reset_tokens');
}
};
2 changes: 1 addition & 1 deletion public/build/assets/outgame-b0a7e11e.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading