1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6namespace App\Http\Controllers;
7
8use App\Libraries\User\CountryChange;
9use App\Libraries\User\DatadogLoginAttempt;
10use App\Libraries\User\ForceReactivation;
11use App\Models\Country;
12use App\Models\User;
13use App\Transformers\CurrentUserTransformer;
14use Auth;
15use romanzipp\Turnstile\Validator as TurnstileValidator;
16
17class SessionsController extends Controller
18{
19 public function __construct()
20 {
21 $this->middleware('guest', ['only' => [
22 'store',
23 ]]);
24
25 parent::__construct();
26 }
27
28 public function store()
29 {
30 $request = request();
31
32 $params = get_params($request->all(), null, ['username:string', 'password:string', 'remember:bool', 'cf-turnstile-response:string']);
33 $username = presence(trim($params['username'] ?? null));
34 $password = presence($params['password'] ?? null);
35 $remember = $params['remember'] ?? false;
36
37 if ($username === null) {
38 DatadogLoginAttempt::log('missing_username');
39
40 abort(422);
41 }
42
43 if ($password === null) {
44 DatadogLoginAttempt::log('missing_password');
45
46 abort(422);
47 }
48
49 if (captcha_login_triggered()) {
50 $token = presence($params['cf-turnstile-response'] ?? null);
51 $validCaptcha = false;
52
53 if ($token !== null) {
54 $validCaptcha = (new TurnstileValidator())->validate($token)->isValid();
55 }
56
57 if (!$validCaptcha) {
58 if ($token === null) {
59 DatadogLoginAttempt::log('missing_captcha');
60 } else {
61 DatadogLoginAttempt::log('invalid_captcha');
62 }
63
64 return $this->triggerCaptcha(osu_trans('users.login.invalid_captcha'), 422);
65 }
66 }
67
68 $ip = $request->getClientIp();
69
70 $user = User::findForLogin($username);
71
72 if ($user === null && strpos($username, '@') !== false && !$GLOBALS['cfg']['osu']['user']['allow_email_login']) {
73 $authError = osu_trans('users.login.email_login_disabled');
74 } else {
75 $authError = User::attemptLogin($user, $password, $ip);
76 }
77
78 if ($authError === null) {
79 $forceReactivation = new ForceReactivation($user, $request);
80
81 if ($forceReactivation->isRequired()) {
82 DatadogLoginAttempt::log('password_reset');
83 $forceReactivation->run();
84
85 \Session::flash('password_reset_start', [
86 'reason' => $forceReactivation->getReason(),
87 'username' => $username,
88 ]);
89
90 return ujs_redirect(route('password-reset'));
91 }
92
93 // try fixing user country if it's currently set to unknown
94 if ($user->country_acronym === Country::UNKNOWN) {
95 try {
96 CountryChange::handle($user, request_country(), 'automated unknown country fixup on login');
97 } catch (\Throwable $e) {
98 // report failures but continue anyway
99 log_error($e);
100 }
101 }
102
103 DatadogLoginAttempt::log(null);
104 $this->login($user, $remember);
105
106 return [
107 'csrf_token' => csrf_token(),
108 'header' => view('layout._header_user')->render(),
109 'header_popup' => view('layout._popup_user')->render(),
110 'user' => json_item($user, new CurrentUserTransformer()),
111 ];
112 }
113
114 if (captcha_login_triggered()) {
115 return $this->triggerCaptcha($authError);
116 }
117
118 return error_popup($authError, 403);
119 }
120
121 public function destroy()
122 {
123 if (Auth::check()) {
124 logout();
125 }
126
127 return [];
128 }
129
130 private function triggerCaptcha($message, $returnCode = 403)
131 {
132 return response([
133 'error' => $message,
134 'captcha_triggered' => true,
135 ], $returnCode);
136 }
137}