mirror of
https://github.com/plankanban/planka.git
synced 2025-07-19 21:29:43 +02:00
parent
988afba1d0
commit
6c826c7127
10 changed files with 80 additions and 46 deletions
|
@ -68,6 +68,7 @@ const Login = React.memo(
|
||||||
isSubmittingUsingOidc,
|
isSubmittingUsingOidc,
|
||||||
error,
|
error,
|
||||||
withOidc,
|
withOidc,
|
||||||
|
isOidcEnforced,
|
||||||
onAuthenticate,
|
onAuthenticate,
|
||||||
onAuthenticateUsingOidc,
|
onAuthenticateUsingOidc,
|
||||||
onMessageDismiss,
|
onMessageDismiss,
|
||||||
|
@ -107,8 +108,10 @@ const Login = React.memo(
|
||||||
}, [onAuthenticate, data]);
|
}, [onAuthenticate, data]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
emailOrUsernameField.current.focus();
|
if (!isOidcEnforced) {
|
||||||
}, []);
|
emailOrUsernameField.current.focus();
|
||||||
|
}
|
||||||
|
}, [isOidcEnforced]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (wasSubmitting && !isSubmitting && error) {
|
if (wasSubmitting && !isSubmitting && error) {
|
||||||
|
@ -159,51 +162,57 @@ const Login = React.memo(
|
||||||
onDismiss={onMessageDismiss}
|
onDismiss={onMessageDismiss}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Form size="large" onSubmit={handleSubmit}>
|
{!isOidcEnforced && (
|
||||||
<div className={styles.inputWrapper}>
|
<Form size="large" onSubmit={handleSubmit}>
|
||||||
<div className={styles.inputLabel}>{t('common.emailOrUsername')}</div>
|
<div className={styles.inputWrapper}>
|
||||||
<Input
|
<div className={styles.inputLabel}>{t('common.emailOrUsername')}</div>
|
||||||
fluid
|
<Input
|
||||||
ref={emailOrUsernameField}
|
fluid
|
||||||
name="emailOrUsername"
|
ref={emailOrUsernameField}
|
||||||
value={data.emailOrUsername}
|
name="emailOrUsername"
|
||||||
readOnly={isSubmitting}
|
value={data.emailOrUsername}
|
||||||
className={styles.input}
|
readOnly={isSubmitting}
|
||||||
onChange={handleFieldChange}
|
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>
|
||||||
<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}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
{withOidc && (
|
{withOidc && (
|
||||||
<Button
|
<Button
|
||||||
type="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}
|
loading={isSubmittingUsingOidc}
|
||||||
disabled={isSubmitting || isSubmittingUsingOidc}
|
disabled={isSubmitting || isSubmittingUsingOidc}
|
||||||
onClick={onAuthenticateUsingOidc}
|
onClick={onAuthenticateUsingOidc}
|
||||||
>
|
/>
|
||||||
{t('action.logInWithSSO')}
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -242,6 +251,7 @@ Login.propTypes = {
|
||||||
isSubmittingUsingOidc: PropTypes.bool.isRequired,
|
isSubmittingUsingOidc: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
error: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||||
withOidc: PropTypes.bool.isRequired,
|
withOidc: PropTypes.bool.isRequired,
|
||||||
|
isOidcEnforced: PropTypes.bool.isRequired,
|
||||||
onAuthenticate: PropTypes.func.isRequired,
|
onAuthenticate: PropTypes.func.isRequired,
|
||||||
onAuthenticateUsingOidc: PropTypes.func.isRequired,
|
onAuthenticateUsingOidc: PropTypes.func.isRequired,
|
||||||
onMessageDismiss: PropTypes.func.isRequired,
|
onMessageDismiss: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import Item from './Item';
|
||||||
const UsersModal = React.memo(
|
const UsersModal = React.memo(
|
||||||
({
|
({
|
||||||
items,
|
items,
|
||||||
|
canAdd,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
onUsernameUpdate,
|
onUsernameUpdate,
|
||||||
onUsernameUpdateMessageDismiss,
|
onUsernameUpdateMessageDismiss,
|
||||||
|
@ -130,11 +131,13 @@ const UsersModal = React.memo(
|
||||||
</Table.Body>
|
</Table.Body>
|
||||||
</Table>
|
</Table>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
<Modal.Actions>
|
{canAdd && (
|
||||||
<UserAddPopupContainer>
|
<Modal.Actions>
|
||||||
<Button positive content={t('action.addUser')} />
|
<UserAddPopupContainer>
|
||||||
</UserAddPopupContainer>
|
<Button positive content={t('action.addUser')} />
|
||||||
</Modal.Actions>
|
</UserAddPopupContainer>
|
||||||
|
</Modal.Actions>
|
||||||
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -142,6 +145,7 @@ const UsersModal = React.memo(
|
||||||
|
|
||||||
UsersModal.propTypes = {
|
UsersModal.propTypes = {
|
||||||
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
canAdd: PropTypes.bool.isRequired,
|
||||||
onUpdate: PropTypes.func.isRequired,
|
onUpdate: PropTypes.func.isRequired,
|
||||||
onUsernameUpdate: PropTypes.func.isRequired,
|
onUsernameUpdate: PropTypes.func.isRequired,
|
||||||
onUsernameUpdateMessageDismiss: PropTypes.func.isRequired,
|
onUsernameUpdateMessageDismiss: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -20,6 +20,7 @@ const mapStateToProps = (state) => {
|
||||||
isSubmittingUsingOidc,
|
isSubmittingUsingOidc,
|
||||||
error,
|
error,
|
||||||
withOidc: !!oidcConfig,
|
withOidc: !!oidcConfig,
|
||||||
|
isOidcEnforced: oidcConfig && oidcConfig.isEnforced,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,12 @@ import entryActions from '../entry-actions';
|
||||||
import UsersModal from '../components/UsersModal';
|
import UsersModal from '../components/UsersModal';
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
|
const oidcConfig = selectors.selectOidcConfig(state);
|
||||||
const users = selectors.selectUsersExceptCurrent(state);
|
const users = selectors.selectUsersExceptCurrent(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: users,
|
items: users,
|
||||||
|
canAdd: !oidcConfig || !oidcConfig.isEnforced,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ services:
|
||||||
# - OIDC_ROLES_ATTRIBUTE=groups
|
# - OIDC_ROLES_ATTRIBUTE=groups
|
||||||
# - OIDC_IGNORE_USERNAME=true
|
# - OIDC_IGNORE_USERNAME=true
|
||||||
# - OIDC_IGNORE_ROLES=true
|
# - OIDC_IGNORE_ROLES=true
|
||||||
|
# - OIDC_ENFORCED=true
|
||||||
|
|
||||||
# - SLACK_BOT_TOKEN=xoxb-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx
|
# - SLACK_BOT_TOKEN=xoxb-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
# - SLACK_CHANNEL_ID=xxxxxxxxxx
|
# - SLACK_CHANNEL_ID=xxxxxxxxxx
|
||||||
|
|
|
@ -33,6 +33,7 @@ SECRET_KEY=notsecretkey
|
||||||
# OIDC_ROLES_ATTRIBUTE=groups
|
# OIDC_ROLES_ATTRIBUTE=groups
|
||||||
# OIDC_IGNORE_USERNAME=true
|
# OIDC_IGNORE_USERNAME=true
|
||||||
# OIDC_IGNORE_ROLES=true
|
# OIDC_IGNORE_ROLES=true
|
||||||
|
# OIDC_ENFORCED=true
|
||||||
|
|
||||||
## Do not edit this
|
## Do not edit this
|
||||||
|
|
||||||
|
|
|
@ -46,8 +46,11 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async fn(inputs) {
|
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);
|
const user = await sails.helpers.users.getOneByEmailOrUsername(inputs.emailOrUsername);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|
|
@ -10,6 +10,7 @@ module.exports = {
|
||||||
response_mode: 'fragment',
|
response_mode: 'fragment',
|
||||||
}),
|
}),
|
||||||
endSessionUrl: oidcClient.issuer.end_session_endpoint ? oidcClient.endSessionUrl({}) : null,
|
endSessionUrl: oidcClient.issuer.end_session_endpoint ? oidcClient.endSessionUrl({}) : null,
|
||||||
|
isEnforced: sails.config.custom.oidcEnforced,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
const zxcvbn = require('zxcvbn');
|
const zxcvbn = require('zxcvbn');
|
||||||
|
|
||||||
const Errors = {
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
EMAIL_ALREADY_IN_USE: {
|
EMAIL_ALREADY_IN_USE: {
|
||||||
emailAlreadyInUse: 'Email already in use',
|
emailAlreadyInUse: 'Email already in use',
|
||||||
},
|
},
|
||||||
|
@ -56,6 +59,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
emailAlreadyInUse: {
|
emailAlreadyInUse: {
|
||||||
responseType: 'conflict',
|
responseType: 'conflict',
|
||||||
},
|
},
|
||||||
|
@ -65,6 +71,10 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async fn(inputs) {
|
async fn(inputs) {
|
||||||
|
if (sails.config.custom.oidcEnforced) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
const values = _.pick(inputs, [
|
const values = _.pick(inputs, [
|
||||||
'email',
|
'email',
|
||||||
'password',
|
'password',
|
||||||
|
|
|
@ -44,6 +44,7 @@ module.exports.custom = {
|
||||||
oidcRolesAttribute: process.env.OIDC_ROLES_ATTRIBUTE || 'groups',
|
oidcRolesAttribute: process.env.OIDC_ROLES_ATTRIBUTE || 'groups',
|
||||||
oidcIgnoreUsername: process.env.OIDC_IGNORE_USERNAME === 'true',
|
oidcIgnoreUsername: process.env.OIDC_IGNORE_USERNAME === 'true',
|
||||||
oidcIgnoreRoles: process.env.OIDC_IGNORE_ROLES === 'true',
|
oidcIgnoreRoles: process.env.OIDC_IGNORE_ROLES === 'true',
|
||||||
|
oidcEnforced: process.env.OIDC_ENFORCED === 'true',
|
||||||
|
|
||||||
// TODO: move client base url to environment variable?
|
// TODO: move client base url to environment variable?
|
||||||
oidcRedirectUri: `${
|
oidcRedirectUri: `${
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue