admin

package
v0.9.4 Latest Latest
Warning

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

Go to latest
Published: Jun 10, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Index

Constants

View Source
const (
	RoleAdmin = "admin"
	RoleUser  = "user"
)

User roles.

Variables

This section is empty.

Functions

func DeriveKey added in v0.8.8

func DeriveKey(master, purpose string) []byte

DeriveKey derives a purpose-specific key from a master secret using HMAC-SHA256.

func FormatUptime

func FormatUptime(since time.Time) string

FormatUptime returns a human-readable duration string.

Types

type AuditEntry added in v0.8.0

type AuditEntry struct {
	Timestamp time.Time `json:"timestamp"`
	Actor     string    `json:"actor"`
	Action    string    `json:"action"`
	Detail    string    `json:"detail"`
}

AuditEntry represents a single audit log event.

type AuditLog added in v0.8.0

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

AuditLog is an append-only NDJSON audit log: one JSON object per line, appended with O(1) cost (no whole-file rewrites). The most recent entries are also kept in memory (newest first) to serve the admin UI cheaply.

func NewAuditLog added in v0.8.0

func NewAuditLog(path string) (*AuditLog, error)

NewAuditLog loads or creates an audit log at the given path. A legacy JSON-array file (from older versions) is transparently migrated to NDJSON.

func (*AuditLog) Count added in v0.8.0

func (a *AuditLog) Count() int

Count returns the number of recent entries available to the UI.

func (*AuditLog) Entries added in v0.8.0

func (a *AuditLog) Entries(limit, offset int) []AuditEntry

Entries returns recent audit entries (newest first) with offset-based pagination, served from the in-memory ring.

func (*AuditLog) Facets added in v0.8.19

func (a *AuditLog) Facets() (actors, actions []string)

Facets returns the distinct actors and actions present in the window, sorted, for populating filter controls.

func (*AuditLog) Log added in v0.8.0

func (a *AuditLog) Log(actor, action, detail string)

Log records a new audit event. Newest entries are first in the UI.

func (*AuditLog) Query added in v0.8.19

func (a *AuditLog) Query(limit, offset int, actor, action string) (entries []AuditEntry, total int)

Query returns recent entries (newest first) matching the optional actor and action filters (empty = any), with offset-based pagination, plus the total number of matching entries in the window (for pagination).

type ClientView

type ClientView struct {
	Addr    string
	Uptime  string // human-readable, e.g. "2h 15m"
	Version string
}

ClientView holds display info for a single connected client.

type EpochFunc added in v0.9.2

type EpochFunc func(userID string) (epoch int, exists bool)

EpochFunc returns a user's current session revocation epoch and whether the user still exists. Sessions whose embedded epoch is older than the current one — or whose user no longer exists — are rejected, giving real server-side revocation on logout, passkey reset, role change, and deletion. A nil EpochFunc disables the check (used in tests without a user store).

type Flash

type Flash struct {
	Type    string // "success", "danger", "warning", "info"
	Message string
}

Flash is a one-time notification message.

type Handlers

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

Handlers holds the admin panel HTTP handlers.

func NewHandlers

func NewHandlers(version string, checkInterval time.Duration, templateFS fs.FS) (*Handlers, error)

NewHandlers creates admin panel handlers.

func (*Handlers) Render

func (h *Handlers) Render(w http.ResponseWriter, page string, data *PageData)

Render renders a page template inside the base layout. The page parameter is the template path, e.g. "pages/login.html".

func (*Handlers) RenderPartial

func (h *Handlers) RenderPartial(w http.ResponseWriter, page, block string, data any)

RenderPartial renders a named template block without the base layout.

type HostCert

type HostCert struct {
	Hostname string
	Valid    bool
	Expiry   string // formatted date, e.g. "May 15, 2026"
	Error    string // non-empty when cert is missing or invalid
}

HostCert holds TLS certificate status for a single hostname.

type PageData

type PageData struct {
	Title           string
	Active          string
	Version         string
	CheckIntervalMS int64
	CSRFToken       string
	Flash           *Flash
	Data            any
	UserID          string // logged-in user's opaque id
	Name            string // logged-in user's display label
	IsAdmin         bool   // logged-in user is an admin (drives nav gating)
}

PageData is the template rendering context.

type PasskeyView

type PasskeyView struct {
	Name       string
	CreatedAt  string
	LastUsedAt string
	IDB64      string // base64url-encoded credential ID
}

PasskeyView represents a passkey for display in templates.

type SessionManager

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

SessionManager handles JWT-based sessions.

func NewSessionManager

func NewSessionManager(secret string) *SessionManager

NewSessionManager creates a new session manager with a purpose-derived key.

func (*SessionManager) CSRFToken

func (sm *SessionManager) CSRFToken(r *http.Request) string

CSRFToken generates a CSRF token bound to the session's stable identity, not the (frequently re-issued) signed cookie value. New sessions bind to the SID; legacy sessions without a SID fall back to the actor. Either way the token is stable across keepalive re-issues, so it stays valid across open tabs. Returns empty string if there is no valid session.

func (*SessionManager) ClearSession

func (sm *SessionManager) ClearSession(w http.ResponseWriter)

ClearSession removes the session cookie.

func (*SessionManager) CreateSession

func (sm *SessionManager) CreateSession(w http.ResponseWriter, userID, role string) error

CreateSession starts a new session (fresh SID) for a login as the given user, stamping it with the user's current revocation epoch.

func (*SessionManager) GetActor added in v0.8.0

func (sm *SessionManager) GetActor(r *http.Request) string

GetActor returns the user ID from the session JWT (for the audit log).

func (*SessionManager) IsAdmin added in v0.9.0

func (sm *SessionManager) IsAdmin(r *http.Request) bool

IsAdmin reports whether the request has a valid admin session.

func (*SessionManager) RefreshSession added in v0.8.17

func (sm *SessionManager) RefreshSession(w http.ResponseWriter, r *http.Request) error

RefreshSession re-issues the current session with a bumped expiry, PRESERVING its user, role, and SID. Used by the sliding-window keepalive so the CSRF token (bound to the SID) does not change underneath other open tabs.

func (*SessionManager) Role added in v0.9.0

func (sm *SessionManager) Role(r *http.Request) string

Role returns the logged-in user's role, or "" if no valid session.

func (*SessionManager) SetEpochSource added in v0.9.2

func (sm *SessionManager) SetEpochSource(fn EpochFunc)

SetEpochSource wires the revocation-epoch lookup (typically UserStore.Epoch).

func (*SessionManager) UserID added in v0.9.0

func (sm *SessionManager) UserID(r *http.Request) string

UserID returns the logged-in user's ID, or "" if no valid session.

func (*SessionManager) ValidCSRFToken

func (sm *SessionManager) ValidCSRFToken(r *http.Request, token string) bool

ValidCSRFToken checks that a submitted CSRF token matches the session.

func (*SessionManager) ValidateSession

func (sm *SessionManager) ValidateSession(r *http.Request) bool

ValidateSession checks if the request has a valid session.

type StoredCredential

type StoredCredential struct {
	ID             []byte    `json:"id"`
	PublicKey      []byte    `json:"public_key"`
	Name           string    `json:"name"`
	AAGUID         []byte    `json:"aaguid"`
	SignCount      uint32    `json:"sign_count"`
	CreatedAt      time.Time `json:"created_at"`
	LastUsedAt     time.Time `json:"last_used_at"`
	Transport      []string  `json:"transport"`
	AttType        string    `json:"att_type"`
	BackupEligible bool      `json:"backup_eligible"`
	BackupState    bool      `json:"backup_state"`
}

StoredCredential is a single WebAuthn passkey credential on disk.

type TunnelSession added in v0.9.0

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

TunnelSession manages per-hostname authenticated sessions for protected tunnels, established via the cross-host handoff from the admin login.

func NewTunnelSession added in v0.9.0

func NewTunnelSession(secret string) *TunnelSession

NewTunnelSession creates a tunnel session manager with a purpose-derived key.

func (*TunnelSession) CreateSession added in v0.9.0

func (t *TunnelSession) CreateSession(w http.ResponseWriter, hostname, userID, role string) error

CreateSession sets a tunnel session cookie scoped to the hostname, recording the authenticated user.

func (*TunnelSession) SetEpochSource added in v0.9.2

func (t *TunnelSession) SetEpochSource(fn EpochFunc)

SetEpochSource wires the revocation-epoch lookup (typically UserStore.Epoch), so logout / passkey reset / role change / deletion also kill tunnel sessions.

func (*TunnelSession) ValidateSession added in v0.9.0

func (t *TunnelSession) ValidateSession(r *http.Request, hostname string) (userID, role string, ok bool)

ValidateSession returns the authenticated user for a tunnel hostname, if any.

type TunnelView

type TunnelView struct {
	ID             string
	Type           string
	Hostnames      []string
	ListenPort     int
	PreserveHost   bool
	TLSPassthrough bool
	IPPolicy       string
	AuthPolicy     string
	Connected      bool
	ClientCount    int
	Clients        []ClientView
	Requests       int64
	BytesIn        int64
	BytesOut       int64
	ActiveConns    int32
	Token          string
	HostCerts      []HostCert
	ServerVersion  string
}

TunnelView is the template data for a single tunnel row.

func (TunnelView) BytesInFmt

func (t TunnelView) BytesInFmt() string

BytesInFmt formats bytes in as a human-readable string.

func (TunnelView) BytesOutFmt

func (t TunnelView) BytesOutFmt() string

BytesOutFmt formats bytes out as a human-readable string.

func (TunnelView) ClientSummary

func (t TunnelView) ClientSummary() string

ClientSummary returns a tooltip-friendly summary of all clients with uptime and version. The output is sorted by address for a stable, deterministic tooltip.

func (TunnelView) HostnamesCSV

func (t TunnelView) HostnamesCSV() string

HostnamesCSV returns hostnames as a comma-separated string.

func (TunnelView) IsClientOutdated added in v0.8.1

func (t TunnelView) IsClientOutdated() bool

IsClientOutdated returns true if any connected client has a version different from the server.

type User added in v0.9.0

type User struct {
	ID            string             `json:"id"`
	Name          string             `json:"name"`
	Role          string             `json:"role"`
	Credentials   []StoredCredential `json:"credentials"`
	InviteToken   string             `json:"invite_token,omitempty"`
	InviteExpires time.Time          `json:"invite_expires,omitempty"`
	CreatedAt     time.Time          `json:"created_at"`

	// SessionEpoch is a monotonically increasing revocation counter. Every issued
	// session JWT carries the epoch current at mint time; a session whose epoch is
	// older than this is rejected. Bumping it (logout, passkey reset, role change)
	// invalidates all of the user's outstanding sessions across devices, which is
	// what makes logout and credential reset actually revoke a stolen cookie.
	SessionEpoch int `json:"session_epoch,omitempty"`
}

User is a member of the gatecrash directory. The ID is an opaque, immutable GUID — it is what passkeys, sessions, and access policies bind to, so it can never change. Name is a free, renameable display label. Users authenticate with one or more passkeys.

func (*User) CredentialName added in v0.9.3

func (u *User) CredentialName(id []byte) string

CredentialName returns the label of the credential with the given ID, or "" if the user has no such credential. Used to record which passkey was used.

func (*User) HasPasskeys added in v0.9.0

func (u *User) HasPasskeys() bool

func (*User) InviteActive added in v0.9.0

func (u *User) InviteActive(now time.Time) bool

func (*User) IsAdmin added in v0.9.0

func (u *User) IsAdmin() bool

type UserStore added in v0.9.0

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

UserStore persists the user directory to a JSON file. It is safe for concurrent use.

func NewUserStore added in v0.9.0

func NewUserStore(path string) (*UserStore, error)

NewUserStore loads or creates a user store at the given path.

func (*UserStore) AddCredential added in v0.9.0

func (s *UserStore) AddCredential(userID string, c StoredCredential) error

AddCredential appends a passkey to a user and consumes any pending invite.

func (*UserStore) All added in v0.9.0

func (s *UserStore) All() []*User

All returns copies of every user, sorted by ID (admins first).

func (*UserStore) BootstrapInvite added in v0.9.1

func (s *UserStore) BootstrapInvite() (string, error)

BootstrapInvite ensures there is a first admin awaiting passkey registration and returns its active invite token. It returns "" when an admin already has a passkey (already initialized — no bootstrap needed). Idempotent: it reuses an existing pending bootstrap admin and a still-valid invite, only minting a new token when none is active. This replaces an open first-login page: the only way to claim the first admin is the link printed to the logs / config file.

func (*UserStore) BumpEpoch added in v0.9.2

func (s *UserStore) BumpEpoch(id string) error

BumpEpoch increments a user's session epoch, invalidating all of their outstanding sessions, and persists it. A missing user is a no-op.

func (*UserStore) Create added in v0.9.0

func (s *UserStore) Create(name, role string) (id, inviteToken string, err error)

Create adds a new user with the given display name and role, assigning an opaque GUID id, and issues an invite token. Returns the new id and the plaintext invite token (shown once).

func (*UserStore) Delete added in v0.9.0

func (s *UserStore) Delete(id string) error

Delete removes a user. The last admin cannot be removed.

func (*UserStore) Epoch added in v0.9.2

func (s *UserStore) Epoch(id string) (int, bool)

Epoch returns a user's current session revocation epoch and whether the user exists. It is the source consulted by SessionManager/TunnelSession to reject stale (logged-out / reset / re-roled) or deleted-user sessions.

func (*UserStore) FindByCredentialID added in v0.9.0

func (s *UserStore) FindByCredentialID(credID []byte) *User

FindByCredentialID returns the user who owns the given credential (for usernameless/discoverable login).

func (*UserStore) FindByInvite added in v0.9.0

func (s *UserStore) FindByInvite(token string) *User

FindByInvite returns the user holding a matching, unexpired invite token.

func (*UserStore) Get added in v0.9.0

func (s *UserStore) Get(id string) *User

Get returns a copy of the user with the given ID, or nil.

func (*UserStore) NeedsSetup added in v0.9.0

func (s *UserStore) NeedsSetup() bool

NeedsSetup reports whether the directory has no admin yet (first boot).

func (*UserStore) RemoveCredential added in v0.9.0

func (s *UserStore) RemoveCredential(userID string, credID []byte) error

RemoveCredential deletes a passkey from a user. It won't remove the last passkey of the last admin (which would lock everyone out).

func (*UserStore) Rename added in v0.9.1

func (s *UserStore) Rename(id, name string) error

Rename changes a user's display label. The id is immutable.

func (*UserStore) Reset added in v0.9.0

func (s *UserStore) Reset(id string) (inviteToken string, err error)

Reset clears a user's passkeys and issues a fresh invite token.

func (*UserStore) SetRole added in v0.9.0

func (s *UserStore) SetRole(id, role string) error

SetRole changes a user's role. The last admin cannot be demoted.

func (*UserStore) UpdateSignCount added in v0.9.0

func (s *UserStore) UpdateSignCount(userID string, credID []byte, count uint32)

UpdateSignCount records a credential's sign count and last-used time.

type WebAuthnHandler

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

WebAuthnHandler runs passkey registration and (usernameless) login ceremonies against the user directory. It is transport-agnostic: callers handle HTTP and persistence.

func NewWebAuthnHandler

func NewWebAuthnHandler(rpID, rpOrigin string, users *UserStore) (*WebAuthnHandler, error)

NewWebAuthnHandler creates a WebAuthn handler bound to the user directory.

func (*WebAuthnHandler) BeginLogin added in v0.9.0

func (h *WebAuthnHandler) BeginLogin() (assertion interface{}, challengeID string, err error)

BeginLogin starts a usernameless (discoverable) login.

func (*WebAuthnHandler) BeginRegistration added in v0.9.0

func (h *WebAuthnHandler) BeginRegistration(u *User) (creation interface{}, challengeID string, err error)

BeginRegistration starts a passkey registration for the target user, requiring a discoverable (resident) credential so usernameless login works.

func (*WebAuthnHandler) FinishLogin added in v0.9.0

func (h *WebAuthnHandler) FinishLogin(r *http.Request, challengeID string) (userID string, credID []byte, signCount uint32, err error)

FinishLogin verifies a discoverable login, resolving and returning the authenticated user ID, the credential used, and its new sign count.

func (*WebAuthnHandler) FinishRegistration added in v0.9.0

func (h *WebAuthnHandler) FinishRegistration(r *http.Request, challengeID string) (userID string, sc StoredCredential, err error)

FinishRegistration verifies the registration response and returns the target user ID and the new credential. The caller persists it.

func (*WebAuthnHandler) NeedsSetup

func (h *WebAuthnHandler) NeedsSetup() bool

NeedsSetup reports whether first-boot admin provisioning is required.

Jump to

Keyboard shortcuts

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