staffHolding($request->username); if ($staff === null || ! Hash::check($request->password, $staff->password)) { throw ValidationException::withMessages(['email' => [self::REJECTED]]); } // Deliberately reached only once the pair has been proven, so nobody // can spend an invitation by guessing at somebody else's password. $this->redeemPendingInvitation($request, $staff); $minted = $staff->createToken($request->device_name); return response()->json([ 'type' => 'Bearer', 'token' => $minted->plainTextToken, ]); } /** * Drop the token that carried this request. * * Quirk kept as is: exactly one token is revoked, never the account's * whole set, so the caller's other devices stay signed in. And a caller * authenticated by session cookie rather than a bearer token holds a * transient token that has nothing to delete, so that request errors out * instead of closing the session. */ public function logout(Request $request) { $carrier = $request->user()->currentAccessToken(); $carrier->delete(); return response()->json(['success' => true]); } /** * Report whether the caller is signed in. * * The bare boolean body is deliberate: the route already sits behind the * API guard, so the SPA reads this purely as a liveness ping. */ public function check() { return Auth::check(); } /** * Find the staff account holding the submitted address. * * The comparison runs against the lower-cased column so that capitalising * an address differently from how it was stored still gets the account * in, on every database engine the app supports. */ private function staffHolding($submitted): ?User { return User::query() ->whereRaw('LOWER(email) = ?', [strtolower($submitted)]) ->first(); } /** * Accept an invitation carried alongside the credentials, when one is * still live. * * A token that is unknown, already spent or past its expiry is passed * over in silence. Sign-in itself is never held up by it. */ private function redeemPendingInvitation(LoginRequest $request, User $staff): void { $offered = $request->input('invitation_token'); if (! $offered) { return; } $invitation = CompanyInvitation::query() ->where('token', $offered) ->pending() ->first(); if ($invitation !== null) { app(InvitationService::class)->accept($invitation, $staff); } } }