diff --git a/PROJECTS/api-security-scanner/.github/workflows/eslint-check.yml b/.github/workflows/eslint-check.yml similarity index 87% rename from PROJECTS/api-security-scanner/.github/workflows/eslint-check.yml rename to .github/workflows/eslint-check.yml index 67bc1bf3..dbef3d30 100644 --- a/PROJECTS/api-security-scanner/.github/workflows/eslint-check.yml +++ b/.github/workflows/eslint-check.yml @@ -4,11 +4,11 @@ on: pull_request: branches: [ '*' ] paths: - - 'frontend/**/*.ts' - - 'frontend/**/*.tsx' - - 'frontend/eslint.config.js' - - 'frontend/tsconfig*.json' - - 'frontend/package.json' + - 'PROJECTS/api-security-scanner/frontend/**/*.ts' + - 'PROJECTS/api-security-scanner/frontend/**/*.tsx' + - 'PROJECTS/api-security-scanner/frontend/eslint.config.js' + - 'PROJECTS/api-security-scanner/frontend/tsconfig*.json' + - 'PROJECTS/api-security-scanner/frontend/package.json' - '.github/workflows/eslint-check.yml' jobs: @@ -22,7 +22,7 @@ jobs: defaults: run: - working-directory: frontend + working-directory: PROJECTS/api-security-scanner/frontend steps: - name: Checkout code @@ -33,7 +33,7 @@ jobs: with: node-version: '20' cache: 'npm' - cache-dependency-path: frontend/package-lock.json + cache-dependency-path: PROJECTS/api-security-scanner/frontend/package-lock.json - name: Install dependencies run: npm install @@ -106,7 +106,7 @@ jobs: uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.pull_request.number }} - body-path: frontend/eslint-report.md + body-path: PROJECTS/api-security-scanner/frontend/eslint-report.md edit-mode: replace - name: Exit with proper code diff --git a/PROJECTS/api-security-scanner/.github/workflows/lint.yml b/.github/workflows/lint.yml similarity index 94% rename from PROJECTS/api-security-scanner/.github/workflows/lint.yml rename to .github/workflows/lint.yml index 6fb5eb95..eff214e1 100755 --- a/PROJECTS/api-security-scanner/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,37 +12,37 @@ jobs: lint: name: Run Linters runs-on: ubuntu-latest - + permissions: pull-requests: write contents: read - + defaults: run: - working-directory: backend - + working-directory: PROJECTS/api-security-scanner/backend + steps: - name: Checkout code uses: actions/checkout@v4 - + - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.12' - + - name: Cache pip dependencies uses: actions/cache@v4 with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('backend/pyproject.toml') }} + key: ${{ runner.os }}-pip-${{ hashFiles('PROJECTS/api-security-scanner/backend/pyproject.toml') }} restore-keys: | ${{ runner.os }}-pip- - + - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e ".[dev]" - + - name: Run pylint id: pylint run: | @@ -56,7 +56,7 @@ jobs: fi cat pylint-output.txt continue-on-error: true - + - name: Run ruff id: ruff run: | @@ -70,7 +70,7 @@ jobs: fi cat ruff-output.txt continue-on-error: true - + - name: Run mypy id: mypy run: | @@ -84,7 +84,7 @@ jobs: fi cat mypy-output.txt continue-on-error: true - + - name: Create Lint Summary id: create_summary if: github.event_name == 'pull_request' @@ -92,7 +92,7 @@ jobs: { echo '## 🔍 Lint & Type Check Results' echo '' - + # Pylint Status if [[ "${{ env.PYLINT_PASSED }}" == "true" ]]; then echo '### ✅ Pylint: **Passed**' @@ -107,7 +107,7 @@ jobs: echo '' fi echo '' - + # Ruff Status if [[ "${{ env.RUFF_PASSED }}" == "true" ]]; then echo '### ✅ Ruff: **Passed**' @@ -122,7 +122,7 @@ jobs: echo '' fi echo '' - + # Mypy Status if [[ "${{ env.MYPY_PASSED }}" == "true" ]]; then echo '### ✅ Mypy: **Passed**' @@ -137,7 +137,7 @@ jobs: echo '' fi echo '' - + # Overall Summary if [[ "${{ env.PYLINT_PASSED }}" == "true" ]] && [[ "${{ env.RUFF_PASSED }}" == "true" ]] && [[ "${{ env.MYPY_PASSED }}" == "true" ]]; then echo '---' @@ -149,11 +149,11 @@ jobs: echo '' echo '' } > ../lint-report.md - + - name: Post PR Comment if: github.event_name == 'pull_request' uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.pull_request.number }} - body-path: lint-report.md + body-path: PROJECTS/api-security-scanner/lint-report.md comment-marker: lint-check-comment-marker diff --git a/PROJECTS/api-security-scanner/.github/workflows/typescript-check.yml b/.github/workflows/typescript-check.yml similarity index 89% rename from PROJECTS/api-security-scanner/.github/workflows/typescript-check.yml rename to .github/workflows/typescript-check.yml index 51a21615..fe81c403 100644 --- a/PROJECTS/api-security-scanner/.github/workflows/typescript-check.yml +++ b/.github/workflows/typescript-check.yml @@ -4,10 +4,10 @@ on: pull_request: branches: [ '*' ] paths: - - 'frontend/**/*.ts' - - 'frontend/**/*.tsx' - - 'frontend/tsconfig*.json' - - 'frontend/package.json' + - 'PROJECTS/api-security-scanner/frontend/**/*.ts' + - 'PROJECTS/api-security-scanner/frontend/**/*.tsx' + - 'PROJECTS/api-security-scanner/frontend/tsconfig*.json' + - 'PROJECTS/api-security-scanner/frontend/package.json' - '.github/workflows/typescript-check.yml' jobs: @@ -21,7 +21,7 @@ jobs: defaults: run: - working-directory: frontend + working-directory: PROJECTS/api-security-scanner/frontend steps: - name: Checkout code @@ -32,7 +32,7 @@ jobs: with: node-version: '20' cache: 'npm' - cache-dependency-path: frontend/package-lock.json + cache-dependency-path: PROJECTS/api-security-scanner/frontend/package-lock.json - name: Install dependencies run: | @@ -115,7 +115,7 @@ jobs: uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.pull_request.number }} - body-path: frontend/typescript-report.md + body-path: PROJECTS/api-security-scanner/frontend/typescript-report.md edit-mode: replace - name: Exit with proper code diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..fd4bcbfb --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,65 @@ +repos: + # Python Backend Checks + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.4 + hooks: + - id: ruff + name: ruff check (backend) + args: [--fix, --exit-non-zero-on-fix] + files: ^PROJECTS/api-security-scanner/backend/ + exclude: ^PROJECTS/api-security-scanner/backend/(\.venv|__pycache__|\.pytest_cache)/ + + - repo: local + hooks: + - id: yapf + name: yapf format (backend) + entry: bash -c 'cd PROJECTS/api-security-scanner/backend && yapf -i -r -vv models/ repositories/ schemas/ scanners/ core/ factory/' + language: system + types: [python] + files: ^PROJECTS/api-security-scanner/backend/ + pass_filenames: false + + - id: mypy + name: mypy type check (backend) + entry: bash -c 'cd PROJECTS/api-security-scanner/backend && mypy .' + language: system + types: [python] + files: ^PROJECTS/api-security-scanner/backend/ + pass_filenames: false + + - id: pylint + name: pylint check (backend) + entry: bash -c 'cd PROJECTS/api-security-scanner/backend && pylint **/*.py' + language: system + types: [python] + files: ^PROJECTS/api-security-scanner/backend/ + pass_filenames: false + + # Frontend TypeScript/ESLint Checks + - id: eslint + name: eslint check (frontend) + entry: bash -c 'cd PROJECTS/api-security-scanner/frontend && npm run lint:eslint' + language: system + types_or: [ts, tsx, javascript, jsx] + files: ^PROJECTS/api-security-scanner/frontend/src/ + pass_filenames: false + + - id: typescript + name: TypeScript type check (frontend) + entry: bash -c 'cd PROJECTS/api-security-scanner/frontend && npm run lint:types' + language: system + types_or: [ts, tsx] + files: ^PROJECTS/api-security-scanner/frontend/src/ + pass_filenames: false + + # General File Checks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-json + - id: check-merge-conflict + - id: check-added-large-files + args: ['--maxkb=1000'] diff --git a/PROJECTS/api-security-scanner/.gitignore b/PROJECTS/api-security-scanner/.gitignore index 49a7792a..71df0cb6 100644 --- a/PROJECTS/api-security-scanner/.gitignore +++ b/PROJECTS/api-security-scanner/.gitignore @@ -1,5 +1,5 @@ # ⒸAngelaMos | 2025 -# Gitignore - This is where you list files/folders that +# Gitignore - This is where you list files/folders that # should not be publicly commited to the remote repository # Python diff --git a/PROJECTS/api-security-scanner/backend/Makefile b/PROJECTS/api-security-scanner/backend/Makefile index c15582b9..8927fe1b 100644 --- a/PROJECTS/api-security-scanner/backend/Makefile +++ b/PROJECTS/api-security-scanner/backend/Makefile @@ -41,17 +41,17 @@ help: @echo "\033[1;4;91mSyntax Checking (Ruff):\033[0m" @echo " \033[92mmake \033[36mqa\033[0m \033[94m- Check all code for syntax issues\033[0m" @echo "\033[1;4;91mSyntax Fixing (Ruff):\033[0m" - @echo " \033[92mmake \033[36mqa\033[0m \033[94m- Fix all code of syntax issues\033[0m" + @echo " \033[92mmake \033[36mqa\033[0m \033[94m- Fix all code of syntax issues\033[0m" @echo "" @echo "\033[1;4;94mType Checking (mypy):\033[0m" @echo " \033[92mmake \033[36mqa\033[0m \033[94m- Type check all code\033[0m" @echo "\033[1;4;94mFormat:\033[0m" - @echo " \033[92mmake \033[36mqa\033[0m \033[94m- Format code\033[0m" + @echo " \033[92mmake \033[36mqa\033[0m \033[94m- Format code\033[0m" @echo "" @echo "\033[1;4;92mUtilities:\033[0m" @echo " \033[92mmake \033[36mclean\033[0m \033[94m- Remove all cache files\033[0m" @echo " \033[92mmake \033[36mtree\033[0m \033[94m- Display the file tree\033[0m" - @echo " \033[92mmake \033[36mTODO\033[0m \033[94m- Find All TODO \033[0m" + @echo " \033[92mmake \033[36mTODO\033[0m \033[94m- Find All TODO \033[0m" @echo "" @echo "\033[1;93mCurrent default domain: \033[1;5;95m$(DOMAIN)\033[0m" @echo "" @@ -59,7 +59,7 @@ help: @echo " \033[91maccount\033[0m, \033[92mcommunity\033[0m, \033[93mcontent\033[0m, \033[94mfreemium\033[0m, \033[95mgames\033[0m," @echo " \033[96mmarketing\033[0m, \033[91mprogression\033[0m, \033[92mshop\033[0m, \033[93mtesting\033[0m, \033[94mtools\033[0m" @echo "" - @echo "\033[95m ------------------------------" + @echo "\033[95m ------------------------------" @echo "\033[34m ⣿⣿⣿⡷⠊⡢⡹⣦⡑⢂⢕⢂⢕⢂⢕⢂⠕⠔⠌⠝⠛⠶⠶⢶⣦⣄⢂⢕⢂⢕" @echo "\033[34m ⣿⣿⠏⣠⣾⣦⡐⢌⢿⣷⣦⣅⡑⠕⠡⠐⢿⠿⣛⠟⠛⠛⠛⠛⠡⢷⡈⢂⢕⢂" @echo "\033[34m ⠟⣡⣾⣿⣿⣿⣿⣦⣑⠝⢿⣿⣿⣿⣿⣿⡵⢁⣤⣶⣶⣿⢿⢿⢿⡟⢻⣤⢑⢂" @@ -70,7 +70,7 @@ help: @echo "\033[32m ⠣⡁⠹⡪⡪⡪⡪⣪⣾⣿⣿⣿⣿⠋⠐⢉⢍⢄⢌⠻⣿⣿⣿⣿⣿⣿⣿⣿⠏⠈" @echo "\033[32m ⡣⡘⢄⠙⣾⣾⣾⣿⣿⣿⣿⣿⣿⡀⢐⢕⢕⢕⢕⢕⡘⣿⣿⣿⣿⣿⣿⠏⠠⠈" @echo "\033[32m ⠌⢊⢂⢣⠹⣿⣿⣿⣿⣿⣿⣿⣿⣧⢐⢕⢕⢕⢕⢕⢅⣿⣿⣿⣿⡿⢋⢜⠠⠈" - @echo "\033[95m ------------------------------" + @echo "\033[95m ------------------------------" @echo "\033[0m" @@ -104,20 +104,20 @@ check: @ruff check . $(call ASCII) - + # ==================== Syntax Checking (Ruff) ==================== fix: @echo "\033[1;3;4;96m======== ruff fix All Code ========\033[0m" @ruff check . --fix $(call ASCII) - + # ======================= In Depth Linting (pylint)======================= pylint: @echo "\033[1;3;4;96m======== Linting All Code ========\033[0m" @pylint . $(call ASCII) - + # ======================= Type Checking (mypy) ======================= mypy: @echo "\033[1;3;4;96m======== Type Checking All Code ========\033[0m" @@ -146,9 +146,8 @@ tree: @echo "\033[1;3;4;96m======== Creating Tree ========\033[0m" @tree -I 'node_modules|.git|*.log|dist|build|*.cache|certgames.egg-info|context|.venv|*.env|qa_context' $(call ASCII) - + TODO: @echo "\033[1;3;4;96m======== Looking for TODO's ========\033[0m" @find . -name "*.py" -print0 | xargs -0 pylint --disable=all --enable=W0511 --msg-template='{path}:{line}:{column}: {msg_id}: {msg} ({symbol})' || true $(call ASCII) - diff --git a/PROJECTS/api-security-scanner/backend/__init__.py b/PROJECTS/api-security-scanner/backend/__init__.py index 3378af5a..1708f30b 100644 --- a/PROJECTS/api-security-scanner/backend/__init__.py +++ b/PROJECTS/api-security-scanner/backend/__init__.py @@ -1,5 +1,5 @@ """ -ⒸAngelaMos | CarterPerez-dev +ⒸAngelaMos | CarterPerez-dev ⒸCertGames.com | 2025 ---- API Security Scanner @@ -15,6 +15,6 @@ API Security Scanner ⣿⣿⣿⣿⣿⣿⣿⣿⠄⣴⣿⣶⣄⠄⣴⣶⠄⢀⣾⣿⣿⣿⣿⣿⣿⠃⠄⠄ ⠈⠻⣿⣿⣿⣿⣿⣿⡄⢻⣿⣿⣿⠄⣿⣿⡀⣾⣿⣿⣿⣿⣛⠛⠁ ⠄⠄⠈⠛⢿⣿⣿⣿⠁⠞⢿⣿⣿⡄⢿⣿⡇⣸⣿⣿⠿⠛⠁⠄ -⠄⠄⠄⠄⠄⠉⠻⣿⣿⣾⣦⡙⠻⣷⣾⣿⠃⠿⠋⠁⠄ +⠄⠄⠄⠄⠄⠉⠻⣿⣿⣾⣦⡙⠻⣷⣾⣿⠃⠿⠋⠁⠄ """ diff --git a/PROJECTS/api-security-scanner/backend/config.py b/PROJECTS/api-security-scanner/backend/config.py index 48a157fa..e0c1d923 100644 --- a/PROJECTS/api-security-scanner/backend/config.py +++ b/PROJECTS/api-security-scanner/backend/config.py @@ -16,9 +16,7 @@ class Settings(BaseSettings): """ model_config = SettingsConfigDict( - env_file = "../.env", - env_file_encoding = "utf-8", - case_sensitive = True + env_file="../.env", env_file_encoding="utf-8", case_sensitive=True ) # Application metadata @@ -86,9 +84,7 @@ class Settings(BaseSettings): """ Convert comma separated CORS origins string to list """ - return [ - origin.strip() for origin in self.CORS_ORIGINS.split(",") - ] + return [origin.strip() for origin in self.CORS_ORIGINS.split(",")] @lru_cache diff --git a/PROJECTS/api-security-scanner/backend/core/enums.py b/PROJECTS/api-security-scanner/backend/core/enums.py index b308747b..4101d793 100644 --- a/PROJECTS/api-security-scanner/backend/core/enums.py +++ b/PROJECTS/api-security-scanner/backend/core/enums.py @@ -9,6 +9,7 @@ class ScanStatus(str, Enum): """ Enum for scan result status """ + VULNERABLE = "vulnerable" SAFE = "safe" ERROR = "error" @@ -18,6 +19,7 @@ class Severity(str, Enum): """ Enum for vulnerability severity levels """ + CRITICAL = "critical" HIGH = "high" MEDIUM = "medium" @@ -29,6 +31,7 @@ class TestType(str, Enum): """ Enum for available security test types """ + RATE_LIMIT = "rate_limit" AUTH = "auth" SQLI = "sqli" diff --git a/PROJECTS/api-security-scanner/backend/core/security.py b/PROJECTS/api-security-scanner/backend/core/security.py index c2585ee5..96feed6b 100644 --- a/PROJECTS/api-security-scanner/backend/core/security.py +++ b/PROJECTS/api-security-scanner/backend/core/security.py @@ -16,10 +16,10 @@ def hash_password(password: str) -> str: """ Hash a plain text password using bcrypt """ - password_bytes = password.encode('utf-8') + password_bytes = password.encode("utf-8") salt = bcrypt.gensalt() hashed = bcrypt.hashpw(password_bytes, salt) - return hashed.decode('utf-8') + return hashed.decode("utf-8") def verify_password( @@ -29,8 +29,8 @@ def verify_password( """ Verify a plain text password against a hashed password """ - password_bytes = plain_password.encode('utf-8') - hashed_bytes = hashed_password.encode('utf-8') + password_bytes = plain_password.encode("utf-8") + hashed_bytes = hashed_password.encode("utf-8") return bcrypt.checkpw(password_bytes, hashed_bytes) diff --git a/PROJECTS/api-security-scanner/backend/factory.py b/PROJECTS/api-security-scanner/backend/factory.py index a810bf31..f25be051 100644 --- a/PROJECTS/api-security-scanner/backend/factory.py +++ b/PROJECTS/api-security-scanner/backend/factory.py @@ -22,31 +22,28 @@ def create_app() -> FastAPI: """ Application factory function """ - Base.metadata.create_all(bind = engine) + Base.metadata.create_all(bind=engine) app = FastAPI( - title = settings.APP_NAME, - version = settings.VERSION, - openapi_version = "3.1.0", - docs_url = "/docs", - redoc_url = "/redoc", - openapi_url = "/openapi.json", - debug = settings.DEBUG, + title=settings.APP_NAME, + version=settings.VERSION, + openapi_version="3.1.0", + docs_url="/docs", + redoc_url="/redoc", + openapi_url="/openapi.json", + debug=settings.DEBUG, ) - limiter = Limiter(key_func = get_remote_address) + limiter = Limiter(key_func=get_remote_address) app.state.limiter = limiter - app.add_exception_handler( - RateLimitExceeded, - _rate_limit_exceeded_handler - ) + app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) app.add_middleware( CORSMiddleware, - allow_origins = settings.cors_origins_list, - allow_credentials = True, - allow_methods = ["*"], - allow_headers = ["*"], + allow_origins=settings.cors_origins_list, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], ) _register_routes(app) @@ -58,6 +55,7 @@ def _register_routes(app: FastAPI) -> None: """ Register all application routes """ + @app.get("/") def root() -> dict[str, str]: """ diff --git a/PROJECTS/api-security-scanner/backend/main.py b/PROJECTS/api-security-scanner/backend/main.py index e956d2fe..5d97d2f2 100644 --- a/PROJECTS/api-security-scanner/backend/main.py +++ b/PROJECTS/api-security-scanner/backend/main.py @@ -1,6 +1,6 @@ """ ⒸCertGames.com | 2025 -ⒸAngelaMos | CarterPerez-dev +ⒸAngelaMos | CarterPerez-dev ---- API Security Scanner FastAPI entry point """ diff --git a/PROJECTS/api-security-scanner/backend/models/Base.py b/PROJECTS/api-security-scanner/backend/models/Base.py index dc01f799..8e3a2a0b 100644 --- a/PROJECTS/api-security-scanner/backend/models/Base.py +++ b/PROJECTS/api-security-scanner/backend/models/Base.py @@ -1,6 +1,6 @@ """ ⒸAngelaMos | 2025 -Base model class +Base model class Common fields and methods for all models """ @@ -21,6 +21,7 @@ class BaseModel(Base): Abstract base model with common fields and methods All models inherit from this class """ + __abstract__ = True id = Column( diff --git a/PROJECTS/api-security-scanner/backend/models/Scan.py b/PROJECTS/api-security-scanner/backend/models/Scan.py index dea6dac6..ec784252 100644 --- a/PROJECTS/api-security-scanner/backend/models/Scan.py +++ b/PROJECTS/api-security-scanner/backend/models/Scan.py @@ -24,6 +24,7 @@ class Scan(BaseModel): """ Stores metadata about scans performed on target URLs """ + __tablename__ = "scans" user_id = Column( diff --git a/PROJECTS/api-security-scanner/backend/models/TestResult.py b/PROJECTS/api-security-scanner/backend/models/TestResult.py index 9a9beabc..04ac8d61 100644 --- a/PROJECTS/api-security-scanner/backend/models/TestResult.py +++ b/PROJECTS/api-security-scanner/backend/models/TestResult.py @@ -8,7 +8,7 @@ from sqlalchemy import ( Enum, Integer, Text, - ForeignKey, + ForeignKey, ) from sqlalchemy.orm import relationship from sqlalchemy.dialects.postgresql import JSON @@ -25,6 +25,7 @@ class TestResult(BaseModel): """ Stores individual test results for each security scan """ + __tablename__ = "test_results" scan_id = Column( diff --git a/PROJECTS/api-security-scanner/backend/models/User.py b/PROJECTS/api-security-scanner/backend/models/User.py index 47a531f1..54445262 100644 --- a/PROJECTS/api-security-scanner/backend/models/User.py +++ b/PROJECTS/api-security-scanner/backend/models/User.py @@ -17,6 +17,7 @@ class User(BaseModel): """ Stores authentication credentials and user information """ + __tablename__ = "users" email = Column( diff --git a/PROJECTS/api-security-scanner/backend/pyproject.toml b/PROJECTS/api-security-scanner/backend/pyproject.toml index 2a50c421..7f292b0c 100644 --- a/PROJECTS/api-security-scanner/backend/pyproject.toml +++ b/PROJECTS/api-security-scanner/backend/pyproject.toml @@ -56,19 +56,19 @@ build-backend = "setuptools.build_meta" [tool.setuptools] py-modules = [ - "main", - "config", + "main", + "config", "factory" ] [tool.setuptools.packages.find] include = [ "core", - "routes", - "models", - "schemas", - "services", - "scanners", + "routes", + "models", + "schemas", + "services", + "scanners", "repositories" ] @@ -190,6 +190,7 @@ disable_error_code = [ "call-arg", "dict-item", "abstract", + "import-untyped", ] @@ -203,21 +204,21 @@ load-plugins = [ persistent = true suggestion-mode = true init-hook = "import sys; import os; sys.path.insert(0, os.getcwd())" -ignore = [ - "venv", - ".venv", - "__pycache__", - "build", - "dist", +ignore = [ + "venv", + ".venv", + "__pycache__", + "build", + "dist", ".git", - ".pytest_cache", - ".mypy_cache", - ".ruff_cache", + ".pytest_cache", + ".mypy_cache", + ".ruff_cache", ] -ignore-paths = [ - "^venv/.*", - "^.venv/.*", - "^build/.*", +ignore-paths = [ + "^venv/.*", + "^.venv/.*", + "^build/.*", "^dist/.*", ] [tool.pylint.type-check] @@ -268,7 +269,7 @@ max-statements = 40 [tool.bandit] exclude_dirs = [ - ".venv", + ".venv", "venv", ] skips = ["B104"] diff --git a/PROJECTS/api-security-scanner/backend/repositories/test_result_repository.py b/PROJECTS/api-security-scanner/backend/repositories/test_result_repository.py index 191d7b1a..03f411d6 100644 --- a/PROJECTS/api-security-scanner/backend/repositories/test_result_repository.py +++ b/PROJECTS/api-security-scanner/backend/repositories/test_result_repository.py @@ -167,11 +167,9 @@ class TestResultRepository: Returns: int: Number of test results deleted """ - count = ( - db.query(TestResult).filter( - TestResult.scan_id == scan_id - ).delete() - ) + count = db.query(TestResult).filter( + TestResult.scan_id == scan_id + ).delete() if commit: db.commit() return count diff --git a/PROJECTS/api-security-scanner/backend/repositories/user_repository.py b/PROJECTS/api-security-scanner/backend/repositories/user_repository.py index 4fff5c38..78896375 100644 --- a/PROJECTS/api-security-scanner/backend/repositories/user_repository.py +++ b/PROJECTS/api-security-scanner/backend/repositories/user_repository.py @@ -89,10 +89,8 @@ class UserRepository: if limit is None: limit = settings.DEFAULT_PAGINATION_LIMIT - return ( - db.query(User).filter(User.is_active - ).offset(skip).limit(limit).all() - ) + return db.query(User).filter(User.is_active + ).offset(skip).limit(limit).all() @staticmethod def update_active_status( diff --git a/PROJECTS/api-security-scanner/backend/routes/auth.py b/PROJECTS/api-security-scanner/backend/routes/auth.py index 8150c938..3b218634 100644 --- a/PROJECTS/api-security-scanner/backend/routes/auth.py +++ b/PROJECTS/api-security-scanner/backend/routes/auth.py @@ -24,14 +24,14 @@ from schemas.user_schemas import ( from services.auth_service import AuthService -router = APIRouter(prefix = "/auth", tags = ["authentication"]) -limiter = Limiter(key_func = get_remote_address) +router = APIRouter(prefix="/auth", tags=["authentication"]) +limiter = Limiter(key_func=get_remote_address) @router.post( "/register", - response_model = UserResponse, - status_code = status.HTTP_201_CREATED, + response_model=UserResponse, + status_code=status.HTTP_201_CREATED, ) @limiter.limit(settings.API_RATE_LIMIT_REGISTER) async def register( @@ -47,8 +47,8 @@ async def register( @router.post( "/login", - response_model = TokenResponse, - status_code = status.HTTP_200_OK, + response_model=TokenResponse, + status_code=status.HTTP_200_OK, ) @limiter.limit(settings.API_RATE_LIMIT_LOGIN) async def login( diff --git a/PROJECTS/api-security-scanner/backend/routes/scans.py b/PROJECTS/api-security-scanner/backend/routes/scans.py index c9c9706d..5b5574e9 100644 --- a/PROJECTS/api-security-scanner/backend/routes/scans.py +++ b/PROJECTS/api-security-scanner/backend/routes/scans.py @@ -24,14 +24,14 @@ from schemas.user_schemas import UserResponse from services.scan_service import ScanService -router = APIRouter(prefix = "/scans", tags = ["scans"]) -limiter = Limiter(key_func = get_remote_address) +router = APIRouter(prefix="/scans", tags=["scans"]) +limiter = Limiter(key_func=get_remote_address) @router.post( "/", - response_model = ScanResponse, - status_code = status.HTTP_201_CREATED, + response_model=ScanResponse, + status_code=status.HTTP_201_CREATED, ) @limiter.limit(settings.API_RATE_LIMIT_SCAN) async def create_scan( @@ -48,8 +48,8 @@ async def create_scan( @router.get( "/", - response_model = list[ScanResponse], - status_code = status.HTTP_200_OK, + response_model=list[ScanResponse], + status_code=status.HTTP_200_OK, ) @limiter.limit(settings.API_RATE_LIMIT_DEFAULT) async def get_user_scans( @@ -62,18 +62,13 @@ async def get_user_scans( """ Get all scans for the authenticated user """ - return ScanService.get_user_scans( - db, - current_user.id, - skip, - limit - ) + return ScanService.get_user_scans(db, current_user.id, skip, limit) @router.get( "/{scan_id}", - response_model = ScanResponse, - status_code = status.HTTP_200_OK, + response_model=ScanResponse, + status_code=status.HTTP_200_OK, ) @limiter.limit(settings.API_RATE_LIMIT_DEFAULT) async def get_scan( @@ -90,7 +85,7 @@ async def get_scan( @router.delete( "/{scan_id}", - status_code = status.HTTP_204_NO_CONTENT, + status_code=status.HTTP_204_NO_CONTENT, ) @limiter.limit(settings.API_RATE_LIMIT_DEFAULT) async def delete_scan( diff --git a/PROJECTS/api-security-scanner/backend/scanners/idor_scanner.py b/PROJECTS/api-security-scanner/backend/scanners/idor_scanner.py index 4cec9cf0..705e4aa2 100644 --- a/PROJECTS/api-security-scanner/backend/scanners/idor_scanner.py +++ b/PROJECTS/api-security-scanner/backend/scanners/idor_scanner.py @@ -135,7 +135,7 @@ class IDORScanner(BaseScanner): response_text = response.text - uuid_pattern = r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' + uuid_pattern = r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" uuids = re.findall( uuid_pattern, response_text, diff --git a/PROJECTS/api-security-scanner/backend/scanners/payloads.py b/PROJECTS/api-security-scanner/backend/scanners/payloads.py index 5c5a4d47..05cead97 100644 --- a/PROJECTS/api-security-scanner/backend/scanners/payloads.py +++ b/PROJECTS/api-security-scanner/backend/scanners/payloads.py @@ -8,6 +8,7 @@ class SQLiPayloads: """ SQL Injection test payloads covering various database types and techniques """ + ERROR_SIGNATURES = { "mysql": [ "sql syntax", @@ -221,6 +222,7 @@ class IDORPayloads: """ Insecure Direct Object Reference (IDOR) test patterns """ + NUMERIC_ID_MANIPULATIONS = [ 0, -1, @@ -273,6 +275,7 @@ class RateLimitBypassPayloads: """ Rate limiting bypass techniques and patterns """ + HEADER_PATTERNS = { "limit": r"x-ratelimit-limit|x-rate-limit-limit|ratelimit-limit", @@ -413,9 +416,9 @@ class XSSPayloads: ATTRIBUTE_BREAKING = [ "' onmouseover='alert(\"XSS\")'", - "\" onmouseover=\"alert('XSS')\"", + '" onmouseover="alert(\'XSS\')"', "' onclick='alert(\"XSS\")' '", - "\" autofocus onfocus=\"alert('XSS')\"", + '" autofocus onfocus="alert(\'XSS\')"', "'/>", "\"/>", ] @@ -427,7 +430,7 @@ class XSSPayloads: "", - "alert('XSS')", + "alert('XSS')", ] POLYGLOT_XSS = [ diff --git a/PROJECTS/api-security-scanner/backend/scanners/sqli_scanner.py b/PROJECTS/api-security-scanner/backend/scanners/sqli_scanner.py index a6d76948..cf04feea 100644 --- a/PROJECTS/api-security-scanner/backend/scanners/sqli_scanner.py +++ b/PROJECTS/api-security-scanner/backend/scanners/sqli_scanner.py @@ -273,10 +273,7 @@ class SQLiScanner(BaseScanner): avg_delay = statistics.mean(delay_times) if avg_delay >= expected_delay_time - 1: - confidence = ( - "HIGH" if avg_delay >= expected_delay_time - else "MEDIUM" - ) + confidence = "HIGH" if avg_delay >= expected_delay_time else "MEDIUM" return { "vulnerable": diff --git a/PROJECTS/api-security-scanner/backend/schemas/scan_schemas.py b/PROJECTS/api-security-scanner/backend/schemas/scan_schemas.py index 2c9e63c0..d8675457 100644 --- a/PROJECTS/api-security-scanner/backend/schemas/scan_schemas.py +++ b/PROJECTS/api-security-scanner/backend/schemas/scan_schemas.py @@ -22,6 +22,7 @@ class ScanRequest(BaseModel): """ Schema for creating a new security scan """ + target_url: HttpUrl = Field(max_length = settings.URL_MAX_LENGTH) auth_token: str | None = None tests_to_run: list[TestType] = Field(min_length = 1) @@ -36,6 +37,7 @@ class ScanResponse(BaseModel): """ Schema for scan data in API responses """ + model_config = ConfigDict(from_attributes = True) id: int diff --git a/PROJECTS/api-security-scanner/backend/schemas/test_result_schemas.py b/PROJECTS/api-security-scanner/backend/schemas/test_result_schemas.py index c42162b7..fd549b87 100644 --- a/PROJECTS/api-security-scanner/backend/schemas/test_result_schemas.py +++ b/PROJECTS/api-security-scanner/backend/schemas/test_result_schemas.py @@ -22,6 +22,7 @@ class TestResultCreate(BaseModel): """ Schema for creating a new test result (used by scanners) """ + test_name: TestType status: ScanStatus severity: Severity @@ -34,6 +35,7 @@ class TestResultResponse(BaseModel): """ Schema for individual test result in API responses """ + model_config = ConfigDict(from_attributes = True) id: int diff --git a/PROJECTS/api-security-scanner/backend/schemas/user_schemas.py b/PROJECTS/api-security-scanner/backend/schemas/user_schemas.py index b988e56e..92c9907b 100644 --- a/PROJECTS/api-security-scanner/backend/schemas/user_schemas.py +++ b/PROJECTS/api-security-scanner/backend/schemas/user_schemas.py @@ -8,13 +8,7 @@ from __future__ import annotations import re from datetime import datetime -from pydantic import ( - BaseModel, - ConfigDict, - EmailStr, - Field, - field_validator -) +from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator from config import settings @@ -22,24 +16,31 @@ class UserCreate(BaseModel): """ Schema for user registration request. """ + email: EmailStr password: str = Field( min_length = settings.PASSWORD_MIN_LENGTH, max_length = settings.PASSWORD_MAX_LENGTH, ) - @field_validator('password') + @field_validator("password") @classmethod def validate_password_strength(cls, v: str) -> str: """ Validate password meets security requirements """ - if not re.search(r'[A-Z]', v): - raise ValueError('Password must contain at least one uppercase letter') - if not re.search(r'[a-z]', v): - raise ValueError('Password must contain at least one lowercase letter') - if not re.search(r'[0-9]', v): - raise ValueError('Password must contain at least one number') + if not re.search(r"[A-Z]", v): + raise ValueError( + "Password must contain at least one uppercase letter" + ) + if not re.search(r"[a-z]", v): + raise ValueError( + "Password must contain at least one lowercase letter" + ) + if not re.search(r"[0-9]", v): + raise ValueError( + "Password must contain at least one number" + ) return v @@ -47,6 +48,7 @@ class UserLogin(BaseModel): """ Schema for user login request. """ + email: EmailStr password: str @@ -56,6 +58,7 @@ class UserResponse(BaseModel): Schema for user data in API responses. Excludes sensitive fields like hashed_password. """ + model_config = ConfigDict(from_attributes = True) id: int @@ -68,5 +71,6 @@ class TokenResponse(BaseModel): """ Schema for JWT token response. """ + access_token: str token_type: str = "bearer" diff --git a/PROJECTS/api-security-scanner/backend/services/auth_service.py b/PROJECTS/api-security-scanner/backend/services/auth_service.py index 66a2c4a0..2f14e0cd 100644 --- a/PROJECTS/api-security-scanner/backend/services/auth_service.py +++ b/PROJECTS/api-security-scanner/backend/services/auth_service.py @@ -28,11 +28,9 @@ class AuthService: """ User registration, login, and token generation """ + @staticmethod - def register_user( - db: Session, - user_data: UserCreate - ) -> UserResponse: + def register_user(db: Session, user_data: UserCreate) -> UserResponse: """ Register a new user @@ -43,31 +41,25 @@ class AuthService: Returns: UserResponse: Created user data """ - existing_user = UserRepository.get_by_email( - db, - user_data.email - ) + existing_user = UserRepository.get_by_email(db, user_data.email) if existing_user: raise HTTPException( - status_code = status.HTTP_400_BAD_REQUEST, - detail = "Email already registered", + status_code=status.HTTP_400_BAD_REQUEST, + detail="Email already registered", ) hashed_password = hash_password(user_data.password) user = UserRepository.create_user( - db = db, - email = user_data.email, - hashed_password = hashed_password, + db=db, + email=user_data.email, + hashed_password=hashed_password, ) return UserResponse.model_validate(user) @staticmethod - def login_user( - db: Session, - login_data: UserLogin - ) -> TokenResponse: + def login_user(db: Session, login_data: UserLogin) -> TokenResponse: """ Authenticate user and generate access token @@ -82,40 +74,31 @@ class AuthService: if not user: raise HTTPException( - status_code = status.HTTP_401_UNAUTHORIZED, - detail = "Invalid email or password", + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid email or password", ) - if not verify_password(login_data.password, - user.hashed_password): + if not verify_password(login_data.password, user.hashed_password): raise HTTPException( - status_code = status.HTTP_401_UNAUTHORIZED, - detail = "Invalid email or password", + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid email or password", ) if not user.is_active: raise HTTPException( - status_code = status.HTTP_403_FORBIDDEN, - detail = "Account is inactive", + status_code=status.HTTP_403_FORBIDDEN, + detail="Account is inactive", ) access_token = create_access_token( - data = {"sub": user.email}, - expires_delta = timedelta( - minutes = settings.ACCESS_TOKEN_EXPIRE_MINUTES - ), + data={"sub": user.email}, + expires_delta=timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES), ) - return TokenResponse( - access_token = access_token, - token_type = "bearer" - ) + return TokenResponse(access_token=access_token, token_type="bearer") @staticmethod - def get_user_by_email( - db: Session, - email: str - ) -> UserResponse | None: + def get_user_by_email(db: Session, email: str) -> UserResponse | None: """ Get user by email address diff --git a/PROJECTS/api-security-scanner/backend/services/scan_service.py b/PROJECTS/api-security-scanner/backend/services/scan_service.py index f18c86be..d11a9fda 100644 --- a/PROJECTS/api-security-scanner/backend/services/scan_service.py +++ b/PROJECTS/api-security-scanner/backend/services/scan_service.py @@ -5,8 +5,6 @@ Coordinates scanners and saves results from __future__ import annotations -from typing import Type - from sqlalchemy.orm import Session from fastapi import HTTPException, status @@ -27,12 +25,9 @@ class ScanService: """ Orchestrates security scanning workflow """ + @staticmethod - def run_scan( - db: Session, - user_id: int, - scan_request: ScanRequest - ) -> ScanResponse: + def run_scan(db: Session, user_id: int, scan_request: ScanRequest) -> ScanResponse: """ Execute security scan with selected tests @@ -45,12 +40,12 @@ class ScanService: ScanResponse: Scan results with all test outcomes """ scan = ScanRepository.create_scan( - db = db, - user_id = user_id, - target_url = str(scan_request.target_url), + db=db, + user_id=user_id, + target_url=str(scan_request.target_url), ) - scanner_mapping: dict[TestType, Type[BaseScanner]] = { + scanner_mapping: dict[TestType, type[BaseScanner]] = { TestType.RATE_LIMIT: RateLimitScanner, TestType.AUTH: AuthScanner, TestType.SQLI: SQLiScanner, @@ -60,16 +55,16 @@ class ScanService: results: list[TestResultCreate] = [] for test_type in scan_request.tests_to_run: - scanner_class: Type[BaseScanner] | None = scanner_mapping.get(test_type) + scanner_class: type[BaseScanner] | None = scanner_mapping.get(test_type) if not scanner_class: continue try: scanner = scanner_class( - target_url = str(scan_request.target_url), - auth_token = scan_request.auth_token, - max_requests = scan_request.max_requests, + target_url=str(scan_request.target_url), + auth_token=scan_request.auth_token, + max_requests=scan_request.max_requests, ) result = scanner.scan() @@ -78,12 +73,12 @@ class ScanService: except Exception as e: results.append( TestResultCreate( - test_name = test_type, - status = "error", - severity = "info", - details = f"Scanner error: {str(e)}", - evidence_json = {"error": str(e)}, - recommendations_json = [ + test_name=test_type, + status="error", + severity="info", + details=f"Scanner error: {str(e)}", + evidence_json={"error": str(e)}, + recommendations_json=[ "Check target URL is accessible", "Verify authentication token if provided", ], @@ -92,14 +87,14 @@ class ScanService: for result in results: TestResultRepository.create_test_result( - db = db, - scan_id = scan.id, - test_name = result.test_name, - status = result.status, - severity = result.severity, - details = result.details, - evidence_json = result.evidence_json, - recommendations_json = result.recommendations_json, + db=db, + scan_id=scan.id, + test_name=result.test_name, + status=result.status, + severity=result.severity, + details=result.details, + evidence_json=result.evidence_json, + recommendations_json=result.recommendations_json, ) db.refresh(scan) @@ -107,11 +102,7 @@ class ScanService: return ScanResponse.model_validate(scan) @staticmethod - def get_scan_by_id( - db: Session, - scan_id: int, - user_id: int - ) -> ScanResponse: + def get_scan_by_id(db: Session, scan_id: int, user_id: int) -> ScanResponse: """ Get scan by ID with authorization check @@ -127,24 +118,21 @@ class ScanService: if not scan: raise HTTPException( - status_code = status.HTTP_404_NOT_FOUND, - detail = "Scan not found", + status_code=status.HTTP_404_NOT_FOUND, + detail="Scan not found", ) if scan.user_id != user_id: raise HTTPException( - status_code = status.HTTP_403_FORBIDDEN, - detail = "Not authorized to access this scan", + status_code=status.HTTP_403_FORBIDDEN, + detail="Not authorized to access this scan", ) return ScanResponse.model_validate(scan) @staticmethod def get_user_scans( - db: Session, - user_id: int, - skip: int = 0, - limit: int | None = None + db: Session, user_id: int, skip: int = 0, limit: int | None = None ) -> list[ScanResponse]: """ Get all scans for a user with pagination @@ -158,12 +146,7 @@ class ScanService: Returns: list[ScanResponse]: List of user's scans """ - scans = ScanRepository.get_by_user( - db = db, - user_id = user_id, - skip = skip, - limit = limit - ) + scans = ScanRepository.get_by_user(db=db, user_id=user_id, skip=skip, limit=limit) return [ScanResponse.model_validate(scan) for scan in scans] @@ -184,14 +167,14 @@ class ScanService: if not scan: raise HTTPException( - status_code = status.HTTP_404_NOT_FOUND, - detail = "Scan not found", + status_code=status.HTTP_404_NOT_FOUND, + detail="Scan not found", ) if scan.user_id != user_id: raise HTTPException( - status_code = status.HTTP_403_FORBIDDEN, - detail = "Not authorized to delete this scan", + status_code=status.HTTP_403_FORBIDDEN, + detail="Not authorized to delete this scan", ) return ScanRepository.delete(db, scan_id) diff --git a/PROJECTS/api-security-scanner/frontend/.prettierignore b/PROJECTS/api-security-scanner/frontend/.prettierignore new file mode 100755 index 00000000..fedf8027 --- /dev/null +++ b/PROJECTS/api-security-scanner/frontend/.prettierignore @@ -0,0 +1,24 @@ +# Dependencies +node_modules/ + +# Build outputs +dist/ +build/ + +# Logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Coverage directory used by tools like istanbul +coverage/ + +# Other +.DS_Store +.env* +.vscode/ +.idea/ diff --git a/PROJECTS/api-security-scanner/frontend/.prettierrc.json b/PROJECTS/api-security-scanner/frontend/.prettierrc.json new file mode 100755 index 00000000..1a5f7bfd --- /dev/null +++ b/PROJECTS/api-security-scanner/frontend/.prettierrc.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "printWidth": 77, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "quoteProps": "consistent", + "jsxSingleQuote": false, + "trailingComma": "all", + "bracketSpacing": true, + "bracketSameLine": false, + "arrowParens": "always", + "proseWrap": "always", + "htmlWhitespaceSensitivity": "strict", + "endOfLine": "lf", + "embeddedLanguageFormatting": "auto", + "singleAttributePerLine": true, + "overrides": [ + { + "files": "*.tsx", + "options": { + "printWidth": 77, + "jsxSingleQuote": false, + "bracketSameLine": false + } + }, + { + "files": "*.ts", + "options": { + "printWidth": 77, + "parser": "typescript" + } + }, + { + "files": ["*.json", "*.jsonc"], + "options": { + "printWidth": 80, + "trailingComma": "none" + } + }, + { + "files": "*.scss", + "options": { + "singleQuote": false, + "printWidth": 100 + } + }, + { + "files": "*.md", + "options": { + "proseWrap": "always", + "printWidth": 80 + } + }, + { + "files": ["*.yml", "*.yaml"], + "options": { + "singleQuote": false, + "bracketSpacing": true + } + }, + { + "files": ".prettierrc", + "options": { + "parser": "json" + } + }, + { + "files": "package.json", + "options": { + "printWidth": 1000 + } + } + ] +} diff --git a/PROJECTS/api-security-scanner/frontend/eslint.config.js b/PROJECTS/api-security-scanner/frontend/eslint.config.js index 91c820ca..6cf693c1 100644 --- a/PROJECTS/api-security-scanner/frontend/eslint.config.js +++ b/PROJECTS/api-security-scanner/frontend/eslint.config.js @@ -17,7 +17,7 @@ export default tseslint.config( }, js.configs.recommended, - + ...tseslint.configs.strictTypeChecked, ...tseslint.configs.stylisticTypeChecked, @@ -49,10 +49,10 @@ export default tseslint.config( rules: { '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }], '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports', fixStyle: 'inline-type-imports' }], - '@typescript-eslint/explicit-function-return-type': ['error', { - allowExpressions: true, - allowTypedFunctionExpressions: true, - allowHigherOrderFunctions: true, + '@typescript-eslint/explicit-function-return-type': ['error', { + allowExpressions: true, + allowTypedFunctionExpressions: true, + allowHigherOrderFunctions: true, allowDirectConstAssertionInArrowFunctions: true, allowedNames: ['Component'] }], @@ -60,9 +60,9 @@ export default tseslint.config( '@typescript-eslint/no-non-null-assertion': 'error', '@typescript-eslint/array-type': ['error', { default: 'array' }], '@typescript-eslint/no-explicit-any': 'error', - '@typescript-eslint/no-confusing-void-expression': 'off', - '@typescript-eslint/no-unnecessary-condition': 'off', - '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-confusing-void-expression': 'off', + '@typescript-eslint/no-unnecessary-condition': 'off', + '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/strict-boolean-expressions': ['error', { allowString: false, allowNumber: false, diff --git a/PROJECTS/api-security-scanner/frontend/public/site.webmanifest b/PROJECTS/api-security-scanner/frontend/public/site.webmanifest index 45dc8a20..1dd91123 100644 --- a/PROJECTS/api-security-scanner/frontend/public/site.webmanifest +++ b/PROJECTS/api-security-scanner/frontend/public/site.webmanifest @@ -1 +1 @@ -{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} diff --git a/PROJECTS/api-security-scanner/frontend/src/App.tsx b/PROJECTS/api-security-scanner/frontend/src/App.tsx index 46df2632..e01fd519 100644 --- a/PROJECTS/api-security-scanner/frontend/src/App.tsx +++ b/PROJECTS/api-security-scanner/frontend/src/App.tsx @@ -13,7 +13,9 @@ import { router } from '@/router'; import { useAuthStore } from '@/store/authStore'; const AuthInitializer = (): null => { - const loadUserFromStorage = useAuthStore((state) => state.loadUserFromStorage); + const loadUserFromStorage = useAuthStore( + (state) => state.loadUserFromStorage, + ); useEffect(() => { loadUserFromStorage(); @@ -27,7 +29,12 @@ function App(): React.ReactElement { - + ); diff --git a/PROJECTS/api-security-scanner/frontend/src/components/auth/LoginForm.tsx b/PROJECTS/api-security-scanner/frontend/src/components/auth/LoginForm.tsx index 2b0555c9..bef790a2 100644 --- a/PROJECTS/api-security-scanner/frontend/src/components/auth/LoginForm.tsx +++ b/PROJECTS/api-security-scanner/frontend/src/components/auth/LoginForm.tsx @@ -21,9 +21,10 @@ export const LoginForm = (): React.ReactElement => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const [errors, setErrors] = useState<{ email?: string; password?: string }>( - {}, - ); + const [errors, setErrors] = useState<{ + email?: string; + password?: string; + }>({}); const { mutate: login, isPending, error } = useLogin(); @@ -49,14 +50,19 @@ export const LoginForm = (): React.ReactElement => { } }; - const validateField = (field: 'email' | 'password', value: string): void => { + const validateField = ( + field: 'email' | 'password', + value: string, + ): void => { const result = loginSchema.safeParse({ email: field === 'email' ? value : email, password: field === 'password' ? value : password, }); if (!result.success) { - const fieldError = result.error.issues.find((err) => err.path[0] === field); + const fieldError = result.error.issues.find( + (err) => err.path[0] === field, + ); if (fieldError !== null && fieldError !== undefined) { setErrors((prev) => ({ ...prev, [field]: fieldError.message })); } else { @@ -120,7 +126,10 @@ export const LoginForm = (): React.ReactElement => { }; return ( -
+

Welcome Back

Sign in to your account

@@ -153,18 +162,29 @@ export const LoginForm = (): React.ReactElement => {
{error !== null && error !== undefined && isAxiosError(error) ? ( -
- {(error.response?.data as { detail?: string } | undefined)?.detail ?? 'Login failed. Please try again.'} +
+ {(error.response?.data as { detail?: string } | undefined) + ?.detail ?? 'Login failed. Please try again.'}
) : null} -

Don't have an account?{' '} - + Sign up

diff --git a/PROJECTS/api-security-scanner/frontend/src/components/auth/RegisterForm.tsx b/PROJECTS/api-security-scanner/frontend/src/components/auth/RegisterForm.tsx index 7fdefd44..4cedc2d4 100644 --- a/PROJECTS/api-security-scanner/frontend/src/components/auth/RegisterForm.tsx +++ b/PROJECTS/api-security-scanner/frontend/src/components/auth/RegisterForm.tsx @@ -15,7 +15,9 @@ import './AuthForm.css'; export const RegisterForm = (): React.ReactElement => { const registerFormState = useUIStore((state) => state.registerForm); - const setRegisterFormField = useUIStore((state) => state.setRegisterFormField); + const setRegisterFormField = useUIStore( + (state) => state.setRegisterFormField, + ); const clearRegisterForm = useUIStore((state) => state.clearRegisterForm); const clearExpiredData = useUIStore((state) => state.clearExpiredData); @@ -61,7 +63,10 @@ export const RegisterForm = (): React.ReactElement => { const handleConfirmPasswordChange = (value: string): void => { setConfirmPassword(value); setRegisterFormField('confirmPassword', value); - if (errors.confirmPassword !== null && errors.confirmPassword !== undefined) { + if ( + errors.confirmPassword !== null && + errors.confirmPassword !== undefined + ) { validateField('confirmPassword', value); } }; @@ -77,7 +82,9 @@ export const RegisterForm = (): React.ReactElement => { }); if (!result.success) { - const fieldError = result.error.issues.find((err) => err.path[0] === field); + const fieldError = result.error.issues.find( + (err) => err.path[0] === field, + ); if (fieldError !== null && fieldError !== undefined) { setErrors((prev) => ({ ...prev, [field]: fieldError.message })); } else { @@ -94,7 +101,9 @@ export const RegisterForm = (): React.ReactElement => { } }; - const handleBlur = (field: 'email' | 'password' | 'confirmPassword'): void => { + const handleBlur = ( + field: 'email' | 'password' | 'confirmPassword', + ): void => { let value: string; if (field === 'email') { value = email; @@ -153,10 +162,15 @@ export const RegisterForm = (): React.ReactElement => { }; return ( - +

Create Account

-

Get started with API Security Scanner

+

+ Get started with API Security Scanner +

@@ -198,18 +212,29 @@ export const RegisterForm = (): React.ReactElement => {
{error !== null && error !== undefined && isAxiosError(error) ? ( -
- {(error.response?.data as { detail?: string } | undefined)?.detail ?? 'Registration failed. Please try again.'} +
+ {(error.response?.data as { detail?: string } | undefined) + ?.detail ?? 'Registration failed. Please try again.'}
) : null} -

Already have an account?{' '} - + Sign in

diff --git a/PROJECTS/api-security-scanner/frontend/src/components/common/Input.tsx b/PROJECTS/api-security-scanner/frontend/src/components/common/Input.tsx index b22b9a77..f9761539 100644 --- a/PROJECTS/api-security-scanner/frontend/src/components/common/Input.tsx +++ b/PROJECTS/api-security-scanner/frontend/src/components/common/Input.tsx @@ -13,12 +13,16 @@ interface InputProps extends InputHTMLAttributes { export const Input = forwardRef( ({ label, error, id, ...props }, ref) => { - const inputId = id ?? `input-${label.toLowerCase().replace(/\s+/g, '-')}`; + const inputId = + id ?? `input-${label.toLowerCase().replace(/\s+/g, '-')}`; const errorId = `${inputId}-error`; return (
-