✨ 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
|
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;
|
border: 0;
|
||||||
background-color: #c4e1ff;
|
background-color: #c4e1ff;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="submit"].delete {
|
input[type="submit"].delete {
|
||||||
|
@ -64,6 +65,32 @@
|
||||||
border: 1px solid gray;
|
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 {
|
body {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
margin-left: 200px
|
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-content" }}
|
{{ define "page-styles"}}
|
||||||
<table>
|
<style>
|
||||||
<thead>
|
table {
|
||||||
<tr>
|
border-spacing: .5em;
|
||||||
<td>User</td>
|
}
|
||||||
<td>Role</td>
|
</style>
|
||||||
</tr>
|
{{ end }}
|
||||||
</thead>
|
|
||||||
<tbody>
|
{{ define "page-content" }}
|
||||||
{{ range $user := .Users }}<tr>
|
<table>
|
||||||
<td>{{ $user.Username }}</td>
|
<thead>
|
||||||
<td>{{ $user.Role }}</td>
|
<tr>
|
||||||
</tr>{{ end }}
|
<td>User</td>
|
||||||
</tbody>
|
<td>Role</td>
|
||||||
</table>
|
<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 }}
|
{{ end }}
|
81
webadmin.go
81
webadmin.go
|
@ -54,6 +54,7 @@ type webAdminHandler struct {
|
||||||
tplShareAddUser *template.Template
|
tplShareAddUser *template.Template
|
||||||
tplShareAddLogin *template.Template
|
tplShareAddLogin *template.Template
|
||||||
tplCreateShare *template.Template
|
tplCreateShare *template.Template
|
||||||
|
tplCreateUser *template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
type sessionContext struct {
|
type sessionContext struct {
|
||||||
|
@ -107,6 +108,7 @@ func newWebAdminHandler(app *app) *webAdminHandler {
|
||||||
tplShareAddUser: loadTemplate("share-add-user"),
|
tplShareAddUser: loadTemplate("share-add-user"),
|
||||||
tplShareAddLogin: loadTemplate("share-add-login"),
|
tplShareAddLogin: loadTemplate("share-add-login"),
|
||||||
tplCreateShare: loadTemplate("create-share"),
|
tplCreateShare: loadTemplate("create-share"),
|
||||||
|
tplCreateUser: loadTemplate("create-user"),
|
||||||
}
|
}
|
||||||
|
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
|
@ -519,6 +521,85 @@ Are you sure you want to continue?`, share.UUID, share.Name)
|
||||||
}
|
}
|
||||||
sessionContext.Redirect("shares")
|
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
|
h.router = r
|
||||||
|
|
||||||
return h
|
return h
|
||||||
|
|
Loading…
Reference in New Issue