deleçao de contas web
This commit is contained in:
@@ -18,8 +18,8 @@ func (s *Store) InsertAudit(ctx context.Context, e domain.AuditEntry) error {
|
||||
return err
|
||||
}
|
||||
_, err = s.pool.Exec(ctx, `
|
||||
INSERT INTO audit_logs (source, actor_user_id, actor_account, target_user_id, event_type, success, details)
|
||||
VALUES ($1::text::audit_source, $2, $3, $4, $5, $6, $7::text::jsonb)`,
|
||||
INSERT INTO audit_logs (source, actor_user_id, actor_account, actor_account_name, target_user_id, event_type, success, details)
|
||||
VALUES ($1::text::audit_source, $2, $3, (SELECT username::text FROM web_accounts WHERE id = $3), $4, $5, $6, $7::text::jsonb)`,
|
||||
string(e.Source), e.ActorUserID, e.ActorAccount, e.TargetUserID, e.EventType, e.Success, string(raw))
|
||||
return err
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func (s *Store) ListAudit(ctx context.Context, f AuditFilter) ([]domain.AuditLog
|
||||
f.Limit = 50
|
||||
}
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
SELECT a.id, a.occurred_at, a.source, a.actor_user_id, au.display_name, a.actor_account, wa.username::text,
|
||||
SELECT a.id, a.occurred_at, a.source, a.actor_user_id, au.display_name, a.actor_account, COALESCE(wa.username::text, a.actor_account_name),
|
||||
a.target_user_id, tu.display_name, a.event_type, a.success, a.details::text
|
||||
FROM audit_logs a
|
||||
LEFT JOIN users au ON au.id = a.actor_user_id
|
||||
|
||||
@@ -82,6 +82,11 @@ func (s *Store) SetWebAccountPassword(ctx context.Context, id int64, passwordHas
|
||||
WHERE id = $1`, id, passwordHash))
|
||||
}
|
||||
|
||||
// DeleteWebAccount apaga a conta; as sessões vão junto e a auditoria mantém o nome (migration 00003).
|
||||
func (s *Store) DeleteWebAccount(ctx context.Context, id int64) error {
|
||||
return expectOne(s.pool.Exec(ctx, `DELETE FROM web_accounts WHERE id = $1`, id))
|
||||
}
|
||||
|
||||
func (s *Store) CountActiveOwners(ctx context.Context) (int, error) {
|
||||
var n int
|
||||
err := s.pool.QueryRow(ctx, `SELECT count(*) FROM web_accounts WHERE role = 'owner' AND disabled_at IS NULL`).Scan(&n)
|
||||
|
||||
@@ -246,6 +246,29 @@ func (s *Service) UpdateAccount(ctx context.Context, actor Actor, id int64, role
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAccount exclui a conta do painel, mantendo sempre um owner ativo (9.6).
|
||||
func (s *Service) DeleteAccount(ctx context.Context, actor Actor, id int64) error {
|
||||
account, err := s.store.GetWebAccount(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if account.Role == domain.RoleOwner && !account.IsDisabled() {
|
||||
owners, err := s.store.CountActiveOwners(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if owners <= 1 {
|
||||
return domain.ErrLastOwner
|
||||
}
|
||||
}
|
||||
if err := s.store.DeleteWebAccount(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
s.audit(ctx, actor, account.UserID, domain.EventWebAccountDeleted, true,
|
||||
map[string]any{"account_id": id, "username": account.Username, "role": string(account.Role)})
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetPassword troca a senha e encerra todas as sessões da conta.
|
||||
func (s *Service) ResetPassword(ctx context.Context, actor Actor, id int64, password string) error {
|
||||
if err := crypto.ValidatePassword(password); err != nil {
|
||||
|
||||
@@ -49,6 +49,7 @@ const (
|
||||
EventWebLogout = "web_logout"
|
||||
EventWebAccountCreated = "web_account_created"
|
||||
EventWebAccountChanged = "web_account_changed"
|
||||
EventWebAccountDeleted = "web_account_deleted"
|
||||
EventWebPasswordReset = "web_password_reset"
|
||||
EventRetentionApplied = "retention_applied"
|
||||
EventEncryptionKeyRotated = "encryption_key_rotated"
|
||||
|
||||
@@ -71,6 +71,7 @@ func NewHandler(db Pinger, svc *app.Service, cfg config.Config, logger *slog.Log
|
||||
mux.Handle("GET /api/v1/accounts", a.auth(domain.RoleOwner, a.listAccounts))
|
||||
mux.Handle("POST /api/v1/accounts", a.auth(domain.RoleOwner, a.createAccount))
|
||||
mux.Handle("PATCH /api/v1/accounts/{id}", a.auth(domain.RoleOwner, a.updateAccount))
|
||||
mux.Handle("DELETE /api/v1/accounts/{id}", a.auth(domain.RoleOwner, a.deleteAccount))
|
||||
mux.Handle("POST /api/v1/accounts/{id}/reset-password", a.auth(domain.RoleOwner, a.resetPassword))
|
||||
|
||||
mux.HandleFunc("/", notFound)
|
||||
|
||||
@@ -340,6 +340,22 @@ func (a *API) updateAccount(w http.ResponseWriter, r *http.Request) {
|
||||
httpx.WriteJSON(w, http.StatusOK, toAccount(account))
|
||||
}
|
||||
|
||||
func (a *API) deleteAccount(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := pathID(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if id == sessionFrom(r).AccountID {
|
||||
a.writeError(w, domain.InvalidState("você não pode excluir a própria conta"))
|
||||
return
|
||||
}
|
||||
if err := a.svc.DeleteAccount(r.Context(), actorOf(r), id); err != nil {
|
||||
a.writeError(w, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (a *API) resetPassword(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := pathID(w, r)
|
||||
if !ok {
|
||||
|
||||
Reference in New Issue
Block a user