admin: Allow impersonation and deactivation
This commit is contained in:
parent
2384ee0841
commit
7d0fb60d67
6 changed files with 74 additions and 5 deletions
|
|
@ -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/"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 }}
|
||||||
|
|
|
||||||
|
|
@ -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]{
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@
|
||||||
<th>
|
<th>
|
||||||
Enabled
|
Enabled
|
||||||
</th>
|
</th>
|
||||||
|
<th>
|
||||||
|
Has Password
|
||||||
|
</th>
|
||||||
<th>
|
<th>
|
||||||
Admin
|
Admin
|
||||||
</th>
|
</th>
|
||||||
|
|
@ -45,7 +48,9 @@
|
||||||
</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>
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue