Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support browser query parameters on chat start #1239

Closed
4 changes: 2 additions & 2 deletions cypress/e2e/copilot/spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe('Copilot', () => {
cy.document().then((document) => {
document.body.innerHTML = '<div id="root"><h1>Copilot test!</h1></div>';
const script = document.createElement('script');
script.src = 'http://localhost:8000/copilot/index.js';
script.src = 'http://127.0.0.1:8000/copilot/index.js';
document.body.appendChild(script);
});

Expand All @@ -15,7 +15,7 @@ describe('Copilot', () => {
cy.wait(1000).then(() => {
// @ts-expect-error is not a valid prop
win.mountChainlitWidget({
chainlitServer: 'http://localhost:8000'
chainlitServer: 'http://127.0.0.1:8000'
});

win.addEventListener('chainlit-call-fn', (e) => {
Expand Down
20 changes: 19 additions & 1 deletion cypress/e2e/header_auth/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from typing import Optional
from urllib.parse import parse_qs, urlparse

import chainlit as cl
from chainlit import logger


@cl.header_auth_callback
Expand All @@ -14,4 +16,20 @@ def header_auth_callback(headers) -> Optional[cl.User]:
@cl.on_chat_start
async def on_chat_start():
user = cl.user_session.get("user")
await cl.Message(f"Hello {user.identifier}").send()
query_param = None
if cl.user_session.get("http_referer"):
try:
parsed_url = urlparse(cl.user_session.get("http_referer"))
query_params = parse_qs(parsed_url.query)

query_param = query_params.get("q")

if isinstance(query_param, list):
query_param = query_param[0]

except Exception as e:
logger.warning({"msg": "Failed to parse http_referer", "error": str(e)})
else:
logger.warning({"msg": "http_referer is not set in user_session"})

await cl.Message(f"Hello {user.identifier}, query param: {query_param}").send()
6 changes: 4 additions & 2 deletions cypress/e2e/header_auth/spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ describe('Header auth', () => {
cy.get('.MuiAlert-message').should('exist');
});

it('should be able to auth with custom header', () => {
it('should be able to auth with custom header and read custom query parameter', () => {
cy.intercept('*', (req) => {
req.headers['test-header'] = 'test header value';
});
cy.visit('/');
cy.visit('/?q=test+value');
cy.get('.MuiAlert-message').should('not.exist');
cy.get('.step').eq(0).should('contain', 'Hello admin');
cy.get('.step').eq(0).should('contain', 'query param: test value');

cy.reload();
cy.get('.step').eq(0).should('contain', 'Hello admin');
cy.get('.step').eq(0).should('contain', 'query param: test value');
});
});
20 changes: 19 additions & 1 deletion cypress/e2e/password_auth/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from typing import Optional
from urllib.parse import parse_qs, urlparse

import chainlit as cl
from chainlit import logger


@cl.password_auth_callback
Expand All @@ -14,4 +16,20 @@ def auth_callback(username: str, password: str) -> Optional[cl.User]:
@cl.on_chat_start
async def on_chat_start():
user = cl.user_session.get("user")
await cl.Message(f"Hello {user.identifier}").send()
query_param = None
if cl.user_session.get("http_referer"):
try:
parsed_url = urlparse(cl.user_session.get("http_referer"))
query_params = parse_qs(parsed_url.query)

query_param = query_params.get("q")

if isinstance(query_param, list):
query_param = query_param[0]

except Exception as e:
logger.warning({"msg": "Failed to parse http_referer", "error": str(e)})
else:
logger.warning({"msg": "http_referer is not set in user_session"})

await cl.Message(f"Hello {user.identifier}, query param: {query_param}").send()
6 changes: 4 additions & 2 deletions cypress/e2e/password_auth/spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ describe('Password Auth', () => {
cy.get('.MuiAlert-message').should('exist');
});

it('should be able to login with correct credentials', () => {
cy.visit('/');
it('should be able to login with correct credentials and read custom query parameter', () => {
cy.visit('/?q=test+value');
cy.get("input[name='email']").type('admin');
cy.get("input[name='password']").type('admin');
cy.get("button[type='submit']").click();
cy.get('.step').eq(0).should('contain', 'Hello admin');
cy.get('.step').eq(0).should('contain', 'query param: test value');

cy.reload();
cy.get("input[name='email']").should('not.exist');
cy.get('.step').eq(0).should('contain', 'Hello admin');
cy.get('.step').eq(0).should('contain', 'query param: test value');
});
});
12 changes: 1 addition & 11 deletions frontend/src/AppWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import App from 'App';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import getRouterBasename from 'utils/router';

import { useApi, useAuth, useConfig } from '@chainlit/react-client';

export default function AppWrapper() {
const { isAuthenticated, isReady } = useAuth();
const { isReady } = useAuth();
const { language: languageInUse } = useConfig();
const { i18n } = useTranslation();

Expand All @@ -19,15 +18,6 @@ export default function AppWrapper() {
`/project/translations?language=${languageInUse}`
);

if (
isReady &&
!isAuthenticated &&
window.location.pathname !== getRouterBasename() + '/login' &&
window.location.pathname !== getRouterBasename() + '/login/callback'
) {
window.location.href = getRouterBasename() + '/login';
}

useEffect(() => {
if (!translations) return;
handleChangeLanguage(translations.translation);
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/pages/Env.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useFormik } from 'formik';
import { useNavigate } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import { useRecoilState } from 'recoil';
import { toast } from 'sonner';
import * as yup from 'yup';
Expand All @@ -23,6 +23,9 @@ export default function Env() {
const layoutMaxWidth = useLayoutMaxWidth();

const navigate = useNavigate();
const location = useLocation();

const homePath = location.search ? `/${location.search}` : '/';

const { t } = useTranslation();

Expand All @@ -45,12 +48,12 @@ export default function Env() {
localStorage.setItem('userEnv', JSON.stringify(values));
setUserEnv(values);
toast.success(t('pages.Env.savedSuccessfully'));
return navigate('/');
return navigate(homePath);
}
});

if (requiredKeys.length === 0) {
navigate('/');
navigate(homePath);
}

const renderInput = (key: string) => {
Expand Down
13 changes: 8 additions & 5 deletions frontend/src/pages/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useContext, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';

import { Logo } from 'components/atoms/logo';
import { Translator } from 'components/i18n';
Expand All @@ -16,12 +16,15 @@ export default function Login() {
const apiClient = useContext(ChainlitContext);

const navigate = useNavigate();
const location = useLocation();

const homePath = location.search ? `/${location.search}` : '/';

const handleHeaderAuth = async () => {
try {
const json = await apiClient.headerAuth();
setAccessToken(json.access_token);
navigate('/');
navigate(homePath);
} catch (error: any) {
setError(error.message);
}
Expand Down Expand Up @@ -54,21 +57,21 @@ export default function Login() {
return;
}
if (!config.requireLogin) {
navigate('/');
navigate(homePath);
}
if (config.headerAuth) {
handleHeaderAuth();
}
if (user) {
navigate('/');
navigate(homePath);
}
}, [config, user]);

return (
<AuthLogin
title={<Translator path="components.molecules.auth.authLogin.title" />}
error={error}
callbackUrl="/"
callbackUrl={homePath}
providers={config?.oauthProviders || []}
onPasswordSignIn={config?.passwordAuth ? handlePasswordLogin : undefined}
onOAuthSignIn={async (provider: string) => {
Expand Down
40 changes: 33 additions & 7 deletions frontend/src/pages/Page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Navigate } from 'react-router-dom';
import { useContext, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useRecoilValue } from 'recoil';

import { Alert, Box, Stack } from '@mui/material';

import { sideViewState, useAuth, useConfig } from '@chainlit/react-client';
import {
ChainlitContext,
sideViewState,
useAuth,
useConfig
} from '@chainlit/react-client';

import { ElementSideView } from 'components/atoms/elements';
import { Translator } from 'components/i18n';
Expand All @@ -18,20 +24,40 @@ type Props = {
};

const Page = ({ children }: Props) => {
const { isAuthenticated } = useAuth();
const { data: appConfig, isAuthenticated, setAccessToken } = useAuth();
const { config } = useConfig();
const userEnv = useRecoilValue(userEnvState);
const sideViewElement = useRecoilValue(sideViewState);
const apiClient = useContext(ChainlitContext);

const navigate = useNavigate();
const location = useLocation();

if (config?.userEnv) {
const envPath = location.search ? `/env${location.search}` : '/env';
for (const key of config.userEnv || []) {
if (!userEnv[key]) return <Navigate to="/env" />;
if (!userEnv[key]) navigate(envPath);
}
}

if (!isAuthenticated) {
return <Navigate to="/login" />;
}
const loginPath = location.search ? `/login${location.search}` : '/login';

useEffect(() => {
if (appConfig && appConfig.requireLogin && !isAuthenticated) {
if (appConfig.headerAuth) {
apiClient
.headerAuth()
.then((json) => {
setAccessToken(json.access_token);
})
.catch(() => {
navigate(loginPath);
});
} else {
navigate(loginPath);
}
}
}, [appConfig, isAuthenticated]);

return (
<Box
Expand Down
3 changes: 2 additions & 1 deletion libs/react-client/src/api/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ export class APIBase {
method,
headers,
signal,
body
body,
credentials: 'include'
});

if (!res.ok) {
Expand Down
Loading