feat(sso): introduce authentication with SAML
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
PORT=3001
|
||||
REACT_APP_API_URL=http://localhost:3000
|
||||
REACT_APP_GRAPHQL_URL=http://localhost:3000/graphql
|
||||
# HTTPS=true
|
||||
REACT_APP_BASE_URL=http://localhost:3001
|
||||
|
39
packages/web/src/components/SsoProviders/index.ee.tsx
Normal file
39
packages/web/src/components/SsoProviders/index.ee.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import * as React from 'react';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Button from '@mui/material/Button';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Divider from '@mui/material/Divider';
|
||||
|
||||
import appConfig from 'config/app';
|
||||
import useSamlAuthProviders from 'hooks/useSamlAuthProviders.ee';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
|
||||
function SsoProviders() {
|
||||
const formatMessage = useFormatMessage();
|
||||
const { providers, loading } = useSamlAuthProviders();
|
||||
|
||||
if (!loading && providers.length === 0) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider>{formatMessage('loginPage.divider')}</Divider>
|
||||
|
||||
<Paper sx={{ px: 2, py: 4 }}>
|
||||
<Stack direction="column" gap={1}>
|
||||
{providers.map((provider) => (
|
||||
<Button
|
||||
key={provider.id}
|
||||
component="a"
|
||||
href={`${appConfig.apiUrl}/login/saml/${provider.issuer}`}
|
||||
variant="outlined"
|
||||
>
|
||||
{provider.name}
|
||||
</Button>
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default SsoProviders;
|
@@ -1,13 +1,24 @@
|
||||
type Config = {
|
||||
[key: string]: string;
|
||||
baseUrl: string;
|
||||
apiUrl: string;
|
||||
graphqlUrl: string;
|
||||
notificationsUrl: string;
|
||||
chatwootBaseUrl: string;
|
||||
supportEmailAddress: string;
|
||||
};
|
||||
|
||||
const config: Config = {
|
||||
baseUrl: process.env.REACT_APP_BASE_URL as string,
|
||||
apiUrl: process.env.REACT_APP_API_URL as string,
|
||||
graphqlUrl: process.env.REACT_APP_GRAPHQL_URL as string,
|
||||
notificationsUrl: process.env.REACT_APP_NOTIFICATIONS_URL as string,
|
||||
chatwootBaseUrl: 'https://app.chatwoot.com',
|
||||
supportEmailAddress: 'support@automatisch.io'
|
||||
};
|
||||
|
||||
if (!config.apiUrl) {
|
||||
config.apiUrl = (new URL(config.graphqlUrl)).origin;
|
||||
}
|
||||
|
||||
export default config;
|
||||
|
@@ -5,6 +5,7 @@ export const EXECUTION = (executionId: string): string =>
|
||||
`/executions/${executionId}`;
|
||||
|
||||
export const LOGIN = '/login';
|
||||
export const LOGIN_CALLBACK = `${LOGIN}/callback`;
|
||||
export const SIGNUP = '/sign-up';
|
||||
export const FORGOT_PASSWORD = '/forgot-password';
|
||||
export const RESET_PASSWORD = '/reset-password';
|
||||
|
@@ -0,0 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_SAML_AUTH_PROVIDERS = gql`
|
||||
query GetSamlAuthProviders {
|
||||
getSamlAuthProviders {
|
||||
id
|
||||
name
|
||||
issuer
|
||||
}
|
||||
}
|
||||
`;
|
18
packages/web/src/hooks/useSamlAuthProviders.ee.ts
Normal file
18
packages/web/src/hooks/useSamlAuthProviders.ee.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
|
||||
import { TSamlAuthProvider } from '@automatisch/types';
|
||||
import { GET_SAML_AUTH_PROVIDERS } from 'graphql/queries/get-saml-auth-providers.ee';
|
||||
|
||||
type UseSamlAuthProvidersReturn = {
|
||||
providers: TSamlAuthProvider[];
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
export default function useSamlAuthProviders(): UseSamlAuthProvidersReturn {
|
||||
const { data, loading } = useQuery(GET_SAML_AUTH_PROVIDERS);
|
||||
|
||||
return {
|
||||
providers: data?.getSamlAuthProviders || [],
|
||||
loading
|
||||
};
|
||||
}
|
@@ -129,6 +129,7 @@
|
||||
"loginForm.submit": "Login",
|
||||
"loginForm.noAccount": "Don't have an Automatisch account yet?",
|
||||
"loginForm.signUp": "Sign up",
|
||||
"loginPage.divider": "OR",
|
||||
"forgotPasswordForm.title": "Forgot password",
|
||||
"forgotPasswordForm.submit": "Send reset instructions",
|
||||
"forgotPasswordForm.instructionsSent": "The instructions have been sent!",
|
||||
|
@@ -1,13 +1,20 @@
|
||||
import * as React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Container from 'components/Container';
|
||||
import LoginForm from 'components/LoginForm';
|
||||
import SsoProviders from 'components/SsoProviders/index.ee';
|
||||
|
||||
|
||||
export default function Login(): React.ReactElement {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flex: 1, alignItems: 'center' }}>
|
||||
<Container maxWidth="sm">
|
||||
<LoginForm />
|
||||
<Stack direction="column" gap={2}>
|
||||
<LoginForm />
|
||||
|
||||
<SsoProviders />
|
||||
</Stack>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
|
29
packages/web/src/pages/LoginCallback/index.tsx
Normal file
29
packages/web/src/pages/LoginCallback/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as React from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
|
||||
import useAuthentication from 'hooks/useAuthentication';
|
||||
import * as URLS from 'config/urls';
|
||||
|
||||
export default function LoginCallback(): React.ReactElement {
|
||||
const navigate = useNavigate();
|
||||
const authentication = useAuthentication();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (authentication.isAuthenticated) {
|
||||
navigate(URLS.DASHBOARD);
|
||||
}
|
||||
}, [authentication.isAuthenticated]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const token = searchParams.get('token');
|
||||
|
||||
if (token) {
|
||||
authentication.updateToken(token);
|
||||
}
|
||||
|
||||
// TODO: handle non-existing token scenario
|
||||
}, []);
|
||||
|
||||
return (<></>);
|
||||
}
|
@@ -8,6 +8,7 @@ import Execution from 'pages/Execution';
|
||||
import Flows from 'pages/Flows';
|
||||
import Flow from 'pages/Flow';
|
||||
import Login from 'pages/Login';
|
||||
import LoginCallback from 'pages/LoginCallback';
|
||||
import SignUp from 'pages/SignUp/index.ee';
|
||||
import ForgotPassword from 'pages/ForgotPassword/index.ee';
|
||||
import ResetPassword from 'pages/ResetPassword/index.ee';
|
||||
@@ -83,6 +84,11 @@ export default (
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={URLS.LOGIN_CALLBACK}
|
||||
element={<LoginCallback />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={URLS.SIGNUP}
|
||||
element={
|
||||
|
Reference in New Issue
Block a user