This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Important: The AI agent should always be in ask mode and should never modify the code without explicit permission or if asked by the developer. Claude should announce on session, "I am in ask mode, let's develop"
BulwarkAuthAdmin is a Go microservice for managing user accounts for bulwarkauth that handles authentication. It provides REST API endpoints for account lifecycle operations, social provider management, and role-based access control (RBAC). Built with Echo web framework and MongoDB.
Current Version: v0.2.0
Go Version: 1.24.0
Module: github.com/latebit-io/bulwarkauthadmin
This codebase follows a layered architecture with anemic domain models:
HTTP Layer (api/)
↓
Service Layer (internal/*/accounts.go - business logic)
↓
Repository Layer (internal/*/mongodb_*_repository.go - data access)
↓
MongoDB Database (SHARED with BulwarkAuth)
BulwarkAuthAdmin and BulwarkAuth share the same MongoDB database. This is a critical architectural decision:
- Accounts are stored in the shared database with roles and permissions fields
- JWT tokens issued by BulwarkAuth include roles from the account document in the shared database
- Role assignments made by BulwarkAuthAdmin are immediately reflected in accounts, affecting future JWT issuance
- Middleware validates JWT claims (which come from BulwarkAuth) and can enrich them by reading the account from the shared database if needed
This means:
- When a role is assigned to an account in BulwarkAuthAdmin, it's updated in the shared database
- When the user authenticates NEXT TIME (after role assignment), the new JWT will include the updated roles
- Current JWTs won't update - only NEW authentications get updated JWTs
- The middleware can look up the account in the shared database to get the authoritative role set
-
Anemic Models: Data structures are separate from business logic. Models are plain structs used for data transfer.
-
Service Layer Naming: Use business-oriented method names (
RegisterAccount,GrantPermissionsToRole) rather than CRUD terms (Create,Update). -
Repository Layer Naming: Use technical CRUD terms (
Create,Read,Update,Delete,AddPermissions). -
Return Values vs Pointers: Prefer returning values
(Model, error)over pointers(*Model, error)to avoid nil pointer issues. Only use pointers for truly optional fields within structs. -
Natural Keys for RBAC: Roles and Permissions use their name as the primary key (not ObjectIDs). Names are immutable - renaming means creating a new role/permission. This provides:
- Human-readable references in logs and database
- Simpler permission checks (no joins needed)
- Better API ergonomics
- Database portability
-
RBAC Design: Hybrid model supporting both role-based and direct permission grants:
User → Roles → Permissions(primary path)User → Permissions(direct grants for exceptions)
-
Simple and Concise: keep bound to the business logic, avoiding unnecessary complexity.
- Use meaningful variable names
- Follow Go best practices for code organization and readability
- Write clean, modular code with clear separation of concerns
- Use minimal comments
- avoid complex if statements, keep it concise as possible
- always use the least amount of code and maintain readability
- never use c style for loops use range and keep it idiomatic to go
The complete API is documented in OpenAPI 3.0 format:
- File:
openapi.yaml - View online: Use any OpenAPI viewer (Swagger UI, Redoc, etc.)
- Tools:
swag initcan generate code from this spec if needed
Key sections:
- All endpoints documented with request/response schemas
- Authentication requirements clearly marked
- Authorization rules for tenant admin vs system admin
- RFC 7807 Problem Details for error responses
- Comprehensive component schemas for data models
api/ # HTTP handlers and routes
accounts/ # Account management endpoints
apikey/ # API key management endpoints
health/ # Health check endpoint
problem/ # RFC 7807 problem details (standardized errors)
rbac/ # RBAC endpoints
tenants/ # Tenant management endpoints
internal/ # Business logic and data access
accounts/ # Account domain
accounts.go # Service interfaces and implementations
mongodb_account_repository.go # Repository implementation
error.go # Domain-specific errors
apikey/ # API key domain
apikey.go # ApiKey model, service interface and implementation
mongodb_apikey_repository.go # Repository implementation
rbac/ # RBAC domain
roles.go # Role/Permission services and repositories
permissions.go # Permission implementation
tenants/ # Tenant domain
tenants.go # Service interfaces and implementations
mongodb_tenants_repository.go # Repository implementation
admin_roles.go # TenantAdminService for creating default tenant admin roles
error.go # Domain-specific errors
email/ # Email template management
emails.go # Email service
mongodb_email_repository.go # Repository
shared/ # Common utilities (paging)
utils/ # Test utilities (memongo setup)
version/ # Version info
middleware/ # JWT and tenant middleware
cmd/bulwarkauthadmin/ # Application entry point
main.go # Service initialization and route registration
config.go # Environment configuration
.env # Configuration file
openapi.yaml # OpenAPI 3.0 specification for the entire API
.github/workflows/ # CI/CD workflows
docker-compose.yml # Docker Compose for local development
docker-compose.test.yml # Docker Compose for testing
tests/integration/ # Integration tests
setup.go # Test infrastructure and helpers
accounts/ # Account handler tests
rbac/ # RBAC handler tests
tenants/ # Tenant handler tests
# Run the service
go run cmd/bulwarkauthadmin/main.go
# Run with custom .env file
cp cmd/bulwarkauthadmin/.env cmd/bulwarkauthadmin/.env.local
# Edit .env.local, then:
go run cmd/bulwarkauthadmin/main.go# Unit tests (no external dependencies)
go test -v ./internal/...
go test -v -cover ./internal/...
# Integration tests (requires MongoDB + running service + BulwarkAuth + MailHog)
./run-integration-tests.sh
# Run specific integration test package
go test -v -tags=integration ./tests/integration/accounts
go test -v -tags=integration ./tests/integration/rbac
go test -v -tags=integration ./tests/integration/tenants
# Run specific test
go test -v -tags=integration -run TestAccountHandler_RegisterAccount ./tests/integration/accounts# Start MongoDB for development/testing
docker-compose -f docker-compose.test.yml up -d
# View MongoDB logs
docker-compose -f docker-compose.test.yml logs mongodb
# Stop MongoDB
docker-compose -f docker-compose.test.yml down
# Remove volumes (clean slate)
docker-compose -f docker-compose.test.yml down -v# Build binary
go build -o bulwarkauthadmin cmd/bulwarkauthadmin/main.go
# Run linting (if golangci-lint installed)
golangci-lint run
# Test GoReleaser config (requires Docker)
goreleaser release --snapshot --cleantype Tenant struct {
ID string // UUID
Name string // Unique, immutable
Description string
Domain string
Created time.Time
Modified time.Time
}Important Notes:
- Tenant name has unique index at database level
- System tenant created on startup with ID
00000000-0000-0000-0000-000000000000
type Account struct {
ID string // UUID
TenantID string // Reference to tenant
Email string // Unique per tenant
IsVerified bool
VerificationToken string // UUID
IsEnabled bool // Account active status
IsDeleted bool // Soft delete flag
SocialProviders []SocialProvider
Roles []string // Role names (natural keys)
Permissions []string // Permission names (natural keys)
Created time.Time
Modified time.Time
}Important Notes:
- Accounts use soft deletes (
IsDeletedflag) by default PurgeAccountperforms hard deletion- Email has unique index per tenant
- Roles and Permissions are stored as string arrays (names, not ObjectIDs)
type Role struct {
Name string `bson:"_id"` // Natural key (immutable)
Description string
Permissions []string // Permission names
Created time.Time
Modified time.Time
}
type Permission struct {
Name string `bson:"_id"` // Natural key (immutable)
Description string
Resource string // e.g., "users"
Action string // e.g., "delete"
}type ApiKey struct {
ID string // UUID
TenantID string // Reference to tenant
AccountID string // Reference to account
Name string // Unique per account per tenant
KeyHash string // Bcrypt-hashed key (plaintext only shown once at creation)
KeyPrefix string // "api_"
IsEnabled bool // Key active status
Expires *time.Time // Optional expiration date
Created time.Time
Modified time.Time
}Important Notes:
- API keys are scoped to both tenant and account (multi-tenant isolation)
- Key is hashed with bcrypt before storage; plaintext returned only at creation as
"api_:<uuid>" - Unique composite index on
{tenantId, accountId, name} - Supports suspend/enable lifecycle via
IsEnabledflag - Revoke performs hard deletion
All routes are under /api/v1/ prefix.
Requires: Tenant admin or system admin role in the tenant
POST /accounts- Register new accountGET /accounts- List all accounts (paginated)GET /accounts/:id- Get account detailsPUT /accounts/email- Change account emailPUT /accounts/disable- Disable accountPUT /accounts/enable- Enable accountPUT /accounts/deactivate- Soft delete accountPUT /accounts/unlink- Unlink social provider
Requires: Tenant admin or system admin role in the tenant
POST /accounts/:id/apikeys- Generate new API key (returns plaintext key once)GET /accounts/:id/apikeys- List all API keys for accountGET /accounts/:id/apikeys/:apiKeyID- Get API key detailsPUT /accounts/:id/apikeys/:apiKeyID/suspend- Suspend API keyPUT /accounts/:id/apikeys/:apiKeyID/enable- Enable API keyDELETE /accounts/:id/apikeys/:apiKeyID- Revoke (delete) API key
Requires: Tenant admin or system admin role in the tenant
POST /roles- Create roleGET /roles- List rolesGET /roles/:name- Get role detailsPUT /roles/:name- Update roleDELETE /roles/:name- Delete rolePOST /permissions- Create permissionGET /permissions- List permissionsDELETE /permissions/:name- Delete permission
Requires: Tenant admin or system admin role in the tenant
POST /roles- Assign role to accountDELETE /roles- Remove role from accountPOST /permissions- Assign permission to accountDELETE /permissions- Remove permission from account
Requires: System admin role
POST /tenants- Create tenantGET /tenants- List all tenantsGET /tenants/:id- Get tenant detailsPUT /tenants/:id- Update tenantDELETE /tenants/:id- Delete tenant
GET /health- Health check (no authentication required)
Key environment variables (see cmd/bulwarkauthadmin/.env):
PORT=8081 # Server port
CORS_ENABLED=false # Enable CORS
ALLOWED_WEB_ORIGINS=http://localhost:5173 # CORS origins (comma-separated)
DB_CONNECTION=mongodb://localhost:27017/?connect=direct
DB_NAME_SEED="" # Database suffix (e.g., "test")
BULWARK_AUTH_URL=http://localhost:8080 # BulwarkAuth service URL
ADMIN_ACCOUNT=admin@test.example.com # Initial admin email (REMOVE AFTER FIRST RUN)
ADMIN_ACCOUNT_PASSWORD=TestAdminPassword123! # Initial admin password (REMOVE AFTER FIRST RUN)- Use in-memory MongoDB (memongo)
- Test repository layer in isolation
- No external dependencies required
- Table-driven test patterns
- Located alongside code (
*_test.go) - Run with:
go test -v ./internal/...
- Test full HTTP API against real MongoDB, BulwarkAuth, and MailHog
- Build tag:
//go:build integration - Located in
tests/integration/ - Require services running via Docker Compose
- Use helper functions in
tests/integration/setup.go - Run with:
./run-integration-tests.sh(automated) or manual setup
Integration Test Helpers:
NewTestContext(t)- Authenticated test contextSetupTestTenant(t)- Test tenant + user setupSetupSystemAdminContext(t)- System admin contextMakeAuthenticatedRequest()- HTTP request with JWTGetVerificationTokenFromEmail()- Extract token from MailHogExtractTokenFromEmailBody()- Parse email body
Use RFC 7807 Problem Details for HTTP responses:
problem.InternalServerError(c, "failed to create account", err)
problem.Conflict(c, "account already exists", err)
problem.BadRequest(c, "invalid account id", err)type AccountRepository interface {
Create(ctx context.Context, account Account) (Account, error)
Read(ctx context.Context, id string) (Account, error)
ReadByEmail(ctx context.Context, email string) (Account, error)
ReadAll(ctx context.Context, opts shared.PagingOptions) ([]Account, error)
Update(ctx context.Context, account Account) error
Delete(ctx context.Context, id string) error
}type AccountManagementService interface {
RegisterAccount(ctx context.Context, email string) (Account, error)
GetAccountDetails(ctx context.Context, id string) (AccountDetails, error)
ChangeAccountEmail(ctx context.Context, id, newEmail string) error
DisableAccount(ctx context.Context, id string) error
EnableAccount(ctx context.Context, id string) error
DeactivateAccount(ctx context.Context, id string) error
UnlinkSocialProvider(ctx context.Context, accountID, providerName string) error
}- Use
primitive.ObjectIDfor MongoDB_idfields - Use context for all database operations
- Create unique indexes in repository constructors
- Handle
mongo.ErrNoDocumentsfor not found cases
.github/workflows/publish.yml handles:
- Semantic Versioning - Auto-calculates version from commit messages:
BREAKING CHANGE:→ major version bumpfeat:→ minor version bumpfix:→ patch version bump
- Tag Creation - Automatic git tags on main branch
- GoReleaser - Cross-platform builds (Linux, macOS, Windows on amd64/arm64)
- Docker Images - Published to GitHub Container Registry
- Integration Tests - Runs via
./run-integration-tests.sh
Use conventional commits:
feat: add new feature
fix: bug fix
BREAKING CHANGE: breaking API change
The system supports a hierarchical authorization model:
-
System Admin (
bulwark_admin)- Located in the system tenant (UUID nil:
00000000-0000-0000-0000-000000000000) - Can perform operations across all tenants
- Implicitly has tenant admin permissions for all tenants
- Routes:
/api/v1/admin/*
- Located in the system tenant (UUID nil:
-
Tenant Admin (
tenant_admin)- Located in their respective tenant
- Can manage accounts and RBAC within their tenant only
- Created automatically when a tenant is created
- Cannot access other tenants
-
Regular User
- Can perform read-only operations or operations assigned via direct permissions
- Cannot access tenant management endpoints
Tenant admins can:
- List, create, and manage accounts in their tenant
- Create and manage roles and permissions
- Assign roles (including
tenant_admin) to other accounts - View and manage RBAC configurations
Default Permissions Created per Tenant:
accounts:manage- For managing accountsrbac:manage- For managing roles and permissions
Default Role Created per Tenant:
tenant_admin- Has bothaccounts:manageandrbac:managepermissions
- JWT Validation -
JwtMiddlewarevalidates tokens and extracts claims - Tenant Extraction -
ExtractAndAuthorizeTenantvalidates tenant access - Tenant Admin Check -
RequireTenantAdminOrSystemAdminenforces admin role requirement - System Admin Check -
RequireSystemAdminenforces system admin role requirement
All tenant-scoped routes automatically require either tenant_admin or bulwark_admin role.
- Multi-tenant architecture - System tenant + user-managed tenants
- Account management - Registration, lifecycle, social provider linking
- RBAC system - Roles, permissions, role-based and direct permission grants
- Tenant management - Create, read, update, delete tenants (system admin only)
- Tenant Admin Authorization - Tenant admins can manage their tenant's accounts and RBAC
- Email templates - Per-tenant email template management
- Authentication - JWT validation with tenant context extraction
- CORS middleware - Configurable cross-origin support
- API key management - Generate, list, get, suspend, enable, and revoke API keys per account
- Comprehensive testing - Unit tests + integration tests for all domains
- Account tests: 3 unit tests + 7 integration tests
- API key tests: 6 unit tests + 8 integration tests
- RBAC tests: 2 unit tests + 10 integration tests
- Tenant tests: 8 unit tests + 12 integration tests
- Middleware tests: 6 unit tests for authorization helpers
- Tenant admin tests: 6 integration tests
- All 39 integration tests passing ✅
- CI/CD - Automated versioning, building, and Docker image publishing
- Docker Compose compatible - Works with both
docker-composeanddocker composecommands
- Main branch:
main - Current feature branch:
feat-api-key
- tenants - Multi-tenant configuration with unique name index
- accounts - User accounts (per tenant) with unique email index per tenant
- apiKeys - API keys (per account per tenant) with unique composite index on {tenantId, accountId, name}
- roles - RBAC roles (per tenant)
- permissions - RBAC permissions (per tenant)
- email_templates - Email templates (per tenant)
Database name: bulwarkauth{DB_NAME_SEED} (e.g., bulwarkauth, bulwarkauthtest)
-
Do not rename roles or permissions - Names are immutable identifiers. Create new ones instead.
-
Use value returns, not pointers - Return
(Model, error)rather than(*Model, error)for safety. -
Service methods use business language -
AssignRoleToUser, notCreateUserRole. -
Repository methods use CRUD language -
Create,Read,Update,Delete. -
Soft deletes by default - Use
IsDeletedflag. Only usePurgeAccountwhen explicitly requested. -
MongoDB natural keys for RBAC - Use name strings as
_idfor roles and permissions. -
Always read before write - When modifying existing files, read them first to understand the context.
-
Avoid over-engineering - Only implement what's requested. Don't add extra features, error handling for impossible scenarios, or premature abstractions.
-
Tenant Admin Role is Automatic - The
tenant_adminrole and its permissions are created automatically:- When the service starts (for the system tenant)
- When a new tenant is created via
TenantAdminService.CreateTenantAdminRole() - Idempotent design - safe to call multiple times
-
System Admins Implicitly Have Tenant Admin Access - Use
IsTenantAdminOrSystemAdmin()for permission checks on tenant-scoped routes to allow system admins to bypass tenant admin checks and access any tenant. -
Middleware Ordering Matters - Apply middleware in this order for tenant-scoped routes:
- JWT validation (
jwt.Jwt) - Tenant extraction and authorization (
tenantMiddleware.ExtractAndAuthorizeTenant) - Tenant admin role check (
tenantMiddleware.RequireTenantAdminOrSystemAdmin)
- JWT validation (
-
Test Helpers are Public - Public functions in
tests/integration/setup.go:GetVerificationTokenFromEmail()- Retrieve verification tokens from MailHogExtractTokenFromEmailBody()- Parse email body for tokens- Other helpers are used internally by tests
-
Docker Compose Compatibility - The
run-integration-tests.shscript automatically detects and uses either:docker-compose(standalone command)docker compose(Docker plugin - GitHub Actions runner)