diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9f171f0aab..b2c6d7dda3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -4,9 +4,9 @@ on: workflow_call: workflow_dispatch: pull_request: - branches: [main, dev] + branches: [main, dev, 'release/**'] push: - branches: [main, dev] + branches: [main, dev, 'release/**'] permissions: read-all diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 6c6a0523b3..5e420edea9 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -34,6 +34,9 @@ jobs: name: Install Python, poetry and Python dependencies with: poetry-working-directory: ${{ env.BACKEND_DIR }} + - name: Build Python distribution + run: poetry self add poetry-plugin-ignore-build-script && poetry build --ignore-build-script + working-directory: ${{ env.BACKEND_DIR }} - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 608b5cb9b0..eb58059a0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,110 @@ All notable changes to Chainlit will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [1.3.1] - 2024-10-25 + +### Security Advisory + +- **IMPORTANT**: This release temporarily reverts the file access security improvements from 1.3.0 to restore element functionality. The element feature currently has a known security vulnerability that could allow unauthorized access to files. We strongly recommend against using elements in production environments until the next release. +- A comprehensive security fix using HTTP-only cookie authentication will be implemented in an upcoming release. + +### Changed + +- Reverted authentication requirements for file access endpoints to restore element functionality (#1474) + +### Development + +- Work in progress on implementing HTTP-only cookie authentication for proper security (#1472) + +## [1.3.0] - 2024-10-22 + +### Security + +- Fixed critical endpoint security vulnerabilities (#1441) +- Enhanced authentication for file-related endpoints (#1431) +- Upgraded frontend and backend dependencies to address security issues (#1431) + +### Added + +- SQLite support in SQLAlchemy integration (#1319) +- Support for IETF BCP 47 language tags, enabling localized languages like es-419 (#1399) +- Environment variables `OAUTH__PROMPT` and `OAUTH_PROMPT` to +override oauth prompt parameter. Enabling users to explicitly enable login/consent prompts for oauth, e.g. `OAUTH_PROMPT=consent` to prevent automatic re-login. (#1362, #1456). +- Added `get_element()` method to SQLAlchemyDataLayer (#1346) + +### Changed + +- Bumped LiteralAI dependency to version 0.0.625 (#1376) +- Optimized LiteralDataLayer for improved performance and consistency (#1376) +- Refactored context handling in SQLAlchemy data layer (#1319) +- Updated package metadata with correct authors, license, and documentation links (#1413) +- Enhanced GitHub Actions workflow with restricted permissions (#1349) + +### Fixed + +- Resolved dialog boxes extending beyond window bounds (#1446) +- Fixed tasklist functionality when Chainlit is submounted (#1433) +- Corrected handling of `display_name` in PersistentUser during authentication (#1425) +- Fixed SQLAlchemy identifier quoting (#1395) +- Improved spaces handling in avatar filenames (#1418) + +### Development + +- Implemented extensive test coverage for LiteralDataLayer and SQLAlchemyDataLayer +- Added comprehensive unit tests for file-related endpoints +- Enhanced code organization and import structure +- Improved Python code style and linting (#1353) +- Resolved various small text and documentation issues (#1347, #1348) + +## [2.0.dev2] - 2024-10-25 + +### Security Advisory + +- **IMPORTANT**: This release temporarily reverts the file access security improvements from 2.0.dev1 to restore element functionality. The element feature currently has a known security vulnerability that could allow unauthorized access to files. We strongly recommend against using elements in production environments until the next release. +- A comprehensive security fix using HTTP-only cookie authentication will be implemented in an upcoming release. + +### Changed + +- Reverted authentication requirements for file access endpoints to restore element functionality (#1474) + +### Development + +- Work in progress on implementing HTTP-only cookie authentication for proper security (#1472) + +## [2.0.dev1] - 2024-10-22 + +### Added + +- Interactive DataFrame display component using MUI Data Grid (#1373) +- Optional websocket connection in react-client (#1379) +- Current URL in message payload (#1403) +- Improved image interaction - clicking opens popup with download option (#1402) +- Configurable user session timeout (#1032) + +### Security + +- Fixed file access vulnerability in get_file and upload_file endpoints (#1441) +- Added authentication to /project/file endpoint (#1441) +- Addressed security vulnerabilities in frontend dependencies (#1431, #1414) + +### Fixed + +- Dialog boxes extending beyond window (#1446) +- Allow empty chat input when submitting attachments (#1261) +- Tasklist when Chainlit is submounted (#1433) +- Spaces in avatar filenames (#1418) +- Step argument input and concurrency issues (#1409) +- Display_name copying to PersistentUser during authentication (#1425) + +### Development + +- Refactored storage clients into separate modules (#1363) +- Support for IETF BCP 47 language tags (#1399) +- Improved GitHub Actions workflows and build process (#1445) +- Direct installation from GitHub support (#1423) +- Extended package metadata with homepage and documentation links (#1413) +- Various backend fixes and code cleanup (#1432) + ## [2.0.dev0] - 2024-10-08 ### Breaking Changes @@ -40,36 +144,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Added new wavtools directory with various audio processing utilities - Implemented new AudioWorklet processors for more efficient audio handling -## [1.3.0rc0] - 2024-10-02 - -### Added - -- SQLite support in SQLAlchemy integration (#1137) -- Extensive test coverage for LiteralDataLayer and SQLAlchemyDataLayer -- `get_element()` method to SQLAlchemyDataLayer (#1346) - -### Changed - -- Bumped LiteralAI dependency to version 0.0.625 (#1376) -- Refactored LiteralDataLayer for improved performance and consistency -- Refactored context handling in SQLAlchemy data layer (#1319) -- Enhanced GitHub Actions workflow with restricted permissions (#1349) - -### Fixed - -- Resolved issues with SQLite database support (#1137) -- Addressed automatic OAuth login after logout (#1362) -- Various code style and linting improvements (#1353, #1348, #1347) - -### Development - -- Implemented LiteralToChainlitConverter class for handling conversions -- Added comprehensive unit tests for data layer components -- Improved import structure and removed unused imports -- Updated README with latest project information (#1351) - -We encourage users to thoroughly test this release candidate, particularly the LiteralAI integration and history features, and provide feedback before the final 1.3.0 release. - ## [1.2.0] - 2024-09-16 ### Security diff --git a/backend/README.md b/backend/README.md index ffd682b9af..ab9aab12c1 100644 --- a/backend/README.md +++ b/backend/README.md @@ -18,7 +18,7 @@ Chainlit is an open-source async Python framework which allows developers to bui Full documentation is available [here](https://docs.chainlit.io). You can ask Chainlit related questions to [Chainlit Help](https://help.chainlit.io/), an app built using Chainlit! -> [!NOTE] +> [!NOTE] > Check out [Literal AI](https://literalai.com), our product to monitor and evaluate LLM applications! It works with any Python or TypeScript applications and [seamlessly](https://docs.chainlit.io/data-persistence/overview) with Chainlit by adding a `LITERAL_API_KEY` in your project. > > Chainlit is developed and maintained by the Literal AI team, which is currently focused on expanding the capabilities of Literal AI. While we continue to support and maintain Chainlit, we are also committed to enabling the community to contribute, particularly in areas like integrations and data layers. @@ -43,7 +43,7 @@ If this opens the `hello app` in your browser, you're all set! The latest in-development version can be installed straight from GitHub with: ```sh -pip install git+https://github.com/Chainlit/chainlit.git@dokterbob/build_frontend_on_poetry_build#subdirectory=backend/ +pip install git+https://github.com/Chainlit/chainlit.git#subdirectory=backend/ ``` (Requires Node and pnpm installed on the system.) diff --git a/backend/chainlit/auth.py b/backend/chainlit/auth.py index 093d4a7407..8ee0943f84 100644 --- a/backend/chainlit/auth.py +++ b/backend/chainlit/auth.py @@ -52,7 +52,9 @@ def create_jwt(data: User) -> str: to_encode: Dict[str, Any] = data.to_dict() to_encode.update( { - "exp": datetime.utcnow() + timedelta(minutes=60 * 24 * 15), # 15 days + "exp": datetime.utcnow() + timedelta( + seconds=config.project.user_session_timeout + ), } ) encoded_jwt = jwt.encode(to_encode, get_jwt_secret(), algorithm="HS256") diff --git a/backend/chainlit/config.py b/backend/chainlit/config.py index 9a1c4f0e2a..ad9c1aaf88 100644 --- a/backend/chainlit/config.py +++ b/backend/chainlit/config.py @@ -62,6 +62,9 @@ # Duration (in seconds) during which the session is saved when the connection is lost session_timeout = 3600 +# Duration (in seconds) of the user session expiry +user_session_timeout = 1296000 # 15 days + # Enable third parties caching (e.g LangChain cache) cache = false @@ -306,6 +309,8 @@ class ProjectSettings(DataClassJsonMixin): # Path to the local chat db # Duration (in seconds) during which the session is saved when the connection is lost session_timeout: int = 3600 + # Duration (in seconds) of the user session expiry + user_session_timeout: int = 1296000 # 15 days # Enable third parties caching (e.g LangChain cache) cache: bool = False # Follow symlink for asset mount (see https://github.com/Chainlit/chainlit/issues/317) diff --git a/backend/chainlit/oauth_providers.py b/backend/chainlit/oauth_providers.py index c383b7592c..5ed9228f51 100644 --- a/backend/chainlit/oauth_providers.py +++ b/backend/chainlit/oauth_providers.py @@ -16,6 +16,7 @@ class OAuthProvider: client_secret: str authorize_url: str authorize_params: Dict[str, str] + default_prompt: Optional[str] = None def is_configured(self): return all([os.environ.get(env) for env in self.env]) @@ -26,6 +27,21 @@ async def get_token(self, code: str, url: str) -> str: async def get_user_info(self, token: str) -> Tuple[Dict[str, str], User]: raise NotImplementedError() + def get_env_prefix(self) -> str: + """Return environment prefix, like AZURE_AD.""" + + return self.id.replace("-", "_").upper() + + def get_prompt(self) -> Optional[str]: + """Return OAuth prompt param.""" + if prompt := os.environ.get(f"OAUTH_{self.get_env_prefix()}_PROMPT"): + return prompt + + if prompt := os.environ.get("OAUTH_PROMPT"): + return prompt + + return self.default_prompt + class GithubOAuthProvider(OAuthProvider): id = "github" @@ -37,9 +53,11 @@ def __init__(self): self.client_secret = os.environ.get("OAUTH_GITHUB_CLIENT_SECRET") self.authorize_params = { "scope": "user:email", - "prompt": "consent", } + if prompt := self.get_prompt(): + self.authorize_params["prompt"] = prompt + async def get_token(self, code: str, url: str): payload = { "client_id": self.client_id, @@ -96,9 +114,11 @@ def __init__(self): "scope": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email", "response_type": "code", "access_type": "offline", - "prompt": "login", } + if prompt := self.get_prompt(): + self.authorize_params["prompt"] = prompt + async def get_token(self, code: str, url: str): payload = { "client_id": self.client_id, @@ -164,9 +184,11 @@ def __init__(self): "response_type": "code", "scope": "https://graph.microsoft.com/User.Read", "response_mode": "query", - "prompt": "login", } + if prompt := self.get_prompt(): + self.authorize_params["prompt"] = prompt + async def get_token(self, code: str, url: str): payload = { "client_id": self.client_id, @@ -249,9 +271,11 @@ def __init__(self): "scope": "https://graph.microsoft.com/User.Read https://graph.microsoft.com/openid", "response_mode": "form_post", "nonce": nonce, - "prompt": "login", } + if prompt := self.get_prompt(): + self.authorize_params["prompt"] = prompt + async def get_token(self, code: str, url: str): payload = { "client_id": self.client_id, @@ -329,9 +353,11 @@ def __init__(self): "response_type": "code", "scope": "openid profile email", "response_mode": "query", - "prompt": "login", } + if prompt := self.get_prompt(): + self.authorize_params["prompt"] = prompt + def get_authorization_server_path(self): if not self.authorization_server_id: return "/default" @@ -401,9 +427,11 @@ def __init__(self): "response_type": "code", "scope": "openid profile email", "audience": f"{self.original_domain}/userinfo", - "prompt": "login", } + if prompt := self.get_prompt(): + self.authorize_params["prompt"] = prompt + async def get_token(self, code: str, url: str): payload = { "client_id": self.client_id, @@ -459,9 +487,11 @@ def __init__(self): "response_type": "code", "scope": "openid profile email", "audience": f"{self.domain}/userinfo", - "prompt": "login", } + if prompt := self.get_prompt(): + self.authorize_params["prompt"] = prompt + async def get_token(self, code: str, url: str): payload = { "client_id": self.client_id, @@ -518,9 +548,11 @@ def __init__(self): "response_type": "code", "client_id": self.client_id, "scope": "openid profile email", - "prompt": "login", } + if prompt := self.get_prompt(): + self.authorize_params["prompt"] = prompt + async def get_token(self, code: str, url: str): payload = { "client_id": self.client_id, @@ -587,9 +619,11 @@ def __init__(self): self.authorize_params = { "scope": "openid profile email", "response_type": "code", - "prompt": "login", } + if prompt := self.get_prompt(): + self.authorize_params["prompt"] = prompt + async def get_token(self, code: str, url: str): payload = { "client_id": self.client_id, diff --git a/backend/chainlit/server.py b/backend/chainlit/server.py index 7c4a824b68..4893c8d03c 100644 --- a/backend/chainlit/server.py +++ b/backend/chainlit/server.py @@ -881,7 +881,7 @@ async def upload_file( async def get_file( file_id: str, session_id: str, - current_user: Annotated[Union[User, PersistedUser], Depends(get_current_user)], + # current_user: Annotated[Union[User, PersistedUser], Depends(get_current_user)], #TODO: Causes 401 error. See https://github.com/Chainlit/chainlit/issues/1472 ): """Get a file from the session files directory.""" @@ -895,12 +895,13 @@ async def get_file( detail="Unauthorized", ) - if current_user: - if not session.user or session.user.identifier != current_user.identifier: - raise HTTPException( - status_code=401, - detail="You are not authorized to download files from this session", - ) + #TODO: Causes 401 error. See https://github.com/Chainlit/chainlit/issues/1472 + # if current_user: + # if not session.user or session.user.identifier != current_user.identifier: + # raise HTTPException( + # status_code=401, + # detail="You are not authorized to download files from this session", + # ) if file_id in session.files: file = session.files[file_id] diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 20eb9a900e..e8f06c13b9 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "chainlit" -version = "2.0.dev0" +version = "2.0.dev2" keywords = [ 'LLM', 'Agents', @@ -14,7 +14,7 @@ keywords = [ ] description = "Build Conversational AI." authors = ["Willy Douhard", "Dan Andre Constantini"] -license = " Apache-2.0" +license = "Apache-2.0" homepage = "https://chainlit.io/" documentation = "https://docs.chainlit.io/" classifiers = [ diff --git a/frontend/src/components/atoms/elements/InlinedDataframeList.tsx b/frontend/src/components/atoms/elements/InlinedDataframeList.tsx index 9f9275b433..d6140e878f 100644 --- a/frontend/src/components/atoms/elements/InlinedDataframeList.tsx +++ b/frontend/src/components/atoms/elements/InlinedDataframeList.tsx @@ -16,7 +16,7 @@ const InlinedDataframeList = ({ items }: Props) => ( key={i} style={{ height: 450, - maxWidth: '650px' + maxWidth: 'fit-content' }} > diff --git a/libs/react-client/src/types/file.ts b/libs/react-client/src/types/file.ts index af3fde1eba..9099a4163d 100644 --- a/libs/react-client/src/types/file.ts +++ b/libs/react-client/src/types/file.ts @@ -22,4 +22,5 @@ export interface IAsk { timeout: number; } & FileSpec & ActionSpec; + parentId?: string; } diff --git a/libs/react-client/src/useChatInteract.ts b/libs/react-client/src/useChatInteract.ts index ab646de970..b598093034 100644 --- a/libs/react-client/src/useChatInteract.ts +++ b/libs/react-client/src/useChatInteract.ts @@ -118,6 +118,7 @@ const useChatInteract = () => { const replyMessage = useCallback( (message: IStep) => { if (askUser) { + if (askUser.parentId) message.parentId = askUser.parentId; setMessages((oldMessages) => addMessage(oldMessages, message)); askUser.callback(message); } diff --git a/libs/react-client/src/useChatSession.ts b/libs/react-client/src/useChatSession.ts index 94f8d74eb5..9020847d2a 100644 --- a/libs/react-client/src/useChatSession.ts +++ b/libs/react-client/src/useChatSession.ts @@ -277,7 +277,7 @@ const useChatSession = () => { ); socket.on('ask', ({ msg, spec }, callback) => { - setAskUser({ spec, callback }); + setAskUser({ spec, callback, parentId: msg.parentId }); setMessages((oldMessages) => addMessage(oldMessages, msg)); setLoading(false); diff --git a/package.json b/package.json index 24a3288f98..4d1d2b640a 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,7 @@ "formatUi": "cd frontend && pnpm run format", "lintPython": "cd backend && poetry run mypy chainlit/ tests/", "formatPython": "black `git ls-files | grep '.py$'` && isort --profile=black .", - "buildUi": "cd libs/react-client && pnpm run build && cd ../copilot && pnpm run build && cd ../../frontend && pnpm run build", - "build": "pnpm run buildUi && (mkdir -p backend/chainlit/frontend && cp -R frontend/dist backend/chainlit/frontend) && (mkdir -p backend/chainlit/copilot && cp -R libs/copilot/dist backend/chainlit/copilot) && (cd backend && poetry build)" + "buildUi": "cd libs/react-client && pnpm run build && cd ../copilot && pnpm run build && cd ../../frontend && pnpm run build" }, "pnpm": { "overrides": {