Documentation
¶
Overview ¶
internal/auth/cookie.go
Index ¶
- Constants
- Variables
- func ExpireSessionCookie(cfg CookieConfig) *http.Cookie
- func NewSessionCookie(token string, cfg CookieConfig) *http.Cookie
- func PrincipalUserContextMiddleware(auth UserAuthenticator) func(http.Handler) http.Handler
- func RequireAdmin(next http.Handler) http.Handler
- func TokenFromRequest(r *http.Request) (string, error)
- func ValidateDisplayName(input string) (string, error)
- func ValidatePassword(password string) error
- func ValidateUsername(username string) (string, error)
- func WithPrincipal(ctx context.Context, principal Principal) context.Context
- type CookieConfig
- type HTTPHandler
- func (h *HTTPHandler) ChangePassword(ctx context.Context, request httpapi.ChangePasswordRequestObject) (httpapi.ChangePasswordResponseObject, error)
- func (h *HTTPHandler) CreateUser(ctx context.Context, request httpapi.CreateUserRequestObject) (httpapi.CreateUserResponseObject, error)
- func (h *HTTPHandler) DeleteUser(ctx context.Context, request httpapi.DeleteUserRequestObject) (httpapi.DeleteUserResponseObject, error)
- func (h *HTTPHandler) DemoteUser(ctx context.Context, request httpapi.DemoteUserRequestObject) (httpapi.DemoteUserResponseObject, error)
- func (h *HTTPHandler) GetCurrentUser(ctx context.Context, _ httpapi.GetCurrentUserRequestObject) (httpapi.GetCurrentUserResponseObject, error)
- func (h *HTTPHandler) Login(ctx context.Context, request httpapi.LoginRequestObject) (httpapi.LoginResponseObject, error)
- func (h *HTTPHandler) Logout(ctx context.Context, _ httpapi.LogoutRequestObject) (httpapi.LogoutResponseObject, error)
- func (h *HTTPHandler) PromoteUser(ctx context.Context, request httpapi.PromoteUserRequestObject) (httpapi.PromoteUserResponseObject, error)
- func (h *HTTPHandler) UpdateMe(ctx context.Context, request httpapi.UpdateMeRequestObject) (httpapi.UpdateMeResponseObject, error)
- func (h *HTTPHandler) UserAuthenticator() UserAuthenticator
- type Principal
- type ProfileUpdates
- type Repository
- func (r *Repository) CountUsers(ctx context.Context) (int, error)
- func (r *Repository) CreateSession(ctx context.Context, session *Session) (*Session, error)
- func (r *Repository) CreateUser(ctx context.Context, user *User) (*User, error)
- func (r *Repository) FindBootstrappedAdmin(ctx context.Context) (*User, error)
- func (r *Repository) GetAllUsers(ctx context.Context) ([]User, error)
- func (r *Repository) GetSessionWithRoleByTokenHash(ctx context.Context, tokenHash string) (*SessionWithUser, error)
- func (r *Repository) GetUserByID(ctx context.Context, userID UserID) (*User, error)
- func (r *Repository) GetUserByUsername(ctx context.Context, username string) (*User, error)
- func (r *Repository) NullifyPasswordHash(ctx context.Context, userID UserID) error
- func (r *Repository) RevokeAllUserSessions(ctx context.Context, userID UserID) error
- func (r *Repository) RevokeAllUserSessionsExcept(ctx context.Context, userID UserID, exceptSessionID SessionID) error
- func (r *Repository) RevokeSessionByID(ctx context.Context, id SessionID) error
- func (r *Repository) SoftDeleteUser(ctx context.Context, userID UserID) error
- func (r *Repository) UpdatePasswordHash(ctx context.Context, userID UserID, newHash []byte, mustChangePassword bool) error
- func (r *Repository) UpdateUser(ctx context.Context, user *User) (*User, error)
- type Role
- type Service
- func (s *Service) AddUserObserver(obs UserObserver)
- func (s *Service) Authenticate(ctx context.Context, rawToken string) (*Principal, error)
- func (s *Service) BootstrapAdmin(ctx context.Context, conf config.ConfServer) error
- func (s *Service) ChangePassword(ctx context.Context, userID UserID, sessionID SessionID, ...) error
- func (s *Service) CreateUser(ctx context.Context, username string, displayName string, email string, ...) (*User, error)
- func (s *Service) DeleteUser(ctx context.Context, principal *Principal, targetID UserID) error
- func (s *Service) DemoteUser(ctx context.Context, principal *Principal, targetID UserID) (*User, error)
- func (s *Service) GetUserFromPrincipal(ctx context.Context, principal *Principal) (*User, error)
- func (s *Service) ListUsers(ctx context.Context) ([]User, error)
- func (s *Service) Login(ctx context.Context, username string, password string) (string, *User, error)
- func (s *Service) PromoteUser(ctx context.Context, principal *Principal, targetID UserID, newPassword string) (*User, error)
- func (s *Service) RevokeSession(ctx context.Context, sessionID SessionID) error
- func (s *Service) UpdateOwnProfile(ctx context.Context, userID UserID, updates ProfileUpdates) (*User, error)
- type Session
- type SessionID
- type SessionWithUser
- type User
- type UserAuthenticator
- type UserEvent
- type UserEventType
- type UserID
- type UserObserver
Constants ¶
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.
const ( BootstrapAdminUsername = "admin" BootstrapAdminDisplayName = "Admin" BootstrapAdminEmail = "admin@pulseweaver.invalid" SuperAdminRole Role = "superadmin" AdminRole Role = "admin" UserRole Role = "user" )
const CookieDuration = 7 * 24 * time.Hour
Variables ¶
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") )
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
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 ValidateDisplayName ¶ added in v0.3.0
TODO: Make private, make user_tests go through the NewUser public path instead
func ValidatePassword ¶ added in v0.3.0
TODO: Make private, make user_tests go through the NewUser public path instead
func ValidateUsername ¶ added in v0.3.0
TODO: Make private, make user_tests go through the NewUser public path instead
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) ChangePassword ¶
func (h *HTTPHandler) ChangePassword(ctx context.Context, request httpapi.ChangePasswordRequestObject) (httpapi.ChangePasswordResponseObject, error)
func (*HTTPHandler) CreateUser ¶
func (h *HTTPHandler) CreateUser(ctx context.Context, request httpapi.CreateUserRequestObject) (httpapi.CreateUserResponseObject, error)
func (*HTTPHandler) DeleteUser ¶
func (h *HTTPHandler) DeleteUser(ctx context.Context, request httpapi.DeleteUserRequestObject) (httpapi.DeleteUserResponseObject, error)
func (*HTTPHandler) DemoteUser ¶
func (h *HTTPHandler) DemoteUser(ctx context.Context, request httpapi.DemoteUserRequestObject) (httpapi.DemoteUserResponseObject, error)
func (*HTTPHandler) GetCurrentUser ¶
func (h *HTTPHandler) GetCurrentUser(ctx context.Context, _ httpapi.GetCurrentUserRequestObject) (httpapi.GetCurrentUserResponseObject, error)
func (*HTTPHandler) Login ¶
func (h *HTTPHandler) Login(ctx context.Context, request httpapi.LoginRequestObject) (httpapi.LoginResponseObject, error)
func (*HTTPHandler) Logout ¶
func (h *HTTPHandler) Logout(ctx context.Context, _ httpapi.LogoutRequestObject) (httpapi.LogoutResponseObject, error)
func (*HTTPHandler) PromoteUser ¶
func (h *HTTPHandler) PromoteUser(ctx context.Context, request httpapi.PromoteUserRequestObject) (httpapi.PromoteUserResponseObject, error)
func (*HTTPHandler) UpdateMe ¶
func (h *HTTPHandler) UpdateMe(ctx context.Context, request httpapi.UpdateMeRequestObject) (httpapi.UpdateMeResponseObject, error)
func (*HTTPHandler) UserAuthenticator ¶
func (h *HTTPHandler) UserAuthenticator() UserAuthenticator
type Principal ¶
func PrincipalFromSession ¶
func PrincipalFromSession(session *SessionWithUser) *Principal
func (Principal) IsSuperAdmin ¶ added in v0.3.0
type ProfileUpdates ¶
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 (*Repository) CreateUser ¶
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 (*Repository) GetUserByUsername ¶
func (*Repository) NullifyPasswordHash ¶ added in v0.3.0
func (r *Repository) NullifyPasswordHash(ctx context.Context, userID UserID) error
func (*Repository) RevokeAllUserSessions ¶
func (r *Repository) RevokeAllUserSessions(ctx context.Context, userID UserID) error
func (*Repository) RevokeAllUserSessionsExcept ¶
func (*Repository) RevokeSessionByID ¶
func (r *Repository) RevokeSessionByID(ctx context.Context, id SessionID) error
func (*Repository) SoftDeleteUser ¶
func (r *Repository) SoftDeleteUser(ctx context.Context, userID UserID) error
func (*Repository) UpdatePasswordHash ¶
func (*Repository) UpdateUser ¶
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
func NewService ¶
func (*Service) AddUserObserver ¶ added in v0.3.0
func (s *Service) AddUserObserver(obs UserObserver)
func (*Service) Authenticate ¶
func (*Service) BootstrapAdmin ¶
func (*Service) ChangePassword ¶
func (*Service) CreateUser ¶
func (*Service) DeleteUser ¶
func (*Service) DemoteUser ¶
func (*Service) GetUserFromPrincipal ¶
func (*Service) PromoteUser ¶
func (*Service) RevokeSession ¶
func (*Service) UpdateOwnProfile ¶
type Session ¶
type Session struct {
ID SessionID `db:"id" `
UserID 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 ¶
type SessionWithUser ¶
type User ¶
type User struct {
ID 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 *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, email, password string, createdByID *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 NewUserAccount ¶ added in v0.3.0
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 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
UserObserver is implemented by any service that needs to react to user lifecycle events. Implementations are called synchronously within the originating transaction context.