Wizard: Add group input to User step

This adds a group input to the User step. The input is implemented as a `LabelInput`, when Administrator checkbox is checked, `wheel` group gets added and it's removal automatically unchecks the Administrator checkbox again.
This commit is contained in:
regexowl 2025-03-24 12:46:48 +01:00 committed by Lucas Garfield
parent 47b5eb8392
commit 7a225e4146
3 changed files with 67 additions and 1 deletions

View file

@ -15,10 +15,16 @@ import {
setUserSshKeyByIndex,
setUserAdministratorByIndex,
removeUser,
selectUserGroupsByIndex,
addUserGroupByIndex,
removeUserGroupByIndex,
} from '../../../../../store/wizardSlice';
import LabelInput from '../../../LabelInput';
import { PasswordValidatedInput } from '../../../utilities/PasswordValidatedInput';
import { useUsersValidation } from '../../../utilities/useValidation';
import { ValidatedInputAndTextArea } from '../../../ValidatedInput';
import { isUserGroupValid } from '../../../validators';
const UserInfo = () => {
const dispatch = useAppDispatch();
const index = 0;
@ -30,6 +36,8 @@ const UserInfo = () => {
const userSshKey = useAppSelector(userSshKeySelector);
const userIsAdministratorSelector = selectUserAdministrator(index);
const userIsAdministrator = useAppSelector(userIsAdministratorSelector);
const userGroupsSelector = selectUserGroupsByIndex(index);
const userGroups = useAppSelector(userGroupsSelector);
const handleNameChange = (
_e: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
@ -111,13 +119,30 @@ const UserInfo = () => {
<FormGroup>
<Checkbox
label="Administrator"
isChecked={userIsAdministrator}
isChecked={userIsAdministrator || userGroups.includes('wheel')}
onChange={(_e, value) => handleCheckboxChange(_e, value)}
aria-label="Administrator"
id="user Administrator"
name="user Administrator"
/>
</FormGroup>
<FormGroup label="Groups">
<LabelInput
ariaLabel="Add user group"
placeholder="Add user group"
validator={isUserGroupValid}
list={userGroups}
item="Group"
addAction={(value) =>
addUserGroupByIndex({ index: index, group: value })
}
removeAction={(value) =>
removeUserGroupByIndex({ index: index, group: value })
}
stepValidation={stepValidation}
fieldName="groups"
/>
</FormGroup>
<Tooltip position="top-start" content={'Remove user'}>
<FormGroup>
<Button

View file

@ -67,6 +67,15 @@ export const isUserNameValid = (userName: string) => {
return isLengthValid && isNotNumericOnly && isPatternValid;
};
export const isUserGroupValid = (group: string) => {
// see `man groupadd` for the exact specification
return (
group.length <= 32 &&
/^[a-zA-Z0-9_][a-zA-Z0-9_-]*(\$)?$/.test(group) &&
/[a-zA-Z]+/.test(group) // contains at least one letter
);
};
export const isSshKeyValid = (sshKey: string) => {
// 1. Key types: ssh-rsa, ssh-dss, ssh-ed25519, or ecdsa-sha2-nistp(256|384|521).
// 2. Base64-encoded key material.

View file

@ -71,6 +71,11 @@ type UserAdministratorPayload = {
isAdministrator: boolean;
};
type UserGroupPayload = {
index: number;
group: string;
};
export type wizardState = {
env: {
serverUrl: string;
@ -411,6 +416,11 @@ export const selectUserAdministrator =
return state.wizard.users[userIndex]?.isAdministrator;
};
export const selectUserGroupsByIndex =
(userIndex: number) => (state: RootState) => {
return state.wizard.users[userIndex]?.groups;
};
export const selectKernel = (state: RootState) => {
return state.wizard.kernel;
};
@ -1022,6 +1032,26 @@ export const wizardSlice = createSlice({
user.groups = user.groups.filter((group) => group !== 'wheel');
}
},
addUserGroupByIndex: (state, action: PayloadAction<UserGroupPayload>) => {
if (
!state.users[action.payload.index].groups.some(
(group) => group === action.payload.group
)
) {
state.users[action.payload.index].groups.push(action.payload.group);
}
},
removeUserGroupByIndex: (
state,
action: PayloadAction<UserGroupPayload>
) => {
const groupIndex = state.users[action.payload.index].groups.findIndex(
(group) => group === action.payload.group
);
if (groupIndex !== -1) {
state.users[action.payload.index].groups.splice(groupIndex, 1);
}
},
},
});
@ -1112,5 +1142,7 @@ export const {
setUserPasswordByIndex,
setUserSshKeyByIndex,
setUserAdministratorByIndex,
addUserGroupByIndex,
removeUserGroupByIndex,
} = wizardSlice.actions;
export default wizardSlice.reducer;