154 lines
4.5 KiB
Go
154 lines
4.5 KiB
Go
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)
|
|
}
|