feat: Remove OIDC and SSO support
Existing SSO accounts have no local password, so the migration deactivates them before dropping is_sso_user and the identity_provider_user table.
This commit is contained in:
@@ -32,7 +32,6 @@ const StepTypes = {
|
||||
EDIT_PASSWORD: 'EDIT_PASSWORD',
|
||||
EDIT_ROLE: 'EDIT_ROLE',
|
||||
API_KEY: 'API_KEY',
|
||||
UNLINK_SSO: 'UNLINK_SSO',
|
||||
ACTIVATE: 'ACTIVATE',
|
||||
DEACTIVATE: 'DEACTIVATE',
|
||||
DELETE: 'DELETE',
|
||||
@@ -61,16 +60,6 @@ const ActionsStep = React.memo(({ userId, onClose }) => {
|
||||
[userId, dispatch],
|
||||
);
|
||||
|
||||
const handleUnlinkSsoConfirm = useCallback(() => {
|
||||
dispatch(
|
||||
entryActions.updateUser(userId, {
|
||||
isSsoUser: false,
|
||||
}),
|
||||
);
|
||||
|
||||
onClose();
|
||||
}, [userId, onClose, dispatch]);
|
||||
|
||||
const handleActivateConfirm = useCallback(() => {
|
||||
dispatch(
|
||||
entryActions.updateUser(userId, {
|
||||
@@ -123,10 +112,6 @@ const ActionsStep = React.memo(({ userId, onClose }) => {
|
||||
openStep(StepTypes.API_KEY);
|
||||
}, [openStep]);
|
||||
|
||||
const handleUnlinkSsoClick = useCallback(() => {
|
||||
openStep(StepTypes.UNLINK_SSO);
|
||||
}, [openStep]);
|
||||
|
||||
const handleActivateClick = useCallback(() => {
|
||||
openStep(StepTypes.ACTIVATE);
|
||||
}, [openStep]);
|
||||
@@ -165,16 +150,6 @@ const ActionsStep = React.memo(({ userId, onClose }) => {
|
||||
);
|
||||
case StepTypes.API_KEY:
|
||||
return <ApiKeyStep userId={userId} onBack={handleBack} onClose={onClose} />;
|
||||
case StepTypes.UNLINK_SSO:
|
||||
return (
|
||||
<ConfirmationStep
|
||||
title="common.unlinkSso"
|
||||
content="common.areYouSureYouWantToUnlinkSsoFromThisUser"
|
||||
buttonContent="action.unlinkSso"
|
||||
onConfirm={handleUnlinkSsoConfirm}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
);
|
||||
case StepTypes.ACTIVATE:
|
||||
return (
|
||||
<ConfirmationStep
|
||||
@@ -271,14 +246,6 @@ const ActionsStep = React.memo(({ userId, onClose }) => {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
{user.isSsoUser && !user.lockedFieldNames.includes('isSsoUser') && !isCurrentUser && (
|
||||
<Menu.Item className={styles.menuItem} onClick={handleUnlinkSsoClick}>
|
||||
<Icon name="unlink" className={styles.menuItemIcon} />
|
||||
{t('action.unlinkSso', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{!isCurrentUser && (
|
||||
<>
|
||||
<Menu.Item
|
||||
|
||||
@@ -21,11 +21,6 @@ const UsersPane = React.memo(() => {
|
||||
const activeUsersTotal = useSelector(selectors.selectActiveUsersTotal);
|
||||
const users = useSelector(selectors.selectUsers);
|
||||
|
||||
const canAdd = useSelector((state) => {
|
||||
const oidcBootstrap = selectors.selectOidcBootstrap(state);
|
||||
return !oidcBootstrap || !oidcBootstrap.isEnforced;
|
||||
});
|
||||
|
||||
const [t] = useTranslation();
|
||||
|
||||
const [search, handleSearchChange] = useField('');
|
||||
@@ -102,22 +97,20 @@ const UsersPane = React.memo(() => {
|
||||
className={styles.toggleDeactivatedButton}
|
||||
onClick={handleToggleDeactivatedClick}
|
||||
/>
|
||||
{canAdd && (
|
||||
<AddPopup>
|
||||
<Button
|
||||
positive
|
||||
disabled={activeUsersLimit !== null && activeUsersTotal >= activeUsersLimit}
|
||||
className={styles.addButton}
|
||||
>
|
||||
{t('action.addUser')}
|
||||
{activeUsersLimit !== null && (
|
||||
<span className={styles.addButtonCounter}>
|
||||
{activeUsersTotal}/{activeUsersLimit}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</AddPopup>
|
||||
)}
|
||||
<AddPopup>
|
||||
<Button
|
||||
positive
|
||||
disabled={activeUsersLimit !== null && activeUsersTotal >= activeUsersLimit}
|
||||
className={styles.addButton}
|
||||
>
|
||||
{t('action.addUser')}
|
||||
{activeUsersLimit !== null && (
|
||||
<span className={styles.addButtonCounter}>
|
||||
{activeUsersTotal}/{activeUsersLimit}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</AddPopup>
|
||||
</div>
|
||||
</Tab.Pane>
|
||||
);
|
||||
|
||||
@@ -8,8 +8,7 @@ import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { Button, Divider, Form, Grid, Header, Message, TextArea } from 'semantic-ui-react';
|
||||
import { Form, Grid, Header, Message } from 'semantic-ui-react';
|
||||
import { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';
|
||||
import { Input } from '../../../lib/custom-ui';
|
||||
|
||||
@@ -24,7 +23,7 @@ import logo from '../../../assets/images/logo.png';
|
||||
|
||||
import styles from './Content.module.scss';
|
||||
|
||||
const createMessage = (error, isDebug) => {
|
||||
const createMessage = (error) => {
|
||||
if (!error) {
|
||||
return error;
|
||||
}
|
||||
@@ -45,11 +44,6 @@ const createMessage = (error, isDebug) => {
|
||||
type: 'error',
|
||||
content: 'common.invalidPassword',
|
||||
};
|
||||
case 'Use single sign-on':
|
||||
return {
|
||||
type: 'error',
|
||||
content: 'common.useSingleSignOn',
|
||||
};
|
||||
case 'Admin login required to initialize instance':
|
||||
return {
|
||||
type: 'error',
|
||||
@@ -83,7 +77,7 @@ const createMessage = (error, isDebug) => {
|
||||
default:
|
||||
return {
|
||||
type: 'warning',
|
||||
content: isDebug ? error.message : 'common.unknownError',
|
||||
content: 'common.unknownError',
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -94,9 +88,7 @@ const Content = React.memo(() => {
|
||||
const {
|
||||
data: defaultData,
|
||||
isSubmitting,
|
||||
isSubmittingWithOidc,
|
||||
error,
|
||||
debugLogs,
|
||||
step,
|
||||
} = useSelector(selectors.selectAuthenticateForm);
|
||||
|
||||
@@ -126,11 +118,7 @@ const Content = React.memo(() => {
|
||||
return initialData;
|
||||
});
|
||||
|
||||
const withOidc = !!bootstrap.oidc;
|
||||
const isOidcEnforced = withOidc && bootstrap.oidc.isEnforced;
|
||||
const isOidcDebug = withOidc && bootstrap.oidc.debug;
|
||||
|
||||
const message = useMemo(() => createMessage(error, isOidcDebug), [error, isOidcDebug]);
|
||||
const message = useMemo(() => createMessage(error), [error]);
|
||||
const [focusPasswordFieldState, focusPasswordField] = useToggle();
|
||||
|
||||
const [emailOrUsernameFieldRef, handleEmailOrUsernameFieldRef] = useNestedRef('inputRef');
|
||||
@@ -155,19 +143,13 @@ const Content = React.memo(() => {
|
||||
dispatch(entryActions.authenticate(cleanData));
|
||||
}, [dispatch, data, emailOrUsernameFieldRef, passwordFieldRef]);
|
||||
|
||||
const handleAuthenticateWithOidcClick = useCallback(() => {
|
||||
dispatch(entryActions.authenticateWithOidc());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleMessageDismiss = useCallback(() => {
|
||||
dispatch(entryActions.clearAuthenticateError());
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOidcEnforced) {
|
||||
emailOrUsernameFieldRef.current.focus();
|
||||
}
|
||||
}, [isOidcEnforced, emailOrUsernameFieldRef]);
|
||||
emailOrUsernameFieldRef.current.focus();
|
||||
}, [emailOrUsernameFieldRef]);
|
||||
|
||||
useDidUpdate(() => {
|
||||
if (wasSubmitting && !isSubmitting && error) {
|
||||
@@ -227,73 +209,43 @@ const Content = React.memo(() => {
|
||||
onDismiss={handleMessageDismiss}
|
||||
/>
|
||||
)}
|
||||
{!isOidcEnforced && (
|
||||
<>
|
||||
<Form size="large" onSubmit={handleSubmit}>
|
||||
<div className={styles.inputWrapper}>
|
||||
<div className={styles.inputLabel}>{t('common.emailOrUsername')}</div>
|
||||
<Input
|
||||
fluid
|
||||
ref={handleEmailOrUsernameFieldRef}
|
||||
name="emailOrUsername"
|
||||
value={data.emailOrUsername}
|
||||
maxLength={256}
|
||||
readOnly={isSubmitting}
|
||||
className={styles.input}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.inputWrapper}>
|
||||
<div className={styles.inputLabel}>{t('common.password')}</div>
|
||||
<Input.Password
|
||||
fluid
|
||||
ref={handlePasswordFieldRef}
|
||||
name="password"
|
||||
value={data.password}
|
||||
maxLength={256}
|
||||
readOnly={isSubmitting}
|
||||
className={styles.input}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
</div>
|
||||
<Form.Button
|
||||
fluid
|
||||
primary
|
||||
icon="right arrow"
|
||||
labelPosition="right"
|
||||
content={t('action.logIn')}
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting || isSubmittingWithOidc}
|
||||
/>
|
||||
</Form>
|
||||
{withOidc && (
|
||||
<Divider horizontal content={t('common.or')} className={styles.divider} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{withOidc && (
|
||||
<>
|
||||
<Button
|
||||
<Form size="large" onSubmit={handleSubmit}>
|
||||
<div className={styles.inputWrapper}>
|
||||
<div className={styles.inputLabel}>{t('common.emailOrUsername')}</div>
|
||||
<Input
|
||||
fluid
|
||||
primary={isOidcDebug ? undefined : isOidcEnforced}
|
||||
color={isOidcDebug ? 'orange' : undefined}
|
||||
icon={isOidcEnforced ? 'right arrow' : undefined}
|
||||
labelPosition={isOidcEnforced ? 'right' : undefined}
|
||||
content={isOidcDebug ? t('action.debugSso') : t('action.logInWithSso')}
|
||||
loading={isSubmittingWithOidc}
|
||||
disabled={isSubmitting || isSubmittingWithOidc}
|
||||
onClick={handleAuthenticateWithOidcClick}
|
||||
ref={handleEmailOrUsernameFieldRef}
|
||||
name="emailOrUsername"
|
||||
value={data.emailOrUsername}
|
||||
maxLength={256}
|
||||
readOnly={isSubmitting}
|
||||
className={styles.input}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
{debugLogs && (
|
||||
<TextArea
|
||||
readOnly
|
||||
as={TextareaAutosize}
|
||||
value={debugLogs.join('\n')}
|
||||
className={styles.debugLog}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.inputWrapper}>
|
||||
<div className={styles.inputLabel}>{t('common.password')}</div>
|
||||
<Input.Password
|
||||
fluid
|
||||
ref={handlePasswordFieldRef}
|
||||
name="password"
|
||||
value={data.password}
|
||||
maxLength={256}
|
||||
readOnly={isSubmitting}
|
||||
className={styles.input}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
</div>
|
||||
<Form.Button
|
||||
fluid
|
||||
primary
|
||||
icon="right arrow"
|
||||
labelPosition="right"
|
||||
content={t('action.logIn')}
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
<div className={styles.poweredBy}>
|
||||
<p className={styles.poweredByText}>
|
||||
|
||||
@@ -32,7 +32,6 @@ function Root({ store, history }) {
|
||||
<ToasterProvider toaster={toaster}>
|
||||
<Routes>
|
||||
<Route path={Paths.LOGIN} element={<Login />} />
|
||||
<Route path={Paths.OIDC_CALLBACK} element={<Login />} />
|
||||
<Route path={Paths.ROOT} element={<Core />} />
|
||||
<Route path={Paths.PROJECTS} element={<Core />} />
|
||||
<Route path={Paths.BOARDS} element={<Core />} />
|
||||
|
||||
@@ -47,12 +47,11 @@ const EditUserEmailStep = React.memo(({ id, onBack, onClose }) => {
|
||||
|
||||
const {
|
||||
email,
|
||||
isSsoUser,
|
||||
emailUpdateForm: { data: defaultData, isSubmitting, error },
|
||||
} = useSelector((state) => selectUserById(state, id));
|
||||
|
||||
const withPasswordConfirmation = useSelector(
|
||||
(state) => id === selectors.selectCurrentUserId(state) && !isSsoUser,
|
||||
(state) => id === selectors.selectCurrentUserId(state),
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -42,12 +42,11 @@ const EditUserPasswordStep = React.memo(({ id, onBack, onClose }) => {
|
||||
const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);
|
||||
|
||||
const {
|
||||
isSsoUser,
|
||||
passwordUpdateForm: { data: defaultData, isSubmitting, error },
|
||||
} = useSelector((state) => selectUserById(state, id));
|
||||
|
||||
const withPasswordConfirmation = useSelector(
|
||||
(state) => id === selectors.selectCurrentUserId(state) && !isSsoUser,
|
||||
(state) => id === selectors.selectCurrentUserId(state),
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -47,12 +47,11 @@ const EditUserUsernameStep = React.memo(({ id, onBack, onClose }) => {
|
||||
|
||||
const {
|
||||
username,
|
||||
isSsoUser,
|
||||
usernameUpdateForm: { data: defaultData, isSubmitting, error },
|
||||
} = useSelector((state) => selectUserById(state, id));
|
||||
|
||||
const withPasswordConfirmation = useSelector(
|
||||
(state) => id === selectors.selectCurrentUserId(state) && !isSsoUser,
|
||||
(state) => id === selectors.selectCurrentUserId(state),
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
Reference in New Issue
Block a user