mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-08 22:58:01 -05:00
feat(backend, frontend): make changes to use our security modules more effectively (#10123)
<!-- Clearly explain the need for these changes: --> Doing the CASA Audit and this is something to check ### Changes 🏗️ - limits APIs to use their specific endpoints - use expected trusted sources for each block and requests call - Use cryptographically valid string comparisons - Don't log secrets <!-- Concisely describe all of the changes made in this pull request: --> ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: <!-- Put your test plan here: --> - [x] Testing in dev branch once merged --------- Co-authored-by: Swifty <craigswift13@gmail.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -176,3 +176,4 @@ autogpt_platform/backend/settings.py
|
|||||||
|
|
||||||
*.ign.*
|
*.ign.*
|
||||||
.test-contents
|
.test-contents
|
||||||
|
.claude/settings.local.json
|
||||||
|
|||||||
@@ -31,4 +31,5 @@ class APIKeyManager:
|
|||||||
"""Verify if a provided API key matches the stored hash."""
|
"""Verify if a provided API key matches the stored hash."""
|
||||||
if not provided_key.startswith(self.PREFIX):
|
if not provided_key.startswith(self.PREFIX):
|
||||||
return False
|
return False
|
||||||
return hashlib.sha256(provided_key.encode()).hexdigest() == stored_hash
|
provided_hash = hashlib.sha256(provided_key.encode()).hexdigest()
|
||||||
|
return secrets.compare_digest(provided_hash, stored_hash)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
|
import secrets
|
||||||
from typing import Any, Callable, Optional
|
from typing import Any, Callable, Optional
|
||||||
|
|
||||||
from fastapi import HTTPException, Request, Security
|
from fastapi import HTTPException, Request, Security
|
||||||
@@ -93,7 +94,11 @@ class APIKeyValidator:
|
|||||||
self.error_message = error_message
|
self.error_message = error_message
|
||||||
|
|
||||||
async def default_validator(self, api_key: str) -> bool:
|
async def default_validator(self, api_key: str) -> bool:
|
||||||
return api_key == self.expected_token
|
if not self.expected_token:
|
||||||
|
raise ValueError(
|
||||||
|
"Expected Token Required to be set when uisng API Key Validator default validation"
|
||||||
|
)
|
||||||
|
return secrets.compare_digest(api_key, self.expected_token)
|
||||||
|
|
||||||
async def __call__(
|
async def __call__(
|
||||||
self, request: Request, api_key: str = Security(APIKeyHeader)
|
self, request: Request, api_key: str = Security(APIKeyHeader)
|
||||||
|
|||||||
149
autogpt_platform/autogpt_libs/poetry.lock
generated
149
autogpt_platform/autogpt_libs/poetry.lock
generated
@@ -1,4 +1,4 @@
|
|||||||
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiohappyeyeballs"
|
name = "aiohappyeyeballs"
|
||||||
@@ -177,7 +177,7 @@ files = [
|
|||||||
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
|
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
|
||||||
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
|
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
|
||||||
]
|
]
|
||||||
markers = {main = "python_version < \"3.11\"", dev = "python_full_version < \"3.11.3\""}
|
markers = {main = "python_version == \"3.10\"", dev = "python_full_version < \"3.11.3\""}
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "attrs"
|
name = "attrs"
|
||||||
@@ -323,6 +323,21 @@ files = [
|
|||||||
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
|
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.2.1"
|
||||||
|
description = "Composable command line interface toolkit"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"},
|
||||||
|
{file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
@@ -375,7 +390,7 @@ description = "Backport of PEP 654 (exception groups)"
|
|||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
markers = "python_version < \"3.11\""
|
markers = "python_version == \"3.10\""
|
||||||
files = [
|
files = [
|
||||||
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
||||||
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
||||||
@@ -399,6 +414,27 @@ files = [
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
tests = ["coverage", "coveralls", "dill", "mock", "nose"]
|
tests = ["coverage", "coveralls", "dill", "mock", "nose"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastapi"
|
||||||
|
version = "0.115.12"
|
||||||
|
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d"},
|
||||||
|
{file = "fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
|
||||||
|
starlette = ">=0.40.0,<0.47.0"
|
||||||
|
typing-extensions = ">=4.8.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
||||||
|
standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "frozenlist"
|
name = "frozenlist"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
@@ -895,6 +931,47 @@ files = [
|
|||||||
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "launchdarkly-eventsource"
|
||||||
|
version = "1.2.4"
|
||||||
|
description = "LaunchDarkly SSE Client"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "launchdarkly_eventsource-1.2.4-py3-none-any.whl", hash = "sha256:048ef8c4440d0d8219778661ee4d4b5e12aa6ed2c29a3004417ede44c2386e8c"},
|
||||||
|
{file = "launchdarkly_eventsource-1.2.4.tar.gz", hash = "sha256:b8b9342681f55e1d35c56243431cbbaca4eb9812d6785f8de204af322104e066"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
urllib3 = ">=1.26.0,<3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "launchdarkly-server-sdk"
|
||||||
|
version = "9.11.1"
|
||||||
|
description = "LaunchDarkly SDK for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "launchdarkly_server_sdk-9.11.1-py3-none-any.whl", hash = "sha256:128569cebf666dd115cc0ba03c48ff75f6acc9788301a7e2c3a54d06107e445a"},
|
||||||
|
{file = "launchdarkly_server_sdk-9.11.1.tar.gz", hash = "sha256:150e29656cb8c506d1967f3c59e62b69310d345ec27217640a6146dd1db5d250"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
certifi = ">=2018.4.16"
|
||||||
|
expiringdict = ">=1.1.4"
|
||||||
|
launchdarkly-eventsource = ">=1.2.4,<2.0.0"
|
||||||
|
pyRFC3339 = ">=1.0"
|
||||||
|
semver = ">=2.10.2"
|
||||||
|
urllib3 = ">=1.26.0,<3"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
consul = ["python-consul (>=1.0.1)"]
|
||||||
|
dynamodb = ["boto3 (>=1.9.71)"]
|
||||||
|
redis = ["redis (>=2.10.5)"]
|
||||||
|
test-filesource = ["pyyaml (>=5.3.1)", "watchdog (>=3.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "multidict"
|
name = "multidict"
|
||||||
version = "6.1.0"
|
version = "6.1.0"
|
||||||
@@ -1412,6 +1489,18 @@ dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pyte
|
|||||||
docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
|
docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
|
||||||
tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
|
tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyrfc3339"
|
||||||
|
version = "2.0.1"
|
||||||
|
description = "Generate and parse RFC 3339 timestamps"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "pyRFC3339-2.0.1-py3-none-any.whl", hash = "sha256:30b70a366acac3df7386b558c21af871522560ed7f3f73cf344b8c2cbb8b0c9d"},
|
||||||
|
{file = "pyrfc3339-2.0.1.tar.gz", hash = "sha256:e47843379ea35c1296c3b6c67a948a1a490ae0584edfcbdea0eaffb5dd29960b"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "8.3.3"
|
version = "8.3.3"
|
||||||
@@ -1604,6 +1693,18 @@ files = [
|
|||||||
{file = "ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6"},
|
{file = "ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "3.0.4"
|
||||||
|
description = "Python helper for Semantic Versioning (https://semver.org)"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746"},
|
||||||
|
{file = "semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "six"
|
name = "six"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
@@ -1628,6 +1729,24 @@ files = [
|
|||||||
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "starlette"
|
||||||
|
version = "0.46.2"
|
||||||
|
description = "The little ASGI library that shines."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35"},
|
||||||
|
{file = "starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
anyio = ">=3.6.2,<5"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "storage3"
|
name = "storage3"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@@ -1704,7 +1823,7 @@ description = "A lil' TOML parser"
|
|||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
markers = "python_version < \"3.11\""
|
markers = "python_version == \"3.10\""
|
||||||
files = [
|
files = [
|
||||||
{file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"},
|
{file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"},
|
||||||
{file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"},
|
{file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"},
|
||||||
@@ -1755,6 +1874,26 @@ h2 = ["h2 (>=4,<5)"]
|
|||||||
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||||
zstd = ["zstandard (>=0.18.0)"]
|
zstd = ["zstandard (>=0.18.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uvicorn"
|
||||||
|
version = "0.34.3"
|
||||||
|
description = "The lightning-fast ASGI server."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885"},
|
||||||
|
{file = "uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
click = ">=7.0"
|
||||||
|
h11 = ">=0.8"
|
||||||
|
typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "websockets"
|
name = "websockets"
|
||||||
version = "12.0"
|
version = "12.0"
|
||||||
@@ -2037,4 +2176,4 @@ type = ["pytest-mypy"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = ">=3.10,<4.0"
|
python-versions = ">=3.10,<4.0"
|
||||||
content-hash = "78ebf65cdef769cfbe92fe204f01e32d219cca9ee5a6ca9e657aa0630be63802"
|
content-hash = "d92143928a88ca3a56ac200c335910eafac938940022fed8bd0d17c95040b54f"
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ pyjwt = "^2.10.1"
|
|||||||
pytest-asyncio = "^0.26.0"
|
pytest-asyncio = "^0.26.0"
|
||||||
pytest-mock = "^3.14.0"
|
pytest-mock = "^3.14.0"
|
||||||
supabase = "^2.15.1"
|
supabase = "^2.15.1"
|
||||||
|
launchdarkly-server-sdk = "^9.11.1"
|
||||||
|
fastapi = "^0.115.12"
|
||||||
|
uvicorn = "^0.34.3"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
redis = "^5.2.1"
|
redis = "^5.2.1"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from backend.blocks.exa._auth import (
|
|||||||
)
|
)
|
||||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||||
from backend.data.model import SchemaField
|
from backend.data.model import SchemaField
|
||||||
from backend.util.request import requests
|
from backend.util.request import Requests
|
||||||
|
|
||||||
|
|
||||||
class ContentRetrievalSettings(BaseModel):
|
class ContentRetrievalSettings(BaseModel):
|
||||||
@@ -79,7 +79,7 @@ class ExaContentsBlock(Block):
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(url, headers=headers, json=payload)
|
response = Requests().post(url, headers=headers, json=payload)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = response.json()
|
data = response.json()
|
||||||
yield "results", data.get("results", [])
|
yield "results", data.get("results", [])
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from backend.blocks.exa._auth import (
|
|||||||
from backend.blocks.exa.helpers import ContentSettings
|
from backend.blocks.exa.helpers import ContentSettings
|
||||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||||
from backend.data.model import SchemaField
|
from backend.data.model import SchemaField
|
||||||
from backend.util.request import requests
|
from backend.util.request import Requests
|
||||||
|
|
||||||
|
|
||||||
class ExaSearchBlock(Block):
|
class ExaSearchBlock(Block):
|
||||||
@@ -136,7 +136,7 @@ class ExaSearchBlock(Block):
|
|||||||
payload[api_field] = value
|
payload[api_field] = value
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(url, headers=headers, json=payload)
|
response = Requests().post(url, headers=headers, json=payload)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = response.json()
|
data = response.json()
|
||||||
# Extract just the results array from the response
|
# Extract just the results array from the response
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from backend.blocks.exa._auth import (
|
|||||||
)
|
)
|
||||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||||
from backend.data.model import SchemaField
|
from backend.data.model import SchemaField
|
||||||
from backend.util.request import requests
|
from backend.util.request import Requests
|
||||||
|
|
||||||
from .helpers import ContentSettings
|
from .helpers import ContentSettings
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ class ExaFindSimilarBlock(Block):
|
|||||||
payload[api_field] = value.strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
payload[api_field] = value.strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(url, headers=headers, json=payload)
|
response = Requests().post(url, headers=headers, json=payload)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = response.json()
|
data = response.json()
|
||||||
yield "results", data.get("results", [])
|
yield "results", data.get("results", [])
|
||||||
|
|||||||
@@ -114,9 +114,24 @@ class SendWebRequestBlock(Block):
|
|||||||
body = input_data.body
|
body = input_data.body
|
||||||
if isinstance(body, str):
|
if isinstance(body, str):
|
||||||
try:
|
try:
|
||||||
body = json.loads(body)
|
# Validate JSON string length to prevent DoS attacks
|
||||||
except json.JSONDecodeError:
|
if len(body) > 10_000_000: # 10MB limit
|
||||||
# plain text – treat as form‑field value instead
|
raise ValueError("JSON body too large")
|
||||||
|
|
||||||
|
parsed_body = json.loads(body)
|
||||||
|
|
||||||
|
# Validate that parsed JSON is safe (basic object/array/primitive types)
|
||||||
|
if (
|
||||||
|
isinstance(parsed_body, (dict, list, str, int, float, bool))
|
||||||
|
or parsed_body is None
|
||||||
|
):
|
||||||
|
body = parsed_body
|
||||||
|
else:
|
||||||
|
# Unexpected type, treat as plain text
|
||||||
|
input_data.json_format = False
|
||||||
|
|
||||||
|
except (json.JSONDecodeError, ValueError):
|
||||||
|
# Invalid JSON or too large – treat as form‑field value instead
|
||||||
input_data.json_format = False
|
input_data.json_format = False
|
||||||
|
|
||||||
# ─── Prepare files (if any) ──────────────────────────────────
|
# ─── Prepare files (if any) ──────────────────────────────────
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from backend.blocks.hubspot._auth import (
|
|||||||
)
|
)
|
||||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||||
from backend.data.model import SchemaField
|
from backend.data.model import SchemaField
|
||||||
from backend.util.request import requests
|
from backend.util.request import Requests
|
||||||
|
|
||||||
|
|
||||||
class HubSpotCompanyBlock(Block):
|
class HubSpotCompanyBlock(Block):
|
||||||
@@ -45,7 +45,7 @@ class HubSpotCompanyBlock(Block):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if input_data.operation == "create":
|
if input_data.operation == "create":
|
||||||
response = requests.post(
|
response = Requests().post(
|
||||||
base_url, headers=headers, json={"properties": input_data.company_data}
|
base_url, headers=headers, json={"properties": input_data.company_data}
|
||||||
)
|
)
|
||||||
result = response.json()
|
result = response.json()
|
||||||
@@ -67,14 +67,14 @@ class HubSpotCompanyBlock(Block):
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
response = requests.post(search_url, headers=headers, json=search_data)
|
response = Requests().post(search_url, headers=headers, json=search_data)
|
||||||
result = response.json()
|
result = response.json()
|
||||||
yield "company", result.get("results", [{}])[0]
|
yield "company", result.get("results", [{}])[0]
|
||||||
yield "status", "retrieved"
|
yield "status", "retrieved"
|
||||||
|
|
||||||
elif input_data.operation == "update":
|
elif input_data.operation == "update":
|
||||||
# First get company ID by domain
|
# First get company ID by domain
|
||||||
search_response = requests.post(
|
search_response = Requests().post(
|
||||||
f"{base_url}/search",
|
f"{base_url}/search",
|
||||||
headers=headers,
|
headers=headers,
|
||||||
json={
|
json={
|
||||||
@@ -94,7 +94,7 @@ class HubSpotCompanyBlock(Block):
|
|||||||
company_id = search_response.json().get("results", [{}])[0].get("id")
|
company_id = search_response.json().get("results", [{}])[0].get("id")
|
||||||
|
|
||||||
if company_id:
|
if company_id:
|
||||||
response = requests.patch(
|
response = Requests().patch(
|
||||||
f"{base_url}/{company_id}",
|
f"{base_url}/{company_id}",
|
||||||
headers=headers,
|
headers=headers,
|
||||||
json={"properties": input_data.company_data},
|
json={"properties": input_data.company_data},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from backend.blocks.hubspot._auth import (
|
|||||||
)
|
)
|
||||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||||
from backend.data.model import SchemaField
|
from backend.data.model import SchemaField
|
||||||
from backend.util.request import requests
|
from backend.util.request import Requests
|
||||||
|
|
||||||
|
|
||||||
class HubSpotContactBlock(Block):
|
class HubSpotContactBlock(Block):
|
||||||
@@ -45,7 +45,7 @@ class HubSpotContactBlock(Block):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if input_data.operation == "create":
|
if input_data.operation == "create":
|
||||||
response = requests.post(
|
response = Requests().post(
|
||||||
base_url, headers=headers, json={"properties": input_data.contact_data}
|
base_url, headers=headers, json={"properties": input_data.contact_data}
|
||||||
)
|
)
|
||||||
result = response.json()
|
result = response.json()
|
||||||
@@ -68,13 +68,13 @@ class HubSpotContactBlock(Block):
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
response = requests.post(search_url, headers=headers, json=search_data)
|
response = Requests().post(search_url, headers=headers, json=search_data)
|
||||||
result = response.json()
|
result = response.json()
|
||||||
yield "contact", result.get("results", [{}])[0]
|
yield "contact", result.get("results", [{}])[0]
|
||||||
yield "status", "retrieved"
|
yield "status", "retrieved"
|
||||||
|
|
||||||
elif input_data.operation == "update":
|
elif input_data.operation == "update":
|
||||||
search_response = requests.post(
|
search_response = Requests().post(
|
||||||
f"{base_url}/search",
|
f"{base_url}/search",
|
||||||
headers=headers,
|
headers=headers,
|
||||||
json={
|
json={
|
||||||
@@ -94,7 +94,7 @@ class HubSpotContactBlock(Block):
|
|||||||
contact_id = search_response.json().get("results", [{}])[0].get("id")
|
contact_id = search_response.json().get("results", [{}])[0].get("id")
|
||||||
|
|
||||||
if contact_id:
|
if contact_id:
|
||||||
response = requests.patch(
|
response = Requests().patch(
|
||||||
f"{base_url}/{contact_id}",
|
f"{base_url}/{contact_id}",
|
||||||
headers=headers,
|
headers=headers,
|
||||||
json={"properties": input_data.contact_data},
|
json={"properties": input_data.contact_data},
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from backend.blocks.hubspot._auth import (
|
|||||||
)
|
)
|
||||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||||
from backend.data.model import SchemaField
|
from backend.data.model import SchemaField
|
||||||
from backend.util.request import requests
|
from backend.util.request import Requests
|
||||||
|
|
||||||
|
|
||||||
class HubSpotEngagementBlock(Block):
|
class HubSpotEngagementBlock(Block):
|
||||||
@@ -66,7 +66,7 @@ class HubSpotEngagementBlock(Block):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(email_url, headers=headers, json=email_data)
|
response = Requests().post(email_url, headers=headers, json=email_data)
|
||||||
result = response.json()
|
result = response.json()
|
||||||
yield "result", result
|
yield "result", result
|
||||||
yield "status", "email_sent"
|
yield "status", "email_sent"
|
||||||
@@ -80,7 +80,7 @@ class HubSpotEngagementBlock(Block):
|
|||||||
|
|
||||||
params = {"limit": 100, "after": from_date.isoformat()}
|
params = {"limit": 100, "after": from_date.isoformat()}
|
||||||
|
|
||||||
response = requests.get(engagement_url, headers=headers, params=params)
|
response = Requests().get(engagement_url, headers=headers, params=params)
|
||||||
engagements = response.json()
|
engagements = response.json()
|
||||||
|
|
||||||
# Process engagement metrics
|
# Process engagement metrics
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from backend.data.model import (
|
|||||||
SchemaField,
|
SchemaField,
|
||||||
)
|
)
|
||||||
from backend.integrations.providers import ProviderName
|
from backend.integrations.providers import ProviderName
|
||||||
from backend.util.request import requests
|
from backend.util.request import Requests
|
||||||
|
|
||||||
TEST_CREDENTIALS = APIKeyCredentials(
|
TEST_CREDENTIALS = APIKeyCredentials(
|
||||||
id="01234567-89ab-cdef-0123-456789abcdef",
|
id="01234567-89ab-cdef-0123-456789abcdef",
|
||||||
@@ -267,7 +267,7 @@ class IdeogramModelBlock(Block):
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(url, json=data, headers=headers)
|
response = Requests().post(url, json=data, headers=headers)
|
||||||
return response.json()["data"][0]["url"]
|
return response.json()["data"][0]["url"]
|
||||||
except RequestException as e:
|
except RequestException as e:
|
||||||
raise Exception(f"Failed to fetch image: {str(e)}")
|
raise Exception(f"Failed to fetch image: {str(e)}")
|
||||||
@@ -280,14 +280,14 @@ class IdeogramModelBlock(Block):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Step 1: Download the image from the provided URL
|
# Step 1: Download the image from the provided URL
|
||||||
image_response = requests.get(image_url)
|
image_response = Requests().get(image_url)
|
||||||
|
|
||||||
# Step 2: Send the downloaded image to the upscale API
|
# Step 2: Send the downloaded image to the upscale API
|
||||||
files = {
|
files = {
|
||||||
"image_file": ("image.png", image_response.content, "image/png"),
|
"image_file": ("image.png", image_response.content, "image/png"),
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(
|
response = Requests().post(
|
||||||
url,
|
url,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
data={"image_request": "{}"},
|
data={"image_request": "{}"},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from backend.blocks.jina._auth import (
|
|||||||
)
|
)
|
||||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||||
from backend.data.model import SchemaField
|
from backend.data.model import SchemaField
|
||||||
from backend.util.request import requests
|
from backend.util.request import Requests
|
||||||
|
|
||||||
|
|
||||||
class JinaChunkingBlock(Block):
|
class JinaChunkingBlock(Block):
|
||||||
@@ -55,7 +55,7 @@ class JinaChunkingBlock(Block):
|
|||||||
"max_chunk_length": str(input_data.max_chunk_length),
|
"max_chunk_length": str(input_data.max_chunk_length),
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(url, headers=headers, json=data)
|
response = Requests().post(url, headers=headers, json=data)
|
||||||
result = response.json()
|
result = response.json()
|
||||||
|
|
||||||
all_chunks.extend(result.get("chunks", []))
|
all_chunks.extend(result.get("chunks", []))
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from backend.blocks.jina._auth import (
|
|||||||
)
|
)
|
||||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||||
from backend.data.model import SchemaField
|
from backend.data.model import SchemaField
|
||||||
from backend.util.request import requests
|
from backend.util.request import Requests
|
||||||
|
|
||||||
|
|
||||||
class JinaEmbeddingBlock(Block):
|
class JinaEmbeddingBlock(Block):
|
||||||
@@ -38,6 +38,6 @@ class JinaEmbeddingBlock(Block):
|
|||||||
"Authorization": f"Bearer {credentials.api_key.get_secret_value()}",
|
"Authorization": f"Bearer {credentials.api_key.get_secret_value()}",
|
||||||
}
|
}
|
||||||
data = {"input": input_data.texts, "model": input_data.model}
|
data = {"input": input_data.texts, "model": input_data.model}
|
||||||
response = requests.post(url, headers=headers, json=data)
|
response = Requests().post(url, headers=headers, json=data)
|
||||||
embeddings = [e["embedding"] for e in response.json()["data"]]
|
embeddings = [e["embedding"] for e in response.json()["data"]]
|
||||||
yield "embeddings", embeddings
|
yield "embeddings", embeddings
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from backend.blocks.jina._auth import (
|
from backend.blocks.jina._auth import (
|
||||||
JinaCredentials,
|
JinaCredentials,
|
||||||
JinaCredentialsField,
|
JinaCredentialsField,
|
||||||
@@ -9,6 +7,7 @@ from backend.blocks.jina._auth import (
|
|||||||
)
|
)
|
||||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||||
from backend.data.model import SchemaField
|
from backend.data.model import SchemaField
|
||||||
|
from backend.util.request import requests
|
||||||
|
|
||||||
|
|
||||||
class FactCheckerBlock(Block):
|
class FactCheckerBlock(Block):
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from backend.data.model import (
|
|||||||
SecretField,
|
SecretField,
|
||||||
)
|
)
|
||||||
from backend.integrations.providers import ProviderName
|
from backend.integrations.providers import ProviderName
|
||||||
from backend.util.request import requests
|
from backend.util.request import Requests
|
||||||
|
|
||||||
TEST_CREDENTIALS = APIKeyCredentials(
|
TEST_CREDENTIALS = APIKeyCredentials(
|
||||||
id="01234567-89ab-cdef-0123-456789abcdef",
|
id="01234567-89ab-cdef-0123-456789abcdef",
|
||||||
@@ -160,7 +160,7 @@ class PublishToMediumBlock(Block):
|
|||||||
"notifyFollowers": notify_followers,
|
"notifyFollowers": notify_followers,
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(
|
response = Requests().post(
|
||||||
f"https://api.medium.com/v1/users/{author_id}/posts",
|
f"https://api.medium.com/v1/users/{author_id}/posts",
|
||||||
headers=headers,
|
headers=headers,
|
||||||
json=data,
|
json=data,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from backend.blocks.nvidia._auth import (
|
|||||||
)
|
)
|
||||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||||
from backend.data.model import SchemaField
|
from backend.data.model import SchemaField
|
||||||
from backend.util.request import requests
|
from backend.util.request import Requests
|
||||||
from backend.util.type import MediaFileType
|
from backend.util.type import MediaFileType
|
||||||
|
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ class NvidiaDeepfakeDetectBlock(Block):
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(url, headers=headers, json=payload)
|
response = Requests().post(url, headers=headers, json=payload)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
|
|||||||
@@ -121,11 +121,10 @@ class ScreenshotWebPageBlock(Block):
|
|||||||
"""
|
"""
|
||||||
Takes a screenshot using the ScreenshotOne API
|
Takes a screenshot using the ScreenshotOne API
|
||||||
"""
|
"""
|
||||||
api = Requests(trusted_origins=["https://api.screenshotone.com"])
|
api = Requests()
|
||||||
|
|
||||||
# Build API URL with parameters
|
# Build API parameters
|
||||||
params = {
|
params = {
|
||||||
"access_key": credentials.api_key.get_secret_value(),
|
|
||||||
"url": url,
|
"url": url,
|
||||||
"viewport_width": viewport_width,
|
"viewport_width": viewport_width,
|
||||||
"viewport_height": viewport_height,
|
"viewport_height": viewport_height,
|
||||||
@@ -137,7 +136,14 @@ class ScreenshotWebPageBlock(Block):
|
|||||||
"cache": str(cache).lower(),
|
"cache": str(cache).lower(),
|
||||||
}
|
}
|
||||||
|
|
||||||
response = api.get("https://api.screenshotone.com/take", params=params)
|
# Use header-based authentication instead of query parameter
|
||||||
|
headers = {
|
||||||
|
"X-Access-Key": credentials.api_key.get_secret_value(),
|
||||||
|
}
|
||||||
|
|
||||||
|
response = api.get(
|
||||||
|
"https://api.screenshotone.com/take", params=params, headers=headers
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"image": store_media_file(
|
"image": store_media_file(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from backend.data.block import Block
|
from backend.data.block import Block
|
||||||
from backend.util.request import requests
|
from backend.util.request import Requests
|
||||||
|
|
||||||
from ._api import Color, CustomerDetails, OrderItem, Profile
|
from ._api import Color, CustomerDetails, OrderItem, Profile
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ class Slant3DBlockBase(Block):
|
|||||||
|
|
||||||
def _make_request(self, method: str, endpoint: str, api_key: str, **kwargs) -> Dict:
|
def _make_request(self, method: str, endpoint: str, api_key: str, **kwargs) -> Dict:
|
||||||
url = f"{self.BASE_URL}/{endpoint}"
|
url = f"{self.BASE_URL}/{endpoint}"
|
||||||
response = requests.request(
|
response = Requests().request(
|
||||||
method=method, url=url, headers=self._get_headers(api_key), **kwargs
|
method=method, url=url, headers=self._get_headers(api_key), **kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import requests as baserequests
|
|
||||||
|
|
||||||
from backend.data.block import BlockOutput, BlockSchema
|
from backend.data.block import BlockOutput, BlockSchema
|
||||||
from backend.data.model import APIKeyCredentials, SchemaField
|
from backend.data.model import APIKeyCredentials, SchemaField
|
||||||
from backend.util import settings
|
from backend.util import settings
|
||||||
|
from backend.util.request import req
|
||||||
from backend.util.settings import BehaveAs
|
from backend.util.settings import BehaveAs
|
||||||
|
|
||||||
from ._api import (
|
from ._api import (
|
||||||
@@ -181,7 +180,7 @@ class Slant3DEstimateOrderBlock(Slant3DBlockBase):
|
|||||||
yield "total_price", result["totalPrice"]
|
yield "total_price", result["totalPrice"]
|
||||||
yield "shipping_cost", result["shippingCost"]
|
yield "shipping_cost", result["shippingCost"]
|
||||||
yield "printing_cost", result["printingCost"]
|
yield "printing_cost", result["printingCost"]
|
||||||
except baserequests.HTTPError as e:
|
except req.HTTPError as e:
|
||||||
yield "error", str(f"Error estimating order: {e} {e.response.text}")
|
yield "error", str(f"Error estimating order: {e} {e.response.text}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|||||||
@@ -121,16 +121,17 @@ def reddit(server_address: str):
|
|||||||
"""
|
"""
|
||||||
Create an event graph
|
Create an event graph
|
||||||
"""
|
"""
|
||||||
import requests
|
|
||||||
|
|
||||||
from backend.usecases.reddit_marketing import create_test_graph
|
from backend.usecases.reddit_marketing import create_test_graph
|
||||||
|
from backend.util.request import Requests
|
||||||
|
|
||||||
test_graph = create_test_graph()
|
test_graph = create_test_graph()
|
||||||
url = f"{server_address}/graphs"
|
url = f"{server_address}/graphs"
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
data = test_graph.model_dump_json()
|
data = test_graph.model_dump_json()
|
||||||
|
|
||||||
response = requests.post(url, headers=headers, data=data)
|
response = Requests(trusted_origins=[server_address]).post(
|
||||||
|
url, headers=headers, data=data
|
||||||
|
)
|
||||||
|
|
||||||
graph_id = response.json()["id"]
|
graph_id = response.json()["id"]
|
||||||
print(f"Graph created with ID: {graph_id}")
|
print(f"Graph created with ID: {graph_id}")
|
||||||
@@ -142,16 +143,18 @@ def populate_db(server_address: str):
|
|||||||
"""
|
"""
|
||||||
Create an event graph
|
Create an event graph
|
||||||
"""
|
"""
|
||||||
import requests
|
|
||||||
|
|
||||||
from backend.usecases.sample import create_test_graph
|
from backend.usecases.sample import create_test_graph
|
||||||
|
from backend.util.request import Requests
|
||||||
|
|
||||||
test_graph = create_test_graph()
|
test_graph = create_test_graph()
|
||||||
url = f"{server_address}/graphs"
|
url = f"{server_address}/graphs"
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
data = test_graph.model_dump_json()
|
data = test_graph.model_dump_json()
|
||||||
|
|
||||||
response = requests.post(url, headers=headers, data=data)
|
response = Requests(trusted_origins=[server_address]).post(
|
||||||
|
url, headers=headers, data=data
|
||||||
|
)
|
||||||
|
|
||||||
graph_id = response.json()["id"]
|
graph_id = response.json()["id"]
|
||||||
|
|
||||||
@@ -159,7 +162,9 @@ def populate_db(server_address: str):
|
|||||||
execute_url = f"{server_address}/graphs/{response.json()['id']}/execute"
|
execute_url = f"{server_address}/graphs/{response.json()['id']}/execute"
|
||||||
text = "Hello, World!"
|
text = "Hello, World!"
|
||||||
input_data = {"input": text}
|
input_data = {"input": text}
|
||||||
response = requests.post(execute_url, headers=headers, json=input_data)
|
response = Requests(trusted_origins=[server_address]).post(
|
||||||
|
execute_url, headers=headers, json=input_data
|
||||||
|
)
|
||||||
|
|
||||||
schedule_url = f"{server_address}/graphs/{graph_id}/schedules"
|
schedule_url = f"{server_address}/graphs/{graph_id}/schedules"
|
||||||
data = {
|
data = {
|
||||||
@@ -167,7 +172,9 @@ def populate_db(server_address: str):
|
|||||||
"cron": "*/5 * * * *",
|
"cron": "*/5 * * * *",
|
||||||
"input_data": {"input": "Hello, World!"},
|
"input_data": {"input": "Hello, World!"},
|
||||||
}
|
}
|
||||||
response = requests.post(schedule_url, headers=headers, json=data)
|
response = Requests(trusted_origins=[server_address]).post(
|
||||||
|
schedule_url, headers=headers, json=data
|
||||||
|
)
|
||||||
|
|
||||||
print("Database populated with: \n- graph\n- execution\n- schedule")
|
print("Database populated with: \n- graph\n- execution\n- schedule")
|
||||||
|
|
||||||
@@ -178,21 +185,25 @@ def graph(server_address: str):
|
|||||||
"""
|
"""
|
||||||
Create an event graph
|
Create an event graph
|
||||||
"""
|
"""
|
||||||
import requests
|
|
||||||
|
|
||||||
from backend.usecases.sample import create_test_graph
|
from backend.usecases.sample import create_test_graph
|
||||||
|
from backend.util.request import Requests
|
||||||
|
|
||||||
url = f"{server_address}/graphs"
|
url = f"{server_address}/graphs"
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
data = create_test_graph().model_dump_json()
|
data = create_test_graph().model_dump_json()
|
||||||
response = requests.post(url, headers=headers, data=data)
|
response = Requests(trusted_origins=[server_address]).post(
|
||||||
|
url, headers=headers, data=data
|
||||||
|
)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
print(response.json()["id"])
|
print(response.json()["id"])
|
||||||
execute_url = f"{server_address}/graphs/{response.json()['id']}/execute"
|
execute_url = f"{server_address}/graphs/{response.json()['id']}/execute"
|
||||||
text = "Hello, World!"
|
text = "Hello, World!"
|
||||||
input_data = {"input": text}
|
input_data = {"input": text}
|
||||||
response = requests.post(execute_url, headers=headers, json=input_data)
|
response = Requests(trusted_origins=[server_address]).post(
|
||||||
|
execute_url, headers=headers, json=input_data
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("Failed to send graph")
|
print("Failed to send graph")
|
||||||
@@ -206,12 +217,15 @@ def execute(graph_id: str, content: dict):
|
|||||||
"""
|
"""
|
||||||
Create an event graph
|
Create an event graph
|
||||||
"""
|
"""
|
||||||
import requests
|
|
||||||
|
from backend.util.request import Requests
|
||||||
|
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
|
|
||||||
execute_url = f"http://0.0.0.0:8000/graphs/{graph_id}/execute"
|
execute_url = f"http://0.0.0.0:8000/graphs/{graph_id}/execute"
|
||||||
requests.post(execute_url, headers=headers, json=content)
|
Requests(trusted_origins=["http://0.0.0.0:8000"]).post(
|
||||||
|
execute_url, headers=headers, json=content
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@test.command()
|
@test.command()
|
||||||
|
|||||||
@@ -398,7 +398,7 @@ class IntegrationCredentialsStore:
|
|||||||
(
|
(
|
||||||
state
|
state
|
||||||
for state in oauth_states
|
for state in oauth_states
|
||||||
if state.token == token
|
if secrets.compare_digest(state.token, token)
|
||||||
and state.provider == provider
|
and state.provider == provider
|
||||||
and state.expires_at > now.timestamp()
|
and state.expires_at > now.timestamp()
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from urllib.parse import urlencode
|
|||||||
|
|
||||||
from backend.data.model import OAuth2Credentials
|
from backend.data.model import OAuth2Credentials
|
||||||
from backend.integrations.providers import ProviderName
|
from backend.integrations.providers import ProviderName
|
||||||
from backend.util.request import requests
|
from backend.util.request import Requests
|
||||||
|
|
||||||
from .base import BaseOAuthHandler
|
from .base import BaseOAuthHandler
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ class GitHubOAuthHandler(BaseOAuthHandler):
|
|||||||
"X-GitHub-Api-Version": "2022-11-28",
|
"X-GitHub-Api-Version": "2022-11-28",
|
||||||
}
|
}
|
||||||
|
|
||||||
requests.delete(
|
Requests().delete(
|
||||||
url=self.revoke_url.format(client_id=self.client_id),
|
url=self.revoke_url.format(client_id=self.client_id),
|
||||||
auth=(self.client_id, self.client_secret),
|
auth=(self.client_id, self.client_secret),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
@@ -89,7 +89,7 @@ class GitHubOAuthHandler(BaseOAuthHandler):
|
|||||||
**params,
|
**params,
|
||||||
}
|
}
|
||||||
headers = {"Accept": "application/json"}
|
headers = {"Accept": "application/json"}
|
||||||
response = requests.post(self.token_url, data=request_body, headers=headers)
|
response = Requests().post(self.token_url, data=request_body, headers=headers)
|
||||||
token_data: dict = response.json()
|
token_data: dict = response.json()
|
||||||
|
|
||||||
username = self._request_username(token_data["access_token"])
|
username = self._request_username(token_data["access_token"])
|
||||||
@@ -132,7 +132,7 @@ class GitHubOAuthHandler(BaseOAuthHandler):
|
|||||||
"X-GitHub-Api-Version": "2022-11-28",
|
"X-GitHub-Api-Version": "2022-11-28",
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.get(url, headers=headers)
|
response = Requests().get(url, headers=headers)
|
||||||
|
|
||||||
if not response.ok:
|
if not response.ok:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class GoogleOAuthHandler(BaseOAuthHandler):
|
|||||||
logger.debug(f"Scopes granted by Google: {granted_scopes}")
|
logger.debug(f"Scopes granted by Google: {granted_scopes}")
|
||||||
|
|
||||||
google_creds = flow.credentials
|
google_creds = flow.credentials
|
||||||
logger.debug(f"Received credentials: {google_creds}")
|
logger.debug("Received credentials")
|
||||||
|
|
||||||
logger.debug("Requesting user email")
|
logger.debug("Requesting user email")
|
||||||
username = self._request_email(google_creds)
|
username = self._request_email(google_creds)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from pydantic import SecretStr
|
|||||||
from backend.blocks.linear._api import LinearAPIException
|
from backend.blocks.linear._api import LinearAPIException
|
||||||
from backend.data.model import APIKeyCredentials, OAuth2Credentials
|
from backend.data.model import APIKeyCredentials, OAuth2Credentials
|
||||||
from backend.integrations.providers import ProviderName
|
from backend.integrations.providers import ProviderName
|
||||||
from backend.util.request import requests
|
from backend.util.request import Requests
|
||||||
|
|
||||||
from .base import BaseOAuthHandler
|
from .base import BaseOAuthHandler
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ class LinearOAuthHandler(BaseOAuthHandler):
|
|||||||
"Authorization": f"Bearer {credentials.access_token.get_secret_value()}"
|
"Authorization": f"Bearer {credentials.access_token.get_secret_value()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(self.revoke_url, headers=headers)
|
response = Requests().post(self.revoke_url, headers=headers)
|
||||||
if not response.ok:
|
if not response.ok:
|
||||||
try:
|
try:
|
||||||
error_data = response.json()
|
error_data = response.json()
|
||||||
@@ -95,7 +95,7 @@ class LinearOAuthHandler(BaseOAuthHandler):
|
|||||||
headers = {
|
headers = {
|
||||||
"Content-Type": "application/x-www-form-urlencoded"
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
} # Correct header for token request
|
} # Correct header for token request
|
||||||
response = requests.post(self.token_url, data=request_body, headers=headers)
|
response = Requests().post(self.token_url, data=request_body, headers=headers)
|
||||||
|
|
||||||
if not response.ok:
|
if not response.ok:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from urllib.parse import urlencode
|
|||||||
|
|
||||||
from backend.data.model import OAuth2Credentials
|
from backend.data.model import OAuth2Credentials
|
||||||
from backend.integrations.providers import ProviderName
|
from backend.integrations.providers import ProviderName
|
||||||
from backend.util.request import requests
|
from backend.util.request import Requests
|
||||||
|
|
||||||
from .base import BaseOAuthHandler
|
from .base import BaseOAuthHandler
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ class NotionOAuthHandler(BaseOAuthHandler):
|
|||||||
"Authorization": f"Basic {auth_str}",
|
"Authorization": f"Basic {auth_str}",
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
}
|
}
|
||||||
response = requests.post(self.token_url, json=request_body, headers=headers)
|
response = Requests().post(self.token_url, json=request_body, headers=headers)
|
||||||
token_data = response.json()
|
token_data = response.json()
|
||||||
# Email is only available for non-bot users
|
# Email is only available for non-bot users
|
||||||
email = (
|
email = (
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import urllib.parse
|
import urllib.parse
|
||||||
from typing import ClassVar, Optional
|
from typing import ClassVar, Optional
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from backend.data.model import OAuth2Credentials, ProviderName
|
from backend.data.model import OAuth2Credentials, ProviderName
|
||||||
from backend.integrations.oauth.base import BaseOAuthHandler
|
from backend.integrations.oauth.base import BaseOAuthHandler
|
||||||
|
from backend.util.request import Requests
|
||||||
|
|
||||||
|
|
||||||
class TodoistOAuthHandler(BaseOAuthHandler):
|
class TodoistOAuthHandler(BaseOAuthHandler):
|
||||||
@@ -48,12 +47,12 @@ class TodoistOAuthHandler(BaseOAuthHandler):
|
|||||||
"redirect_uri": self.redirect_uri,
|
"redirect_uri": self.redirect_uri,
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(self.TOKEN_URL, data=data)
|
response = Requests().post(self.TOKEN_URL, data=data)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
tokens = response.json()
|
tokens = response.json()
|
||||||
|
|
||||||
response = requests.post(
|
response = Requests().post(
|
||||||
"https://api.todoist.com/sync/v9/sync",
|
"https://api.todoist.com/sync/v9/sync",
|
||||||
headers={"Authorization": f"Bearer {tokens['access_token']}"},
|
headers={"Authorization": f"Bearer {tokens['access_token']}"},
|
||||||
data={"sync_token": "*", "resource_types": '["user"]'},
|
data={"sync_token": "*", "resource_types": '["user"]'},
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import time
|
|||||||
import urllib.parse
|
import urllib.parse
|
||||||
from typing import ClassVar, Optional
|
from typing import ClassVar, Optional
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from backend.data.model import OAuth2Credentials, ProviderName
|
from backend.data.model import OAuth2Credentials, ProviderName
|
||||||
from backend.integrations.oauth.base import BaseOAuthHandler
|
from backend.integrations.oauth.base import BaseOAuthHandler
|
||||||
|
from backend.util.request import Requests, req
|
||||||
|
|
||||||
|
|
||||||
class TwitterOAuthHandler(BaseOAuthHandler):
|
class TwitterOAuthHandler(BaseOAuthHandler):
|
||||||
@@ -78,7 +77,9 @@ class TwitterOAuthHandler(BaseOAuthHandler):
|
|||||||
|
|
||||||
auth = (self.client_id, self.client_secret)
|
auth = (self.client_id, self.client_secret)
|
||||||
|
|
||||||
response = requests.post(self.TOKEN_URL, headers=headers, data=data, auth=auth)
|
response = Requests().post(
|
||||||
|
self.TOKEN_URL, headers=headers, data=data, auth=auth
|
||||||
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
tokens = response.json()
|
tokens = response.json()
|
||||||
@@ -102,7 +103,7 @@ class TwitterOAuthHandler(BaseOAuthHandler):
|
|||||||
|
|
||||||
params = {"user.fields": "username"}
|
params = {"user.fields": "username"}
|
||||||
|
|
||||||
response = requests.get(
|
response = Requests().get(
|
||||||
f"{self.USERNAME_URL}?{urllib.parse.urlencode(params)}", headers=headers
|
f"{self.USERNAME_URL}?{urllib.parse.urlencode(params)}", headers=headers
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
@@ -122,13 +123,12 @@ class TwitterOAuthHandler(BaseOAuthHandler):
|
|||||||
|
|
||||||
auth = (self.client_id, self.client_secret)
|
auth = (self.client_id, self.client_secret)
|
||||||
|
|
||||||
response = requests.post(self.TOKEN_URL, headers=header, data=data, auth=auth)
|
response = Requests().post(self.TOKEN_URL, headers=header, data=data, auth=auth)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except requests.exceptions.HTTPError as e:
|
except req.exceptions.HTTPError:
|
||||||
print("HTTP Error:", e)
|
print(f"HTTP Error: {response.status_code}")
|
||||||
print("Response Content:", response.text)
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
tokens = response.json()
|
tokens = response.json()
|
||||||
@@ -159,13 +159,14 @@ class TwitterOAuthHandler(BaseOAuthHandler):
|
|||||||
|
|
||||||
auth = (self.client_id, self.client_secret)
|
auth = (self.client_id, self.client_secret)
|
||||||
|
|
||||||
response = requests.post(self.REVOKE_URL, headers=header, data=data, auth=auth)
|
response = Requests().post(
|
||||||
|
self.REVOKE_URL, headers=header, data=data, auth=auth
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except requests.exceptions.HTTPError as e:
|
except req.exceptions.HTTPError:
|
||||||
print("HTTP Error:", e)
|
print(f"HTTP Error: {response.status_code}")
|
||||||
print("Response Content:", response.text)
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return response.status_code == 200
|
return response.status_code == 200
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import hashlib
|
|||||||
import hmac
|
import hmac
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests
|
|
||||||
from fastapi import HTTPException, Request
|
from fastapi import HTTPException, Request
|
||||||
from strenum import StrEnum
|
from strenum import StrEnum
|
||||||
|
|
||||||
from backend.data import integrations
|
from backend.data import integrations
|
||||||
from backend.data.model import Credentials
|
from backend.data.model import Credentials
|
||||||
from backend.integrations.providers import ProviderName
|
from backend.integrations.providers import ProviderName
|
||||||
|
from backend.util.request import Requests, req
|
||||||
|
|
||||||
from ._base import BaseWebhooksManager
|
from ._base import BaseWebhooksManager
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ class GithubWebhooksManager(BaseWebhooksManager):
|
|||||||
repo, github_hook_id = webhook.resource, webhook.provider_webhook_id
|
repo, github_hook_id = webhook.resource, webhook.provider_webhook_id
|
||||||
ping_url = f"{self.GITHUB_API_URL}/repos/{repo}/hooks/{github_hook_id}/pings"
|
ping_url = f"{self.GITHUB_API_URL}/repos/{repo}/hooks/{github_hook_id}/pings"
|
||||||
|
|
||||||
response = requests.post(ping_url, headers=headers)
|
response = Requests().post(ping_url, headers=headers)
|
||||||
|
|
||||||
if response.status_code != 204:
|
if response.status_code != 204:
|
||||||
error_msg = extract_github_error_msg(response)
|
error_msg = extract_github_error_msg(response)
|
||||||
@@ -110,7 +110,7 @@ class GithubWebhooksManager(BaseWebhooksManager):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(
|
response = Requests().post(
|
||||||
f"{self.GITHUB_API_URL}/repos/{resource}/hooks",
|
f"{self.GITHUB_API_URL}/repos/{resource}/hooks",
|
||||||
headers=headers,
|
headers=headers,
|
||||||
json=webhook_data,
|
json=webhook_data,
|
||||||
@@ -153,7 +153,7 @@ class GithubWebhooksManager(BaseWebhooksManager):
|
|||||||
f"Unsupported webhook type '{webhook.webhook_type}'"
|
f"Unsupported webhook type '{webhook.webhook_type}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
response = requests.delete(delete_url, headers=headers)
|
response = Requests().delete(delete_url, headers=headers)
|
||||||
|
|
||||||
if response.status_code not in [204, 404]:
|
if response.status_code not in [204, 404]:
|
||||||
# 204 means successful deletion, 404 means the webhook was already deleted
|
# 204 means successful deletion, 404 means the webhook was already deleted
|
||||||
@@ -166,7 +166,7 @@ class GithubWebhooksManager(BaseWebhooksManager):
|
|||||||
# --8<-- [end:GithubWebhooksManager]
|
# --8<-- [end:GithubWebhooksManager]
|
||||||
|
|
||||||
|
|
||||||
def extract_github_error_msg(response: requests.Response) -> str:
|
def extract_github_error_msg(response: req.Response) -> str:
|
||||||
error_msgs = []
|
error_msgs = []
|
||||||
resp = response.json()
|
resp = response.json()
|
||||||
if resp.get("message"):
|
if resp.get("message"):
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests
|
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
|
|
||||||
from backend.data import integrations
|
from backend.data import integrations
|
||||||
from backend.data.model import APIKeyCredentials, Credentials
|
from backend.data.model import APIKeyCredentials, Credentials
|
||||||
from backend.integrations.providers import ProviderName
|
from backend.integrations.providers import ProviderName
|
||||||
from backend.integrations.webhooks._base import BaseWebhooksManager
|
from backend.integrations.webhooks._base import BaseWebhooksManager
|
||||||
|
from backend.util.request import Requests
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ class Slant3DWebhooksManager(BaseWebhooksManager):
|
|||||||
# Slant3D's API doesn't use events list, just register for all order updates
|
# Slant3D's API doesn't use events list, just register for all order updates
|
||||||
payload = {"endPoint": ingress_url}
|
payload = {"endPoint": ingress_url}
|
||||||
|
|
||||||
response = requests.post(
|
response = Requests().post(
|
||||||
f"{self.BASE_URL}/customer/webhookSubscribe", headers=headers, json=payload
|
f"{self.BASE_URL}/customer/webhookSubscribe", headers=headers, json=payload
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@router.post("/unsubscribe")
|
@router.post("/unsubscribe")
|
||||||
async def unsubscribe_via_one_click(token: Annotated[str, Query()]):
|
async def unsubscribe_via_one_click(token: Annotated[str, Query()]):
|
||||||
logger.info(f"Received unsubscribe request from One Click Unsubscribe: {token}")
|
logger.info("Received unsubscribe request from One Click Unsubscribe")
|
||||||
try:
|
try:
|
||||||
await unsubscribe_user_by_token(token)
|
await unsubscribe_user_by_token(token)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception("Unsubscribe token %s failed: %s", token, e)
|
logger.exception("Unsubscribe failed: %s", e)
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
detail={"message": str(e), "hint": "Verify Postmark token settings."},
|
detail={"message": str(e), "hint": "Verify Postmark token settings."},
|
||||||
|
|||||||
3
autogpt_platform/backend/poetry.lock
generated
3
autogpt_platform/backend/poetry.lock
generated
@@ -303,13 +303,16 @@ develop = true
|
|||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
colorama = "^0.4.6"
|
colorama = "^0.4.6"
|
||||||
expiringdict = "^1.2.2"
|
expiringdict = "^1.2.2"
|
||||||
|
fastapi = "^0.115.12"
|
||||||
google-cloud-logging = "^3.12.1"
|
google-cloud-logging = "^3.12.1"
|
||||||
|
launchdarkly-server-sdk = "^9.11.1"
|
||||||
pydantic = "^2.11.4"
|
pydantic = "^2.11.4"
|
||||||
pydantic-settings = "^2.9.1"
|
pydantic-settings = "^2.9.1"
|
||||||
pyjwt = "^2.10.1"
|
pyjwt = "^2.10.1"
|
||||||
pytest-asyncio = "^0.26.0"
|
pytest-asyncio = "^0.26.0"
|
||||||
pytest-mock = "^3.14.0"
|
pytest-mock = "^3.14.0"
|
||||||
supabase = "^2.15.1"
|
supabase = "^2.15.1"
|
||||||
|
uvicorn = "^0.34.3"
|
||||||
|
|
||||||
[package.source]
|
[package.source]
|
||||||
type = "directory"
|
type = "directory"
|
||||||
|
|||||||
@@ -11,13 +11,25 @@ async function shouldShowOnboarding() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate redirect URL to prevent open redirect attacks
|
||||||
|
function validateRedirectUrl(url: string): string {
|
||||||
|
// Only allow relative URLs that start with /
|
||||||
|
if (url.startsWith("/") && !url.startsWith("//")) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
// Default to home page for any invalid URLs
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
|
||||||
// Handle the callback to complete the user session login
|
// Handle the callback to complete the user session login
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
const { searchParams, origin } = new URL(request.url);
|
const { searchParams, origin } = new URL(request.url);
|
||||||
const code = searchParams.get("code");
|
const code = searchParams.get("code");
|
||||||
|
|
||||||
// if "next" is in param, use it as the redirect URL
|
// if "next" is in param, use it as the redirect URL
|
||||||
let next = searchParams.get("next") ?? "/";
|
const nextParam = searchParams.get("next") ?? "/";
|
||||||
|
// Validate redirect URL to prevent open redirect attacks
|
||||||
|
let next = validateRedirectUrl(nextParam);
|
||||||
|
|
||||||
if (code) {
|
if (code) {
|
||||||
const supabase = await getServerSupabase();
|
const supabase = await getServerSupabase();
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
cn,
|
cn,
|
||||||
getValue,
|
getValue,
|
||||||
hasNonNullNonObjectValue,
|
hasNonNullNonObjectValue,
|
||||||
|
isObject,
|
||||||
parseKeys,
|
parseKeys,
|
||||||
setNestedProperty,
|
setNestedProperty,
|
||||||
} from "@/lib/utils";
|
} from "@/lib/utils";
|
||||||
@@ -435,8 +436,15 @@ export const CustomNode = React.memo(
|
|||||||
if (activeKey) {
|
if (activeKey) {
|
||||||
try {
|
try {
|
||||||
const parsedValue = JSON.parse(value);
|
const parsedValue = JSON.parse(value);
|
||||||
handleInputChange(activeKey, parsedValue);
|
// Validate that the parsed value is safe before using it
|
||||||
|
if (isObject(parsedValue) || Array.isArray(parsedValue)) {
|
||||||
|
handleInputChange(activeKey, parsedValue);
|
||||||
|
} else {
|
||||||
|
// For primitive values, use the original string
|
||||||
|
handleInputChange(activeKey, value);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// If JSON parsing fails, treat as plain text
|
||||||
handleInputChange(activeKey, value);
|
handleInputChange(activeKey, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -396,3 +396,8 @@ export function getValue(key: string, value: any) {
|
|||||||
export function isEmptyOrWhitespace(str: string | undefined | null): boolean {
|
export function isEmptyOrWhitespace(str: string | undefined | null): boolean {
|
||||||
return !str || str.trim().length === 0;
|
return !str || str.trim().length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Chech if a value is an object or not */
|
||||||
|
export function isObject(value: unknown): value is Record<string, unknown> {
|
||||||
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user