Auth: convert auth views and verification email to Nova layout
This commit is contained in:
@@ -19,9 +19,13 @@ class AvatarUrl
|
||||
return self::default();
|
||||
}
|
||||
|
||||
$base = rtrim((string) config('cdn.avatar_url', 'https://file.skinbase.org'), '/');
|
||||
$base = rtrim((string) config('cdn.avatar_url', 'https://files.skinbase.org'), '/');
|
||||
|
||||
return sprintf('%s/avatars/%d/%d.webp?v=%s', $base, $userId, $size, $avatarHash);
|
||||
// Use hash-based path: avatars/ab/cd/{hash}/{size}.webp?v={hash}
|
||||
$p1 = substr($avatarHash, 0, 2);
|
||||
$p2 = substr($avatarHash, 2, 2);
|
||||
|
||||
return sprintf('%s/avatars/%s/%s/%s/%d.webp?v=%s', $base, $p1, $p2, $avatarHash, $size, $avatarHash);
|
||||
}
|
||||
|
||||
public static function default(): string
|
||||
|
||||
36
app/Support/ForumPostContent.php
Normal file
36
app/Support/ForumPostContent.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
class ForumPostContent
|
||||
{
|
||||
public static function render(?string $raw): string
|
||||
{
|
||||
$content = (string) ($raw ?? '');
|
||||
|
||||
if ($content === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$allowedTags = '<p><br><strong><em><b><i><u><ul><ol><li><blockquote><code><pre><a><img>';
|
||||
$sanitized = strip_tags($content, $allowedTags);
|
||||
|
||||
$sanitized = preg_replace('/\son\w+\s*=\s*"[^"]*"/i', '', $sanitized) ?? $sanitized;
|
||||
$sanitized = preg_replace('/\son\w+\s*=\s*\'[^\']*\'/i', '', $sanitized) ?? $sanitized;
|
||||
$sanitized = preg_replace('/\s(href|src)\s*=\s*"\s*javascript:[^"]*"/i', ' $1="#"', $sanitized) ?? $sanitized;
|
||||
$sanitized = preg_replace('/\s(href|src)\s*=\s*\'\s*javascript:[^\']*\'/i', ' $1="#"', $sanitized) ?? $sanitized;
|
||||
|
||||
$linked = preg_replace_callback(
|
||||
'/(?<!["\'>])(https?:\/\/[^\s<]+)/i',
|
||||
static function (array $matches): string {
|
||||
$url = $matches[1] ?? '';
|
||||
$escapedUrl = e($url);
|
||||
|
||||
return '<a href="' . $escapedUrl . '" target="_blank" rel="noopener noreferrer" class="text-sky-300 hover:text-sky-200 underline">' . $escapedUrl . '</a>';
|
||||
},
|
||||
$sanitized,
|
||||
);
|
||||
|
||||
return (string) ($linked ?? $sanitized);
|
||||
}
|
||||
}
|
||||
128
app/Support/UsernamePolicy.php
Normal file
128
app/Support/UsernamePolicy.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
final class UsernamePolicy
|
||||
{
|
||||
public static function min(): int
|
||||
{
|
||||
return (int) config('usernames.min', 3);
|
||||
}
|
||||
|
||||
public static function max(): int
|
||||
{
|
||||
return (int) config('usernames.max', 20);
|
||||
}
|
||||
|
||||
public static function regex(): string
|
||||
{
|
||||
return (string) config('usernames.regex', '/^[a-zA-Z0-9_-]+$/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function reserved(): array
|
||||
{
|
||||
return array_values(array_unique(array_map(static fn (string $v): string => strtolower(trim($v)), (array) config('usernames.reserved', []))));
|
||||
}
|
||||
|
||||
public static function normalize(string $value): string
|
||||
{
|
||||
return strtolower(trim($value));
|
||||
}
|
||||
|
||||
public static function sanitizeLegacy(string $value): string
|
||||
{
|
||||
$value = Str::ascii($value);
|
||||
$value = strtolower(trim($value));
|
||||
$value = preg_replace('/[^a-z0-9_-]+/', '_', $value) ?? '';
|
||||
$value = trim($value, '_-');
|
||||
|
||||
if ($value === '') {
|
||||
return 'user';
|
||||
}
|
||||
|
||||
return substr($value, 0, self::max());
|
||||
}
|
||||
|
||||
public static function isReserved(string $username): bool
|
||||
{
|
||||
return in_array(self::normalize($username), self::reserved(), true);
|
||||
}
|
||||
|
||||
public static function similarReserved(string $username): ?string
|
||||
{
|
||||
$normalized = self::normalize($username);
|
||||
$reduced = self::reduceForSimilarity($normalized);
|
||||
$threshold = (int) config('usernames.similarity_threshold', 2);
|
||||
|
||||
foreach (self::reserved() as $reserved) {
|
||||
if (levenshtein($reduced, self::reduceForSimilarity($reserved)) <= $threshold) {
|
||||
return $reserved;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function hasApprovedOverride(string $username, ?int $userId = null): bool
|
||||
{
|
||||
if (! Schema::hasTable('username_approval_requests')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$normalized = self::normalize($username);
|
||||
|
||||
return DB::table('username_approval_requests')
|
||||
->where('requested_username', $normalized)
|
||||
->where('status', 'approved')
|
||||
->when($userId !== null, fn ($q) => $q->where(function ($sub) use ($userId) {
|
||||
$sub->where('user_id', $userId)->orWhereNull('user_id');
|
||||
}))
|
||||
->exists();
|
||||
}
|
||||
|
||||
public static function uniqueCandidate(string $base, ?int $ignoreUserId = null): string
|
||||
{
|
||||
$base = self::sanitizeLegacy($base);
|
||||
if ($base === '' || self::isReserved($base) || self::similarReserved($base) !== null) {
|
||||
$base = 'user';
|
||||
}
|
||||
|
||||
$max = self::max();
|
||||
$candidate = substr($base, 0, $max);
|
||||
$suffix = 1;
|
||||
|
||||
while (self::exists($candidate, $ignoreUserId) || self::isReserved($candidate) || self::similarReserved($candidate) !== null) {
|
||||
$suffixStr = (string) $suffix;
|
||||
$prefixLen = max(1, $max - strlen($suffixStr));
|
||||
$candidate = substr($base, 0, $prefixLen) . $suffixStr;
|
||||
$suffix++;
|
||||
}
|
||||
|
||||
return $candidate;
|
||||
}
|
||||
|
||||
private static function exists(string $username, ?int $ignoreUserId = null): bool
|
||||
{
|
||||
$query = User::query()->whereRaw('LOWER(username) = ?', [strtolower($username)]);
|
||||
if ($ignoreUserId !== null) {
|
||||
$query->where('id', '!=', $ignoreUserId);
|
||||
}
|
||||
|
||||
return $query->exists();
|
||||
}
|
||||
|
||||
private static function reduceForSimilarity(string $value): string
|
||||
{
|
||||
return preg_replace('/[0-9_-]+/', '', strtolower($value)) ?? strtolower($value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user