-
-
Notifications
You must be signed in to change notification settings - Fork 19.6k
Feature: Allowed domains for chatflows #1765
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
Merged
Merged
Changes from 11 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
31c89aa
Add allowed domains settings and disallow prediction based on this list
0xi4o dd0862b
Update dialog title and description
0xi4o ce16fd9
Add logging to allowed domains checks
0xi4o c07e908
Update condition for checking allowed origins
0xi4o 7d76c12
Update how allowed origins are saved in chatbot config
0xi4o 930bdd5
Fix how allowed domains are checked - origin not host
0xi4o f477c74
Fix allowed origins not working when they're all removed
0xi4o c76fe7e
Fix merge conflicts
0xi4o d5b8dcd
Merge branch 'main' of github.com:0xi4o/Flowise into feature/allowed-…
0xi4o d0ddf01
Update access denied error message
0xi4o d706ca3
Detect host from list of allowed urls even if they have http/https
0xi4o 91765c2
Merge branch 'main' into feature/allowed-domains
HenryHengZJ 07503f9
add placeholder
HenryHengZJ 3b84e71
fix isValidAllowedOrigins check and invalid URL
HenryHengZJ File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1319,7 +1319,32 @@ export class App { | |
upload.array('files'), | ||
(req: Request, res: Response, next: NextFunction) => getRateLimiter(req, res, next), | ||
async (req: Request, res: Response) => { | ||
await this.buildChatflow(req, res, socketIO) | ||
const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ | ||
id: req.params.id | ||
}) | ||
if (!chatflow) return res.status(404).send(`Chatflow ${req.params.id} not found`) | ||
let isDomainAllowed = true | ||
logger.info(`[server]: Request originated from ${req.headers.origin}`) | ||
if (chatflow.chatbotConfig) { | ||
const parsedConfig = JSON.parse(chatflow.chatbotConfig) | ||
// check whether the first one is not empty. if it is empty that means the user set a value and then removed it. | ||
const isValidAllowedOrigins = parsedConfig.allowedOrigins[0] !== '' | ||
if (parsedConfig.allowedOrigins && parsedConfig.allowedOrigins.length > 0 && isValidAllowedOrigins) { | ||
const originHeader = req.headers.origin as string | ||
const origin = new URL(originHeader).host | ||
isDomainAllowed = | ||
parsedConfig.allowedOrigins.filter((domain: string) => { | ||
const allowedOrigin = new URL(domain).host | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if |
||
return origin === allowedOrigin | ||
}).length > 0 | ||
} | ||
} | ||
|
||
if (isDomainAllowed) { | ||
await this.buildChatflow(req, res, socketIO) | ||
} else { | ||
return res.status(401).send(`This site is not allowed to access this chatbot`) | ||
} | ||
} | ||
) | ||
|
||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
214 changes: 214 additions & 0 deletions
214
packages/ui/src/ui-component/dialog/AllowedDomainsDialog.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
import { createPortal } from 'react-dom' | ||
import { useDispatch } from 'react-redux' | ||
import { useState, useEffect } from 'react' | ||
import PropTypes from 'prop-types' | ||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions' | ||
|
||
// material-ui | ||
import { | ||
Button, | ||
IconButton, | ||
Dialog, | ||
DialogContent, | ||
OutlinedInput, | ||
DialogTitle, | ||
DialogActions, | ||
Box, | ||
List, | ||
InputAdornment | ||
} from '@mui/material' | ||
import { IconX, IconTrash, IconPlus } from '@tabler/icons' | ||
|
||
// Project import | ||
import { StyledButton } from 'ui-component/button/StyledButton' | ||
|
||
// store | ||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' | ||
import useNotifier from 'utils/useNotifier' | ||
|
||
// API | ||
import chatflowsApi from 'api/chatflows' | ||
|
||
const AllowedDomainsDialog = ({ show, dialogProps, onCancel, onConfirm }) => { | ||
const portalElement = document.getElementById('portal') | ||
const dispatch = useDispatch() | ||
|
||
useNotifier() | ||
|
||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) | ||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) | ||
|
||
const [inputFields, setInputFields] = useState(['']) | ||
|
||
const [chatbotConfig, setChatbotConfig] = useState({}) | ||
|
||
const addInputField = () => { | ||
setInputFields([...inputFields, '']) | ||
} | ||
const removeInputFields = (index) => { | ||
const rows = [...inputFields] | ||
rows.splice(index, 1) | ||
setInputFields(rows) | ||
} | ||
|
||
const handleChange = (index, evnt) => { | ||
const { value } = evnt.target | ||
const list = [...inputFields] | ||
list[index] = value | ||
setInputFields(list) | ||
} | ||
|
||
const onSave = async () => { | ||
try { | ||
let value = { | ||
allowedOrigins: [...inputFields] | ||
} | ||
chatbotConfig.allowedOrigins = value.allowedOrigins | ||
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, { | ||
chatbotConfig: JSON.stringify(chatbotConfig) | ||
}) | ||
if (saveResp.data) { | ||
enqueueSnackbar({ | ||
message: 'Allowed Origins Saved', | ||
options: { | ||
key: new Date().getTime() + Math.random(), | ||
variant: 'success', | ||
action: (key) => ( | ||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}> | ||
<IconX /> | ||
</Button> | ||
) | ||
} | ||
}) | ||
dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) | ||
} | ||
onConfirm() | ||
} catch (error) { | ||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` | ||
enqueueSnackbar({ | ||
message: `Failed to save Allowed Origins: ${errorData}`, | ||
options: { | ||
key: new Date().getTime() + Math.random(), | ||
variant: 'error', | ||
persist: true, | ||
action: (key) => ( | ||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}> | ||
<IconX /> | ||
</Button> | ||
) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
useEffect(() => { | ||
if (dialogProps.chatflow && dialogProps.chatflow.chatbotConfig) { | ||
try { | ||
let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig) | ||
setChatbotConfig(chatbotConfig || {}) | ||
if (chatbotConfig.allowedOrigins) { | ||
let inputFields = [...chatbotConfig.allowedOrigins] | ||
setInputFields(inputFields) | ||
} else { | ||
setInputFields(['']) | ||
} | ||
} catch (e) { | ||
setInputFields(['']) | ||
} | ||
} | ||
|
||
return () => {} | ||
}, [dialogProps]) | ||
|
||
useEffect(() => { | ||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) | ||
else dispatch({ type: HIDE_CANVAS_DIALOG }) | ||
return () => dispatch({ type: HIDE_CANVAS_DIALOG }) | ||
}, [show, dispatch]) | ||
|
||
const component = show ? ( | ||
<Dialog | ||
onClose={onCancel} | ||
open={show} | ||
fullWidth | ||
maxWidth='sm' | ||
aria-labelledby='alert-dialog-title' | ||
aria-describedby='alert-dialog-description' | ||
> | ||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'> | ||
{dialogProps.title || 'Allowed Origins'} | ||
</DialogTitle> | ||
<DialogContent> | ||
<div | ||
style={{ | ||
display: 'flex', | ||
flexDirection: 'column' | ||
}} | ||
> | ||
<span>Your chatbot will only work when used from the following domains.</span> | ||
</div> | ||
<Box sx={{ '& > :not(style)': { m: 1 }, pt: 2 }}> | ||
<List> | ||
{inputFields.map((origin, index) => { | ||
return ( | ||
<div key={index} style={{ display: 'flex', width: '100%' }}> | ||
<Box sx={{ width: '100%', mb: 1 }}> | ||
<OutlinedInput | ||
sx={{ width: '100%' }} | ||
key={index} | ||
type='text' | ||
onChange={(e) => handleChange(index, e)} | ||
size='small' | ||
value={origin} | ||
name='origin' | ||
endAdornment={ | ||
<InputAdornment position='end' sx={{ padding: '2px' }}> | ||
{inputFields.length > 1 && ( | ||
<IconButton | ||
sx={{ height: 30, width: 30 }} | ||
size='small' | ||
color='error' | ||
disabled={inputFields.length === 1} | ||
onClick={() => removeInputFields(index)} | ||
edge='end' | ||
> | ||
<IconTrash /> | ||
</IconButton> | ||
)} | ||
</InputAdornment> | ||
} | ||
/> | ||
</Box> | ||
<Box sx={{ width: '5%', mb: 1 }}> | ||
{index === inputFields.length - 1 && ( | ||
<IconButton color='primary' onClick={addInputField}> | ||
<IconPlus /> | ||
</IconButton> | ||
)} | ||
</Box> | ||
</div> | ||
) | ||
})} | ||
</List> | ||
</Box> | ||
</DialogContent> | ||
<DialogActions> | ||
<Button onClick={onCancel}>Cancel</Button> | ||
<StyledButton variant='contained' onClick={onSave}> | ||
Save | ||
</StyledButton> | ||
</DialogActions> | ||
</Dialog> | ||
) : null | ||
|
||
return createPortal(component, portalElement) | ||
} | ||
|
||
AllowedDomainsDialog.propTypes = { | ||
show: PropTypes.bool, | ||
dialogProps: PropTypes.object, | ||
onCancel: PropTypes.func, | ||
onConfirm: PropTypes.func | ||
} | ||
|
||
export default AllowedDomainsDialog |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if
parsedConfig
doesn't have the propertyallowedOrigins
, will it fails?shouldn't we do something like
parsedConfig.allowedOrigins?.length && parsedConfig.allowedOrigins[0] !== ''
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, it does error:
