auth

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Jun 26, 2026 License: AGPL-3.0 Imports: 20 Imported by: 0

Documentation

Overview

internal/auth/cookie.go

Index

Constants

View Source
const (
	AttrKeyComponent   = logging.AttrKeyComponent
	AttrKeyOperation   = logging.AttrKeyOperation
	AttrKeyError       = logging.AttrKeyError
	AttrKeyUserID      = "user_id"
	AttrKeyDisplayName = "display_name"
	AttrKeySessionID   = "session_id"
	AttrKeyUsername    = "username"
)

Slog attribute key names for the auth domain. Use these constants when logging so keys are consistent and typo-safe across handlers and services.

View Source
const (
	BootstrapAdminUsername    = "admin"
	BootstrapAdminDisplayName = "Admin"
	BootstrapAdminEmail       = "admin@pulseweaver.invalid"

	SuperAdminRole Role = "superadmin"
	AdminRole      Role = "admin"
	UserRole       Role = "user"
)
View Source
const CookieDuration = 7 * 24 * time.Hour

Variables

View Source
var (
	ErrInvalidCredentials            = errors.New("invalid credentials")
	ErrAdminCredentialsRequired      = errors.New("admin credentials are required")
	ErrSuperAdminCredentialsRequired = errors.New("superadmin credentials are required")
	ErrUsernameTaken                 = errors.New("username already taken")
	ErrEmailTaken                    = errors.New("email already taken")
	ErrUserNotFound                  = errors.New("user not found")
	ErrInvalidUsername               = errors.New("invalid username")
	ErrInvalidDisplayName            = errors.New("invalid display name")
	ErrInvalidPassword               = errors.New("invalid password")
	ErrSelfDeleteForbidden           = errors.New("cannot delete yourself")
	ErrSelfRoleChangeForbidden       = errors.New("cannot change your own role")
	ErrPromoteAlreadyAdmin           = errors.New("cannot promote an admin")
	ErrNoUpdateFields                = errors.New("no fields to update")
)
View Source
var DefaultCookieConfig = CookieConfig{
	Name:     httpapi.SessionCookieName,
	Secure:   true,
	HTTPOnly: true,
	SameSite: http.SameSiteLaxMode,
	Domain:   "",
}

Default production config

Functions

func ExpireSessionCookie

func ExpireSessionCookie(cfg CookieConfig) *http.Cookie

ExpireSessionCookie creates a cookie to clear the session

func NewSessionCookie

func NewSessionCookie(token string, cfg CookieConfig) *http.Cookie

NewSessionCookie creates a standardized auth cookie

func PrincipalUserContextMiddleware

func PrincipalUserContextMiddleware(auth UserAuthenticator) func(http.Handler) http.Handler

PrincipalUserContextMiddleware resolves the authenticated principal into a user principal and injects it into the request context.

func RequireAdmin added in v0.3.0

func RequireAdmin(next http.Handler) http.Handler

RequireAdmin is an invariant enforcer: any user session principal must be an admin. Device-principal requests (no user principal in context) pass through unchecked. Must run after PrincipalUserContextMiddleware.

func TokenFromRequest

func TokenFromRequest(r *http.Request) (string, error)

func ValidateDisplayName added in v0.3.0

func ValidateDisplayName(input string) (string, error)

TODO: Make private, make user_tests go through the NewUser public path instead

func ValidatePassword added in v0.3.0

func ValidatePassword(password string) error

TODO: Make private, make user_tests go through the NewUser public path instead

func ValidateUsername added in v0.3.0

func ValidateUsername(username string) (string, error)

TODO: Make private, make user_tests go through the NewUser public path instead

func WithPrincipal

func WithPrincipal(ctx context.Context, principal Principal) context.Context

Types

type CookieConfig

type CookieConfig struct {
	Name     string
	Secure   bool
	HTTPOnly bool
	SameSite http.SameSite
	Domain   string // Empty for host-only (recommended)
}

CookieConfig holds environment-specific cookie settings

type HTTPHandler

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

func NewHandler

func NewHandler(service *Service, logger *slog.Logger) *HTTPHandler

func (*HTTPHandler) CreateUser

func (*HTTPHandler) DeleteUser

func (*HTTPHandler) DemoteUser

func (*HTTPHandler) Login

func (*HTTPHandler) UpdateMe

func (*HTTPHandler) UserAuthenticator

func (h *HTTPHandler) UserAuthenticator() UserAuthenticator

type Principal

type Principal struct {
	UserID    ids.UserID
	SessionID ids.SessionID
	Role      Role
}

func NewPrincipal

func NewPrincipal(userID ids.UserID, sessionID ids.SessionID, role Role) *Principal

func PrincipalFromContext

func PrincipalFromContext(ctx context.Context) (*Principal, bool)

func PrincipalFromSession

func PrincipalFromSession(session *SessionWithUser) *Principal

func (Principal) IsAdmin

func (principal Principal) IsAdmin() bool

func (Principal) IsSuperAdmin added in v0.3.0

func (principal Principal) IsSuperAdmin() bool

type ProfileUpdates

type ProfileUpdates struct {
	DisplayName *string
	Username    *string
	Email       **string
}

ProfileUpdates carries the raw nullable API values for a profile update. nil pointer = field was absent in the request (leave unchanged). For Email, **string semantics apply: nil = absent, *nil = clear, *&s = set.

type Repository

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

func NewRepository

func NewRepository(db *database.DB) *Repository

func (*Repository) CountUsers

func (r *Repository) CountUsers(ctx context.Context) (int, error)

func (*Repository) CreateSession

func (r *Repository) CreateSession(ctx context.Context, session *Session) (*Session, error)

func (*Repository) CreateUser

func (r *Repository) CreateUser(ctx context.Context, user *User) (*User, error)

func (*Repository) FindBootstrappedAdmin added in v0.3.0

func (r *Repository) FindBootstrappedAdmin(ctx context.Context) (*User, error)

func (*Repository) GetAllUsers

func (r *Repository) GetAllUsers(ctx context.Context) ([]User, error)

func (*Repository) GetSessionWithRoleByTokenHash

func (r *Repository) GetSessionWithRoleByTokenHash(ctx context.Context, tokenHash string) (*SessionWithUser, error)

GetSessionWithRoleByTokenHash Finds and retrieves valid session(non-expired or revoked) given a tokenHash. Also returns the user_role

func (*Repository) GetUserByID

func (r *Repository) GetUserByID(ctx context.Context, userID ids.UserID) (*User, error)

func (*Repository) GetUserByUsername

func (r *Repository) GetUserByUsername(ctx context.Context, username string) (*User, error)

func (*Repository) NullifyPasswordHash added in v0.3.0

func (r *Repository) NullifyPasswordHash(ctx context.Context, userID ids.UserID) error

func (*Repository) RevokeAllUserSessions

func (r *Repository) RevokeAllUserSessions(ctx context.Context, userID ids.UserID) error

func (*Repository) RevokeAllUserSessionsExcept

func (r *Repository) RevokeAllUserSessionsExcept(ctx context.Context, userID ids.UserID, exceptSessionID ids.SessionID) error

func (*Repository) RevokeSessionByID

func (r *Repository) RevokeSessionByID(ctx context.Context, id ids.SessionID) error

func (*Repository) SoftDeleteUser

func (r *Repository) SoftDeleteUser(ctx context.Context, userID ids.UserID) error

func (*Repository) UpdatePasswordHash

func (r *Repository) UpdatePasswordHash(ctx context.Context, userID ids.UserID, newHash []byte, mustChangePassword bool) error

func (*Repository) UpdateUser

func (r *Repository) UpdateUser(ctx context.Context, user *User) (*User, error)

type Role

type Role string

func (Role) String

func (r Role) String() string

type Service

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

func NewService

func NewService(repo repository, tx transactor, logger *slog.Logger) *Service

func (*Service) AddUserObserver added in v0.3.0

func (s *Service) AddUserObserver(obs UserObserver)

func (*Service) Authenticate

func (s *Service) Authenticate(ctx context.Context, rawToken string) (*Principal, error)

func (*Service) BootstrapAdmin

func (s *Service) BootstrapAdmin(ctx context.Context, conf config.ConfServer) error

func (*Service) ChangePassword

func (s *Service) ChangePassword(ctx context.Context, userID ids.UserID, sessionID ids.SessionID, currentPassword string, newPassword string) error

func (*Service) CreateUser

func (s *Service) CreateUser(ctx context.Context, username string, displayName string, email *string, principal *Principal) (*User, error)

func (*Service) DeleteUser

func (s *Service) DeleteUser(ctx context.Context, principal *Principal, targetID ids.UserID) error

func (*Service) DemoteUser

func (s *Service) DemoteUser(ctx context.Context, principal *Principal, targetID ids.UserID) (*User, error)

func (*Service) GetUserFromPrincipal

func (s *Service) GetUserFromPrincipal(ctx context.Context, principal *Principal) (*User, error)

func (*Service) Login

func (s *Service) Login(ctx context.Context, username string, password string) (string, *User, error)

func (*Service) PromoteUser

func (s *Service) PromoteUser(ctx context.Context, principal *Principal, targetID ids.UserID, newPassword string) (*User, error)

func (*Service) RevokeSession

func (s *Service) RevokeSession(ctx context.Context, sessionID ids.SessionID) error

func (*Service) UpdateOwnProfile

func (s *Service) UpdateOwnProfile(ctx context.Context, userID ids.UserID, updates ProfileUpdates) (*User, error)

type Session

type Session struct {
	ID         ids.SessionID `db:"id" `
	UserID     ids.UserID    `db:"user_id" `
	TokenHash  string        `db:"token_hash" `
	CreatedAt  time.Time     `db:"created_at" `
	ExpiresAt  time.Time     `db:"expires_at" `
	LastUsedAt *time.Time    `db:"last_used_at" `
	RevokedAt  *time.Time    `db:"revoked_at" `
}

func NewSession

func NewSession(userID ids.UserID, tokenHash string) Session

type SessionWithUser

type SessionWithUser struct {
	Session
	UserRole Role `db:"user_role"` // Alias in SQL Join
}

type User

type User struct {
	ID                 ids.UserID  `db:"id"`
	Username           string      `db:"username"`
	DisplayName        string      `db:"display_name"`
	Email              *string     `db:"email"`
	PasswordHash       []byte      `db:"password_hash"`
	Role               Role        `db:"role"`
	MustChangePassword bool        `db:"must_change_password"`
	CreatedBy          *ids.UserID `db:"created_by"`
	CreatedAt          time.Time   `db:"created_at"`
	DeletedAt          *time.Time  `db:"deleted_at"`
}

func NewAdminUser added in v0.3.0

func NewAdminUser(username, displayName, password string, email *string, createdByID *ids.UserID, mustChangePassword bool) (User, error)

NewAdminUser creates an admin-role user; password is required and hashed. MustChangePassword is set to true so the admin must change the assigned password on first login.

func NewBootstrappedAdmin added in v0.3.0

func NewBootstrappedAdmin(password string) (User, error)

func NewUserAccount added in v0.3.0

func NewUserAccount(username, displayName string, email *string, createdByID *ids.UserID) (User, error)

NewUserAccount creates a user-role account with no password. Non-admin users cannot log in; they exist solely to own devices.

func (*User) Update

func (u *User) Update(up ProfileUpdates) error

type UserAuthenticator

type UserAuthenticator interface {
	Authenticate(ctx context.Context, token string) (*Principal, error)
}

UserAuthenticator defines the interface for authenticating user sessions. This interface is defined in the auth package to avoid import cycles.

type UserEvent added in v0.3.0

type UserEvent struct {
	Type   UserEventType
	UserID ids.UserID
}

type UserEventType added in v0.3.0

type UserEventType string
const (
	EventTypeUserCreated UserEventType = "user_created"
	EventTypeUserDeleted UserEventType = "user_deleted"
)

type UserObserver added in v0.3.0

type UserObserver interface {
	OnUserEvent(ctx context.Context, event UserEvent)
}

UserObserver is implemented by any service that needs to react to user lifecycle events. Implementations are called synchronously within the originating transaction context.

Jump to

Keyboard shortcuts

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