OSDN Git Service

Merge branch 'dev' into dev-verify
[bytom/bytom.git] / accesstoken / accesstoken.go
1 // Package accesstoken provides storage and validation of Chain Core
2 // credentials.
3 package accesstoken
4
5 import (
6         "context"
7         "crypto/rand"
8         "encoding/json"
9         "fmt"
10         "regexp"
11         "strings"
12         "time"
13
14         dbm "github.com/tendermint/tmlibs/db"
15
16         "github.com/bytom/crypto/sha3pool"
17         "github.com/bytom/errors"
18 )
19
20 const tokenSize = 32
21
22 var (
23         // ErrBadID is returned when Create is called on an invalid id string.
24         ErrBadID = errors.New("invalid id")
25         // ErrDuplicateID is returned when Create is called on an existing ID.
26         ErrDuplicateID = errors.New("duplicate access token ID")
27         // ErrBadType is returned when Create is called with a bad type.
28         ErrBadType = errors.New("type must be client or network")
29         // ErrNoMatchID is returned when Delete is called on nonexisting ID.
30         ErrNoMatchID = errors.New("nonexisting access token ID")
31         // ErrInvalidToken is returned when Check is called on invalid token
32         ErrInvalidToken = errors.New("invalid token")
33
34         // validIDRegexp checks that all characters are alphumeric, _ or -.
35         // It also must have a length of at least 1.
36         validIDRegexp = regexp.MustCompile(`^[\w-]+$`)
37 )
38
39 // Token describe the access token.
40 type Token struct {
41         ID      string    `json:"id"`
42         Token   string    `json:"token,omitempty"`
43         Type    string    `json:"type,omitempty"`
44         Created time.Time `json:"created_at"`
45 }
46
47 // CredentialStore store user access credential.
48 type CredentialStore struct {
49         DB dbm.DB
50 }
51
52 // NewStore creates and returns a new Store object.
53 func NewStore(db dbm.DB) *CredentialStore {
54         return &CredentialStore{
55                 DB: db,
56         }
57 }
58
59 // Create generates a new access token with the given ID.
60 func (cs *CredentialStore) Create(ctx context.Context, id, typ string) (*Token, error) {
61         if !validIDRegexp.MatchString(id) {
62                 return nil, errors.WithDetailf(ErrBadID, "invalid id %q", id)
63         }
64
65         key := []byte(id)
66         if cs.DB.Get(key) != nil {
67                 return nil, errors.WithDetailf(ErrDuplicateID, "id %q already in use", id)
68         }
69
70         secret := make([]byte, tokenSize)
71         if _, err := rand.Read(secret); err != nil {
72                 return nil, err
73         }
74
75         hashedSecret := make([]byte, tokenSize)
76         sha3pool.Sum256(hashedSecret, secret)
77
78         token := &Token{
79                 ID:      id,
80                 Token:   fmt.Sprintf("%s:%x", id, hashedSecret),
81                 Type:    typ,
82                 Created: time.Now(),
83         }
84
85         value, err := json.Marshal(token)
86         if err != nil {
87                 return nil, err
88         }
89         cs.DB.Set(key, value)
90
91         return token, nil
92 }
93
94 // Check returns whether or not an id-secret pair is a valid access token.
95 func (cs *CredentialStore) Check(ctx context.Context, id string, secret string) error {
96         if !validIDRegexp.MatchString(id) {
97                 return errors.WithDetailf(ErrBadID, "invalid id %q", id)
98         }
99
100         var value []byte
101         token := &Token{}
102
103         if value = cs.DB.Get([]byte(id)); value == nil {
104                 return errors.WithDetailf(ErrNoMatchID, "check id %q nonexisting", id)
105         }
106         if err := json.Unmarshal(value, token); err != nil {
107                 return err
108         }
109
110         if strings.Split(token.Token, ":")[1] == secret {
111                 return nil
112         }
113
114         return ErrInvalidToken
115 }
116
117 // List lists all access tokens.
118 func (cs *CredentialStore) List(ctx context.Context) ([]*Token, error) {
119         tokens := make([]*Token, 0)
120         iter := cs.DB.Iterator()
121         defer iter.Release()
122
123         for iter.Next() {
124                 token := &Token{}
125                 if err := json.Unmarshal(iter.Value(), token); err != nil {
126                         return nil, err
127                 }
128                 tokens = append(tokens, token)
129         }
130         return tokens, nil
131 }
132
133 // Delete deletes an access token by id.
134 func (cs *CredentialStore) Delete(ctx context.Context, id string) error {
135         if !validIDRegexp.MatchString(id) {
136                 return errors.WithDetailf(ErrBadID, "invalid id %q", id)
137         }
138
139         if value := cs.DB.Get([]byte(id)); value == nil {
140                 return errors.WithDetailf(ErrNoMatchID, "check id %q", id)
141         }
142
143         cs.DB.Delete([]byte(id))
144         return nil
145 }