feat(sso): introduce authentication with SAML

This commit is contained in:
Ali BARIN
2023-07-06 11:05:28 +00:00
parent aac1295c10
commit b581f539e2
28 changed files with 720 additions and 9 deletions

View File

@@ -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

View 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;

View File

@@ -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;

View File

@@ -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';

View File

@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
export const GET_SAML_AUTH_PROVIDERS = gql`
query GetSamlAuthProviders {
getSamlAuthProviders {
id
name
issuer
}
}
`;

View 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
};
}

View File

@@ -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!",

View File

@@ -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>
);

View 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 (<></>);
}

View File

@@ -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={