blasphem/pkg/auth/store.go

203 lines
4.4 KiB
Go
Raw Normal View History

2022-11-12 15:56:17 -05:00
package auth
import (
2022-12-19 13:09:01 -05:00
"crypto/subtle"
2022-11-12 15:56:17 -05:00
"encoding/json"
2022-11-12 17:42:51 -05:00
"fmt"
2022-11-12 15:56:17 -05:00
2022-12-18 21:26:34 -05:00
"dynatron.me/x/blasphem/internal/generate"
2022-11-13 11:55:10 -05:00
"dynatron.me/x/blasphem/pkg/auth/provider"
2022-11-12 15:56:17 -05:00
"dynatron.me/x/blasphem/pkg/storage"
2022-12-18 09:55:08 -05:00
"github.com/rs/zerolog/log"
2022-11-12 15:56:17 -05:00
)
const (
AuthStoreKey = "auth"
)
type AuthStore interface {
2022-11-12 17:50:01 -05:00
User(UserID) *User
2022-12-19 02:42:01 -05:00
GetCredential(provider.ProviderUser) *Credentials
PutRefreshToken(*RefreshToken) (*RefreshToken, error)
2022-12-19 13:09:01 -05:00
GetRefreshTokenByToken(token RefreshTokenToken) *RefreshToken
2022-12-20 19:31:46 -05:00
GetRefreshToken(RefreshTokenID) *RefreshToken
2022-11-12 15:56:17 -05:00
}
type authStore struct {
2022-12-19 02:42:01 -05:00
storage.Item `json:"-"`
2022-12-18 21:26:34 -05:00
Users []*User `json:"users"`
Groups []*Group `json:"groups"`
Credentials []*Credentials `json:"credentials"`
Refresh []*RefreshToken `json:"refresh_tokens"`
2022-11-12 15:56:17 -05:00
2022-11-20 08:49:24 -05:00
userMap map[UserID]*User
2022-12-18 09:55:08 -05:00
providerUsers map[provider.ProviderUser]*Credentials
2022-12-19 02:42:01 -05:00
store storage.Store
}
func (as *authStore) sync() {
err := as.store.Flush(as.ItemKey())
if err != nil {
log.Error().Err(err).Msg("sync authStore")
}
2022-11-13 19:06:53 -05:00
}
2022-12-18 21:26:34 -05:00
func strPtrEq(n1, n2 *string) bool {
return (n1 == n2 || (n1 != nil && n2 != nil && *n1 == *n2))
}
2022-12-19 02:42:01 -05:00
func (as *authStore) GetCredential(p provider.ProviderUser) *Credentials {
2022-12-19 13:09:01 -05:00
var found *Credentials
2022-12-18 21:26:34 -05:00
for _, cr := range as.Credentials {
2022-12-19 02:42:01 -05:00
if p != nil && (p == cr.User ||
(p.Provider() != nil &&
strPtrEq(cr.AuthProviderID, p.Provider().ProviderID()) &&
cr.AuthProviderType == p.Provider().ProviderType() &&
p.Provider().EqualCreds(cr.User.UserData(), p.UserData()))) {
2022-12-19 13:09:01 -05:00
found = cr
2022-12-18 21:26:34 -05:00
}
}
2022-12-19 13:09:01 -05:00
return found
2022-12-18 21:26:34 -05:00
}
2022-12-19 02:42:01 -05:00
func (as *authStore) PutRefreshToken(rt *RefreshToken) (*RefreshToken, error) {
e := func(es string, a ...interface{}) (*RefreshToken, error) {
return nil, fmt.Errorf(es, a...)
}
u, hasUser := as.userMap[rt.UserID]
if !hasUser {
return e("no such user %v", rt.UserID)
}
as.Refresh = append(as.Refresh, rt)
u.RefreshTokens = append(u.RefreshTokens, rt)
as.sync()
return rt, nil
}
2022-12-19 13:09:01 -05:00
func (as *authStore) GetRefreshTokenByToken(token RefreshTokenToken) *RefreshToken {
var found *RefreshToken
for _, u := range as.Users {
for _, rt := range u.RefreshTokens {
if subtle.ConstantTimeCompare([]byte(token), []byte(rt.Token)) == 1 {
found = rt
2022-12-19 19:24:01 -05:00
found.User = u
2022-12-19 13:09:01 -05:00
}
}
}
return found
}
2022-12-20 19:31:46 -05:00
func (as *authStore) GetRefreshToken(tid RefreshTokenID) *RefreshToken {
var found *RefreshToken
for _, u := range as.Users {
for _, rt := range u.RefreshTokens {
2022-12-20 20:11:11 -05:00
if subtle.ConstantTimeCompare([]byte(tid), []byte(rt.ID.String())) == 1 {
2022-12-20 19:31:46 -05:00
found = rt
found.User = u
}
}
}
return found
}
2022-12-18 21:26:34 -05:00
func (as *authStore) newCredential(p provider.ProviderUser) *Credentials {
// XXX: probably broken
prov := p.Provider()
id := generate.UUID()
c := &Credentials{
ID: CredID(id),
AuthProviderType: prov.ProviderBase().Type,
AuthProviderID: prov.ProviderBase().ID,
2022-11-13 19:06:53 -05:00
}
return c
2022-11-12 15:56:17 -05:00
}
2022-12-19 19:24:01 -05:00
func (a *authenticator) newAuthStore(s storage.Store) (as *authStore, err error) {
2022-12-19 02:42:01 -05:00
as = &authStore{
store: s,
}
as.Item, err = s.GetItem(AuthStoreKey, as)
if err != nil {
return
}
2022-11-12 15:56:17 -05:00
as.userMap = make(map[UserID]*User)
2022-12-18 09:55:08 -05:00
as.providerUsers = make(map[provider.ProviderUser]*Credentials)
2022-11-12 15:56:17 -05:00
for _, u := range as.Users {
2022-12-18 21:26:34 -05:00
as.userMap[u.ID] = u
2022-11-12 15:56:17 -05:00
}
for _, c := range as.Credentials {
prov := a.Provider(c.AuthProviderType)
if prov == nil {
return nil, fmt.Errorf("no such provider %s", c.AuthProviderType)
}
2022-11-13 09:05:09 -05:00
if c.DataRaw != nil {
2022-11-13 11:55:10 -05:00
pd := prov.NewCredData()
2022-11-13 09:05:09 -05:00
err := json.Unmarshal(*c.DataRaw, pd)
if err != nil {
return nil, err
}
2022-11-13 11:55:10 -05:00
2022-12-18 21:26:34 -05:00
c.User = prov.Lookup(pd.(provider.ProviderUser))
if c.User == nil {
2022-11-13 19:06:53 -05:00
return nil, fmt.Errorf("cannot find user in provider %s", prov.ProviderName())
}
2022-12-18 21:26:34 -05:00
as.providerUsers[c.User] = c
2022-11-12 15:56:17 -05:00
}
2022-12-18 09:55:08 -05:00
u, hasUser := as.userMap[c.UserID]
if !hasUser {
2022-12-19 02:42:01 -05:00
log.Error().Str("userid", string(c.UserID)).Msg("creds no such userid in map")
2022-12-18 09:55:08 -05:00
continue
}
u.Creds = append(u.Creds, c)
2022-11-12 15:56:17 -05:00
}
2022-12-17 15:17:05 -05:00
// remove invalid RefreshTokens
i := 0
for _, rt := range as.Refresh {
if rt.IsValid() {
2022-12-19 02:42:01 -05:00
u, hasUser := as.userMap[rt.UserID]
if !hasUser {
log.Error().Str("userid", string(rt.UserID)).Msg("refreshtokens no such userid in map")
continue
}
2022-12-17 15:17:05 -05:00
as.Refresh[i] = rt
i++
2022-12-19 02:42:01 -05:00
u.RefreshTokens = append(u.RefreshTokens, rt)
2022-12-17 15:17:05 -05:00
}
}
// don't leak memory
for j := i; j < len(as.Refresh); j++ {
2022-12-18 21:26:34 -05:00
as.Refresh[j] = nil
2022-12-17 15:17:05 -05:00
}
as.Refresh = as.Refresh[:i]
2022-11-12 15:56:17 -05:00
return
}
2022-11-12 17:50:01 -05:00
func (s *authStore) User(uid UserID) *User {
return s.userMap[uid]
}