Documentation
¶
Overview ¶
Package auth password-protects the dashboard and API.
The model follows LAN Orangutan's, so that the two siblings behave the same way: a fresh install has no password; when the server is reachable from the network, the first visitor must create one before anything is shown; after that a password is exchanged for a session cookie.
Bound to loopback only, no password is required, a dashboard nothing else can reach is already private, and demanding a password would be friction with no benefit. That default is what keeps the zero-configuration promise intact.
This differs from Orangutan in one respect: LAN Sheriff's front end is a single-page app, so setup and login are JSON endpoints the app calls rather than server-rendered form pages with redirects.
Index ¶
- Constants
- Variables
- func IsHash(s string) bool
- func LoadHash(path string) string
- func SaveHash(path, hash string) error
- func ValidatePassword(password string) error
- type Authenticator
- func (a *Authenticator) Authenticated(token string) bool
- func (a *Authenticator) Enabled() bool
- func (a *Authenticator) LockedOut(remoteAddr string) bool
- func (a *Authenticator) Login(remoteAddr, password string) (string, bool)
- func (a *Authenticator) Logout(token string)
- func (a *Authenticator) NeedsSetup() bool
- func (a *Authenticator) SessionTTL() time.Duration
- func (a *Authenticator) SetInitialPassword(plaintext string) (string, error)
- func (a *Authenticator) SetPassword(plaintext string) (string, error)
- func (a *Authenticator) SetSetupRequired(required bool)
- func (a *Authenticator) StartSession() (string, bool)
Constants ¶
const ( // SessionCookie holds the session token. SessionCookie = "sheriff_session" // MinPasswordLength is the shortest password setup accepts. MinPasswordLength = 8 // MaxPasswordBytes is bcrypt's own limit. It is checked here so the failure // can be explained in plain terms rather than surfacing the library's // "password length exceeds 72 bytes". MaxPasswordBytes = 72 // DefaultSessionTTL is how long a login lasts. DefaultSessionTTL = 7 * 24 * time.Hour )
Variables ¶
var ErrPasswordAlreadySet = errors.New("a password has already been set")
ErrPasswordAlreadySet reports that a password existed by the time a first-run setup got as far as storing one.
Functions ¶
func LoadHash ¶
LoadHash reads a stored password hash. A missing file is not an error: it means no password has been set.
func ValidatePassword ¶
ValidatePassword reports whether a proposed password is acceptable.
Types ¶
type Authenticator ¶
type Authenticator struct {
// contains filtered or unexported fields
}
Authenticator guards the API with a password.
func New ¶
func New(password string, sessionTTL time.Duration) (*Authenticator, error)
New creates an Authenticator. password may be a plaintext password or an existing bcrypt hash, so a stored hash can be passed straight through.
func (*Authenticator) Authenticated ¶
func (a *Authenticator) Authenticated(token string) bool
Authenticated reports whether a request carries a valid, unexpired session.
func (*Authenticator) Enabled ¶
func (a *Authenticator) Enabled() bool
Enabled reports whether a password is currently set.
func (*Authenticator) LockedOut ¶
func (a *Authenticator) LockedOut(remoteAddr string) bool
LockedOut reports whether an address has failed too many logins recently.
func (*Authenticator) Login ¶
func (a *Authenticator) Login(remoteAddr, password string) (string, bool)
Login verifies a password and, on success, returns a new session token.
func (*Authenticator) Logout ¶
func (a *Authenticator) Logout(token string)
Logout invalidates a session token.
func (*Authenticator) NeedsSetup ¶
func (a *Authenticator) NeedsSetup() bool
NeedsSetup reports whether a password still has to be created.
func (*Authenticator) SessionTTL ¶
func (a *Authenticator) SessionTTL() time.Duration
SessionTTL is how long a login lasts.
func (*Authenticator) SetInitialPassword ¶
func (a *Authenticator) SetInitialPassword(plaintext string) (string, error)
SetInitialPassword establishes the *first* password, and only if there is not one already. This is what first-run setup must call.
The check and the assignment happen under a single lock, which is the entire point of the method. Setup used to be a NeedsSetup call in the HTTP handler followed by a separate SetPassword, and those are two acquisitions with a gap between them. Twelve simultaneous setup requests all passed the check before any of them assigned: every one of the twelve was answered with success, the last to finish owned the password, and whoever arrived first was handed a session cookie that quietly stopped working some minutes later.
On a bind reachable from the network that gap is the difference between two very different models. "Whoever gets there first wins" is the documented one, and it is defensible: the dashboard is unreachable until someone claims it. "Whoever is still in flight last wins" is what the code actually did, and it lets a stranger who merely holds requests open take an install away from the person sitting in front of the machine, while telling that person setup succeeded.
bcrypt runs before the lock is taken rather than inside it. It costs roughly a tenth of a second, and holding the mutex across it would park every session check in the server behind an unauthenticated stranger's request, which is a denial of service offered up for free. The cost is that a loser has burned a hash for nothing, and that is exactly the right thing to waste.
func (*Authenticator) SetPassword ¶
func (a *Authenticator) SetPassword(plaintext string) (string, error)
SetPassword replaces the password unconditionally, and returns its hash so the caller can persist it.
For *changing* a password that already exists. First-run setup must use SetInitialPassword instead: this one overwrites whatever is there, so on the setup path it is the race described above. Any caller added here has to establish that the person asking already holds the current password.
func (*Authenticator) SetSetupRequired ¶
func (a *Authenticator) SetSetupRequired(required bool)
SetSetupRequired controls what happens when no password is set: either the user must create one, or access is open.
func (*Authenticator) StartSession ¶
func (a *Authenticator) StartSession() (string, bool)
StartSession issues a session without checking a password, so that completing setup signs the user in rather than bouncing them to a login form for the password they just chose.