validate([ 'grant_type' => ['required', 'in:password,refresh_token'], 'username' => ['required_if:grant_type,password', 'string'], 'password' => ['required_if:grant_type,password', 'string'], 'refresh_token' => ['required_if:grant_type,refresh_token', 'string'], 'scope' => ['sometimes', 'string'], ]); $scopes = isset($data['scope']) ? array_values(array_filter(explode(' ', $data['scope']))) : null; try { $token = $data['grant_type'] === 'password' ? $this->client->requestPasswordToken($data['username'], $data['password'], $scopes) : $this->client->refresh($data['refresh_token']); $identity = $this->client->identity($token->accessToken); } catch (PireonException $e) { // An identity server that failed to answer has said nothing about // these credentials, so it must not be reported as though it had. if (! $e->isRejection()) { return response()->json([ 'message' => __('pireon.server_unavailable'), ], Response::HTTP_SERVICE_UNAVAILABLE); } // Otherwise a validation error rather than the upstream body, so // we never echo details of the identity server back to a caller // probing for valid accounts. throw ValidationException::withMessages([ 'username' => [__('auth.failed')], ])->status($e->status === 401 ? 401 : 422); } if (! $identity->isUsable()) { return response()->json([ 'message' => $identity->isApproved() ? __('pireon.inactive') : __('pireon.pending_approval'), ], 403); } // Provision the local account now, so the very first API call after // this one already has roles and a user row to authorise against. $user = $this->sync->handle($identity); return response()->json([ 'access_token' => $token->accessToken, 'refresh_token' => $token->refreshToken, 'token_type' => $token->tokenType, 'expires_at' => $token->expiresAt?->toIso8601String(), 'scopes' => $token->scopes, 'user' => [ 'id' => $user->id, 'name' => $user->name, 'email' => $user->email, 'roles' => $user->roles->pluck('name')->values(), 'permissions' => $user->getAllPermissions()->pluck('name')->values(), ], ]); } }