Files
authz_client/events.go
T
argoyleandClaude Opus 5 84e9309901
authz_client / test (push) Skipped
authz_client / vulnerabilities (push) Skipped
pre-commit / pre-commit (push) Skipped
authz_client / vulnerabilities (pull_request) Successful in 1m0s
authz_client / test (pull_request) Successful in 1m10s
pre-commit / pre-commit (pull_request) Successful in 3m47s
fix!: order privilege events by sequence number and merge snapshots by position
The four privilege keys arrive on separate transient queues, so a late
Privilege.Added or User.Added could resurrect a revoked grant. Services also
fetched /authz before binding their queues, losing revocations published in
between.

Process now orders events by authz-service's global sequenceNo per
(email, company): an event only overrides older facts, User.Removed stamps
every privilege, and events without a sequence number fail closed (additions
dropped, removals held until the next snapshot). Fetch checks the status,
retries 503 while authz-service's read view is behind, reads the
X-Authz-Sequence header and merges the snapshot as facts at that position,
ignoring snapshots older than one already merged. CompaniesByUser returns []
instead of nil.

BREAKING CHANGE: events without SequenceNo no longer grant anything; tests that
seed the handler through Process must set SequenceNo. Call Fetch() after
conn.Start (ADR-0015).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DVGsVQ8AMFR4NZoxyCoEqS
2026-09-16 20:27:28 +02:00

70 lines
1.8 KiB
Go

package client
// UserAdded is the event sent when a new user is added to a company.
// SequenceNo is authz-service's global event sequence number; it orders the events.
type UserAdded struct {
Email string `json:"email"`
CompanyID string `json:"companyId"`
SequenceNo int `json:"sequenceNo"`
}
// UserRemoved is the event sent when a user is removed from a company
type UserRemoved struct {
Email string `json:"email"`
CompanyID string `json:"companyId"`
SequenceNo int `json:"sequenceNo"`
}
// Privilege is an enumeration of all available privileges
type Privilege string
const (
PrivilegeAdmin = "ADMIN"
PrivilegeCompany = "COMPANY"
PrivilegeConsumer = "CONSUMER"
PrivilegeTime = "TIME"
PrivilegeInvoicing = "INVOICING"
PrivilegeAccounting = "ACCOUNTING"
PrivilegeSupplier = "SUPPLIER"
PrivilegeSalary = "SALARY"
)
var AllPrivilege = []Privilege{
PrivilegeAdmin,
PrivilegeCompany,
PrivilegeConsumer,
PrivilegeTime,
PrivilegeInvoicing,
PrivilegeAccounting,
PrivilegeSupplier,
PrivilegeSalary,
}
func (e Privilege) IsValid() bool {
switch e {
case PrivilegeAdmin, PrivilegeCompany, PrivilegeConsumer, PrivilegeTime, PrivilegeInvoicing, PrivilegeAccounting, PrivilegeSupplier, PrivilegeSalary:
return true
}
return false
}
func (e Privilege) String() string {
return string(e)
}
// PrivilegeAdded is the event sent when a new privilege is added
type PrivilegeAdded struct {
Email string `json:"email"`
CompanyID string `json:"companyId"`
Privilege Privilege `json:"privilege"`
SequenceNo int `json:"sequenceNo"`
}
// PrivilegeRemoved is the event sent when a privilege is removed
type PrivilegeRemoved struct {
Email string `json:"email"`
CompanyID string `json:"companyId"`
Privilege Privilege `json:"privilege"`
SequenceNo int `json:"sequenceNo"`
}