auth

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Jun 16, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package auth handles authentication for graphdb: JWT tokens and API keys.

JWTManager (from NewJWTManager) issues and validates JWTs whose Claims carry the user ID, username, role (admin/editor/viewer), and optional tenant. UserStore (from NewUserStore) manages users and password verification, APIKeyStore manages hashed API keys, and the HTTP handlers cover login/refresh and user management. Token validation can compose JWT with OIDC when configured.

The signing secret comes from the JWT_SECRET environment variable (minimum 32 characters) and is never persisted — the same secret must be supplied wherever tokens are minted (e.g. the graphdb-admin mint-token command).

Index

Constants

View Source
const (
	KeyPrefixProduction = "gdb_live_"
	KeyPrefixTest       = "gdb_test_"
	KeyRandomLength     = 32 // bytes of random data
)
View Source
const (
	DefaultTokenDuration        = 15 * time.Minute
	DefaultRefreshTokenDuration = 7 * 24 * time.Hour
)
View Source
const (
	RoleAdmin  = "admin"
	RoleEditor = "editor"
	RoleViewer = "viewer"
)

Valid roles

View Source
const (
	MinPasswordLength = 8
	MinUsernameLength = 3
	MaxUsernameLength = 50
	BcryptCost        = 12 // Cost factor for bcrypt
)

Variables

View Source
var (
	ErrAPIKeyNotFound    = errors.New("API key not found")
	ErrAPIKeyExpired     = errors.New("API key has expired")
	ErrAPIKeyRevoked     = errors.New("API key has been revoked")
	ErrAPIKeyWrongEnv    = errors.New("API key environment mismatch")
	ErrInvalidPermission = errors.New("invalid permission")
	ErrEmptyKeyName      = errors.New("key name cannot be empty")
	ErrEmptyPermissions  = errors.New("permissions cannot be empty")
)
View Source
var (
	ErrInvalidToken  = errors.New("invalid token")
	ErrExpiredToken  = errors.New("token has expired")
	ErrInvalidClaims = errors.New("invalid token claims")
	ErrEmptyUserID   = errors.New("userID cannot be empty")
	ErrEmptyUsername = errors.New("username cannot be empty")
	ErrEmptyRole     = errors.New("role cannot be empty")
	ErrInvalidRole   = errors.New("invalid role")
	ErrShortSecret   = errors.New("secret must be at least 32 characters")
)
View Source
var (
	ErrUserNotFound       = errors.New("user not found")
	ErrUserExists         = errors.New("user already exists")
	ErrEmptyPassword      = errors.New("password cannot be empty")
	ErrWeakPassword       = errors.New("password must be at least 8 characters")
	ErrInvalidUsername    = errors.New("username must be 3-50 alphanumeric characters")
	ErrPasswordHashFailed = errors.New("failed to hash password")
)
View Source
var ErrNoValidatorMatched = errors.New("no validator could validate the token")

ErrNoValidatorMatched is returned when no validator can validate the token

Functions

This section is empty.

Types

type APIKey

type APIKey struct {
	ID          string    `json:"id"`
	UserID      string    `json:"user_id"`
	Name        string    `json:"name"`
	Permissions []string  `json:"permissions"`
	Environment string    `json:"environment"` // "live" or "test"
	KeyHash     string    `json:"-"`           // Never serialize key hash
	Prefix      string    `json:"prefix"`
	CreatedAt   time.Time `json:"created_at"`
	ExpiresAt   time.Time `json:"expires_at,omitempty"` // Zero value means no expiration
	LastUsed    time.Time `json:"last_used,omitempty"`
	Revoked     bool      `json:"revoked"`
}

APIKey represents an API key with metadata

type APIKeyHandler

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

APIKeyHandler handles API key management endpoints

func NewAPIKeyHandler

func NewAPIKeyHandler(store *APIKeyStore, userStore *UserStore) *APIKeyHandler

NewAPIKeyHandler creates a new API key handler

func (*APIKeyHandler) HandleCreateAPIKey

func (h *APIKeyHandler) HandleCreateAPIKey(w http.ResponseWriter, r *http.Request, userID, role string)

HandleCreateAPIKey creates a new API key (admin only)

func (*APIKeyHandler) HandleListAPIKeys

func (h *APIKeyHandler) HandleListAPIKeys(w http.ResponseWriter, r *http.Request, userID, role string)

HandleListAPIKeys lists API keys for the authenticated user

func (*APIKeyHandler) HandleRevokeAPIKey

func (h *APIKeyHandler) HandleRevokeAPIKey(w http.ResponseWriter, r *http.Request, keyID, userID, role string)

HandleRevokeAPIKey revokes an API key

type APIKeyStore

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

APIKeyStore manages API keys

func NewAPIKeyStore

func NewAPIKeyStore() *APIKeyStore

NewAPIKeyStore creates a new API key store with a random HMAC secret

func NewAPIKeyStoreWithSecret

func NewAPIKeyStoreWithSecret(secret []byte) (*APIKeyStore, error)

NewAPIKeyStoreWithSecret creates a new API key store with a provided HMAC secret. Use this for persistence across restarts - the same secret must be used to validate existing keys.

func (*APIKeyStore) CreateKey

func (s *APIKeyStore) CreateKey(userID, name string, permissions []string, expiresIn time.Duration) (*APIKey, string, error)

CreateKey creates a new API key and returns the APIKey metadata and the actual key string The key string is only returned once and cannot be retrieved later

func (*APIKeyStore) CreateKeyWithEnv

func (s *APIKeyStore) CreateKeyWithEnv(userID, name string, permissions []string, expiresIn time.Duration, env string) (*APIKey, string, error)

CreateKeyWithEnv creates a new API key with explicit environment prefix. env can be "live", "test", or "" (auto-detect from GRAPHDB_ENV).

func (*APIKeyStore) GetKey

func (s *APIKeyStore) GetKey(keyID string) (*APIKey, error)

GetKey retrieves a specific API key by ID

func (*APIKeyStore) HasPermission

func (s *APIKeyStore) HasPermission(apiKey *APIKey, permission string) bool

HasPermission checks if an API key has a specific permission

func (*APIKeyStore) ListKeys

func (s *APIKeyStore) ListKeys(userID string) []*APIKey

ListKeys returns all API keys for a user

func (*APIKeyStore) LoadAPIKeys

func (s *APIKeyStore) LoadAPIKeys(dataDir string) error

LoadAPIKeys loads API keys from disk

func (*APIKeyStore) RevokeKey

func (s *APIKeyStore) RevokeKey(keyID string) error

RevokeKey revokes an API key

func (*APIKeyStore) SaveAPIKeys

func (s *APIKeyStore) SaveAPIKeys(dataDir string) error

SaveAPIKeys persists all API keys to disk

func (*APIKeyStore) UpdateKeyName

func (s *APIKeyStore) UpdateKeyName(keyID, newName string) error

UpdateKeyName updates the name of an API key

func (*APIKeyStore) UpdateLastUsed

func (s *APIKeyStore) UpdateLastUsed(keyID string) error

UpdateLastUsed updates the last used timestamp for a key

func (*APIKeyStore) ValidateKey

func (s *APIKeyStore) ValidateKey(keyString string) (*APIKey, error)

ValidateKey validates an API key and returns the associated APIKey metadata

func (*APIKeyStore) ValidateKeyForEnv

func (s *APIKeyStore) ValidateKeyForEnv(keyString, serverEnv string) (*APIKey, error)

ValidateKeyForEnv validates an API key and checks it matches the required environment. serverEnv should be "live" or "test" - typically derived from GRAPHDB_ENV. If serverEnv is empty, no environment check is performed (backwards compatible).

type AuthHandler

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

AuthHandler handles authentication endpoints

func NewAuthHandler

func NewAuthHandler(userStore *UserStore, jwtManager *JWTManager) *AuthHandler

NewAuthHandler creates a new authentication handler

func (*AuthHandler) ServeHTTP

func (h *AuthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler interface for routing

func (*AuthHandler) SetTokenValidator added in v0.5.0

func (h *AuthHandler) SetTokenValidator(v TokenValidator)

SetTokenValidator overrides the access-token validator (e.g. the composite JWT+OIDC validator). A nil argument is ignored so the jwtManager default is never accidentally cleared.

type AuthProvider

type AuthProvider string

AuthProvider indicates how a user authenticates

const (
	AuthProviderLocal AuthProvider = "local"
	AuthProviderOIDC  AuthProvider = "oidc"
)

type ChangePasswordRequest

type ChangePasswordRequest struct {
	NewPassword string `json:"new_password"`
}

ChangePasswordRequest is the request for changing password

type ChangePasswordResponse

type ChangePasswordResponse struct {
	Success bool `json:"success"`
}

ChangePasswordResponse is the response for changing password

type Claims

type Claims struct {
	UserID    string    `json:"user_id"`
	Username  string    `json:"username"`
	Role      string    `json:"role"`
	TenantID  string    `json:"tenant_id,omitempty"` // Multi-tenancy: empty defaults to "default" tenant
	ExpiresAt time.Time `json:"expires_at"`
	IssuedAt  time.Time `json:"issued_at"`
	// TokenGeneration is the user's token-generation counter at issue time
	// (security audit M-7). requireAuth rejects a token whose generation is
	// older than the user's current counter, so bumping the counter (on
	// password change, role change, or explicit revoke) invalidates every
	// outstanding token without rotating the global JWT secret. Absent on
	// legacy / offline-minted tokens → 0.
	TokenGeneration int `json:"gen,omitempty"`
}

Claims represents JWT claims

type CompositeTokenValidator

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

CompositeTokenValidator chains multiple validators, trying each in order

func NewCompositeTokenValidator

func NewCompositeTokenValidator(validators ...TokenValidator) *CompositeTokenValidator

NewCompositeTokenValidator creates a validator that tries multiple validators in order

func (*CompositeTokenValidator) AddValidator

func (c *CompositeTokenValidator) AddValidator(v TokenValidator)

AddValidator adds a validator to the chain

func (*CompositeTokenValidator) Name

func (c *CompositeTokenValidator) Name() string

Name returns a composite name of all validators

func (*CompositeTokenValidator) ValidateToken

func (c *CompositeTokenValidator) ValidateToken(ctx context.Context, token string) (*Claims, error)

ValidateToken tries each validator in order until one succeeds

type CreateAPIKeyRequest

type CreateAPIKeyRequest struct {
	Name        string   `json:"name"`
	Permissions []string `json:"permissions"`
	ExpiresIn   int64    `json:"expires_in"` // seconds, 0 = never expires
}

CreateAPIKeyRequest represents a request to create an API key

type CreateAPIKeyResponse

type CreateAPIKeyResponse struct {
	Key    string  `json:"key"` // Only returned once!
	APIKey *APIKey `json:"api_key"`
}

CreateAPIKeyResponse represents the response when creating an API key

type CreateUserRequest

type CreateUserRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
	Role     string `json:"role"`
}

CreateUserRequest is the request for creating a user

type CreateUserResponse

type CreateUserResponse struct {
	User UserListItem `json:"user"`
}

CreateUserResponse is the response for creating a user

type DeleteUserResponse

type DeleteUserResponse struct {
	Success bool `json:"success"`
}

DeleteUserResponse is the response for deleting a user

type ErrorResponse

type ErrorResponse struct {
	Error   string `json:"error"`
	Message string `json:"message"`
}

ErrorResponse represents error responses

type GetUserResponse

type GetUserResponse struct {
	User UserListItem `json:"user"`
}

GetUserResponse is the response for getting a user

type JWTManager

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

JWTManager manages JWT token generation and validation

func NewJWTManager

func NewJWTManager(secret string, tokenDuration, refreshTokenDuration time.Duration) (*JWTManager, error)

NewJWTManager creates a new JWT manager. Returns an error if the secret is shorter than 32 characters (security requirement).

func (*JWTManager) GenerateRefreshToken

func (m *JWTManager) GenerateRefreshToken(userID string) (string, error)

GenerateRefreshToken generates a refresh token

func (*JWTManager) GenerateToken

func (m *JWTManager) GenerateToken(userID, username, role string) (string, error)

GenerateToken generates a new JWT token for the default tenant. For multi-tenant operations, use GenerateTokenWithTenant instead.

func (*JWTManager) GenerateTokenWithGeneration added in v0.5.0

func (m *JWTManager) GenerateTokenWithGeneration(userID, username, role, tenantID string, tokenGen int) (string, error)

GenerateTokenWithGeneration generates a JWT carrying the user's token generation (security audit M-7). Login/refresh paths pass the user's current counter so the token can be revoked by bumping it.

func (*JWTManager) GenerateTokenWithTenant

func (m *JWTManager) GenerateTokenWithTenant(userID, username, role, tenantID string) (string, error)

GenerateTokenWithTenant generates a new JWT token with tenant context. Empty tenantID defaults to "default" tenant for backward compatibility. Equivalent to GenerateTokenWithGeneration with generation 0 — used by the offline mint-token CLI, which has no UserStore to read the current generation from (such tokens are correctly invalidated by a later password/role change that bumps the user's counter past 0).

func (*JWTManager) GetTokenDuration

func (m *JWTManager) GetTokenDuration() time.Duration

GetTokenDuration returns the configured token duration

func (*JWTManager) Name

func (m *JWTManager) Name() string

Name returns the validator name for logging/debugging. Implements TokenValidator interface.

func (*JWTManager) ValidateRefreshToken

func (m *JWTManager) ValidateRefreshToken(tokenString string) (string, error)

ValidateRefreshToken validates a refresh token and returns the userID

func (*JWTManager) ValidateToken

func (m *JWTManager) ValidateToken(_ context.Context, tokenString string) (*Claims, error)

ValidateToken validates a JWT token and returns claims. Implements TokenValidator interface.

type ListUsersResponse

type ListUsersResponse struct {
	Users []UserListItem `json:"users"`
}

ListUsersResponse is the response for listing users

type LoginRequest

type LoginRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

LoginRequest is the request body for login

type LoginResponse

type LoginResponse struct {
	AccessToken  string       `json:"access_token"`
	RefreshToken string       `json:"refresh_token"`
	User         UserResponse `json:"user"`
}

LoginResponse is the response body for login

type MeResponse

type MeResponse struct {
	User UserResponse `json:"user"`
}

MeResponse is the response body for current user info

type OIDCUserInfo

type OIDCUserInfo struct {
	Subject           string // OIDC 'sub' claim (required)
	Issuer            string // OIDC issuer URL (required)
	Email             string
	EmailVerified     bool
	Name              string // Display name
	PreferredUsername string
	Picture           string
	Role              string // Mapped GraphDB role
}

OIDCUserInfo contains OIDC claims for user provisioning

type RefreshClaims

type RefreshClaims struct {
	UserID    string    `json:"user_id"`
	ExpiresAt time.Time `json:"expires_at"`
	IssuedAt  time.Time `json:"issued_at"`
}

RefreshClaims represents refresh token claims

type RefreshRequest

type RefreshRequest struct {
	RefreshToken string `json:"refresh_token"`
}

RefreshRequest is the request body for token refresh

type RefreshResponse

type RefreshResponse struct {
	AccessToken string `json:"access_token"`
}

RefreshResponse is the response body for token refresh

type RegisterRequest

type RegisterRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
	Role     string `json:"role"`
}

RegisterRequest is the request body for user registration

type RegisterResponse

type RegisterResponse struct {
	User UserResponse `json:"user"`
}

RegisterResponse is the response body for user registration

type TokenValidator

type TokenValidator interface {
	// ValidateToken validates a token and returns claims.
	// Returns error if token is invalid, expired, or malformed.
	ValidateToken(ctx context.Context, token string) (*Claims, error)

	// Name returns the validator name for logging/debugging
	Name() string
}

TokenValidator abstracts token validation to support multiple auth methods (JWT, OIDC, etc.)

type UpdateUserRequest

type UpdateUserRequest struct {
	Role string `json:"role"`
}

UpdateUserRequest is the request for updating a user

type UpdateUserResponse

type UpdateUserResponse struct {
	User UserListItem `json:"user"`
}

UpdateUserResponse is the response for updating a user

type User

type User struct {
	ID           string `json:"id"`
	Username     string `json:"username"`
	PasswordHash string `json:"-"` // Never serialize password hash
	Role         string `json:"role"`
	CreatedAt    int64  `json:"created_at"`
	// TokenGeneration is the user's revocation counter (security audit
	// M-7). It is embedded in every login-issued JWT; requireAuth rejects
	// a token whose generation is older than this. Bumped on password
	// change, role change, or explicit RevokeUserTokens — invalidating all
	// outstanding tokens for the user without rotating the global JWT
	// secret. Serializes with the user (persists wherever SaveUsers runs).
	TokenGeneration int `json:"token_generation,omitempty"`

	// OIDC-specific fields (populated for OIDC users)
	AuthProvider  AuthProvider `json:"auth_provider,omitempty"`
	OIDCSubject   string       `json:"oidc_subject,omitempty"` // OIDC 'sub' claim - unique per issuer
	OIDCIssuer    string       `json:"oidc_issuer,omitempty"`  // OIDC issuer URL
	Email         string       `json:"email,omitempty"`        // User's email (from OIDC or manual)
	EmailVerified bool         `json:"email_verified,omitempty"`
	DisplayName   string       `json:"display_name,omitempty"`  // Full name from OIDC
	Picture       string       `json:"picture,omitempty"`       // Profile picture URL
	LastLoginAt   int64        `json:"last_login_at,omitempty"` // Last successful login timestamp
}

User represents a user in the system

func (*User) IsOIDCUser

func (u *User) IsOIDCUser() bool

IsOIDCUser returns true if the user was provisioned via OIDC

type UserListItem

type UserListItem struct {
	ID        string `json:"id"`
	Username  string `json:"username"`
	Role      string `json:"role"`
	CreatedAt int64  `json:"created_at"`
}

UserListItem represents a user in list responses

type UserManagementHandler

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

UserManagementHandler handles user management endpoints

func NewUserManagementHandler

func NewUserManagementHandler(userStore *UserStore, jwtManager *JWTManager) *UserManagementHandler

NewUserManagementHandler creates a new user management handler

func (*UserManagementHandler) ServeHTTP

ServeHTTP implements http.Handler interface for routing

func (*UserManagementHandler) SetTokenValidator added in v0.5.0

func (h *UserManagementHandler) SetTokenValidator(v TokenValidator)

SetTokenValidator overrides the access-token validator (nil ignored).

type UserResponse

type UserResponse struct {
	ID       string `json:"id"`
	Username string `json:"username"`
	Role     string `json:"role"`
}

UserResponse represents user data in responses

type UserStore

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

UserStore manages user storage and authentication

func NewUserStore

func NewUserStore() *UserStore

NewUserStore creates a new user store

func (*UserStore) ChangePassword

func (s *UserStore) ChangePassword(userID, newPassword string) error

ChangePassword changes a user's password

func (*UserStore) CreateOrUpdateOIDCUser

func (s *UserStore) CreateOrUpdateOIDCUser(info *OIDCUserInfo, createdAt int64) (*User, bool, error)

CreateOrUpdateOIDCUser creates a new OIDC user or updates an existing one. This implements the "auto-provision on first login" pattern. Returns the user and whether it was newly created.

func (*UserStore) CreateUser

func (s *UserStore) CreateUser(username, password, role string) (*User, error)

CreateUser creates a new user with hashed password

func (*UserStore) DeleteUser

func (s *UserStore) DeleteUser(userID string) error

DeleteUser deletes a user

func (*UserStore) GetUserByID

func (s *UserStore) GetUserByID(userID string) (*User, error)

GetUserByID retrieves a user by ID

func (*UserStore) GetUserByOIDCSubject

func (s *UserStore) GetUserByOIDCSubject(issuer, subject string) (*User, error)

GetUserByOIDCSubject looks up a user by their OIDC issuer and subject

func (*UserStore) GetUserByUsername

func (s *UserStore) GetUserByUsername(username string) (*User, error)

GetUserByUsername retrieves a user by username

func (*UserStore) ListUsers

func (s *UserStore) ListUsers() []*User

ListUsers returns all users

func (*UserStore) LoadUsers

func (s *UserStore) LoadUsers(dataDir string) error

LoadUsers loads users from disk

func (*UserStore) RevokeUserTokens added in v0.5.0

func (s *UserStore) RevokeUserTokens(userID string) error

RevokeUserTokens invalidates every outstanding token for the user by bumping its generation counter (security audit M-7). The next requireAuth on any pre-existing token sees a stale generation and 401s. Use after a credential compromise or to force re-authentication.

func (*UserStore) SaveUsers

func (s *UserStore) SaveUsers(dataDir string) error

SaveUsers persists all users to disk

func (*UserStore) UpdateUserRole

func (s *UserStore) UpdateUserRole(userID, newRole string) error

UpdateUserRole updates a user's role

func (*UserStore) VerifyPassword

func (s *UserStore) VerifyPassword(user *User, password string) bool

VerifyPassword verifies a password against a user's stored hash

Directories

Path Synopsis
Package oidc provides OpenID Connect authentication support for GraphDB.
Package oidc provides OpenID Connect authentication support for GraphDB.

Jump to

Keyboard shortcuts

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