package models import ( "context" "time" "code.crute.us/mcrute/golib/db/mongodb" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) const userCol = "users" type UserStore interface { List(context.Context) ([]*User, error) Get(context.Context, string) (*User, error) // Error on not found Put(context.Context, *User) error Delete(context.Context, *User) error } type AuthToken struct { Kind string `json:"kind"` Token string `json:"token"` // Do not expose refresh tokens in JSON as they are long-lived tokens that // are harder to invalidate and thus rather security sensitive. RefreshToken string `json:"-"` } type User struct { Username string `bson:"_id" json:"key_id"` IsAdmin bool `json:"is_admin"` IsService bool `json:"is_service"` Keys map[string]*SessionKey `json:"keys,omitempty"` // kid -> key AuthTokens map[string]*AuthToken `json:"auth_tokens,omitempty"` // kind -> token Deleted *time.Time `json:"deleted,omitempty"` } // GCKeys garbage collects keys that are no longer valid func (u *User) GCKeys() { for k, v := range u.Keys { if v.IsGarbage() { delete(u.Keys, k) } } } // GetKey returns a key for a key ID. It will only return valid keys. func (u *User) GetKey(kid string) *SessionKey { if u.Keys != nil { if k := u.Keys[kid]; k != nil && k.IsValid() { return k } } return nil } func (u *User) AddKey(k *SessionKey) { if u.Keys == nil { u.Keys = map[string]*SessionKey{} } u.Keys[k.KeyId] = k } func (u *User) AddToken(t *AuthToken) { if u.AuthTokens == nil { u.AuthTokens = map[string]*AuthToken{} } u.AuthTokens[t.Kind] = t } type MongoDbUserStore struct { Db *mongodb.Mongo // ReturnDeleted will allow all methods to return deleted items. By default // items where the Deleted field is set will not be returned. This should // be the common cast for most code using this store but in some Admin // use-cases it would be useful to show deleted accounts. ReturnDeleted bool } func (s *MongoDbUserStore) List(ctx context.Context) ([]*User, error) { var out []*User filter := bson.M{} if !s.ReturnDeleted { filter["deleted"] = primitive.Null{} } if err := s.Db.FindAllByFilter(ctx, userCol, filter, &out); err != nil { return nil, err } return out, nil } func (s *MongoDbUserStore) Get(ctx context.Context, username string) (*User, error) { var u User filter := bson.M{"_id": username} if !s.ReturnDeleted { filter["deleted"] = primitive.Null{} } if err := s.Db.FindOneByFilter(ctx, userCol, filter, &u); err != nil { return nil, err } return &u, nil } func (s *MongoDbUserStore) Put(ctx context.Context, u *User) error { if err := s.Db.ReplaceOneById(ctx, userCol, u.Username, u); err != nil { return err } return nil } func (s *MongoDbUserStore) Delete(ctx context.Context, u *User) error { u, err := s.Get(ctx, u.Username) if err != nil { return err } now := time.Now() u.Deleted = &now return s.Put(ctx, u) } var _ UserStore = (*MongoDbUserStore)(nil)