auth

package
v0.4.2 Latest Latest
Warning

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

Go to latest
Published: Jun 29, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package auth implements the R5 RBAC identity layer: a pluggable Authenticator interface, an admin-managed users file, bearer-token issuance and verification, and the role/permission model the review service enforces.

The package is pure (no HTTP). Downstream HTTP handlers consume the Authenticator interface and the Permission checks defined here.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUserNotFound is returned when a lookup matches no user.
	ErrUserNotFound = errors.New("auth: user not found")
	// ErrUserExists is returned when adding a user whose email already exists.
	ErrUserExists = errors.New("auth: user already exists")
)
View Source
var ErrHashMalformed = errors.New("auth: malformed hash")

ErrHashMalformed is returned by VerifyToken when the stored hash is not a well-formed argon2id PHC string.

View Source
var ErrTokenMalformed = errors.New("auth: malformed token")

ErrTokenMalformed is returned when a presented token is empty or does not carry the expected prefix.

View Source
var ErrUnauthenticated = errors.New("auth: unauthenticated")

ErrUnauthenticated is returned by Authenticate when the token is missing, malformed, or matches no user. Callers map this to a 401.

Functions

func DefaultUsersPath

func DefaultUsersPath() (string, error)

DefaultUsersPath returns ~/.config/da/review/users.yaml, honoring XDG_CONFIG_HOME first (-> $XDG_CONFIG_HOME/da/review/users.yaml) before falling back to ~/.config/da/review/users.yaml.

Secrets never live in AGENTS_HOME (the git-synced config tree pushed by `da sync`); auth state is per-host local and resolves only into the local-secrets home, alongside ~/.config/da/credentials.json.

func GenerateToken

func GenerateToken() (string, error)

GenerateToken returns a fresh cryptographically-random plaintext token. The plaintext is shown to the operator exactly once at issuance; only its hash is persisted (design D5.3, OQ1 print-once).

func HashToken

func HashToken(plaintext string) (string, error)

HashToken returns an argon2id PHC-encoded hash of a plaintext token, suitable for storage in the users file. A fresh random salt is generated per call, so hashing the same token twice yields distinct strings.

func VerifyToken

func VerifyToken(plaintext, hash string) (bool, error)

VerifyToken reports whether the plaintext matches the stored argon2id hash. A mismatch (or a malformed token) returns false with no error; an error is returned only when the stored hash itself is unusable.

Types

type Authenticator

type Authenticator interface {
	// Authenticate resolves a plaintext bearer token to an Identity. It returns
	// ErrUnauthenticated when the token does not correspond to a known user. Any
	// other error indicates an operational failure (e.g. unreadable backing
	// store) and should not be conflated with a 401.
	Authenticate(token string) (Identity, error)
}

Authenticator resolves a presented bearer token to an Identity. It is the pluggable seam that lets an OIDC backend replace the local users file later (design D5.3) without changing handler code.

type Identity

type Identity struct {
	Email string
	Role  Role
}

Identity is the resolved principal behind a presented token. It is what an Authenticator returns on success and what downstream handlers consult for authorization decisions.

func (Identity) Can

func (id Identity) Can(p Permission) bool

Can reports whether the identity's role grants the given permission.

type LocalUsersAuthenticator

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

LocalUsersAuthenticator authenticates bearer tokens against a users file on disk. The file is reloaded on each call so out-of-band CLI edits (user add / remove) take effect without a service restart.

func NewLocalUsersAuthenticator

func NewLocalUsersAuthenticator(path string) *LocalUsersAuthenticator

NewLocalUsersAuthenticator constructs an authenticator backed by the users file at path.

func (*LocalUsersAuthenticator) Authenticate

func (a *LocalUsersAuthenticator) Authenticate(token string) (Identity, error)

Authenticate implements Authenticator. It reloads the users file, then scans for the user whose stored hash matches the presented token. Because the plaintext token does not encode the owning user, every candidate hash is compared; a malformed or unmatched token yields ErrUnauthenticated.

type Permission

type Permission string

Permission is a capability the review service gates on. Permissions are derived from roles via RolePermissions; handlers check a permission rather than a role so the role→capability mapping stays in one place.

const (
	// PermReadLabels allows fetching labels.
	PermReadLabels Permission = "labels:read"
	// PermWriteLabels allows submitting and editing labels.
	PermWriteLabels Permission = "labels:write"
	// PermManageUsers allows creating, listing, and removing users.
	PermManageUsers Permission = "users:manage"
	// PermAdminLabels allows editing any reviewer's label (admin correction).
	PermAdminLabels Permission = "labels:admin"
	// PermReadAudit allows reading and verifying the audit log.
	PermReadAudit Permission = "audit:read"
)

type Role

type Role string

Role is one of the three single-tenant roles defined by R5 design D5.3.

const (
	// RoleReviewer can read and submit/edit their own labels.
	RoleReviewer Role = "reviewer"
	// RoleAdmin can do everything a reviewer can plus manage users, edit any
	// label, and administer the audit log.
	RoleAdmin Role = "admin"
	// RoleReadonly can read labels but cannot mutate anything.
	RoleReadonly Role = "readonly"
)

func ParseRole

func ParseRole(s string) (Role, error)

ParseRole validates and normalizes a role string.

func (Role) Can

func (r Role) Can(p Permission) bool

Can reports whether the role grants the given permission.

func (Role) Permissions

func (r Role) Permissions() []Permission

Permissions returns the permission set granted to the role. The returned slice is a copy; callers may not mutate the canonical table.

func (Role) Valid

func (r Role) Valid() bool

Valid reports whether r is a recognized role.

type User

type User struct {
	Email     string `yaml:"email"`
	Role      Role   `yaml:"role"`
	TokenHash string `yaml:"token_hash"`
	CreatedAt string `yaml:"created_at"`
}

User is one entry in the admin-managed users file. The token plaintext is never persisted — only TokenHash (argon2id) is stored.

type UsersFile

type UsersFile struct {
	SchemaVersion int    `yaml:"schema_version"`
	Users         []User `yaml:"users"`
}

UsersFile is the on-disk shape of ~/.config/da/review/users.yaml.

func LoadUsersFile

func LoadUsersFile(path string) (*UsersFile, error)

LoadUsersFile reads and parses a users file. A missing file is not an error: it returns an empty, current-schema UsersFile so first-run callers can add the first user without a separate init step.

func (*UsersFile) AddUser

func (uf *UsersFile) AddUser(email string, role Role) (string, error)

AddUser issues a fresh token for a new user, appends the user (storing only the token hash), and returns the issued plaintext token. The caller is responsible for persisting via Save and for displaying the plaintext exactly once. ErrUserExists is returned if the email is already present.

func (*UsersFile) Find

func (uf *UsersFile) Find(email string) (User, bool)

Find returns the user with the given email (case-insensitive) and true, or the zero value and false.

func (*UsersFile) RemoveUser

func (uf *UsersFile) RemoveUser(email string) error

RemoveUser deletes the user with the given email (case-insensitive). It returns ErrUserNotFound if no such user exists.

func (*UsersFile) Save

func (uf *UsersFile) Save(path string) error

Save persists the users file atomically (temp file + rename) so concurrent readers never observe a partially-written file. Parent directories are created as needed, and the file is written 0600 because it holds secrets.

Jump to

Keyboard shortcuts

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