增加 OAuth 登录
This commit is contained in:
parent
f12758b3a2
commit
162b7a6b4e
@ -107,3 +107,9 @@ ROADRUNNER_VERSION=
|
||||
# 实人认证的配置
|
||||
SUPPORT_REAL_NAME_APP_CODE=
|
||||
|
||||
# OAuth
|
||||
OAUTH_CLIENT_ID=
|
||||
OAUTH_CLIENT_SECRET=
|
||||
OAUTH_REDIRECT=${APP_URL}/auth/callback
|
||||
OAUTH_DOMAIN=http://oauth.test
|
||||
|
||||
|
@ -5,15 +5,19 @@
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Notifications\User\UserNotification;
|
||||
use function back;
|
||||
use function config;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
use function back;
|
||||
use function config;
|
||||
use function redirect;
|
||||
use function session;
|
||||
use function view;
|
||||
@ -33,13 +37,13 @@ public function index(Request $request): View|RedirectResponse
|
||||
$dashboardHost = parse_url(config('settings.dashboard.base_url'), PHP_URL_HOST);
|
||||
|
||||
if ($callbackHost === $dashboardHost) {
|
||||
if (! $request->user('web')->isRealNamed()) {
|
||||
if (!$request->user('web')->isRealNamed()) {
|
||||
return redirect()->route('real_name.create')->with('status', '重定向已被打断,需要先实人认证。');
|
||||
}
|
||||
|
||||
$token = $request->user()->createToken('Dashboard')->plainTextToken;
|
||||
|
||||
return redirect($callback.'?token='.$token);
|
||||
return redirect($callback . '?token=' . $token);
|
||||
}
|
||||
|
||||
session(['referer.domain' => parse_url($request->header('referer'), PHP_URL_HOST)]);
|
||||
@ -124,7 +128,7 @@ public function exitSudo(): RedirectResponse
|
||||
|
||||
public function showAuthRequest($token): View|RedirectResponse
|
||||
{
|
||||
$data = Cache::get('auth_request:'.$token);
|
||||
$data = Cache::get('auth_request:' . $token);
|
||||
|
||||
if (empty($data)) {
|
||||
return redirect()->route('index')->with('error', '登录请求的 Token 不存在或已过期。');
|
||||
@ -148,7 +152,7 @@ public function storeAuthRequest(Request $request): RedirectResponse|View
|
||||
'token' => 'required|string|max:128',
|
||||
]);
|
||||
|
||||
$data = Cache::get('auth_request:'.$request->input('token'));
|
||||
$data = Cache::get('auth_request:' . $request->input('token'));
|
||||
|
||||
if (empty($data)) {
|
||||
return back()->with('error', '登录请求的 Token 不存在或已过期。');
|
||||
@ -172,10 +176,10 @@ public function storeAuthRequest(Request $request): RedirectResponse|View
|
||||
$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);
|
||||
|
||||
if (isset($data['meta']['return_url']) && $data['meta']['return_url']) {
|
||||
return redirect()->to($data['meta']['return_url'].'?auth_request='.$request->input('token'));
|
||||
return redirect()->to($data['meta']['return_url'] . '?auth_request=' . $request->input('token'));
|
||||
}
|
||||
|
||||
return redirect()->route('index')->with('success', '登录请求已确认。');
|
||||
@ -183,8 +187,8 @@ public function storeAuthRequest(Request $request): RedirectResponse|View
|
||||
|
||||
public function fastLogin(string $token): RedirectResponse
|
||||
{
|
||||
$cache_key = 'session_login:'.$token;
|
||||
$user_id = Cache::get('session_login:'.$token);
|
||||
$cache_key = 'session_login:' . $token;
|
||||
$user_id = Cache::get('session_login:' . $token);
|
||||
|
||||
if (empty($user_id)) {
|
||||
return redirect()->route('index')->with('error', '登录请求的 Token 不存在或已过期。');
|
||||
@ -202,4 +206,81 @@ public function fastLogin(string $token): RedirectResponse
|
||||
|
||||
return redirect()->route('index');
|
||||
}
|
||||
|
||||
public function redirect(Request $request)
|
||||
{
|
||||
$request->session()->put('state', $state = Str::random(40));
|
||||
|
||||
$query = http_build_query([
|
||||
'client_id' => config('oauth.client_id'),
|
||||
'redirect_uri' => config('oauth.callback_uri'),
|
||||
'response_type' => 'code',
|
||||
'scope' => config('oauth.scope'),
|
||||
'state' => $state,
|
||||
]);
|
||||
|
||||
return redirect()->to(config('oauth.oauth_auth_url') . '?' . $query);
|
||||
}
|
||||
|
||||
public function callback(Request $request)
|
||||
{
|
||||
try {
|
||||
$authorize = Http::asForm()->throw()->post(config('oauth.oauth_token_url'), [
|
||||
'grant_type' => 'authorization_code',
|
||||
'client_id' => config('oauth.client_id'),
|
||||
'client_secret' => config('oauth.client_secret'),
|
||||
'redirect_uri' => config('oauth.callback_uri'),
|
||||
'code' => $request->input('code'),
|
||||
])->getBody();
|
||||
} catch (Exception $e) {
|
||||
return redirect()->route('index')->with('error', '无法获取授权。' . $e->getMessage());
|
||||
}
|
||||
|
||||
$authorize = json_decode($authorize);
|
||||
|
||||
$http = Http::asForm()->withHeaders([
|
||||
'Accept' => 'application/json',
|
||||
'Authorization' => 'Bearer ' . $authorize->access_token
|
||||
])->throw();
|
||||
|
||||
$oauth_user = $http->get(config('oauth.oauth_user_url'))->object();
|
||||
$real_name = $http->get(config('oauth.oauth_real_name_url'))->object();
|
||||
|
||||
$user_sql = User::where('email', $oauth_user->email);
|
||||
$user = $user_sql->first();
|
||||
|
||||
if (is_null($user)) {
|
||||
$name = $oauth_user->name;
|
||||
$email = $oauth_user->email;
|
||||
$email_verified_at = $oauth_user->email_verified_at;
|
||||
|
||||
$user = new User();
|
||||
|
||||
$user->name = $name;
|
||||
$user->email = $email;
|
||||
$user->email_verified_at = $email_verified_at;
|
||||
$user->real_name_verified_at = $real_name->real_name_verified_at;
|
||||
$user->real_name = $real_name->real_name;
|
||||
$user->id_card = $real_name->id_card;
|
||||
|
||||
|
||||
$user->password = Hash::make(Str::random(16));
|
||||
|
||||
|
||||
$user->save();
|
||||
|
||||
$request->session()->put('auth.password_confirmed_at', time());
|
||||
} else {
|
||||
if ($user->name != $oauth_user->name) {
|
||||
User::where('email', $oauth_user->email)->update([
|
||||
'name' => $oauth_user->name
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Auth::guard('web')->loginUsingId($user->id, true);
|
||||
|
||||
return redirect()->route('index');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -158,7 +158,7 @@ public function selectPublic(): self|Builder|CachedBuilder
|
||||
// 仅需选择公开的
|
||||
return $this->select($this->publics);
|
||||
}
|
||||
|
||||
|
||||
public function getOnlyPublic($appened_excepts = [], $display = []): array
|
||||
{
|
||||
if ($display) {
|
||||
|
12
config/oauth.php
Normal file
12
config/oauth.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'callback_uri' => env('OAUTH_REDIRECT'),
|
||||
'client_id' => env('OAUTH_CLIENT_ID'),
|
||||
'client_secret' => env('OAUTH_CLIENT_SECRET'),
|
||||
'oauth_auth_url' => env('OAUTH_DOMAIN') . '/oauth/authorize',
|
||||
'oauth_token_url' => env('OAUTH_DOMAIN') . '/oauth/token',
|
||||
'oauth_user_url' => env('OAUTH_DOMAIN') . '/api/user',
|
||||
'oauth_real_name_url' => env('OAUTH_DOMAIN') . '/api/real-name',
|
||||
'scope' => 'realname login user',
|
||||
];
|
@ -8,169 +8,15 @@
|
||||
|
||||
</span>
|
||||
|
||||
<h2 id="form-title">注册 或 登录</h2>
|
||||
<h2 id="form-title">使用 LoliArt Account 登录。</h2>
|
||||
|
||||
<form id="main-form" method="POST" onsubmit="return canSubmit()">
|
||||
@csrf
|
||||
<a class="btn btn-primary" href="{{route('login')}}">使用 LoliArt Account 登录</a>
|
||||
|
||||
<div class="form-group">
|
||||
<input type="email" name="email" id="email" class="form-control mb-3 text-center" placeholder="邮箱"
|
||||
aria-label="邮箱" required autofocus>
|
||||
</div>
|
||||
|
||||
<div id="suffix-form"></div>
|
||||
</form>
|
||||
|
||||
<br/>
|
||||
|
||||
<a class="link" href="{{ route('password.request') }}">
|
||||
{{ __('Forgot Your Password?') }}
|
||||
</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="d-none">
|
||||
|
||||
<div id="password-input">
|
||||
<div class="form-group mt-2">
|
||||
<input type="password" id="password" name="password"
|
||||
class="form-control rounded-right text-center @error('password') is-invalid @enderror" required
|
||||
placeholder="密码" aria-label="密码">
|
||||
@error('password')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group mt-2" id="password-confirm-input">
|
||||
<label for="password-confirm">确认密码</label>
|
||||
<input type="password" id="password-confirm" name="password_confirmation"
|
||||
class="form-control rounded-right" required autocomplete="new-password"
|
||||
placeholder="再次输入您的密码">
|
||||
</div>
|
||||
|
||||
<div id="remember-form">
|
||||
<input class="form-check-input" type="hidden" id="remember" name="remember" value="1">
|
||||
</div>
|
||||
|
||||
<small id="tip" class="d-block"></small>
|
||||
|
||||
<div class="mt-1" id="tos">如果您继续,则代表您已经阅读并同意 <a
|
||||
href="https://www.laecloud.com/tos/"
|
||||
target="_blank"
|
||||
class="text-decoration-underline">服务条款</a>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary btn-block mt-2" type="submit" id="login-btn">
|
||||
继续
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
const login = "{{ route('login') }}"
|
||||
const register = "{{ route('register') }}"
|
||||
|
||||
const mainIcon = document.getElementById('main-icon')
|
||||
const email = document.getElementById('email');
|
||||
const title = document.getElementById('form-title');
|
||||
const formSuffix = document.getElementById('suffix-form')
|
||||
const rememberForm = document.getElementById('remember-form')
|
||||
const passwordInput = document.getElementById('password-input')
|
||||
const passwordConfirmInput = document.getElementById('password-confirm-input')
|
||||
const loginBtn = document.getElementById('login-btn')
|
||||
const nameInput = document.getElementById('name')
|
||||
const mainForm = document.getElementById('main-form')
|
||||
const tos = document.getElementById('tos')
|
||||
const tip = document.getElementById('tip')
|
||||
|
||||
@error('password')
|
||||
title.innerText = "注册莱云"
|
||||
formSuffix.appendChild(rememberForm)
|
||||
|
||||
|
||||
@enderror
|
||||
|
||||
@error('email')
|
||||
title.innerText = "密码错误"
|
||||
email.value = "{{ old('email') }}"
|
||||
formSuffix.appendChild(passwordInput)
|
||||
formSuffix.appendChild(rememberForm)
|
||||
formSuffix.appendChild(tos)
|
||||
formSuffix.appendChild(loginBtn)
|
||||
loginBtn.innerText = '登录'
|
||||
@enderror
|
||||
|
||||
let canSubmit = function () {
|
||||
return (email.value !== '' && passwordInput.value !== '')
|
||||
}
|
||||
|
||||
const validateUrl = "{{ route('login.exists-if-user') }}"
|
||||
|
||||
email.onchange = function (ele) {
|
||||
const target = ele.target
|
||||
|
||||
if (email.value === '') {
|
||||
title.innerText = "输入邮箱"
|
||||
|
||||
formSuffix.innerHTML = ''
|
||||
|
||||
|
||||
mainIcon.classList.remove(...mainIcon.classList)
|
||||
mainIcon.classList.add('bi', 'bi-person-circle')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
formSuffix.innerHTML = ''
|
||||
formSuffix.appendChild(passwordInput)
|
||||
|
||||
axios.post(validateUrl, {
|
||||
email: target.value
|
||||
})
|
||||
.then(function (res) {
|
||||
mainForm.action = login
|
||||
|
||||
mainIcon.classList.remove(...mainIcon.classList)
|
||||
mainIcon.classList.add('bi', 'bi-person-check')
|
||||
|
||||
title.innerText = "欢迎, " + res.data.name
|
||||
|
||||
formSuffix.appendChild(passwordInput)
|
||||
formSuffix.appendChild(rememberForm)
|
||||
formSuffix.appendChild(tos)
|
||||
formSuffix.appendChild(loginBtn)
|
||||
loginBtn.innerText = '登录'
|
||||
|
||||
|
||||
})
|
||||
.catch(function () {
|
||||
mainForm.action = register
|
||||
|
||||
title.innerText = "注册莱云"
|
||||
|
||||
mainIcon.classList.remove(...mainIcon.classList)
|
||||
mainIcon.classList.add('bi', 'bi-person-plus')
|
||||
|
||||
formSuffix.appendChild(passwordInput)
|
||||
formSuffix.appendChild(tos)
|
||||
formSuffix.appendChild(tip)
|
||||
|
||||
formSuffix.appendChild(loginBtn)
|
||||
|
||||
tip.innerText = '当您注册后,我们将为您分配随机用户名。'
|
||||
|
||||
loginBtn.innerText = '注册'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
|
@ -19,8 +19,8 @@
|
||||
Route::get('/', [AuthController::class, 'index'])->middleware('banned')->name('index');
|
||||
|
||||
Route::prefix('auth')->group(function () {
|
||||
Route::get('login', [LoginController::class, 'showLoginForm'])->name('login');
|
||||
Route::post('login', [LoginController::class, 'login']);
|
||||
// Route::get('login', [LoginController::class, 'showLoginForm'])->name('login');
|
||||
// Route::post('login', [LoginController::class, 'login']);
|
||||
Route::post('logout', [LoginController::class, 'logout'])->name('logout');
|
||||
Route::post('exists', [LoginController::class, 'userIfExists'])->name('login.exists-if-user');
|
||||
|
||||
@ -40,6 +40,9 @@
|
||||
Route::post('email/resend', [VerificationController::class, 'resend'])->name('verification.resend');
|
||||
|
||||
Route::get('token/{token}', [AuthController::class, 'fastLogin'])->name('auth.fast-login');
|
||||
|
||||
Route::get('redirect', [AuthController::class, 'redirect'])->name('login');
|
||||
Route::get('callback', [AuthController::class, 'callback'])->name('callback');
|
||||
});
|
||||
|
||||
Route::middleware(['auth:web', 'banned', 'verified'])->group(
|
||||
|
Loading…
Reference in New Issue
Block a user