feat: password-change hook and the reset-2FA admin slot

The D-07 password-change hook is the existing AccessRevokedEvent, fired
inside the write's transaction by a reset, the user's own change and an
administrator's password; (*AccessRevokedEvent).PasswordChanged reports
those three reasons, and the login rehash fires nothing.

The Users screen declares the record action reset_two_factor (danger)
behind the new permission golem15.users.reset_two_factor, which no role
has by default. It is offered only when a two-factor plugin publishes a
classes.TwoFactorAdmin and the user has a second factor; Run calls the
service's Reset in the action's transaction.
This commit is contained in:
2026-10-26 10:03:59 +01:00
parent 8501f3475a
commit 7900f47ba6
14 changed files with 419 additions and 12 deletions

View File

@@ -29,7 +29,7 @@ var langKey = regexp.MustCompile(`golem15\.user::lang\.[a-z0-9_]+(\.[a-z0-9_]+)+
func TestAdminRegistration(t *testing.T) {
plugin := &Plugin{}
// The six permission codes, each on the plugin's tab.
// The seven permission codes, each on the plugin's tab.
var codes []string
for _, permission := range plugin.Permissions() {
codes = append(codes, permission.Code)
@@ -40,15 +40,15 @@ func TestAdminRegistration(t *testing.T) {
wantCodes := []string{
"golem15.users.access_users", "golem15.users.access_groups", "golem15.users.access_settings",
"golem15.users.impersonate_user", "golem15.users.manage_privileged_groups",
"golem15.users.manage_backend_links",
"golem15.users.manage_backend_links", "golem15.users.reset_two_factor",
}
if !reflect.DeepEqual(codes, wantCodes) {
t.Fatalf("permission codes = %v, want %v", codes, wantCodes)
}
// The privileged-groups and backend-links permissions have no default
// role: nobody holds them until they are granted on purpose.
// The privileged-groups, backend-links and reset-2FA permissions have no
// default role: nobody holds them until they are granted on purpose.
for _, permission := range plugin.Permissions() {
if privileged := permission.Code == permManagePrivileged || permission.Code == permManageBackendLinks; privileged != (len(permission.Roles) == 0) {
if privileged := permission.Code == permManagePrivileged || permission.Code == permManageBackendLinks || permission.Code == permResetTwoFactor; privileged != (len(permission.Roles) == 0) {
t.Fatalf("permission %s has roles %v", permission.Code, permission.Roles)
}
}