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

core

package
v0.69.0 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package core holds the drive server's accounts, tokens, and policies. It reaches storage through the port interfaces in ports.go, which store/control implements; core must not import it, because the generated sqlc package imports Scope.

Index

Constants

View Source
const (
	DefaultRecentLimit = 50
	MaxRecentLimit     = 200
)
View Source
const (
	StartPerEmail        = 3
	StartPerEmailWindow  = time.Hour
	StartPerIP           = 10
	StartPerIPWindow     = time.Hour
	VerifyPerEmail       = 10
	VerifyPerEmailWindow = 10 * time.Minute
)

Sign-in limits. Start has two buckets so one address cannot be mailed without end and one client cannot spray many addresses.

View Source
const (
	// SessionTTL is how long a session lives past its last use.
	SessionTTL = 30 * 24 * time.Hour
)

Variables

View Source
var ErrInvalidScope = fmt.Errorf("not a valid Scope, try [%s]", strings.Join(_ScopeNames, ", "))
View Source
var ErrNotFound = NewError(KindNotFound, "not found")

ErrNotFound is what a store returns for a missing row. Services translate it into a message that names the thing when a caller sees it.

View Source
var ErrQuotaExceeded = NewError(KindTooLarge, "storage quota exceeded")

ErrQuotaExceeded is what DocumentStore.Put returns when the write would push the user's stored bytes over the limit.

Functions

func DeriveTitle

func DeriveTitle(docPath string, kind drive.Kind, body []byte) string

DeriveTitle names a document: the frontmatter title (markdown), else the first "# " heading (markdown) or the <title> element (HTML), else the file name without its extension. It never returns an empty string.

func IsNotFound

func IsNotFound(err error) bool

func NormalizeEmail

func NormalizeEmail(email string) string

func ScopeNames

func ScopeNames() []string

ScopeNames returns a list of possible string values of Scope.

func SearchText

func SearchText(kind drive.Kind, body []byte) string

SearchText is the text the search index reads for a document. Markdown is indexed as written. HTML loses its tags, comments, and the contents of script and style elements, and its entities become characters, so a query for "div" does not match every page.

Types

type Account

type Account struct {
	ID           string
	Email        string
	CreatedAt    time.Time
	LastSignInAt *time.Time
}

type AccountStore

type AccountStore interface {
	// Insert stores a, and does nothing when its email already has an account.
	Insert(ctx context.Context, a Account) error
	GetByEmail(ctx context.Context, email string) (Account, error)
	GetByID(ctx context.Context, id string) (Account, error)
	SetLastSignIn(ctx context.Context, id string, at time.Time) error
	List(ctx context.Context) ([]Account, error)
	// DeleteByEmail removes the account, and with it everything that
	// references it: tokens, sessions, share links, and pending sign-in codes.
	DeleteByEmail(ctx context.Context, email string) error
}

AccountStore methods return ErrNotFound for a missing account.

type Accounts

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

func NewAccounts

func NewAccounts(store AccountStore, users UserStores, policies *Policies, opts ...Option) *Accounts

func (*Accounts) Delete

func (a *Accounts) Delete(ctx context.Context, email string) error

Delete removes the account's control rows, then its document file. It does not need a matching policy, so a locked account can still be cleaned up. The order matters: once the control rows are gone no request resolves to the user, so nothing recreates the file after Remove.

func (*Accounts) Ensure

func (a *Accounts) Ensure(ctx context.Context, email string, operator bool) (Account, bool, error)

Ensure returns the account for email, creating it on first use, and reports whether this call created it. An email that matches no policy gets no account. A policy with sign_up: false refuses to create one unless operator is set.

func (*Accounts) List

func (a *Accounts) List(ctx context.Context) ([]Account, error)

type CodeStore

type CodeStore interface {
	Put(ctx context.Context, email, codeHash string, expiresAt time.Time) error
	// Consume checks codeHash against email's pending code. A match burns the
	// code and returns ok. A miss counts an attempt; the code burns on the
	// maxAttempts-th miss. left is the attempts remaining, and 0 when no live
	// code remains.
	Consume(ctx context.Context, email, codeHash string, now time.Time, maxAttempts int) (ok bool, left int, err error)
	DeleteExpired(ctx context.Context, now time.Time) (int, error)
}

CodeStore holds at most one pending sign-in code per email.

type Document

type Document struct {
	ID        string
	Space     string
	Path      string
	Title     string
	Kind      drive.Kind
	SHA256    string
	Size      int64
	UpdatedAt time.Time
}

type DocumentStore

type DocumentStore interface {
	// Put checks the quota and upserts in one transaction. A nil quota means
	// unlimited.
	Put(ctx context.Context, d NewDocument, quota *int64) (Document, error)
	Get(ctx context.Context, space, path string) (Document, []byte, error)
	List(ctx context.Context, space string) ([]Document, error)
	Recent(ctx context.Context, limit int) ([]Document, error)
	// ListByIDs returns the documents that exist among ids, newest first.
	ListByIDs(ctx context.Context, ids []string) ([]Document, error)
	Spaces(ctx context.Context) ([]SpaceUsage, error)
	Delete(ctx context.Context, space, path string) (string, error)
	GetByID(ctx context.Context, id string) (Document, []byte, error)
	// Search matches q as plain terms, the last one as a prefix, in one space
	// or in all of them when space is empty. Title and path matches rank above
	// body matches.
	Search(ctx context.Context, q, space string, limit int) ([]SearchHit, error)
	// Usage is the sum of all stored body sizes.
	Usage(ctx context.Context) (int64, error)
}

DocumentStore is one user's documents. No method takes a user id: the handle is the user, so a caller cannot reach another user's rows. Methods return ErrNotFound for a missing document.

type Documents

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

func NewDocuments

func NewDocuments(users UserStores, shares ShareStore, maxDocumentSize int64, opts ...Option) *Documents

NewDocuments returns the document service. A maxDocumentSize of zero or less means no cap.

func (*Documents) Delete

func (d *Documents) Delete(ctx context.Context, p Principal, space drive.Space, path string) error

func (*Documents) Get

func (d *Documents) Get(ctx context.Context, p Principal, space drive.Space, path string) (driveapi.Document, []byte, error)

func (*Documents) List

func (d *Documents) List(ctx context.Context, p Principal, space drive.Space) ([]driveapi.Document, error)

func (*Documents) MaxDocumentSize

func (d *Documents) MaxDocumentSize() int64

MaxDocumentSize is the largest accepted body, or zero for no cap.

func (*Documents) Put

func (d *Documents) Put(ctx context.Context, p Principal, space drive.Space, path string, body []byte) (driveapi.Document, error)

func (*Documents) Recent

func (d *Documents) Recent(ctx context.Context, p Principal, limit int) ([]driveapi.Document, error)

Recent returns the caller's newest documents across all spaces. A limit outside 1..MaxRecentLimit falls back to the default or the maximum.

func (*Documents) Search

func (d *Documents) Search(ctx context.Context, p Principal, q string, space *drive.Space, limit int) ([]driveapi.SearchHit, error)

Search looks across the caller's documents, or one space's when space is non-nil. A limit of zero or less returns nothing.

func (d *Documents) SharedLinks(ctx context.Context, p Principal) ([]driveapi.Document, error)

SharedLinks returns every document of the caller that has a share link, newest first. A link whose document is gone is left out.

func (*Documents) Spaces

func (d *Documents) Spaces(ctx context.Context, p Principal) ([]driveapi.SpaceSummary, error)

func (*Documents) Usage

func (d *Documents) Usage(ctx context.Context, p Principal) (int64, error)

type Error

type Error struct {
	Kind    Kind
	Message string
	Err     error
}

Error is the error type services return for outcomes a caller can act on.

func NewError

func NewError(kind Kind, msg string) *Error

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

type Kind

type Kind string

Kind is the stable error category the HTTP adapter maps to a status.

const (
	KindInvalid         Kind = "invalid"
	KindForbidden       Kind = "forbidden"
	KindNotFound        Kind = "not_found"
	KindConflict        Kind = "conflict"
	KindUnauthenticated Kind = "unauthenticated"
	KindTooLarge        Kind = "too_large"
	KindRateLimited     Kind = "rate_limited"
)

type Mail

type Mail struct {
	ID        string
	To        string
	Subject   string
	Body      string
	Attempts  int
	NextAt    time.Time
	CreatedAt time.Time
}

type NewDocument

type NewDocument struct {
	ID         string
	Space      string
	Path       string
	Title      string
	Kind       drive.Kind
	SHA256     string
	Body       []byte
	SearchText string
	UpdatedAt  time.Time
}

NewDocument is what Put stores. ID is used only when the (space, path) row does not exist yet; an existing row keeps its id so share links follow a re-upload.

type Option

type Option func(*options)

func WithBaseURL

func WithBaseURL(u string) Option

WithBaseURL sets the public origin that share URLs are built on.

func WithClock

func WithClock(now func() time.Time) Option

type OutboxStore

type OutboxStore interface {
	Append(ctx context.Context, m Mail) error
	// Due returns up to limit mails whose NextAt is not after now, oldest first.
	Due(ctx context.Context, now time.Time, limit int) ([]Mail, error)
	// MarkSent removes m: the outbox holds only mail still to send.
	MarkSent(ctx context.Context, id string) error
	MarkFailed(ctx context.Context, id string, nextAt time.Time) error
}

type Policies

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

Policies is the set of user policies. Swap lets tests change them on a running server, as a restart with a new config would.

func NewPolicies

func NewPolicies(ps []config.Policy) *Policies

func (*Policies) Match

func (p *Policies) Match(email string) (config.Policy, bool)

Match returns the first policy with a glob that matches email. An empty policy list admits nobody.

func (*Policies) Swap

func (p *Policies) Swap(ps []config.Policy)

type Principal

type Principal struct {
	UserID     string
	Email      string
	Scope      Scope
	Policy     config.Policy
	ViaSession bool
}

type Scope

type Scope string

Scope is what an API token may do. push also grants everything read does.

ENUM(read, push)

const (
	// ScopeRead is a Scope of type read.
	ScopeRead Scope = "read"
	// ScopePush is a Scope of type push.
	ScopePush Scope = "push"
)

func ParseScope

func ParseScope(name string) (Scope, error)

ParseScope attempts to convert a string to a Scope.

func (*Scope) AppendText

func (x *Scope) AppendText(b []byte) ([]byte, error)

AppendText appends the textual representation of itself to the end of b (allocating a larger slice if necessary) and returns the updated slice.

Implementations must not retain b, nor mutate any bytes within b[:len(b)].

func (Scope) Covers

func (s Scope) Covers(want Scope) bool

Covers reports whether holding s satisfies a requirement for want.

func (Scope) IsValid

func (x Scope) IsValid() bool

IsValid provides a quick way to determine if the typed value is part of the allowed enumerated values

func (Scope) MarshalText

func (x Scope) MarshalText() ([]byte, error)

MarshalText implements the text marshaller method.

func (Scope) String

func (x Scope) String() string

String implements the Stringer interface.

func (*Scope) UnmarshalText

func (x *Scope) UnmarshalText(text []byte) error

UnmarshalText implements the text unmarshaller method.

type SearchHit

type SearchHit struct {
	Document Document
	Snippet  string
}

SearchHit is one match. Snippet wraps each matched run in driveapi.SnippetMatchStart and driveapi.SnippetMatchEnd.

type Session

type Session struct {
	ID        string
	UserID    string
	ExpiresAt time.Time
	CreatedAt time.Time
}

type SessionStore

type SessionStore interface {
	Insert(ctx context.Context, s Session, valueHash string) error
	GetByValueHash(ctx context.Context, valueHash string) (Session, error)
	SetExpiry(ctx context.Context, id string, expiresAt time.Time) error
	// DeleteByValueHash does nothing when the session is already gone.
	DeleteByValueHash(ctx context.Context, valueHash string) error
	DeleteExpired(ctx context.Context, now time.Time) (int, error)
}

SessionStore methods return ErrNotFound for a missing session.

type Sessions

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

func NewSessions

func NewSessions(store SessionStore, accounts AccountStore, policies *Policies, opts ...Option) *Sessions

func (*Sessions) Mint

func (s *Sessions) Mint(ctx context.Context, userID string) (string, Session, error)

Mint starts a session for userID and returns the cookie value, which is not recoverable afterwards: only its SHA-256 is stored.

func (*Sessions) Resolve

func (s *Sessions) Resolve(ctx context.Context, value string) (Principal, error)

Resolve authenticates a cookie value. Like a token, it re-checks the policy on every request, and every caller-caused failure is the same unauthenticated error.

func (*Sessions) Revoke

func (s *Sessions) Revoke(ctx context.Context, value string) error

Revoke ends the session; an unknown value is not an error.

type ShareLink struct {
	ID         string
	Slug       string
	UserID     string
	DocumentID string
	ExpiresAt  *time.Time
	// ExpiresIn is the preset that set ExpiresAt, or empty for no expiry.
	ExpiresIn string
	CreatedAt time.Time
}

ShareLink is the public link to one document. A document has at most one.

type ShareStore

type ShareStore interface {
	// InsertIfAbsent stores l unless its document already has a link.
	InsertIfAbsent(ctx context.Context, l ShareLink) error
	GetBySlug(ctx context.Context, slug string) (ShareLink, error)
	GetByDocument(ctx context.Context, userID, documentID string) (ShareLink, error)
	ListByUser(ctx context.Context, userID string) ([]ShareLink, error)
	UpdateExpiry(ctx context.Context, id string, expiresAt *time.Time, expiresIn string) error
	Delete(ctx context.Context, id string) error
	DeleteByDocument(ctx context.Context, userID, documentID string) error
	DeleteExpired(ctx context.Context, now time.Time) (int, error)
}

ShareStore methods return ErrNotFound for a missing link.

type Shares

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

func NewShares

func NewShares(links ShareStore, accounts AccountStore, users UserStores, policies *Policies, opts ...Option) *Shares

func (*Shares) Create

func (s *Shares) Create(ctx context.Context, p Principal, space drive.Space, path, expiresIn string) (driveapi.Share, error)

Create returns the document's share link, making one when it has none. A preset expiresIn ("1d", "7d", "30d") or driveapi.ShareExpiryNone sets the expiry of a new or existing link; an empty one leaves an existing link's expiry as it is.

func (*Shares) Resolve

func (s *Shares) Resolve(ctx context.Context, slug string) (Account, driveapi.Document, []byte, error)

Resolve returns the shared document for slug, with its owner. Every reason a link stops working is the same not_found, so a caller cannot tell a revoked link from a guessed one.

func (*Shares) Revoke

func (s *Shares) Revoke(ctx context.Context, p Principal, space drive.Space, path string) error

Revoke removes the document's link. A document with no link is not an error.

type SignIn

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

func NewSignIn

func NewSignIn(log zerolog.Logger, codes CodeStore, accounts AccountStore, outbox OutboxStore, policies *Policies, ensure *Accounts, sessions *Sessions, opts ...Option) *SignIn

func (*SignIn) Start

func (s *SignIn) Start(ctx context.Context, email string) error

Start queues a sign-in code for email. It returns nil whether or not a code was queued, so the response never says which emails can sign in.

func (*SignIn) Verify

func (s *SignIn) Verify(ctx context.Context, email, code string) (string, error)

Verify trades email's pending code for a session and returns the cookie value.

type SpaceUsage

type SpaceUsage struct {
	Space     string
	Documents int
	Bytes     int64
}

type Sweeper

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

Sweeper deletes expired share links, sign-in codes, and sessions.

func NewSweeper

func NewSweeper(log zerolog.Logger, links ShareStore, codes CodeStore, sessions SessionStore, opts ...Option) *Sweeper

func (*Sweeper) Run

func (s *Sweeper) Run(ctx context.Context, every time.Duration) error

func (*Sweeper) Tick

func (s *Sweeper) Tick(ctx context.Context) error

Tick runs one sweep. A failed step is logged and does not stop the rest.

type Token

type Token struct {
	ID         string
	UserID     string
	Name       string
	Scope      Scope
	ExpiresAt  *time.Time
	LastUsedAt *time.Time
	CreatedAt  time.Time
}

type TokenStore

type TokenStore interface {
	Insert(ctx context.Context, t Token, secretHash string) error
	GetBySecretHash(ctx context.Context, secretHash string) (Token, error)
	ListByUser(ctx context.Context, userID string) ([]Token, error)
	Delete(ctx context.Context, userID, id string) error
	Touch(ctx context.Context, id string, at time.Time) error
}

TokenStore methods return ErrNotFound for a missing token.

type Tokens

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

func NewTokens

func NewTokens(tokens TokenStore, accounts AccountStore, policies *Policies, opts ...Option) *Tokens

func (*Tokens) List

func (t *Tokens) List(ctx context.Context, userID string) ([]Token, error)

func (*Tokens) Mint

func (t *Tokens) Mint(ctx context.Context, userID, name string, scope Scope, expiresAt *time.Time) (string, Token, error)

Mint creates a token for userID and returns its secret, which is not recoverable afterwards: only its SHA-256 is stored.

func (*Tokens) MintFor

func (t *Tokens) MintFor(ctx context.Context, userID, name string, scope Scope, expiresIn string) (string, Token, error)

MintFor is Mint with an expiry named as the API names it: "", "1d", "7d", "30d", "90d", or "365d".

func (*Tokens) Resolve

func (t *Tokens) Resolve(ctx context.Context, secret string) (Principal, error)

Resolve authenticates a token secret. Every failure a caller could cause is unauthenticated with the same message, so a response does not say whether a secret exists.

func (*Tokens) Revoke

func (t *Tokens) Revoke(ctx context.Context, userID, id string) error

type UserStores

type UserStores interface {
	// Open returns userID's store, creating an empty one on first use. The
	// caller must call release when done; the store stays valid until then.
	Open(ctx context.Context, userID string) (store DocumentStore, release func(), err error)
	// Remove deletes everything stored for userID, once its leases are released.
	Remove(ctx context.Context, userID string) error
}

Jump to

Keyboard shortcuts

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