Files
pad/internal/models/activity.go
T
xarmian d968b551b7 feat: add account disable/deactivation (#104)
* 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.
2026-04-13 21:56:40 -04:00

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"`
}