* @brief Consumes the first line as a {"type":"auth","token":...} handshake before commands. */
| 647 | * @brief Consumes the first line as a {"type":"auth","token":...} handshake before commands. |
| 648 | */ |
| 649 | void API::Server::handleAuthHandshake(QTcpSocket* socket, |
| 650 | ConnectionState& state, |
| 651 | const QByteArray& data) |
| 652 | { |
| 653 | Q_ASSERT(socket); |
| 654 | |
| 655 | state.buffer.append(data); |
| 656 | if (state.buffer.size() > kMaxApiMessageBytes) { |
| 657 | disconnectClient( |
| 658 | socket, state, ErrorCode::ExecutionError, QStringLiteral("Authentication required")); |
| 659 | return; |
| 660 | } |
| 661 | |
| 662 | const int newlineIndex = state.buffer.indexOf('\n'); |
| 663 | if (newlineIndex < 0) |
| 664 | return; |
| 665 | |
| 666 | const QByteArray line = state.buffer.left(newlineIndex).trimmed(); |
| 667 | state.buffer.remove(0, newlineIndex + 1); |
| 668 | |
| 669 | bool ok = false; |
| 670 | QJsonParseError parseError; |
| 671 | const auto doc = QJsonDocument::fromJson(line, &parseError); |
| 672 | if (parseError.error == QJsonParseError::NoError && doc.isObject()) { |
| 673 | const auto obj = doc.object(); |
| 674 | if (obj.value(QStringLiteral("type")).toString() == QStringLiteral("auth")) { |
| 675 | const QByteArray provided = obj.value(QStringLiteral("token")).toString().toUtf8(); |
| 676 | ok = verifyToken(provided); |
| 677 | } |
| 678 | } |
| 679 | |
| 680 | if (!ok) { |
| 681 | if (++state.authAttempts >= kMaxAuthAttempts) { |
| 682 | qWarning() << "[API] Authentication failed:" << state.peerAddress << ":" << state.peerPort |
| 683 | << "- Disconnecting after" << state.authAttempts << "attempts"; |
| 684 | disconnectClient( |
| 685 | socket, state, ErrorCode::ExecutionError, QStringLiteral("Authentication failed")); |
| 686 | return; |
| 687 | } |
| 688 | |
| 689 | sendResponseToSocket(socket, |
| 690 | CommandResponse::makeError(QString(), |
| 691 | ErrorCode::ExecutionError, |
| 692 | QStringLiteral("Authentication required")) |
| 693 | .toJsonBytes()); |
| 694 | return; |
| 695 | } |
| 696 | |
| 697 | state.authenticated = true; |
| 698 | qInfo() << "[API] Client authenticated:" << state.peerAddress << ":" << state.peerPort; |
| 699 | |
| 700 | QJsonObject result; |
| 701 | result[QStringLiteral("authenticated")] = true; |
| 702 | sendResponseToSocket(socket, CommandResponse::makeSuccess(QString(), result).toJsonBytes()); |
| 703 | |
| 704 | if (!state.buffer.isEmpty()) { |
| 705 | const QByteArray pipelined = state.buffer; |
| 706 | state.buffer.clear(); |