AuthController.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Http\Requests\Auth\LoginRequest;
  4. use App\Http\Requests\Auth\RegisterRequest;
  5. use App\Models\EmailFilter;
  6. use App\Models\Invite;
  7. use App\Models\User;
  8. use App\Models\Verify;
  9. use App\Models\VerifyCode;
  10. use App\Notifications\AccountActivation;
  11. use App\Notifications\PasswordReset;
  12. use App\Notifications\Verification;
  13. use App\Utils\Helpers;
  14. use App\Utils\IP;
  15. use Hash;
  16. use Hashids\Hashids;
  17. use Illuminate\Contracts\View\View;
  18. use Illuminate\Http\JsonResponse;
  19. use Illuminate\Http\RedirectResponse;
  20. use Illuminate\Http\Request;
  21. use Notification;
  22. use romanzipp\Turnstile\Rules\TurnstileCaptcha;
  23. use Str;
  24. use Validator;
  25. class AuthController extends Controller
  26. {
  27. // 登录
  28. public function showLoginForm(): RedirectResponse|View
  29. {
  30. // 根据权限跳转
  31. if (auth()->check()) {
  32. if (auth()->getUser()?->can('admin.index')) {
  33. return redirect()->route('admin.index');
  34. }
  35. return redirect()->route('home');
  36. }
  37. return view('auth.login');
  38. }
  39. public function login(LoginRequest $request): RedirectResponse
  40. {
  41. $data = $request->validated();
  42. // 是否校验验证码
  43. $captcha = $this->check_captcha($request);
  44. if ($captcha !== true) {
  45. return $captcha;
  46. }
  47. // 验证账号并创建会话
  48. if (! auth()->attempt($data, $request->has('remember'))) {
  49. return redirect()->back()->withInput()->withErrors(trans('auth.error.login_failed'));
  50. }
  51. $user = auth()->getUser();
  52. if (! $user) {
  53. return redirect()->back()->withInput()->withErrors(trans('auth.error.login_error'));
  54. }
  55. if ($user->can('admin.index')) {
  56. return redirect()->back();
  57. }
  58. if ($request->routeIs('admin.login.post')) {
  59. // 管理页面登录, 非权限者清场
  60. auth()->logout();
  61. return redirect()->route('login')->withErrors(trans('common.failed_item', ['attribute' => trans('auth.login')]));
  62. }
  63. // 校验普通用户账号状态
  64. if ($user->status === -1) {
  65. auth()->logout(); // 强制销毁会话,因为Auth::attempt的时候会产生会话
  66. return redirect()->back()->withInput()->withErrors(trans('auth.error.account_baned'));
  67. }
  68. if ($user->status === 0 && sysConfig('is_activate_account')) {
  69. auth()->logout(); // 强制销毁会话,因为Auth::attempt的时候会产生会话
  70. return redirect()->back()->withInput()->withErrors(trans('auth.active.promotion',
  71. ['action' => '<a href="'.route('active', ['username' => $user->username]).'" target="_blank">'.trans('common.active_item', ['attribute' => trans('common.account')]).'</a>']));
  72. }
  73. Helpers::userLoginAction($user, IP::getClientIp()); // 用户登录后操作
  74. return redirect()->back();
  75. }
  76. private function check_captcha(Request $request): RedirectResponse|bool
  77. {
  78. // Define the rules based on the captcha type
  79. $rules = [
  80. 1 => ['captcha' => 'required|captcha'], // Mews\Captcha
  81. 2 => ['geetest_challenge' => 'required|geetest'], // Geetest
  82. 3 => ['g-recaptcha-response' => 'required|NoCaptcha'], // Google reCAPTCHA
  83. 4 => ['h-captcha-response' => 'required|HCaptcha'], // hCaptcha
  84. 5 => ['cf-turnstile-response' => ['required', 'string', new TurnstileCaptcha]], // Turnstile
  85. ];
  86. // Get the current captcha setting
  87. $captchaType = sysConfig('is_captcha');
  88. // Check if the captcha is enabled and has a defined rule
  89. if (isset($rules[$captchaType])) {
  90. $validator = Validator::make($request->all(), $rules[$captchaType]);
  91. if ($validator->fails()) {
  92. return redirect()->back()->withInput()->withErrors(trans('auth.captcha.error.failed'));
  93. }
  94. }
  95. return true;
  96. }
  97. public function logout(Request $request): RedirectResponse
  98. { // 退出
  99. auth()->logout();
  100. $request->session()->invalidate();
  101. $request->session()->regenerateToken();
  102. return redirect()->route('login');
  103. }
  104. public function showRegistrationForm(): View
  105. {
  106. session()->put('register_token', Str::random());
  107. return view('auth.register', ['emailList' => (int) sysConfig('is_email_filtering') !== 2 ? false : EmailFilter::whereType(2)->get()]);
  108. }
  109. public function register(RegisterRequest $request): RedirectResponse
  110. { // 注册
  111. $cacheKey = 'register_times_'.md5(IP::getClientIp()); // 注册限制缓存key
  112. $data = $request->validated();
  113. $register_token = $request->input('register_token');
  114. $invite_code = $request->input('code');
  115. $verify_code = $request->input('verify_code');
  116. $aff = $request->input('aff');
  117. // 防止重复提交
  118. if ($register_token !== session()->pull('register_token')) {
  119. return redirect()->back()->withInput()->withErrors(trans('auth.error.repeat_request'));
  120. }
  121. // 是否开启注册
  122. if (! sysConfig('is_register')) {
  123. return redirect()->back()->withErrors(trans('auth.register.error.disable'));
  124. }
  125. // 校验域名邮箱黑白名单
  126. if (sysConfig('is_email_filtering')) {
  127. $result = $this->emailChecker($data['username'], 1);
  128. if ($result !== false) {
  129. return $result;
  130. }
  131. }
  132. // 如果需要邀请注册
  133. if (sysConfig('is_invite_register')) {
  134. // 校验邀请码合法性
  135. if ($invite_code) {
  136. if (Invite::whereCode($invite_code)->whereStatus(0)->doesntExist()) {
  137. return redirect()->back()->withInput($request->except('code'))->withErrors(trans('auth.invite.unavailable'));
  138. }
  139. } elseif ((int) sysConfig('is_invite_register') === 2) { // 必须使用邀请码
  140. return redirect()->back()->withInput()->withErrors(trans('validation.required', ['attribute' => trans('user.invite.attribute')]));
  141. }
  142. }
  143. // 注册前发送激活码
  144. if ((int) sysConfig('is_activate_account') === 1) {
  145. if (! $verify_code) {
  146. return redirect()->back()->withInput($request->except('verify_code'))->withErrors(trans('auth.captcha.required'));
  147. }
  148. $verifyCode = VerifyCode::whereAddress($data['username'])->whereCode($verify_code)->whereStatus(0)->first();
  149. if (! $verifyCode) {
  150. return redirect()->back()->withInput($request->except('verify_code'))->withErrors(trans('auth.captcha.error.timeout'));
  151. }
  152. $verifyCode->status = 1;
  153. $verifyCode->save();
  154. }
  155. // 是否校验验证码
  156. $captcha = $this->check_captcha($request);
  157. if ($captcha !== true) {
  158. return $captcha;
  159. }
  160. // 24小时内同IP注册限制
  161. if (sysConfig('register_ip_limit') && cache()->has($cacheKey)) {
  162. $registerTimes = cache()->get($cacheKey);
  163. if ($registerTimes >= sysConfig('register_ip_limit')) {
  164. return redirect()->back()->withInput($request->except('code'))->withErrors(trans('auth.register.error.throttle'));
  165. }
  166. }
  167. // 获取可用端口
  168. $port = Helpers::getPort();
  169. if ($port > sysConfig('max_port')) {
  170. return redirect()->back()->withInput()->withErrors(trans('auth.register.error.disable'));
  171. }
  172. // 获取aff
  173. $affArr = $this->getAff($invite_code, $aff);
  174. $inviter_id = $affArr['inviter_id'];
  175. $transfer_enable = MiB * ((int) sysConfig('default_traffic') + ($inviter_id ? (int) sysConfig('referral_traffic') : 0));
  176. // 创建新用户
  177. if (! $user = Helpers::addUser($data['username'], $data['password'], $transfer_enable, (int) sysConfig('default_days'), $inviter_id, $data['nickname'])) { // 注册失败,抛出异常
  178. return redirect()->back()->withInput()->withErrors(trans('auth.register.failed'));
  179. }
  180. // 注册次数+1
  181. if (cache()->has($cacheKey)) {
  182. cache()->increment($cacheKey);
  183. } else {
  184. cache()->put($cacheKey, 1, Day); // 24小时
  185. }
  186. // 更新邀请码
  187. if ($affArr['code_id'] && sysConfig('is_invite_register')) {
  188. Invite::find($affArr['code_id'])?->update(['invitee_id' => $user->id, 'status' => 1]);
  189. }
  190. // 清除邀请人Cookie
  191. cookie()->unqueue('register_aff');
  192. // 注册后发送激活码
  193. if ((int) sysConfig('is_activate_account') === 2) {
  194. // 生成激活账号的地址
  195. $token = $this->addVerifyUrl($user->id, $user->username);
  196. $activeUserUrl = route('activeAccount', $token);
  197. $user->notifyNow(new AccountActivation($activeUserUrl));
  198. session()->flash('successMsg',
  199. __("Thank you for signing up! Before you start, you need to verify your email by clicking on the link we have just sent to your email! If you haven't received an email, we would be happy to send another one."));
  200. } else {
  201. // 则直接给推荐人加流量
  202. if ($inviter_id) {
  203. $referralUser = User::find($inviter_id);
  204. if ($referralUser && $referralUser->expiration_date >= date('Y-m-d')) {
  205. $referralUser->incrementData(sysConfig('referral_traffic') * MiB);
  206. }
  207. }
  208. if ((int) sysConfig('is_activate_account') === 1) {
  209. $user->update(['status' => 1]);
  210. }
  211. session()->flash('successMsg', trans('common.success_item', ['attribute' => trans('auth.register.attribute')]));
  212. }
  213. return redirect()->route('login')->withInput();
  214. }
  215. private function emailChecker(string $email, int $returnType = 0): RedirectResponse|JsonResponse|false
  216. { // 邮箱检查
  217. $emailFilterList = EmailFilter::whereType(sysConfig('is_email_filtering'))->pluck('words')->toArray();
  218. $emailSuffix = explode('@', $email); // 提取邮箱后缀
  219. if ($emailSuffix) {
  220. switch (sysConfig('is_email_filtering')) {
  221. case 1: // 黑名单
  222. if (in_array(strtolower($emailSuffix[1]), $emailFilterList, true)) {
  223. if ($returnType) {
  224. return redirect()->back()->withErrors(trans('auth.email.error.banned'));
  225. }
  226. return response()->json(['status' => 'fail', 'message' => trans('auth.email.error.banned')]);
  227. }
  228. break;
  229. case 2: // 白名单
  230. if (! in_array(strtolower($emailSuffix[1]), $emailFilterList, true)) {
  231. if ($returnType) {
  232. return redirect()->back()->withErrors(trans('auth.email.error.invalid'));
  233. }
  234. return response()->json(['status' => 'fail', 'message' => trans('auth.email.error.invalid')]);
  235. }
  236. break;
  237. default:
  238. if ($returnType) {
  239. return redirect()->back()->withErrors(trans('auth.email.error.invalid'));
  240. }
  241. return response()->json(['status' => 'fail', 'message' => trans('auth.email.error.invalid')]);
  242. }
  243. }
  244. return false;
  245. }
  246. private function getAff(?string $code, string|int|null $aff): array
  247. { // 获取AFF
  248. $data = ['inviter_id' => null, 'code_id' => 0]; // 邀请人ID 与 邀请码ID
  249. // 有邀请码先用邀请码,用谁的邀请码就给谁返利
  250. if ($code) {
  251. $inviteCode = Invite::whereCode($code)->whereStatus(0)->first();
  252. if ($inviteCode) {
  253. $data['inviter_id'] = $inviteCode->inviter_id;
  254. $data['code_id'] = $inviteCode->id;
  255. }
  256. }
  257. // 没有用邀请码或者邀请码是管理员生成的,则检查cookie或者url链接
  258. if (! $data['inviter_id']) {
  259. $cookieAff = \request()?->cookie('register_aff'); // 检查一下cookie里有没有aff
  260. if ($cookieAff || $aff) {
  261. $data['inviter_id'] = $this->setInviter($aff ?: $cookieAff);
  262. }
  263. }
  264. return $data;
  265. }
  266. private function setInviter(string|int $aff): ?int
  267. {
  268. $uid = 0;
  269. if (is_numeric($aff)) {
  270. $uid = (int) $aff;
  271. } else {
  272. $decode = (new Hashids(sysConfig('aff_salt'), 8))->decode($aff);
  273. if ($decode) {
  274. $uid = $decode[0];
  275. }
  276. }
  277. return $uid && User::whereId($uid)->exists() ? $uid : null;
  278. }
  279. private function addVerifyUrl(int $uid, string $email): string
  280. { // 生成申请的请求地址
  281. $token = md5(sysConfig('website_name').$email.microtime());
  282. $verify = new Verify;
  283. $verify->user_id = $uid;
  284. $verify->token = $token;
  285. $verify->save();
  286. return $token;
  287. }
  288. public function resetPassword(Request $request): RedirectResponse|View
  289. { // 重设密码页
  290. if ($request->isMethod('POST')) {
  291. // 校验请求
  292. $validator = Validator::make($request->all(), ['username' => 'required|'.(sysConfig('username_type') ?? 'email').'|exists:user,username']);
  293. if ($validator->fails()) {
  294. return redirect()->back()->withInput()->withErrors($validator->errors());
  295. }
  296. $username = $request->input('username');
  297. // 是否开启重设密码
  298. if (! sysConfig('password_reset_notification')) {
  299. return redirect()->back()->withErrors(trans('auth.password.reset.error.disabled', ['email' => sysConfig('webmaster_email')]));
  300. }
  301. // 查找账号
  302. $user = User::whereUsername($username)->firstOrFail();
  303. // 24小时内重设密码次数限制
  304. $resetTimes = 0;
  305. if (cache()->has('resetPassword_'.md5($username))) {
  306. $resetTimes = cache()->get('resetPassword_'.md5($username));
  307. if ($resetTimes >= sysConfig('reset_password_times')) {
  308. return redirect()->back()->withErrors(trans('auth.password.reset.error.throttle', ['time' => sysConfig('reset_password_times')]));
  309. }
  310. }
  311. // 生成取回密码的地址
  312. $token = $this->addVerifyUrl($user->id, $username);
  313. // 发送邮件
  314. $resetUrl = route('resettingPasswd', $token);
  315. $user->notifyNow(new PasswordReset($resetUrl));
  316. cache()->put('resetPassword_'.md5($username), $resetTimes + 1, Day);
  317. return redirect()->back()->with('successMsg', trans('auth.password.reset.sent'));
  318. }
  319. return view('auth.resetPassword');
  320. }
  321. public function reset(Request $request, ?string $token): RedirectResponse|View
  322. { // 重设密码
  323. if (! $token) {
  324. return redirect()->route('login');
  325. }
  326. if ($request->isMethod('POST')) {
  327. $validator = Validator::make($request->all(), [
  328. 'password' => 'required|min:6|confirmed',
  329. ]);
  330. if ($validator->fails()) {
  331. return redirect()->back()->withInput()->withErrors($validator->errors());
  332. }
  333. $password = $request->input('password');
  334. // 校验账号
  335. $verify = Verify::type(1)->whereToken($token)->firstOrFail();
  336. $user = $verify->user;
  337. if (! $verify) {
  338. return redirect()->route('login');
  339. }
  340. if ($user->status === -1) {
  341. return redirect()->back()->withErrors(trans('auth.error.account_baned'));
  342. }
  343. if ($verify->status === 1) {
  344. return redirect()->back()->withErrors(trans('auth.error.url_timeout'));
  345. }
  346. if (Hash::check($password, $verify->user->password)) {
  347. return redirect()->back()->withErrors(trans('auth.password.reset.error.same'));
  348. }
  349. // 更新密码
  350. if (! $user->update(['password' => $password])) {
  351. return redirect()->back()->withErrors(trans('common.failed_item', ['attribute' => trans('auth.password.reset.attribute')]));
  352. }
  353. // 置为已使用
  354. $verify->status = 1;
  355. $verify->save();
  356. return redirect()->route('login')->with('successMsg', trans('auth.password.reset.success'));
  357. }
  358. $verify = Verify::type(1)->whereToken($token)->first();
  359. if (! $verify) {
  360. return redirect()->route('login');
  361. }
  362. if (time() - strtotime($verify->created_at) >= 1800) {
  363. // 置为已失效
  364. $verify->status = 2;
  365. $verify->save();
  366. }
  367. return view('auth.reset', ['verify' => Verify::type(1)->whereToken($token)->first()]); // 重新获取一遍verify
  368. }
  369. public function activeUser(Request $request): RedirectResponse|View
  370. { // 激活账号页
  371. if ($request->isMethod('POST')) {
  372. $validator = Validator::make($request->all(), ['username' => 'required|'.(sysConfig('username_type') ?? 'email').'|exists:user,username']);
  373. if ($validator->fails()) {
  374. return redirect()->back()->withInput()->withErrors($validator->errors());
  375. }
  376. $username = $request->input('username');
  377. // 是否开启账号激活
  378. if (! sysConfig('is_activate_account')) {
  379. return redirect()->back()->withInput()->withErrors(trans('auth.active.error.disable'));
  380. }
  381. // 查找账号
  382. $user = User::whereUsername($username)->firstOrFail();
  383. if ($user->status === -1) {
  384. return redirect()->back()->withErrors(trans('auth.error.account_baned'));
  385. }
  386. if ($user->status === 1) {
  387. return redirect()->back()->withErrors(trans('auth.active.error.activated'));
  388. }
  389. // 24小时内激活次数限制
  390. $activeTimes = 0;
  391. if (cache()->has('activeUser_'.md5($username))) {
  392. $activeTimes = cache()->get('activeUser_'.md5($username));
  393. if ($activeTimes >= sysConfig('active_times')) {
  394. return redirect()->back()->withErrors(trans('auth.active.error.throttle', ['email' => sysConfig('webmaster_email')]));
  395. }
  396. }
  397. // 生成激活账号的地址
  398. $token = $this->addVerifyUrl($user->id, $username);
  399. // 发送邮件
  400. $activeUserUrl = route('activeAccount', $token);
  401. Notification::route('mail', $username)->notifyNow(new AccountActivation($activeUserUrl));
  402. cache()->put('activeUser_'.md5($username), $activeTimes + 1, Day);
  403. return redirect()->back()->with('successMsg', trans('auth.active.sent'));
  404. }
  405. return view('auth.activeUser');
  406. }
  407. public function active(string $token): RedirectResponse|View
  408. { // 激活账号
  409. $verify = Verify::type(1)->with('user')->whereToken($token)->firstOrFail();
  410. $user = $verify->user;
  411. if (! $verify) {
  412. return redirect()->route('login');
  413. }
  414. if (empty($user) || $verify->status > 0) {
  415. session()->flash('errorMsg', trans('auth.error.url_timeout'));
  416. return view('auth.active');
  417. }
  418. if ($user->status === 1) {
  419. session()->flash('errorMsg', trans('auth.active.error.activated'));
  420. return view('auth.active');
  421. }
  422. if (time() - strtotime($verify->created_at) >= 1800) {
  423. session()->flash('errorMsg', trans('auth.error.url_timeout'));
  424. // 置为已失效
  425. $verify->status = 2;
  426. $verify->save();
  427. return view('auth.active');
  428. }
  429. // 更新账号状态
  430. if (! $user->update(['status' => 1])) {
  431. session()->flash('errorMsg', trans('common.active_item', ['attribute' => trans('common.failed')]));
  432. return redirect()->back();
  433. }
  434. // 置为已使用
  435. $verify->status = 1;
  436. $verify->save();
  437. // 账号激活后给邀请人送流量
  438. $inviter = $user->inviter;
  439. if ($inviter) {
  440. $inviter->incrementData(sysConfig('referral_traffic') * MiB);
  441. }
  442. session()->flash('successMsg', trans('common.active_item', ['attribute' => trans('common.success')]));
  443. return view('auth.active');
  444. }
  445. public function sendCode(Request $request): JsonResponse
  446. { // 发送注册验证码
  447. $validator = Validator::make($request->all(), ['username' => 'required|'.(sysConfig('username_type') ?? 'email').'|unique:user,username']);
  448. if ($validator->fails()) {
  449. return response()->json(['status' => 'fail', 'message' => $validator->getMessageBag()->first()]);
  450. }
  451. $email = $request->input('username');
  452. $ip = IP::getClientIP();
  453. // 校验域名邮箱黑白名单
  454. if (sysConfig('is_email_filtering')) {
  455. $result = $this->emailChecker($email);
  456. if ($result !== false) {
  457. return $result;
  458. }
  459. }
  460. // 是否开启注册发送验证码
  461. if ((int) sysConfig('is_activate_account') !== 1) {
  462. return response()->json(['status' => 'fail', 'message' => trans('auth.active.error.disable')]);
  463. }
  464. // 防刷机制
  465. if (cache()->has('send_verify_code_'.md5($ip))) {
  466. return response()->json(['status' => 'fail', 'message' => trans('auth.register.error.throttle')]);
  467. }
  468. // 发送邮件
  469. $code = Str::random(6);
  470. if (VerifyCode::create(['address' => $email, 'code' => $code])) { // 生成注册验证码
  471. Notification::route('mail', $email)->notifyNow(new Verification($code));
  472. }
  473. cache()->put('send_verify_code_'.md5($ip), $ip, Minute);
  474. return response()->json(['status' => 'success', 'message' => trans('auth.captcha.sent')]);
  475. }
  476. public function free(): View
  477. { // 公开的邀请码列表
  478. return view('auth.free', ['inviteList' => Invite::whereInviterId(null)->whereStatus(0)->paginate()]);
  479. }
  480. public function switchLang(string $locale): RedirectResponse
  481. { // 切换语言
  482. if (array_key_exists($locale, config('common.language'))) {
  483. session()->put('locale', $locale);
  484. }
  485. return redirect()->back();
  486. }
  487. }