Skip to content

Commit

Permalink
feat: Add ability to enforce SSO
Browse files Browse the repository at this point in the history
Closes #543, closes #545
  • Loading branch information
meltyshev committed Jan 31, 2024
1 parent a1fd694 commit b8d262f
Show file tree
Hide file tree
Showing 10 changed files with 80 additions and 46 deletions.
90 changes: 50 additions & 40 deletions client/src/components/Login/Login.jsx
Expand Up @@ -68,6 +68,7 @@ const Login = React.memo(
isSubmittingUsingOidc,
error,
withOidc,
isOidcEnforced,
onAuthenticate,
onAuthenticateUsingOidc,
onMessageDismiss,
Expand Down Expand Up @@ -107,8 +108,10 @@ const Login = React.memo(
}, [onAuthenticate, data]);

useEffect(() => {
emailOrUsernameField.current.focus();
}, []);
if (!isOidcEnforced) {
emailOrUsernameField.current.focus();
}
}, [isOidcEnforced]);

useEffect(() => {
if (wasSubmitting && !isSubmitting && error) {
Expand Down Expand Up @@ -159,51 +162,57 @@ const Login = React.memo(
onDismiss={onMessageDismiss}
/>
)}
<Form size="large" onSubmit={handleSubmit}>
<div className={styles.inputWrapper}>
<div className={styles.inputLabel}>{t('common.emailOrUsername')}</div>
<Input
fluid
ref={emailOrUsernameField}
name="emailOrUsername"
value={data.emailOrUsername}
readOnly={isSubmitting}
className={styles.input}
onChange={handleFieldChange}
/>
</div>
<div className={styles.inputWrapper}>
<div className={styles.inputLabel}>{t('common.password')}</div>
<Input.Password
fluid
ref={passwordField}
name="password"
value={data.password}
readOnly={isSubmitting}
className={styles.input}
onChange={handleFieldChange}
{!isOidcEnforced && (
<Form size="large" onSubmit={handleSubmit}>
<div className={styles.inputWrapper}>
<div className={styles.inputLabel}>{t('common.emailOrUsername')}</div>
<Input
fluid
ref={emailOrUsernameField}
name="emailOrUsername"
value={data.emailOrUsername}
readOnly={isSubmitting}
className={styles.input}
onChange={handleFieldChange}
/>
</div>
<div className={styles.inputWrapper}>
<div className={styles.inputLabel}>{t('common.password')}</div>
<Input.Password
fluid
ref={passwordField}
name="password"
value={data.password}
readOnly={isSubmitting}
className={styles.input}
onChange={handleFieldChange}
/>
</div>
<Form.Button
primary
size="large"
icon="right arrow"
labelPosition="right"
content={t('action.logIn')}
floated="right"
loading={isSubmitting}
disabled={isSubmitting || isSubmittingUsingOidc}
/>
</div>
<Form.Button
primary
size="large"
icon="right arrow"
labelPosition="right"
content={t('action.logIn')}
floated="right"
loading={isSubmitting}
disabled={isSubmitting || isSubmittingUsingOidc}
/>
</Form>
</Form>
)}
{withOidc && (
<Button
type="button"
fluid={isOidcEnforced}
primary={isOidcEnforced}
size={isOidcEnforced ? 'large' : undefined}
icon={isOidcEnforced ? 'right arrow' : undefined}
labelPosition={isOidcEnforced ? 'right' : undefined}
content={t('action.logInWithSSO')}
loading={isSubmittingUsingOidc}
disabled={isSubmitting || isSubmittingUsingOidc}
onClick={onAuthenticateUsingOidc}
>
{t('action.logInWithSSO')}
</Button>
/>
)}
</div>
</div>
Expand Down Expand Up @@ -242,6 +251,7 @@ Login.propTypes = {
isSubmittingUsingOidc: PropTypes.bool.isRequired,
error: PropTypes.object, // eslint-disable-line react/forbid-prop-types
withOidc: PropTypes.bool.isRequired,
isOidcEnforced: PropTypes.bool.isRequired,
onAuthenticate: PropTypes.func.isRequired,
onAuthenticateUsingOidc: PropTypes.func.isRequired,
onMessageDismiss: PropTypes.func.isRequired,
Expand Down
14 changes: 9 additions & 5 deletions client/src/components/UsersModal/UsersModal.jsx
Expand Up @@ -10,6 +10,7 @@ import Item from './Item';
const UsersModal = React.memo(
({
items,
canAdd,
onUpdate,
onUsernameUpdate,
onUsernameUpdateMessageDismiss,
Expand Down Expand Up @@ -130,18 +131,21 @@ const UsersModal = React.memo(
</Table.Body>
</Table>
</Modal.Content>
<Modal.Actions>
<UserAddPopupContainer>
<Button positive content={t('action.addUser')} />
</UserAddPopupContainer>
</Modal.Actions>
{canAdd && (
<Modal.Actions>
<UserAddPopupContainer>
<Button positive content={t('action.addUser')} />
</UserAddPopupContainer>
</Modal.Actions>
)}
</Modal>
);
},
);

UsersModal.propTypes = {
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
canAdd: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired,
onUsernameUpdate: PropTypes.func.isRequired,
onUsernameUpdateMessageDismiss: PropTypes.func.isRequired,
Expand Down
1 change: 1 addition & 0 deletions client/src/containers/LoginContainer.js
Expand Up @@ -20,6 +20,7 @@ const mapStateToProps = (state) => {
isSubmittingUsingOidc,
error,
withOidc: !!oidcConfig,
isOidcEnforced: oidcConfig && oidcConfig.isEnforced,
};
};

Expand Down
2 changes: 2 additions & 0 deletions client/src/containers/UsersModalContainer.js
Expand Up @@ -6,10 +6,12 @@ import entryActions from '../entry-actions';
import UsersModal from '../components/UsersModal';

const mapStateToProps = (state) => {
const oidcConfig = selectors.selectOidcConfig(state);
const users = selectors.selectUsersExceptCurrent(state);

return {
items: users,
canAdd: !oidcConfig || !oidcConfig.isEnforced,
};
};

Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Expand Up @@ -50,6 +50,7 @@ services:
# - OIDC_ROLES_ATTRIBUTE=groups
# - OIDC_IGNORE_USERNAME=true
# - OIDC_IGNORE_ROLES=true
# - OIDC_ENFORCED=true
depends_on:
- postgres

Expand Down
1 change: 1 addition & 0 deletions server/.env.sample
Expand Up @@ -33,6 +33,7 @@ SECRET_KEY=notsecretkey
# OIDC_ROLES_ATTRIBUTE=groups
# OIDC_IGNORE_USERNAME=true
# OIDC_IGNORE_ROLES=true
# OIDC_ENFORCED=true

## Do not edit this

Expand Down
5 changes: 4 additions & 1 deletion server/api/controllers/access-tokens/create.js
Expand Up @@ -46,8 +46,11 @@ module.exports = {
},

async fn(inputs) {
const remoteAddress = getRemoteAddress(this.req);
if (sails.config.custom.oidcEnforced) {
throw Errors.USE_SINGLE_SIGN_ON;
}

const remoteAddress = getRemoteAddress(this.req);
const user = await sails.helpers.users.getOneByEmailOrUsername(inputs.emailOrUsername);

if (!user) {
Expand Down
1 change: 1 addition & 0 deletions server/api/controllers/show-config.js
Expand Up @@ -10,6 +10,7 @@ module.exports = {
response_mode: 'fragment',
}),
endSessionUrl: oidcClient.issuer.end_session_endpoint ? oidcClient.endSessionUrl({}) : null,
isEnforced: sails.config.custom.oidcEnforced,
};
}

Expand Down
10 changes: 10 additions & 0 deletions server/api/controllers/users/create.js
@@ -1,6 +1,9 @@
const zxcvbn = require('zxcvbn');

const Errors = {
NOT_ENOUGH_RIGHTS: {
notEnoughRights: 'Not enough rights',
},
EMAIL_ALREADY_IN_USE: {
emailAlreadyInUse: 'Email already in use',
},
Expand Down Expand Up @@ -56,6 +59,9 @@ module.exports = {
},

exits: {
notEnoughRights: {
responseType: 'forbidden',
},
emailAlreadyInUse: {
responseType: 'conflict',
},
Expand All @@ -65,6 +71,10 @@ module.exports = {
},

async fn(inputs) {
if (sails.config.custom.oidcEnforced) {
throw Errors.NOT_ENOUGH_RIGHTS;
}

const values = _.pick(inputs, [
'email',
'password',
Expand Down
1 change: 1 addition & 0 deletions server/config/custom.js
Expand Up @@ -44,6 +44,7 @@ module.exports.custom = {
oidcRolesAttribute: process.env.OIDC_ROLES_ATTRIBUTE || 'groups',
oidcIgnoreUsername: process.env.OIDC_IGNORE_USERNAME === 'true',
oidcIgnoreRoles: process.env.OIDC_IGNORE_ROLES === 'true',
oidcEnforced: process.env.OIDC_ENFORCED === 'true',

// TODO: move client base url to environment variable?
oidcRedirectUri: `${
Expand Down

0 comments on commit b8d262f

Please sign in to comment.