krz/gitbay

A CLI-first git forge.

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

main: internal/store/sessions.go · raw

 1package store
 2
 3import (
 4	"crypto/rand"
 5	"crypto/sha256"
 6	"database/sql"
 7	"encoding/hex"
 8	"errors"
 9	"time"
10)
11
12// NewToken returns a fresh random token and its storage hash. Only the hash
13// is persisted; the token itself goes to the user once.
14func NewToken() (token, hash string, err error) {
15	var b [32]byte
16	if _, err := rand.Read(b[:]); err != nil {
17		return "", "", err
18	}
19	token = hex.EncodeToString(b[:])
20	return token, HashToken(token), nil
21}
22
23func HashToken(token string) string {
24	sum := sha256.Sum256([]byte(token))
25	return hex.EncodeToString(sum[:])
26}
27
28func fmtTime(t time.Time) string { return t.UTC().Format("2006-01-02T15:04:05.000Z") }
29
30// CreateLoginToken stores a one-time login token hash.
31func (s *Store) CreateLoginToken(userID int64, hash string, ttl time.Duration) error {
32	_, err := s.DB.Exec(
33		"INSERT INTO login_tokens (token_hash, user_id, expires_at) VALUES (?, ?, ?)",
34		hash, userID, fmtTime(time.Now().Add(ttl)))
35	return err
36}
37
38// ConsumeLoginToken redeems a token exactly once; expired or used tokens
39// fail identically.
40func (s *Store) ConsumeLoginToken(hash string) (int64, error) {
41	res, err := s.DB.Exec(`
42		UPDATE login_tokens SET used_at = strftime('%Y-%m-%dT%H:%M:%fZ','now')
43		WHERE token_hash = ? AND used_at IS NULL AND expires_at > ?`,
44		hash, fmtTime(time.Now()))
45	if err != nil {
46		return 0, err
47	}
48	if n, _ := res.RowsAffected(); n == 0 {
49		return 0, ErrNotFound
50	}
51	var userID int64
52	err = s.DB.QueryRow("SELECT user_id FROM login_tokens WHERE token_hash = ?", hash).Scan(&userID)
53	return userID, err
54}
55
56func (s *Store) CreateWebSession(hash string, userID int64, ttl time.Duration) error {
57	_, err := s.DB.Exec(
58		"INSERT INTO web_sessions (token_hash, user_id, expires_at) VALUES (?, ?, ?)",
59		hash, userID, fmtTime(time.Now().Add(ttl)))
60	return err
61}
62
63// WebSessionUser resolves a session cookie hash to its user.
64func (s *Store) WebSessionUser(hash string) (User, error) {
65	var userID int64
66	err := s.DB.QueryRow(
67		"SELECT user_id FROM web_sessions WHERE token_hash = ? AND expires_at > ?",
68		hash, fmtTime(time.Now())).Scan(&userID)
69	if errors.Is(err, sql.ErrNoRows) {
70		return User{}, ErrNotFound
71	}
72	if err != nil {
73		return User{}, err
74	}
75	return s.UserByID(userID)
76}
77
78func (s *Store) DeleteWebSession(hash string) error {
79	_, err := s.DB.Exec("DELETE FROM web_sessions WHERE token_hash = ?", hash)
80	return err
81}