Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c1c7e2192 | ||
|
|
b8d262f745 | ||
|
|
a1fd694248 | ||
|
|
634d6ceab1 |
@@ -15,13 +15,13 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.1.15
|
||||
version: 0.1.17
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "1.15.4"
|
||||
appVersion: "1.15.6"
|
||||
|
||||
dependencies:
|
||||
- alias: postgresql
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
REACT_APP_VERSION=1.15.4
|
||||
REACT_APP_VERSION=1.15.6
|
||||
|
||||
@@ -68,6 +68,7 @@ const Login = React.memo(
|
||||
isSubmittingUsingOidc,
|
||||
error,
|
||||
withOidc,
|
||||
isOidcEnforced,
|
||||
onAuthenticate,
|
||||
onAuthenticateUsingOidc,
|
||||
onMessageDismiss,
|
||||
@@ -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) {
|
||||
@@ -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}
|
||||
{!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>
|
||||
<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>
|
||||
</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>
|
||||
@@ -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,
|
||||
|
||||
@@ -24,6 +24,7 @@ const AccountPane = React.memo(
|
||||
organization,
|
||||
language,
|
||||
isLocked,
|
||||
isUsernameLocked,
|
||||
isAvatarUpdating,
|
||||
usernameUpdateForm,
|
||||
emailUpdateForm,
|
||||
@@ -104,7 +105,7 @@ const AccountPane = React.memo(
|
||||
value={language || 'auto'}
|
||||
onChange={handleLanguageChange}
|
||||
/>
|
||||
{!isLocked && (
|
||||
{(!isLocked || !isUsernameLocked) && (
|
||||
<>
|
||||
<Divider horizontal section>
|
||||
<Header as="h4">
|
||||
@@ -113,56 +114,62 @@ const AccountPane = React.memo(
|
||||
})}
|
||||
</Header>
|
||||
</Divider>
|
||||
<div className={styles.action}>
|
||||
<UserUsernameEditPopup
|
||||
usePasswordConfirmation
|
||||
defaultData={usernameUpdateForm.data}
|
||||
username={username}
|
||||
isSubmitting={usernameUpdateForm.isSubmitting}
|
||||
error={usernameUpdateForm.error}
|
||||
onUpdate={onUsernameUpdate}
|
||||
onMessageDismiss={onUsernameUpdateMessageDismiss}
|
||||
>
|
||||
<Button className={styles.actionButton}>
|
||||
{t('action.editUsername', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Button>
|
||||
</UserUsernameEditPopup>
|
||||
</div>
|
||||
<div className={styles.action}>
|
||||
<UserEmailEditPopup
|
||||
usePasswordConfirmation
|
||||
defaultData={emailUpdateForm.data}
|
||||
email={email}
|
||||
isSubmitting={emailUpdateForm.isSubmitting}
|
||||
error={emailUpdateForm.error}
|
||||
onUpdate={onEmailUpdate}
|
||||
onMessageDismiss={onEmailUpdateMessageDismiss}
|
||||
>
|
||||
<Button className={styles.actionButton}>
|
||||
{t('action.editEmail', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Button>
|
||||
</UserEmailEditPopup>
|
||||
</div>
|
||||
<div className={styles.action}>
|
||||
<UserPasswordEditPopup
|
||||
usePasswordConfirmation
|
||||
defaultData={passwordUpdateForm.data}
|
||||
isSubmitting={passwordUpdateForm.isSubmitting}
|
||||
error={passwordUpdateForm.error}
|
||||
onUpdate={onPasswordUpdate}
|
||||
onMessageDismiss={onPasswordUpdateMessageDismiss}
|
||||
>
|
||||
<Button className={styles.actionButton}>
|
||||
{t('action.editPassword', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Button>
|
||||
</UserPasswordEditPopup>
|
||||
</div>
|
||||
{!isUsernameLocked && (
|
||||
<div className={styles.action}>
|
||||
<UserUsernameEditPopup
|
||||
defaultData={usernameUpdateForm.data}
|
||||
username={username}
|
||||
isSubmitting={usernameUpdateForm.isSubmitting}
|
||||
error={usernameUpdateForm.error}
|
||||
usePasswordConfirmation={!isLocked} // FIXME: hack
|
||||
onUpdate={onUsernameUpdate}
|
||||
onMessageDismiss={onUsernameUpdateMessageDismiss}
|
||||
>
|
||||
<Button className={styles.actionButton}>
|
||||
{t('action.editUsername', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Button>
|
||||
</UserUsernameEditPopup>
|
||||
</div>
|
||||
)}
|
||||
{!isLocked && (
|
||||
<>
|
||||
<div className={styles.action}>
|
||||
<UserEmailEditPopup
|
||||
usePasswordConfirmation
|
||||
defaultData={emailUpdateForm.data}
|
||||
email={email}
|
||||
isSubmitting={emailUpdateForm.isSubmitting}
|
||||
error={emailUpdateForm.error}
|
||||
onUpdate={onEmailUpdate}
|
||||
onMessageDismiss={onEmailUpdateMessageDismiss}
|
||||
>
|
||||
<Button className={styles.actionButton}>
|
||||
{t('action.editEmail', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Button>
|
||||
</UserEmailEditPopup>
|
||||
</div>
|
||||
<div className={styles.action}>
|
||||
<UserPasswordEditPopup
|
||||
usePasswordConfirmation
|
||||
defaultData={passwordUpdateForm.data}
|
||||
isSubmitting={passwordUpdateForm.isSubmitting}
|
||||
error={passwordUpdateForm.error}
|
||||
onUpdate={onPasswordUpdate}
|
||||
onMessageDismiss={onPasswordUpdateMessageDismiss}
|
||||
>
|
||||
<Button className={styles.actionButton}>
|
||||
{t('action.editPassword', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Button>
|
||||
</UserPasswordEditPopup>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Tab.Pane>
|
||||
@@ -179,6 +186,7 @@ AccountPane.propTypes = {
|
||||
organization: PropTypes.string,
|
||||
language: PropTypes.string,
|
||||
isLocked: PropTypes.bool.isRequired,
|
||||
isUsernameLocked: PropTypes.bool.isRequired,
|
||||
isAvatarUpdating: PropTypes.bool.isRequired,
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
usernameUpdateForm: PropTypes.object.isRequired,
|
||||
|
||||
@@ -18,6 +18,7 @@ const UserSettingsModal = React.memo(
|
||||
language,
|
||||
subscribeToOwnCards,
|
||||
isLocked,
|
||||
isUsernameLocked,
|
||||
isAvatarUpdating,
|
||||
usernameUpdateForm,
|
||||
emailUpdateForm,
|
||||
@@ -50,6 +51,7 @@ const UserSettingsModal = React.memo(
|
||||
organization={organization}
|
||||
language={language}
|
||||
isLocked={isLocked}
|
||||
isUsernameLocked={isUsernameLocked}
|
||||
isAvatarUpdating={isAvatarUpdating}
|
||||
usernameUpdateForm={usernameUpdateForm}
|
||||
emailUpdateForm={emailUpdateForm}
|
||||
@@ -108,6 +110,7 @@ UserSettingsModal.propTypes = {
|
||||
language: PropTypes.string,
|
||||
subscribeToOwnCards: PropTypes.bool.isRequired,
|
||||
isLocked: PropTypes.bool.isRequired,
|
||||
isUsernameLocked: PropTypes.bool.isRequired,
|
||||
isAvatarUpdating: PropTypes.bool.isRequired,
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
usernameUpdateForm: PropTypes.object.isRequired,
|
||||
|
||||
@@ -136,13 +136,15 @@ const ActionsStep = React.memo(
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
{!user.isUsernameLocked && (
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditUsernameClick}>
|
||||
{t('action.editUsername', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{!user.isLocked && (
|
||||
<>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditUsernameClick}>
|
||||
{t('action.editUsername', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditEmailClick}>
|
||||
{t('action.editEmail', {
|
||||
context: 'title',
|
||||
|
||||
@@ -19,6 +19,7 @@ const Item = React.memo(
|
||||
isAdmin,
|
||||
isLocked,
|
||||
isRoleLocked,
|
||||
isUsernameLocked,
|
||||
isDeletionLocked,
|
||||
emailUpdateForm,
|
||||
passwordUpdateForm,
|
||||
@@ -61,6 +62,7 @@ const Item = React.memo(
|
||||
phone,
|
||||
isAdmin,
|
||||
isLocked,
|
||||
isUsernameLocked,
|
||||
isDeletionLocked,
|
||||
emailUpdateForm,
|
||||
passwordUpdateForm,
|
||||
@@ -95,6 +97,7 @@ Item.propTypes = {
|
||||
isAdmin: PropTypes.bool.isRequired,
|
||||
isLocked: PropTypes.bool.isRequired,
|
||||
isRoleLocked: PropTypes.bool.isRequired,
|
||||
isUsernameLocked: PropTypes.bool.isRequired,
|
||||
isDeletionLocked: PropTypes.bool.isRequired,
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
emailUpdateForm: PropTypes.object.isRequired,
|
||||
|
||||
@@ -10,6 +10,7 @@ import Item from './Item';
|
||||
const UsersModal = React.memo(
|
||||
({
|
||||
items,
|
||||
canAdd,
|
||||
onUpdate,
|
||||
onUsernameUpdate,
|
||||
onUsernameUpdateMessageDismiss,
|
||||
@@ -112,6 +113,7 @@ const UsersModal = React.memo(
|
||||
isAdmin={item.isAdmin}
|
||||
isLocked={item.isLocked}
|
||||
isRoleLocked={item.isRoleLocked}
|
||||
isUsernameLocked={item.isUsernameLocked}
|
||||
isDeletionLocked={item.isDeletionLocked}
|
||||
emailUpdateForm={item.emailUpdateForm}
|
||||
passwordUpdateForm={item.passwordUpdateForm}
|
||||
@@ -129,11 +131,13 @@ 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>
|
||||
);
|
||||
},
|
||||
@@ -141,6 +145,7 @@ const UsersModal = React.memo(
|
||||
|
||||
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,
|
||||
|
||||
@@ -20,6 +20,7 @@ const mapStateToProps = (state) => {
|
||||
isSubmittingUsingOidc,
|
||||
error,
|
||||
withOidc: !!oidcConfig,
|
||||
isOidcEnforced: oidcConfig && oidcConfig.isEnforced,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ const mapStateToProps = (state) => {
|
||||
language,
|
||||
subscribeToOwnCards,
|
||||
isLocked,
|
||||
isUsernameLocked,
|
||||
isAvatarUpdating,
|
||||
emailUpdateForm,
|
||||
passwordUpdateForm,
|
||||
@@ -32,6 +33,7 @@ const mapStateToProps = (state) => {
|
||||
language,
|
||||
subscribeToOwnCards,
|
||||
isLocked,
|
||||
isUsernameLocked,
|
||||
isAvatarUpdating,
|
||||
emailUpdateForm,
|
||||
passwordUpdateForm,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ export default class extends BaseModel {
|
||||
isAdmin: attr(),
|
||||
isLocked: attr(),
|
||||
isRoleLocked: attr(),
|
||||
isUsernameLocked: attr(),
|
||||
isDeletionLocked: attr(),
|
||||
deletedAt: attr(),
|
||||
createdAt: attr({
|
||||
|
||||
@@ -44,8 +44,13 @@ services:
|
||||
# - OIDC_CLIENT_SECRET=
|
||||
# - OIDC_SCOPES=openid email profile
|
||||
# - OIDC_ADMIN_ROLES=admin
|
||||
# - OIDC_EMAIL_ATTRIBUTE=email
|
||||
# - OIDC_NAME_ATTRIBUTE=name
|
||||
# - OIDC_USERNAME_ATTRIBUTE=preferred_username
|
||||
# - OIDC_ROLES_ATTRIBUTE=groups
|
||||
# - OIDC_IGNORE_USERNAME=true
|
||||
# - OIDC_IGNORE_ROLES=true
|
||||
# - OIDC_ENFORCED=true
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "planka",
|
||||
"version": "1.15.4",
|
||||
"version": "1.15.6",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "planka",
|
||||
"version": "1.15.4",
|
||||
"version": "1.15.6",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "planka",
|
||||
"version": "1.15.4",
|
||||
"version": "1.15.6",
|
||||
"private": true,
|
||||
"homepage": "https://plankanban.github.io/planka",
|
||||
"repository": {
|
||||
|
||||
@@ -27,8 +27,13 @@ SECRET_KEY=notsecretkey
|
||||
# OIDC_CLIENT_SECRET=
|
||||
# OIDC_SCOPES=openid email profile
|
||||
# OIDC_ADMIN_ROLES=admin
|
||||
# OIDC_EMAIL_ATTRIBUTE=email
|
||||
# OIDC_NAME_ATTRIBUTE=name
|
||||
# OIDC_USERNAME_ATTRIBUTE=preferred_username
|
||||
# OIDC_ROLES_ATTRIBUTE=groups
|
||||
# OIDC_IGNORE_USERNAME=true
|
||||
# OIDC_IGNORE_ROLES=true
|
||||
# OIDC_ENFORCED=true
|
||||
|
||||
## Do not edit this
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -10,6 +10,7 @@ module.exports = {
|
||||
response_mode: 'fragment',
|
||||
}),
|
||||
endSessionUrl: oidcClient.issuer.end_session_endpoint ? oidcClient.endSessionUrl({}) : null,
|
||||
isEnforced: sails.config.custom.oidcEnforced,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
@@ -56,6 +59,9 @@ module.exports = {
|
||||
},
|
||||
|
||||
exits: {
|
||||
notEnoughRights: {
|
||||
responseType: 'forbidden',
|
||||
},
|
||||
emailAlreadyInUse: {
|
||||
responseType: 'conflict',
|
||||
},
|
||||
@@ -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',
|
||||
|
||||
@@ -53,11 +53,7 @@ module.exports = {
|
||||
async fn(inputs) {
|
||||
const { currentUser } = this.req;
|
||||
|
||||
if (inputs.id === currentUser.id) {
|
||||
if (!inputs.currentPassword) {
|
||||
throw Errors.INVALID_CURRENT_PASSWORD;
|
||||
}
|
||||
} else if (!currentUser.isAdmin) {
|
||||
if (inputs.id !== currentUser.id && !currentUser.isAdmin) {
|
||||
throw Errors.USER_NOT_FOUND; // Forbidden
|
||||
}
|
||||
|
||||
@@ -67,15 +63,18 @@ module.exports = {
|
||||
throw Errors.USER_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (user.email === sails.config.custom.defaultAdminEmail || user.isSso) {
|
||||
if (user.email === sails.config.custom.defaultAdminEmail) {
|
||||
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||
}
|
||||
|
||||
if (
|
||||
inputs.id === currentUser.id &&
|
||||
!bcrypt.compareSync(inputs.currentPassword, user.password)
|
||||
) {
|
||||
throw Errors.INVALID_CURRENT_PASSWORD;
|
||||
if (user.isSso) {
|
||||
if (!sails.config.custom.oidcIgnoreUsername) {
|
||||
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||
}
|
||||
} else if (inputs.id === currentUser.id) {
|
||||
if (!inputs.currentPassword || !bcrypt.compareSync(inputs.currentPassword, user.password)) {
|
||||
throw Errors.INVALID_CURRENT_PASSWORD;
|
||||
}
|
||||
}
|
||||
|
||||
const values = _.pick(inputs, ['username']);
|
||||
|
||||
@@ -38,7 +38,10 @@ module.exports = {
|
||||
throw 'invalidCodeOrNonce';
|
||||
}
|
||||
|
||||
if (!userInfo.email || !userInfo.name) {
|
||||
if (
|
||||
!userInfo[sails.config.custom.oidcEmailAttribute] ||
|
||||
!userInfo[sails.config.custom.oidcNameAttribute]
|
||||
) {
|
||||
throw 'missingValues';
|
||||
}
|
||||
|
||||
@@ -56,12 +59,14 @@ module.exports = {
|
||||
|
||||
const values = {
|
||||
isAdmin,
|
||||
email: userInfo.email,
|
||||
email: userInfo[sails.config.custom.oidcEmailAttribute],
|
||||
isSso: true,
|
||||
name: userInfo.name,
|
||||
username: userInfo.preferred_username,
|
||||
name: userInfo[sails.config.custom.oidcNameAttribute],
|
||||
subscribeToOwnCards: false,
|
||||
};
|
||||
if (!sails.config.custom.oidcIgnoreUsername) {
|
||||
values.username = userInfo[sails.config.custom.oidcUsernameAttribute];
|
||||
}
|
||||
|
||||
let user;
|
||||
// This whole block technically needs to be executed in a transaction
|
||||
@@ -95,7 +100,10 @@ module.exports = {
|
||||
});
|
||||
}
|
||||
|
||||
const updateFieldKeys = ['email', 'isSso', 'name', 'username'];
|
||||
const updateFieldKeys = ['email', 'isSso', 'name'];
|
||||
if (!sails.config.custom.oidcIgnoreUsername) {
|
||||
updateFieldKeys.push('username');
|
||||
}
|
||||
if (!sails.config.custom.oidcIgnoreRoles) {
|
||||
updateFieldKeys.push('isAdmin');
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ module.exports = {
|
||||
..._.omit(this, ['password', 'isSso', 'avatar', 'passwordChangedAt']),
|
||||
isLocked: this.isSso || isDefaultAdmin,
|
||||
isRoleLocked: (this.isSso && !sails.config.custom.oidcIgnoreRoles) || isDefaultAdmin,
|
||||
isUsernameLocked: (this.isSso && !sails.config.custom.oidcIgnoreUsername) || isDefaultAdmin,
|
||||
isDeletionLocked: isDefaultAdmin,
|
||||
avatarUrl:
|
||||
this.avatar &&
|
||||
|
||||
@@ -38,8 +38,13 @@ module.exports.custom = {
|
||||
oidcClientSecret: process.env.OIDC_CLIENT_SECRET,
|
||||
oidcScopes: process.env.OIDC_SCOPES || 'openid email profile',
|
||||
oidcAdminRoles: process.env.OIDC_ADMIN_ROLES ? process.env.OIDC_ADMIN_ROLES.split(',') : [],
|
||||
oidcEmailAttribute: process.env.OIDC_EMAIL_ATTRIBUTE || 'email',
|
||||
oidcNameAttribute: process.env.OIDC_NAME_ATTRIBUTE || 'name',
|
||||
oidcUsernameAttribute: process.env.OIDC_USERNAME_ATTRIBUTE || 'preferred_username',
|
||||
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: `${
|
||||
|
||||
Reference in New Issue
Block a user