Files

261 lines
5.2 KiB
Go
Raw Permalink Normal View History

2016-04-11 20:43:18 -07:00
package vain
import (
2016-06-22 23:01:21 -07:00
"encoding/json"
2016-04-11 20:43:18 -07:00
"fmt"
"net/http"
2016-06-22 23:01:21 -07:00
"os"
2016-06-28 21:17:23 -07:00
"strings"
2016-06-22 23:01:21 -07:00
"sync"
2016-04-11 20:43:18 -07:00
"time"
verrors "mcquay.me/vain/errors"
2016-06-24 00:06:43 -07:00
"mcquay.me/vain/metrics"
2016-04-11 20:43:18 -07:00
)
2016-06-22 23:01:21 -07:00
// NewMemDB returns a functional MemDB.
func NewMemDB(p string) (*MemDB, error) {
m := &MemDB{
filename: p,
2016-04-11 20:43:18 -07:00
2016-06-22 23:01:21 -07:00
Users: map[Email]User{},
TokToEmail: map[Token]Email{},
2016-04-11 20:43:18 -07:00
2016-06-22 23:01:21 -07:00
Packages: map[path]Package{},
Namespaces: map[namespace]Email{},
2016-04-11 20:43:18 -07:00
}
2016-06-22 23:01:21 -07:00
f, err := os.Open(p)
2016-04-11 20:43:18 -07:00
if err != nil {
2016-06-22 23:01:21 -07:00
// file doesn't exist yet
return m, nil
2016-04-11 20:43:18 -07:00
}
2016-06-22 23:01:21 -07:00
err = json.NewDecoder(f).Decode(m)
return m, err
2016-04-11 20:43:18 -07:00
}
2016-06-22 23:01:21 -07:00
// MemDB implements an in-memory, and disk-backed database for a vain server.
type MemDB struct {
filename string
2016-04-11 20:43:18 -07:00
2016-06-22 23:01:21 -07:00
l sync.RWMutex
2016-04-11 20:43:18 -07:00
2016-06-22 23:01:21 -07:00
Users map[Email]User
TokToEmail map[Token]Email
Packages map[path]Package
Namespaces map[namespace]Email
2016-04-11 20:43:18 -07:00
}
2016-05-23 23:54:35 -07:00
// NSForToken creates an entry namespaces with a relation to the token.
2016-06-22 23:01:21 -07:00
func (m *MemDB) NSForToken(ns namespace, tok Token) error {
m.l.Lock()
defer m.l.Unlock()
2016-04-11 20:43:18 -07:00
2016-06-22 23:01:21 -07:00
e, ok := m.TokToEmail[tok]
if !ok {
2016-04-11 20:43:18 -07:00
return verrors.HTTP{
2016-06-22 23:01:21 -07:00
Message: fmt.Sprintf("User for token %q not found", tok),
Code: http.StatusNotFound,
2016-04-11 20:43:18 -07:00
}
}
2016-06-22 23:01:21 -07:00
if owner, ok := m.Namespaces[ns]; !ok {
m.Namespaces[ns] = e
} else {
if m.Namespaces[ns] != owner {
2016-06-21 22:02:06 -07:00
return verrors.HTTP{
2016-06-22 23:01:21 -07:00
Message: fmt.Sprintf("not authorized against namespace %q", ns),
Code: http.StatusUnauthorized,
2016-06-21 22:02:06 -07:00
}
}
2016-04-11 20:43:18 -07:00
}
2016-06-22 23:01:21 -07:00
return m.flush(m.filename)
}
2016-04-11 20:43:18 -07:00
2016-06-22 23:01:21 -07:00
// Package fetches the package associated with path.
func (m *MemDB) Package(pth string) (Package, error) {
m.l.RLock()
2016-06-28 21:17:23 -07:00
defer m.l.RUnlock()
2016-06-22 23:01:21 -07:00
pkg, ok := m.Packages[path(pth)]
2016-06-28 21:17:23 -07:00
if ok {
return pkg, nil
}
var longest Package
for _, p := range m.Packages {
if splitPathHasPrefix(strings.Split(pth, "/"), strings.Split(p.Path, "/")) {
if len(p.Path) > len(longest.Path) {
longest = p
}
}
}
2016-06-22 23:01:21 -07:00
var err error
2016-06-28 21:17:23 -07:00
if longest.Path == "" {
2016-04-11 20:43:18 -07:00
err = verrors.HTTP{
2016-06-22 23:01:21 -07:00
Message: fmt.Sprintf("couldn't find package %q", pth),
Code: http.StatusNotFound,
2016-04-11 20:43:18 -07:00
}
}
2016-06-28 21:17:23 -07:00
return longest, err
2016-04-11 20:43:18 -07:00
}
2016-06-22 23:01:21 -07:00
// AddPackage adds p into packages table.
func (m *MemDB) AddPackage(p Package) error {
m.l.Lock()
m.Packages[path(p.Path)] = p
m.l.Unlock()
return m.flush(m.filename)
}
2016-04-11 20:43:18 -07:00
2016-06-22 23:01:21 -07:00
// RemovePackage removes package with given path
func (m *MemDB) RemovePackage(pth path) error {
m.l.Lock()
delete(m.Packages, pth)
m.l.Unlock()
return m.flush(m.filename)
}
// PackageExists tells if a package with path is in the database.
func (m *MemDB) PackageExists(pth path) bool {
m.l.RLock()
_, ok := m.Packages[path(pth)]
m.l.RUnlock()
return ok
}
2016-04-11 20:43:18 -07:00
2016-06-22 23:01:21 -07:00
// Pkgs returns all packages from the database
func (m *MemDB) Pkgs() []Package {
ps := []Package{}
m.l.RLock()
for _, p := range m.Packages {
ps = append(ps, p)
}
m.l.RUnlock()
return ps
}
// Register adds email to the database, returning an error if there was one.
func (m *MemDB) Register(e Email) (Token, error) {
m.l.Lock()
defer m.l.Unlock()
if _, ok := m.Users[e]; ok {
2016-04-11 20:43:18 -07:00
return "", verrors.HTTP{
2016-06-22 23:01:21 -07:00
Message: fmt.Sprintf("duplicate email %q", e),
2016-04-11 20:43:18 -07:00
Code: http.StatusConflict,
}
}
tok := FreshToken()
2016-06-22 23:01:21 -07:00
m.Users[e] = User{
Email: e,
token: tok,
Requested: time.Now(),
}
m.TokToEmail[tok] = e
return tok, m.flush(m.filename)
2016-04-11 20:43:18 -07:00
}
2016-05-23 23:54:35 -07:00
// Confirm modifies the user with the given token. Used on register confirmation.
2016-06-22 23:01:21 -07:00
func (m *MemDB) Confirm(tok Token) (Token, error) {
m.l.Lock()
defer m.l.Unlock()
2016-04-11 20:43:18 -07:00
2016-06-22 23:01:21 -07:00
e, ok := m.TokToEmail[tok]
if !ok {
2016-04-11 20:43:18 -07:00
return "", verrors.HTTP{
2016-06-22 23:01:21 -07:00
Message: fmt.Sprintf("bad token: %s", tok),
2016-04-11 20:43:18 -07:00
Code: http.StatusNotFound,
}
}
2016-06-22 23:01:21 -07:00
delete(m.TokToEmail, tok)
tok = FreshToken()
u, ok := m.Users[e]
if !ok {
2016-04-11 20:43:18 -07:00
return "", verrors.HTTP{
2016-06-22 23:01:21 -07:00
Message: fmt.Sprintf("inconsistent db; found email for token %q, but no user for email %q", tok, e),
2016-04-11 20:43:18 -07:00
Code: http.StatusInternalServerError,
}
}
2016-06-22 23:01:21 -07:00
u.token = tok
m.Users[e] = u
m.TokToEmail[tok] = e
return tok, m.flush(m.filename)
2016-04-11 20:43:18 -07:00
}
2016-06-22 23:01:21 -07:00
// Forgot is used fetch a user's token. It implements rudimentary rate
// limiting.
func (m *MemDB) Forgot(e Email, window time.Duration) (Token, error) {
m.l.Lock()
defer m.l.Unlock()
2016-05-14 21:30:58 -07:00
2016-06-22 23:01:21 -07:00
u, ok := m.Users[e]
if !ok {
2016-05-14 21:30:58 -07:00
return "", verrors.HTTP{
2016-06-22 23:01:21 -07:00
Message: fmt.Sprintf("could not find email %q in db", e),
2016-05-14 21:30:58 -07:00
Code: http.StatusNotFound,
}
}
2016-06-22 23:01:21 -07:00
if u.Requested.After(time.Now()) {
2016-05-14 21:30:58 -07:00
return "", verrors.HTTP{
2016-06-22 23:01:21 -07:00
Message: fmt.Sprintf("rate limit hit for %q; try again in %0.2f mins", u.Email, u.Requested.Sub(time.Now()).Minutes()),
2016-05-14 21:30:58 -07:00
Code: http.StatusTooManyRequests,
}
}
2016-06-22 23:01:21 -07:00
return u.token, nil
}
// Sync takes a lock, and flushes the data to disk.
func (m *MemDB) Sync() error {
m.l.RLock()
defer m.l.RUnlock()
return m.flush(m.filename)
}
// flush writes to disk, but expects the user to have taken the lock.
func (m *MemDB) flush(p string) error {
2016-06-24 00:06:43 -07:00
defer metrics.DBTime("flush")()
2016-06-22 23:01:21 -07:00
f, err := os.Create(p)
2016-05-14 21:30:58 -07:00
if err != nil {
2016-06-22 23:01:21 -07:00
return err
2016-04-11 20:43:18 -07:00
}
2016-06-22 23:01:21 -07:00
return json.NewEncoder(f).Encode(&m)
2016-04-11 20:43:18 -07:00
}
2016-06-22 23:01:21 -07:00
func (m *MemDB) addUser(e Email) (Token, error) {
2016-04-11 20:43:18 -07:00
tok := FreshToken()
2016-06-22 23:01:21 -07:00
m.l.Lock()
m.Users[e] = User{
Email: e,
token: tok,
Requested: time.Now(),
}
m.TokToEmail[tok] = e
m.l.Unlock()
return tok, m.flush(m.filename)
2016-04-11 20:43:18 -07:00
}
2016-06-03 13:42:05 -07:00
2016-06-22 23:01:21 -07:00
func (m *MemDB) user(e Email) (User, error) {
m.l.Lock()
u, ok := m.Users[e]
m.l.Unlock()
var err error
if !ok {
err = verrors.HTTP{
Message: fmt.Sprintf("couldn't find user %q", e),
2016-06-03 13:42:05 -07:00
Code: http.StatusNotFound,
}
}
return u, err
}