Files
authz_client/CLAUDE.md
T
argoyleandClaude Opus 5 92f78d369a
authz_client / test (push) Skipped
authz_client / vulnerabilities (push) Skipped
pre-commit / pre-commit (push) Skipped
authz_client / vulnerabilities (pull_request) Failing after 55s
authz_client / test (pull_request) Successful in 1m4s
pre-commit / pre-commit (pull_request) Successful in 2m16s
fix: keep existing privileges when User.Added is processed late
Setup() registers one transient consumer per routing key, and each mints its
own queue drained by its own goroutine, so User.Added and Privilege.Added for the
same company can be processed in either order. Process(*UserAdded) replaced the
company entry with empty privileges, so a Privilege.Added handled first lost its
privilege until the next Fetch(), which only runs at service start.

Create the entry only when it is missing instead, matching authz-service's own
aggregate and read view, which both leave an existing user entry alone. This also
makes a User.Added redelivery harmless.

Seen as authz-service #826 acceptance-test failures: a new company's Admin grant
vanished, so company-service's CreateCompany callback waited out its 30s timeout
and the company page stayed empty.

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

2.0 KiB

authz_client

Shared Go library for authorization service client integration.

Shared Documentation

@../docs/claude/architecture.md @../docs/claude/go-services.md @../docs/claude/conventions.md

Library Information

Purpose

Provides a client for the authz-service, handling privilege management for users across companies. Used by all microservices that need to check user permissions.

Usage

import client "gitea.unbound.se/shiny/authz_client"

// Create handler with options
handler := client.New(client.WithBaseURL("http://authz-service"))

// Check user privileges
privileges := handler.Get(email, companyID)
if privileges.Invoicing {
    // User has invoicing privileges
}

Privileges

The CompanyPrivileges struct contains permission flags:

  • Admin - Administrative access
  • Company - Company management
  • Consumer - Consumer/customer access
  • Time - Time tracking
  • Invoicing - Invoice management
  • Accounting - Accounting access
  • Supplier - Supplier management
  • Salary - Salary/payroll access

Event Handling

Registers per-replica (transient) go-messaging-amqp consumers for privilege update events from the authz-service (Setup()), keeping the local privilege cache up-to-date. Each routing key gets its own queue, so events for one company arrive in any order — Process must never discard state it didn't itself record. User.Added therefore creates the company entry only when it is missing: a Privilege.Added processed ahead of it would otherwise be overwritten, and nothing re-reads the privilege until the next Fetch(). The converse doesn't hold yet — setPrivileges still creates an entry for a company it never saw a User.Added for, so a Privilege.* delivered after User.Removed resurrects an all-false membership. Don't combine Setup() with go-messaging-amqp's WithReconnect: a reconnect declares new per-replica queues, so revocations published during the outage are lost unless Fetch() runs again. Services exit on connection loss (CloseListener) and re-fetch on start.