krz/gitbay
A CLI-first git forge.
clone: git clone https://gitbay.org/krz/gitbay.git
repo-descriptions: internal/store/tokens.go · raw
1package store
2
3import (
4 "database/sql"
5 "errors"
6 "fmt"
7 "time"
8)
9
10type APIToken struct {
11 Name string
12 Scope string
13 CreatedAt string
14 ExpiresAt *time.Time
15 LastUsedAt *time.Time
16}
17
18// CreateAPIToken stores a token hash; expires nil means no expiry.
19func (s *Store) CreateAPIToken(userID int64, name, tokenHash, scope string, expires *time.Time) error {
20 var exp any
21 if expires != nil {
22 exp = fmtTime(*expires)
23 }
24 _, err := s.DB.Exec(
25 "INSERT INTO api_tokens (user_id, name, token_hash, scope, expires_at) VALUES (?, ?, ?, ?, ?)",
26 userID, name, tokenHash, scope, exp)
27 if isUniqueErr(err) {
28 return fmt.Errorf("you already have a token named %q", name)
29 }
30 return err
31}
32
33// APITokenUser resolves a presented token to its user and scope; expired and
34// unknown tokens fail identically.
35func (s *Store) APITokenUser(tokenHash string) (User, string, error) {
36 var userID int64
37 var scope string
38 err := s.DB.QueryRow(`
39 SELECT user_id, scope FROM api_tokens
40 WHERE token_hash = ? AND (expires_at IS NULL OR expires_at > ?)`,
41 tokenHash, fmtTime(time.Now())).Scan(&userID, &scope)
42 if errors.Is(err, sql.ErrNoRows) {
43 return User{}, "", ErrNotFound
44 }
45 if err != nil {
46 return User{}, "", err
47 }
48 s.DB.Exec("UPDATE api_tokens SET last_used_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE token_hash = ?", tokenHash)
49 u, err := s.UserByID(userID)
50 return u, scope, err
51}
52
53func (s *Store) ListAPITokens(userID int64) ([]APIToken, error) {
54 rows, err := s.DB.Query(
55 "SELECT name, scope, created_at, expires_at, last_used_at FROM api_tokens WHERE user_id = ? ORDER BY name", userID)
56 if err != nil {
57 return nil, err
58 }
59 defer rows.Close()
60 var out []APIToken
61 for rows.Next() {
62 var t APIToken
63 var exp, used sql.NullString
64 if err := rows.Scan(&t.Name, &t.Scope, &t.CreatedAt, &exp, &used); err != nil {
65 return nil, err
66 }
67 t.ExpiresAt = parseTime(exp)
68 t.LastUsedAt = parseTime(used)
69 out = append(out, t)
70 }
71 return out, rows.Err()
72}
73
74func (s *Store) RevokeAPIToken(userID int64, name string) error {
75 res, err := s.DB.Exec("DELETE FROM api_tokens WHERE user_id = ? AND name = ?", userID, name)
76 if err != nil {
77 return err
78 }
79 if n, _ := res.RowsAffected(); n == 0 {
80 return ErrNotFound
81 }
82 return nil
83}