From dbfeb9e27fe27ca2aee5ec08492fa8a0108e77fe Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Thu, 8 Jan 2026 11:33:34 +0300 Subject: [PATCH] merge develop --- .pre-commit-config.yaml | 8 +- CITATION.cff | 4 +- backend/tools/download_linux.sh | 117 +++++++----------- .../storages/models/GoogleDriveStorage.ts | 1 + .../EditGoogleDriveStorageComponent.tsx | 29 ++++- .../ShowGoogleDriveStorageComponent.tsx | 5 + frontend/src/pages/OauthStorageComponent.tsx | 75 +++++++++-- 7 files changed, 149 insertions(+), 90 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8821ca1..659269b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,14 +6,14 @@ repos: hooks: - id: frontend-format name: Frontend Format (Prettier) - entry: powershell -Command "cd frontend; npm run format" + entry: bash -c "cd frontend && npm run format" language: system files: ^frontend/.*\.(ts|tsx|js|jsx|json|css|md)$ pass_filenames: false - id: frontend-lint name: Frontend Lint (ESLint) - entry: powershell -Command "cd frontend; npm run lint" + entry: bash -c "cd frontend && npm run lint" language: system files: ^frontend/.*\.(ts|tsx|js|jsx)$ pass_filenames: false @@ -23,7 +23,7 @@ repos: hooks: - id: backend-format-and-lint name: Backend Format & Lint (golangci-lint) - entry: powershell -Command "cd backend; golangci-lint fmt; golangci-lint run" + entry: bash -c "cd backend && golangci-lint run --fix" language: system files: ^backend/.*\.go$ - pass_filenames: false \ No newline at end of file + pass_filenames: false diff --git a/CITATION.cff b/CITATION.cff index bb73027..c49a8f0 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -32,5 +32,5 @@ keywords: - mongodb - mariadb license: Apache-2.0 -version: 2.18.1 -date-released: "2025-12-28" +version: 2.21.0 +date-released: "2026-01-05" diff --git a/backend/tools/download_linux.sh b/backend/tools/download_linux.sh index 5bf8779..a318243 100644 --- a/backend/tools/download_linux.sh +++ b/backend/tools/download_linux.sh @@ -9,13 +9,13 @@ echo "Installing PostgreSQL, MySQL, MariaDB and MongoDB client tools for Linux ( echo # Check if running on supported system -if ! command -v apt-get > /dev/null 2>&1; then +if ! command -v apt-get &> /dev/null; then echo "Error: This script requires apt-get (Debian/Ubuntu-like system)" exit 1 fi # Check if running as root or with sudo -if [ $EUID -eq 0 ]; then +if [[ $EUID -eq 0 ]]; then SUDO="" else SUDO="sudo" @@ -107,12 +107,6 @@ for version in $mysql_versions; do version_dir="$MYSQL_DIR/mysql-$version" mkdir -p "$version_dir/bin" - # Skip if already exists - if [ -f "$version_dir/bin/mysqldump" ]; then - echo " MySQL $version already installed, skipping..." - continue - fi - # Download MySQL client tools from official CDN # Note: 5.7 is in Downloads, 8.0, 8.4 specific versions are in archives, 9.5 is in MySQL-9.5 case $version in @@ -138,14 +132,11 @@ for version in $mysql_versions; do wget -q "$MYSQL_URL" -O "mysql-$version.tar.gz" || wget -q "$MYSQL_URL" -O "mysql-$version.tar.xz" echo " Extracting MySQL $version..." - case "$MYSQL_URL" in - *.xz) - tar -xJf "mysql-$version.tar.xz" 2>/dev/null || tar -xJf "mysql-$version.tar.gz" 2>/dev/null - ;; - *) - tar -xzf "mysql-$version.tar.gz" 2>/dev/null || tar -xzf "mysql-$version.tar.xz" 2>/dev/null - ;; - esac + if [[ "$MYSQL_URL" == *.xz ]]; then + tar -xJf "mysql-$version.tar.xz" 2>/dev/null || tar -xJf "mysql-$version.tar.gz" 2>/dev/null + else + tar -xzf "mysql-$version.tar.gz" 2>/dev/null || tar -xzf "mysql-$version.tar.xz" 2>/dev/null + fi # Find extracted directory EXTRACTED_DIR=$(ls -d mysql-*/ 2>/dev/null | head -1) @@ -184,7 +175,12 @@ echo "Installing MariaDB client tools to: $MARIADB_DIR" # Install dependencies $SUDO apt-get install -y -qq apt-transport-https curl -# MariaDB versions to install +# MariaDB versions to install with their URLs +declare -A MARIADB_URLS=( + ["10.6"]="https://archive.mariadb.org/mariadb-10.6.21/bintar-linux-systemd-x86_64/mariadb-10.6.21-linux-systemd-x86_64.tar.gz" + ["12.1"]="https://archive.mariadb.org/mariadb-12.1.2/bintar-linux-systemd-x86_64/mariadb-12.1.2-linux-systemd-x86_64.tar.gz" +) + mariadb_versions="10.6 12.1" for version in $mariadb_versions; do @@ -199,19 +195,7 @@ for version in $mariadb_versions; do continue fi - # Get URL based on version - case "$version" in - "10.6") - url="https://archive.mariadb.org/mariadb-10.6.21/bintar-linux-systemd-x86_64/mariadb-10.6.21-linux-systemd-x86_64.tar.gz" - ;; - "12.1") - url="https://archive.mariadb.org/mariadb-12.1.2/bintar-linux-systemd-x86_64/mariadb-12.1.2-linux-systemd-x86_64.tar.gz" - ;; - *) - echo " Warning: Unknown MariaDB version $version" - continue - ;; - esac + url=${MARIADB_URLS[$version]} TEMP_DIR="/tmp/mariadb_install_$version" mkdir -p "$TEMP_DIR" @@ -254,48 +238,43 @@ mkdir -p "$MONGODB_DIR/bin" echo "Installing MongoDB Database Tools to: $MONGODB_DIR" -# Skip if already installed -if [ -f "$MONGODB_DIR/bin/mongodump" ] && [ -L "$MONGODB_DIR/bin/mongodump" ]; then - echo "MongoDB Database Tools already installed, skipping..." +# MongoDB Database Tools are backward compatible - single version supports all servers (4.0-8.0) +# Detect architecture +ARCH=$(uname -m) +if [ "$ARCH" = "x86_64" ]; then + MONGODB_TOOLS_URL="https://fastdl.mongodb.org/tools/db/mongodb-database-tools-debian12-x86_64-100.10.0.deb" +elif [ "$ARCH" = "aarch64" ]; then + MONGODB_TOOLS_URL="https://fastdl.mongodb.org/tools/db/mongodb-database-tools-debian12-aarch64-100.10.0.deb" else - # MongoDB Database Tools are backward compatible - single version supports all servers (4.0-8.0) - # Detect architecture - ARCH=$(uname -m) - if [ "$ARCH" = "x86_64" ]; then - MONGODB_TOOLS_URL="https://fastdl.mongodb.org/tools/db/mongodb-database-tools-debian12-x86_64-100.10.0.deb" - elif [ "$ARCH" = "aarch64" ]; then - MONGODB_TOOLS_URL="https://fastdl.mongodb.org/tools/db/mongodb-database-tools-debian12-aarch64-100.10.0.deb" - else - echo "Warning: Unsupported architecture $ARCH for MongoDB Database Tools" - MONGODB_TOOLS_URL="" + echo "Warning: Unsupported architecture $ARCH for MongoDB Database Tools" + MONGODB_TOOLS_URL="" +fi + +if [ -n "$MONGODB_TOOLS_URL" ]; then + TEMP_DIR="/tmp/mongodb_install" + mkdir -p "$TEMP_DIR" + cd "$TEMP_DIR" + + echo "Downloading MongoDB Database Tools..." + wget -q "$MONGODB_TOOLS_URL" -O mongodb-database-tools.deb || { + echo "Warning: Could not download MongoDB Database Tools" + cd - >/dev/null + rm -rf "$TEMP_DIR" + } + + if [ -f "mongodb-database-tools.deb" ]; then + echo "Installing MongoDB Database Tools..." + $SUDO dpkg -i mongodb-database-tools.deb 2>/dev/null || $SUDO apt-get install -f -y -qq + + # Create symlinks to tools directory + ln -sf /usr/bin/mongodump "$MONGODB_DIR/bin/mongodump" + ln -sf /usr/bin/mongorestore "$MONGODB_DIR/bin/mongorestore" + + echo "MongoDB Database Tools installed successfully" fi - if [ -n "$MONGODB_TOOLS_URL" ]; then - TEMP_DIR="/tmp/mongodb_install" - mkdir -p "$TEMP_DIR" - cd "$TEMP_DIR" - - echo "Downloading MongoDB Database Tools..." - if ! wget -q "$MONGODB_TOOLS_URL" -O mongodb-database-tools.deb; then - echo "Warning: Could not download MongoDB Database Tools" - cd - >/dev/null - rm -rf "$TEMP_DIR" - else - if [ -f "mongodb-database-tools.deb" ]; then - echo "Installing MongoDB Database Tools..." - $SUDO dpkg -i mongodb-database-tools.deb 2>/dev/null || $SUDO apt-get install -f -y -qq - - # Create symlinks to tools directory - ln -sf /usr/bin/mongodump "$MONGODB_DIR/bin/mongodump" - ln -sf /usr/bin/mongorestore "$MONGODB_DIR/bin/mongorestore" - - echo "MongoDB Database Tools installed successfully" - fi - - cd - >/dev/null - rm -rf "$TEMP_DIR" - fi - fi + cd - >/dev/null + rm -rf "$TEMP_DIR" fi echo diff --git a/frontend/src/entity/storages/models/GoogleDriveStorage.ts b/frontend/src/entity/storages/models/GoogleDriveStorage.ts index 992aa7d..0d158ac 100644 --- a/frontend/src/entity/storages/models/GoogleDriveStorage.ts +++ b/frontend/src/entity/storages/models/GoogleDriveStorage.ts @@ -2,4 +2,5 @@ export interface GoogleDriveStorage { clientId: string; clientSecret: string; tokenJson?: string; + useLocalRedirect?: boolean; } diff --git a/frontend/src/features/storages/ui/edit/storages/EditGoogleDriveStorageComponent.tsx b/frontend/src/features/storages/ui/edit/storages/EditGoogleDriveStorageComponent.tsx index 5e87236..2bf45ee 100644 --- a/frontend/src/features/storages/ui/edit/storages/EditGoogleDriveStorageComponent.tsx +++ b/frontend/src/features/storages/ui/edit/storages/EditGoogleDriveStorageComponent.tsx @@ -1,4 +1,4 @@ -import { Button, Input } from 'antd'; +import { Button, Checkbox, Input } from 'antd'; import { GOOGLE_DRIVE_OAUTH_REDIRECT_URL } from '../../../../../constants'; import type { Storage } from '../../../../../entity/storages'; @@ -16,7 +16,10 @@ export function EditGoogleDriveStorageComponent({ storage, setStorage, setUnsave return; } - const redirectUri = GOOGLE_DRIVE_OAUTH_REDIRECT_URL; + const localRedirectUri = `${window.location.origin}/storages/google-oauth`; + const useLocal = storage.googleDriveStorage.useLocalRedirect; + const redirectUri = useLocal ? localRedirectUri : GOOGLE_DRIVE_OAUTH_REDIRECT_URL; + const clientId = storage.googleDriveStorage.clientId; const scope = 'https://www.googleapis.com/auth/drive.file'; const originUrl = `${window.location.origin}/storages/google-oauth`; @@ -92,6 +95,28 @@ export function EditGoogleDriveStorageComponent({ storage, setStorage, setUnsave /> +
+
+ { + if (!storage?.googleDriveStorage) return; + + setStorage({ + ...storage, + googleDriveStorage: { + ...storage.googleDriveStorage, + useLocalRedirect: e.target.checked, + }, + }); + setUnsaved(); + }} + disabled={!!storage?.googleDriveStorage?.tokenJson} + > + Use local redirect (more secure) + +
+ {storage?.googleDriveStorage?.tokenJson && ( <>
diff --git a/frontend/src/features/storages/ui/show/storages/ShowGoogleDriveStorageComponent.tsx b/frontend/src/features/storages/ui/show/storages/ShowGoogleDriveStorageComponent.tsx index 66617d2..17c16b7 100644 --- a/frontend/src/features/storages/ui/show/storages/ShowGoogleDriveStorageComponent.tsx +++ b/frontend/src/features/storages/ui/show/storages/ShowGoogleDriveStorageComponent.tsx @@ -19,6 +19,11 @@ export function ShowGoogleDriveStorageComponent({ storage }: Props) { {`*************`}
+
+
Use local redirect
+ {`*************`} +
+
User Token
{`*************`} diff --git a/frontend/src/pages/OauthStorageComponent.tsx b/frontend/src/pages/OauthStorageComponent.tsx index ff16ee6..2dbefde 100644 --- a/frontend/src/pages/OauthStorageComponent.tsx +++ b/frontend/src/pages/OauthStorageComponent.tsx @@ -18,6 +18,8 @@ export function OauthStorageComponent() { const { clientId, clientSecret } = oauthDto.storage.googleDriveStorage; const { authCode } = oauthDto; + const redirectUri = oauthDto.redirectUrl || GOOGLE_DRIVE_OAUTH_REDIRECT_URL; + try { // Exchange authorization code for access token const response = await fetch('https://oauth2.googleapis.com/token', { @@ -29,13 +31,16 @@ export function OauthStorageComponent() { code: authCode, client_id: clientId, client_secret: clientSecret, - redirect_uri: GOOGLE_DRIVE_OAUTH_REDIRECT_URL, + redirect_uri: redirectUri, grant_type: 'authorization_code', }), }); if (!response.ok) { - throw new Error(`OAuth exchange failed: ${response.statusText}`); + const errorData = await response.json(); + throw new Error( + errorData.error_description || `OAuth exchange failed: ${response.statusText}`, + ); } const tokenData = await response.json(); @@ -44,27 +49,71 @@ export function OauthStorageComponent() { setStorage(oauthDto.storage); } catch (error) { alert(`Failed to exchange OAuth code: ${error}`); + // Return to home if exchange fails + setTimeout(() => { + window.location.href = '/'; + }, 3000); } }; - useEffect(() => { - const oauthDtoParam = new URLSearchParams(window.location.search).get('oauthDto'); - if (!oauthDtoParam) { - alert('OAuth param not found'); - return; - } - - const decodedParam = decodeURIComponent(oauthDtoParam); - const oauthDto: StorageOauthDto = JSON.parse(decodedParam); - + /** + * Helper to validate the DTO and start the exchange process + */ + const processOauthDto = (oauthDto: StorageOauthDto) => { if (oauthDto.storage.type === StorageType.GOOGLE_DRIVE) { if (!oauthDto.storage.googleDriveStorage) { - alert('Google Drive storage not found'); + alert('Google Drive storage configuration not found in DTO'); return; } exchangeGoogleOauthCode(oauthDto); + } else { + alert('Unsupported storage type for OAuth'); } + }; + + useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + + // Attempt 1: Check for the 'oauthDto' param (Third-party/Legacy way) + const oauthDtoParam = urlParams.get('oauthDto'); + if (oauthDtoParam) { + try { + const decodedParam = decodeURIComponent(oauthDtoParam); + const oauthDto: StorageOauthDto = JSON.parse(decodedParam); + processOauthDto(oauthDto); + return; + } catch (e) { + console.error('Error parsing oauthDto parameter:', e); + alert('Malformed OAuth parameter received'); + return; + } + } + + // Attempt 2: Check for 'code' and 'state' (Direct Google/Local way) + const code = urlParams.get('code'); + const state = urlParams.get('state'); + + if (code && state) { + try { + // The 'state' parameter contains our stringified StorageOauthDto + const decodedState = decodeURIComponent(state); + const oauthDto: StorageOauthDto = JSON.parse(decodedState); + + // Inject the authorization code received from Google + oauthDto.authCode = code; + + processOauthDto(oauthDto); + return; + } catch (e) { + console.error('Error parsing OAuth state:', e); + alert('OAuth state parameter is invalid'); + return; + } + } + + // Attempt 3: No valid parameters found + alert('OAuth param not found. Ensure the redirect URL is configured correctly.'); }, []); if (!storage) {