1package core
2
3import (
4 "crypto/sha256"
5 "errors"
6 "fmt"
7 "regexp"
8 "sort"
9 "strings"
10 "time"
11
12 "github.com/MichaelMure/git-bug/entity"
13 "github.com/MichaelMure/git-bug/repository"
14)
15
16const (
17 tokenConfigKeyPrefix = "git-bug.token"
18 tokenValueKey = "value"
19 tokenTargetKey = "target"
20 tokenCreateTimeKey = "createtime"
21)
22
23var ErrTokenNotExist = errors.New("token doesn't exist")
24
25func NewErrMultipleMatchToken(matching []entity.Id) *entity.ErrMultipleMatch {
26 return entity.NewErrMultipleMatch("token", matching)
27}
28
29// Token holds an API access token data
30type Token struct {
31 Value string
32 Target string
33 CreateTime time.Time
34}
35
36// NewToken instantiate a new token
37func NewToken(value, target string) *Token {
38 return &Token{
39 Value: value,
40 Target: target,
41 CreateTime: time.Now(),
42 }
43}
44
45func (t *Token) ID() entity.Id {
46 sum := sha256.Sum256([]byte(t.Target + t.Value))
47 return entity.Id(fmt.Sprintf("%x", sum))
48}
49
50// Validate ensure token important fields are valid
51func (t *Token) Validate() error {
52 if t.Value == "" {
53 return fmt.Errorf("missing value")
54 }
55 if t.Target == "" {
56 return fmt.Errorf("missing target")
57 }
58 if t.CreateTime.IsZero() || t.CreateTime.Equal(time.Time{}) {
59 return fmt.Errorf("missing creation time")
60 }
61 if !TargetExist(t.Target) {
62 return fmt.Errorf("unknown target")
63 }
64 return nil
65}
66
67// LoadToken loads a token from the repo config
68func LoadToken(repo repository.RepoCommon, id entity.Id) (*Token, error) {
69 keyPrefix := fmt.Sprintf("git-bug.token.%s.", id)
70
71 // read token config pairs
72 rawconfigs, err := repo.GlobalConfig().ReadAll(keyPrefix)
73 if err != nil {
74 // Not exactly right due to the limitation of ReadAll()
75 return nil, ErrTokenNotExist
76 }
77
78 // trim key prefix
79 configs := make(map[string]string)
80 for key, value := range rawconfigs {
81 newKey := strings.TrimPrefix(key, keyPrefix)
82 configs[newKey] = value
83 }
84
85 token := &Token{}
86
87 token.Value = configs[tokenValueKey]
88 token.Target = configs[tokenTargetKey]
89 if createTime, ok := configs[tokenCreateTimeKey]; ok {
90 if t, err := repository.ParseTimestamp(createTime); err == nil {
91 token.CreateTime = t
92 }
93 }
94
95 return token, nil
96}
97
98// LoadTokenPrefix load a token from the repo config with a prefix
99func LoadTokenPrefix(repo repository.RepoCommon, prefix string) (*Token, error) {
100 tokens, err := ListTokens(repo)
101 if err != nil {
102 return nil, err
103 }
104
105 // preallocate but empty
106 matching := make([]entity.Id, 0, 5)
107
108 for _, id := range tokens {
109 if id.HasPrefix(prefix) {
110 matching = append(matching, id)
111 }
112 }
113
114 if len(matching) > 1 {
115 return nil, NewErrMultipleMatchToken(matching)
116 }
117
118 if len(matching) == 0 {
119 return nil, ErrTokenNotExist
120 }
121
122 return LoadToken(repo, matching[0])
123}
124
125// ListTokens return a map representing the stored tokens in the repo config and global config
126// along with their type (global: true, local:false)
127func ListTokens(repo repository.RepoCommon) ([]entity.Id, error) {
128 configs, err := repo.GlobalConfig().ReadAll(tokenConfigKeyPrefix + ".")
129 if err != nil {
130 return nil, err
131 }
132
133 re, err := regexp.Compile(tokenConfigKeyPrefix + `.([^.]+)`)
134 if err != nil {
135 panic(err)
136 }
137
138 set := make(map[string]interface{})
139
140 for key := range configs {
141 res := re.FindStringSubmatch(key)
142
143 if res == nil {
144 continue
145 }
146
147 set[res[1]] = nil
148 }
149
150 result := make([]entity.Id, 0, len(set))
151 for key := range set {
152 result = append(result, entity.Id(key))
153 }
154
155 sort.Sort(entity.Alphabetical(result))
156
157 return result, nil
158}
159
160// StoreToken stores a token in the repo config
161func StoreToken(repo repository.RepoCommon, token *Token) error {
162 storeValueKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenValueKey)
163 err := repo.GlobalConfig().StoreString(storeValueKey, token.Value)
164 if err != nil {
165 return err
166 }
167
168 storeTargetKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenTargetKey)
169 err = repo.GlobalConfig().StoreString(storeTargetKey, token.Target)
170 if err != nil {
171 return err
172 }
173
174 createTimeKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenCreateTimeKey)
175 return repo.GlobalConfig().StoreTimestamp(createTimeKey, token.CreateTime)
176}
177
178// RemoveToken removes a token from the repo config
179func RemoveToken(repo repository.RepoCommon, id entity.Id) error {
180 keyPrefix := fmt.Sprintf("git-bug.token.%s", id)
181 return repo.GlobalConfig().RemoveAll(keyPrefix)
182}