aboutsummaryrefslogtreecommitdiff
path: root/app/models/user.go
blob: 2871380073af7f11f705e90529c12fe83781bf7f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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)