改进 跨越

允许所有 CORS
根据 URL 来设置权限
This commit is contained in:
iVampireSP.com 2023-02-17 20:58:20 +08:00
parent cc41963734
commit 5a1d9df976
No known key found for this signature in database
GPG Key ID: 2F7B001CA27A8132
9 changed files with 142 additions and 32 deletions

View File

@ -98,8 +98,6 @@ USER_GROUP_BIRTHDAY=1
DASHBOARD_BASE_URL=https://dash.laecloud.com DASHBOARD_BASE_URL=https://dash.laecloud.com
DASHBOARD_BIRTHDAY_PATH=/stars DASHBOARD_BIRTHDAY_PATH=/stars
CORS_ORIGINS=${DASHBOARD_BASE_URL},https://web.laecloud.com,http://localhost:5173
# 可信代理,用于获取真实 IP。多个 IP 用逗号分隔。 # 可信代理,用于获取真实 IP。多个 IP 用逗号分隔。
TRUSTED_PROXIES= TRUSTED_PROXIES=

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Public; namespace App\Http\Controllers\Public;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Rules\Domain;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
@ -15,6 +16,7 @@ public function store(Request $request): JsonResponse
$request->validate([ $request->validate([
'description' => 'required|string|max:255', 'description' => 'required|string|max:255',
'require_token' => 'nullable|boolean', 'require_token' => 'nullable|boolean',
'abilities' => 'nullable|array|max:255',
]); ]);
$token = Str::random(128); $token = Str::random(128);
@ -24,6 +26,7 @@ public function store(Request $request): JsonResponse
'description' => $request->input('description'), 'description' => $request->input('description'),
'token' => $token, 'token' => $token,
'require_token' => $request->input('require_token', false), 'require_token' => $request->input('require_token', false),
'abilities' => $request->input('abilities'),
], ],
]; ];

View File

@ -6,14 +6,15 @@
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Notifications\User\UserNotification; use App\Notifications\User\UserNotification;
use function back; use App\Rules\Domain;
use function config;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\View\View; use Illuminate\View\View;
use function back;
use function config;
use function redirect; use function redirect;
use function session; use function session;
use function view; use function view;
@ -33,13 +34,17 @@ public function index(Request $request): View|RedirectResponse
$dashboardHost = parse_url(config('settings.dashboard.base_url'), PHP_URL_HOST); $dashboardHost = parse_url(config('settings.dashboard.base_url'), PHP_URL_HOST);
if ($callbackHost === $dashboardHost) { if ($callbackHost === $dashboardHost) {
if (! $request->user('web')->isRealNamed()) { if (!$request->user('web')->isRealNamed()) {
return redirect()->route('real_name.create')->with('status', '重定向已被打断,需要先实人认证。'); return redirect()->route('real_name.create')->with('status', '重定向已被打断,需要先实人认证。');
} }
$token = $request->user()->createToken('Dashboard')->plainTextToken; $requestHost = parse_url($request->header('referer'), PHP_URL_HOST);
return redirect($callback.'?token='.$token); $token = $request->user()->createToken('Dashboard', [
'domain-access:' . $requestHost,
])->plainTextToken;
return redirect($callback . '?token=' . $token);
} }
return redirect()->route('confirm_redirect'); return redirect()->route('confirm_redirect');
@ -80,9 +85,24 @@ public function newToken(Request $request): RedirectResponse
{ {
$request->validate([ $request->validate([
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
'domain' => ['nullable', 'string', 'max:255', new Domain],
]); ]);
$token = $request->user()->createToken($request->input('name')); $abilities = [];
if ($request->has('domain')) {
// 检测是不是一个合格的域名
if (!preg_match('/^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/', $request->input('domain'))) {
return back()->with('error', '域名格式不正确。');
}
$abilities = ['domain-access:' . $request->input('domain')];
}
$token = $request->user()->createToken(
$request->input('name'),
$abilities
);
return back()->with('token', $token->plainTextToken); return back()->with('token', $token->plainTextToken);
} }
@ -115,7 +135,7 @@ public function exitSudo(): RedirectResponse
public function showAuthRequest($token): View|RedirectResponse public function showAuthRequest($token): View|RedirectResponse
{ {
$data = Cache::get('auth_request:'.$token); $data = Cache::get('auth_request:' . $token);
if (empty($data)) { if (empty($data)) {
return redirect()->route('index')->with('error', '登录请求的 Token 不存在或已过期。'); return redirect()->route('index')->with('error', '登录请求的 Token 不存在或已过期。');
@ -139,7 +159,7 @@ public function storeAuthRequest(Request $request): RedirectResponse
'token' => 'required|string|max:128', 'token' => 'required|string|max:128',
]); ]);
$data = Cache::get('auth_request:'.$request->input('token')); $data = Cache::get('auth_request:' . $request->input('token'));
if (empty($data)) { if (empty($data)) {
return back()->with('error', '登录请求的 Token 不存在或已过期。'); return back()->with('error', '登录请求的 Token 不存在或已过期。');
@ -157,11 +177,13 @@ public function storeAuthRequest(Request $request): RedirectResponse
'real_name_verified_at', 'real_name_verified_at',
]); ]);
$abilities = $data['meta']['abilities'] ?? ['*'];
if (isset($data['meta']['require_token']) && $data['meta']['require_token']) { if (isset($data['meta']['require_token']) && $data['meta']['require_token']) {
$data['token'] = $user->createToken($data['meta']['description'] ?? Carbon::now()->toDateString())->plainTextToken; $data['token'] = $user->createToken($data['meta']['description'] ?? Carbon::now()->toDateString(), $abilities)->plainTextToken;
} }
Cache::put('auth_request:'.$request->input('token'), $data, 60); Cache::put('auth_request:' . $request->input('token'), $data, 60);
return redirect()->route('index')->with('success', '登录请求已确认。'); return redirect()->route('index')->with('success', '登录请求已确认。');
} }

View File

@ -13,11 +13,11 @@
use App\Http\Middleware\RedirectIfAuthenticated; use App\Http\Middleware\RedirectIfAuthenticated;
use App\Http\Middleware\ReportRequestToCluster; use App\Http\Middleware\ReportRequestToCluster;
use App\Http\Middleware\TrimStrings; use App\Http\Middleware\TrimStrings;
use App\Http\Middleware\TrustedDomain;
use App\Http\Middleware\TrustProxies; use App\Http\Middleware\TrustProxies;
use App\Http\Middleware\ValidateSignature; use App\Http\Middleware\ValidateSignature;
use App\Http\Middleware\ValidateUserIfBanned; use App\Http\Middleware\ValidateUserIfBanned;
use App\Http\Middleware\VerifyCsrfToken; use App\Http\Middleware\VerifyCsrfToken;
use Fruitcake\Cors\HandleCors;
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
use Illuminate\Auth\Middleware\Authorize; use Illuminate\Auth\Middleware\Authorize;
use Illuminate\Auth\Middleware\EnsureEmailIsVerified; use Illuminate\Auth\Middleware\EnsureEmailIsVerified;
@ -26,6 +26,7 @@
use Illuminate\Foundation\Http\Kernel as HttpKernel; use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
use Illuminate\Foundation\Http\Middleware\ValidatePostSize; use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
use Illuminate\Http\Middleware\HandleCors;
use Illuminate\Http\Middleware\SetCacheHeaders; use Illuminate\Http\Middleware\SetCacheHeaders;
use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Routing\Middleware\ThrottleRequests;
@ -45,12 +46,11 @@ class Kernel extends HttpKernel
protected $middleware = [ protected $middleware = [
// \App\Http\Middleware\TrustHosts::class, // \App\Http\Middleware\TrustHosts::class,
TrustProxies::class, TrustProxies::class,
// \Illuminate\Http\Middleware\HandleCors::class, HandleCors::class,
PreventRequestsDuringMaintenance::class, PreventRequestsDuringMaintenance::class,
ValidatePostSize::class, ValidatePostSize::class,
TrimStrings::class, TrimStrings::class,
ConvertEmptyStringsToNull::class, ConvertEmptyStringsToNull::class,
HandleCors::class,
AddHeaders::class, AddHeaders::class,
]; ];
@ -72,6 +72,7 @@ class Kernel extends HttpKernel
'api' => [ 'api' => [
JsonRequest::class, JsonRequest::class,
TrustedDomain::class,
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api', 'throttle:api',
SubstituteBindings::class, SubstituteBindings::class,

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class TrustedDomain
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure(Request): (Response|RedirectResponse) $next
*
* @return mixed
*/
public function handle(Request $request, Closure $next): mixed
{
$user = $request->user('sanctum');
if (!$user) {
return $next($request);
}
// 获取请求的域名
$requestHost = parse_url($request->header('referer'), PHP_URL_HOST);
if ($requestHost) {
// 获取当前域名
$currentHost = parse_url(config('app.url'), PHP_URL_HOST);
// 如果请求的域名和当前域名相同,则直接放行
if ($requestHost === $currentHost) {
return $next($request);
}
return $user->tokenCan('domain-access:' . $requestHost) ? $next($request) : response()->json([
'message' => 'Token 无权访问此域名。',
], 401);
}
return $next($request);
}
}

31
app/Rules/Domain.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class Domain implements Rule
{
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value): bool
{
// 验证域名是否合法
return preg_match('/^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/', $value);
}
/**
* Get the validation error message.
*
* @return string
*/
public function message(): string
{
return '域名格式不正确。';
}
}

View File

@ -1,11 +1,5 @@
<?php <?php
$cors_origin = explode(',', env('CORS_ORIGINS'));
if (env('APP_ENV') === 'local') {
$cors_origin = ['*'];
}
return [ return [
/* /*
@ -25,7 +19,7 @@
'allowed_methods' => ['*'], 'allowed_methods' => ['*'],
'allowed_origins' => $cors_origin, 'allowed_origins' => ['*'],
'allowed_origins_patterns' => [], 'allowed_origins_patterns' => [],

View File

@ -7,7 +7,6 @@
<h3> <h3>
<code> <code>
@if (isset($data['module']) && !is_null($data['module'])) @if (isset($data['module']) && !is_null($data['module']))
)
<span>模块:{{ $data['module']['name'] }}</span> <span>模块:{{ $data['module']['name'] }}</span>
@elseif (isset($data['applications']) && !is_null($data['application'])) @elseif (isset($data['applications']) && !is_null($data['application']))
<span>应用程序:{{ $data['application']['name'] }}</span> <span>应用程序:{{ $data['application']['name'] }}</span>
@ -23,18 +22,26 @@
<p>{{ $data['meta']['description'] }}</p> <p>{{ $data['meta']['description'] }}</p>
<br/> <br/>
<p>
在您同意后,您的 <b>ID</b>, <b>UUID</b>, <b>昵称</b>, <b>邮件信息 实人认证成功的时间(不包含个人信息)</b>, <b>余额</b>,
<b>用户组 ID</b> 将会被发送给它们。
@if ($data['meta']['require_token'])
<br />
你的 <b>Token</b> 将会新建一个,并发送给它们。
@endif
</p>
在您同意后,您的 <b>ID</b>, <b>UUID</b>, <b>昵称</b>, <b>邮件信息 实人认证成功的时间(不包含个人信息)</b>,
<b>余额</b>,
<b>用户组 ID</b> 将会被发送给它们。
@if ($data['meta']['require_token'])
<br/>
你的 <b>Token</b> 将会新建一个,并发送给它们。
@endif
@if (isset($data['meta']['abilities']))
<div>
权限列表:
@foreach($data['meta']['abilities'] as $ability)
<b>{{ $ability }}</b>
@endforeach
</div>
@endif
@auth('web') @auth('web')
<form method="POST" action="{{ route('auth_request.store') }}"> <form method="POST" action="{{ route('auth_request.store') }}" class="mt-3">
@csrf @csrf
<input type="hidden" name="token" value="{{ $data['meta']['token'] }}"> <input type="hidden" name="token" value="{{ $data['meta']['token'] }}">
<button type="submit" class="btn btn-primary">同意</button> <button type="submit" class="btn btn-primary">同意</button>

View File

@ -45,12 +45,19 @@
<form action="{{ route('token.new') }}" name="newToken" method="POST"> <form action="{{ route('token.new') }}" name="newToken" method="POST">
@csrf @csrf
<div class="form-floating mb-2"> <div class="form-floating mb-2">
<input type="text" class="form-control" placeholder="Token 名称" <input type="text" class="form-control" placeholder="Token 名称"
aria-label="密钥名称" name="name" required maxlength="25"> aria-label="密钥名称" name="name" required maxlength="25">
<label>Token 名称</label> <label>Token 名称</label>
</div> </div>
<div class="form-floating mb-2">
<input type="text" class="form-control" placeholder="授权的域名"
aria-label="授权的域名" name="domain" maxlength="255">
<label>授权的域名</label>
</div>
<button type="submit" class="btn btn-primary visually-hidden"> <button type="submit" class="btn btn-primary visually-hidden">
创建 创建
</button> </button>