auth

package
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package auth provides authentication and authorization for NornicDB.

This package implements JWT authentication with role-based access control, designed to meet regulatory compliance requirements:

  • GDPR Art.32: Technical and organizational measures to ensure security
  • HIPAA §164.312(a): Access Control - Unique User Identification
  • FISMA AC-2: Account Management
  • SOC 2 CC6.1: Logical access controls

Architecture:

  • JWT tokens (HS256 algorithm) for stateless authentication
  • Multiple credential sources: Bearer header, cookies, query parameters
  • Role-based access control (RBAC) with 4 roles: admin, editor, viewer, none
  • Account lockout after failed login attempts
  • Password hashing with bcrypt
  • Audit logging for compliance

Example Usage:

// Initialize authenticator
config := auth.DefaultAuthConfig()
config.JWTSecret = []byte("your-secret-key-min-32-chars")
config.MinPasswordLength = 12

authenticator, err := auth.NewAuthenticator(config)
if err != nil {
	log.Fatal(err)
}

// Set audit logging (required for HIPAA/GDPR)
authenticator.SetAuditLogger(func(event auth.AuditEvent) {
	log.Printf("[AUDIT] %s: %s (success=%v)",
		event.EventType, event.Username, event.Success)
})

// Create users
admin, _ := authenticator.CreateUser("admin", "SecurePass123!",
	[]auth.Role{auth.RoleAdmin})

viewer, _ := authenticator.CreateUser("alice", "AlicePass456!",
	[]auth.Role{auth.RoleViewer})

// Authenticate and get JWT token
tokenResp, user, err := authenticator.Authenticate(
	"admin", "SecurePass123!", "192.168.1.1", "Mozilla/5.0")
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Token: %s\n", tokenResp.AccessToken)
fmt.Printf("Type: %s\n", tokenResp.TokenType) // "Bearer"

// Validate token
claims, err := authenticator.ValidateToken(tokenResp.AccessToken)
if err != nil {
	log.Fatal(err)
}

// Check permissions
if user.HasPermission(auth.PermWrite) {
	fmt.Println("User can write")
}

OAuth 2.0 Compatibility:

The token endpoint follows RFC 6749 (OAuth 2.0) format:

POST /auth/token
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=alice&password=secret

Response:

{
  "access_token": "eyJhbGc...",
  "token_type": "Bearer",
  "expires_in": 3600  // omitted if never expires
}

Compliance Features:

GDPR Art.32 (Security):

  • Password hashing (bcrypt with configurable cost)
  • Token-based authentication (no session storage)
  • Account lockout (prevents brute force)
  • Audit logging (tracks all authentication events)

HIPAA §164.312(a) (Access Control):

  • Unique user identification (User.ID)
  • Role-based permissions
  • Account disable/enable
  • Failed login tracking

FISMA AC-2 (Account Management):

  • User creation with roles
  • Account lockout
  • Audit trail
  • Password policy enforcement

Security Best Practices:

  • bcrypt for password hashing (adjustable cost)
  • HMAC-SHA256 for JWT signatures
  • Constant-time string comparison (prevents timing attacks)
  • Account lockout after N failed attempts
  • No password in logs or responses

ELI12 (Explain Like I'm 12):

Think of this like your school's login system:

1. **Creating an account**: Like signing up for a school portal with username/password 2. **Logging in**: Like entering your credentials at the library computer 3. **JWT token**: Like a hall pass that proves you're allowed to be here 4. **Roles**: Like student vs teacher vs admin - different people can do different things 5. **Account lockout**: If you get your password wrong 5 times, you're locked out for 15 minutes 6. **Audit log**: Like the principal keeping a record of who entered the building and when

The system makes sure only the right people can access the right data!

Plan 04-06-05: auth_attempts_total wiring (D-11 + D-05e + GAP-6 / MET-15).

Design (CONTEXT D-05e + RESEARCH §Q11):

  • The pkg/observability.AuthMetrics bag is the single owner of the `nornicdb_auth_attempts_total{result, protocol}` counter family. pkg/auth never imports prometheus directly — it talks to the bag.
  • The shared core Authenticator.Authenticate is protocol-agnostic and does NOT increment the counter; the protocol label belongs at the protocol-specific adapter chokepoint (Bolt: handleHello; HTTP: middleware; gRPC: UnaryInterceptor).
  • classifyAuthResult is the closed-enum mapper from the Authenticate (..., error) return shape to the result enum.
  • RecordAttempt is the SOLE observation entry point — DRY chokepoint used by every protocol adapter. Nil-safe: when the bag is not injected (e.g., test fixtures, embedded usage without a Provider), RecordAttempt is a no-op.

**Audit boundary (T-04-06):** this file ships ZERO changes to pkg/audit/audit.go. The auth_attempts_total counter is a parallel observability signal, not a replacement for the audit log. Operators observe `auth_attempts_total{result="failure"}` rate; compliance auditors read the audit log.

**PII discipline (T-04-01 / ASVS V2):** no `user`, `user_id`, `ip`, or `email` label is exposed. Phase 3 D-03a forbidden-label panic at observability.NewCounterVec catches any attempt to drift.

Package auth: canonical enumeration of RBAC entitlements.

Entitlements are the individual permissions/rights that can be assigned to roles. They include global permissions (read, write, admin, etc.) and per-database entitlements (see database, access database, read, write). Use this list for UI, APIs, and documentation so admins can assign and audit entitlements per role.

Package auth provides OAuth 2.0 authentication support for NornicDB.

This package implements OAuth 2.0 authorization code flow with token validation and refresh capabilities. It integrates with external OAuth providers to authenticate users and issue NornicDB JWT tokens.

OAuth Flow:

  1. User initiates OAuth login → redirects to OAuth provider
  2. User authenticates with OAuth provider
  3. OAuth provider redirects back with authorization code
  4. Exchange code for access token
  5. Get user info from OAuth provider
  6. Create/find NornicDB user account
  7. Issue NornicDB JWT token
  8. Validate OAuth tokens on subsequent requests

Security Features:

  • CSRF protection via state parameter
  • OAuth token validation on each request
  • Automatic token refresh when expired
  • Short-lived NornicDB tokens (match OAuth token expiry)

Example Usage:

// Configure OAuth
os.Setenv("NORNICDB_AUTH_PROVIDER", "oauth")
os.Setenv("NORNICDB_OAUTH_ISSUER", "http://localhost:8888")
os.Setenv("NORNICDB_OAUTH_CLIENT_ID", "nornicdb-local-test")
os.Setenv("NORNICDB_OAUTH_CLIENT_SECRET", "local-test-secret-123")
os.Setenv("NORNICDB_OAUTH_CALLBACK_URL", "http://localhost:7474/auth/oauth/callback")

// Create OAuth manager
oauthMgr := oauth.NewManager(authenticator)

// Generate authorization URL
state, authURL, err := oauthMgr.GenerateAuthURL("http://localhost:7474")

// Handle callback
user, token, err := oauthMgr.HandleCallback(code, state)

Package auth provides request-scoped RBAC context helpers. When the server mounts authenticated endpoints (Bifrost, GraphQL), it can attach principal roles, DatabaseAccessMode, and ResolvedAccess resolver to the request context so handlers and resolvers can enforce per-database access.

Index

Constants

View Source
const (
	// DefaultAuthCacheEntries is the default max size for auth caches.
	DefaultAuthCacheEntries = 1024
	// DefaultAuthCacheTTL is the default TTL for cached auth results.
	DefaultAuthCacheTTL = 30 * time.Second
)

Variables

View Source
var (
	ErrUserNotFound       = errors.New("user not found")
	ErrUserExists         = errors.New("user already exists")
	ErrInvalidCredentials = errors.New("invalid credentials")
	ErrAccountLocked      = errors.New("account locked due to failed login attempts")
	ErrPasswordTooShort   = errors.New("password does not meet minimum length requirement")
	ErrInvalidToken       = errors.New("invalid or expired token")
	ErrInsufficientRole   = errors.New("insufficient role permissions")
	ErrSessionExpired     = errors.New("session expired")
	ErrNoCredentials      = errors.New("no credentials provided")
	ErrMissingSecret      = errors.New("JWT secret not configured")
)

Errors for authentication operations.

View Source
var (
	ErrInvalidRoleName         = errors.New("invalid role name")
	ErrRoleExists              = errors.New("role already exists")
	ErrRoleNotFound            = errors.New("role not found")
	ErrCannotDeleteBuiltinRole = errors.New("cannot delete or rename built-in role")
)

Role store errors.

RolePermissions maps roles to their allowed permissions. Follows Neo4j's RBAC model.

Functions

func ClassifyAuthResult added in v1.1.0

func ClassifyAuthResult(err error) string

ClassifyAuthResult maps the (User, error) return from Authenticate or ValidateToken into the closed result enum {success, failure, denied}.

Mapping per CONTEXT D-05e:

err == nil                                 → "success"
errors.Is(err, ErrInvalidCredentials)      → "failure" (bad password)
errors.Is(err, ErrInvalidToken)            → "failure" (bad token)
errors.Is(err, ErrSessionExpired)          → "failure" (expired)
errors.Is(err, ErrAccountLocked)           → "denied"  (lockout)
errors.Is(err, ErrInsufficientRole)        → "denied"  (RBAC reject)
errors.Is(err, ErrNoCredentials)           → "denied"  (no creds)
other (e.g. storage error)                 → "failure" (defensive)

The mapping is intentional:

  • "success" = identity established
  • "failure" = credentials presented but rejected
  • "denied" = request rejected before / despite credentials (gate)

Operators write per-result-rate alerts:

rate(auth_attempts_total{result="failure"}[5m]) > 1  ⇒ brute force
rate(auth_attempts_total{result="denied"}[5m]) > 0.1 ⇒ misconfig

func ExtractToken

func ExtractToken(authHeader, apiKeyHeader, cookie, queryToken, queryAPIKey string) string

func GlobalEntitlementIDs

func GlobalEntitlementIDs() []string

GlobalEntitlementIDs returns the IDs of all global entitlements (same as Permission values for read/write/create/delete/admin/schema/user_manage).

func HasCredentials

func HasCredentials(authHeader, apiKeyHeader, cookie, queryToken, queryAPIKey string) bool

HasCredentials checks if a request has any form of authentication credentials. Checks: Authorization header, X-API-Key header, cookie, query params.

func IsBuiltinRole

func IsBuiltinRole(name string) bool

IsBuiltin returns true if the role name is a built-in (admin, editor, viewer).

func PerDatabaseEntitlementIDs

func PerDatabaseEntitlementIDs() []string

PerDatabaseEntitlementIDs returns the IDs of per-database entitlements.

func PermissionsForRole

func PermissionsForRole(role string, store *RoleEntitlementsStore) []string

PermissionsForRole returns the effective entitlement IDs (permission strings) for a single role. If store is nil or has no override for role, built-in roles use RolePermissions; user-defined return nil.

func PermissionsForRoles

func PermissionsForRoles(roles []string, store *RoleEntitlementsStore) []string

PermissionsForRoles returns the union of effective entitlement IDs for the given roles. Used by server hasPermission and GetEffectivePermissions.

func RequestPrincipalRolesFromContext

func RequestPrincipalRolesFromContext(ctx context.Context) []string

RequestPrincipalRolesFromContext returns the principal's roles from the request context, or nil.

func RequestResolvedAccessResolverFromContext

func RequestResolvedAccessResolverFromContext(ctx context.Context) func(string) ResolvedAccess

RequestResolvedAccessResolverFromContext returns the ResolvedAccess resolver from context, or nil.

func RolePermissionsAsStrings

func RolePermissionsAsStrings() map[string][]string

RolePermissionsAsStrings returns role → permission IDs for fallback when role entitlements store has no override. Used by Bolt and other consumers that need the default role→permissions mapping. IDs match GlobalEntitlementIDs().

func SecureCompare

func SecureCompare(a, b string) bool

SecureCompare performs a constant-time string comparison. Prevents timing attacks on token validation.

func ValidRole

func ValidRole(r Role) bool

ValidRole checks if a role is valid.

func WithRequestDatabaseAccessMode

func WithRequestDatabaseAccessMode(ctx context.Context, mode DatabaseAccessMode) context.Context

WithRequestDatabaseAccessMode attaches the principal's per-database access mode to the context.

func WithRequestPrincipalRoles

func WithRequestPrincipalRoles(ctx context.Context, roles []string) context.Context

WithRequestPrincipalRoles attaches the principal's role names to the context.

func WithRequestResolvedAccessResolver

func WithRequestResolvedAccessResolver(ctx context.Context, fn func(string) ResolvedAccess) context.Context

WithRequestResolvedAccessResolver attaches a resolver (dbName -> ResolvedAccess) to the context.

Types

type AllowlistStore

type AllowlistStore struct {
	// contains filtered or unexported fields
}

AllowlistStore loads and saves role→databases allowlist in the system database.

func NewAllowlistStore

func NewAllowlistStore(systemStorage storage.Engine) *AllowlistStore

NewAllowlistStore creates a store that reads/writes allowlist to the given system storage.

func (*AllowlistStore) Allowlist

func (a *AllowlistStore) Allowlist() map[string][]string

Allowlist returns a copy of the current allowlist (role → databases). Nil or empty slice means "all databases".

func (*AllowlistStore) DeleteRoleDatabases

func (a *AllowlistStore) DeleteRoleDatabases(ctx context.Context, role string) error

DeleteRoleDatabases removes one role's allowlist entry (node + in-memory). Used when a user-defined role is deleted or renamed.

func (*AllowlistStore) HasAllowlistData

func (a *AllowlistStore) HasAllowlistData(ctx context.Context) (bool, error)

HasAllowlistData returns true if any allowlist entry exists in storage (for seed skip).

func (*AllowlistStore) Load

func (a *AllowlistStore) Load(ctx context.Context) error

Load reads the full allowlist from storage into memory. Call at startup and after PUT.

func (*AllowlistStore) RenameRoleInAllowlist

func (a *AllowlistStore) RenameRoleInAllowlist(ctx context.Context, oldName, newName string) error

RenameRoleInAllowlist copies allowlist entry from oldName to newName and deletes old. Call when a user-defined role is renamed so DB access is preserved.

func (*AllowlistStore) SaveRoleDatabases

func (a *AllowlistStore) SaveRoleDatabases(ctx context.Context, role string, databases []string) error

SaveRoleDatabases persists one role's database list and refreshes in-memory allowlist.

func (*AllowlistStore) SeedIfEmpty

func (a *AllowlistStore) SeedIfEmpty(ctx context.Context, databaseNames []string) error

SeedIfEmpty creates default allowlist entries for admin, editor, viewer if no allowlist data exists. Built-in roles are seeded with an empty list so they have access to all databases (including dynamically created ones). databaseNames is unused but kept for API compatibility; callers may pass the current db list from dbManager for logging or future use. Call after Load() so in-memory state is correct; or call on fresh DB before any Load().

type AuditEvent

type AuditEvent struct {
	Timestamp   time.Time `json:"timestamp"`
	EventType   string    `json:"event_type"`
	Username    string    `json:"username,omitempty"`
	UserID      string    `json:"user_id,omitempty"`
	IPAddress   string    `json:"ip_address,omitempty"`
	UserAgent   string    `json:"user_agent,omitempty"`
	Success     bool      `json:"success"`
	Details     string    `json:"details,omitempty"`
	RequestPath string    `json:"request_path,omitempty"`
}

AuditEvent represents an authentication-related event for compliance logging. Required for GDPR Art.30, HIPAA §164.312(b), FISMA AU controls.

type AuthConfig

type AuthConfig struct {
	// Password policy
	MinPasswordLength int
	BcryptCost        int

	// Token settings
	JWTSecret   []byte
	TokenExpiry time.Duration // 0 = never expire (default)

	// Lockout settings
	MaxFailedLogins int
	LockoutDuration time.Duration

	// Default admin username (never gets locked out)
	// This ensures the root admin account is always accessible
	DefaultAdminUsername string

	// Feature flags
	SecurityEnabled bool
}

AuthConfig holds authentication configuration.

func DefaultAuthConfig

func DefaultAuthConfig() AuthConfig

DefaultAuthConfig returns default authentication configuration. Returns sensible defaults.

type AuthMetricsRecorder added in v1.1.0

type AuthMetricsRecorder interface {
	// RecordAttempt observes a single auth attempt with the given result
	// and protocol. Nil-safe — implementations no-op when the bag is nil.
	RecordAttempt(result, protocol string)
}

AuthMetricsRecorder is the seam the protocol adapters use to publish the auth_attempts_total counter. The shared Authenticator owns one (set via SetAuthMetrics); protocol adapters can also own their own bag pointer for direct wiring at the chokepoint.

The interface lives in pkg/auth (not pkg/observability) because the caller domain — the protocol adapters — is the one that knows the protocol label value. pkg/observability only knows about the underlying CounterVec.

type Authenticator

type Authenticator struct {
	// contains filtered or unexported fields
}

Authenticator manages users and authentication.

func NewAuthenticator

func NewAuthenticator(config AuthConfig, storage storage.Engine) (*Authenticator, error)

NewAuthenticator creates a new Authenticator with the given configuration.

The authenticator manages user accounts, authentication, and authorization. All operations are thread-safe.

Configuration validation:

  • If SecurityEnabled=true, JWTSecret is required (min 32 bytes recommended)
  • MinPasswordLength defaults to 8 characters
  • BcryptCost defaults to bcrypt.DefaultCost (10)
  • MaxFailedLogins defaults to 5
  • LockoutDuration defaults to 15 minutes

Example:

// Production configuration
config := auth.AuthConfig{
	MinPasswordLength: 12,
	BcryptCost:        12,  // Higher = more secure but slower
	JWTSecret:         []byte("your-secret-key-at-least-32-chars-long"),
	TokenExpiry:       24 * time.Hour,  // Tokens expire after 24h
	MaxFailedLogins:   5,
	LockoutDuration:   30 * time.Minute,
	SecurityEnabled:   true,
}

auth, err := auth.NewAuthenticator(config)
if err != nil {
	log.Fatal(err)
}

// Development configuration (no security)
devConfig := auth.AuthConfig{
	SecurityEnabled: false,  // All requests allowed
}
auth = auth.NewAuthenticator(devConfig)

Returns error if SecurityEnabled=true but JWTSecret is empty.

Example 1 - Production HIPAA-Compliant Setup:

config := auth.DefaultAuthConfig()
config.SecurityEnabled = true
config.JWTSecret = []byte(os.Getenv("JWT_SECRET")) // Min 32 bytes
config.MinPasswordLength = 12
config.BcryptCost = 12 // High cost for security
config.MaxFailedAttempts = 5
config.LockoutDuration = 30 * time.Minute
config.TokenExpiry = 4 * time.Hour

authenticator, err := auth.NewAuthenticator(config)
if err != nil {
	log.Fatal("Failed to initialize auth:", err)
}

// Set up HIPAA-required audit logging
authenticator.SetAuditLogger(func(event auth.AuditEvent) {
	auditLogger.Log(audit.Event{
		Type:      audit.EventLogin,
		UserID:    event.UserID,
		Username:  event.Username,
		IPAddress: event.IPAddress,
		Success:   event.Success,
		Metadata:  map[string]string{"reason": event.Message},
	})
})

// Create admin user with strong password
admin, err := authenticator.CreateUser("admin",
	"Str0ng!P@ssw0rd#2024", []auth.Role{auth.RoleAdmin})

Example 2 - Multi-Tenant SaaS with Short-Lived Tokens:

config := auth.DefaultAuthConfig()
config.SecurityEnabled = true
config.JWTSecret = loadSecretFromVault()
config.TokenExpiry = 15 * time.Minute // Short-lived tokens
config.MaxFailedAttempts = 3          // Strict lockout

authenticator, err := auth.NewAuthenticator(config)
if err != nil {
	return nil, fmt.Errorf("auth init failed: %w", err)
}

// Create per-tenant users
for _, tenant := range tenants {
	user, err := authenticator.CreateUser(
		fmt.Sprintf("%s-%s", tenant.ID, username),
		generateStrongPassword(),
		[]auth.Role{auth.RoleEditor},
	)
	if err != nil {
		log.Printf("Failed to create user for tenant %s: %v", tenant.ID, err)
	}
}

Example 3 - Development Mode (No Security):

config := auth.DefaultAuthConfig()
config.SecurityEnabled = false // Bypass all auth checks

authenticator, err := auth.NewAuthenticator(config)
if err != nil {
	log.Fatal(err)
}

// In dev mode, any token is accepted
// WARNING: Never use in production!
claims, _ := authenticator.ValidateToken("any-token") // Always succeeds

Example 4 - API Integration with Rate Limiting:

config := auth.DefaultAuthConfig()
config.JWTSecret = []byte("secure-secret-32-chars-minimum!!")
config.MaxFailedAttempts = 5

authenticator, err := auth.NewAuthenticator(config)
if err != nil {
	return nil, err
}

// Track failed attempts for rate limiting
authenticator.SetAuditLogger(func(event auth.AuditEvent) {
	if event.EventType == "login_failed" {
		rateLimiter.RecordFailure(event.IPAddress)
		if rateLimiter.IsBlocked(event.IPAddress) {
			firewall.BlockIP(event.IPAddress, 1*time.Hour)
		}
	}
})

ELI12:

Think of NewAuthenticator like hiring a bouncer for a club:

  • The bouncer checks IDs (validates JWT tokens)
  • They remember troublemakers (account lockout after failed attempts)
  • They keep a guest list (user database)
  • They write down who comes in (audit logging)
  • They have different wristbands for VIP, regular, and just-looking (roles)

SecurityEnabled = true means "bouncer is on duty" SecurityEnabled = false means "anyone can walk in" (development only!)

Real-world Analogy:

JWT tokens are like temporary wristbands:
- When you log in, you get a wristband (token)
- Show the wristband to access stuff (no need to login again)
- Wristband expires after a few hours (token expiry)
- If you lose it, get a new one by logging in again

Why JWT Instead of Sessions?

Sessions: "I'll remember you" (server stores state)
JWT: "Here's a signed badge, prove yourself each time" (stateless)

JWT Benefits:
- Works across multiple servers (no shared session storage)
- Scales better (no memory for sessions)
- Mobile-friendly (just store the token)

Security Features:

  • Passwords hashed with bcrypt (can't be reversed)
  • Account lockout (prevents password guessing)
  • Token expiry (stolen tokens eventually die)
  • Audit logging (track who did what)

Compliance:

  • GDPR Art.32: Security measures ✓
  • HIPAA §164.312(a): Access control ✓
  • FISMA AC-2: Account management ✓
  • SOC 2 CC6.1: Logical access controls ✓

Performance:

  • bcrypt hashing: ~50-200ms (intentionally slow for security)
  • Token validation: ~1ms (just signature check)
  • Thread-safe for concurrent authentication

Thread Safety:

All methods are thread-safe for concurrent use.

Storage Engine (Required):

Users are always persisted to the provided storage engine.
For testing, use a memory storage engine via dependency injection.

// Production: System database storage
systemStorage := dbManager.GetStorage("system")
auth, err := auth.NewAuthenticator(config, systemStorage)

// Testing: Memory storage engine
memoryStorage := storage.NewMemoryEngine()
auth, err := auth.NewAuthenticator(config, memoryStorage)

func (*Authenticator) AuthMetrics added in v1.1.0

func (a *Authenticator) AuthMetrics() *observability.AuthMetrics

AuthMetrics returns the currently-injected bag (nil if unset). Used by protocol adapters to obtain the bag for direct wiring.

func (*Authenticator) Authenticate

func (a *Authenticator) Authenticate(username, password, ipAddress, userAgent string) (*TokenResponse, *User, error)

Authenticate verifies user credentials and returns a JWT token.

This implements the OAuth 2.0 password grant flow (RFC 6749 Section 4.3). On successful authentication:

  1. Password is verified with bcrypt
  2. Failed login counter is reset
  3. LastLogin timestamp is updated
  4. JWT token is generated
  5. Audit event is logged

Security features:

  • Account lockout after MaxFailedLogins attempts
  • Disabled accounts cannot authenticate
  • Timing attack resistant (doesn't reveal if user exists)
  • Failed attempts are logged for security monitoring

Parameters:

  • username: User's username
  • password: User's password (plain text)
  • ipAddress: Client IP for audit logging
  • userAgent: Client User-Agent for audit logging

Returns:

  • TokenResponse: OAuth 2.0 token response (access_token, token_type, expires_in)
  • User: User object (without password)
  • Error: ErrInvalidCredentials, ErrAccountLocked, or ErrUserNotFound

Example:

token, user, err := auth.Authenticate(
	"alice",
	"AlicePassword123!",
	"192.168.1.100",
	"Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
)
if err != nil {
	if errors.Is(err, auth.ErrAccountLocked) {
		http.Error(w, "Account locked. Try again in 15 minutes.", 423)
		return
	}
	http.Error(w, "Invalid credentials", 401)
	return
}

// Use token in Authorization header
fmt.Printf("Authorization: Bearer %s\n", token.AccessToken)

// Set cookie for browser sessions
http.SetCookie(w, &http.Cookie{
	Name:     "token",
	Value:    token.AccessToken,
	HttpOnly: true,
	Secure:   true,
	SameSite: http.SameSiteStrictMode,
})

Compliance:

  • HIPAA §164.312(a)(2)(i): Unique User Identification
  • HIPAA §164.312(d): Person or Entity Authentication
  • GDPR Art.32: Technical measures to ensure security
  • FISMA AC-7: Unsuccessful Login Attempts

func (*Authenticator) ChangePassword

func (a *Authenticator) ChangePassword(username, oldPassword, newPassword string) error

ChangePassword updates a user's password.

func (*Authenticator) CreateUser

func (a *Authenticator) CreateUser(username, password string, roles []Role) (*User, error)

CreateUser creates a new user account with the given credentials and roles.

The password is immediately hashed with bcrypt and never stored in plain text. If no roles are specified, defaults to RoleViewer.

Parameters:

  • username: Unique username (must not already exist)
  • password: Plain text password (will be hashed)
  • roles: User roles, or empty slice for default (viewer)

Returns:

  • User object (without password hash)
  • ErrUserExists if username already taken
  • ErrPasswordTooShort if password doesn't meet minimum length

Example:

// Create admin user
admin, err := auth.CreateUser("admin", "SecurePassword123!",
	[]auth.Role{auth.RoleAdmin})
if err != nil {
	return err
}

// Create regular user (defaults to viewer)
user, err := auth.CreateUser("alice", "AlicePass456!", nil)

// Create user with multiple roles
editor, err := auth.CreateUser("bob", "BobPass789!",
	[]auth.Role{auth.RoleEditor, auth.RoleViewer})

Audit event: Logs "user_create" with success/failure.

Compliance: HIPAA §164.308(a)(3)(ii)(A) - Unique user IDs

func (*Authenticator) DeleteUser

func (a *Authenticator) DeleteUser(username string) error

DeleteUser removes a user.

func (*Authenticator) DisableUser

func (a *Authenticator) DisableUser(username string) error

DisableUser disables a user account.

func (*Authenticator) EnableUser

func (a *Authenticator) EnableUser(username string) error

EnableUser re-enables a disabled user account.

func (*Authenticator) GenerateAPIToken

func (a *Authenticator) GenerateAPIToken(user *User, subject string, expiry time.Duration) (string, error)

GenerateAPIToken creates a stateless API token for the given user. These tokens are for MCP servers and other API integrations. The token is not stored; it's generated on-demand and validated by signature.

Parameters:

  • user: The authenticated user requesting the token
  • subject: A descriptive label for the token (e.g., "my-mcp-server")
  • expiry: Token lifetime (0 = never expires)

Returns the raw JWT token string.

func (*Authenticator) GenerateClusterToken

func (a *Authenticator) GenerateClusterToken(nodeID string, role Role) (string, error)

GenerateClusterToken creates a JWT token for cluster inter-node authentication. This token can be used by cluster nodes to authenticate with each other using the same shared JWT secret.

The token contains:

  • Sub: node identifier (e.g., "cluster-node-2")
  • Username: same as nodeID for identification
  • Roles: specified role (typically "admin" for cluster nodes)
  • Iat: issued at timestamp
  • Exp: expiration (if TokenExpiry is configured, otherwise never expires)

Usage:

  1. Generate the secret (must be same on all nodes): openssl rand -base64 48

  2. Configure all cluster nodes with the same secret: NORNICDB_JWT_SECRET=<your-generated-secret>

  3. Generate a cluster token (from admin API or code): token, _ := authenticator.GenerateClusterToken("node-2", auth.RoleAdmin)

  4. Use the token to connect from other nodes: driver = GraphDatabase.driver("bolt://node1:7687", basic_auth("", token)) # Empty principal triggers bearer/JWT auth

Example:

// On cluster setup, generate tokens for each node
token, err := authenticator.GenerateClusterToken("cluster-node-west", auth.RoleAdmin)
if err != nil {
	log.Fatal(err)
}

// Distribute token securely to node-west
// Node-west uses it to connect:
driver, _ := neo4j.NewDriverWithContext(
	"bolt://node-east:7687",
	neo4j.BasicAuth("", token, ""),  // Empty username = bearer auth
)

Security considerations:

  • Tokens are signed with HMAC-SHA256 using the shared JWT secret
  • All cluster nodes MUST use the same JWT secret
  • Store the secret securely (e.g., HashiCorp Vault, K8s Secrets)
  • Rotate secrets periodically by generating new tokens

func (*Authenticator) GenerateClusterTokenWithExpiry

func (a *Authenticator) GenerateClusterTokenWithExpiry(nodeID string, role Role, expiry time.Duration) (string, error)

GenerateClusterTokenWithExpiry creates a JWT token with a custom expiration time. Useful for short-lived tokens during initial cluster setup or testing.

Parameters:

  • nodeID: identifier for the cluster node (e.g., "node-2", "replica-west")
  • role: role to assign (typically RoleAdmin for cluster nodes)
  • expiry: token lifetime (e.g., 24*time.Hour, 0 for never expires)

Example:

// Generate a token that expires in 7 days
token, _ := auth.GenerateClusterTokenWithExpiry("node-2", auth.RoleAdmin, 7*24*time.Hour)

func (*Authenticator) GetUser

func (a *Authenticator) GetUser(username string) (*User, error)

GetUser returns user info by username without sensitive data.

func (*Authenticator) GetUserByID

func (a *Authenticator) GetUserByID(id string) (*User, error)

GetUserByID retrieves a user by their ID.

func (*Authenticator) IsSecurityEnabled

func (a *Authenticator) IsSecurityEnabled() bool

IsSecurityEnabled returns whether security is enabled.

func (*Authenticator) ListUsers

func (a *Authenticator) ListUsers() []*User

ListUsers returns all users without sensitive data.

func (*Authenticator) RecordAttempt added in v1.1.0

func (a *Authenticator) RecordAttempt(result, protocol string)

RecordAttempt implements AuthMetricsRecorder on the shared Authenticator. Protocol adapters that share a single Authenticator instance can call `auth.RecordAttempt(...)` directly with the appropriate protocol label.

Closed-enum discipline at the call site: callers must pass values from observability.AllowedAuthResults × observability.AllowedAuthProtocols. Any drift is caught by Phase 3 D-03a forbidden-label panic at registration (defense in depth — the call site gate is the actual check).

func (*Authenticator) SetAuditLogger

func (a *Authenticator) SetAuditLogger(fn func(AuditEvent))

SetAuditLogger sets the audit logging callback.

func (*Authenticator) SetAuthMetrics added in v1.1.0

func (a *Authenticator) SetAuthMetrics(bag *observability.AuthMetrics)

SetAuthMetrics injects the observability AuthMetrics bag for the auth_attempts_total counter (D-11 / D-05e / MET-15).

Idempotent. Calling with nil disables observation.

IMPORTANT: this is a parallel observability signal — the audit log (pkg/audit/audit.go) is the compliance source of truth and remains unchanged.

func (*Authenticator) UnlockUser

func (a *Authenticator) UnlockUser(username string) error

UnlockUser manually unlocks a locked user account.

func (*Authenticator) UpdateRoles

func (a *Authenticator) UpdateRoles(username string, newRoles []Role) error

UpdateRoles changes a user's roles.

func (*Authenticator) UpdateUser

func (a *Authenticator) UpdateUser(username string, email string, metadata map[string]string) error

UpdateUser updates user profile information (email, metadata). Users can update their own profile, admins can update any user.

func (*Authenticator) UserCount

func (a *Authenticator) UserCount() int

UserCount returns the number of registered users.

func (*Authenticator) ValidateToken

func (a *Authenticator) ValidateToken(token string) (*JWTClaims, error)

ValidateToken validates a JWT token and returns the claims.

The token is verified using HMAC-SHA256. If valid, returns the decoded claims including user ID, username, roles, and expiration.

Validation checks:

  1. Token format (header.payload.signature)
  2. Signature verification (HMAC-SHA256)
  3. Expiration (if Exp > 0)
  4. Not before (if configured)

If SecurityEnabled=false, returns dummy claims allowing all access.

The token can include "Bearer " prefix (will be stripped).

Parameters:

  • token: JWT token string (with or without "Bearer " prefix)

Returns:

  • JWTClaims: Decoded claims with user info and roles
  • Error: ErrInvalidToken, ErrSessionExpired, or ErrNoCredentials

Example:

// From Authorization header
authHeader := r.Header.Get("Authorization") // "Bearer eyJhbGc..."
claims, err := auth.ValidateToken(authHeader)
if err != nil {
	http.Error(w, "Unauthorized", 401)
	return
}

fmt.Printf("User: %s\n", claims.Username)
fmt.Printf("Roles: %v\n", claims.Roles)

// Check if user has admin role
hasAdmin := false
for _, role := range claims.Roles {
	if role == string(auth.RoleAdmin) {
		hasAdmin = true
		break
	}
}

Security:

  • Uses constant-time comparison to prevent timing attacks
  • Validates expiration to prevent replay attacks
  • Checks signature to prevent tampering

type BasicAuthCache

type BasicAuthCache struct {
	// contains filtered or unexported fields
}

BasicAuthCache caches successful Basic authentication results to avoid repeated bcrypt work on high-volume request paths.

func NewBasicAuthCache

func NewBasicAuthCache(maxEntries int, ttl time.Duration) *BasicAuthCache

NewBasicAuthCache creates a new cache for Basic auth results.

func (*BasicAuthCache) Get

func (c *BasicAuthCache) Get(username, password string) (*JWTClaims, bool)

Get retrieves cached claims using username/password (when header isn't available).

func (*BasicAuthCache) GetFromHeader

func (c *BasicAuthCache) GetFromHeader(authHeader string) (*JWTClaims, bool)

GetFromHeader retrieves cached claims using the full Basic auth header.

func (*BasicAuthCache) Set

func (c *BasicAuthCache) Set(username, password string, claims *JWTClaims)

Set caches claims using username/password (when header isn't available).

func (*BasicAuthCache) SetFromHeader

func (c *BasicAuthCache) SetFromHeader(authHeader string, claims *JWTClaims)

SetFromHeader caches claims using the full Basic auth header.

type DatabaseAccessMode

type DatabaseAccessMode interface {
	CanSeeDatabase(dbName string) bool
	CanAccessDatabase(dbName string) bool
}

DatabaseAccessMode answers whether a principal may see and access databases. Equivalent to Neo4j DatabaseAccessMode. Used before executing Cypher: CanAccessDatabase(dbName) must be true or the request is denied with 403.

var DenyAllDatabaseAccessMode DatabaseAccessMode = denyAllDBAccessMode{}

DenyAllDatabaseAccessMode is the singleton that denies all databases. Use when auth is enabled and no allowlist is configured (secure default).

var FullDatabaseAccessMode DatabaseAccessMode = fullDBAccessMode{}

FullDatabaseAccessMode is the singleton that allows all databases. Use when auth is disabled so dev/local usage works without RBAC.

func NewAllowlistDatabaseAccessMode

func NewAllowlistDatabaseAccessMode(allowlist map[string][]string, principalRoles []string) DatabaseAccessMode

NewAllowlistDatabaseAccessMode returns a DatabaseAccessMode for the given allowlist and principal roles. For each role: if the role has no allowlist (or empty), treat as "all databases"; else allow only listed DBs. CanAccessDatabase(dbName) is true iff at least one of the principal's roles allows the database.

func RequestDatabaseAccessModeFromContext

func RequestDatabaseAccessModeFromContext(ctx context.Context) DatabaseAccessMode

RequestDatabaseAccessModeFromContext returns the principal's DatabaseAccessMode from context, or nil.

type DbPrivilege

type DbPrivilege struct {
	Read  bool
	Write bool
}

DbPrivilege holds read/write for one (role, database).

type Entitlement

type Entitlement struct {
	ID          string              `json:"id"`          // Stable ID (e.g. "read", "database_write")
	Name        string              `json:"name"`        // Display name
	Description string              `json:"description"` // What this entitlement gates
	Category    EntitlementCategory `json:"category"`    // global or per_database
}

Entitlement describes a single assignable permission or right.

func AllEntitlements

func AllEntitlements() []Entitlement

AllEntitlements returns the full list of entitlements with descriptions. Use this for UI (assign entitlements to roles), APIs (GET /auth/entitlements), and documentation. Global entitlements map to Permission; per-database entitlements map to allowlist (see/access) and privileges matrix (read/write).

type EntitlementCategory

type EntitlementCategory string

EntitlementCategory indicates whether an entitlement is global or per-database.

const (
	EntitlementCategoryGlobal      EntitlementCategory = "global"
	EntitlementCategoryPerDatabase EntitlementCategory = "per_database"
)

type JWTClaims

type JWTClaims struct {
	Sub      string   `json:"sub"`                // Subject (user ID)
	Email    string   `json:"email,omitempty"`    // User email
	Username string   `json:"username,omitempty"` // Username
	Roles    []string `json:"roles"`              // User roles
	Iat      int64    `json:"iat"`                // Issued at (Unix timestamp)
	Exp      int64    `json:"exp,omitempty"`      // Expiration (Unix timestamp, 0 = never)
}

JWTClaims represents the claims in a JWT token. Standard JWT claims structure.

type OAuthConfig

type OAuthConfig struct {
	Provider     string // "oauth" to enable
	Issuer       string // OAuth provider base URL
	ClientID     string // OAuth client ID
	ClientSecret string // OAuth client secret
	CallbackURL  string // OAuth callback URL
}

OAuthConfig holds OAuth provider configuration.

func GetOAuthConfig

func GetOAuthConfig() *OAuthConfig

GetConfig returns the current OAuth configuration from environment variables.

func (*OAuthConfig) IsConfigured

func (c *OAuthConfig) IsConfigured() bool

IsConfigured checks if OAuth is properly configured.

type OAuthManager

type OAuthManager struct {
	// contains filtered or unexported fields
}

OAuthManager manages OAuth 2.0 authentication flow and token validation.

func NewOAuthManager

func NewOAuthManager(authenticator *Authenticator) *OAuthManager

NewOAuthManager creates a new OAuth manager.

func (*OAuthManager) ExchangeCode

func (m *OAuthManager) ExchangeCode(code string) (*OAuthTokenData, error)

ExchangeCode exchanges an authorization code for an access token.

func (*OAuthManager) GenerateAuthURL

func (m *OAuthManager) GenerateAuthURL(baseURL string) (string, string, error)

GenerateAuthURL generates an OAuth authorization URL and stores state for CSRF protection. Returns the state parameter and the authorization URL.

func (*OAuthManager) GetUserInfo

func (m *OAuthManager) GetUserInfo(accessToken string) (*OAuthUserInfo, error)

GetUserInfo retrieves user information from the OAuth provider.

func (*OAuthManager) HandleCallback

func (m *OAuthManager) HandleCallback(code, state string) (*User, string, time.Time, error)

HandleCallback handles the OAuth callback, exchanges code for token, gets user info, and creates/updates the NornicDB user account. Returns the user, NornicDB JWT token, and OAuth token expiry time.

func (*OAuthManager) RefreshOAuthToken

func (m *OAuthManager) RefreshOAuthToken(user *User, refreshToken string) error

RefreshOAuthToken attempts to refresh an expired OAuth access token using a refresh token.

func (*OAuthManager) ValidateOAuthToken

func (m *OAuthManager) ValidateOAuthToken(user *User) error

ValidateOAuthToken validates that an OAuth user's OAuth access token is still valid by calling the OAuth provider's userinfo endpoint.

func (*OAuthManager) ValidateState

func (m *OAuthManager) ValidateState(state string) error

ValidateState validates an OAuth state parameter and removes it (one-time use).

type OAuthTokenData

type OAuthTokenData struct {
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int64  `json:"expires_in"`
	Scope        string `json:"scope"`
	RefreshToken string `json:"refresh_token,omitempty"`
}

OAuthTokenData represents an OAuth access token response.

type OAuthUserInfo

type OAuthUserInfo struct {
	Sub               string   `json:"sub"`
	Email             string   `json:"email"`
	PreferredUsername string   `json:"preferred_username"`
	Roles             []string `json:"roles"`
}

OAuthUserInfo represents user information from OAuth provider.

type Permission

type Permission string

Permission represents an action that can be performed.

const (
	PermRead       Permission = "read"
	PermWrite      Permission = "write"
	PermCreate     Permission = "create"
	PermDelete     Permission = "delete"
	PermAdmin      Permission = "admin"
	PermSchema     Permission = "schema"
	PermUserManage Permission = "user_manage"
)

Permissions map to Neo4j-compatible actions.

type PrivilegeDatabaseRef

type PrivilegeDatabaseRef struct {
	Name               string // Normalized database name, e.g. "nornic", "system"
	OwningDatabaseName string // For composite/sharded; empty or Name for normal DBs
}

PrivilegeDatabaseRef identifies the database for privilege checks. Equivalent to Neo4j PrivilegeDatabaseReference (name, owningDatabaseName). For normal (non-composite) databases, Name and OwningDatabaseName are the same.

type PrivilegesStore

type PrivilegesStore struct {
	// contains filtered or unexported fields
}

PrivilegesStore persists and resolves per (role, database) read/write.

func NewPrivilegesStore

func NewPrivilegesStore(systemStorage storage.Engine) *PrivilegesStore

NewPrivilegesStore creates a store that reads/writes _DbPrivilege nodes.

func (*PrivilegesStore) Load

func (p *PrivilegesStore) Load(ctx context.Context) error

Load reads all _DbPrivilege nodes from storage into memory.

func (*PrivilegesStore) Matrix

func (p *PrivilegesStore) Matrix() []struct {
	Role     string `json:"role"`
	Database string `json:"database"`
	Read     bool   `json:"read"`
	Write    bool   `json:"write"`
}

Matrix returns a copy of the full matrix for GET API.

func (*PrivilegesStore) PutMatrix

func (p *PrivilegesStore) PutMatrix(ctx context.Context, entries []struct {
	Role     string `json:"role"`
	Database string `json:"database"`
	Read     bool   `json:"read"`
	Write    bool   `json:"write"`
}) error

PutMatrix replaces the stored matrix with the given list (for PUT /auth/access/privileges). Deletes all existing _DbPrivilege nodes then creates nodes for each entry.

func (*PrivilegesStore) Resolve

func (p *PrivilegesStore) Resolve(principalRoles []string, dbName string) ResolvedAccess

Resolve returns ResolvedAccess for (principalRoles, dbName). If any role has an entry for this db, aggregate read/write. If none have an entry, fall back to global RolePermissions.

func (*PrivilegesStore) SavePrivilege

func (p *PrivilegesStore) SavePrivilege(ctx context.Context, role, dbName string, read, write bool) error

SavePrivilege persists one (role, database, read, write) and refreshes in-memory.

type ResolvedAccess

type ResolvedAccess struct {
	Read  bool // Allow MATCH, read properties, etc.
	Write bool // Allow CREATE, DELETE, SET, MERGE, etc.
}

ResolvedAccess is the resolved read/write capability for a (principal, database). Equivalent to Neo4j AccessMode for a single DB. Used for execution-time mutation checks: Write must be true to allow CREATE, DELETE, SET, MERGE, etc.

type Role

type Role string

Role represents a user role with associated permissions.

const (
	RoleAdmin  Role = "admin"  // Full access including user management
	RoleEditor Role = "editor" // Read/write data
	RoleViewer Role = "viewer" // Read only (default)
	RoleNone   Role = "none"   // No access
)

Predefined roles following Neo4j conventions.

func ConvertOAuthRoles

func ConvertOAuthRoles(roles []string) []Role

ConvertOAuthRoles converts OAuth role strings to auth.Role enum.

func RoleFromString

func RoleFromString(s string) (Role, error)

RoleFromString converts a string to a Role.

type RoleEntitlementsStore

type RoleEntitlementsStore struct {
	// contains filtered or unexported fields
}

RoleEntitlementsStore loads and saves role→entitlement IDs (global permissions) in the system database.

func NewRoleEntitlementsStore

func NewRoleEntitlementsStore(systemStorage storage.Engine) *RoleEntitlementsStore

NewRoleEntitlementsStore creates a store that reads/writes role entitlements to the given system storage.

func (*RoleEntitlementsStore) All

func (r *RoleEntitlementsStore) All() map[string][]string

All returns role→entitlement IDs for all roles that have a stored override. For full role→entitlements (including built-in defaults) the server merges with RolePermissions.

func (*RoleEntitlementsStore) Get

func (r *RoleEntitlementsStore) Get(role string) []string

Get returns the stored entitlement IDs for a role. Returns nil if no override is stored. For permission resolution use PermissionsForRole so built-in roles use RolePermissions when not overridden.

func (*RoleEntitlementsStore) Load

Load reads all _RoleEntitlement nodes from storage into memory. Call at startup and after PUT.

func (*RoleEntitlementsStore) Set

func (r *RoleEntitlementsStore) Set(ctx context.Context, role string, entitlementIDs []string) error

Set persists one role's entitlement IDs and refreshes in-memory. Empty list removes override (built-in will use RolePermissions).

type RoleStore

type RoleStore struct {
	// contains filtered or unexported fields
}

RoleStore persists user-defined role names in the system database.

func NewRoleStore

func NewRoleStore(systemStorage storage.Engine) *RoleStore

NewRoleStore creates a store that reads/writes _Role nodes in the given system storage.

func (*RoleStore) AllRoles

func (r *RoleStore) AllRoles() []string

AllRoles returns built-in role names plus all user-defined role names.

func (*RoleStore) CreateRole

func (r *RoleStore) CreateRole(ctx context.Context, name string) error

CreateRole creates a user-defined role. Fails if name is built-in or already exists.

func (*RoleStore) DeleteRole

func (r *RoleStore) DeleteRole(ctx context.Context, name string) error

DeleteRole removes a user-defined role. Fails if name is built-in or does not exist. Caller must ensure no user has this role (e.g. check auth.ListUsers()).

func (*RoleStore) Exists

func (r *RoleStore) Exists(name string) bool

Exists returns true if the role exists (built-in or user-defined).

func (*RoleStore) Load

func (r *RoleStore) Load(ctx context.Context) error

Load reads all _Role nodes from storage into memory.

func (*RoleStore) RenameRole

func (r *RoleStore) RenameRole(ctx context.Context, oldName, newName string) error

RenameRole renames a user-defined role (oldName -> newName). Fails if old is built-in, new is built-in or exists, or old does not exist. Does not update user nodes or allowlist; caller should update those or document that rename is for the role list only and allowlist/user assignments use the new name going forward.

type TokenResponse

type TokenResponse struct {
	AccessToken string `json:"access_token"`
	TokenType   string `json:"token_type"`           // Always "Bearer"
	ExpiresIn   int64  `json:"expires_in,omitempty"` // Seconds until expiration (omitted if never expires)
	Scope       string `json:"scope,omitempty"`
}

TokenResponse follows OAuth 2.0 RFC 6749 token response format. Compatible with standard OAuth 2.0 token endpoints.

type User

type User struct {
	ID           string            `json:"id"`
	Username     string            `json:"username"`
	Email        string            `json:"email,omitempty"`
	PasswordHash string            `json:"-"` // Never serialize
	Roles        []Role            `json:"roles"`
	CreatedAt    time.Time         `json:"created_at"`
	UpdatedAt    time.Time         `json:"updated_at"`
	LastLogin    time.Time         `json:"last_login,omitempty"`
	FailedLogins int               `json:"-"` // Internal tracking
	LockedUntil  time.Time         `json:"-"` // Internal tracking
	Disabled     bool              `json:"disabled,omitempty"`
	Metadata     map[string]string `json:"metadata,omitempty"`
}

User represents an authenticated user account.

Users have:

  • Unique ID and username
  • Password (hashed with bcrypt, never exposed)
  • One or more roles (admin, editor, viewer, none)
  • Timestamps for auditing
  • Metadata for custom properties

Security features:

  • PasswordHash is never serialized (json:"-" tag)
  • FailedLogins and LockedUntil track brute force attempts
  • Disabled flag allows account suspension

Example:

user := &auth.User{
	ID:       "usr-abc123",
	Username: "alice",
	Email:    "alice@example.com",
	Roles:    []auth.Role{auth.RoleEditor},
	Metadata: map[string]string{
		"department": "Engineering",
		"team":       "Backend",
	},
}

// Check permissions
if user.HasRole(auth.RoleAdmin) {
	fmt.Println("User is admin")
}

if user.HasPermission(auth.PermWrite) {
	fmt.Println("User can write data")
}

func (*User) HasPermission

func (u *User) HasPermission(perm Permission) bool

HasPermission checks if the user has a specific permission through any of their roles.

Permissions are granted by roles. This method checks all the user's roles and returns true if any role grants the requested permission.

Permission hierarchy:

  • RoleAdmin: read, write, create, delete, admin, schema, user_manage
  • RoleEditor: read, write, create, delete
  • RoleViewer: read
  • RoleNone: (no permissions)

Example:

user := &auth.User{
	Roles: []auth.Role{auth.RoleEditor},
}

if user.HasPermission(auth.PermRead) {
	fmt.Println("Can read") // Printed
}

if user.HasPermission(auth.PermWrite) {
	fmt.Println("Can write") // Printed
}

if user.HasPermission(auth.PermUserManage) {
	fmt.Println("Can manage users") // NOT printed (needs RoleAdmin)
}

func (*User) HasRole

func (u *User) HasRole(role Role) bool

HasRole checks if the user has a specific role.

A user can have multiple roles. This method returns true if any of the user's roles match the specified role.

Example:

user := &auth.User{
	Roles: []auth.Role{auth.RoleEditor, auth.RoleViewer},
}

if user.HasRole(auth.RoleAdmin) {
	fmt.Println("Is admin") // Not printed
}

if user.HasRole(auth.RoleEditor) {
	fmt.Println("Is editor") // Printed
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL