local

package
v0.48.0 Latest Latest
Warning

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

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package local implements username + password authentication backed by the users table. Password hashes use bcrypt. Sessions are issued via the sessions table and conveyed by Cookie or Bearer header.

Index

Constants

View Source
const (
	// SessionCookieName is the HTTP cookie used by the local driver.
	SessionCookieName = "filex_session"
	// SessionTTL is how long a freshly-issued session is valid for.
	SessionTTL = 12 * time.Hour
	// BearerPrefix detects API token usage.
	BearerPrefix = "Bearer "
)
View Source
const BootstrapAdminSetting = "auth.bootstrap_admin_id"

BootstrapAdminSetting names the settings row holding the id of the administrator the installation created for itself on first run — the `admin@local` account, or the one FILEX_ADMIN_EMAIL named.

View Source
const RecoveryLoginName = "recovery"

RecoveryLoginName is the chain name of the recovery sign-in driver.

View Source
const SessionAuthenticatorName = "session"

SessionAuthenticatorName is the chain name of the session-only validator. It is deliberately not registered with auth.Register: it is not a driver an operator enables or configures, so the admin auth-providers page must not list it.

Variables

This section is empty.

Functions

func BootstrapAdminID

func BootstrapAdminID(ctx context.Context, store db.Store) (int64, error)

BootstrapAdminID reads the recorded bootstrap administrator. Zero, with no error, means none is recorded.

func EnsureBootstrapAdmin

func EnsureBootstrapAdmin(ctx context.Context, store db.Store) (*model.User, error)

EnsureBootstrapAdmin records the bootstrap administrator on an installation that predates the setting, and reports which account it is.

First run records it as it creates the account. An installation created before that has only `first_run_at`, so the account is recovered the way it was made: the OLDEST administrator that holds a local password. An account a provider created just in time has no password and is skipped — it could not use a password recovery anyway. Nothing is chosen when no administrator has a password; recovery then refuses everyone, and says why in the log.

func HashPassword

func HashPassword(password string) (string, error)

HashPassword returns a bcrypt hash suitable for users.password_hash.

func IssueSession

func IssueSession(ctx context.Context, store db.Store, userID int64) (string, error)

IssueSession mints a `filex_session` token for uid and records it.

⚠ Exported because it is the ONE place a browser session comes into existence, and more than one login driver has to be able to mint one. The LDAP driver used to end its Login with `return user, "", nil` and the comment "caller mints session token via the local driver" — no caller ever did, so a directory login that had already succeeded handed back an EMPTY token: the cookie was set to "", the very next request had no session, and the failure looked like a login failure. A second implementation would have been worse still: the cookie name and the 12h TTL are a contract the middleware and the SPA both depend on, and two copies of a TTL drift.

func RecordBootstrapAdmin

func RecordBootstrapAdmin(ctx context.Context, store db.Store, id int64) error

RecordBootstrapAdmin stores id as the bootstrap administrator.

func RevokeSession

func RevokeSession(ctx context.Context, store db.Store, token string) error

RevokeSession deletes a session token. The counterpart to IssueSession, for the same reason: a driver that can mint must be able to revoke.

Types

type Driver

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

Driver is the local-DB password auth driver.

func New

func New(store db.Store) *Driver

New constructs an empty driver — must be Init'd before use.

func (*Driver) Authenticate

func (d *Driver) Authenticate(r *http.Request) (*model.User, error)

Authenticate looks for either a session cookie or an `Authorization: Bearer …` token, validates it against sessions table, and resolves the user. Returns auth.ErrUnauthorized when no credentials present.

func (*Driver) Capabilities

func (d *Driver) Capabilities() auth.Capabilities

Capabilities implements auth.Driver.

func (*Driver) Init

func (d *Driver) Init(_ context.Context, _ map[string]any) error

Init currently has no config of its own.

func (*Driver) Login

func (d *Driver) Login(ctx context.Context, email, password string) (*model.User, string, error)

Login validates an identifier + password and returns a freshly created session token.

The identifier is an e-mail OR a username (migration 00025): identity.Resolve owns that decision so this surface cannot drift from WebDAV, SFTP or FTPS. The parameter keeps its old name because every caller passes what the user typed into a field labelled "e-mail or username", and renaming it would touch the whole auth.LoginDriver interface for no behaviour change. ⚠ The four ways this can fail all answer the caller with the SAME auth.ErrUnauthorized, which the handler turns into one unhelpful `401 invalid credentials`. That is deliberate and must stay: telling an anonymous caller "no such account" apart from "wrong password" is account enumeration, and it is the whole reason identity.Resolve reports a malformed username as ErrNotFound too.

What was NOT deliberate is that the SERVER could not tell either. A Cypress run once saw every login answer 401 with nothing in the log to say why, and it could not be reproduced — because a store failure (a locked sqlite file, a dropped postgres connection) came back through exactly the same return statement as a typo in a password. So each case now names itself in the log, at a level that matches who is at fault:

debug  the caller got it wrong — a typo is not an operator's problem, and
       an unauthenticated endpoint must not let a stranger fill the log
info   a real account exists but has no local password (directory/OIDC
       account) — worth seeing while an operator debugs a rollout
error  the server could not judge at all — a store failure or an unusable
       stored hash, which is nobody's login attempt going wrong

The store failure is ALSO returned as itself rather than as ErrUnauthorized: auth.LoginChain treats "not ErrUnauthorized" as "this driver could not judge" and reports it, so an operator reading the log can tell a wrong password from a database that is down. The client still gets its single 401 either way — handlers.Auth.Login answers 401 for any error.

func (*Driver) Logout

func (d *Driver) Logout(ctx context.Context, token string) error

Logout revokes the given session.

func (*Driver) Name

func (d *Driver) Name() string

Name implements auth.Driver.

type RecoveryLogin

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

RecoveryLogin lets ONE account sign in with its password when password sign-in is otherwise switched off: the bootstrap administrator.

Why it exists

An installation that signs people in through an identity provider alone (`FILEX_AUTH_DRIVERS=oidc`) is locked the moment that provider is down, its client secret expires or its realm is misconfigured — and the one person who could fix filex's side of it cannot get in to do so. The owner's ruling (2026-09-14): the administrator created at installation must always be able to sign in, for recovery purposes only.

What it deliberately does not do

  • It is added only when no `local` driver is enabled; with `local` in the list every local account already signs in and this adds nothing.
  • It accepts nobody but the bootstrap administrator. Every other account — including another administrator with a local password — is refused exactly as if the driver were not there, so turning password sign-in off still means what it says for everyone else.
  • Two-factor still applies: the login handler checks TOTP after any login driver, this one included.
  • An operator whose policy forbids any password sign-in turns it off with `FILEX_AUTH_RECOVERY_LOGIN=false`.

Every recovery sign-in is logged at WARN, because on an SSO-only server it is by definition an event somebody should know happened.

func NewRecoveryLogin

func NewRecoveryLogin(store db.Store) *RecoveryLogin

NewRecoveryLogin returns the recovery sign-in driver over store.

func (*RecoveryLogin) Login

func (r *RecoveryLogin) Login(ctx context.Context, identifier, password string) (*model.User, string, error)

Login implements auth.LoginDriver for the bootstrap administrator only.

func (*RecoveryLogin) Logout

func (r *RecoveryLogin) Logout(ctx context.Context, token string) error

Logout implements auth.LoginDriver.

func (*RecoveryLogin) Name

func (r *RecoveryLogin) Name() string

Name reports the driver's chain name.

type SessionAuthenticator

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

SessionAuthenticator turns the session a request carries into its user — the half of this driver that has nothing to do with passwords.

Every login driver mints its session through IssueSession, into one table, behind one cookie; this is what reads it back. It exists apart from Driver so the server can validate sessions on an installation that has turned password sign-in off (`FILEX_AUTH_DRIVERS=oidc`, issue #24) without turning it back on: it implements auth.Driver and nothing else, never auth.LoginDriver.

func NewSessionAuthenticator

func NewSessionAuthenticator(store db.Store) *SessionAuthenticator

NewSessionAuthenticator returns a validator over the shared sessions table.

func (*SessionAuthenticator) Authenticate

func (s *SessionAuthenticator) Authenticate(r *http.Request) (*model.User, error)

Authenticate implements auth.Driver: the same session lookup the local driver performs, one implementation.

func (*SessionAuthenticator) Capabilities

func (s *SessionAuthenticator) Capabilities() auth.Capabilities

Capabilities implements auth.Driver. It signs nobody in.

func (*SessionAuthenticator) Init

func (s *SessionAuthenticator) Init(ctx context.Context, cfg map[string]any) error

Init implements auth.Driver.

func (*SessionAuthenticator) Name

func (s *SessionAuthenticator) Name() string

Name implements auth.Driver.

Jump to

Keyboard shortcuts

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