1 // Package accesstoken provides storage and validation of Chain Core
5 // TODO(tessr): merge this package into chain/net/http/authn
15 "github.com/bytom/crypto/sha3pool"
16 // "chain/database/pg"
17 "github.com/bytom/errors"
26 // ErrBadID is returned when Create is called on an invalid id string.
27 ErrBadID = errors.New("invalid id")
28 // ErrDuplicateID is returned when Create is called on an existing ID.
29 ErrDuplicateID = errors.New("duplicate access token ID")
30 // ErrBadType is returned when Create is called with a bad type.
31 ErrBadType = errors.New("type must be client or network")
33 // validIDRegexp checks that all characters are alphumeric, _ or -.
34 // It also must have a length of at least 1.
35 validIDRegexp = regexp.MustCompile(`^[\w-]+$`)
40 Token string `json:"token,omitempty"`
41 Type string `json:"type,omitempty"` // deprecated in 1.2
42 Created time.Time `json:"created_at"`
46 type CredentialStore struct {
50 // Create generates a new access token with the given ID.
51 func (cs *Token) Create(ctx context.Context, id, typ string) (*Token, error) {
52 if !validIDRegexp.MatchString(id) {
53 return nil, errors.WithDetailf(ErrBadID, "invalid id %q", id)
56 var secret [tokenSize]byte
57 _, err := rand.Read(secret[:])
61 var hashedSecret [32]byte
62 sha3pool.Sum256(hashedSecret[:], secret[:])
65 INSERT INTO access_tokens (id, type, hashed_secret)
67 RETURNING created, sort_id
72 // maybeType = sql.NullString{String: typ, Valid: typ != ""}
74 /* err = cs.DB.QueryRowContext(ctx, q, id, maybeType, hashedSecret[:]).Scan(&created, &sortID)
75 if pg.IsUniqueViolation(err) {
76 return nil, errors.WithDetailf(ErrDuplicateID, "id %q already in use", id)
79 return nil, errors.Wrap(err)
84 Token: fmt.Sprintf("%s:%x", id, secret),
91 // Check returns whether or not an id-secret pair is a valid access token.
92 func (cs *Token) Check(ctx context.Context, id string, secret []byte) (bool, error) {
94 toHash [tokenSize]byte
97 copy(toHash[:], secret)
98 sha3pool.Sum256(hashed[:], toHash[:])
100 /*const q = `SELECT EXISTS(SELECT 1 FROM access_tokens WHERE id=$1 AND hashed_secret=$2)`
102 err := cs.DB.QueryRowContext(ctx, q, id, hashed[:]).Scan(&valid)
111 // Exists returns whether an id is part of a valid access token. It does not validate a secret.
112 func (cs *Token) Exists(ctx context.Context, id string) bool {
113 /* const q = `SELECT EXISTS(SELECT 1 FROM access_tokens WHERE id=$1)`
115 err := cs.DB.QueryRowContext(ctx, q, id).Scan(&valid)
124 // List lists all access tokens.
125 func (cs *Token) List(ctx context.Context, typ, after string, limit int) ([]*Token, string, error) {
130 SELECT id, type, sort_id, created FROM access_tokens
131 WHERE ($1='' OR type=$1::access_token_type) AND ($2='' OR sort_id<$2)
132 ORDER BY sort_id DESC
136 // err := pg.ForQueryRows(ctx, cs.DB, q, typ, after, limit, func(id string, maybeType sql.NullString, sortID string, created time.Time) {
140 Type: maybeType.String,
143 tokens = append(tokens, &t)
146 return nil, "", errors.Wrap(err)
152 next = tokens[len(tokens)-1].sortID
155 return tokens, next, nil
158 // Delete deletes an access token by id.
159 func (cs *Token) Delete(ctx context.Context, id string) error {
160 /* const q = `DELETE FROM access_tokens WHERE id=$1`
161 res, err := cs.DB.ExecContext(ctx, q, id)
163 return errors.Wrap(err)
166 /* deleted, err := res.RowsAffected()
168 return errors.Wrap(err)
172 return errors.WithDetailf(pg.ErrUserInputNotFound, "acccess token id %s", id)