mainbackend

package
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Nov 11, 2026 License: AGPL-3.0 Imports: 58 Imported by: 0

Documentation

Overview

Package mainbackend is the main backend, the process behind `dexafix serve api`. It owns the database, the keys and every security decision: the internal API that the frontend calls, the public API (/api/v1), the importers and the maintenance jobs. It never renders a page, and it does not import the frontend. (The directory is pkg/backend/main; a package cannot be named main and be imported.)

Index

Constants

This section is empty.

Variables

View Source
var NetworkSettingNames = []string{settingBaseURL, settingTrustedProxies, settingProxyProtocol}

NetworkSettingNames are the settings that can make the Admin page unreachable when they are wrong (the base URL, the trusted proxies, the PROXY protocol). `dexafix settings reset-network` clears them.

Functions

func ParseBaseURL

func ParseBaseURL(raw string) (*url.URL, error)

ParseBaseURL checks the public address of the app. See edge.ParseBaseURL.

func ParseTrustedProxies

func ParseTrustedProxies(items []string) ([]netip.Prefix, error)

ParseTrustedProxies turns addresses, CIDR ranges and the keywords "loopback" and "private" into prefixes. See edge.ParseTrustedProxies.

Types

type Backend

type Backend struct {

	// Version is shown on the admin page.
	Version string
	// ForcePasswordLogin keeps password sign-in on regardless of the admin setting, to recover
	// from a lockout. It is a flag because it is needed when nobody can reach the Admin page.
	ForcePasswordLogin bool
	// contains filtered or unexported fields
}

Backend is the half of the application that owns the database, the keys and every security decision. It never renders a page. Nothing in the frontend can reach its fields: the Frontend is another type, and the two meet only in the internal API (see internal.go).

func NewBackend

func NewBackend(st store.Store, opts ...Option) (*Backend, error)

NewBackend builds a backend: the database side only. The internal API serves a frontend (see SetServiceSecrets and InternalHandler), and PublicHandler serves /api/v1.

Any number of backends may share one database; exactly one of them should run the maintenance (StartMaintenance) and the others should be made WithoutCertificateGeneration.

func (*Backend) APIHandler

func (s *Backend) APIHandler() http.Handler

APIHandler serves the public API, authenticated, without the load budget and the edge handling of PublicHandler: the combined process mounts it on the pages' handler, which does both.

func (*Backend) Close

func (s *Backend) Close()

Close cancels the background jobs and waits for them to stop. Call it after the HTTP servers have been shut down.

func (*Backend) DisableInternalBudget

func (s *Backend) DisableInternalBudget()

DisableInternalBudget removes the load budget of the internal API, and of the worker endpoints. The combined process uses it: there is no flood protection between the halves of one process.

func (*Backend) InternalHandler

func (s *Backend) InternalHandler() http.Handler

InternalHandler returns the handler of the internal API.

func (*Backend) OnSettingsChange

func (s *Backend) OnSettingsChange(f func())

OnSettingsChange sets a function that is called after the settings in force change. The combined process uses it to pass a changed setting on to the frontend at once. Set it before serving.

func (*Backend) PublicHandler

func (s *Backend) PublicHandler() http.Handler

PublicHandler serves the public API, /api/v1, and /healthz. Everything else is not found: pages belong to the frontend.

func (*Backend) Ready

func (s *Backend) Ready(ctx context.Context) error

Ready reports whether the backend can do its work: the database answers. It is the readiness check of the health listener. The body service and the workers are not part of it: the pages say so themselves when the body service is down, and everything else keeps working.

func (*Backend) ReloadSettings

func (s *Backend) ReloadSettings(ctx context.Context) error

ReloadSettings reads the administrator settings from the database again and puts them in force. A running backend does this by itself (StartSettingsPoll) when another process changes them; it is for a caller that changed the database behind the backend's back and cannot wait for the poll.

func (*Backend) SetBaseURL

func (s *Backend) SetBaseURL(u *url.URL)

SetBaseURL and SetTrustedProxies put a value in force without storing it. They exist for tests; the application changes these settings through the Admin page.

func (*Backend) SetServiceSecrets

func (s *Backend) SetServiceSecrets(secrets ...string)

SetServiceSecrets sets the secrets the internal API accepts. The first is the one the frontend sends; the others are accepted so that a secret can be rotated without downtime.

func (*Backend) SetTrustedProxies

func (s *Backend) SetTrustedProxies(p []netip.Prefix)

func (*Backend) SetWorkerSecrets

func (s *Backend) SetWorkerSecrets(secrets ...string)

SetWorkerSecrets sets the secrets the worker endpoints accept. The first is the one workers send; the others are accepted so that a secret can be rotated without downtime. Without any, the worker endpoints refuse every call.

func (*Backend) StartMaintenance

func (s *Backend) StartMaintenance()

StartMaintenance purges audit events older than the retention period now and every few hours, until Close. The period is an Admin setting read at every round; zero keeps the log for good.

func (*Backend) StartSettingsPoll

func (s *Backend) StartSettingsPoll()

StartSettingsPoll reloads the settings when another process (a second backend, or the `settings reset-network` command) changed them, until Close.

func (*Backend) WrapListener

func (s *Backend) WrapListener(ln net.Listener) net.Listener

WrapListener makes ln understand the PROXY protocol according to the current settings. Wrap the listener once at start; it costs nothing while the setting is off.

type Option

type Option func(*Backend)

Option changes how NewBackend builds a backend.

func WithBody

func WithBody(c *bodyapi.Client) Option

WithBody gives the backend the body service to call. A backend cannot be built without one.

func WithState

func WithState(v StateServer) Option

WithState keeps the failed-attempt counters, the single-use nonces and the sessions on a server of their own (Valkey) instead of in the database, so that a flood of wrong passwords costs the database nothing and a signed-in visitor costs it no session lookup. It fails closed like the database does: when the server does not answer, every address counts as locked out and every nonce as spent. The server must not evict keys (maxmemory-policy noeviction), or a counter or nonce could vanish and let an attacker try again or replay a value. /status shows it, and Close of the backend does not close it: the caller owns it.

func WithoutCertificateGeneration

func WithoutCertificateGeneration() Option

WithoutCertificateGeneration makes the backend only load the stored self-signed certificate. It never generates or renews one by itself (the maintenance backend does); only an administrator's explicit TLS change on the Admin page still generates one. For backends that run with --maintenance=false.

type SessionState

type SessionState interface {
	CreateSession(ctx context.Context, tokenHash string, userID int64, method string, expires time.Time) error
	GetSession(ctx context.Context, tokenHash string) (models.Session, error)
	DeleteSession(ctx context.Context, tokenHash string) error
	DeleteUserSessions(ctx context.Context, userID int64) error
	CountSessions(ctx context.Context) (int64, error)
}

SessionState holds the sessions on a server of its own (Valkey), in place of the sessions table. It is what store.Store offers for sessions, except that it knows nothing about users: the backend reads the user (name, role, whether they exist) from the database on every request.

type StateServer

type StateServer interface {
	StateStore
	SessionState
	Ping(ctx context.Context) error
}

StateServer is a StateStore and a SessionState on a server of its own, which can also be asked whether it answers.

type StateStore

type StateStore interface {
	RecordFailure(ctx context.Context, key string, at time.Time) error
	CountFailures(ctx context.Context, key string, since time.Time) (int, error)
	ClearFailures(ctx context.Context, key string) error
	UseNonce(ctx context.Context, nonce string, expires time.Time) (first bool, err error)
	NonceSeen(ctx context.Context, nonce string) (bool, error)
}

StateStore holds the state that must agree across backend processes and is touched on every sign-in: the failed-attempt counters and the single-use nonces. The database holds it unless a Valkey server is configured (WithState), which keeps a flood of wrong passwords off the database. store.Store and valkeystate.Client both satisfy it.

Jump to

Keyboard shortcuts

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