The codebase that powers boop.cat
boop.cat
1// Copyright 2025 boop.cat
2// Licensed under the Apache License, Version 2.0
3// See LICENSE file for details.
4
5package db
6
7import (
8 "crypto/sha256"
9 "database/sql"
10 "encoding/hex"
11 "time"
12)
13
14type APIKey struct {
15 ID string `json:"id"`
16 UserID string `json:"userId"`
17 Name string `json:"name"`
18 KeyHash string `json:"-"`
19 KeyPrefix string `json:"prefix"`
20 CreatedAt string `json:"createdAt"`
21 LastUsedAt sql.NullString `json:"lastUsedAt"`
22}
23
24type User struct {
25 ID string
26 Email string
27 Username sql.NullString
28 EmailVerified bool
29 Banned bool
30}
31
32func ListAPIKeys(db *sql.DB, userID string) ([]APIKey, error) {
33 rows, err := db.Query(`
34 SELECT id, userId, name, keyHash, keyPrefix, createdAt, lastUsedAt
35 FROM apiKeys WHERE userId = ?
36 `, userID)
37 if err != nil {
38 return nil, err
39 }
40 defer rows.Close()
41
42 var keys []APIKey
43 for rows.Next() {
44 var k APIKey
45 if err := rows.Scan(&k.ID, &k.UserID, &k.Name, &k.KeyHash, &k.KeyPrefix, &k.CreatedAt, &k.LastUsedAt); err != nil {
46 return nil, err
47 }
48 keys = append(keys, k)
49 }
50 return keys, rows.Err()
51}
52
53func CreateAPIKey(db *sql.DB, id, userID, name, keyHash, keyPrefix string) error {
54 _, err := db.Exec(`
55 INSERT INTO apiKeys (id, userId, name, keyHash, keyPrefix, createdAt, lastUsedAt)
56 VALUES (?, ?, ?, ?, ?, ?, NULL)
57 `, id, userID, name, keyHash, keyPrefix, time.Now().UTC().Format(time.RFC3339))
58 return err
59}
60
61func DeleteAPIKey(db *sql.DB, userID, keyID string) error {
62 result, err := db.Exec(`DELETE FROM apiKeys WHERE id = ? AND userId = ?`, keyID, userID)
63 if err != nil {
64 return err
65 }
66 rows, _ := result.RowsAffected()
67 if rows == 0 {
68 return sql.ErrNoRows
69 }
70 return nil
71}
72
73func CountAPIKeys(db *sql.DB, userID string) (int, error) {
74 var count int
75 err := db.QueryRow(`SELECT COUNT(*) FROM apiKeys WHERE userId = ?`, userID).Scan(&count)
76 return count, err
77}
78
79func ValidateAPIKey(db *sql.DB, key string) (*User, string, error) {
80
81 hash := sha256.Sum256([]byte(key))
82 keyHash := hex.EncodeToString(hash[:])
83
84 var apiKey APIKey
85 err := db.QueryRow(`
86 SELECT id, userId, name, keyHash, keyPrefix, createdAt, lastUsedAt
87 FROM apiKeys WHERE keyHash = ?
88 `, keyHash).Scan(&apiKey.ID, &apiKey.UserID, &apiKey.Name, &apiKey.KeyHash, &apiKey.KeyPrefix, &apiKey.CreatedAt, &apiKey.LastUsedAt)
89 if err != nil {
90 return nil, "", err
91 }
92
93 var user User
94 var emailVerified, banned int
95 err = db.QueryRow(`
96 SELECT id, email, username, emailVerified, banned
97 FROM users WHERE id = ?
98 `, apiKey.UserID).Scan(&user.ID, &user.Email, &user.Username, &emailVerified, &banned)
99 if err != nil {
100 return nil, "", err
101 }
102 user.EmailVerified = emailVerified != 0
103 user.Banned = banned != 0
104
105 if user.Banned {
106 return nil, "", sql.ErrNoRows
107 }
108 if !user.EmailVerified {
109 return nil, "", sql.ErrNoRows
110 }
111
112 _, _ = db.Exec(`UPDATE apiKeys SET lastUsedAt = ? WHERE id = ?`,
113 time.Now().UTC().Format(time.RFC3339), apiKey.ID)
114
115 return &user, apiKey.ID, nil
116}