WIP: panics on shutdown

This commit is contained in:
Daniel 2022-11-13 09:05:09 -05:00
parent 54185be835
commit 3ab5b5b78a
5 changed files with 195 additions and 38 deletions

View file

@ -70,33 +70,39 @@ type Credential struct {
UserID UserID `json:"user_id"` UserID UserID `json:"user_id"`
AuthProviderType string `json:"auth_provider_type"` AuthProviderType string `json:"auth_provider_type"`
AuthProviderID *string `json:"auth_provider_id"` AuthProviderID *string `json:"auth_provider_id"`
DataRaw json.RawMessage `json:"data,omitempty"` DataRaw *json.RawMessage `json:"data,omitempty"`
user provider.ProviderUser user provider.ProviderUser `json:"-"`
} }
func (cred *Credential) MarshalJSON() ([]byte, error) { func (cred *Credential) MarshalJSON() ([]byte, error) {
rm := map[string]interface{}{ type CredAlias Credential
"id": cred.ID, nCd := (*CredAlias)(cred)
"user_id": cred.UserID,
"auth_provider_type": cred.user.ProviderType(),
"auth_provider_id": cred.user.ProviderID(),
}
providerData := cred.user.ProviderUserData() providerData := cred.user.ProviderUserData()
if providerData != nil { if providerData != nil {
rm["data"] = providerData b, err := json.Marshal(providerData)
if err != nil {
return nil, err
}
dr := json.RawMessage(b)
nCd.DataRaw = &dr
} }
return json.Marshal(rm) return json.Marshal(nCd)
} }
func (ss *SessionStore) verifyAndGetCredential(tr *TokenRequest, r *http.Request) *Credential { func (a *Authenticator) verifyAndGetCredential(tr *TokenRequest, r *http.Request) *Credential {
user, success := ss.verify(tr, r) user, success := a.sessions.verify(tr, r)
if !success { if !success {
return nil return nil
} }
return &Credential{user: user} cred := &Credential{
user: user,
}
return cred
} }
const defaultExpiration = 2 * time.Hour const defaultExpiration = 2 * time.Hour
@ -156,7 +162,7 @@ func (a *Authenticator) TokenHandler(c echo.Context) error {
return c.JSON(http.StatusBadRequest, AuthError{Error: "invalid_request", Description: "invalid code"}) return c.JSON(http.StatusBadRequest, AuthError{Error: "invalid_request", Description: "invalid code"})
} }
if cred := a.sessions.verifyAndGetCredential(rq, c.Request()); cred != nil { if cred := a.verifyAndGetCredential(rq, c.Request()); cred != nil {
// TODO: success // TODO: success
user, err := a.getOrCreateUser(cred) user, err := a.getOrCreateUser(cred)
if err != nil { if err != nil {

View file

@ -4,6 +4,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/rs/zerolog/log"
"dynatron.me/x/blasphem/pkg/storage" "dynatron.me/x/blasphem/pkg/storage"
) )
@ -31,6 +33,7 @@ func (a *Authenticator) newAuthStore(s storage.Store) (as *authStore, err error)
for _, u := range as.Users { for _, u := range as.Users {
as.userMap[u.ID] = &u as.userMap[u.ID] = &u
log.Debug().Interface("user", u).Msg("user")
} }
for _, c := range as.Credentials { for _, c := range as.Credentials {
@ -41,9 +44,11 @@ func (a *Authenticator) newAuthStore(s storage.Store) (as *authStore, err error)
pd := prov.NewCredData() pd := prov.NewCredData()
err := json.Unmarshal(c.DataRaw, pd) if c.DataRaw != nil {
if err != nil { err := json.Unmarshal(*c.DataRaw, pd)
return nil, err if err != nil {
return nil, err
}
} }
} }

View file

@ -1,8 +1,6 @@
package auth package auth
import ( import (
"errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -34,7 +32,7 @@ func (u *User) allowedToAuth() error {
} }
func (a *Authenticator) getOrCreateUser(c *Credential) (*User, error) { func (a *Authenticator) getOrCreateUser(c *Credential) (*User, error) {
log.Debug().Interface("userdata", c.user.ProviderUserData()).Msg("getOrCreateUser") log.Debug().Interface("userdata", c).Msg("getOrCreateUser")
u := a.store.User(c.UserID) u := a.store.User(c.UserID)
if u == nil { if u == nil {
return nil, ErrInvalidAuth return nil, ErrInvalidAuth

View file

@ -20,6 +20,7 @@ type Blas struct {
func (b *Blas) Shutdown(ctx context.Context) error { func (b *Blas) Shutdown(ctx context.Context) error {
b.Bus.Shutdown() b.Bus.Shutdown()
b.Store.Shutdown()
return ctx.Err() return ctx.Err()
} }
@ -44,7 +45,8 @@ func (b *Blas) ConfigDir() (cd string) {
} }
func (b *Blas) openStore() error { func (b *Blas) openStore() error {
stor, err := storage.Open(os.DirFS(b.ConfigDir())) // TODO: based on config, open filestore or db store
stor, err := storage.OpenFileStore(b.ConfigDir())
b.Store = stor b.Store = stor
return err return err
} }

View file

@ -2,57 +2,203 @@ package storage
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/fs" "io/fs"
"os"
"path"
"strings"
"github.com/rs/zerolog/log"
)
var (
IndentStr = strings.Repeat(" ", 4)
ErrNoSuchKey = errors.New("no such key in store")
)
const (
SecretMode os.FileMode = 0600
DefaultMode os.FileMode = 0644
) )
type Data interface { type Data interface {
} }
type Item struct { type item struct {
Version int `json:"version"` Version int `json:"version"`
MinorVersion *int `json:"minor_version,omitempty"` MinorVersion *int `json:"minor_version,omitempty"`
Key string `json:"key"` Key string `json:"key"`
Data Data `json:"data"` Data interface{} `json:"data"`
fmode os.FileMode
dirty bool
} }
type store struct { type Item interface {
Dirty()
IsDirty() bool
GetData() interface{}
SetData(interface{})
ItemKey() string
}
func (i *item) Dirty() { i.dirty = true }
func (i *item) IsDirty() bool { return i.dirty }
func (i *item) GetData() interface{} { return i.Data }
func (i *item) SetData(d interface{}) { i.Data = d; i.Dirty() }
func (i *item) ItemKey() string { return i.Key }
func (it *item) mode() os.FileMode {
if it.fmode != 0 {
return it.fmode
}
return SecretMode
}
type fsStore struct {
fs.FS fs.FS
storeRoot string
s map[string]*item
} }
type Store interface { type Store interface {
GetItem(key string, data interface{}) (Item, error)
Get(key string, data interface{}) error Get(key string, data interface{}) error
Put(key string, version, minorVersion int, secretMode bool, data interface{}) (Item, error)
FlushAll() []error
Flush(key string) error
Shutdown()
} }
func (s *store) Get(key string, data interface{}) error { func (s *fsStore) persist(it *item) error {
f, err := s.Open(key) f, err := os.OpenFile(path.Join(s.storeRoot, it.Key), os.O_WRONLY|os.O_CREATE, it.mode())
if err != nil { if err != nil {
return err return err
} }
defer f.Close() defer f.Close()
item := Item{ enc := json.NewEncoder(f)
Data: data, enc.SetIndent("", IndentStr)
}
d := json.NewDecoder(f) err = enc.Encode(it)
err = d.Decode(&item) if err == nil {
if err != nil { it.dirty = false
return err
} }
if item.Key != key { return err
return fmt.Errorf("key mismatch '%s' != '%s'", item.Key, key) }
func (s *fsStore) Dirty(key string) error {
it, has := s.s[key]
if !has {
return ErrNoSuchKey
} }
it.dirty = true
return nil return nil
} }
func Open(dir fs.FS) (*store, error) { func (s *fsStore) Flush(key string) error {
stor, err := fs.Sub(dir, ".storage") it, exists := s.s[key]
if !exists {
return ErrNoSuchKey
}
return s.persist(it)
}
func (s *fsStore) FlushAll() []error {
var errs []error
for _, it := range s.s {
err := s.persist(it)
if err != nil {
errs = append(errs, fmt.Errorf("store key %s: %w", it.Key, err))
}
}
return errs
}
func (s *fsStore) Shutdown() {
errs := s.FlushAll()
if errs != nil {
log.Error().Errs("errors", errs).Msg("errors persisting store")
}
}
// Put puts an item into the store.
// NB: Any user of a previous item with this key will now have a dangling reference that will not be persisted.
// It is up to consumers to coordinate against this case!
func (s *fsStore) Put(key string, version, minorVersion int, secretMode bool, data interface{}) (Item, error) {
var mv *int
if minorVersion != 0 {
mv = &minorVersion
}
mode := DefaultMode
if secretMode {
mode = SecretMode
}
it := &item{
Version: version,
MinorVersion: mv,
Key: key,
Data: data,
fmode: mode,
dirty: true,
}
s.s[key] = it
return it, s.persist(it)
}
func (s *fsStore) Get(key string, data interface{}) error {
_, err := s.GetItem(key, data)
return err
}
func (s *fsStore) GetItem(key string, data interface{}) (Item, error) {
f, err := s.Open(key)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &store{stor}, nil defer f.Close()
item := &item{
Data: data,
}
d := json.NewDecoder(f)
err = d.Decode(item)
if err != nil {
return nil, err
}
if item.Key != key {
return nil, fmt.Errorf("key mismatch '%s' != '%s'", item.Key, key)
}
s.s[key] = item
return item, nil
}
func OpenFileStore(configRoot string) (*fsStore, error) {
storeRoot := path.Join(configRoot, ".storage")
stor := os.DirFS(storeRoot)
return &fsStore{
FS: stor,
storeRoot: storeRoot,
s: make(map[string]*item),
}, nil
} }