This repository has been archived by the owner on Oct 27, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add CORS instance settings (#1239)
* feat: add CORS instance settings * refactor: hide the CORS page when embedProxy is false
- Loading branch information
Showing
15 changed files
with
297 additions
and
27 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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,23 @@ | ||
import { | ||
parseInputValue, | ||
formatInputValue, | ||
} from 'component/admin/cors/CorsForm'; | ||
|
||
test('parseInputValue', () => { | ||
const fn = parseInputValue; | ||
expect(fn('')).toEqual([]); | ||
expect(fn('a')).toEqual(['a']); | ||
expect(fn('a\nb,,c,d,')).toEqual(['a', 'b', 'c', 'd']); | ||
expect(fn('http://localhost:8080')).toEqual(['http://localhost:8080']); | ||
expect(fn('https://example.com')).toEqual(['https://example.com']); | ||
expect(fn('https://example.com/')).toEqual(['https://example.com']); | ||
expect(fn('https://example.com/')).toEqual(['https://example.com']); | ||
}); | ||
|
||
test('formatInputValue', () => { | ||
const fn = formatInputValue; | ||
expect(fn(undefined)).toEqual(''); | ||
expect(fn([])).toEqual(''); | ||
expect(fn(['a'])).toEqual('a'); | ||
expect(fn(['a', 'b', 'c', 'd'])).toEqual('a\nb\nc\nd'); | ||
}); |
This file contains 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,74 @@ | ||
import { ADMIN } from 'component/providers/AccessProvider/permissions'; | ||
import React, { useState } from 'react'; | ||
import { TextField, Box } from '@mui/material'; | ||
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton'; | ||
import { useUiConfigApi } from 'hooks/api/actions/useUiConfigApi/useUiConfigApi'; | ||
import useToast from 'hooks/useToast'; | ||
import { formatUnknownError } from 'utils/formatUnknownError'; | ||
import { useId } from 'hooks/useId'; | ||
|
||
interface ICorsFormProps { | ||
frontendApiOrigins: string[] | undefined; | ||
} | ||
|
||
export const CorsForm = ({ frontendApiOrigins }: ICorsFormProps) => { | ||
const { setFrontendSettings } = useUiConfigApi(); | ||
const { setToastData, setToastApiError } = useToast(); | ||
const [value, setValue] = useState(formatInputValue(frontendApiOrigins)); | ||
const inputFieldId = useId(); | ||
const helpTextId = useId(); | ||
|
||
const onSubmit = async (event: React.FormEvent) => { | ||
try { | ||
const split = parseInputValue(value); | ||
event.preventDefault(); | ||
await setFrontendSettings(split); | ||
setValue(formatInputValue(split)); | ||
setToastData({ title: 'Settings saved', type: 'success' }); | ||
} catch (error) { | ||
setToastApiError(formatUnknownError(error)); | ||
} | ||
}; | ||
|
||
return ( | ||
<form onSubmit={onSubmit}> | ||
<Box sx={{ display: 'grid', gap: 1 }}> | ||
<label htmlFor={inputFieldId}> | ||
Which origins should be allowed to call the Frontend API | ||
(one per line)? | ||
</label> | ||
<TextField | ||
id={inputFieldId} | ||
aria-describedby={helpTextId} | ||
placeholder={textareaDomainsPlaceholder} | ||
value={value} | ||
onChange={event => setValue(event.target.value)} | ||
multiline | ||
rows={12} | ||
variant="outlined" | ||
fullWidth | ||
InputProps={{ | ||
style: { fontFamily: 'monospace', fontSize: '0.8em' }, | ||
}} | ||
/> | ||
<UpdateButton permission={ADMIN} /> | ||
</Box> | ||
</form> | ||
); | ||
}; | ||
|
||
export const parseInputValue = (value: string): string[] => { | ||
return value | ||
.split(/[,\n\s]+/) // Split by commas/newlines/spaces. | ||
.map(value => value.replace(/\/$/, '')) // Remove trailing slashes. | ||
.filter(Boolean); // Remove empty values from (e.g.) double newlines. | ||
}; | ||
|
||
export const formatInputValue = (values: string[] | undefined): string => { | ||
return values?.join('\n') ?? ''; | ||
}; | ||
|
||
const textareaDomainsPlaceholder = [ | ||
'https://example.com', | ||
'https://example.org', | ||
].join('\n'); |
This file contains 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,22 @@ | ||
import React from 'react'; | ||
import { Alert } from '@mui/material'; | ||
|
||
export const CorsHelpAlert = () => { | ||
return ( | ||
<Alert severity="info"> | ||
<p> | ||
Use this page to configure allowed CORS origins for the Frontend | ||
API (<code>/api/frontend</code>). | ||
</p> | ||
<p> | ||
This configuration will not affect the Admin API ( | ||
<code>/api/admin</code>) nor the Client API ( | ||
<code>/api/client</code>). | ||
</p> | ||
<p> | ||
An asterisk (<code>*</code>) may be used to allow API calls from | ||
any origin. | ||
</p> | ||
</Alert> | ||
); | ||
}; |
This file contains 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,17 @@ | ||
import { TokenType } from 'interfaces/token'; | ||
import { Link } from 'react-router-dom'; | ||
import { Alert } from '@mui/material'; | ||
|
||
export const CorsTokenAlert = () => { | ||
return ( | ||
<Alert sx={{ mt: 4 }} severity="info"> | ||
By default, all {TokenType.FRONTEND} tokens may be used from any | ||
CORS origin. If you'd like to configure a strict set of origins, | ||
please use the{' '} | ||
<Link to="/admin/cors" target="_blank"> | ||
CORS origins configuration page | ||
</Link> | ||
. | ||
</Alert> | ||
); | ||
}; |
This file contains 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,50 @@ | ||
import { useLocation } from 'react-router-dom'; | ||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||
import AdminMenu from '../menu/AdminMenu'; | ||
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; | ||
import { ADMIN } from 'component/providers/AccessProvider/permissions'; | ||
import AccessContext from 'contexts/AccessContext'; | ||
import React, { useContext } from 'react'; | ||
import { PageContent } from 'component/common/PageContent/PageContent'; | ||
import { PageHeader } from 'component/common/PageHeader/PageHeader'; | ||
import { Box } from '@mui/material'; | ||
import { CorsHelpAlert } from 'component/admin/cors/CorsHelpAlert'; | ||
import { CorsForm } from 'component/admin/cors/CorsForm'; | ||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||
|
||
export const CorsAdmin = () => { | ||
const { pathname } = useLocation(); | ||
const showAdminMenu = pathname.includes('/admin/'); | ||
const { hasAccess } = useContext(AccessContext); | ||
|
||
return ( | ||
<div> | ||
<ConditionallyRender | ||
condition={showAdminMenu} | ||
show={<AdminMenu />} | ||
/> | ||
<ConditionallyRender | ||
condition={hasAccess(ADMIN)} | ||
show={<CorsPage />} | ||
elseShow={<AdminAlert />} | ||
/> | ||
</div> | ||
); | ||
}; | ||
|
||
const CorsPage = () => { | ||
const { uiConfig, loading } = useUiConfig(); | ||
|
||
if (loading) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<PageContent header={<PageHeader title="CORS origins" />}> | ||
<Box sx={{ display: 'grid', gap: 4 }}> | ||
<CorsHelpAlert /> | ||
<CorsForm frontendApiOrigins={uiConfig.frontendApiOrigins} /> | ||
</Box> | ||
</PageContent> | ||
); | ||
}; |
This file contains 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
This file contains 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
This file contains 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
Oops, something went wrong.