Add user management

This commit is contained in:
Andreas Schneider 2020-10-31 11:46:02 +01:00
parent 1f91686d25
commit 762d43c330
5 changed files with 185 additions and 15 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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 }}

View File

@ -1,16 +1,34 @@
{{ define "page-content" }}
<table>
<thead>
<tr>
<td>User</td>
<td>Role</td>
</tr>
</thead>
<tbody>
{{ range $user := .Users }}<tr>
<td>{{ $user.Username }}</td>
<td>{{ $user.Role }}</td>
</tr>{{ end }}
</tbody>
</table>
{{ 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 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 }}

View File

@ -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