diff --git a/duckdb b/duckdb index 1986445..a7b4053 160000 --- a/duckdb +++ b/duckdb @@ -1 +1 @@ -Subproject commit 19864453f7d0ed095256d848b46e7b8630989bac +Subproject commit a7b405351ed3c319c4989fac0653fcf6fdbf126e diff --git a/src/gsheets_auth.cpp b/src/gsheets_auth.cpp index b1483b6..d0b5881 100644 --- a/src/gsheets_auth.cpp +++ b/src/gsheets_auth.cpp @@ -6,6 +6,15 @@ #include "duckdb/main/extension_util.hpp" #include #include +#ifdef _WIN32 +#include +#include +#pragma comment(lib, "ws2_32.lib") +#else +#include +#include +#include +#endif namespace duckdb { @@ -104,40 +113,135 @@ namespace duckdb std::string InitiateOAuthFlow() { - // This is using the Web App OAuth flow, as I can't figure out desktop app flow. + const int PORT = 8765; // Define port constant const std::string client_id = "793766532675-rehqgocfn88h0nl88322ht6d1i12kl4e.apps.googleusercontent.com"; - const std::string redirect_uri = "https://duckdb-gsheets.com/oauth"; + const std::string redirect_uri = "http://localhost:" + std::to_string(PORT); const std::string auth_url = "https://accounts.google.com/o/oauth2/v2/auth"; + std::string access_token; + + // Create socket + int server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (server_fd < 0) { + throw IOException("Failed to create socket"); + } + + // Set socket options to allow reuse + int opt = 1; + if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { + close(server_fd); + throw IOException("Failed to set socket options"); + } + + // Bind to localhost:8765 + struct sockaddr_in address; + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons(PORT); + + if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { + close(server_fd); + throw IOException("Failed to bind to port " + std::to_string(PORT)); + } + + if (listen(server_fd, 1) < 0) { + close(server_fd); + throw IOException("Failed to listen on socket"); + } - // Generate a random state for CSRF protection + // Generate state for CSRF protection std::string state = generate_random_string(10); - + + // Construct auth URL std::string auth_request_url = auth_url + "?client_id=" + client_id + "&redirect_uri=" + redirect_uri + "&response_type=token" + "&scope=https://www.googleapis.com/auth/spreadsheets" + "&state=" + state; - - // Instruct the user to visit the URL and grant permission - std::cout << "Visit the below URL to authorize DuckDB GSheets" << std::endl << std::endl; - std::cout << auth_request_url << std::endl << std::endl; - - // Open the URL in the user's default browser + // Open browser #ifdef _WIN32 + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + throw std::runtime_error("Failed to initialize Winsock"); + } system(("start \"\" \"" + auth_request_url + "\"").c_str()); #elif __APPLE__ system(("open \"" + auth_request_url + "\"").c_str()); #elif __linux__ system(("xdg-open \"" + auth_request_url + "\"").c_str()); #endif - - - std::cout << "After granting permission, enter the token: "; - std::string access_token; - std::cin >> access_token; - + + std::cout << std::endl << "Waiting for Login via Browser..." << std::endl << std::endl; + + // Accept first connection (GET request) + int client_socket; + if ((client_socket = accept(server_fd, nullptr, nullptr)) < 0) { + close(server_fd); + throw IOException("Failed to accept connection"); + } + + // Read initial request + char buffer[4096] = {0}; + ssize_t bytes_read = read(client_socket, buffer, sizeof(buffer)); + + // Send response to browser + std::string response = "HTTP/1.1 200 OK\r\n" + "Access-Control-Allow-Origin: *\r\n" + "Access-Control-Allow-Methods: POST, OPTIONS\r\n" + "Access-Control-Allow-Headers: Content-Type\r\n" + "Content-Type: text/html\r\n\r\n" + ""; + write(client_socket, response.c_str(), response.length()); + close(client_socket); + + // Accept second connection (POST request) + if ((client_socket = accept(server_fd, nullptr, nullptr)) < 0) { + close(server_fd); + throw IOException("Failed to accept second connection"); + } + + // Read the POST request + memset(buffer, 0, sizeof(buffer)); + bytes_read = read(client_socket, buffer, sizeof(buffer)); + std::string token_request(buffer); + + // Send response to POST request + std::string post_response = "HTTP/1.1 200 OK\r\n" + "Access-Control-Allow-Origin: *\r\n" + "Content-Length: 0\r\n\r\n"; + write(client_socket, post_response.c_str(), post_response.length()); + + // Extract token from POST body + size_t body_start = token_request.find("\r\n\r\n"); + if (body_start != std::string::npos) { + access_token = token_request.substr(body_start + 4); + } + + // Clean up + close(client_socket); + close(server_fd); + + if (access_token.empty()) { + throw IOException("Failed to obtain access token"); + } + return access_token; } + #ifdef _WIN32 + WSACleanup(); + #endif + } // namespace duckdb