mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-25 15:55:22 +00:00
Merge remote-tracking branch 'origin/staging' into jippi-fork
This commit is contained in:
commit
36850235a8
13 changed files with 512 additions and 56 deletions
|
@ -14,6 +14,7 @@
|
||||||
- Added Mutual Followers API endpoint ([33dbbe46](https://github.com/pixelfed/pixelfed/commit/33dbbe46))
|
- Added Mutual Followers API endpoint ([33dbbe46](https://github.com/pixelfed/pixelfed/commit/33dbbe46))
|
||||||
- Added User Domain Blocks ([#4834](https://github.com/pixelfed/pixelfed/pull/4834)) ([fa0380ac](https://github.com/pixelfed/pixelfed/commit/fa0380ac))
|
- Added User Domain Blocks ([#4834](https://github.com/pixelfed/pixelfed/pull/4834)) ([fa0380ac](https://github.com/pixelfed/pixelfed/commit/fa0380ac))
|
||||||
- Added Parental Controls ([#4862](https://github.com/pixelfed/pixelfed/pull/4862)) ([c91f1c59](https://github.com/pixelfed/pixelfed/commit/c91f1c59))
|
- Added Parental Controls ([#4862](https://github.com/pixelfed/pixelfed/pull/4862)) ([c91f1c59](https://github.com/pixelfed/pixelfed/commit/c91f1c59))
|
||||||
|
- Added Forgot Email Feature ([67c650b1](https://github.com/pixelfed/pixelfed/commit/67c650b1))
|
||||||
|
|
||||||
### Federation
|
### Federation
|
||||||
- Update Privacy Settings, add support for Mastodon `indexable` search flag ([fc24630e](https://github.com/pixelfed/pixelfed/commit/fc24630e))
|
- Update Privacy Settings, add support for Mastodon `indexable` search flag ([fc24630e](https://github.com/pixelfed/pixelfed/commit/fc24630e))
|
||||||
|
@ -86,6 +87,8 @@
|
||||||
- Update FollowerService, add $silent param to remove method to more efficently purge relationships ([1664a5bc](https://github.com/pixelfed/pixelfed/commit/1664a5bc))
|
- Update FollowerService, add $silent param to remove method to more efficently purge relationships ([1664a5bc](https://github.com/pixelfed/pixelfed/commit/1664a5bc))
|
||||||
- Update AP ProfileTransformer, add published attribute ([adfaa2b1](https://github.com/pixelfed/pixelfed/commit/adfaa2b1))
|
- Update AP ProfileTransformer, add published attribute ([adfaa2b1](https://github.com/pixelfed/pixelfed/commit/adfaa2b1))
|
||||||
- Update meta tags, improve descriptions and seo/og tags ([fd44c80c](https://github.com/pixelfed/pixelfed/commit/fd44c80c))
|
- Update meta tags, improve descriptions and seo/og tags ([fd44c80c](https://github.com/pixelfed/pixelfed/commit/fd44c80c))
|
||||||
|
- Update login view, add email prefill logic ([d76f0168](https://github.com/pixelfed/pixelfed/commit/d76f0168))
|
||||||
|
- Update LoginController, fix captcha validation error message ([0325e171](https://github.com/pixelfed/pixelfed/commit/0325e171))
|
||||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||||
|
|
||||||
## [v0.11.9 (2023-08-21)](https://github.com/pixelfed/pixelfed/compare/v0.11.8...v0.11.9)
|
## [v0.11.9 (2023-08-21)](https://github.com/pixelfed/pixelfed/compare/v0.11.8...v0.11.9)
|
||||||
|
|
|
@ -71,6 +71,7 @@ class LoginController extends Controller
|
||||||
$this->username() => 'required|email',
|
$this->username() => 'required|email',
|
||||||
'password' => 'required|string|min:6',
|
'password' => 'required|string|min:6',
|
||||||
];
|
];
|
||||||
|
$messages = [];
|
||||||
|
|
||||||
if(
|
if(
|
||||||
config('captcha.enabled') ||
|
config('captcha.enabled') ||
|
||||||
|
@ -82,9 +83,9 @@ class LoginController extends Controller
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
$rules['h-captcha-response'] = 'required|filled|captcha|min:5';
|
$rules['h-captcha-response'] = 'required|filled|captcha|min:5';
|
||||||
|
$messages['h-captcha-response.required'] = 'The captcha must be filled';
|
||||||
}
|
}
|
||||||
|
$request->validate($rules, $messages);
|
||||||
$this->validate($request, $rules);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
131
app/Http/Controllers/UserEmailForgotController.php
Normal file
131
app/Http/Controllers/UserEmailForgotController.php
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\User;
|
||||||
|
use App\Models\UserEmailForgot;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use App\Mail\UserEmailForgotReminder;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
|
||||||
|
class UserEmailForgotController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('guest');
|
||||||
|
abort_unless(config('security.forgot-email.enabled'), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
abort_if($request->user(), 404);
|
||||||
|
return view('auth.email.forgot');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'username' => 'required|min:2|max:15|exists:users'
|
||||||
|
];
|
||||||
|
|
||||||
|
$messages = [
|
||||||
|
'username.exists' => 'This username is no longer active or does not exist!'
|
||||||
|
];
|
||||||
|
|
||||||
|
if(config('captcha.enabled') || config('captcha.active.login') || config('captcha.active.register')) {
|
||||||
|
$rules['h-captcha-response'] = 'required|captcha';
|
||||||
|
$messages['h-captcha-response.required'] = 'You need to complete the captcha!';
|
||||||
|
}
|
||||||
|
|
||||||
|
$randomDelay = random_int(500000, 2000000);
|
||||||
|
usleep($randomDelay);
|
||||||
|
|
||||||
|
$this->validate($request, $rules, $messages);
|
||||||
|
$check = self::checkLimits();
|
||||||
|
|
||||||
|
if(!$check) {
|
||||||
|
return redirect()->back()->withErrors([
|
||||||
|
'username' => 'Please try again later, we\'ve reached our quota and cannot process any more requests at this time.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = User::whereUsername($request->input('username'))
|
||||||
|
->whereNotNull('email_verified_at')
|
||||||
|
->whereNull('status')
|
||||||
|
->whereIsAdmin(false)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if(!$user) {
|
||||||
|
return redirect()->back()->withErrors([
|
||||||
|
'username' => 'Invalid username or account. It may not exist, or does not have a verified email, is an admin account or is disabled.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = UserEmailForgot::whereUserId($user->id)
|
||||||
|
->where('email_sent_at', '>', now()->subHours(24))
|
||||||
|
->count();
|
||||||
|
|
||||||
|
if($exists) {
|
||||||
|
return redirect()->back()->withErrors([
|
||||||
|
'username' => 'An email reminder was recently sent to this account, please try again after 24 hours!'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->storeHandle($request, $user);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function storeHandle($request, $user)
|
||||||
|
{
|
||||||
|
UserEmailForgot::create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'ip_address' => $request->ip(),
|
||||||
|
'user_agent' => $request->userAgent(),
|
||||||
|
'email_sent_at' => now()
|
||||||
|
]);
|
||||||
|
|
||||||
|
Mail::to($user->email)->send(new UserEmailForgotReminder($user));
|
||||||
|
self::getLimits(true);
|
||||||
|
return redirect()->back()->with(['status' => 'Successfully sent an email reminder!']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function checkLimits()
|
||||||
|
{
|
||||||
|
$limits = self::getLimits();
|
||||||
|
|
||||||
|
if(
|
||||||
|
$limits['current']['hourly'] >= $limits['max']['hourly'] ||
|
||||||
|
$limits['current']['daily'] >= $limits['max']['daily'] ||
|
||||||
|
$limits['current']['weekly'] >= $limits['max']['weekly'] ||
|
||||||
|
$limits['current']['monthly'] >= $limits['max']['monthly']
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getLimits($forget = false)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'max' => config('security.forgot-email.limits.max'),
|
||||||
|
'current' => [
|
||||||
|
'hourly' => self::activeCount(60, $forget),
|
||||||
|
'daily' => self::activeCount(1440, $forget),
|
||||||
|
'weekly' => self::activeCount(10080, $forget),
|
||||||
|
'monthly' => self::activeCount(43800, $forget)
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function activeCount($mins, $forget = false)
|
||||||
|
{
|
||||||
|
if($forget) {
|
||||||
|
Cache::forget('pf:auth:forgot-email:active-count:dur-' . $mins);
|
||||||
|
}
|
||||||
|
return Cache::remember('pf:auth:forgot-email:active-count:dur-' . $mins, 14200, function() use($mins) {
|
||||||
|
return UserEmailForgot::where('email_sent_at', '>', now()->subMinutes($mins))->count();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
55
app/Mail/UserEmailForgotReminder.php
Normal file
55
app/Mail/UserEmailForgotReminder.php
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class UserEmailForgotReminder extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct($user)
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
return new Envelope(
|
||||||
|
subject: '[' . config('pixelfed.domain.app') . '] Pixelfed Account Email Reminder',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
markdown: 'emails.forgot-email.message',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
17
app/Models/UserEmailForgot.php
Normal file
17
app/Models/UserEmailForgot.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class UserEmailForgot extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'email_sent_at' => 'datetime',
|
||||||
|
];
|
||||||
|
}
|
|
@ -5,5 +5,18 @@ return [
|
||||||
'verify_dns' => env('PF_SECURITY_URL_VERIFY_DNS', false),
|
'verify_dns' => env('PF_SECURITY_URL_VERIFY_DNS', false),
|
||||||
|
|
||||||
'trusted_domains' => env('PF_SECURITY_URL_TRUSTED_DOMAINS', 'pixelfed.social,pixelfed.art,mastodon.social'),
|
'trusted_domains' => env('PF_SECURITY_URL_TRUSTED_DOMAINS', 'pixelfed.social,pixelfed.art,mastodon.social'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'forgot-email' => [
|
||||||
|
'enabled' => env('PF_AUTH_ALLOW_EMAIL_FORGOT', true),
|
||||||
|
|
||||||
|
'limits' => [
|
||||||
|
'max' => [
|
||||||
|
'hourly' => env('PF_AUTH_FORGOT_EMAIL_MAX_HOURLY', 50),
|
||||||
|
'daily' => env('PF_AUTH_FORGOT_EMAIL_MAX_DAILY', 100),
|
||||||
|
'weekly' => env('PF_AUTH_FORGOT_EMAIL_MAX_WEEKLY', 200),
|
||||||
|
'monthly' => env('PF_AUTH_FORGOT_EMAIL_MAX_MONTHLY', 500),
|
||||||
|
]
|
||||||
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?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('user_email_forgots', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedInteger('user_id')->index();
|
||||||
|
$table->string('ip_address')->nullable();
|
||||||
|
$table->string('user_agent')->nullable();
|
||||||
|
$table->string('referrer')->nullable();
|
||||||
|
$table->timestamp('email_sent_at')->nullable()->index();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('user_email_forgots');
|
||||||
|
}
|
||||||
|
};
|
127
resources/views/auth/email/forgot.blade.php
Normal file
127
resources/views/auth/email/forgot.blade.php
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
@extends('layouts.blank')
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<link href="{{ mix('css/landing.css') }}" rel="stylesheet">
|
||||||
|
<link rel="preload" as="image" href="{{ url('/_landing/bg.jpg')}}" />
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="container mt-4">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-xl-6 col-lg-5 col-md-7 col-12">
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="/">
|
||||||
|
<img src="/img/pixelfed-icon-white.svg" height="60px">
|
||||||
|
</a>
|
||||||
|
<h1 class="pt-4 pb-1">Forgot Email</h1>
|
||||||
|
<p class="font-weight-light pb-2">Recover your account by sending an email to an associated username</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if(session('status'))
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<div class="d-flex align-items-center font-weight-bold" style="gap:1rem;">
|
||||||
|
<i class="far fa-check-circle fa-lg" style="opacity:70%"></i>
|
||||||
|
|
||||||
|
{{ session('status') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($errors->any())
|
||||||
|
@foreach ($errors->all() as $error)
|
||||||
|
<div class="alert alert-danger bg-danger text-white border-danger">
|
||||||
|
<div class="d-flex align-items-center font-weight-bold" style="gap:1rem;">
|
||||||
|
<i class="far fa-exclamation-triangle fa-2x" style="opacity:70%"></i>
|
||||||
|
{{ $error }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="card bg-glass">
|
||||||
|
<div class="card-header bg-transparent p-3 text-center font-weight-bold" style="border-bottom:1px solid #ffffff20">{{ __('Recover Email') }}</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<form id="passwordReset" method="POST" action="{{ route('email.forgot') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label class="font-weight-bold small text-muted">Username</label>
|
||||||
|
<input
|
||||||
|
id="username"
|
||||||
|
type="text"
|
||||||
|
class="form-control form-control-lg bg-glass text-white"
|
||||||
|
name="username"
|
||||||
|
maxlength="15"
|
||||||
|
placeholder="{{ __('Your username') }}" required>
|
||||||
|
@if ($errors->has('username') )
|
||||||
|
<span class="text-danger small mb-3">
|
||||||
|
<strong>{{ $errors->first('username') }}</strong>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if(config('captcha.enabled') || config('captcha.active.login') || config('captcha.active.register'))
|
||||||
|
<label class="font-weight-bold small text-muted">Captcha</label>
|
||||||
|
<div class="d-flex flex-grow-1">
|
||||||
|
{!! Captcha::display(['data-theme' => 'dark']) !!}
|
||||||
|
</div>
|
||||||
|
@if ($errors->has('h-captcha-response'))
|
||||||
|
<div class="text-danger small mb-3">
|
||||||
|
<strong>{{ $errors->first('h-captcha-response') }}</strong>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="form-group row pt-4 mb-0">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<button type="button" id="sbtn" class="btn btn-primary btn-block rounded-pill font-weight-bold" onclick="event.preventDefault();handleSubmit()">
|
||||||
|
{{ __('Send Email Reminder') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 d-flex justify-content-between align-items-center">
|
||||||
|
<a class="btn btn-link text-white font-weight-bold text-decoration-none" href="{{ route('login') }}">
|
||||||
|
<i class="far fa-long-arrow-left fa-lg mr-1"></i> {{ __('Back to Login') }}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="{{ route('password.request') }}" class="text-white font-weight-bold text-decoration-none">Forgot password?</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript">
|
||||||
|
function handleSubmit() {
|
||||||
|
let username = document.getElementById('username');
|
||||||
|
username.classList.add('disabled');
|
||||||
|
|
||||||
|
let btn = document.getElementById('sbtn');
|
||||||
|
btn.classList.add('disabled');
|
||||||
|
btn.setAttribute('disabled', 'disabled');
|
||||||
|
btn.innerHTML = '<div class="spinner-border spinner-border-sm" role="status"><span class="sr-only">Loading...</span></div>';
|
||||||
|
document.getElementById('passwordReset').submit()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<style>
|
||||||
|
.bg-glass:focus {
|
||||||
|
background: rgba(255, 255, 255, 0.05) !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border-color: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
|
@ -4,16 +4,28 @@
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-lg-5">
|
<div class="col-lg-5">
|
||||||
<div class="">
|
<div class="card shadow-none border">
|
||||||
<div class="card-header bg-transparent p-3 text-center font-weight-bold h3">{{ __('Login') }}</div>
|
<div class="card-header bg-transparent p-3">
|
||||||
|
<h4 class="font-weight-bold mb-0 text-center">
|
||||||
|
Account Login
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ($errors->any())
|
||||||
|
@foreach ($errors->all() as $error)
|
||||||
|
<div class="alert alert-danger m-3">
|
||||||
|
<span class="font-weight-bold small"><i class="far fa-exclamation-triangle mr-2"></i> {{ $error }}</span>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST" action="{{ route('login') }}">
|
<form method="POST" action="{{ route('login') }}">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
<div class="form-group row">
|
<div class="form-group row mb-0">
|
||||||
|
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
|
<label for="email" class="small font-weight-bold text-muted mb-0">Email Address</label>
|
||||||
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" placeholder="{{__('Email')}}" required autofocus>
|
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" placeholder="{{__('Email')}}" required autofocus>
|
||||||
|
|
||||||
@if ($errors->has('email'))
|
@if ($errors->has('email'))
|
||||||
|
@ -21,12 +33,19 @@
|
||||||
<strong>{{ $errors->first('email') }}</strong>
|
<strong>{{ $errors->first('email') }}</strong>
|
||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<div class="help-text small text-right mb-0">
|
||||||
|
<a href="{{ route('email.forgot') }}" class="small text-muted font-weight-bold">
|
||||||
|
{{ __('Forgot Email') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group row">
|
<div class="form-group row mb-0">
|
||||||
|
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
|
<label for="password" class="small font-weight-bold text-muted mb-0">Password</label>
|
||||||
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" placeholder="{{__('Password')}}" required>
|
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" placeholder="{{__('Password')}}" required>
|
||||||
|
|
||||||
@if ($errors->has('password'))
|
@if ($errors->has('password'))
|
||||||
|
@ -34,6 +53,12 @@
|
||||||
<strong>{{ $errors->first('password') }}</strong>
|
<strong>{{ $errors->first('password') }}</strong>
|
||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<p class="help-text small text-right mb-0">
|
||||||
|
<a href="{{ route('password.request') }}" class="small text-muted font-weight-bold">
|
||||||
|
{{ __('Forgot Password') }}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -64,14 +89,9 @@
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="form-group row mb-4">
|
<button type="submit" class="btn btn-primary btn-block btn-lg font-weight-bold rounded-pill">
|
||||||
<div class="col-md-12">
|
{{ __('Login') }}
|
||||||
<button type="submit" class="btn btn-primary btn-block btn-lg font-weight-bold">
|
</button>
|
||||||
{{ __('Login') }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
@if(
|
@if(
|
||||||
|
@ -91,20 +111,38 @@
|
||||||
</form>
|
</form>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@if(config_cache('pixelfed.open_registration'))
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<p class="text-center font-weight-bold">
|
<p class="text-center font-weight-bold mb-0">
|
||||||
@if(config_cache('pixelfed.open_registration'))
|
|
||||||
<a href="/register">Register</a>
|
<a href="/register">Register</a>
|
||||||
<span class="px-1">·</span>
|
|
||||||
@endif
|
|
||||||
<a href="{{ route('password.request') }}">
|
|
||||||
{{ __('Forgot Password') }}
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
function getQueryParam(name) {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
return urlParams.get(name);
|
||||||
|
}
|
||||||
|
const email = getQueryParam('email');
|
||||||
|
if (email) {
|
||||||
|
const emailInput = document.getElementById('email');
|
||||||
|
if (emailInput) {
|
||||||
|
emailInput.value = email;
|
||||||
|
const passwordInput = document.getElementById('password');
|
||||||
|
if (passwordInput) {
|
||||||
|
passwordInput.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper">
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-lg-5">
|
<div class="col-xl-6 col-lg-5 col-md-7 col-12">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<img src="/img/pixelfed-icon-white.svg" height="60px">
|
<img src="/img/pixelfed-icon-white.svg" height="60px">
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(session('status') || $errors->has('email'))
|
@if(session('status') || $errors->has('email'))
|
||||||
<div class="alert alert-info small">
|
<div class="alert alert-info">
|
||||||
<div class="d-flex align-items-center font-weight-bold" style="gap:0.5rem;">
|
<div class="d-flex align-items-center font-weight-bold" style="gap:1rem;">
|
||||||
<i class="far fa-exclamation-triangle fa-lg" style="opacity:20%"></i>
|
<i class="far fa-exclamation-triangle fa-2x" style="opacity:70%"></i>
|
||||||
|
|
||||||
{{ session('status') ?? $errors->first('email') }}
|
{{ session('status') ?? $errors->first('email') }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,7 +39,13 @@
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<label class="font-weight-bold small text-muted">Email</label>
|
<label class="font-weight-bold small text-muted">Email</label>
|
||||||
<input id="email" type="email" class="form-control" name="email" placeholder="{{ __('E-Mail Address') }}" required>
|
<input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
class="form-control form-control-lg bg-glass text-white"
|
||||||
|
name="email"
|
||||||
|
placeholder="{{ __('E-Mail Address') }}"
|
||||||
|
required>
|
||||||
@if ($errors->has('email') && $errors->first('email') === 'The email must be a valid email address.')
|
@if ($errors->has('email') && $errors->first('email') === 'The email must be a valid email address.')
|
||||||
<span class="text-danger small mb-3">
|
<span class="text-danger small mb-3">
|
||||||
<strong>{{ $errors->first('email') }}</strong>
|
<strong>{{ $errors->first('email') }}</strong>
|
||||||
|
@ -76,7 +82,7 @@
|
||||||
<i class="far fa-long-arrow-left fa-lg mr-1"></i> {{ __('Back to Login') }}
|
<i class="far fa-long-arrow-left fa-lg mr-1"></i> {{ __('Back to Login') }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="#" class="text-white font-weight-bold text-decoration-none" onclick="event.preventDefault();forgotUsername()">Forgot email?</a>
|
<a href="{{route('email.forgot')}}" class="text-white font-weight-bold text-decoration-none">Forgot email?</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -86,29 +92,6 @@
|
||||||
|
|
||||||
@push('scripts')
|
@push('scripts')
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function forgotUsername() {
|
|
||||||
swal({
|
|
||||||
title: 'Forgot email?',
|
|
||||||
text: 'Contact the instance admins to assist you in recovering your account.',
|
|
||||||
icon: 'info',
|
|
||||||
buttons: {
|
|
||||||
contact: {
|
|
||||||
text: "Contact Admins",
|
|
||||||
value: "contact",
|
|
||||||
className: "bg-danger"
|
|
||||||
},
|
|
||||||
cancel: "Close",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((value) => {
|
|
||||||
switch(value) {
|
|
||||||
case 'contact':
|
|
||||||
window.location.href = '/site/contact';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
let email = document.getElementById('email');
|
let email = document.getElementById('email');
|
||||||
email.classList.add('disabled');
|
email.classList.add('disabled');
|
||||||
|
@ -121,3 +104,13 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
@endpush
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<style>
|
||||||
|
.bg-glass:focus {
|
||||||
|
background: rgba(255, 255, 255, 0.05) !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border-color: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper">
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-lg-5">
|
<div class="col-xl-6 col-lg-5 col-md-7 col-12">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<img src="/img/pixelfed-icon-white.svg" height="60px">
|
<img src="/img/pixelfed-icon-white.svg" height="60px">
|
||||||
|
@ -41,14 +41,14 @@
|
||||||
<label class="font-weight-bold small text-muted">Email</label>
|
<label class="font-weight-bold small text-muted">Email</label>
|
||||||
<input
|
<input
|
||||||
id="email"
|
id="email"
|
||||||
type="email"
|
type="text"
|
||||||
class="form-control {{ $errors->has('email') ? ' is-invalid' : '' }}"
|
class="form-control form-control-lg bg-dark bg-glass text-white{{ $errors->has('email') ? ' is-invalid' : '' }}"
|
||||||
name="email"
|
name="email"
|
||||||
value="{{ $email ?? old('email') }}"
|
value="{{ $email ?? old('email') }}"
|
||||||
placeholder="{{ __('E-Mail Address') }}"
|
placeholder="{{ __('E-Mail Address') }}"
|
||||||
required
|
required
|
||||||
disabled
|
disabled
|
||||||
style="opacity: 20%;">
|
style="opacity:.5">
|
||||||
|
|
||||||
@if ($errors->has('email'))
|
@if ($errors->has('email'))
|
||||||
<span class="invalid-feedback">
|
<span class="invalid-feedback">
|
||||||
|
@ -67,7 +67,7 @@
|
||||||
<input
|
<input
|
||||||
id="password"
|
id="password"
|
||||||
type="password"
|
type="password"
|
||||||
class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}"
|
class="form-control form-control-lg bg-glass text-white{{ $errors->has('password') ? ' is-invalid' : '' }}"
|
||||||
name="password"
|
name="password"
|
||||||
placeholder="{{ __('Password') }}"
|
placeholder="{{ __('Password') }}"
|
||||||
minlength="{{config('pixelfed.min_password_length')}}"
|
minlength="{{config('pixelfed.min_password_length')}}"
|
||||||
|
@ -93,7 +93,7 @@
|
||||||
<input
|
<input
|
||||||
id="password-confirm"
|
id="password-confirm"
|
||||||
type="password"
|
type="password"
|
||||||
class="form-control{{ $errors->has('password_confirmation') ? ' is-invalid' : '' }}"
|
class="form-control form-control-lg bg-glass text-white{{ $errors->has('password_confirmation') ? ' is-invalid' : '' }}"
|
||||||
name="password_confirmation"
|
name="password_confirmation"
|
||||||
placeholder="{{ __('Confirm Password') }}"
|
placeholder="{{ __('Confirm Password') }}"
|
||||||
minlength="{{config('pixelfed.min_password_length')}}"
|
minlength="{{config('pixelfed.min_password_length')}}"
|
||||||
|
@ -152,3 +152,13 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
@endpush
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<style>
|
||||||
|
.bg-glass:focus {
|
||||||
|
background: rgba(255, 255, 255, 0.05) !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border-color: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
33
resources/views/emails/forgot-email/message.blade.php
Normal file
33
resources/views/emails/forgot-email/message.blade.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
@component('mail::message')
|
||||||
|
Hello,
|
||||||
|
|
||||||
|
You recently requested to know the email address associated with your username [**{{'@' . $user->username}}**]({{$user->url()}}) on [**{{config('pixelfed.domain.app')}}**]({{config('app.url')}}).
|
||||||
|
|
||||||
|
We're here to assist! Simply tap on the Login button below.
|
||||||
|
|
||||||
|
<x-mail::button :url="url('/login?email=' . urlencode($user->email))" color="success">
|
||||||
|
Login to my <strong>{{'@' . $user->username}}</strong> account
|
||||||
|
</x-mail::button>
|
||||||
|
|
||||||
|
----
|
||||||
|
<br>
|
||||||
|
|
||||||
|
The email address linked to your username is:
|
||||||
|
<x-mail::panel>
|
||||||
|
<p>
|
||||||
|
<strong>{{$user->email}}</strong>
|
||||||
|
</p>
|
||||||
|
</x-mail::panel>
|
||||||
|
|
||||||
|
You can use this email address to log in to your account.
|
||||||
|
|
||||||
|
<small>If needed, you can [reset your password]({{ route('password.request')}}). For security reasons, we recommend keeping your account information, including your email address, updated and secure. If you did not make this request or if you have any other questions or concerns, please feel free to [contact our support team]({{route('site.contact')}}).</small>
|
||||||
|
|
||||||
|
Thank you for being a part of our community!
|
||||||
|
|
||||||
|
Best regards,<br>
|
||||||
|
<a href="{{ config('app.url') }}"><strong>{{ config('pixelfed.domain.app') }}</strong></a>
|
||||||
|
<br>
|
||||||
|
<hr>
|
||||||
|
<p style="font-size:10pt;">This is an automated message, please be aware that replies to this email cannot be monitored or responded to.</p>
|
||||||
|
@endcomponent
|
|
@ -203,6 +203,9 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
Route::get('auth/pci/{id}/{code}', 'ParentalControlsController@inviteRegister');
|
Route::get('auth/pci/{id}/{code}', 'ParentalControlsController@inviteRegister');
|
||||||
Route::post('auth/pci/{id}/{code}', 'ParentalControlsController@inviteRegisterStore');
|
Route::post('auth/pci/{id}/{code}', 'ParentalControlsController@inviteRegisterStore');
|
||||||
|
|
||||||
|
Route::get('auth/forgot/email', 'UserEmailForgotController@index')->name('email.forgot');
|
||||||
|
Route::post('auth/forgot/email', 'UserEmailForgotController@store')->middleware('throttle:10,900,forgotEmail');
|
||||||
|
|
||||||
Route::get('discover', 'DiscoverController@home')->name('discover');
|
Route::get('discover', 'DiscoverController@home')->name('discover');
|
||||||
|
|
||||||
Route::group(['prefix' => 'api'], function () {
|
Route::group(['prefix' => 'api'], function () {
|
||||||
|
|
Loading…
Reference in a new issue