the browser-facing portion of osu!
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Store session verification request data in redis

nanaya 0194b3ae 9af29f54

+340 -354
+2 -7
app/Exceptions/Handler.php
··· 5 5 6 6 namespace App\Exceptions; 7 7 8 - use App\Libraries\UserVerification; 8 + use App\Libraries\SessionVerification; 9 9 use Auth; 10 10 use Illuminate\Auth\Access\AuthorizationException as LaravelAuthorizationException; 11 11 use Illuminate\Auth\AuthenticationException; ··· 121 121 } 122 122 123 123 if ($e instanceof VerificationRequiredException) { 124 - return $this->unverified(); 124 + return SessionVerification\Controller::initiate(); 125 125 } 126 126 127 127 if ($e instanceof AuthenticationException) { ··· 164 164 } 165 165 166 166 return ext_view('users.login', null, null, 401); 167 - } 168 - 169 - protected function unverified() 170 - { 171 - return UserVerification::fromCurrentRequest()->initiate(); 172 167 } 173 168 174 169 private function reportWithSentry($e)
+4 -16
app/Http/Controllers/AccountController.php
··· 8 8 use App\Exceptions\ImageProcessorException; 9 9 use App\Exceptions\ModelNotSavedException; 10 10 use App\Libraries\Session\Store as SessionStore; 11 + use App\Libraries\SessionVerification; 11 12 use App\Libraries\User\AvatarHelper; 12 13 use App\Libraries\User\CountryChange; 13 14 use App\Libraries\User\CountryChangeTarget; 14 - use App\Libraries\UserVerification; 15 - use App\Libraries\UserVerificationState; 16 15 use App\Mail\UserEmailUpdated; 17 16 use App\Mail\UserPasswordUpdated; 18 17 use App\Models\GithubUser; ··· 302 301 303 302 public function verify() 304 303 { 305 - return UserVerification::fromCurrentRequest()->verify(); 304 + return SessionVerification\Controller::verify(); 306 305 } 307 306 308 307 public function verifyLink() 309 308 { 310 - $state = UserVerificationState::fromVerifyLink(get_string(request('key')) ?? ''); 311 - 312 - if ($state === null) { 313 - UserVerification::logAttempt('link', 'fail', 'incorrect_key'); 314 - 315 - return ext_view('accounts.verification_invalid', null, null, 404); 316 - } 317 - 318 - UserVerification::logAttempt('link', 'success'); 319 - $state->markVerified(); 320 - 321 - return ext_view('accounts.verification_completed'); 309 + return SessionVerification\Controller::verifyLink(); 322 310 } 323 311 324 312 public function reissueCode() 325 313 { 326 - return UserVerification::fromCurrentRequest()->reissue(); 314 + return SessionVerification\Controller::reissue(); 327 315 } 328 316 }
+2 -3
app/Http/Controllers/Controller.php
··· 8 8 use App; 9 9 use App\Http\Middleware\VerifyUserAlways; 10 10 use App\Libraries\LocaleMeta; 11 - use App\Libraries\UserVerificationState; 12 11 use App\Models\Log; 13 12 use Auth; 14 13 use Carbon\Carbon; ··· 38 37 { 39 38 cleanup_cookies(); 40 39 41 - $session = session(); 40 + $session = \Session::instance(); 42 41 $session->flush(); 43 42 $session->regenerateToken(); 44 43 $session->put('requires_verification', VerifyUserAlways::isRequired($user)); 45 44 Auth::login($user, $remember); 46 45 if (config('osu.user.bypass_verification')) { 47 - UserVerificationState::fromCurrentRequest()->markVerified(); 46 + $session->markVerified(); 48 47 } 49 48 $session->migrate(true); 50 49 }
+3 -4
app/Http/Middleware/SetSessionVerification.php
··· 8 8 namespace App\Http\Middleware; 9 9 10 10 use App\Events\UserSessionEvent; 11 - use App\Libraries\UserVerificationState; 12 11 use Closure; 13 12 use Illuminate\Http\Request; 14 13 ··· 16 15 { 17 16 public function handle(Request $request, Closure $next) 18 17 { 19 - $user = auth()->user(); 18 + $user = \Auth::user(); 20 19 if ($user !== null) { 21 - $isVerified = UserVerificationState::fromCurrentRequest()->isDone(); 20 + $session = \Session::instance(); 21 + $isVerified = $session->isVerified(); 22 22 23 23 if ($isVerified) { 24 24 $user->markSessionVerified(); 25 25 } else { 26 26 $isRequired = VerifyUserAlways::isRequired($user); 27 - $session = session(); 28 27 if ($session->get('requires_verification') !== $isRequired) { 29 28 $session->put('requires_verification', $isRequired); 30 29 $session->save();
+2 -2
app/Http/Middleware/VerifyUser.php
··· 5 5 6 6 namespace App\Http\Middleware; 7 7 8 - use App\Libraries\UserVerification; 8 + use App\Libraries\SessionVerification; 9 9 use Closure; 10 10 use Illuminate\Contracts\Auth\Guard as AuthGuard; 11 11 use Illuminate\Http\Request; ··· 40 40 && !$this->alwaysSkipVerification() 41 41 && $this->requiresVerification($request) 42 42 ) { 43 - return UserVerification::fromCurrentRequest()->initiate(); 43 + return SessionVerification\Controller::initiate(); 44 44 } 45 45 46 46 return $next($request);
+17
app/Interfaces/SessionVerificationInterface.php
··· 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 + 6 + declare(strict_types=1); 7 + 8 + namespace App\Interfaces; 9 + 10 + interface SessionVerificationInterface 11 + { 12 + public static function findForVerification(string $id): ?static; 13 + 14 + public function getKey(); 15 + public function isVerified(): bool; 16 + public function markVerified(): void; 17 + }
+24 -1
app/Libraries/Session/Store.php
··· 8 8 namespace App\Libraries\Session; 9 9 10 10 use App\Events\UserSessionEvent; 11 + use App\Interfaces\SessionVerificationInterface; 11 12 use Illuminate\Redis\Connections\PhpRedisConnection; 13 + use Illuminate\Session\Store as BaseStore; 12 14 use Illuminate\Support\Arr; 13 15 use Jenssegers\Agent\Agent; 14 16 15 - class Store extends \Illuminate\Session\Store 17 + class Store extends BaseStore implements SessionVerificationInterface 16 18 { 17 19 private const PREFIX = 'sessions:'; 18 20 ··· 38 40 $redis->srem(self::listKey($userId), ...$ids, ...$idsForEvent); 39 41 UserSessionEvent::newLogout($userId, $idsForEvent)->broadcast(); 40 42 } 43 + } 44 + 45 + public static function findForVerification(string $id): static 46 + { 47 + return static::findOrNew($id); 41 48 } 42 49 43 50 public static function findOrNew(?string $id = null): static ··· 157 164 static::batchDelete($this->userId(), [$this->getId()]); 158 165 } 159 166 167 + public function getKey(): string 168 + { 169 + return $this->getId(); 170 + } 171 + 160 172 public function getKeyForEvent(): string 161 173 { 162 174 return self::keyForEvent($this->getId()); ··· 174 186 { 175 187 // Overridden to allow prefixed id 176 188 return is_string($id); 189 + } 190 + 191 + public function isVerified(): bool 192 + { 193 + return $this->attributes['verified'] ?? false; 194 + } 195 + 196 + public function markVerified(): void 197 + { 198 + $this->attributes['verified'] = true; 199 + $this->save(); 177 200 } 178 201 179 202 /**
+107
app/Libraries/SessionVerification/Controller.php
··· 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 + 6 + declare(strict_types=1); 7 + 8 + namespace App\Libraries\SessionVerification; 9 + 10 + use App\Exceptions\UserVerificationException; 11 + use App\Models\LoginAttempt; 12 + 13 + class Controller 14 + { 15 + public static function initiate() 16 + { 17 + static $statusCode = 401; 18 + 19 + app('route-section')->setError("{$statusCode}-verification"); 20 + 21 + $user = \Auth::user(); 22 + $email = $user->user_email; 23 + 24 + $session = \Session::instance(); 25 + if (State::fromSession($session) === null) { 26 + Helper::logAttempt('input', 'new'); 27 + 28 + Helper::issue($session, $user); 29 + } 30 + 31 + if (\Request::ajax()) { 32 + return response([ 33 + 'authentication' => 'verify', 34 + 'box' => view( 35 + 'users._verify_box', 36 + compact('email') 37 + )->render(), 38 + ], $statusCode); 39 + } 40 + 41 + return ext_view('users.verify', compact('email'), null, $statusCode); 42 + } 43 + 44 + public static function reissue() 45 + { 46 + $session = \Session::instance(); 47 + if ($session->isVerified()) { 48 + return response(null, 204); 49 + } 50 + 51 + Helper::issue($session, \Auth::user()); 52 + 53 + return response(['message' => osu_trans('user_verification.errors.reissued')], 200); 54 + } 55 + 56 + public static function verify() 57 + { 58 + $key = strtr(get_string(\Request::input('verification_key')) ?? '', [' ' => '']); 59 + $user = \Auth::user(); 60 + $session = \Session::instance(); 61 + $state = State::fromSession($session); 62 + 63 + try { 64 + if ($state === null) { 65 + throw new UserVerificationException('expired', true); 66 + } 67 + $state->verify($key); 68 + } catch (UserVerificationException $e) { 69 + Helper::logAttempt('input', 'fail', $e->reasonKey()); 70 + 71 + if ($e->reasonKey() === 'incorrect_key') { 72 + LoginAttempt::logAttempt(\Request::getClientIp(), $user, 'verify-mismatch', $key); 73 + } 74 + 75 + if ($e->shouldReissue()) { 76 + Helper::issue($session, $user); 77 + } 78 + 79 + return error_popup($e->getMessage()); 80 + } 81 + 82 + Helper::logAttempt('input', 'success'); 83 + Helper::markVerified($session); 84 + 85 + return response(null, 204); 86 + } 87 + 88 + public static function verifyLink() 89 + { 90 + $state = State::fromVerifyLink(get_string(\Request::input('key')) ?? ''); 91 + 92 + if ($state === null) { 93 + Helper::logAttempt('link', 'fail', 'incorrect_key'); 94 + 95 + return ext_view('accounts.verification_invalid', null, null, 404); 96 + } 97 + 98 + $session = $state->findSession(); 99 + // Otherwise pretend everything is okay if session is missing 100 + if ($session !== null) { 101 + Helper::logAttempt('link', 'success'); 102 + Helper::markVerified($session); 103 + } 104 + 105 + return ext_view('accounts.verification_completed'); 106 + } 107 + }
+55
app/Libraries/SessionVerification/Helper.php
··· 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 + 6 + declare(strict_types=1); 7 + 8 + namespace App\Libraries\SessionVerification; 9 + 10 + use App\Events\UserSessionEvent; 11 + use App\Interfaces\SessionVerificationInterface; 12 + use App\Mail\UserVerification as UserVerificationMail; 13 + use App\Models\LoginAttempt; 14 + use App\Models\User; 15 + 16 + class Helper 17 + { 18 + public static function issue(SessionVerificationInterface $session, User $user): void 19 + { 20 + if (!is_valid_email_format($user->user_email)) { 21 + return; 22 + } 23 + 24 + $state = State::create($session); 25 + $keys = [ 26 + 'link' => $state->linkKey, 27 + 'main' => $state->key, 28 + ]; 29 + 30 + $request = \Request::instance(); 31 + LoginAttempt::logAttempt($request->getClientIp(), $user, 'verify'); 32 + 33 + $requestCountry = app('countries')->byCode(request_country($request) ?? '')?->name; 34 + 35 + \Mail::to($user) 36 + ->queue(new UserVerificationMail( 37 + compact('keys', 'user', 'requestCountry') 38 + )); 39 + } 40 + 41 + public static function logAttempt(string $source, string $type, string $reason = null): void 42 + { 43 + \Datadog::increment( 44 + \Config::get('datadog-helper.prefix_web').'.verification.attempts', 45 + 1, 46 + compact('reason', 'source', 'type') 47 + ); 48 + } 49 + 50 + public static function markVerified(SessionVerificationInterface $session) 51 + { 52 + $session->markVerified(); 53 + UserSessionEvent::newVerified($session->userId(), $session->getKeyForEvent())->broadcast(); 54 + } 55 + }
+105
app/Libraries/SessionVerification/State.php
··· 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 + 6 + declare(strict_types=1); 7 + 8 + namespace App\Libraries\SessionVerification; 9 + 10 + use App\Exceptions\UserVerificationException; 11 + use App\Interfaces\SessionVerificationInterface; 12 + use App\Libraries\SignedRandomString; 13 + use Carbon\CarbonImmutable; 14 + 15 + class State 16 + { 17 + private const KEY_VALID_DURATION = 5 * 3600; 18 + 19 + public readonly \DateTimeInterface $expiresAt; 20 + public readonly string $key; 21 + public readonly string $linkKey; 22 + public int $tries = 0; 23 + 24 + private function __construct( 25 + private readonly string $sessionClass, 26 + private readonly string $sessionId, 27 + ) { 28 + // 1 byte = 8 bits = 2^8 = 16^2 = 2 hex characters 29 + $this->key = bin2hex(random_bytes(\Config::get('osu.user.verification_key_length_hex') / 2)); 30 + $this->linkKey = SignedRandomString::create(32); 31 + $this->expiresAt = CarbonImmutable::now()->addSeconds(static::KEY_VALID_DURATION); 32 + } 33 + 34 + public static function create(SessionVerificationInterface $session): static 35 + { 36 + $state = new static($session::class, $session->getKey()); 37 + $state->save(true); 38 + 39 + return $state; 40 + } 41 + 42 + public static function fromSession(SessionVerificationInterface $session): ?static 43 + { 44 + return \Cache::get(static::cacheKey($session::class, $session->getKey())); 45 + } 46 + 47 + public static function fromVerifyLink(string $linkKey): ?static 48 + { 49 + if (!SignedRandomString::isValid($linkKey)) { 50 + return null; 51 + } 52 + 53 + $cacheKey = \Cache::get(static::cacheLinkKey($linkKey)); 54 + 55 + return $cacheKey === null ? null : \Cache::get($cacheKey); 56 + } 57 + 58 + private static function cacheKey(string $class, string $id): string 59 + { 60 + return "session_verification:{$class}:{$id}"; 61 + } 62 + 63 + private static function cacheLinkKey(string $linkKey): string 64 + { 65 + return "session_verification_link:{$linkKey}"; 66 + } 67 + 68 + public function delete(): void 69 + { 70 + \Cache::delete(static::cacheKey($this->sessionClass, $this->sessionId)); 71 + \Cache::delete(static::cacheLinkKey($state->linkKey)); 72 + } 73 + 74 + public function findSession(): ?SessionVerificationInterface 75 + { 76 + return $this->sessionClass::findForVerification($this->sessionId); 77 + } 78 + 79 + public function verify(string $inputKey): void 80 + { 81 + $this->tries++; 82 + 83 + if ($this->expiresAt->isPast()) { 84 + throw new UserVerificationException('expired', true); 85 + } 86 + 87 + if (!hash_equals($this->key, $inputKey)) { 88 + if ($this->tries >= \Config::get('osu.user.verification_key_tries_limit')) { 89 + throw new UserVerificationException('retries_exceeded', true); 90 + } else { 91 + $this->save(false); 92 + throw new UserVerificationException('incorrect_key', false); 93 + } 94 + } 95 + } 96 + 97 + private function save(bool $saveLinkKey): void 98 + { 99 + $cacheKey = static::cacheKey($this->sessionClass, $this->sessionId); 100 + \Cache::put($cacheKey, $this, $this->expiresAt); 101 + if ($saveLinkKey) { 102 + \Cache::put(static::cacheLinkKey($this->linkKey), $cacheKey, $this->expiresAt); 103 + } 104 + } 105 + }
-165
app/Libraries/UserVerification.php
··· 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 - 6 - namespace App\Libraries; 7 - 8 - use App\Exceptions\UserVerificationException; 9 - use App\Mail\UserVerification as UserVerificationMail; 10 - use App\Models\Country; 11 - use App\Models\LoginAttempt; 12 - use Datadog; 13 - use Mail; 14 - 15 - class UserVerification 16 - { 17 - private $request; 18 - private $state; 19 - private $user; 20 - 21 - public static function fromCurrentRequest() 22 - { 23 - $request = request(); 24 - $attributes = $request->attributes; 25 - $verification = $attributes->get('user_verification'); 26 - 27 - if ($verification === null) { 28 - $verification = new static( 29 - auth()->user(), 30 - $request, 31 - UserVerificationState::fromCurrentRequest() 32 - ); 33 - $attributes->set('user_verification', $verification); 34 - } 35 - 36 - return $verification; 37 - } 38 - 39 - public static function logAttempt(string $source, string $type, string $reason = null): void 40 - { 41 - Datadog::increment( 42 - config('datadog-helper.prefix_web').'.verification.attempts', 43 - 1, 44 - compact('reason', 'source', 'type') 45 - ); 46 - } 47 - 48 - private function __construct($user, $request, $state) 49 - { 50 - $this->user = $user; 51 - $this->request = $request; 52 - $this->state = $state; 53 - } 54 - 55 - public function initiate() 56 - { 57 - $statusCode = 401; 58 - app('route-section')->setError("{$statusCode}-verification"); 59 - 60 - // Workaround race condition causing $this->issue() to be called in parallel. 61 - // Mainly observed when logging in as privileged user. 62 - if ($this->request->ajax()) { 63 - $routeData = app('route-section')->getOriginal(); 64 - if ($routeData['controller'] === 'notifications_controller' && $routeData['action'] === 'index') { 65 - return response(['error' => 'verification'], $statusCode); 66 - } 67 - } 68 - 69 - $email = $this->user->user_email; 70 - 71 - if (!$this->state->issued()) { 72 - static::logAttempt('input', 'new'); 73 - 74 - $this->issue(); 75 - } 76 - 77 - if ($this->request->ajax()) { 78 - return response([ 79 - 'authentication' => 'verify', 80 - 'box' => view( 81 - 'users._verify_box', 82 - compact('email') 83 - )->render(), 84 - ], $statusCode); 85 - } else { 86 - return ext_view('users.verify', compact('email'), null, $statusCode); 87 - } 88 - } 89 - 90 - public function isDone() 91 - { 92 - return $this->state->isDone(); 93 - } 94 - 95 - public function issue() 96 - { 97 - $user = $this->user; 98 - 99 - if (!is_valid_email_format($user->user_email)) { 100 - return; 101 - } 102 - 103 - $keys = $this->state->issue(); 104 - 105 - LoginAttempt::logAttempt($this->request->getClientIp(), $this->user, 'verify'); 106 - 107 - $requestCountry = Country 108 - ::where('acronym', request_country($this->request)) 109 - ->pluck('name') 110 - ->first(); 111 - 112 - Mail::to($user) 113 - ->queue(new UserVerificationMail( 114 - compact('keys', 'user', 'requestCountry') 115 - )); 116 - } 117 - 118 - public function markVerified() 119 - { 120 - $this->state->markVerified(); 121 - } 122 - 123 - public function markVerifiedAndRespond() 124 - { 125 - $this->markVerified(); 126 - 127 - return response([], 200); 128 - } 129 - 130 - public function reissue() 131 - { 132 - if ($this->state->isDone()) { 133 - return $this->markVerifiedAndRespond(); 134 - } 135 - 136 - $this->issue(); 137 - 138 - return response(['message' => osu_trans('user_verification.errors.reissued')], 200); 139 - } 140 - 141 - public function verify() 142 - { 143 - $key = str_replace(' ', '', $this->request->input('verification_key')); 144 - 145 - try { 146 - $this->state->verify($key); 147 - } catch (UserVerificationException $e) { 148 - static::logAttempt('input', 'fail', $e->reasonKey()); 149 - 150 - if ($e->reasonKey() === 'incorrect_key') { 151 - LoginAttempt::logAttempt($this->request->getClientIp(), $this->user, 'verify-mismatch', $key); 152 - } 153 - 154 - if ($e->shouldReissue()) { 155 - $this->issue(); 156 - } 157 - 158 - return error_popup($e->getMessage()); 159 - } 160 - 161 - static::logAttempt('input', 'success'); 162 - 163 - return $this->markVerifiedAndRespond(); 164 - } 165 - }
-143
app/Libraries/UserVerificationState.php
··· 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 - 6 - namespace App\Libraries; 7 - 8 - use App\Events\UserSessionEvent; 9 - use App\Exceptions\UserVerificationException; 10 - use App\Libraries\Session\Store as SessionStore; 11 - use App\Models\User; 12 - 13 - class UserVerificationState 14 - { 15 - private function __construct(private User $user, private SessionStore $session) 16 - { 17 - } 18 - 19 - public static function fromCurrentRequest() 20 - { 21 - return new static(\Auth::user(), \Session::instance()); 22 - } 23 - 24 - public static function fromVerifyLink($linkKey) 25 - { 26 - if (!SignedRandomString::isValid($linkKey)) { 27 - return null; 28 - } 29 - 30 - $params = cache()->get("verification:{$linkKey}"); 31 - 32 - if ($params !== null) { 33 - $state = static::load($params); 34 - 35 - // As it's from verify link, make sure the state is waiting for verification. 36 - if ($state->issued()) { 37 - return $state; 38 - } 39 - } 40 - } 41 - 42 - public static function load($params) 43 - { 44 - return new static( 45 - User::find($params['userId']), 46 - SessionStore::findOrNew($params['sessionId']), 47 - ); 48 - } 49 - 50 - public function dump() 51 - { 52 - return [ 53 - 'userId' => $this->user->getKey(), 54 - 'sessionId' => $this->session->getId(), 55 - ]; 56 - } 57 - 58 - public function issue() 59 - { 60 - $previousLinkKey = $this->session->get('verification_link_key'); 61 - 62 - if (present($previousLinkKey)) { 63 - cache()->forget("verification:{$previousLinkKey}"); 64 - } 65 - 66 - // 1 byte = 2^8 bits = 16^2 bits = 2 hex characters 67 - $key = bin2hex(random_bytes(config('osu.user.verification_key_length_hex') / 2)); 68 - $linkKey = SignedRandomString::create(32); 69 - $expires = now()->addHours(5); 70 - 71 - $this->session->put('verification_key', $key); 72 - $this->session->put('verification_link_key', $linkKey); 73 - $this->session->put('verification_expire_date', $expires); 74 - $this->session->put('verification_tries', 0); 75 - $this->session->save(); 76 - 77 - cache()->put("verification:{$linkKey}", $this->dump(), $expires); 78 - 79 - return [ 80 - 'link' => $linkKey, 81 - 'main' => $key, 82 - ]; 83 - } 84 - 85 - public function issued() 86 - { 87 - return present($this->session->get('verification_key')); 88 - } 89 - 90 - public function isDone() 91 - { 92 - if ($this->user === null) { 93 - return true; 94 - } 95 - 96 - if ($this->session->get('verified')) { 97 - return true; 98 - } 99 - 100 - return false; 101 - } 102 - 103 - public function markVerified() 104 - { 105 - $this->session->forget('verification_expire_date'); 106 - $this->session->forget('verification_tries'); 107 - $this->session->forget('verification_key'); 108 - $this->session->put('verified', true); 109 - $this->session->save(); 110 - 111 - UserSessionEvent::newVerified($this->user->getKey(), $this->session->getKeyForEvent())->broadcast(); 112 - } 113 - 114 - public function verify($inputKey) 115 - { 116 - if ($this->isDone()) { 117 - return; 118 - } 119 - 120 - $expireDate = $this->session->get('verification_expire_date'); 121 - $tries = $this->session->get('verification_tries'); 122 - $key = $this->session->get('verification_key'); 123 - 124 - if (!present($expireDate) || !present($tries) || !present($key)) { 125 - throw new UserVerificationException('expired', true); 126 - } 127 - 128 - if ($expireDate->isPast()) { 129 - throw new UserVerificationException('expired', true); 130 - } 131 - 132 - if ($tries > config('osu.user.verification_key_tries_limit')) { 133 - throw new UserVerificationException('retries_exceeded', true); 134 - } 135 - 136 - if (!hash_equals($key, $inputKey)) { 137 - $this->session->put('verification_tries', $tries + 1); 138 - $this->session->save(); 139 - 140 - throw new UserVerificationException('incorrect_key', false); 141 - } 142 - } 143 - }
+3 -2
app/Providers/AdditionalDuskServiceProvider.php
··· 5 5 6 6 namespace App\Providers; 7 7 8 - use App\Libraries\UserVerification; 9 8 use Illuminate\Support\ServiceProvider; 10 9 use Route; 11 10 ··· 19 18 public function boot() 20 19 { 21 20 Route::get('/_dusk/verify', function () { 22 - return UserVerification::fromCurrentRequest()->markVerifiedAndRespond(); 21 + \Session::instance()->markVerified(); 22 + 23 + return response(null, 204); 23 24 })->middleware('web'); 24 25 } 25 26 }
+16 -11
tests/Libraries/UserVerificationTest.php tests/Libraries/SessionVerification/ControllerTest.php
··· 5 5 6 6 declare(strict_types=1); 7 7 8 - namespace Tests\Libraries; 8 + namespace Tests\Libraries\SessionVerification; 9 9 10 10 use App\Libraries\Session\Store as SessionStore; 11 - use App\Libraries\UserVerification; 12 - use App\Libraries\UserVerificationState; 11 + use App\Libraries\SessionVerification; 13 12 use App\Models\LoginAttempt; 14 13 use App\Models\User; 15 14 use Tests\TestCase; 16 15 17 - class UserVerificationTest extends TestCase 16 + class ControllerTest extends TestCase 18 17 { 19 18 public function testIssue() 20 19 { ··· 29 28 $record = LoginAttempt::find('127.0.0.1'); 30 29 31 30 $this->assertTrue($record->containsUser($user, 'verify')); 32 - $this->assertFalse(UserVerification::fromCurrentRequest()->isDone()); 31 + $this->assertFalse(\Session::isVerified()); 33 32 } 34 33 35 34 public function testVerify() 36 35 { 37 36 $user = User::factory()->create(); 37 + $session = \Session::instance(); 38 38 39 39 $this 40 40 ->be($user) 41 + ->withPersistentSession($session) 41 42 ->get(route('account.edit')) 42 43 ->assertStatus(401) 43 44 ->assertViewIs('users.verify'); 44 45 45 - $key = session()->get('verification_key'); 46 + $key = SessionVerification\State::fromSession($session)->key; 46 47 47 48 $this 49 + ->withPersistentSession($session) 48 50 ->post(route('account.verify'), ['verification_key' => $key]) 49 51 ->assertSuccessful(); 50 52 51 53 $record = LoginAttempt::find('127.0.0.1'); 52 54 53 55 $this->assertFalse($record->containsUser($user, 'verify-mismatch:')); 54 - $this->assertTrue(UserVerification::fromCurrentRequest()->isDone()); 56 + $this->assertTrue($session->isVerified()); 55 57 } 56 58 57 59 public function testVerifyLink(): void ··· 67 69 ->assertStatus(401) 68 70 ->assertViewIs('users.verify'); 69 71 70 - $linkKey = $session->get('verification_link_key'); 72 + $linkKey = SessionVerification\State::fromSession($session)->linkKey; 71 73 72 74 $guestSession = SessionStore::findOrNew(); 73 75 $this ··· 78 80 $record = LoginAttempt::find('127.0.0.1'); 79 81 80 82 $this->assertFalse($record->containsUser($user, 'verify-mismatch:')); 81 - $this->assertTrue(UserVerificationState::load(['userId' => $user->getKey(), 'sessionId' => $sessionId])->isDone()); 83 + $this->assertTrue(SessionStore::findOrNew($sessionId)->isVerified()); 82 84 } 83 85 84 86 public function testVerifyLinkMismatch(): void ··· 100 102 ->get(route('account.verify', ['key' => 'invalid'])) 101 103 ->assertStatus(404); 102 104 103 - $this->assertFalse(UserVerificationState::load(['userId' => $user->getKey(), 'sessionId' => $sessionId])->isDone()); 105 + $this->assertFalse(SessionStore::findOrNew($sessionId)->isVerified()); 104 106 } 105 107 106 108 public function testVerifyMismatch() 107 109 { 108 110 $user = User::factory()->create(); 111 + $session = \Session::instance(); 109 112 110 113 $this 111 114 ->be($user) 115 + ->withPersistentSession($session) 112 116 ->get(route('account.edit')) 113 117 ->assertStatus(401) 114 118 ->assertViewIs('users.verify'); ··· 117 121 $this->assertFalse($record->containsUser($user, 'verify-mismatch:')); 118 122 119 123 $this 124 + ->withPersistentSession($session) 120 125 ->post(route('account.verify'), ['verification_key' => 'invalid']) 121 126 ->assertStatus(422); 122 127 123 128 $record = LoginAttempt::find('127.0.0.1'); 124 129 125 130 $this->assertTrue($record->containsUser($user, 'verify-mismatch:')); 126 - $this->assertFalse(UserVerification::fromCurrentRequest()->isDone()); 131 + $this->assertFalse($session->isVerified()); 127 132 } 128 133 }