WIP: panics on shutdown
This commit is contained in:
parent
54185be835
commit
3ab5b5b78a
5 changed files with 195 additions and 38 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(rm)
|
dr := json.RawMessage(b)
|
||||||
|
nCd.DataRaw = &dr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *SessionStore) verifyAndGetCredential(tr *TokenRequest, r *http.Request) *Credential {
|
return json.Marshal(nCd)
|
||||||
user, success := ss.verify(tr, r)
|
}
|
||||||
|
|
||||||
|
func (a *Authenticator) verifyAndGetCredential(tr *TokenRequest, r *http.Request) *Credential {
|
||||||
|
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 {
|
||||||
|
|
|
@ -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,11 +44,13 @@ 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 {
|
||||||
|
err := json.Unmarshal(*c.DataRaw, pd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
err = enc.Encode(it)
|
||||||
|
if err == nil {
|
||||||
|
it.dirty = false
|
||||||
}
|
}
|
||||||
d := json.NewDecoder(f)
|
|
||||||
err = d.Decode(&item)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.Key != key {
|
func (s *fsStore) Dirty(key string) error {
|
||||||
return fmt.Errorf("key mismatch '%s' != '%s'", item.Key, key)
|
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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue