feat: add Atlassian OAuth support for Jira and Confluence

- Introduced a shared schema for Atlassian OAuth 2.0 credentials, accommodating both Jira and Confluence.
- Updated Jira connector routes to utilize the new AtlassianAuthCredentialsBase for handling OAuth tokens.
- Enhanced configuration to include new environment variables for Jira OAuth integration.
- Refactored token handling in Jira indexing logic to support the new shared credential structure.
This commit is contained in:
Anish Sarkar 2026-01-06 01:27:29 +05:30
parent 982b9ceb76
commit bf8c3bfcf7
4 changed files with 27 additions and 7 deletions

View file

@ -95,6 +95,11 @@ class Config:
NOTION_CLIENT_SECRET = os.getenv("NOTION_CLIENT_SECRET") NOTION_CLIENT_SECRET = os.getenv("NOTION_CLIENT_SECRET")
NOTION_REDIRECT_URI = os.getenv("NOTION_REDIRECT_URI") NOTION_REDIRECT_URI = os.getenv("NOTION_REDIRECT_URI")
# Jira OAuth
JIRA_CLIENT_ID = os.getenv("JIRA_CLIENT_ID")
JIRA_CLIENT_SECRET = os.getenv("JIRA_CLIENT_SECRET")
JIRA_REDIRECT_URI = os.getenv("JIRA_REDIRECT_URI")
# Linear OAuth # Linear OAuth
LINEAR_CLIENT_ID = os.getenv("LINEAR_CLIENT_ID") LINEAR_CLIENT_ID = os.getenv("LINEAR_CLIENT_ID")
LINEAR_CLIENT_SECRET = os.getenv("LINEAR_CLIENT_SECRET") LINEAR_CLIENT_SECRET = os.getenv("LINEAR_CLIENT_SECRET")

View file

@ -24,7 +24,7 @@ from app.db import (
User, User,
get_async_session, get_async_session,
) )
from app.schemas.jira_auth_credentials import JiraAuthCredentialsBase from app.schemas.atlassian_auth_credentials import AtlassianAuthCredentialsBase
from app.users import current_active_user from app.users import current_active_user
from app.utils.oauth_security import OAuthStateManager, TokenEncryption from app.utils.oauth_security import OAuthStateManager, TokenEncryption
@ -392,7 +392,7 @@ async def refresh_jira_token(
try: try:
logger.info(f"Refreshing Jira token for connector {connector.id}") logger.info(f"Refreshing Jira token for connector {connector.id}")
credentials = JiraAuthCredentialsBase.from_dict(connector.config) credentials = AtlassianAuthCredentialsBase.from_dict(connector.config)
# Decrypt tokens if they are encrypted # Decrypt tokens if they are encrypted
token_encryption = get_token_encryption() token_encryption = get_token_encryption()

View file

@ -1,9 +1,23 @@
"""
Atlassian OAuth 2.0 Authentication Credentials Schema.
Shared schema for both Jira and Confluence OAuth credentials.
Both products use the same Atlassian OAuth 2.0 (3LO) flow and token structure.
"""
from datetime import UTC, datetime from datetime import UTC, datetime
from pydantic import BaseModel, field_validator from pydantic import BaseModel, field_validator
class JiraAuthCredentialsBase(BaseModel): class AtlassianAuthCredentialsBase(BaseModel):
"""
Base model for Atlassian OAuth 2.0 credentials.
Used for both Jira and Confluence connectors since they share
the same Atlassian OAuth infrastructure and token structure.
"""
access_token: str access_token: str
refresh_token: str | None = None refresh_token: str | None = None
token_type: str = "Bearer" token_type: str = "Bearer"
@ -39,7 +53,7 @@ class JiraAuthCredentialsBase(BaseModel):
} }
@classmethod @classmethod
def from_dict(cls, data: dict) -> "JiraAuthCredentialsBase": def from_dict(cls, data: dict) -> "AtlassianAuthCredentialsBase":
"""Create credentials from dictionary.""" """Create credentials from dictionary."""
expires_at = None expires_at = None
if data.get("expires_at"): if data.get("expires_at"):
@ -70,3 +84,4 @@ class JiraAuthCredentialsBase(BaseModel):
if isinstance(v, datetime): if isinstance(v, datetime):
return v if v.tzinfo else v.replace(tzinfo=UTC) return v if v.tzinfo else v.replace(tzinfo=UTC)
return v return v

View file

@ -131,8 +131,8 @@ async def index_jira_issues(
return 0, f"Failed to decrypt Jira tokens: {e!s}" return 0, f"Failed to decrypt Jira tokens: {e!s}"
try: try:
from app.schemas.jira_auth_credentials import JiraAuthCredentialsBase from app.schemas.atlassian_auth_credentials import AtlassianAuthCredentialsBase
credentials = JiraAuthCredentialsBase.from_dict(config_data) credentials = AtlassianAuthCredentialsBase.from_dict(config_data)
except Exception as e: except Exception as e:
await task_logger.log_task_failure( await task_logger.log_task_failure(
log_entry, log_entry,
@ -160,7 +160,7 @@ async def index_jira_issues(
config_data["access_token"] = token_encryption.decrypt_token( config_data["access_token"] = token_encryption.decrypt_token(
config_data["access_token"] config_data["access_token"]
) )
credentials = JiraAuthCredentialsBase.from_dict(config_data) credentials = AtlassianAuthCredentialsBase.from_dict(config_data)
except Exception as e: except Exception as e:
await task_logger.log_task_failure( await task_logger.log_task_failure(
log_entry, log_entry,