Files
authz_client/CLAUDE.md
T
argoyleandClaude Opus 5 80050cbcf0
authz_client / test (push) Skipped
authz_client / vulnerabilities (push) Skipped
pre-commit / pre-commit (push) Skipped
authz_client / vulnerabilities (pull_request) Successful in 55s
authz_client / test (pull_request) Successful in 1m9s
pre-commit / pre-commit (pull_request) Successful in 3m44s
docs: deploy authz-service first when the /authz contract changes
The deploy-order rule was only recorded in the withdrawn ADR-0015.

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

3.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
allowed := handler.IsAllowed(email, companyID, func(p client.CompanyPrivileges) bool {
    return p.Invoicing
})

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 events from authz-service (Setup()). Each routing key gets its own queue, so events arrive in any order. Process orders them by the sequenceNo authz-service's event store stamps on every event: all four events come from authz-service's Company aggregate, so the global sequence number orders them within a company. Each (email, company) keeps the sequence number of the last fact about membership and about each privilege; an event only overrides older facts. User.Removed stamps every privilege, so a late grant can't come back. An event without sequenceNo fails closed: additions are dropped, and removals apply and block every later event for those facts until the next snapshot. An event with a negative or implausibly large sequenceNo is dropped. Tests that seed the handler must set SequenceNo.

Startup

Call Fetch() after conn.Start (consumers bound), never before: events published between the snapshot and the binding would otherwise be lost. authz-service only serves a snapshot whose read view has applied every stored event. It sends that position in the X-Authz-Sequence header and answers 503 while the read view lags (backlog, backfill, reset); Fetch retries 503 for about a minute. Fetch merges the snapshot as facts at that sequence number (newer event facts win, pairs missing from the snapshot are removed) and drops later events at or below it. A snapshot older than one already merged is ignored, so concurrent or repeated fetches are safe. A snapshot without the header merges at 0 and logs a warning (rollout window only). When the /authz contract changes, deploy authz-service before the services that use this library.

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.