Help us shape the future of Go! Take the 2026 Go Developer Survey

auth

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 16, 2026 License: GPL-3.0 Imports: 13 Imported by: 0

Documentation

Overview

Package auth holds the server's credentials: how a password is hashed, how a random token is minted, and how any of it is compared.

It is a leaf package on purpose. Every comparison in here is constant time, every random value comes from crypto/rand, and no secret is ever stored in the form it was presented in — a password becomes an argon2id digest, a token becomes its SHA-256. A database dump therefore contains nothing that can be replayed, which is the property the whole design is for.

Two kinds of credential live here and they are not interchangeable:

  • An administrator signs in to the admin panel with an email and a password, and gets a session cookie.
  • A driver's machine never has a password. It is paired once and holds a 256-bit device token.

Index

Constants

View Source
const CSRFTokenBytes = 32

CSRFTokenBytes is the entropy of a cross-site request forgery token.

View Source
const DeviceCodeBytes = 32

DeviceCodeBytes is the entropy of the device code a client polls with: 256 bits, the same as the token it is exchanged for. It is a secret, so it is stored the way every secret here is — as its SHA-256.

View Source
const DevicePrefixLen = 8

DevicePrefixLen is how much of a device token is kept in clear text, purely so a lookup is an index seek rather than a scan of every row. Eight characters of base64 is 48 bits, which narrows a lookup to one row in practice and is far too little to reconstruct anything.

View Source
const DeviceTokenBytes = 32

DeviceTokenBytes is the entropy of a device token: 256 bits. It is never guessed, only stolen, so the length is chosen to end the conversation about brute force rather than to be convenient.

View Source
const MaxPasswordLen = 1024

MaxPasswordLen caps what is hashed, because argon2id will happily chew through a megabyte of input and an unauthenticated form must not be able to ask it to.

View Source
const MinPasswordLen = 12

MinPasswordLen is the shortest password the server accepts. Length is the only rule: composition rules push people towards "Passw0rd!" and buy nothing.

View Source
const SecretKeyBytes = 32

SecretKeyBytes is the length of the data key that encrypts the operator's own API keys at rest: AES-256.

View Source
const SessionTokenBytes = 32

SessionTokenBytes is the entropy of an admin session cookie.

View Source
const SetupTokenBytes = 20

SetupTokenBytes is the entropy of the one-time setup token: 160 bits, which encodes as exactly 32 characters of the alphabet above with nothing left over.

View Source
const SetupTokenGroup = 4

SetupTokenGroup is how many characters sit between the hyphens.

View Source
const UserCodeGroup = 3

UserCodeGroup is how many characters sit before the hyphen: "H4T-9KQ".

View Source
const UserCodeLen = 6

UserCodeLen is how many characters the driver reads out to the operator. Six characters of the 32-letter alphabet below is thirty bits, which is far more than a code that lives for ten minutes behind a rate limiter needs, and short enough to say down a voice channel without repeating it.

Variables

View Source
var DefaultParams = Params{Memory: 64 * 1024, Time: 3, Threads: 2, SaltLen: 16, KeyLen: 32}

DefaultParams is 64 MiB over three passes, comfortably above the OWASP minimum for argon2id. It costs roughly a tenth of a second on a laptop, which is what makes an offline attack on a stolen database expensive.

View Source
var ErrInvalidHash = errors.New("auth: stored password hash is not readable")

ErrInvalidHash is returned when a stored hash cannot be parsed. It means the row was corrupted or written by something else, not that the password was wrong, and it must never be reported to the person signing in as either.

View Source
var ErrMalformedToken = errors.New("auth: token is not the right shape")

ErrMalformedToken is returned when a presented token is not even the right shape. It is worth distinguishing from "no such token" only in a log line; the caller is told the same thing either way.

View Source
var ErrNoSecretKey = errors.New("auth: no secret key, so the stored API key cannot be read")

ErrNoSecretKey is returned when something sealed needs opening and no key is available — the usual cause being a data directory that was lost while the database survived. It is not corruption: the operator retypes the API key and the feature comes back.

View Source
var ErrSealed = errors.New("auth: the stored value does not open with this key")

ErrSealed is returned when a sealed value will not open with the key given.

Functions

func CheckPasswordStrength

func CheckPasswordStrength(password string) error

CheckPasswordStrength reports whether a password may be used, phrased for the person who has to choose another one.

func EqualBytes

func EqualBytes(a, b []byte) bool

EqualBytes compares two digests in constant time.

func EqualSetupToken

func EqualSetupToken(want, got string) bool

EqualSetupToken compares a typed token against the real one in constant time, after normalising both.

func EqualString

func EqualString(a, b string) bool

EqualString compares two secrets in constant time. Length is not secret, so leaking it through an early return is fine; which byte differs is, so the comparison itself must not stop at the first difference.

func HashPassword

func HashPassword(password string) (string, error)

HashPassword hashes a password with DefaultParams and returns the PHC string that is stored in the database.

func HashPasswordWith

func HashPasswordWith(p Params, password string) (string, error)

HashPasswordWith hashes a password with explicit parameters. Tests use it to stay fast; the server uses HashPassword.

func HashToken

func HashToken(plain string) []byte

HashToken is the digest a token is stored and looked up by.

func MaxConcurrentHashes

func MaxConcurrentHashes() int

MaxConcurrentHashes is how many password hashes may run at once.

func NeedsRehash

func NeedsRehash(encoded string) bool

NeedsRehash reports whether a stored hash was made with weaker parameters than DefaultParams and should be rewritten at the next successful sign-in.

func NewCSRFToken

func NewCSRFToken() (string, error)

NewCSRFToken mints a cross-site request forgery token.

func NewSessionToken

func NewSessionToken() (plain string, sum []byte, err error)

NewSessionToken mints an admin session token and returns it with its digest. The cookie carries the plain value; the database holds only the digest, so a dump cannot be used to sign in as anyone.

func NewSetupToken

func NewSetupToken() (string, error)

NewSetupToken mints the token printed on the terminal at first run, hyphenated into groups so it can be read aloud and typed without losing one's place.

func NewUserCode

func NewUserCode() (string, error)

NewUserCode mints the short code on its own, for the case where a collision forces a second attempt.

func NormaliseSetupToken

func NormaliseSetupToken(s string) string

NormaliseSetupToken puts a token the operator typed into the one spelling the comparison uses: upper case, no hyphens or spaces, and with the four characters the alphabet leaves out folded onto the ones they are mistaken for. Someone who types a lower-case "l" for a "1" gets in; someone who types the wrong token does not.

func NormaliseUserCode

func NormaliseUserCode(s string) string

NormaliseUserCode puts a code an operator typed into the one spelling the comparison uses, folding the four characters the alphabet leaves out onto the ones they are misread as. It is NormaliseSetupToken under a name that says what it is being used for.

func SplitDeviceToken

func SplitDeviceToken(plain string) (sum []byte, prefix string, err error)

SplitDeviceToken turns a presented token into the two things the database is queried with: its lookup prefix and its digest. It is the only way a token from a request becomes a row, so the hashing cannot be forgotten at a call site.

func VerifyPassword

func VerifyPassword(encoded, password string) (bool, error)

VerifyPassword reports whether password matches the stored hash. The comparison is constant time, and a hash that cannot be parsed is ErrInvalidHash rather than a quiet false, so a corrupted row shows up as a fault instead of as everyone's password being wrong.

Types

type DeviceToken

type DeviceToken struct {
	Plain  string
	Sum    []byte
	Prefix string
}

DeviceToken is a freshly minted device credential in its three forms.

Plain is shown to the client once, at pairing, and never again — it is not stored anywhere. Sum is what the database holds. Prefix is the clear-text fragment the lookup index is built on.

func NewDeviceToken

func NewDeviceToken() (DeviceToken, error)

NewDeviceToken mints a device token.

type Keyring

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

Keyring holds the data key in a place the whole process shares, so that an operator who regenerates it in the admin panel does not have to restart the server for the API to notice.

Without it the key would be a value captured when the handlers were built, and a regenerated key would leave every sealed credential unreadable until a restart — which is the one thing the danger zone must not do quietly.

func NewKeyring

func NewKeyring(k SecretKey) *Keyring

NewKeyring holds k. A nil key is valid: it is a server whose configuration file carries no data key, and sealed values simply do not open.

func (*Keyring) Key

func (r *Keyring) Key() SecretKey

Key is the data key in force right now. It is safe on a nil receiver, which is what a caller that was given no keyring has.

func (*Keyring) Replace

func (r *Keyring) Replace(k SecretKey)

Replace swaps in a new data key. Callers must have re-sealed everything that was sealed with the old one first: this call makes the old ciphertext unreadable everywhere at once.

type PairingCodes

type PairingCodes struct {
	DeviceCode    string
	DeviceCodeSum []byte
	UserCode      string
}

PairingCodes is one device-code grant in the three forms the flow needs.

DeviceCode is returned to the client once and never stored; DeviceCodeSum is what the pairings row holds. UserCode is the short code the driver reads out and an operator matches in the admin panel, and it is not a secret — it is useless without the device code.

func NewPairingCodes

func NewPairingCodes() (PairingCodes, error)

NewPairingCodes mints one pairing's codes.

type Params

type Params struct {
	// Memory is the memory cost in KiB.
	Memory uint32
	// Time is the number of passes.
	Time uint32
	// Threads is the parallelism.
	Threads uint8
	// SaltLen and KeyLen are in bytes.
	SaltLen, KeyLen uint32
}

Params are the argon2id cost parameters. They are encoded into every hash, so raising them later does not invalidate the passwords already stored: an old hash still verifies against the parameters it was made with, and is rewritten at the next successful sign-in.

type SecretKey

type SecretKey []byte

SecretKey encrypts the values that must not be readable in a database dump — today every one of them belongs to a plugin: the core holds no vendor credential of its own.

It lives in the configuration file beside the binary, not in the database, which is the whole point: a dump of the database contains the ciphertext and nothing that opens it. The cost is that losing the data directory loses the stored API key, and the server says so and asks for it again rather than pretending the feature is on.

func NewSecretKey

func NewSecretKey() (SecretKey, error)

NewSecretKey mints a data key.

func ParseSecretKey

func ParseSecretKey(s string) (SecretKey, error)

ParseSecretKey reads a key back out of the configuration file. An empty string is not an error: it is a server that has never been given one.

func (SecretKey) Open

func (k SecretKey) Open(sealed []byte) (string, error)

Open decrypts a value produced by SecretKey.Seal. An empty input is an empty result, which is how "no key was ever stored" is spelled.

func (SecretKey) Seal

func (k SecretKey) Seal(plaintext string) ([]byte, error)

Seal encrypts plaintext with AES-256-GCM and returns the nonce followed by the ciphertext. Each call uses a fresh nonce, so sealing the same value twice gives two different results and a dump leaks nothing by comparison.

func (SecretKey) String

func (k SecretKey) String() string

String renders the key for the configuration file.

Jump to

Keyboard shortcuts

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