krz/gitbay

A CLI-first git forge.

clone: git clone https://gitbay.org/krz/gitbay.git

main: 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}