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
- Variables
- func DeriveTitle(docPath string, kind drive.Kind, body []byte) string
- func IsNotFound(err error) bool
- func NormalizeEmail(email string) string
- func ScopeNames() []string
- func SearchText(kind drive.Kind, body []byte) string
- type Account
- type AccountStore
- type Accounts
- type CodeStore
- type Document
- type DocumentStore
- type Documents
- func (d *Documents) Delete(ctx context.Context, p Principal, space drive.Space, path string) error
- func (d *Documents) Get(ctx context.Context, p Principal, space drive.Space, path string) (driveapi.Document, []byte, error)
- func (d *Documents) List(ctx context.Context, p Principal, space drive.Space) ([]driveapi.Document, error)
- func (d *Documents) MaxDocumentSize() int64
- func (d *Documents) Put(ctx context.Context, p Principal, space drive.Space, path string, body []byte) (driveapi.Document, error)
- func (d *Documents) Recent(ctx context.Context, p Principal, limit int) ([]driveapi.Document, error)
- func (d *Documents) Search(ctx context.Context, p Principal, q string, space *drive.Space, limit int) ([]driveapi.SearchHit, error)
- func (d *Documents) SharedLinks(ctx context.Context, p Principal) ([]driveapi.Document, error)
- func (d *Documents) Spaces(ctx context.Context, p Principal) ([]driveapi.SpaceSummary, error)
- func (d *Documents) Usage(ctx context.Context, p Principal) (int64, error)
- type Error
- type Kind
- type Mail
- type NewDocument
- type Option
- type OutboxStore
- type Policies
- type Principal
- type Scope
- type SearchHit
- type Session
- type SessionStore
- type Sessions
- type ShareLink
- type ShareStore
- type Shares
- func (s *Shares) Create(ctx context.Context, p Principal, space drive.Space, path, expiresIn string) (driveapi.Share, error)
- func (s *Shares) Resolve(ctx context.Context, slug string) (Account, driveapi.Document, []byte, error)
- func (s *Shares) Revoke(ctx context.Context, p Principal, space drive.Space, path string) error
- type SignIn
- type SpaceUsage
- type Sweeper
- type Token
- type TokenStore
- type Tokens
- func (t *Tokens) List(ctx context.Context, userID string) ([]Token, error)
- func (t *Tokens) Mint(ctx context.Context, userID, name string, scope Scope, expiresAt *time.Time) (string, Token, error)
- func (t *Tokens) MintFor(ctx context.Context, userID, name string, scope Scope, expiresIn string) (string, Token, error)
- func (t *Tokens) Resolve(ctx context.Context, secret string) (Principal, error)
- func (t *Tokens) Revoke(ctx context.Context, userID, id string) error
- type UserStores
Constants ¶
const ( DefaultRecentLimit = 50 MaxRecentLimit = 200 )
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.
const ( // SessionTTL is how long a session lives past its last use. SessionTTL = 30 * 24 * time.Hour )
Variables ¶
var ErrInvalidScope = fmt.Errorf("not a valid Scope, try [%s]", strings.Join(_ScopeNames, ", "))
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.
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 ¶
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 NormalizeEmail ¶
func ScopeNames ¶
func ScopeNames() []string
ScopeNames returns a list of possible string values of Scope.
func SearchText ¶
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 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 ¶
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.
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 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) MaxDocumentSize ¶
MaxDocumentSize is the largest accepted body, or zero for no cap.
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 (*Documents) SharedLinks ¶
SharedLinks returns every document of the caller that has a share link, newest first. A link whose document is gone is left out.
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 ¶
WithBaseURL sets the public origin that share URLs are built on.
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 ¶
type Scope ¶
type Scope string
Scope is what an API token may do. push also grants everything read does.
ENUM(read, push)
func ParseScope ¶
ParseScope attempts to convert a string to a Scope.
func (*Scope) AppendText ¶
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) IsValid ¶
IsValid provides a quick way to determine if the typed value is part of the allowed enumerated values
func (Scope) MarshalText ¶
MarshalText implements the text marshaller method.
func (*Scope) UnmarshalText ¶
UnmarshalText implements the text unmarshaller method.
type SearchHit ¶
SearchHit is one match. Snippet wraps each matched run in driveapi.SnippetMatchStart and driveapi.SnippetMatchEnd.
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 ¶
Mint starts a session for userID and returns the cookie value, which is not recoverable afterwards: only its SHA-256 is stored.
type ShareLink ¶
type ShareLink struct {
// ExpiresIn is the preset that set ExpiresAt, or empty for no expiry.
}
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.
}
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.
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
type SpaceUsage ¶
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
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) 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".
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
}