✨ Add user management
This commit is contained in:
parent
1f91686d25
commit
762d43c330
23
cmd_user.go
23
cmd_user.go
|
@ -148,3 +148,26 @@ func (cmd CmdUserDelete) Run(app *app) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *app) deleteUser(username string) error {
|
||||
if err := app.userStore.RemoveUser(username); err != nil {
|
||||
return fmt.Errorf("cannot remove user: %w", err)
|
||||
}
|
||||
|
||||
sharesByUser, err := app.shareStore.FindSharesByUser(username)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get shares of user: %w", err)
|
||||
}
|
||||
allSuccessful := true
|
||||
for _, userShare := range sharesByUser {
|
||||
if err := app.shareStore.RemoveShare(userShare.UUID); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "User %q cannot be removed from Share %q: %v\n", username, userShare.UUID.String(), err)
|
||||
allSuccessful = false
|
||||
}
|
||||
}
|
||||
if !allSuccessful {
|
||||
return fmt.Errorf("could not remove user from all shares")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
border: 0;
|
||||
background-color: #c4e1ff;
|
||||
font-weight: bold;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
input[type="submit"].delete {
|
||||
|
@ -64,6 +65,32 @@
|
|||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
.dialog, .error {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
position: absolute;
|
||||
transform: translate(-50%, -50%);
|
||||
border: solid lightgrey;
|
||||
padding: 1em;
|
||||
border-radius: .5em;
|
||||
}
|
||||
|
||||
.dialog label {
|
||||
display: flex;
|
||||
margin-bottom: 1em;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
display: grid;
|
||||
grid-gap : 20px;
|
||||
grid-template-columns: 100px 1fr;
|
||||
}
|
||||
.form-controls {
|
||||
margin-top: 1em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
margin-left: 200px
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
{{ define "page-content" }}
|
||||
<form method="post" class="dialog">
|
||||
<div class="form-content">
|
||||
<label for="username">Username</label>
|
||||
<input name="username" id="username" placeholder="Username"/>
|
||||
|
||||
<label for="role">Role</label>
|
||||
<select name="role" id="role">
|
||||
<option value="user" selected>User</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
|
||||
<label for="password">Password</label>
|
||||
<input type="password" name="password" id="password" placeholder="Password"/>
|
||||
|
||||
</div>
|
||||
<div class="form-controls">
|
||||
<input type="submit" value="Create User"/>
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
|
@ -1,16 +1,34 @@
|
|||
{{ define "page-styles"}}
|
||||
<style>
|
||||
table {
|
||||
border-spacing: .5em;
|
||||
}
|
||||
</style>
|
||||
{{ end }}
|
||||
|
||||
{{ define "page-content" }}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>User</td>
|
||||
<td>Role</td>
|
||||
<td>Action</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range $user := .Users }}<tr>
|
||||
{{ range $user := .Users }}
|
||||
<tr id="user-{{ $user.Username }}">
|
||||
<td>{{ $user.Username }}</td>
|
||||
<td>{{ $user.Role }}</td>
|
||||
<td>
|
||||
<form style="display: inline-block;" action="delete-user" method="post">
|
||||
<input type="hidden" name="user" value="{{ $user.Username }}"/>
|
||||
<input type="submit" value="Delete" class="delete"/>
|
||||
</form>
|
||||
</td>
|
||||
</tr>{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="create-user" class="create-user">Create User</a>
|
||||
{{ end }}
|
81
webadmin.go
81
webadmin.go
|
@ -54,6 +54,7 @@ type webAdminHandler struct {
|
|||
tplShareAddUser *template.Template
|
||||
tplShareAddLogin *template.Template
|
||||
tplCreateShare *template.Template
|
||||
tplCreateUser *template.Template
|
||||
}
|
||||
|
||||
type sessionContext struct {
|
||||
|
@ -107,6 +108,7 @@ func newWebAdminHandler(app *app) *webAdminHandler {
|
|||
tplShareAddUser: loadTemplate("share-add-user"),
|
||||
tplShareAddLogin: loadTemplate("share-add-login"),
|
||||
tplCreateShare: loadTemplate("create-share"),
|
||||
tplCreateUser: loadTemplate("create-user"),
|
||||
}
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
@ -519,6 +521,85 @@ Are you sure you want to continue?`, share.UUID, share.Name)
|
|||
}
|
||||
sessionContext.Redirect("shares")
|
||||
})
|
||||
ar.Route("/create-user", func(r chi.Router) {
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
sessionContext := h.buildSessionContext(w, r)
|
||||
|
||||
if sessionContext.user.Role != GlobalRoleAdmin {
|
||||
sessionContext.Unauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
sessionContext.RenderPage(h.tplCreateUser, nil)
|
||||
})
|
||||
r.Post("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
sessionContext := h.buildSessionContext(w, r)
|
||||
|
||||
if sessionContext.user.Role != GlobalRoleAdmin {
|
||||
sessionContext.Unauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
returnURL := "users"
|
||||
|
||||
pwHash, err := bcrypt.GenerateFromPassword([]byte(r.FormValue("password")), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
sessionContext.RenderError(template.HTML("Internal error: "+err.Error()), returnURL)
|
||||
return
|
||||
}
|
||||
|
||||
user := User{
|
||||
Username: r.FormValue("username"),
|
||||
Password: string(pwHash),
|
||||
Role: GlobalRole(r.FormValue("role")),
|
||||
}
|
||||
|
||||
switch user.Role {
|
||||
case GlobalRoleUser:
|
||||
case GlobalRoleAdmin:
|
||||
default:
|
||||
sessionContext.RenderError(template.HTML("Invalid role."), "")
|
||||
return
|
||||
}
|
||||
|
||||
err = app.userStore.AddUser(user)
|
||||
if err != nil {
|
||||
sessionContext.RenderError(template.HTML("Cannot create user: "+err.Error()), "")
|
||||
return
|
||||
}
|
||||
|
||||
sessionContext.Redirect("users#user-" + user.Username)
|
||||
})
|
||||
})
|
||||
ar.Post("/delete-user", func(w http.ResponseWriter, r *http.Request) {
|
||||
sessionContext := h.buildSessionContext(w, r)
|
||||
|
||||
if sessionContext.user.Role != GlobalRoleAdmin {
|
||||
sessionContext.Unauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
user, err := app.userStore.GetUser(r.FormValue("user"))
|
||||
if err != nil {
|
||||
sessionContext.RenderError(template.HTML("Cannot delete user: "+err.Error()), "users")
|
||||
return
|
||||
}
|
||||
|
||||
message := fmt.Sprintf(`You are about to delete the user %s (<i>%s</i>).<br/>
|
||||
This will delete all share associations and logins.<br/><br/>
|
||||
Are you sure you want to continue?`, user.Username, user.Role)
|
||||
if confirmStatus := sessionContext.RequestConfirmation(template.HTML(message), "delete-user"); confirmStatus == confirmRequested {
|
||||
// We have already rendered. Nothing to do.
|
||||
return
|
||||
} else if confirmStatus == confirmAccepted {
|
||||
cmd := CmdUserDelete{Username: user.Username}
|
||||
if err := cmd.Run(app); err != nil {
|
||||
sessionContext.RenderError(template.HTML("Cannot delete user: "+err.Error()), "users")
|
||||
return
|
||||
}
|
||||
}
|
||||
sessionContext.Redirect("users")
|
||||
})
|
||||
h.router = r
|
||||
|
||||
return h
|
||||
|
|
Loading…
Reference in New Issue