gouncer

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

README

Gouncer

The Go bouncer. Composable authentication primitives for Go, framework-free and storage-agnostic. You assemble them in your own handlers and database. Gouncer owns none of your HTTP layer.

Stability: v0. The API may change between minor releases while the project matures. Pin a version and read the CHANGELOG before upgrading. Production use is at your own risk until v1.

API

  • NewUser validates registration input and hashes the password with argon2id.
  • VerifyPassword checks a password against a stored hash. It never panics and a malformed hash never matches.
  • NewSession issues a login session with a random token. The client sees the token once and only its digest is stored.
  • HashToken digests a token for storage and lookup.
  • Store is the persistence contract. Bring your own database and return the package sentinel errors.

Design

Gouncer is a set of authentication primitives. It stays out of your transport, routing, and storage decisions, and grows by adding small, independent building blocks. You adopt only what you need.

Usage

// Registration.
u, err := gouncer.NewUser("ada@example.com", "Ada Lovelace", "correct horse battery")
if err != nil {
    // errors.Is against the package Err* sentinels.
}
err = store.CreateUser(ctx, u) // gouncer.ErrEmailTaken on duplicates

// Login.
u, err = store.UserByEmail(ctx, email)
if err != nil || !gouncer.VerifyPassword(u.PasswordHash, password) || u.Disabled {
    // Reject with one generic "invalid credentials" answer.
}
s, err := gouncer.NewSession(u.ID)
err = store.CreateSession(ctx, s)
// Hand s.Token to the client, persist only s.TokenHash.

// Authenticating a request.
u, err = store.UserBySession(ctx, gouncer.HashToken(token), time.Now().UTC())
// gouncer.ErrSessionNotFound when unknown, expired, or the user is disabled.

// Logout.
err = store.DeleteSession(ctx, gouncer.HashToken(token))

Security notes for integrators

  • Equalize login timing. When UserByEmail misses, verify against a fixed dummy hash so unknown and known emails cost the same.
  • Serve session tokens in HttpOnly, Secure, SameSite cookies with the __Host- prefix. Never log the plain token.
  • Rate limit your login endpoint. Password verification is expensive by design.

Reporting security issues

Privately, please. See SECURITY.md.

License

Apache-2.0. Copyright © 2026 Manuel 'SirLouen' Camargo.

Documentation

Overview

Package gouncer provides composable authentication primitives for Go.

Index

Examples

Constants

View Source
const DefaultSessionDuration = 30 * 24 * time.Hour

DefaultSessionDuration is the lifetime NewSession applies.

Variables

View Source
var ErrEmailTaken = errors.New("gouncer: email already taken")

ErrEmailTaken reports that another user already owns the email.

View Source
var ErrEmptyName = errors.New("gouncer: empty name")

ErrEmptyName reports that a user name is empty or only whitespace.

View Source
var ErrInvalidEmail = errors.New("gouncer: invalid email")

ErrInvalidEmail reports that an email address is not a plain valid address.

View Source
var ErrNameTooLong = errors.New("gouncer: name longer than 256 characters")

ErrNameTooLong reports that a name exceeds the maximum length.

View Source
var ErrPasswordTooLong = errors.New("gouncer: password longer than 1024 characters")

ErrPasswordTooLong reports that a password exceeds the maximum length.

View Source
var ErrSessionNotFound = errors.New("gouncer: session not found")

ErrSessionNotFound reports that no usable session exists for a token: it is unknown, expired, or its user is disabled.

View Source
var ErrUserNotFound = errors.New("gouncer: user not found")

ErrUserNotFound reports that no user exists for the requested email.

View Source
var ErrWeakPassword = errors.New("gouncer: password shorter than 12 characters")

ErrWeakPassword reports that a password is shorter than the minimum length.

Functions

func HashToken

func HashToken(token string) []byte

HashToken returns the digest under which a session token is persisted and looked up.

func VerifyPassword

func VerifyPassword(hash, password string) bool

VerifyPassword reports whether password matches the argon2id PHC hash. It never panics, a malformed or out-of-envelope hash never matches.

Example
package main

import (
	"fmt"

	"github.com/gopherium/gouncer"
)

func main() {
	u, err := gouncer.NewUser("ada@example.com", "Ada Lovelace", "correct horse battery")
	if err != nil {
		return
	}

	fmt.Println(gouncer.VerifyPassword(u.PasswordHash, "correct horse battery"))
	fmt.Println(gouncer.VerifyPassword(u.PasswordHash, "wrong password entirely"))
}
Output:
true
false

Types

type Session

type Session struct {
	Token     string
	TokenHash []byte
	UserID    uuid.UUID
	CreatedAt time.Time
	ExpiresAt time.Time
}

Session is a login session. Build one with NewSession. Token is handed to the client once, only TokenHash is persisted.

func NewSession

func NewSession(userID uuid.UUID) (Session, error)

NewSession issues a session for the user with a fresh random token.

Example
package main

import (
	"fmt"

	"github.com/google/uuid"

	"github.com/gopherium/gouncer"
)

func main() {
	s, err := gouncer.NewSession(uuid.Must(uuid.NewV7()))
	if err != nil {
		return
	}

	_ = s.Token
	fmt.Println(len(s.TokenHash))
}
Output:
32

type Store

type Store interface {
	// CreateUser stores u, or returns [ErrEmailTaken].
	CreateUser(ctx context.Context, u User) error

	// UserByEmail returns the user with the normalized email, or [ErrUserNotFound].
	UserByEmail(ctx context.Context, email string) (User, error)

	// CreateSession stores s.
	CreateSession(ctx context.Context, s Session) error

	// UserBySession returns the user owning the session, or [ErrSessionNotFound].
	UserBySession(ctx context.Context, tokenHash []byte, now time.Time) (User, error)

	// DeleteSession removes the session. Removing an absent one is not an error.
	DeleteSession(ctx context.Context, tokenHash []byte) error
}

Store persists users and their login sessions, returning the package's Err* sentinels so callers can branch with errors.Is.

type User

type User struct {
	ID           uuid.UUID
	Email        string
	Name         string
	PasswordHash string
	Disabled     bool
	CreatedAt    time.Time
}

User is an account holder with password credentials. Build one with NewUser.

func NewUser

func NewUser(email, name, password string) (User, error)

NewUser returns a validated User with a normalized email, a trimmed name, and the password stored as an argon2id hash. Invalid input returns one of the package's Err* sentinels.

Example
package main

import (
	"fmt"

	"github.com/gopherium/gouncer"
)

func main() {
	u, err := gouncer.NewUser("ada@example.com", "Ada Lovelace", "correct horse battery")
	if err != nil {
		return
	}

	fmt.Println(u.Email)
}
Output:
ada@example.com

Directories

Path Synopsis
authkit module
postgres module
ratelimit module

Jump to

Keyboard shortcuts

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