Generated password modal flashes and disappears when creating a user from an empty group's detail page
Summary
On self-hosted with the embedded IdP, creating a user via Access Control → Groups → (group) → Add User → Create User shows the "User created successfully!" modal containing the generated password for a fraction of a second, then it vanishes before the password can be read or copied.
The user account is created successfully — but its password is displayed exactly once, and once lost there is no recovery path from the dashboard: the account is dead and must be deleted and re-created.
This is what makes the bug more than cosmetic. The only password-change surface in the dashboard is ChangePasswordModal, which is mounted solely in UserDropdown (self-service, for the logged-in user) and PUTs /users/{id}/password with a required old_password — so it cannot help here on two counts: an admin cannot invoke it against another user, and the credential it demands is precisely the one that was just lost. An admin-initiated reset appears to be deliberately absent rather than an oversight, so there is no "just reset it" fallback.
The same modal works correctly from Team → Users → Add User.
Trigger condition: the group must have had zero users before the creation. Creating a user from a group that already has members works fine.
| Entry point |
Group state |
Create User |
Invite User |
| Team → Users |
n/a |
✅ works |
✅ works |
| Group detail → Add User |
non-empty |
✅ works |
✅ works |
| Group detail → Add User |
empty |
❌ modal vanishes |
✅ works |
Environment
- Dashboard
v2.90.9 (also reproduces by inspection on current main — the two relevant files are unchanged between the tag and main)
netbirdio/netbird-server:0.76.1, self-hosted, single box, embedded IdP (embedded_idp_enabled: true, local auth enabled)
Steps to reproduce
- Self-hosted instance with the embedded IdP enabled.
- Access Control → Groups → create a new group, or pick one with no users assigned.
- Open the group → Users tab → Add User.
- Choose the Create User tab, fill in name + email, submit.
- The success modal with the generated password appears, then disappears on its own within ~a second.
Expected: the modal stays open until "Copy & Close" is clicked (it already sets onEscapeKeyDown/onInteractOutside/onPointerDownOutside to preventDefault, so it is clearly intended to be dismissible only via that button).
Workaround
Until this is fixed, on an empty group either use Invite User instead of Create User (unaffected, and the invitee sets their own password), or create the user from Team → Users and assign the group afterwards. Assigning any user to the group first also avoids it, since the bug requires the group to go from 0 to 1 users.
Cause
UserInviteModal owns the success state that holds the generated password:
https://github.com/netbirdio/dashboard/blob/v2.90.9/src/modules/users/UserInviteModal.tsx#L48-L51
const [open, setOpen] = useState(false);
const [successModal, setSuccessModal] = useState(false);
const [successData, setSuccessData] = useState<SuccessData | null>(null);
The password therefore only survives as long as that component instance stays mounted.
On the group detail page, GroupUsersSection mounts InviteUserButton (which wraps UserInviteModal) in two mutually exclusive branches:
DataTable renders exactly one of those branches depending on whether there is data:
https://github.com/netbirdio/dashboard/blob/v2.90.9/src/components/table/DataTable.tsx#L495-L499
Meanwhile createUser() posts with auto_groups set to the current group and revalidates the user list immediately, before handing the password to onUserCreated:
https://github.com/netbirdio/dashboard/blob/v2.90.9/src/modules/users/UserInviteModal.tsx#L231-L234
.then((user) => {
mutate("/users?service_user=false");
onUserCreated && onUserCreated(user);
});
(plus a second refetch 1s later in handleUserCreated, UserInviteModal.tsx#L77-L79).
So the sequence on an empty group is:
successData is set, success modal opens, password is on screen.
- The revalidated
/users response arrives; the group's user count goes 0 → 1.
DataTable swaps getStartedCard for the table body — unmounting the UserInviteModal instance that holds successData.
- The
rightSide branch mounts a different UserInviteModal instance with successModal: false, so nothing is shown.
This also explains why Invite User is unaffected from the same button: an invite does not add an entry to /users?service_user=false (pending invites live in /users/invites), so the group's user list does not change, no branch swap happens, and the modal survives.
Suggested fix
Any of:
- Render a single
UserInviteModal instance at GroupUsersSection level, outside the getStartedCard / rightSide branches, and drive it from a lifted open state — the two branches then only render trigger buttons.
- Or lift
successModal/successData out of UserInviteModal into a context/provider that outlives the table's branch switching.
- Defer the
mutate("/users?service_user=false") on L232 until the success modal is dismissed, rather than firing it before onUserCreated. (This alone narrows the race but does not close it, since handleUserCreated also schedules a refetch and other revalidation paths exist — so it is a mitigation, not a fix.)
Generated password modal flashes and disappears when creating a user from an empty group's detail page
Summary
On self-hosted with the embedded IdP, creating a user via Access Control → Groups → (group) → Add User → Create User shows the "User created successfully!" modal containing the generated password for a fraction of a second, then it vanishes before the password can be read or copied.
The user account is created successfully — but its password is displayed exactly once, and once lost there is no recovery path from the dashboard: the account is dead and must be deleted and re-created.
This is what makes the bug more than cosmetic. The only password-change surface in the dashboard is
ChangePasswordModal, which is mounted solely inUserDropdown(self-service, for the logged-in user) andPUTs/users/{id}/passwordwith a requiredold_password— so it cannot help here on two counts: an admin cannot invoke it against another user, and the credential it demands is precisely the one that was just lost. An admin-initiated reset appears to be deliberately absent rather than an oversight, so there is no "just reset it" fallback.The same modal works correctly from Team → Users → Add User.
Trigger condition: the group must have had zero users before the creation. Creating a user from a group that already has members works fine.
Environment
v2.90.9(also reproduces by inspection on currentmain— the two relevant files are unchanged between the tag andmain)netbirdio/netbird-server:0.76.1, self-hosted, single box, embedded IdP (embedded_idp_enabled: true, local auth enabled)Steps to reproduce
Expected: the modal stays open until "Copy & Close" is clicked (it already sets
onEscapeKeyDown/onInteractOutside/onPointerDownOutsidetopreventDefault, so it is clearly intended to be dismissible only via that button).Workaround
Until this is fixed, on an empty group either use Invite User instead of Create User (unaffected, and the invitee sets their own password), or create the user from Team → Users and assign the group afterwards. Assigning any user to the group first also avoids it, since the bug requires the group to go from 0 to 1 users.
Cause
UserInviteModalowns the success state that holds the generated password:https://github.com/netbirdio/dashboard/blob/v2.90.9/src/modules/users/UserInviteModal.tsx#L48-L51
The password therefore only survives as long as that component instance stays mounted.
On the group detail page,
GroupUsersSectionmountsInviteUserButton(which wrapsUserInviteModal) in two mutually exclusive branches:getStartedCard, rendered only when the group has no users —GroupUsersSection.tsx#L157rightSide, gated onusers?.length > 0—GroupUsersSection.tsx#L208-L220DataTablerenders exactly one of those branches depending on whether there is data:https://github.com/netbirdio/dashboard/blob/v2.90.9/src/components/table/DataTable.tsx#L495-L499
Meanwhile
createUser()posts withauto_groupsset to the current group and revalidates the user list immediately, before handing the password toonUserCreated:https://github.com/netbirdio/dashboard/blob/v2.90.9/src/modules/users/UserInviteModal.tsx#L231-L234
(plus a second refetch 1s later in
handleUserCreated,UserInviteModal.tsx#L77-L79).So the sequence on an empty group is:
successDatais set, success modal opens, password is on screen./usersresponse arrives; the group's user count goes 0 → 1.DataTableswapsgetStartedCardfor the table body — unmounting theUserInviteModalinstance that holdssuccessData.rightSidebranch mounts a differentUserInviteModalinstance withsuccessModal: false, so nothing is shown.This also explains why Invite User is unaffected from the same button: an invite does not add an entry to
/users?service_user=false(pending invites live in/users/invites), so the group's user list does not change, no branch swap happens, and the modal survives.Suggested fix
Any of:
UserInviteModalinstance atGroupUsersSectionlevel, outside thegetStartedCard/rightSidebranches, and drive it from a liftedopenstate — the two branches then only render trigger buttons.successModal/successDataout ofUserInviteModalinto a context/provider that outlives the table's branch switching.mutate("/users?service_user=false")on L232 until the success modal is dismissed, rather than firing it beforeonUserCreated. (This alone narrows the race but does not close it, sincehandleUserCreatedalso schedules a refetch and other revalidation paths exist — so it is a mitigation, not a fix.)