admin: Allow impersonation and deactivation

This commit is contained in:
Tom Wiesing 2023-01-17 12:25:23 +01:00
parent 2384ee0841
commit 7d0fb60d67
No known key found for this signature in database
6 changed files with 74 additions and 5 deletions

View file

@ -30,6 +30,7 @@ type UserPanel struct {
var ( var (
_ component.Routeable = (*UserPanel)(nil) _ component.Routeable = (*UserPanel)(nil)
_ component.Menuable = (*UserPanel)(nil)
) )
func (panel *UserPanel) Routes() component.Routes { func (panel *UserPanel) Routes() component.Routes {
@ -37,9 +38,18 @@ func (panel *UserPanel) Routes() component.Routes {
Prefix: "/user/", Prefix: "/user/",
CSRF: true, CSRF: true,
Decorator: panel.Dependencies.Auth.Require(nil), Decorator: panel.Dependencies.Auth.Require(nil),
}
}
MenuPriority: component.MenuUser, func (panel *UserPanel) Menu(r *http.Request) []component.MenuItem {
MenuTitle: "User", title := "Login"
user, err := panel.Dependencies.Auth.UserOf(r)
if user != nil && err == nil {
title = user.User.User
}
return []component.MenuItem{
{Title: title, Priority: component.MenuUser, Path: "/user/"},
} }
} }

View file

@ -1,10 +1,11 @@
{{ template "_base.html" . }} {{ template "_base.html" . }}
{{ define "title" }}User{{ end }} {{ define "title" }}{{ .User.User }}{{ end }}
{{ define "content" }} {{ define "content" }}
<div class="pure-u-1"> <div class="pure-u-1">
<p> <p>
<ul> <ul>
<li>Username: <code>{{ .User.User }}</code></li>
{{ if .User.IsAdmin }} {{ if .User.IsAdmin }}
<li>Role: <b>Administrator</b></li> <li>Role: <b>Administrator</b></li>
{{ else }} {{ else }}

View file

@ -109,6 +109,8 @@ func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http
router.Handler(http.MethodPost, route+"users/disabletotp", admin.usersDisableTOTPHandler(ctx)) router.Handler(http.MethodPost, route+"users/disabletotp", admin.usersDisableTOTPHandler(ctx))
router.Handler(http.MethodPost, route+"users/password", admin.usersPasswordHandler(ctx)) router.Handler(http.MethodPost, route+"users/password", admin.usersPasswordHandler(ctx))
router.Handler(http.MethodPost, route+"users/toggleadmin", admin.usersToggleAdmin(ctx)) router.Handler(http.MethodPost, route+"users/toggleadmin", admin.usersToggleAdmin(ctx))
router.Handler(http.MethodPost, route+"users/impersonate", admin.usersImpersonateHandler(ctx))
router.Handler(http.MethodPost, route+"users/unsetpassword", admin.usersUnsetPasswordHandler(ctx))
// add a handler for the component page // add a handler for the component page
router.Handler(http.MethodGet, route+"components", httpx.HTMLHandler[componentContext]{ router.Handler(http.MethodGet, route+"components", httpx.HTMLHandler[componentContext]{

View file

@ -25,6 +25,9 @@
<th> <th>
Enabled Enabled
</th> </th>
<th>
Has Password
</th>
<th> <th>
Admin Admin
</th> </th>
@ -44,8 +47,10 @@
{{ .User.User }} {{ .User.User }}
</td> </td>
<td> <td>
{{ .User.IsEnabled }} {{ .User.IsEnabled }}
</td>
<td>
{{ .User.HasPassword }}
</td> </td>
<td> <td>
{{ .User.IsAdmin }} {{ .User.IsAdmin }}
@ -66,6 +71,11 @@
<input type="submit" class="pure-button" value="Update Password"> <input type="submit" class="pure-button" value="Update Password">
{{ $csrf }} {{ $csrf }}
</form> </form>
<form action="/admin/users/unsetpassword" method="POST" class="pure-form-group">
<input type="hidden" name="user" value="{{ .User.User }}">
<input type="submit" class="pure-button" value="Unset Password">
{{ $csrf }}
</form>
<form action="/admin/users/disable" method="POST" class="pure-form-group"> <form action="/admin/users/disable" method="POST" class="pure-form-group">
<input type="hidden" name="user" value="{{ .User.User }}"> <input type="hidden" name="user" value="{{ .User.User }}">
<input type="submit" class="pure-button" {{ if (not .User.IsEnabled) }}disabled{{ end }} value="Disable"> <input type="submit" class="pure-button" {{ if (not .User.IsEnabled) }}disabled{{ end }} value="Disable">
@ -81,6 +91,11 @@
<input type="submit" class="pure-button pure-button-danger" value="Delete"> <input type="submit" class="pure-button pure-button-danger" value="Delete">
{{ $csrf }} {{ $csrf }}
</form> </form>
<form action="/admin/users/impersonate" method="POST" class="pure-form-group">
<input type="hidden" name="user" value="{{ .User.User }}">
<input type="submit" class="pure-button" {{ if (not .User.IsEnabled) }}disabled{{ end }} value="Impersonate">
{{ $csrf }}
</form>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -219,3 +219,40 @@ func (admin *Admin) usersPasswordHandler(ctx context.Context) http.Handler {
return user.SetPassword(r.Context(), []byte(password)) return user.SetPassword(r.Context(), []byte(password))
}) })
} }
func (admin *Admin) usersUnsetPasswordHandler(ctx context.Context) http.Handler {
return admin.useraction(ctx, "unset password", func(r *http.Request, user *auth.AuthUser) error {
user.PasswordHash = nil
return user.Save(r.Context())
})
}
func (admin *Admin) usersImpersonateHandler(ctx context.Context) http.Handler {
logger := zerolog.Ctx(ctx)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
logger.Err(err).Str("action", "impersonate").Msg("failed to parse form")
httpx.HTMLInterceptor.Fallback.ServeHTTP(w, r)
return
}
username := r.PostFormValue("user")
user, err := admin.Dependencies.Auth.User(r.Context(), username)
if err != nil {
logger.Err(err).Str("action", "impersonate").Msg("failed to get user")
httpx.HTMLInterceptor.Fallback.ServeHTTP(w, r)
return
}
// login the user into the session of the provided user
if err := admin.Dependencies.Auth.Login(w, r, user); err != nil {
logger.Err(err).Str("action", "impersonate").Msg("failed to login user")
httpx.HTMLInterceptor.Fallback.ServeHTTP(w, r)
return
}
// and go there
http.Redirect(w, r, "/user/", http.StatusSeeOther)
})
}

View file

@ -17,6 +17,10 @@ type User struct {
Admin *bool `gorm:"column:admin;not null"` Admin *bool `gorm:"column:admin;not null"`
} }
func (user *User) HasPassword() bool {
return len(user.PasswordHash) != 0
}
func (user *User) IsAdmin() bool { func (user *User) IsAdmin() bool {
return user.Admin != nil && *user.Admin return user.Admin != nil && *user.Admin
} }