@@ -0,0 +1,153 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gardomatic.kleiax.de/internal/auth"
|
||||
"gardomatic.kleiax.de/internal/platform/validate"
|
||||
"gardomatic.kleiax.de/internal/storage"
|
||||
)
|
||||
|
||||
const adminInvitationTTL = 3 * 24 * time.Hour
|
||||
|
||||
func (app *application) listAdminUsersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
users, err := app.models.Users.GetAll()
|
||||
if err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
if err := app.writeJSON(w, http.StatusOK, envelope{"users": users}, nil); err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) inviteAdminUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var input struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
if err := app.readJSON(w, r, &input); err != nil {
|
||||
app.badRequestResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
input.Name = strings.TrimSpace(input.Name)
|
||||
input.Email = strings.ToLower(strings.TrimSpace(input.Email))
|
||||
candidate := storage.User{Name: input.Name, Email: input.Email, Activated: false}
|
||||
if passwordErr := candidate.Password.Set(rand.Text()); passwordErr != nil {
|
||||
app.serverErrorResponse(w, r, passwordErr)
|
||||
return
|
||||
}
|
||||
v := validate.New()
|
||||
if storage.ValidateUser(v, candidate); !v.Valid() {
|
||||
app.failedValidationResponse(w, r, v.Errors)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := app.models.Users.GetByEmail(input.Email)
|
||||
switch {
|
||||
case err == nil && user.Activated:
|
||||
app.failedValidationResponse(w, r, map[string]string{"email": "a user with this email address already exists"})
|
||||
return
|
||||
case err == nil:
|
||||
// Re-sending for an inactive account lets an administrator recover from
|
||||
// an earlier delivery failure without creating a duplicate account.
|
||||
case errors.Is(err, storage.ErrRecordNotFound):
|
||||
user, err = app.models.Users.Insert(candidate)
|
||||
if err != nil {
|
||||
if errors.Is(err, storage.ErrDuplicateEmail) {
|
||||
app.failedValidationResponse(w, r, map[string]string{"email": "a user with this email address already exists"})
|
||||
} else {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
default:
|
||||
app.serverErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = app.models.Tokens.DeleteAllForUser(auth.ScopeActivation, user.ID); err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
token, err := app.models.Tokens.New(user.ID, adminInvitationTTL, auth.ScopeActivation)
|
||||
if err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
activationURL := strings.TrimRight(app.config.WebBaseURL, "/") + "/activate?token=" + url.QueryEscape(token.Plaintext) + "&set-password=1"
|
||||
if err = app.mailer.Send(user.Email, "user_invitation.tmpl", map[string]any{"name": user.Name, "activationURL": activationURL}); err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
if err = app.writeJSON(w, http.StatusAccepted, envelope{"user": user}, nil); err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) updateAdminUserRoleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
userID, err := app.readIDParam(r)
|
||||
if err != nil {
|
||||
app.notFoundResponse(w, r)
|
||||
return
|
||||
}
|
||||
actor, _ := app.contextGetAuthenticatedUser(r)
|
||||
if actor.ID == userID {
|
||||
app.permissionDeniedResponse(w, r)
|
||||
return
|
||||
}
|
||||
var input struct {
|
||||
Role storage.ApplicationRole `json:"role"`
|
||||
}
|
||||
if err := app.readJSON(w, r, &input); err != nil {
|
||||
app.badRequestResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
role, err := app.models.Roles.Get(string(input.Role))
|
||||
if err != nil || role.Scope != storage.RoleScopeApplication {
|
||||
app.failedValidationResponse(w, r, map[string]string{"role": "must be an application role"})
|
||||
return
|
||||
}
|
||||
user, err := app.models.Users.UpdateRole(userID, input.Role)
|
||||
if err != nil {
|
||||
if errors.Is(err, storage.ErrRecordNotFound) {
|
||||
app.notFoundResponse(w, r)
|
||||
} else {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := app.writeJSON(w, http.StatusOK, envelope{"user": user}, nil); err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) deleteAdminUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
userID, err := app.readIDParam(r)
|
||||
if err != nil {
|
||||
app.notFoundResponse(w, r)
|
||||
return
|
||||
}
|
||||
actor, _ := app.contextGetAuthenticatedUser(r)
|
||||
if actor.ID == userID {
|
||||
app.permissionDeniedResponse(w, r)
|
||||
return
|
||||
}
|
||||
if err = app.models.Users.Delete(userID); err != nil {
|
||||
switch {
|
||||
case errors.Is(err, storage.ErrRecordNotFound):
|
||||
app.notFoundResponse(w, r)
|
||||
case errors.Is(err, storage.ErrConflict):
|
||||
app.conflictResponse(w, r)
|
||||
default:
|
||||
app.serverErrorResponse(w, r, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
Reference in New Issue
Block a user