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
- Variables
- type APIKey
- type APIKeyHandler
- func (h *APIKeyHandler) HandleCreateAPIKey(w http.ResponseWriter, r *http.Request, userID, role string)
- func (h *APIKeyHandler) HandleListAPIKeys(w http.ResponseWriter, r *http.Request, userID, role string)
- func (h *APIKeyHandler) HandleRevokeAPIKey(w http.ResponseWriter, r *http.Request, keyID, userID, role string)
- type APIKeyStore
- func (s *APIKeyStore) CreateKey(userID, name string, permissions []string, expiresIn time.Duration) (*APIKey, string, error)
- func (s *APIKeyStore) CreateKeyWithEnv(userID, name string, permissions []string, expiresIn time.Duration, env string) (*APIKey, string, error)
- func (s *APIKeyStore) GetKey(keyID string) (*APIKey, error)
- func (s *APIKeyStore) HasPermission(apiKey *APIKey, permission string) bool
- func (s *APIKeyStore) ListKeys(userID string) []*APIKey
- func (s *APIKeyStore) LoadAPIKeys(dataDir string) error
- func (s *APIKeyStore) RevokeKey(keyID string) error
- func (s *APIKeyStore) SaveAPIKeys(dataDir string) error
- func (s *APIKeyStore) UpdateKeyName(keyID, newName string) error
- func (s *APIKeyStore) UpdateLastUsed(keyID string) error
- func (s *APIKeyStore) ValidateKey(keyString string) (*APIKey, error)
- func (s *APIKeyStore) ValidateKeyForEnv(keyString, serverEnv string) (*APIKey, error)
- type AuthHandler
- type AuthProvider
- type ChangePasswordRequest
- type ChangePasswordResponse
- type Claims
- type CompositeTokenValidator
- type CreateAPIKeyRequest
- type CreateAPIKeyResponse
- type CreateUserRequest
- type CreateUserResponse
- type DeleteUserResponse
- type ErrorResponse
- type GetUserResponse
- type JWTManager
- func (m *JWTManager) GenerateRefreshToken(userID string) (string, error)
- func (m *JWTManager) GenerateToken(userID, username, role string) (string, error)
- func (m *JWTManager) GenerateTokenWithGeneration(userID, username, role, tenantID string, tokenGen int) (string, error)
- func (m *JWTManager) GenerateTokenWithTenant(userID, username, role, tenantID string) (string, error)
- func (m *JWTManager) GetTokenDuration() time.Duration
- func (m *JWTManager) Name() string
- func (m *JWTManager) ValidateRefreshToken(tokenString string) (string, error)
- func (m *JWTManager) ValidateToken(_ context.Context, tokenString string) (*Claims, error)
- type ListUsersResponse
- type LoginRequest
- type LoginResponse
- type MeResponse
- type OIDCUserInfo
- type RefreshClaims
- type RefreshRequest
- type RefreshResponse
- type RegisterRequest
- type RegisterResponse
- type TokenValidator
- type UpdateUserRequest
- type UpdateUserResponse
- type User
- type UserListItem
- type UserManagementHandler
- type UserResponse
- type UserStore
- func (s *UserStore) ChangePassword(userID, newPassword string) error
- func (s *UserStore) CreateOrUpdateOIDCUser(info *OIDCUserInfo, createdAt int64) (*User, bool, error)
- func (s *UserStore) CreateUser(username, password, role string) (*User, error)
- func (s *UserStore) DeleteUser(userID string) error
- func (s *UserStore) GetUserByID(userID string) (*User, error)
- func (s *UserStore) GetUserByOIDCSubject(issuer, subject string) (*User, error)
- func (s *UserStore) GetUserByUsername(username string) (*User, error)
- func (s *UserStore) ListUsers() []*User
- func (s *UserStore) LoadUsers(dataDir string) error
- func (s *UserStore) RevokeUserTokens(userID string) error
- func (s *UserStore) SaveUsers(dataDir string) error
- func (s *UserStore) UpdateUserRole(userID, newRole string) error
- func (s *UserStore) VerifyPassword(user *User, password string) bool
Constants ¶
const ( KeyPrefixProduction = "gdb_live_" KeyPrefixTest = "gdb_test_" KeyRandomLength = 32 // bytes of random data )
const ( DefaultTokenDuration = 15 * time.Minute DefaultRefreshTokenDuration = 7 * 24 * time.Hour )
const ( RoleAdmin = "admin" RoleEditor = "editor" RoleViewer = "viewer" )
Valid roles
const ( MinPasswordLength = 8 MinUsernameLength = 3 MaxUsernameLength = 50 BcryptCost = 12 // Cost factor for bcrypt )
Variables ¶
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") )
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") )
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") )
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
func (h *UserManagementHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
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 (*UserStore) ChangePassword ¶
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 ¶
CreateUser creates a new user with hashed password
func (*UserStore) DeleteUser ¶
DeleteUser deletes a user
func (*UserStore) GetUserByID ¶
GetUserByID retrieves a user by ID
func (*UserStore) GetUserByOIDCSubject ¶
GetUserByOIDCSubject looks up a user by their OIDC issuer and subject
func (*UserStore) GetUserByUsername ¶
GetUserByUsername retrieves a user by username
func (*UserStore) RevokeUserTokens ¶ added in v0.5.0
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) UpdateUserRole ¶
UpdateUserRole updates a user's role