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}