refactor: Enhance Supabase integration and Twitter OAuth handling

- Updated `store.py` to improve state token management by adding PKCE support and simplifying code challenge generation.
- Modified environment variable names in `.env.example` for consistency.
- Removed unnecessary `is_multi_select` attributes from various Twitter-related input schemas to streamline the code.
- Cleaned up exception handling in Twitter list management and tweet management blocks by removing redundant error logging.
- Removed debug print statements from various components to clean up the codebase.
- Fixed a minor error message in the Twitter OAuth handler for clarity.
This commit is contained in:
abhi1992002
2024-12-02 23:59:05 +05:30
parent 49a24f73b7
commit 1678a1c69c
16 changed files with 28 additions and 132 deletions

View File

@@ -59,8 +59,8 @@ GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Twitter/X OAuth App server credentials - https://developer.x.com/en/products/x-api
Twitter_CLIENT_ID=
Twitter_CLIENT_SECRET=
TWITTER_CLIENT_ID=
TWITTER_CLIENT_SECRET=
## ===== OPTIONAL API KEYS ===== ##

View File

@@ -258,7 +258,6 @@ class TweetExpansionInputs(BlockSchema):
enum=TweetExpansions,
placeholder="Pick the extra information you want to see",
default=[],
is_multi_select=True,
advanced=True,
)
@@ -267,7 +266,6 @@ class TweetExpansionInputs(BlockSchema):
enum=TweetMediaFields,
placeholder="Choose what media details you want to see",
default=[],
is_multi_select=True,
advanced=True,
)
@@ -276,7 +274,6 @@ class TweetExpansionInputs(BlockSchema):
placeholder="Choose what location details you want to see",
default=[],
advanced=True,
is_multi_select=True,
enum=TweetPlaceFields,
)
@@ -285,7 +282,6 @@ class TweetExpansionInputs(BlockSchema):
placeholder="Choose what poll details you want to see",
default=[],
advanced=True,
is_multi_select=True,
enum=TweetPollFields,
)
@@ -294,7 +290,6 @@ class TweetExpansionInputs(BlockSchema):
placeholder="Choose what tweet details you want to see",
default=[],
advanced=True,
is_multi_select=True,
enum=TweetFields,
)
@@ -303,7 +298,6 @@ class TweetExpansionInputs(BlockSchema):
placeholder="Choose what user details you want to see",
default=[],
advanced=True,
is_multi_select=True,
enum=TweetUserFields,
)
@@ -314,7 +308,6 @@ class DMEventExpansionInputs(BlockSchema):
enum=DMEventExpansion,
placeholder="Enter expansions",
default=[],
is_multi_select=True,
advanced=True,
)
@@ -323,7 +316,6 @@ class DMEventExpansionInputs(BlockSchema):
placeholder="Enter event types",
default=[],
advanced=True,
is_multi_select=True,
enum=DMEventType,
)
@@ -332,7 +324,6 @@ class DMEventExpansionInputs(BlockSchema):
placeholder="Enter media fields",
default=[],
advanced=True,
is_multi_select=True,
enum=DMMediaField,
)
@@ -341,7 +332,6 @@ class DMEventExpansionInputs(BlockSchema):
placeholder="Enter tweet fields",
default=[],
advanced=True,
is_multi_select=True,
enum=DMTweetField,
)
@@ -350,7 +340,6 @@ class DMEventExpansionInputs(BlockSchema):
placeholder="Enter user fields",
default=[],
advanced=True,
is_multi_select=True,
enum=TweetUserFields,
)
@@ -361,7 +350,6 @@ class UserExpansionInputs(BlockSchema):
enum=UserExpansions,
placeholder="Select extra user information to include",
default=[],
is_multi_select=True,
advanced=True,
)
@@ -370,7 +358,6 @@ class UserExpansionInputs(BlockSchema):
placeholder="Choose what details to see in pinned tweets",
default=[],
advanced=True,
is_multi_select=True,
enum=TweetFields,
)
@@ -379,7 +366,6 @@ class UserExpansionInputs(BlockSchema):
placeholder="Choose what user details you want to see",
default=[],
advanced=True,
is_multi_select=True,
enum=TweetUserFields,
)
@@ -390,7 +376,6 @@ class SpaceExpansionInputs(BlockSchema):
enum=SpaceExpansions,
placeholder="Pick what extra information you want to see about the Space",
default=[],
is_multi_select=True,
advanced=True,
)
@@ -399,7 +384,6 @@ class SpaceExpansionInputs(BlockSchema):
placeholder="Choose what Space information you want to get",
default=[SpaceFields.title_, SpaceFields.host_ids],
advanced=True,
is_multi_select=True,
enum=SpaceFields,
)
@@ -408,7 +392,6 @@ class SpaceExpansionInputs(BlockSchema):
placeholder="Pick what details you want to see about the users",
default=[],
advanced=True,
is_multi_select=True,
enum=TweetUserFields,
)
@@ -419,7 +402,6 @@ class ListExpansionInputs(BlockSchema):
enum=ListExpansions,
placeholder="Pick what extra list information you want to see",
default=[ListExpansions.owner_id],
is_multi_select=True,
advanced=True,
)
@@ -428,7 +410,6 @@ class ListExpansionInputs(BlockSchema):
placeholder="Select what details you want to see about list owners",
default=[TweetUserFields.id, TweetUserFields.username],
advanced=True,
is_multi_select=True,
enum=TweetUserFields,
)
@@ -437,7 +418,6 @@ class ListExpansionInputs(BlockSchema):
placeholder="Pick what list details you want to see",
default=[ListFields.owner_id],
advanced=True,
is_multi_select=True,
enum=ListFields,
)

View File

@@ -87,8 +87,7 @@ class TwitterRemoveListMemberBlock(Block):
return True
except tweepy.TweepyException:
raise
except Exception as e:
print(f"Unexpected error: {str(e)}")
except Exception:
raise
def run(
@@ -163,8 +162,7 @@ class TwitterAddListMemberBlock(Block):
return True
except tweepy.TweepyException:
raise
except Exception as e:
print(f"Unexpected error: {str(e)}")
except Exception:
raise
def run(

View File

@@ -60,8 +60,7 @@ class TwitterDeleteListBlock(Block):
except tweepy.TweepyException:
raise
except Exception as e:
print(f"Unexpected error: {str(e)}")
except Exception:
raise
def run(
@@ -151,8 +150,7 @@ class TwitterUpdateListBlock(Block):
except tweepy.TweepyException:
raise
except Exception as e:
print(f"Unexpected error: {str(e)}")
except Exception:
raise
def run(
@@ -256,8 +254,7 @@ class TwitterCreateListBlock(Block):
except tweepy.TweepyException:
raise
except Exception as e:
print(f"Unexpected error: {str(e)}")
except Exception:
raise
def run(

View File

@@ -72,8 +72,7 @@ class TwitterUnpinListBlock(Block):
except tweepy.TweepyException:
raise
except Exception as e:
print(f"Unexpected error: {str(e)}")
except Exception:
raise
def run(
@@ -137,8 +136,7 @@ class TwitterPinListBlock(Block):
except tweepy.TweepyException:
raise
except Exception as e:
print(f"Unexpected error: {str(e)}")
except Exception:
raise
def run(

View File

@@ -147,8 +147,6 @@ class TwitterGetSpacesBlock(Block):
.build()
)
print(" before space response")
response = cast(Response, client.get_spaces(**params))
ids = []

View File

@@ -211,8 +211,7 @@ class TwitterPostTweetBlock(Block):
except tweepy.TweepyException:
raise
except Exception as e:
print(f"Unexpected error: {str(e)}")
except Exception:
raise
def run(
@@ -294,8 +293,7 @@ class TwitterDeleteTweetBlock(Block):
return True
except tweepy.TweepyException:
raise
except Exception as e:
print(f"Unexpected error: {str(e)}")
except Exception:
raise
def run(

View File

@@ -56,7 +56,7 @@ class TwitterGetQuoteTweetsBlock(Block):
description="Types of tweets to exclude",
required=False,
advanced=True,
is_multi_select=True,
enum = TweetExcludes,
default=[],
)

View File

@@ -139,8 +139,6 @@ class TwitterGetUserBlock(Block):
.build()
)
print("params : ", params)
response = cast(Response, client.get_user(**params))
username = ""

View File

@@ -136,7 +136,6 @@ def SchemaField(
placeholder: Optional[str] = None,
advanced: Optional[bool] = False,
secret: bool = False,
is_multi_select: bool = False,
exclude: bool = False,
hidden: Optional[bool] = None,
depends_on: list[str] | None = None,

View File

@@ -212,13 +212,11 @@ class IntegrationCredentialsStore:
]
self._set_user_integration_creds(user_id, filtered_credentials)
def store_state_token(self, user_id: str, provider: str, scopes: list[str]) -> tuple[str, str]:
def store_state_token(self, user_id: str, provider: str, scopes: list[str], use_pkce: bool = False) -> tuple[str, str]:
token = secrets.token_urlsafe(32)
expires_at = datetime.now(timezone.utc) + timedelta(minutes=10)
code_verifier = self._generate_code_verifier(128)
code_challenge = self._generate_code_challenge(code_verifier)
print("login code_verifier: ", code_verifier)
(code_challenge, code_verifier) = self._generate_code_challenge()
state = OAuthState(
token=token,
@@ -241,74 +239,17 @@ class IntegrationCredentialsStore:
return token, code_challenge
def _generate_code_verifier(self, length: int) -> str:
"""
Generate a secure random code verifier of specified length.
"""
return secrets.token_urlsafe(length)
def _generate_code_challenge(self, code_verifier: str, method: str = "S256") -> str:
def _generate_code_challenge(self) -> tuple[str, str]:
"""
Generate code challenge using SHA256 from the code verifier.
Currently only SHA256 is supported.(In future if we want to support more methods we can add them here)
"""
if method != "S256":
raise ValueError(f"Unsupported code challenge method: {method}")
code_verifier = secrets.token_urlsafe(128)
sha256_hash = hashlib.sha256(code_verifier.encode('utf-8')).digest()
code_challenge = base64.urlsafe_b64encode(sha256_hash).decode('utf-8')
return code_challenge.replace('=', '')
return code_challenge.replace('=', ''), code_verifier
def get_any_valid_scopes_from_state_token(
self, user_id: str, token: str, provider: str
) -> list[str]:
"""
Get the valid scopes from the OAuth state token. This will return any valid scopes
from any OAuth state token for the given provider. If no valid scopes are found,
an empty list is returned. DO NOT RELY ON THIS TOKEN TO AUTHENTICATE A USER, AS IT
IS TO CHECK IF THE USER HAS GIVEN PERMISSIONS TO THE APPLICATION BEFORE EXCHANGING
THE CODE FOR TOKENS.
"""
user_integrations = self._get_user_integrations(user_id)
oauth_states = user_integrations.oauth_states
now = datetime.now(timezone.utc)
valid_state = next(
(
state
for state in oauth_states
if state.token == token
and state.provider == provider
and state.expires_at > now.timestamp()
),
None,
)
if valid_state:
return valid_state.scopes
return []
def _get_code_verifier(self, user_id: str, provider: str, token : str) -> Optional[str]:
user_integrations = self._get_user_integrations(user_id)
oauth_states = user_integrations.oauth_states
now = datetime.now(timezone.utc)
valid_state = next(
(
state
for state in oauth_states
if state.token == token
and state.provider == provider
and state.expires_at > now.timestamp()
),
None,
)
if valid_state:
return valid_state.code_verifier
def verify_state_token(self, user_id: str, token: str, provider: str) -> bool:
def verify_state_token(self, user_id: str, token: str, provider: str) -> Optional[OAuthState]:
with self.locked_user_integrations(user_id):
user_integrations = self._get_user_integrations(user_id)
oauth_states = user_integrations.oauth_states
@@ -330,9 +271,9 @@ class IntegrationCredentialsStore:
oauth_states.remove(valid_state)
user_integrations.oauth_states = oauth_states
self.db_manager.update_user_integrations(user_id, user_integrations)
return True
return valid_state
return False
return None
def _set_user_integration_creds(
self, user_id: str, credentials: list[Credentials]

View File

@@ -48,7 +48,7 @@ class TwitterOAuthHandler(BaseOAuthHandler):
# scopes = self.handle_default_scopes(scopes)
if code_challenge is None:
raise ValueError("code_verifier is required for Twitter OAuth")
raise ValueError("code_challenge is required for Twitter OAuth")
params = {
"response_type": "code",

View File

@@ -91,24 +91,20 @@ def callback(
) -> CredentialsMetaResponse:
logger.debug(f"Received OAuth callback for provider: {provider}")
handler = _get_provider_oauth_handler(request, provider)
code_verifier = creds_manager.store._get_code_verifier(
user_id, provider, state_token
)
# Verify the state token
if not creds_manager.store.verify_state_token(user_id, state_token, provider):
valid_state = creds_manager.store.verify_state_token(user_id, state_token, provider)
if not valid_state:
logger.warning(f"Invalid or expired state token for user {user_id}")
raise HTTPException(status_code=400, detail="Invalid or expired state token")
try:
scopes = creds_manager.store.get_any_valid_scopes_from_state_token(
user_id, state_token, provider
)
scopes = valid_state.scopes
logger.debug(f"Retrieved scopes from state token: {scopes}")
scopes = handler.handle_default_scopes(scopes)
credentials = handler.exchange_code_for_tokens(code, scopes, code_verifier)
credentials = handler.exchange_code_for_tokens(code, scopes, valid_state.code_verifier)
logger.debug(f"Received credentials with final scopes: {credentials.scopes}")

View File

@@ -297,9 +297,6 @@ export function CustomNode({
const newValues = JSON.parse(JSON.stringify(data.hardcodedValues));
let current = newValues;
console.log("Keys: ", keys);
console.log("Value: ", newValues);
for (let i = 0; i < keys.length - 1; i++) {
const { key: currentKey, index } = keys[i];
if (index !== undefined) {

View File

@@ -862,10 +862,7 @@ const NodeArrayInput: FC<{
if (!entries || !Array.isArray(entries)) entries = [];
const isMultiSelectEnum =
schema.items &&
isStringSubSchema(schema.items) &&
schema.items.enum &&
schema.isMultiSelect;
schema.items && isStringSubSchema(schema.items) && schema.items.enum;
if (isMultiSelectEnum) {
return (

View File

@@ -76,7 +76,6 @@ export type BlockIOKVSubSchema = BlockIOSubSchemaMeta & {
export type BlockIOArraySubSchema = BlockIOSubSchemaMeta & {
type: "array";
items?: BlockIOSimpleTypeSubSchema;
isMultiSelect?: boolean;
default?: Array<string>;
};