mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-23 11:03:41 +00:00
d968b551b7
* feat: add account disable/deactivation for admin users Allow admins to soft-disable user accounts without deleting data. Disabled users get a 403 on all authenticated requests, their sessions are invalidated on disable, and they show as visually dimmed with a red "disabled" badge in the admin console. Includes migration for disabled_at column, auth middleware check, disable/enable endpoints with audit logging, and frontend toggle with confirmation dialog. * refactor: auto-discover migrations from embedded filesystem Replace hardcoded migration lists with fs.ReadDir on the embedded FS directories. New migrations are now picked up automatically by filename sort order — no need to manually register them in store.go. * fix: block disabled users at login and capture IDs before async calls Reject disabled accounts in the login handler before session creation, not just in RequireAuth middleware (which exempts auth routes). Also capture selectedId into a local const in all async admin panel functions to prevent stale updates if the selection changes during a request. * fix: enforce disabled check in OAuth and password reset flows, always invalidate sessions Block disabled users in all session-minting paths (OAuth login, password reset) not just password login. Also remove early return for already-disabled users in the disable endpoint so session invalidation always runs, handling retry after partial failure.
94 lines
3.0 KiB
Go
94 lines
3.0 KiB
Go
package models
|
|
|
|
import "time"
|
|
|
|
// Item-level actions (existing)
|
|
var ValidActions = []string{
|
|
"created", "updated", "archived", "restored", "moved", "read", "searched",
|
|
}
|
|
|
|
// Audit action constants for auth/admin events
|
|
const (
|
|
ActionLogin = "login"
|
|
ActionLoginFailed = "login_failed"
|
|
ActionLogout = "logout"
|
|
ActionBootstrap = "bootstrap"
|
|
ActionRegister = "register"
|
|
ActionPasswordChanged = "password_changed"
|
|
ActionPasswordReset = "password_reset"
|
|
ActionTokenCreated = "token_created"
|
|
ActionTokenRevoked = "token_revoked"
|
|
ActionTokenRotated = "token_rotated"
|
|
ActionTOTPEnabled = "totp_enabled"
|
|
ActionTOTPDisabled = "totp_disabled"
|
|
ActionMemberInvited = "member_invited"
|
|
ActionMemberRemoved = "member_removed"
|
|
ActionRoleChanged = "role_changed"
|
|
ActionSettingsChanged = "settings_changed"
|
|
ActionOAuthLogin = "oauth_login"
|
|
ActionOAuthLoginFailed = "oauth_login_failed"
|
|
ActionPlanChanged = "plan_changed"
|
|
ActionPasswordResetByAdmin = "password_reset_by_admin"
|
|
ActionUserDisabled = "user_disabled"
|
|
ActionUserEnabled = "user_enabled"
|
|
ActionAccountDeleted = "account_deleted"
|
|
)
|
|
|
|
type Activity struct {
|
|
ID string `json:"id"`
|
|
WorkspaceID string `json:"workspace_id,omitempty"`
|
|
DocumentID string `json:"document_id,omitempty"`
|
|
Action string `json:"action"`
|
|
Actor string `json:"actor"`
|
|
Source string `json:"source"`
|
|
Metadata string `json:"metadata,omitempty"` // JSON
|
|
UserID string `json:"user_id,omitempty"`
|
|
IPAddress string `json:"ip_address,omitempty"`
|
|
UserAgent string `json:"user_agent,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
|
|
// Enrichment fields — populated by handlers, not stored in DB
|
|
ItemTitle string `json:"item_title,omitempty"`
|
|
ItemSlug string `json:"item_slug,omitempty"`
|
|
CollectionSlug string `json:"collection_slug,omitempty"`
|
|
ActorName string `json:"actor_name,omitempty"`
|
|
}
|
|
|
|
type ActivityListParams struct {
|
|
Action string
|
|
Actor string
|
|
Source string
|
|
Limit int
|
|
Offset int
|
|
}
|
|
|
|
// AuditLogParams are query parameters for the audit log endpoint.
|
|
type AuditLogParams struct {
|
|
Action string
|
|
Actor string
|
|
WorkspaceID string
|
|
Days int
|
|
Limit int
|
|
Offset int
|
|
}
|
|
|
|
// TimelineEntry represents a single entry in the unified item timeline.
|
|
// It wraps one of: a comment, an activity, or a version.
|
|
type TimelineEntry struct {
|
|
ID string `json:"id"`
|
|
Kind string `json:"kind"` // "comment", "activity", "version"
|
|
CreatedAt time.Time `json:"created_at"`
|
|
Actor string `json:"actor"`
|
|
ActorName string `json:"actor_name,omitempty"`
|
|
Source string `json:"source"`
|
|
Comment *Comment `json:"comment,omitempty"`
|
|
Activity *Activity `json:"activity,omitempty"`
|
|
Version *Version `json:"version,omitempty"`
|
|
}
|
|
|
|
// TimelineResponse is the paginated response from the timeline endpoint.
|
|
type TimelineResponse struct {
|
|
Entries []TimelineEntry `json:"entries"`
|
|
HasMore bool `json:"has_more"`
|
|
}
|